Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: manual autoplay #5209

Merged
merged 15 commits into from
Jun 21, 2018
Merged

feat: manual autoplay #5209

merged 15 commits into from
Jun 21, 2018

Conversation

brandonocasey
Copy link
Contributor

@brandonocasey brandonocasey commented May 24, 2018

Due to some of the policy changes around autoplay we are going to add a new option. The name of the option and the parameters that the option can be can change in this pr, but I think the general functionality is going to remain the same. Basically you have three paths

  1. Try to autoplay using play and if that doesn't work do nothing, mostly the same as using the autoplay attribute right now.
  2. Try to mute and then autoplay using play, this will work in most cases
  3. Try to autoplay using play and if that doesnt work try to mute and then autoplay, which we know will work.

Questions:

  • Should we remove option 2 and just recommending using autoplay in that case?
  • Should we default to using option 3?
  • Should we just use the autoplay attribute and accept a string?

Coming Soon

  • Tests
  • Documentation updates

src/js/player.js Outdated
// - truthy value: will just call play
// - any: Will call play and if the promise fails, it will try to mute
// and then play
if (this.options_.manualAutoplay) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that maybe autoplay is best and have these options:

  • muted - will mute the video and then play
  • manual - will call play manually
  • any - (or maybe another name?) try to play unmuted and then mute if it fails
  • any other value do current behavior

then the autoplay getter and setter can also handle this whenever it gets called and the autoplay getter can know whether this option was set and which option.

then this line will just turn to something like this.autoplay(this.options_.autoplay) or something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize that this overloads the values for the autoplay option, but it really is the best name and we can always deprecate the old values and go forward with just the new stuff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, we could theoretically, have the setter return the play promise in that way if called later on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK we are going to go this route, let me re-work this pr

@marguinbc
Copy link
Contributor

Late to the comment party but I feel like the 'new autoplay' should have states/settings that correspond to the browser options in those that allow users to choose autoplay options. Something like:

  • never (results in click-to-play)
  • always (same as any - tries w/ sound, then muted)
  • muted (same as muted above)

I'm not sure about manual because I feel always and muted would call play() manually anyway but I see no harm in keeping it and having it return the play promise

  • manual (calls play() and returns play promise?)

And any other value defaults to current behavior...

Copy link
Member

@misteroneill misteroneill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good. Could probably use some JSDoc and documentation updates, though.

@misteroneill
Copy link
Member

@marguinbc always doesn't make sense, though, because we can never guarantee that autoplay will work. I don't see why having a never option would make sense - that's just the default (no autoplay of any type).

Also, we can't return the play promise with these because it happens in a loadstart listener.

@marguinbc
Copy link
Contributor

I suppose any is a better name than always then as they are basically doing the same thing and always implies we can always autoplay, vs any implies "any autoplay that will work".

I see never as being a way to override the autoplay attribute or rather explicitly set it to none as @gkatsev's comment earlier said

any other value do current behavior

Which implies none/default = current behavior and would be dependent on whether the autoplay attribute is set or not.

Fair point on the promise... should have looked a the code.

@brandonocasey brandonocasey force-pushed the feat/manual-autoplay branch from 4440097 to a99a1de Compare May 25, 2018 17:55
Do to recent changes in `autoplay` behavior we no longer recommend using the `autoplay` attribute
on the video element. It's still supported by video.js but many browsers, including Chrome, are changing their
`autoplay` attribute behavior.
For more info on that see: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to link to our blogpost :)

> **Note:** As of iOS 10, Apple offers `autoplay` support in Safari. For details, refer to ["New <video> Policies for iOS"][ios-10-updates].
#### More info on autoplay support and changes:
* As of iOS 10, Apple offers `autoplay` support in Safari. For details, refer to ["New <video> Policies for iOS"][ios-10-updates].
* The autoplay policy in Chrome is changing: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blogpost again

@@ -379,6 +379,13 @@ class Player extends Component {
tag.controls = false;
tag.removeAttribute('controls');

// the attribute overrides the option
if (tag.hasAttribute('autoplay')) {
this.options_.autoplay = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is so that the following case is covered?

<video autoplay data-setup='{"autoplay": "play"}'>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, basically we would autoplay: true here and not autoplay: play

src/js/player.js Outdated
// Grab tech-specific options from player options and add source and parent element to use.
const techOptions = {
source,
'nativeControlsForTouch': this.options_.nativeControlsForTouch,
'playerId': this.id(),
'techId': `${this.id()}_${titleTechName}_api`,
'autoplay': this.options_.autoplay,
autoplay,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should move this up next to source, so, the object is consistent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

src/js/player.js Outdated
* @param {boolean|string} [value]
* - true: autoplay using the browser behavior
* - false: do not autoplay
* - See `manualAutoplay_` for valid string values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best to enumerate the options here instead of in manualAutoplay_

src/js/player.js Outdated
// a setAutoplay call. This option will be passed
// in the constructor for the tech
if (this.tech_) {
this.techCall_('setAutoplay', techAutoplay);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only want to do this if autoplay is boolean otherwise, it will add the autoplay attribute in the html5 tech which we don't want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I did this because in the case where autoplay is currently true on the tech, we want to set it to false, maybe that doesn't remove the attribute like I think that it should, I will test this out as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the attribute will be removed by setting the property to false. I think we do want this.

src/js/player.js Outdated
this.options_.autoplay = value;
// any other value sets autoplay to true
} else {
this.options_.autoplay = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably fine, alternatively, we can cast the value into a boolean and use that instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me see what the video element does, I think any value on autoplay is treated as true.

Copy link
Contributor Author

@brandonocasey brandonocasey May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so it seems that the browsers cast it to a bool, so I will change that here.

src/js/player.js Outdated
this.options_.autoplay = true;
}

techAutoplay = techAutoplay || value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be techAutoplay || this.options_.autoplay?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah

@@ -1904,3 +1904,253 @@ QUnit.test('disposing a tech that dit NOT set a poster, should keep the poster',

player.dispose();
});

QUnit.module('Player autoplay', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better to move this into a separate file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I will do that.

let techAutoplay;

// if the value is a valid string set it to that
if (typeof value === 'string' && (/(any|play|muted)/).test(value)) {
this.options_.autoplay = value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we change how we cache this value? i.e., switch to using this.cache_.autoplay instead and keep the options autoplay what it was initially?
We can also keep updating the option but use the cache until a breaking change

Copy link
Contributor Author

@brandonocasey brandonocasey May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that autoplay is actually an option, and every time we have to switch techs we have to pass it to the tech as such. I see the cache as more of a cache of the techs value on our end so maybe we should cache something like techAutoplay_?. I think autoplay as a whole is more of an option, but I do see the value in being able to see what the tech has autoplay set to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically, I think we should keep this.options_ set to whatever is the initial set of options passed it and use other places to cache transient or instance properties. However, I don't think this is necessarily something for this PR.

@brandonocasey brandonocasey force-pushed the feat/manual-autoplay branch from 4c28774 to c8d2dba Compare May 30, 2018 18:54
Copy link
Member

@misteroneill misteroneill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks solid to me. Some documentation cleanup, though.

```html
<video autoplay controls class="video-js">
```
Do to recent changes in `autoplay` behavior we no longer recommend using the `autoplay` attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Do -> Due
  • I don't think the first autoplay should use backticks. It's not referencing the attribute or any code, but the behavior.

<video autoplay controls class="video-js">
```
Do to recent changes in `autoplay` behavior we no longer recommend using the `autoplay` attribute
on the video element. It's still supported by video.js but many browsers, including Chrome, are changing their
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • video should probably have backticks as it refers to the element.
  • video.js should be capitalized.
  • but should be preceded by a comma.


player.autoplay(true);
```
Instead we recommend using the `autoplay` option rather than the `autoplay` attribute. For more information on using that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The period at the end should be a comma.

player.autoplay(true);
```
Instead we recommend using the `autoplay` option rather than the `autoplay` attribute. For more information on using that.
See the [autoplay option][autoplay-option] in the videojs options guide.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • See should be see
  • videojs should be Video.js

```html
<video muted autoplay playsinline>
```
We do not recommend doing this manually using attributes on the video element. Instead you should pass the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • video should probably have backticks as it refers to the element.
  • Instead should be followed by a comma.


Will make an inline, muted, autoplaying video on an iPhone with iOS10.
> NOTE: At this point the autoplay attribute and option are NOT a guarantee that your video will autoplay.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • At this point should be followed by a comma.

@@ -53,11 +53,34 @@ Each of these options is also available as a [standard `<video>` element attribu

### `autoplay`

> Type: `boolean`
> Type: `boolean|string`
> NOTE: At this point the autoplay attribute and option are NOT a guarantee that your video will autoplay.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • At this point should be followed by a comma.

> NOTE2: If there is an attribute on the media element the option will be ignored.
> NOTE3: You cannot pass a string value in the attribute, you must pass it in the videojs options

Instead of using the `autoplay` attribute you should pass an `autoplay` option to videojs. The following values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think videojs should have backticks here as it refers to the function rather than the library.

Copy link
Contributor

@mister-ben mister-ben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Knowing whether autoplay succeeded and whether muting was necessary will be important for some use cases (analytics, UX changes). Should there be an event or a property with this info for convenience?

var player = videojs('my-video', {
autoplay: true
});
For more information on the autoplay changes see our blog post: https://blog.videojs.com/autoplay-best-practices-with-video-js/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this section flows better with this line at the end.

player.autoplay(true);
```
Instead we recommend using the `autoplay` option rather than the `autoplay` attribute, for more information on using that.
see the [autoplay option][autoplay-option] in the Video.js options guide.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no . after that

src/js/player.js Outdated
this.techCall_('setAutoplay', value);
// getter usage
if (value === undefined) {
return this.options_.autoplay;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns undefined rather than false if not configured.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call

@brandonocasey
Copy link
Contributor Author

hmmm yeah it could be a good idea to have something like manual-autoplay-success and manual-autoplay-failure. Thoughts @gkatsev ?

@brandonocasey
Copy link
Contributor Author

brandonocasey commented Jun 4, 2018

On thinking about this some more, I think we may want to do this for play in general. We might want to have play-fulfilled, play-rejected, and play-pending events. Naming is based on the states specified in the promise specification.

@misteroneill
Copy link
Member

I think that idea has merit for sure. Maybe a property as well, e.g. lastPlayState() that returns a pseudo-symbol like videojs.[FULFILLED|REJECTED|PENDING]? Thinking out loud here.

let techAutoplay;

// if the value is a valid string set it to that
if (typeof value === 'string' && (/(any|play|muted)/).test(value)) {
this.options_.autoplay = value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically, I think we should keep this.options_ set to whatever is the initial set of options passed it and use other places to cache transient or instance properties. However, I don't think this is necessarily something for this PR.

let techAutoplay;

// if the value is a valid string set it to that
if (typeof value === 'string' && (/(any|play|muted)/).test(value)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we call this.manualAutoplay_ in here? Use case would be something like:

var player = videojs('vid');

player.autoplay('any');

I'd expect the player to autoplay in this case. This would also match current behavior (in browsers that support autoplay). Main risk is that we'd want to be able to do something like the following too

var player = videojs('vid', { autoplay: 'any' );

player.autoplay(false);

and have it not autoplay.

Also, should we have a string state "off" and deprecate boolean values for it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just do this in a followup PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should player.autoplay('any') also return a promise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably just want events rather than a promise

@gkatsev
Copy link
Member

gkatsev commented Jun 4, 2018

Autoplay events probably make sense. We probably can have just two events autoplay-success and autoplay-failure and then have extra data on the event object or the data object for events (secondary arg) provide info on autoplay configuration.

I'm hesitant to map the play promise into events unless we have an actual use-case. Most of what we talked about it has been theoretical so far. This is especially since we have moved towards recommending people call play themselves and thus they will generally have a Promise available to them directly. We probably want to resurrect #3927 and actually implement it so that player.promise always returns a promise.

@brandonocasey
Copy link
Contributor Author

See #5227

@brandonocasey brandonocasey force-pushed the feat/manual-autoplay branch from 3565859 to e74c1fd Compare June 4, 2018 20:33
src/js/player.js Outdated
// if we are muted or not set to any here there is nothing we can do
if (type !== 'any' || this.muted()) {
this.trigger('autoplay-failure');
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we've muted the player and the autoplay still fails, shouldn't we unmute again so a subsequent play triggered by a human has sound?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds reasonable to me.

src/js/player.js Outdated
}

playPromise.then(() => {
this.trigger('autoplay-success');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add the autoplay value to the event or are we expecting users to call player.autoplay() to get the current value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can add the autoplay value to the event

src/js/player.js Outdated
// if we are muted or not set to any here there is nothing we can do
if (type !== 'any' || this.muted()) {
this.trigger('autoplay-failure');
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds reasonable to me.

@gkatsev gkatsev merged commit e8e4fe2 into master Jun 21, 2018
@gkatsev gkatsev deleted the feat/manual-autoplay branch June 21, 2018 18:32
gkatsev pushed a commit that referenced this pull request Jul 9, 2018
This PR extends the `autoplay` to the player with a few options that should hopefully make working with browsers that disable unmuted autoplay by default easier.
The current boolean option will match current behavior and any unknown option will be treated as it does now. The new options are the string values `muted`, `play`, and `any`.

- `muted` will mute the element and call `play()` on `loadstart`,
- `play` will call `play()` on `loadstart()`, this is similar to the `autoplay` attribute
- `any` will call `play()` on `loadstart()` but if it fails it will try muting the video and calling `play()` again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants