1
0
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:
Brandon Casey 2019-04-11 14:29:27 -04:00 committed by Gary Katsevman
parent 1844482bf5
commit ad53b80b8a
6 changed files with 719 additions and 120 deletions

View File

@ -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

View File

@ -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);

View File

@ -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
View 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);
});
});
});
});

View File

@ -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');

View File

@ -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);
if (addTechAsMiddleware) {
player.middleware_ = [player.tech_];
}
return player;
},