mirror of
https://github.com/videojs/video.js.git
synced 2024-12-12 11:15:04 +02:00
fix: correctly resolve play promise when terminated via middleware (#5895)
This commit is contained in:
parent
1844482bf5
commit
ad53b80b8a
142
src/js/player.js
142
src/js/player.js
@ -26,7 +26,7 @@ import MediaError from './media-error.js';
|
||||
import safeParseTuple from 'safe-json-parse/tuple';
|
||||
import {assign} from './utils/obj';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
import {silencePromise} from './utils/promise';
|
||||
import {silencePromise, isPromise} from './utils/promise';
|
||||
import textTrackConverter from './tracks/text-track-list-converter.js';
|
||||
import ModalDialog from './modal-dialog';
|
||||
import Tech from './tech/tech.js';
|
||||
@ -421,9 +421,13 @@ class Player extends Component {
|
||||
tag.controls = false;
|
||||
tag.removeAttribute('controls');
|
||||
|
||||
this.changingSrc_ = false;
|
||||
this.playCallbacks_ = [];
|
||||
this.playTerminatedQueue_ = [];
|
||||
|
||||
// the attribute overrides the option
|
||||
if (tag.hasAttribute('autoplay')) {
|
||||
this.options_.autoplay = true;
|
||||
this.autoplay(true);
|
||||
} else {
|
||||
// otherwise use the setter to validate and
|
||||
// set the correct value.
|
||||
@ -535,9 +539,6 @@ class Player extends Component {
|
||||
this.breakpoints(this.options_.breakpoints);
|
||||
this.responsive(this.options_.responsive);
|
||||
|
||||
this.changingSrc_ = false;
|
||||
this.playWaitingForReady_ = false;
|
||||
this.playOnLoadstart_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1357,35 +1358,39 @@ class Player extends Component {
|
||||
|
||||
this.muted(true);
|
||||
|
||||
const playPromise = this.play();
|
||||
const restoreMuted = () => {
|
||||
this.muted(previouslyMuted);
|
||||
};
|
||||
|
||||
if (!playPromise || !playPromise.then || !playPromise.catch) {
|
||||
// restore muted on play terminatation
|
||||
this.playTerminatedQueue_.push(restoreMuted);
|
||||
|
||||
const mutedPromise = this.play();
|
||||
|
||||
if (!isPromise(mutedPromise)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return playPromise.catch((e) => {
|
||||
// restore old value of muted on failure
|
||||
this.muted(previouslyMuted);
|
||||
});
|
||||
return mutedPromise.catch(restoreMuted);
|
||||
};
|
||||
|
||||
let promise;
|
||||
|
||||
if (type === 'any') {
|
||||
// if muted defaults to true
|
||||
// the only thing we can do is call play
|
||||
if (type === 'any' && this.muted() !== true) {
|
||||
promise = this.play();
|
||||
|
||||
if (promise && promise.then && promise.catch) {
|
||||
promise.catch(() => {
|
||||
return muted();
|
||||
});
|
||||
if (isPromise(promise)) {
|
||||
promise = promise.catch(muted);
|
||||
}
|
||||
} else if (type === 'muted') {
|
||||
} else if (type === 'muted' && this.muted() !== true) {
|
||||
promise = muted();
|
||||
} else {
|
||||
promise = this.play();
|
||||
}
|
||||
|
||||
if (!promise || !promise.then || !promise.catch) {
|
||||
if (!isPromise(promise)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2219,54 +2224,77 @@ class Player extends Component {
|
||||
* The callback that should be called when the techs play is actually called
|
||||
*/
|
||||
play_(callback = silencePromise) {
|
||||
// If this is called while we have a play queued up on a loadstart, remove
|
||||
// that listener to avoid getting in a potentially bad state.
|
||||
if (this.playOnLoadstart_) {
|
||||
this.off('loadstart', this.playOnLoadstart_);
|
||||
this.playCallbacks_.push(callback);
|
||||
|
||||
const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
|
||||
|
||||
// treat calls to play_ somewhat like the `one` event function
|
||||
if (this.waitToPlay_) {
|
||||
this.off(['ready', 'loadstart'], this.waitToPlay_);
|
||||
this.waitToPlay_ = null;
|
||||
}
|
||||
|
||||
// If the player/tech is not ready, queue up another call to `play()` for
|
||||
// when it is. This will loop back into this method for another attempt at
|
||||
// playback when the tech is ready.
|
||||
if (!this.isReady_) {
|
||||
|
||||
// Bail out if we're already waiting for `ready`!
|
||||
if (this.playWaitingForReady_) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playWaitingForReady_ = true;
|
||||
this.ready(() => {
|
||||
this.playWaitingForReady_ = false;
|
||||
callback(this.play());
|
||||
});
|
||||
|
||||
// If the player/tech is ready and we have a source, we can attempt playback.
|
||||
} else if (!this.changingSrc_ && (this.src() || this.currentSrc())) {
|
||||
callback(this.techGet_('play'));
|
||||
return;
|
||||
|
||||
// If the tech is ready, but we do not have a source, we'll need to wait
|
||||
// for both the `ready` and a `loadstart` when the source is finally
|
||||
// resolved by middleware and set on the player.
|
||||
//
|
||||
// This can happen if `play()` is called while changing sources or before
|
||||
// one has been set on the player.
|
||||
} else {
|
||||
|
||||
this.playOnLoadstart_ = () => {
|
||||
this.playOnLoadstart_ = null;
|
||||
callback(this.play());
|
||||
// if the player/tech is not ready or the src itself is not ready
|
||||
// queue up a call to play on `ready` or `loadstart`
|
||||
if (!this.isReady_ || !isSrcReady) {
|
||||
this.waitToPlay_ = (e) => {
|
||||
this.play_();
|
||||
};
|
||||
this.one(['ready', 'loadstart'], this.waitToPlay_);
|
||||
|
||||
// if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
|
||||
// in that case, we need to prime the video element by calling load so it'll be ready in time
|
||||
if (browser.IS_ANY_SAFARI || browser.IS_IOS) {
|
||||
if (!isSrcReady && (browser.IS_ANY_SAFARI || browser.IS_IOS)) {
|
||||
this.load();
|
||||
}
|
||||
this.one('loadstart', this.playOnLoadstart_);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the player/tech is ready and we have a source, we can attempt playback.
|
||||
const val = this.techGet_('play');
|
||||
|
||||
// play was terminated if the returned value is null
|
||||
if (val === null) {
|
||||
this.runPlayTerminatedQueue_();
|
||||
} else {
|
||||
this.runPlayCallbacks_(val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These functions will be run when if play is terminated. If play
|
||||
* runPlayCallbacks_ is run these function will not be run. This allows us
|
||||
* to differenciate between a terminated play and an actual call to play.
|
||||
*/
|
||||
runPlayTerminatedQueue_() {
|
||||
const queue = this.playTerminatedQueue_.slice(0);
|
||||
|
||||
this.playTerminatedQueue_ = [];
|
||||
|
||||
queue.forEach(function(q) {
|
||||
q();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When a callback to play is delayed we have to run these
|
||||
* callbacks when play is actually called on the tech. This function
|
||||
* runs the callbacks that were delayed and accepts the return value
|
||||
* from the tech.
|
||||
*
|
||||
* @param {undefined|Promise} val
|
||||
* The return value from the tech.
|
||||
*/
|
||||
runPlayCallbacks_(val) {
|
||||
const callbacks = this.playCallbacks_.slice(0);
|
||||
|
||||
this.playCallbacks_ = [];
|
||||
// clear play terminatedQueue since we finished a real play
|
||||
this.playTerminatedQueue_ = [];
|
||||
|
||||
callbacks.forEach(function(cb) {
|
||||
cb(val);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3289,7 +3317,7 @@ class Player extends Component {
|
||||
this.options_.autoplay = true;
|
||||
}
|
||||
|
||||
techAutoplay = techAutoplay || this.options_.autoplay;
|
||||
techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay;
|
||||
|
||||
// if we don't have a tech then we do not queue up
|
||||
// a setAutoplay call on tech ready. We do this because the
|
||||
|
@ -166,6 +166,8 @@ export function mediate(middleware, tech, method, arg = null) {
|
||||
const callMethod = 'call' + toTitleCase(method);
|
||||
const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
|
||||
const terminated = middlewareValue === TERMINATOR;
|
||||
// deprecated. The `null` return value should instead return TERMINATOR to
|
||||
// prevent confusion if a techs method actually returns null.
|
||||
const returnValue = terminated ? null : tech[method](middlewareValue);
|
||||
|
||||
executeRight(middleware, method, returnValue, terminated);
|
||||
|
@ -3,6 +3,7 @@ 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 window from 'global/window';
|
||||
import sinon from 'sinon';
|
||||
|
||||
QUnit.module('autoplay', {
|
||||
@ -21,7 +22,9 @@ QUnit.module('autoplay', {
|
||||
|
||||
this.counts = {
|
||||
play: 0,
|
||||
muted: 0
|
||||
muted: 0,
|
||||
success: 0,
|
||||
failure: 0
|
||||
};
|
||||
|
||||
fixture.appendChild(videoTag);
|
||||
@ -38,6 +41,16 @@ QUnit.module('autoplay', {
|
||||
}
|
||||
};
|
||||
|
||||
this.resolvePromise = {
|
||||
then(fn) {
|
||||
fn();
|
||||
return this;
|
||||
},
|
||||
catch(fn) {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
this.createPlayer = (options = {}, attributes = {}, playRetval = null) => {
|
||||
Object.keys(attributes).forEach((a) => {
|
||||
videoTag.setAttribute(a, attributes[a]);
|
||||
@ -49,20 +62,26 @@ QUnit.module('autoplay', {
|
||||
this.player.play = () => {
|
||||
this.counts.play++;
|
||||
|
||||
if (playRetval) {
|
||||
return playRetval;
|
||||
if (playRetval || this.playRetval) {
|
||||
return playRetval || this.playRetval;
|
||||
}
|
||||
};
|
||||
|
||||
this.mutedValue = this.player.muted();
|
||||
|
||||
this.player.muted = (v) => {
|
||||
|
||||
if (typeof v !== 'undefined') {
|
||||
this.counts.muted++;
|
||||
this.mutedValue = v;
|
||||
}
|
||||
|
||||
return oldMuted.call(this.player, v);
|
||||
};
|
||||
|
||||
this.player.on('autoplay-success', () => this.counts.success++);
|
||||
this.player.on('autoplay-failure', () => this.counts.failure++);
|
||||
|
||||
// we have to trigger ready so that we
|
||||
// are waiting for loadstart
|
||||
this.player.tech_.triggerReady();
|
||||
@ -84,10 +103,14 @@ QUnit.test('option = false no play/muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = true no play/muted', function(assert) {
|
||||
@ -99,10 +122,14 @@ QUnit.test('option = true no play/muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "random" no play/muted', function(assert) {
|
||||
@ -114,10 +141,14 @@ QUnit.test('option = "random" no play/muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = null, should be set to false no play/muted', function(assert) {
|
||||
@ -129,14 +160,18 @@ QUnit.test('option = null, should be set to false no play/muted', function(asser
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('options = "play" play, no muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'play'});
|
||||
QUnit.test('option = "play" play, no muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'play'}, {}, this.resolvePromise);
|
||||
|
||||
assert.equal(this.player.autoplay(), 'play', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
@ -144,14 +179,18 @@ QUnit.test('options = "play" play, no muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 1, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 2, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any" play, no muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'any'});
|
||||
this.createPlayer({autoplay: 'any'}, {}, this.resolvePromise);
|
||||
|
||||
assert.equal(this.player.autoplay(), 'any', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
@ -159,14 +198,18 @@ QUnit.test('option = "any" play, no muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 1, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 2, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted" play and muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'muted'});
|
||||
this.createPlayer({autoplay: 'muted'}, {}, this.resolvePromise);
|
||||
|
||||
assert.equal(this.player.autoplay(), 'muted', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
@ -174,10 +217,14 @@ QUnit.test('option = "muted" play and muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 1, 'muted count');
|
||||
assert.equal(this.counts.success, 1, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.counts.success, 2, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "play" play, no muted, rejection ignored', function(assert) {
|
||||
@ -189,10 +236,14 @@ QUnit.test('option = "play" play, no muted, rejection ignored', function(assert)
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 1, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 2, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any" play, no muted, rejection leads to muted then play', function(assert) {
|
||||
@ -205,10 +256,14 @@ QUnit.test('option = "any" play, no muted, rejection leads to muted then play',
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 1, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 4, 'play count');
|
||||
assert.equal(this.counts.muted, 4, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 2, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted" play and muted, rejection ignored', function(assert) {
|
||||
@ -221,10 +276,14 @@ QUnit.test('option = "muted" play and muted, rejection ignored', function(assert
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 1, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 4, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 2, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted", attr = true, play and muted', function(assert) {
|
||||
@ -236,10 +295,14 @@ QUnit.test('option = "muted", attr = true, play and muted', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "play", attr = true, play only', function(assert) {
|
||||
@ -251,10 +314,14 @@ QUnit.test('option = "play", attr = true, play only', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any", attr = true, play only', function(assert) {
|
||||
@ -266,8 +333,116 @@ QUnit.test('option = "any", attr = true, play only', function(assert) {
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
assert.equal(this.counts.play, 0, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "any", play terminated restores muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'any'});
|
||||
|
||||
this.playRetval = {
|
||||
then(fn) {
|
||||
fn();
|
||||
return this;
|
||||
},
|
||||
catch: (fn) => {
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 0, 'muted count');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.playRetval = {
|
||||
then(_fn) {
|
||||
window.setTimeout(_fn, 1);
|
||||
return this;
|
||||
},
|
||||
catch(_fn) {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
const retval = fn();
|
||||
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 1, 'muted count');
|
||||
assert.equal(this.mutedValue, true, 'is muted');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
return retval;
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(this.player.autoplay(), 'any', 'player.autoplay getter');
|
||||
assert.equal(this.player.tech_.autoplay(), false, 'tech.autoplay getter');
|
||||
assert.equal(this.mutedValue, false, 'is not muted');
|
||||
|
||||
this.player.tech_.trigger('loadstart');
|
||||
|
||||
this.player.runPlayTerminatedQueue_();
|
||||
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.mutedValue, false, 'is not muted');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.runPlayTerminatedQueue_();
|
||||
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.mutedValue, false, 'is not muted');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
// verify autoplay success
|
||||
this.clock.tick(1);
|
||||
assert.equal(this.counts.play, 2, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.counts.success, 1, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
||||
QUnit.test('option = "muted", play terminated restores muted', function(assert) {
|
||||
this.createPlayer({autoplay: 'muted'}, {}, {
|
||||
then(fn) {
|
||||
window.setTimeout(() => {
|
||||
fn();
|
||||
}, 1);
|
||||
return this;
|
||||
},
|
||||
catch(fn) {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
assert.equal(this.mutedValue, true, 'is muted');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
this.player.runPlayTerminatedQueue_();
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.mutedValue, false, 'no longer muted');
|
||||
assert.equal(this.counts.success, 0, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
|
||||
// verify autoplay success
|
||||
this.clock.tick(1);
|
||||
assert.equal(this.counts.play, 1, 'play count');
|
||||
assert.equal(this.counts.muted, 2, 'muted count');
|
||||
assert.equal(this.counts.success, 1, 'success count');
|
||||
assert.equal(this.counts.failure, 0, 'failure count');
|
||||
});
|
||||
|
446
test/unit/play.test.js
Normal file
446
test/unit/play.test.js
Normal file
@ -0,0 +1,446 @@
|
||||
/* eslint-env qunit */
|
||||
import TestHelpers from './test-helpers.js';
|
||||
import sinon from 'sinon';
|
||||
import window from 'global/window';
|
||||
import * as middleware from '../../src/js/tech/middleware.js';
|
||||
import videojs from '../../src/js/video.js';
|
||||
|
||||
const middleWareTerminations = ['terminates', 'does not-terminate'];
|
||||
const playReturnValues = ['non-promise', 'promise'];
|
||||
|
||||
const mainModule = function(playReturnValue, middlewareTermination, subhooks) {
|
||||
subhooks.beforeEach(function(assert) {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.techPlayCalls = 0;
|
||||
this.playsTerminated = 0;
|
||||
this.playTests = [];
|
||||
this.terminate = false;
|
||||
|
||||
if (middlewareTermination === 'terminates') {
|
||||
this.terminate = true;
|
||||
}
|
||||
this.techPlay = () => {
|
||||
this.techPlayCalls++;
|
||||
|
||||
if (playReturnValue === 'promise') {
|
||||
return window.Promise.resolve('foo');
|
||||
}
|
||||
return 'foo';
|
||||
};
|
||||
|
||||
this.finish = function() {
|
||||
const done = assert.async(this.playTests.length);
|
||||
|
||||
const singleFinish = (playValue, assertName) => {
|
||||
assert.equal(playValue, 'foo', `play call from - ${assertName} - is correct`);
|
||||
done();
|
||||
};
|
||||
|
||||
this.playTests.forEach(function(test) {
|
||||
const playRetval = test.playRetval;
|
||||
const testName = test.assertName;
|
||||
|
||||
if (typeof playRetval === 'string') {
|
||||
singleFinish(playRetval, testName);
|
||||
} else {
|
||||
playRetval.then((v) => {
|
||||
singleFinish(v, testName);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.checkState = (assertName, options = {}) => {
|
||||
const expectedState = videojs.mergeOptions({
|
||||
playCalls: 0,
|
||||
techLoaded: false,
|
||||
techReady: false,
|
||||
playerReady: false,
|
||||
changingSrc: false,
|
||||
playsTerminated: 0
|
||||
}, options);
|
||||
|
||||
if (typeof options.techLoaded === 'undefined' && typeof options.techReady !== 'undefined') {
|
||||
expectedState.techLoaded = options.techReady;
|
||||
}
|
||||
|
||||
const currentState = {
|
||||
playCalls: this.techPlayCalls,
|
||||
techLoaded: Boolean(this.player.tech_),
|
||||
techReady: Boolean((this.player.tech_ || {}).isReady_),
|
||||
playerReady: Boolean(this.player.isReady_),
|
||||
changingSrc: Boolean(this.player.changingSrc_),
|
||||
playsTerminated: Number(this.playsTerminated)
|
||||
};
|
||||
|
||||
assert.deepEqual(currentState, expectedState, assertName);
|
||||
};
|
||||
|
||||
this.playTerminatedQueue = () => this.playsTerminated++;
|
||||
|
||||
this.playTest = (assertName, options = {}) => {
|
||||
if (this.player.playTerminatedQueue_ !== this.playTerminatedQueue) {
|
||||
this.player.runPlayTerminatedQueue_ = this.playTerminatedQueue;
|
||||
}
|
||||
if (this.player && this.player.tech_ && this.player.tech_.play !== this.techPlay) {
|
||||
this.player.tech_.play = this.techPlay;
|
||||
}
|
||||
this.playTests.push({assertName, playRetval: this.player.play()});
|
||||
this.checkState(assertName, options);
|
||||
};
|
||||
|
||||
this.middleware = () => {
|
||||
return {
|
||||
// pass along source
|
||||
setSource(srcObj, next) {
|
||||
next(null, srcObj);
|
||||
},
|
||||
callPlay: () => {
|
||||
if (this.terminate) {
|
||||
return middleware.TERMINATOR;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
middleware.use('*', this.middleware);
|
||||
});
|
||||
|
||||
subhooks.afterEach(function() {
|
||||
// remove added middleware
|
||||
const middlewareList = middleware.getMiddleware('*');
|
||||
|
||||
for (let i = 0; i < middlewareList.length; i++) {
|
||||
if (middlewareList[i] === this.middleware) {
|
||||
middlewareList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (this.player) {
|
||||
this.player.dispose();
|
||||
}
|
||||
this.clock.restore();
|
||||
});
|
||||
|
||||
QUnit.test('Player#play() resolves correctly with dom sources and async tech ready', function(assert) {
|
||||
// turn of mediaLoader to prevent setting a tech right away
|
||||
// similar to settings sources in the DOM
|
||||
// turn off autoReady to prevent syncronous ready from the tech
|
||||
this.player = TestHelpers.makePlayer({mediaLoader: false, techFaker: {autoReady: false}});
|
||||
|
||||
this.playTest('before anything is ready');
|
||||
|
||||
this.player.src({
|
||||
src: 'http://example.com/video.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
this.playTest('only changingSrc', {
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('still changingSrc, tech loaded', {
|
||||
techLoaded: true,
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.player.tech_.triggerReady();
|
||||
this.playTest('still changingSrc, tech loaded and ready', {
|
||||
techReady: true,
|
||||
changingSrc: true
|
||||
});
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('done changingSrc, tech/player ready', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.checkState('state stays the same', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly, unless terminated', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 2,
|
||||
playsTerminated: this.terminate ? 2 : 0
|
||||
});
|
||||
|
||||
if (this.terminate) {
|
||||
this.terminate = false;
|
||||
|
||||
this.playTest('play works if not terminated', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 1,
|
||||
playsTerminated: 2
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 2,
|
||||
playsTerminated: 2
|
||||
});
|
||||
}
|
||||
|
||||
this.finish(assert);
|
||||
});
|
||||
|
||||
QUnit.test('Player#play() resolves correctly with dom sources', function(assert) {
|
||||
this.player = TestHelpers.makePlayer({mediaLoader: false});
|
||||
|
||||
this.playTest('before anything is ready');
|
||||
|
||||
this.player.src({
|
||||
src: 'http://example.com/video.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
this.playTest('only changingSrc', {
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('still changingSrc, tech/player ready', {
|
||||
techLoaded: true,
|
||||
changingSrc: true,
|
||||
playerReady: true,
|
||||
techReady: true
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('done changingSrc, tech#play is called', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.checkState('state stays the same', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 2,
|
||||
playsTerminated: this.terminate ? 2 : 0
|
||||
});
|
||||
|
||||
if (this.terminate) {
|
||||
this.terminate = false;
|
||||
|
||||
this.playTest('play works if not terminated', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 1,
|
||||
playsTerminated: 2
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 2,
|
||||
playsTerminated: 2
|
||||
});
|
||||
}
|
||||
|
||||
this.finish(assert);
|
||||
});
|
||||
|
||||
QUnit.test('Player#play() resolves correctly with async tech ready', function(assert) {
|
||||
this.player = TestHelpers.makePlayer({techFaker: {autoReady: false}});
|
||||
|
||||
this.playTest('before anything is ready', {
|
||||
techLoaded: true
|
||||
});
|
||||
|
||||
this.player.src({
|
||||
src: 'http://example.com/video.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
this.playTest('tech loaded changingSrc', {
|
||||
techLoaded: true,
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('still changingSrc, tech loaded', {
|
||||
techLoaded: true,
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
this.playTest('still changingSrc, tech loaded again', {
|
||||
techLoaded: true,
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.player.tech_.triggerReady();
|
||||
this.playTest('still changingSrc, tech loaded and ready', {
|
||||
techReady: true,
|
||||
changingSrc: true
|
||||
});
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('still changingSrc tech/player ready', {
|
||||
changingSrc: true,
|
||||
playerReady: true,
|
||||
techReady: true
|
||||
});
|
||||
|
||||
// player ready calls fire now
|
||||
// which sets changingSrc_ to false
|
||||
this.clock.tick(1);
|
||||
|
||||
this.checkState('play was called on ready', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 2,
|
||||
playsTerminated: this.terminate ? 2 : 0
|
||||
});
|
||||
|
||||
if (this.terminate) {
|
||||
this.terminate = false;
|
||||
|
||||
this.playTest('play works if not terminated', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 1,
|
||||
playsTerminated: 2
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 2,
|
||||
playsTerminated: 2
|
||||
});
|
||||
}
|
||||
|
||||
this.finish(assert);
|
||||
});
|
||||
|
||||
QUnit.test('Player#play() resolves correctly', function(assert) {
|
||||
this.player = TestHelpers.makePlayer();
|
||||
|
||||
this.playTest('player/tech start out ready', {
|
||||
techReady: true,
|
||||
playerReady: true
|
||||
});
|
||||
|
||||
this.player.src({
|
||||
src: 'http://example.com/video.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
this.playTest('now changingSrc', {
|
||||
techReady: true,
|
||||
playerReady: true,
|
||||
changingSrc: true
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
this.playTest('done changingSrc, play called if not terminated', {
|
||||
techReady: true,
|
||||
playerReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.clock.tick(2);
|
||||
|
||||
this.checkState('state stays the same', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 1,
|
||||
playsTerminated: this.terminate ? 1 : 0
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: this.terminate ? 0 : 2,
|
||||
playsTerminated: this.terminate ? 2 : 0
|
||||
});
|
||||
|
||||
if (this.terminate) {
|
||||
this.terminate = false;
|
||||
|
||||
this.playTest('play works if not terminated', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 1,
|
||||
playsTerminated: 2
|
||||
});
|
||||
|
||||
this.playTest('future calls hit tech#play directly', {
|
||||
playerReady: true,
|
||||
techReady: true,
|
||||
playCalls: 2,
|
||||
playsTerminated: 2
|
||||
});
|
||||
}
|
||||
|
||||
this.finish(assert);
|
||||
});
|
||||
|
||||
// without enableSourceset this test will fail.
|
||||
QUnit.test('Player#play() resolves correctly on tech el src', function(assert) {
|
||||
this.player = TestHelpers.makePlayer({techOrder: ['html5'], enableSourceset: true}, null, false);
|
||||
|
||||
this.playTest('player/tech start out ready', {
|
||||
techReady: true,
|
||||
playerReady: true
|
||||
});
|
||||
|
||||
this.player.tech_.el_.src = 'http://vjs.zencdn.net/v/oceans.mp4';
|
||||
|
||||
this.player.on('loadstart', () => {
|
||||
this.checkState('play should have been called', {
|
||||
techReady: true,
|
||||
playerReady: true,
|
||||
playCalls: 1
|
||||
});
|
||||
});
|
||||
|
||||
this.finish(assert);
|
||||
});
|
||||
};
|
||||
|
||||
QUnit.module('Player#play()', (hooks) => {
|
||||
playReturnValues.forEach((playReturnValue) => {
|
||||
middleWareTerminations.forEach((middlewareTermination) => {
|
||||
QUnit.module(`tech#play() => ${playReturnValue}, middleware ${middlewareTermination}`, (subhooks) => {
|
||||
mainModule(playReturnValue, middlewareTermination, subhooks);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1174,60 +1174,6 @@ QUnit.test('should be scrubbing while seeking', function(assert) {
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
if (window.Promise) {
|
||||
QUnit.test('play promise should resolve to native promise if returned', function(assert) {
|
||||
const player = TestHelpers.makePlayer({});
|
||||
const done = assert.async();
|
||||
|
||||
player.src({
|
||||
src: 'http://example.com/video.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
player.tech_.play = () => window.Promise.resolve('foo');
|
||||
const p = player.play();
|
||||
|
||||
assert.ok(p, 'play returns something');
|
||||
assert.equal(typeof p.then, 'function', 'play returns a promise');
|
||||
p.then(function(val) {
|
||||
assert.equal(val, 'foo', 'should resolve to native promise value');
|
||||
|
||||
player.dispose();
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.test('play promise should resolve to native value if returned', function(assert) {
|
||||
const done = assert.async();
|
||||
const player = TestHelpers.makePlayer({});
|
||||
|
||||
player.src({
|
||||
src: 'http://example.com/video.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
player.tech_.play = () => 'foo';
|
||||
const p = player.play();
|
||||
|
||||
const finish = (v) => {
|
||||
assert.equal(v, 'foo', 'play returns foo');
|
||||
done();
|
||||
};
|
||||
|
||||
if (typeof p === 'string') {
|
||||
finish(p);
|
||||
} else {
|
||||
p.then((v) => {
|
||||
finish(v);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('should throw on startup no techs are specified', function(assert) {
|
||||
const techOrder = videojs.options.techOrder;
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
@ -11,7 +11,7 @@ const TestHelpers = {
|
||||
return videoTag;
|
||||
},
|
||||
|
||||
makePlayer(playerOptions, videoTag) {
|
||||
makePlayer(playerOptions, videoTag, addTechAsMiddleware = true) {
|
||||
videoTag = videoTag || TestHelpers.makeTag();
|
||||
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
@ -23,7 +23,9 @@ const TestHelpers = {
|
||||
|
||||
const player = new Player(videoTag, playerOptions);
|
||||
|
||||
player.middleware_ = [player.tech_];
|
||||
if (addTechAsMiddleware) {
|
||||
player.middleware_ = [player.tech_];
|
||||
}
|
||||
|
||||
return player;
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user