1
0
mirror of https://github.com/videojs/video.js.git synced 2025-07-17 01:42:41 +02:00

feat: middleware (#3788)

Add middleware support. Middleware can function as go-between between the player and the tech. For example, it can modify the duration that the tech returns to the player. In addition, middleware allow for supporting custom video sources and types.

Currently, middleware can only intercept timeline methods like duration, currentTime, and setCurrentTime.

For example,
```js
videojs.use('video/foo', {
  setSource(src, next) {
    next(null, {
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });
  }
});
```
Will allow you to set a source with type `video/foo` which will play back `video.mp4`.

This makes setting the source asynchronous, which aligns it with the spec a bit more. Methods like play can still be called synchronously on the player after setting the source and the player will play once the source has loaded.

`sourceOrder` option was removed as well and it will now always use source ordering.

BREAKING CHANGE: setting the source is now asynchronous. `sourceOrder` option removed and made the default.
This commit is contained in:
Gary Katsevman
2017-01-19 17:29:09 -05:00
committed by GitHub
parent b387437aed
commit 34aab3f357
11 changed files with 726 additions and 132 deletions

View File

@ -25,6 +25,7 @@ import mergeOptions from './utils/merge-options.js';
import textTrackConverter from './tracks/text-track-list-converter.js';
import ModalDialog from './modal-dialog';
import Tech from './tech/tech.js';
import * as middleware from './tech/middleware.js';
import {ALL as TRACK_TYPES} from './tracks/track-types';
// The following imports are used only to ensure that the corresponding modules
@ -369,6 +370,8 @@ class Player extends Component {
this.options_.playerOptions = playerOptionsCopy;
this.middleware_ = [];
this.initChildren();
// Set isAudio based on whether or not an audio tag was used
@ -420,6 +423,8 @@ class Player extends Component {
this.on('fullscreenchange', this.handleFullscreenChange_);
this.on('stageclick', this.handleStageClick_);
this.changingSrc_ = false;
}
/**
@ -834,16 +839,8 @@ class Player extends Component {
techOptions.tag = this.tag;
}
if (source) {
this.currentType_ = source.type;
if (source.src === this.cache_.src && this.cache_.currentTime > 0) {
techOptions.startTime = this.cache_.currentTime;
}
this.cache_.sources = null;
this.cache_.source = source;
this.cache_.src = source.src;
if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
techOptions.startTime = this.cache_.currentTime;
}
// Initialize tech instance
@ -1508,13 +1505,12 @@ class Player extends Component {
*/
techCall_(method, arg) {
// If it's not ready yet, call method when it is
if (this.tech_ && !this.tech_.isReady_) {
this.tech_.ready(function() {
this[method](arg);
}, true);
// Otherwise call method now
} else {
this.ready(function() {
if (method in middleware.allowedSetters) {
return middleware.set(this.middleware_, this.tech_, method, arg);
}
try {
if (this.tech_) {
this.tech_[method](arg);
@ -1523,7 +1519,7 @@ class Player extends Component {
log(e);
throw e;
}
}
});
}
/**
@ -1540,6 +1536,10 @@ class Player extends Component {
techGet_(method) {
if (this.tech_ && this.tech_.isReady_) {
if (method in middleware.allowedGetters) {
return middleware.get(this.middleware_, this.tech_, method);
}
// Flash likes to die and reload when you hide or reposition it.
// In these cases the object methods go away and we get errors.
// When that happens we'll catch the errors and inform tech that it's not ready any more.
@ -1572,21 +1572,26 @@ class Player extends Component {
* return undefined.
*/
play() {
// Only calls the tech's play if we already have a src loaded
if (this.src() || this.currentSrc()) {
return this.techGet_('play');
}
this.ready(function() {
this.tech_.one('loadstart', function() {
const retval = this.play();
// silence errors (unhandled promise from play)
if (retval !== undefined && typeof retval.then === 'function') {
retval.then(null, (e) => {});
}
if (this.changingSrc_) {
this.ready(function() {
this.techCall_('play');
});
});
// Only calls the tech's play if we already have a src loaded
} else if (this.src() || this.currentSrc()) {
return this.techGet_('play');
} else {
this.ready(function() {
this.tech_.one('loadstart', function() {
const retval = this.play();
// silence errors (unhandled promise from play)
if (retval !== undefined && typeof retval.then === 'function') {
retval.then(null, (e) => {});
}
});
});
}
}
/**
@ -2181,66 +2186,96 @@ class Player extends Component {
*/
src(source) {
if (source === undefined) {
return this.techGet_('src');
return this.cache_.src;
}
let currentTech = Tech.getTech(this.techName_);
this.changingSrc_ = true;
// Support old behavior of techs being registered as components.
// Remove once that deprecated behavior is removed.
if (!currentTech) {
currentTech = Component.getComponent(this.techName_);
}
let src = source;
// case: Array of source objects to choose from and pick the best to play
if (Array.isArray(source)) {
this.sourceList_(source);
// case: URL String (http://myvideo...)
this.cache_.sources = source;
src = source[0];
} else if (typeof source === 'string') {
// create a source object from the string
this.src({ src: source });
src = {
src: source
};
// case: Source object { src: '', type: '' ... }
} else if (source instanceof Object) {
// check if the source has a type and the loaded tech cannot play the source
// if there's no type we'll just try the current tech
if (source.type && !currentTech.canPlaySource(source, this.options_[this.techName_.toLowerCase()])) {
// create a source list with the current source and send through
// the tech loop to check for a compatible technology
this.sourceList_([source]);
} else {
this.cache_.sources = null;
this.cache_.source = source;
this.cache_.src = source.src;
this.currentType_ = source.type || '';
// wait until the tech is ready to set the source
this.ready(function() {
// The setSource tech method was added with source handlers
// so older techs won't support it
// We need to check the direct prototype for the case where subclasses
// of the tech do not support source handlers
if (currentTech.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', source);
} else {
this.techCall_('src', source.src);
}
if (this.options_.preload === 'auto') {
this.load();
}
if (this.options_.autoplay) {
this.play();
}
// Set the source synchronously if possible (#2326)
}, true);
}
this.cache_.sources = [src];
}
this.cache_.source = src;
this.currentType_ = src.type;
middleware.setSource(Fn.bind(this, this.setTimeout), src, (src_, mws) => {
this.middleware_ = mws;
const err = this.src_(src_);
if (err) {
if (Array.isArray(source) && source.length > 1) {
return this.src(source.slice(1));
}
// We need to wrap this in a timeout to give folks a chance to add error event handlers
this.setTimeout(function() {
this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
}, 0);
// we could not find an appropriate tech, but let's still notify the delegate that this is it
// this needs a better comment about why this is needed
this.triggerReady();
return;
}
this.changingSrc_ = false;
this.cache_.src = src_.src;
middleware.setTech(mws, this.tech_);
});
}
src_(source) {
const sourceTech = this.selectSource([source]);
if (!sourceTech) {
return true;
}
if (sourceTech.tech !== this.techName_) {
this.changingSrc_ = true;
// load this technology with the chosen source
this.loadTech_(sourceTech.tech, sourceTech.source);
return false;
}
// wait until the tech is ready to set the source
this.ready(function() {
// The setSource tech method was added with source handlers
// so older techs won't support it
// We need to check the direct prototype for the case where subclasses
// of the tech do not support source handlers
if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', source);
} else {
this.techCall_('src', source.src);
}
if (this.options_.preload === 'auto') {
this.load();
}
if (this.options_.autoplay) {
this.play();
}
// Set the source synchronously if possible (#2326)
}, true);
return false;
}
/**
@ -2335,7 +2370,7 @@ class Player extends Component {
* The current source
*/
currentSrc() {
return this.techGet_('currentSrc') || this.cache_.src || '';
return this.cache_.source && this.cache_.source.src || '';
}
/**

View File

@ -719,6 +719,29 @@ Html5.isSupported = function() {
return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
};
/**
* Check if the tech can support the given type
*
* @param {string} type
* The mimetype to check
* @return {string} 'probably', 'maybe', or '' (empty string)
*/
Html5.canPlayType = function(type) {
return Html5.TEST_VID.canPlayType(type);
};
/**
* Check if the tech can support the given source
* @param {Object} srcObj
* The source object
* @param {Object} options
* The options passed to the tech
* @return {string} 'probably', 'maybe', or '' (empty string)
*/
Html5.canPlaySource = function(srcObj, options) {
return Html5.canPlayType(srcObj.type);
};
/**
* Check if the volume can be changed in this browser/device.
* Volume cannot be changed in a lot of mobile devices.

85
src/js/tech/middleware.js Normal file
View File

@ -0,0 +1,85 @@
import { assign } from '../utils/obj.js';
const middlewares = {};
export function use(type, middleware) {
middlewares[type] = middlewares[type] || [];
middlewares[type].push(middleware);
}
export function getMiddleware(type) {
if (type) {
return middlewares[type];
}
return middlewares;
}
export function setSource(setTimeout, src, next) {
setTimeout(() => setSourceHelper(src, middlewares[src.type], next), 1);
}
export function setTech(middleware, tech) {
middleware.forEach((mw) => mw.setTech && mw.setTech(tech));
}
export function get(middleware, tech, method) {
return middleware.reduceRight(middlewareIterator(method), tech[method]());
}
export function set(middleware, tech, method, arg) {
return tech[method](middleware.reduce(middlewareIterator(method), arg));
}
export const allowedGetters = {
currentTime: 1,
duration: 1
};
export const allowedSetters = {
setCurrentTime: 1
};
function middlewareIterator(method) {
return (value, mw) => {
if (mw[method]) {
return mw[method](value);
}
return value;
};
}
function setSourceHelper(src = {}, middleware = [], next, acc = []) {
const [mw, ...mwrest] = middleware;
// if mw is a string, then we're at a fork in the road
if (typeof mw === 'string') {
setSourceHelper(src, middlewares[mw], next, acc);
// if we have an mw, call its setSource method
} else if (mw) {
mw.setSource(assign({}, src), function(err, _src) {
// something happened, try the next middleware on the current level
// make sure to use the old src
if (err) {
return setSourceHelper(src, mwrest, next, acc);
}
// we've succeeded, now we need to go deeper
acc.push(mw);
// if it's the same time, continue does the current chain
// otherwise, we want to go down the new chain
setSourceHelper(_src,
src.type === _src.type ? mwrest : middlewares[_src.type],
next,
acc);
});
} else if (mwrest.length) {
setSourceHelper(src, mwrest, next, acc);
} else {
next(src, acc);
}
}

View File

@ -11,6 +11,7 @@ import { bufferedPercent } from '../utils/buffer.js';
import MediaError from '../media-error.js';
import window from 'global/window';
import document from 'global/document';
import * as middleware from './middleware.js';
import {isPlain} from '../utils/obj';
import * as TRACK_TYPES from '../tracks/track-types';
@ -729,6 +730,32 @@ class Tech extends Component {
return '';
}
/**
* Check if the type is supported by this tech.
*
* The base tech does not support any type, but source handlers might
* overwrite this.
*
* @param {string} type
* The media type to check
* @return {string} Returns the native video element's response
*/
static canPlayType() {
return '';
}
/**
* Check if the tech can support the given source
* @param {Object} srcObj
* The source object
* @param {Object} options
* The options passed to the tech
* @return {string} 'probably', 'maybe', or '' (empty string)
*/
static canPlaySource(srcObj, options) {
return Tech.canPlayType(srcObj.type);
}
/*
* Return whether the argument is a Tech or not.
* Can be passed either a Class like `Html5` or a instance like `player.tech_`
@ -765,6 +792,15 @@ class Tech extends Component {
throw new Error(`Tech ${name} must be a Tech`);
}
if (!Tech.canPlayType) {
throw new Error('Techs must have a static canPlayType method on them');
}
if (!Tech.canPlaySource) {
throw new Error('Techs must have a static canPlaySource method on them');
}
middleware.use('*', {name, tech});
Tech.techs_[name] = tech;
return tech;
}

View File

@ -30,6 +30,7 @@ import xhr from 'xhr';
// Include the built-in techs
import Tech from './tech/tech.js';
import { use as middlewareUse } from './tech/middleware.js';
// HTML5 Element Shim for IE8
if (typeof HTMLVideoElement === 'undefined' && Dom.isReal()) {
@ -298,6 +299,8 @@ videojs.getTech = Tech.getTech;
*/
videojs.registerTech = Tech.registerTech;
videojs.use = middlewareUse;
/**
* A suite of browser and device tests from {@link browser}.
*

View File

@ -7,12 +7,12 @@ import * as browser from '../../src/js/utils/browser.js';
import log from '../../src/js/utils/log.js';
import MediaError from '../../src/js/media-error.js';
import Html5 from '../../src/js/tech/html5.js';
import Tech from '../../src/js/tech/tech.js';
import TestHelpers from './test-helpers.js';
import document from 'global/document';
import sinon from 'sinon';
import window from 'global/window';
import Tech from '../../src/js/tech/tech.js';
import TechFaker from './tech/tech-faker.js';
import * as middleware from '../../src/js/tech/middleware.js';
QUnit.module('Player', {
beforeEach() {
@ -283,6 +283,9 @@ QUnit.test('should asynchronously fire error events during source selection', fu
assert.ok(player.error().code === 4, 'Source could not be played error thrown');
});
// The first one is for player initialization
// The second one is the setTimeout for triggering the error
this.clock.tick(1);
this.clock.tick(1);
player.dispose();
@ -597,49 +600,6 @@ QUnit.test('make sure that controls listeners do not get added too many times',
player.dispose();
});
QUnit.test('should select the proper tech based on the the sourceOrder option', function(assert) {
const fixture = document.getElementById('qunit-fixture');
const html =
'<video id="example_1">' +
'<source src="fake.foo1" type="video/unsupported-format">' +
'<source src="fake.foo2" type="video/foo-format">' +
'</video>';
// Extend TechFaker to create a tech that plays the only mime-type that TechFaker
// will not play
class PlaysUnsupported extends TechFaker {
constructor(options, handleReady) {
super(options, handleReady);
}
// Support ONLY "video/unsupported-format"
static isSupported() {
return true;
}
static canPlayType(type) {
return (type === 'video/unsupported-format' ? 'maybe' : '');
}
static canPlaySource(srcObj) {
return srcObj.type === 'video/unsupported-format';
}
}
Tech.registerTech('PlaysUnsupported', PlaysUnsupported);
fixture.innerHTML += html;
let tag = document.getElementById('example_1');
let player = new Player(tag, { techOrder: ['techFaker', 'playsUnsupported'], sourceOrder: true });
assert.equal(player.techName_, 'PlaysUnsupported', 'selected the PlaysUnsupported tech when sourceOrder is truthy');
player.dispose();
fixture.innerHTML += html;
tag = document.getElementById('example_1');
player = new Player(tag, { techOrder: ['techFaker', 'playsUnsupported']});
assert.equal(player.techName_, 'TechFaker', 'selected the TechFaker tech when sourceOrder is falsey');
player.dispose();
});
QUnit.test('should register players with generated ids', function(assert) {
const fixture = document.getElementById('qunit-fixture');
@ -974,11 +934,17 @@ QUnit.test('should clear pending errors on disposal', function(assert) {
const player = TestHelpers.makePlayer();
clock.tick(1);
player.src({
src: 'http://example.com/movie.unsupported-format',
type: 'video/unsupported-format'
});
clock.tick(1);
player.dispose();
try {
clock.tick(5000);
} catch (e) {
@ -1072,6 +1038,13 @@ if (window.Promise) {
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();
@ -1089,6 +1062,13 @@ if (window.Promise) {
QUnit.test('play promise should resolve to native value if returned', function(assert) {
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();
@ -1426,6 +1406,140 @@ QUnit.test('should not allow to register custom player when any player has been
videojs.registerComponent('Player', Player);
});
QUnit.test('techGet runs through middleware if allowedGetter', function(assert) {
let cts = 0;
let durs = 0;
let ps = 0;
videojs.use('video/foo', {
currentTime() {
cts++;
},
duration() {
durs++;
},
paused() {
ps++;
}
});
const tag = TestHelpers.makeTag();
const player = videojs(tag, {
techOrder: ['techFaker']
});
player.middleware_ = middleware.getMiddleware('video/foo');
player.techGet_('currentTime');
player.techGet_('duration');
player.techGet_('paused');
assert.equal(cts, 1, 'currentTime is allowed');
assert.equal(durs, 1, 'duration is allowed');
assert.equal(ps, 0, 'paused is not allowed');
middleware.getMiddleware('video/foo').pop();
player.dispose();
});
QUnit.test('techCall runs through middleware if allowedSetter', function(assert) {
let cts = 0;
let vols = 0;
videojs.use('video/foo', {
setCurrentTime(ct) {
cts++;
return ct;
},
setVolume() {
vols++;
}
});
const tag = TestHelpers.makeTag();
const player = videojs(tag, {
techOrder: ['techFaker']
});
player.middleware_ = middleware.getMiddleware('video/foo');
this.clock.tick(1);
player.techCall_('setCurrentTime', 10);
player.techCall_('setVolume', 0.5);
this.clock.tick(1);
assert.equal(cts, 1, 'setCurrentTime is allowed');
assert.equal(vols, 0, 'setVolume is not allowed');
middleware.getMiddleware('video/foo').pop();
player.dispose();
});
QUnit.test('src selects tech based on middleware', function(assert) {
class FooTech extends Html5 {}
class BarTech extends Html5 {}
FooTech.isSupported = () => true;
FooTech.canPlayType = (type) => type === 'video/mp4';
FooTech.canPlaySource = (src) => FooTech.canPlayType(src.type);
BarTech.isSupported = () => true;
BarTech.canPlayType = (type) => type === 'video/flv';
BarTech.canPlaySource = (src) => BarTech.canPlayType(src.type);
videojs.registerTech('FooTech', FooTech);
videojs.registerTech('BarTech', BarTech);
videojs.use('video/foo', {
setSource(src, next) {
next(null, {
src: 'http://example.com/video.mp4',
type: 'video/mp4'
});
}
});
videojs.use('video/bar', {
setSource(src, next) {
next(null, {
src: 'http://example.com/video.flv',
type: 'video/flv'
});
}
});
const tag = TestHelpers.makeTag();
const player = videojs(tag, {
techOrder: ['fooTech', 'barTech']
});
player.src({
src: 'foo',
type: 'video/foo'
});
this.clock.tick(1);
assert.equal(player.techName_, 'FooTech', 'the FooTech (html5) tech is chosen');
player.src({
src: 'bar',
type: 'video/bar'
});
this.clock.tick(1);
assert.equal(player.techName_, 'BarTech', 'the BarTech (Flash) tech is chosen');
middleware.getMiddleware('video/foo').pop();
middleware.getMiddleware('video/bar').pop();
player.dispose();
delete Tech.techs_.FooTech;
delete Tech.techs_.BarTech;
});
QUnit.test('options: plugins', function(assert) {
const optionsSpy = sinon.spy();

View File

@ -0,0 +1,280 @@
/* eslint-env qunit */
import * as middleware from '../../../src/js/tech/middleware.js';
import sinon from 'sinon';
import window from 'global/window';
QUnit.module('Middleware', {
beforeEach(assert) {
this.clock = sinon.useFakeTimers();
},
afterEach(assert) {
this.clock.restore();
}
});
QUnit.test('middleware can be added with the use method', function(assert) {
const myMw = {};
middleware.use('foo', myMw);
assert.equal(middleware.getMiddleware('foo').pop(), myMw, 'we are able to add middleware');
});
QUnit.test('middleware get iterates through the middleware array the right order', function(assert) {
const cts = [];
const durs = [];
const foos = [];
const mws = [
{
currentTime(ct) {
cts.push(ct);
return ct * 2;
},
duration(dur) {
durs.push(dur);
return dur + 2;
},
foo(f) {
foos.push(f);
return f / 2;
}
},
{
currentTime(ct) {
cts.push(ct);
return ct + 2;
},
duration(dur) {
durs.push(dur);
return dur / 2;
},
foo(f) {
foos.push(f);
return f + 3;
}
}
];
const tech = {
currentTime(ct) {
return 5;
},
duration(dur) {
return 5;
},
foo(f) {
return 5;
}
};
const ct = middleware.get(mws, tech, 'currentTime');
const dur = middleware.get(mws, tech, 'duration');
const foo = middleware.get(mws, tech, 'foo');
const assertion = (actual, expected, actualArr, expectedArr, type) => {
assert.equal(actual, expected, `our middleware chain return currectly for ${type}`);
assert.deepEqual(actualArr, expectedArr, `we got called in the correct order for ${type}`);
};
assertion(ct, 14, cts, [5, 7], 'currentTime');
assertion(dur, 4.5, durs, [5, 2.5], 'duration');
assertion(foo, 4, foos, [5, 8], 'foo');
});
QUnit.test('middleware set iterates through the middleware array the right order', function(assert) {
const cts = [];
const durs = [];
const foos = [];
const mws = [
{
currentTime(ct) {
cts.push(ct);
return ct * 2;
},
duration(dur) {
durs.push(dur);
return dur + 2;
},
foo(f) {
foos.push(f);
return f / 2;
}
},
{
currentTime(ct) {
cts.push(ct);
return ct + 2;
},
duration(dur) {
durs.push(dur);
return dur / 2;
},
foo(f) {
foos.push(f);
return f + 3;
}
}
];
const tech = {
currentTime(ct) {
cts.push(ct);
return ct / 2;
},
duration(dur) {
durs.push(dur);
return dur;
},
foo(f) {
foos.push(f);
return f;
}
};
const ct = middleware.set(mws, tech, 'currentTime', 10);
const dur = middleware.set(mws, tech, 'duration', 10);
const foo = middleware.set(mws, tech, 'foo', 10);
const assertion = (actual, expected, actualArr, expectedArr, type) => {
assert.equal(actual, expected, `our middleware chain return currectly for ${type}`);
assert.deepEqual(actualArr, expectedArr, `we got called in the correct order for ${type}`);
};
assertion(ct, 11, cts, [10, 20, 22], 'currentTime');
assertion(dur, 6, durs, [10, 12, 6], 'duration');
assertion(foo, 8, foos, [10, 5, 8], 'foo');
});
QUnit.test('setSource is run asynchronously', function(assert) {
let src;
let acc;
middleware.setSource(window.setTimeout, { src: 'foo', type: 'video/foo' }, function(_src, _acc) {
src = _src;
acc = _acc;
});
assert.equal(src, undefined, 'no src was returned yet');
assert.equal(acc, undefined, 'no accumulator was returned yet');
this.clock.tick(1);
assert.deepEqual(src, {src: 'foo', type: 'video/foo'}, 'we got the same source back');
assert.equal(acc.length, 0, 'we did not accumulate any middleware since there were none');
});
QUnit.test('setSource selects a source based on the middleware given', function(assert) {
let src;
let acc;
const mw = {
setSource(_src, next) {
next(null, {
src: 'http://example.com/video.mp4',
type: 'video/mp4'
});
}
};
middleware.use('video/foo', mw);
middleware.setSource(window.setTimeout, {src: 'foo', type: 'video/foo'}, function(_src, _acc) {
src = _src;
acc = _acc;
});
this.clock.tick(1);
assert.equal(src.type, 'video/mp4', 'we selected a new type of video/mp4');
assert.equal(src.src, 'http://example.com/video.mp4', 'we selected a new src of video.mp4');
assert.equal(acc.length, 1, 'we got one middleware');
assert.equal(acc[0], mw, 'we chose the one middleware');
middleware.getMiddleware('video/foo').pop();
});
QUnit.test('setSource can select multiple middleware from multiple types', function(assert) {
let src;
let acc;
const foomw = {
setSource(_src, next) {
next(null, {
src: 'bar',
type: 'video/bar'
});
}
};
const barmw = {
setSource(_src, next) {
next(null, {
src: 'http://example.com/video.mp4',
type: 'video/mp4'
});
}
};
middleware.use('video/foo', foomw);
middleware.use('video/bar', barmw);
middleware.setSource(window.setTimeout, {src: 'foo', type: 'video/foo'}, function(_src, _acc) {
src = _src;
acc = _acc;
});
this.clock.tick(1);
assert.equal(src.type, 'video/mp4', 'we selected a new type of video/mp4');
assert.equal(src.src, 'http://example.com/video.mp4', 'we selected a new src of video.mp4');
assert.equal(acc.length, 2, 'we got two middleware');
assert.equal(acc[0], foomw, 'foomw is the first middleware');
assert.equal(acc[1], barmw, 'barmw is the first middleware');
middleware.getMiddleware('video/foo').pop();
middleware.getMiddleware('video/bar').pop();
});
QUnit.test('setSource will select all middleware of a given type, until src change', function(assert) {
let src;
let acc;
const foomw1 = {
setSource(_src, next) {
next(null, {
src: 'bar',
type: 'video/foo'
});
}
};
const foomw2 = {
setSource(_src, next) {
next(null, {
src: 'http://example.com/video.mp4',
type: 'video/mp4'
});
}
};
const foomw3 = {
setSource(_src, next) {
next(null, {
src: 'http://example.com/video.mp4',
type: 'video/mp4'
});
}
};
middleware.use('video/foo', foomw1);
middleware.use('video/foo', foomw2);
middleware.use('video/foo', foomw3);
middleware.setSource(window.setTimeout, {src: 'foo', type: 'video/foo'}, function(_src, _acc) {
src = _src;
acc = _acc;
});
this.clock.tick(1);
assert.equal(src.type, 'video/mp4', 'we selected a new type of video/mp4');
assert.equal(src.src, 'http://example.com/video.mp4', 'we selected a new src of video.mp4');
assert.equal(acc.length, 2, 'we got two middleware');
assert.equal(acc[0], foomw1, 'foomw is the first middleware');
assert.equal(acc[1], foomw2, 'foomw is the first middleware');
middleware.getMiddleware('video/foo').pop();
middleware.getMiddleware('video/foo').pop();
});

View File

@ -30,6 +30,8 @@ class TechFaker extends Tech {
setControls(val) {}
setVolume(newVolume) {}
currentTime() {
return 0;
}

View File

@ -21,7 +21,11 @@ const TestHelpers = {
playerOptions = playerOptions || {};
playerOptions.techOrder = playerOptions.techOrder || ['techFaker'];
return new Player(videoTag, playerOptions);
const player = new Player(videoTag, playerOptions);
player.middleware_ = [player.tech_];
return player;
},
getComputedStyle(el, rule) {

View File

@ -419,7 +419,7 @@ QUnit.test('should return correct remote text track values', function(assert) {
const tag = document.getElementById('example_1');
const player = TestHelpers.makePlayer({}, tag);
this.clock.tick(1);
this.clock.tick(10);
assert.equal(player.remoteTextTracks().length, 1, 'add text track via html');
assert.equal(player.remoteTextTrackEls().length, 1, 'add html track element via html');

View File

@ -3,8 +3,16 @@ import videojs from '../../src/js/video.js';
import * as Dom from '../../src/js/utils/dom.js';
import log from '../../src/js/utils/log.js';
import document from 'global/document';
import sinon from 'sinon';
QUnit.module('video.js');
QUnit.module('video.js', {
beforeEach() {
this.clock = sinon.useFakeTimers();
},
afterEach() {
this.clock.restore();
}
});
QUnit.test('should create a video tag and have access children in old IE', function(assert) {
const fixture = document.getElementById('qunit-fixture');
@ -276,6 +284,8 @@ QUnit.test('ingested player div should not create a new tag for movingMediaEleme
techOrder: ['html5']
});
this.clock.tick(1);
assert.equal(player.el(), playerDiv, 'we re-used the given div');
assert.equal(player.tech_.el(), vid, 'we re-used the video element');
assert.ok(player.hasClass('foo'), 'keeps any classes that were around previously');
@ -311,6 +321,8 @@ QUnit.test('should create a new tag for movingMediaElementInDOM', function(asser
techOrder: ['html5']
});
this.clock.tick(1);
assert.notEqual(player.el(), playerDiv, 'we used a new div');
assert.notEqual(player.tech_.el(), vid, 'we a new video element');