mirror of
https://github.com/videojs/video.js.git
synced 2025-03-03 15:12:49 +02:00
feat(autoplay): extend autoplay option for greater good (#5209)
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.
This commit is contained in:
parent
edce736d18
commit
e8e4fe2745
@ -134,24 +134,17 @@ techs/plugins made available to Video.js. For more information on media formats
|
||||
|
||||
When an array of sources is available, Video.js test each source in the order given. For each source, each tech in the [`techOrder`][techorder] will be checked to see if it can play it whether directly or via source handler (such as videojs-contrib-hls). The first match will be chosen.
|
||||
|
||||
## Q: How to I autoplay the video?
|
||||
## Q: How do I autoplay a video?
|
||||
|
||||
Video.js supports the standard html5 `autoplay` attribute on the video element.
|
||||
It also supports it as an option to Video.js or as a method invocation on the player.
|
||||
|
||||
```html
|
||||
<video autoplay controls class="video-js">
|
||||
```
|
||||
Due 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.
|
||||
|
||||
```js
|
||||
var player = videojs('my-video', {
|
||||
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.
|
||||
|
||||
// or
|
||||
|
||||
player.autoplay(true);
|
||||
```
|
||||
For more information on the autoplay changes see our blog post: https://blog.videojs.com/autoplay-best-practices-with-video-js/
|
||||
|
||||
### Q: How can I autoplay a video on a mobile device?
|
||||
|
||||
@ -159,13 +152,12 @@ Most mobile devices have blocked autoplaying videos until recently.
|
||||
For mobile devices that don't support autoplaying, autoplay isn't supported by Video.js.
|
||||
For those devices that support autoplaying, like iOS10 and Chrome for Android 53+,
|
||||
you must mute the video or have a video without audio tracks to be able to play it.
|
||||
For example:
|
||||
|
||||
```html
|
||||
<video muted autoplay playsinline>
|
||||
```
|
||||
We do not recommend doing this manually using attributes on the `video` element. Instead, you should pass the
|
||||
[autoplay option][autoplay-option] with a value of `'any'` or `'muted'`. See the previous link for more information
|
||||
on using that option.
|
||||
|
||||
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.
|
||||
|
||||
## Q: How can I play RTMP video in Video.js?
|
||||
|
||||
|
@ -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.
|
||||
> 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
|
||||
|
||||
If `true`/present as an attribute, begins playback when the player is ready.
|
||||
Instead of using the `autoplay` attribute you should pass an `autoplay` option to the `videojs` function. The following values
|
||||
are valid:
|
||||
|
||||
> **Note:** As of iOS 10, Apple offers `autoplay` support in Safari. For details, refer to ["New <video> Policies for iOS"][ios-10-updates].
|
||||
* a boolean value of `false`: the same as having no attribute on the video element, won't `autoplay`
|
||||
* a boolean value of `true`: the same as having attribute on the video element, will use browsers `autoplay`
|
||||
* a string value of `'muted'`: will mute the video element and then manually call `play()` on `loadstart`. This is likely to work.
|
||||
* a string value of `'play'`: will call `play()` on `loadstart`, similar to browsers `autoplay`
|
||||
* a string value of `'any'`: will call `play()` on `loadstart` and if the promise is rejected it will mute the video element then call `play()`.
|
||||
|
||||
To pass the option
|
||||
|
||||
```js
|
||||
var player = videojs('my-video', {
|
||||
autoplay: 'muted'
|
||||
});
|
||||
|
||||
// or
|
||||
|
||||
player.autoplay('muted');
|
||||
```
|
||||
|
||||
#### More info on autoplay support and changes:
|
||||
* See our blog post: https://blog.videojs.com/autoplay-best-practices-with-video-js/
|
||||
|
||||
### `controls`
|
||||
|
||||
|
122
src/js/player.js
122
src/js/player.js
@ -379,6 +379,15 @@ class Player extends Component {
|
||||
tag.controls = false;
|
||||
tag.removeAttribute('controls');
|
||||
|
||||
// the attribute overrides the option
|
||||
if (tag.hasAttribute('autoplay')) {
|
||||
this.options_.autoplay = true;
|
||||
} else {
|
||||
// otherwise use the setter to validate and
|
||||
// set the correct value.
|
||||
this.autoplay(this.options_.autoplay);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the internal state of scrubbing
|
||||
*
|
||||
@ -926,13 +935,17 @@ class Player extends Component {
|
||||
// Turn off API access because we're loading a new tech that might load asynchronously
|
||||
this.isReady_ = false;
|
||||
|
||||
// if autoplay is a string we pass false to the tech
|
||||
// because the player is going to handle autoplay on `loadstart`
|
||||
const autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay();
|
||||
|
||||
// Grab tech-specific options from player options and add source and parent element to use.
|
||||
const techOptions = {
|
||||
source,
|
||||
autoplay,
|
||||
'nativeControlsForTouch': this.options_.nativeControlsForTouch,
|
||||
'playerId': this.id(),
|
||||
'techId': `${this.id()}_${titleTechName}_api`,
|
||||
'autoplay': this.options_.autoplay,
|
||||
'playsinline': this.options_.playsinline,
|
||||
'preload': this.options_.preload,
|
||||
'loop': this.options_.loop,
|
||||
@ -1208,6 +1221,61 @@ class Player extends Component {
|
||||
this.hasStarted(false);
|
||||
this.trigger('loadstart');
|
||||
}
|
||||
|
||||
// autoplay happens after loadstart for the browser,
|
||||
// so we mimic that behavior
|
||||
this.manualAutoplay_(this.autoplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle autoplay string values, rather than the typical boolean
|
||||
* values that should be handled by the tech. Note that this is not
|
||||
* part of any specification. Valid values and what they do can be
|
||||
* found on the autoplay getter at Player#autoplay()
|
||||
*/
|
||||
manualAutoplay_(type) {
|
||||
if (!this.tech_ || typeof type !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
const muted = () => {
|
||||
const previouslyMuted = this.muted();
|
||||
|
||||
this.muted(true);
|
||||
|
||||
const playPromise = this.play();
|
||||
|
||||
if (!playPromise || !playPromise.then || !playPromise.catch) {
|
||||
return;
|
||||
}
|
||||
|
||||
return playPromise.catch((e) => {
|
||||
// restore old value of muted on failure
|
||||
this.muted(previouslyMuted);
|
||||
});
|
||||
};
|
||||
|
||||
let promise;
|
||||
|
||||
if (type === 'any') {
|
||||
promise = this.play();
|
||||
|
||||
if (promise && promise.then && promise.catch) {
|
||||
promise.catch(() => {
|
||||
return muted();
|
||||
});
|
||||
}
|
||||
} else if (type === 'muted') {
|
||||
promise = muted();
|
||||
} else {
|
||||
promise = this.play();
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
this.trigger({type: 'autoplay-success', autoplay: type});
|
||||
}).catch((e) => {
|
||||
this.trigger({type: 'autoplay-failure', autoplay: type});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2747,22 +2815,54 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the autoplay attribute.
|
||||
* Get or set the autoplay option. When this is a boolean it will
|
||||
* modify the attribute on the tech. When this is a string the attribute on
|
||||
* the tech will be removed and `Player` will handle autoplay on loadstarts.
|
||||
*
|
||||
* @param {boolean} [value]
|
||||
* - true means that we should autoplay
|
||||
* - false means that we should not autoplay
|
||||
* @param {boolean|string} [value]
|
||||
* - true: autoplay using the browser behavior
|
||||
* - false: do not autoplay
|
||||
* - 'play': call play() on every loadstart
|
||||
* - 'muted': call muted() then play() on every loadstart
|
||||
* - 'any': call play() on every loadstart. if that fails call muted() then play().
|
||||
* - *: values other than those listed here will be set `autoplay` to true
|
||||
*
|
||||
* @return {string}
|
||||
* @return {boolean|string}
|
||||
* The current value of autoplay when getting
|
||||
*/
|
||||
autoplay(value) {
|
||||
if (value !== undefined) {
|
||||
this.techCall_('setAutoplay', value);
|
||||
this.options_.autoplay = value;
|
||||
return;
|
||||
// getter usage
|
||||
if (value === undefined) {
|
||||
return this.options_.autoplay || false;
|
||||
}
|
||||
|
||||
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;
|
||||
this.manualAutoplay_(value);
|
||||
techAutoplay = false;
|
||||
|
||||
// any falsy value sets autoplay to false in the browser,
|
||||
// lets do the same
|
||||
} else if (!value) {
|
||||
this.options_.autoplay = false;
|
||||
|
||||
// any other value (ie truthy) sets autoplay to true
|
||||
} else {
|
||||
this.options_.autoplay = true;
|
||||
}
|
||||
|
||||
techAutoplay = techAutoplay || this.options_.autoplay;
|
||||
|
||||
// if we don't have a tech then we do not queue up
|
||||
// a setAutoplay call on tech ready. We do this because the
|
||||
// autoplay option will be passed in the constructor and we
|
||||
// do not need to set it twice
|
||||
if (this.tech_) {
|
||||
this.techCall_('setAutoplay', techAutoplay);
|
||||
}
|
||||
return this.techGet_('autoplay', value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
273
test/unit/autoplay.test.js
Normal file
273
test/unit/autoplay.test.js
Normal file
@ -0,0 +1,273 @@
|
||||
/* eslint-env qunit */
|
||||
import Player from '../../src/js/player.js';
|
||||
import videojs from '../../src/js/video.js';
|
||||
import TestHelpers from './test-helpers.js';
|
||||
import document from 'global/document';
|
||||
import sinon from 'sinon';
|
||||
|
||||
QUnit.module('autoplay', {
|
||||
beforeEach() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
// reset players storage
|
||||
for (const playerId in Player.players) {
|
||||
if (Player.players[playerId] !== null) {
|
||||
Player.players[playerId].dispose();
|
||||
}
|
||||
delete Player.players[playerId];
|
||||
}
|
||||
|
||||
const videoTag = TestHelpers.makeTag();
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
this.counts = {
|
||||
play: 0,
|
||||
muted: 0
|
||||
};
|
||||
|
||||
fixture.appendChild(videoTag);
|
||||
|
||||
// this promise fake will act right away
|
||||
// it will also only act on catch calls
|
||||
this.rejectPromise = {
|
||||
then(fn) {
|
||||
return this;
|
||||
},
|
||||
catch(fn) {
|
||||
fn();
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
this.createPlayer = (options = {}, attributes = {}, playRetval = null) => {
|
||||
Object.keys(attributes).forEach((a) => {
|
||||
videoTag.setAttribute(a, attributes[a]);
|
||||
});
|
||||
|
||||
this.player = videojs(videoTag.id, videojs.mergeOptions({techOrder: ['techFaker']}, options));
|
||||
const oldMuted = this.player.muted;
|
||||
|
||||
this.player.play = () => {
|
||||
this.counts.play++;
|
||||
|
||||
if (playRetval) {
|
||||
return playRetval;
|
||||
}
|
||||
};
|
||||
|
||||
this.player.muted = (v) => {
|
||||
|
||||
if (typeof v !== 'undefined') {
|
||||
this.counts.muted++;
|
||||
}
|
||||
|
||||
return oldMuted.call(this.player, v);
|
||||
};
|
||||
|
||||
// we have to trigger ready so that we
|
||||
// are waiting for loadstart
|
||||
this.player.tech_.triggerReady();
|
||||
return this.player;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
this.clock.restore();
|
||||
this.player.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('option = false no play/muted', function(assert) {
|
||||
this.createPlayer({autoplay: false});
|
||||
|
||||
assert.equal(this.player.autoplay(), false, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = true no play/muted', function(assert) {
|
||||
this.createPlayer({autoplay: true});
|
||||
|
||||
assert.equal(this.player.autoplay(), true, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), true, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "random" no play/muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'random'});
|
||||
|
||||
assert.equal(this.player.autoplay(), true, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), true, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = null, should be set to false no play/muted', function(assert) {
|
||||
this.createPlayer({autoplay: null});
|
||||
|
||||
assert.equal(this.player.autoplay(), false, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('options = "play" play, no muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'play'});
|
||||
|
||||
assert.equal(this.player.autoplay(), 'play', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any" play, no muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'any'});
|
||||
|
||||
assert.equal(this.player.autoplay(), 'any', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted" play and muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'muted'});
|
||||
|
||||
assert.equal(this.player.autoplay(), 'muted', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 1, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "play" play, no muted, rejection ignored', function(assert) {
|
||||
this.createPlayer({autoplay: 'play'}, {}, this.rejectPromise);
|
||||
|
||||
assert.equal(this.player.autoplay(), 'play', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any" play, no muted, rejection leads to muted then play', function(assert) {
|
||||
this.createPlayer({autoplay: 'any'}, {}, this.rejectPromise);
|
||||
|
||||
assert.equal(this.player.autoplay(), 'any', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
// muted called twice here, as muted is value is restored on failure.
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 4, 'play count');
|
||||
assert.equal(this.counts.muted, 4, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted" play and muted, rejection ignored', function(assert) {
|
||||
this.createPlayer({autoplay: 'muted'}, {}, this.rejectPromise);
|
||||
|
||||
assert.equal(this.player.autoplay(), 'muted', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
|
||||
// muted called twice here, as muted is value is restored on failure.
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 4, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted", attr = true, play and muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'muted'}, {autoplay: true});
|
||||
|
||||
assert.equal(this.player.autoplay(), true, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), true, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "play", attr = true, play only', function(assert) {
|
||||
this.createPlayer({autoplay: 'play'}, {autoplay: true});
|
||||
|
||||
assert.equal(this.player.autoplay(), true, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), true, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any", attr = true, play only', function(assert) {
|
||||
this.createPlayer({autoplay: 'any'}, {autoplay: true});
|
||||
|
||||
assert.equal(this.player.autoplay(), true, 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), true, 'tech.autoplay getter');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
});
|
@ -37,7 +37,13 @@ class TechFaker extends Tech {
|
||||
|
||||
setMuted() {}
|
||||
|
||||
setAutoplay() {}
|
||||
setAutoplay(v) {
|
||||
if (!v) {
|
||||
this.options_.autoplay = false;
|
||||
}
|
||||
|
||||
this.options_.autoplay = true;
|
||||
}
|
||||
|
||||
currentTime() {
|
||||
return 0;
|
||||
@ -58,7 +64,7 @@ class TechFaker extends Tech {
|
||||
return false;
|
||||
}
|
||||
autoplay() {
|
||||
return false;
|
||||
return this.options_.autoplay || false;
|
||||
}
|
||||
pause() {
|
||||
return false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user