mirror of
https://github.com/videojs/video.js.git
synced 2025-01-04 06:48:49 +02:00
feat: sourceset event (#4660)
Trigger a sourceset event whenever the source is set in the Html5 tech, including initial source. We override the video element's src and setAttribute methods so that we can trigger the sourceset event when people change the src with both the video element and our API methods. The event object for sourceset will contain the src string that was provided at the time the sourceset was triggered. This is mostly important if a source is being set while a tech is changing. A Tech has a featuresSourceset option that it can set to for sourceset handling. It can then call the helper triggerSourceset(src) to trigger the sourceset.
This commit is contained in:
parent
1fa9dfbee2
commit
df96a74f6b
@ -964,6 +964,7 @@ class Player extends Component {
|
||||
this.on(this.tech_, event, this[`handleTech${toTitleCase(event)}_`]);
|
||||
});
|
||||
this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
|
||||
this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
|
||||
this.on(this.tech_, 'waiting', this.handleTechWaiting_);
|
||||
this.on(this.tech_, 'canplay', this.handleTechCanPlay_);
|
||||
this.on(this.tech_, 'canplaythrough', this.handleTechCanPlayThrough_);
|
||||
@ -1173,6 +1174,40 @@ class Player extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the source is set or changed on the {@link Tech}
|
||||
* causing the media element to reload.
|
||||
*
|
||||
* It will fire for the initial source and each subsequent source.
|
||||
* This event is a custom event from Video.js and is triggered by the {@link Tech}.
|
||||
*
|
||||
* The event object for this event contains a `src` property that will contain the source
|
||||
* that was available when the event was triggered. This is generally only necessary if Video.js
|
||||
* is switching techs while the source was being changed.
|
||||
*
|
||||
* It is also fired when `load` is called on the player (or media element)
|
||||
* because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
|
||||
* says that the resource selection algorithm
|
||||
* needs to be aborted and restarted.
|
||||
*
|
||||
* @event Player#sourceset
|
||||
* @type {EventTarget~Event}
|
||||
* @prop {string} src The source url available when the `sourceset` was triggered
|
||||
*/
|
||||
/**
|
||||
* Retrigger the `sourceset` event that was triggered by the {@link Tech}.
|
||||
*
|
||||
* @fires Player#sourceset
|
||||
* @listens Tech#sourceset
|
||||
* @private
|
||||
*/
|
||||
handleTechSourceset_(event) {
|
||||
this.trigger({
|
||||
src: event.src,
|
||||
type: 'sourceset'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/remove the vjs-has-started class
|
||||
*
|
||||
|
@ -34,6 +34,8 @@ class Html5 extends Tech {
|
||||
constructor(options, ready) {
|
||||
super(options, ready);
|
||||
|
||||
this.setupSourcesetHandling_();
|
||||
|
||||
const source = options.source;
|
||||
let crossoriginTracks = false;
|
||||
|
||||
@ -119,6 +121,86 @@ class Html5 extends Tech {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the media element so that we can detect when
|
||||
* the source is changed. Fires `sourceset` just after the source has changed
|
||||
*/
|
||||
setupSourcesetHandling_() {
|
||||
if (!this.featuresSourceset) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = this.el();
|
||||
|
||||
// we need to fire sourceset when the player is ready
|
||||
// if we find that the media element had a src when it was
|
||||
// given to us and that tech element is not in a stalled state
|
||||
if (el.src || el.currentSrc && this.el().initNetworkState_ !== 3) {
|
||||
this.triggerSourceset(el.src || el.currentSrc);
|
||||
}
|
||||
|
||||
const proto = window.HTMLMediaElement.prototype;
|
||||
let srcDescriptor = {};
|
||||
|
||||
// preserve getters/setters already on `el.src` if they exist
|
||||
if (Object.getOwnPropertyDescriptor(el, 'src')) {
|
||||
srcDescriptor = Object.getOwnPropertyDescriptor(el, 'src');
|
||||
} else if (Object.getOwnPropertyDescriptor(proto, 'src')) {
|
||||
srcDescriptor = mergeOptions(srcDescriptor, Object.getOwnPropertyDescriptor(proto, 'src'));
|
||||
}
|
||||
|
||||
if (!srcDescriptor.get) {
|
||||
srcDescriptor.get = function() {
|
||||
return proto.getAttribute.call(this, 'src');
|
||||
};
|
||||
}
|
||||
|
||||
if (!srcDescriptor.set) {
|
||||
srcDescriptor.set = function(v) {
|
||||
return proto.setAttribute.call(this, 'src', v);
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof srcDescriptor.enumerable === 'undefined') {
|
||||
srcDescriptor.enumerable = true;
|
||||
}
|
||||
|
||||
Object.defineProperty(el, 'src', {
|
||||
get: srcDescriptor.get.bind(el),
|
||||
set: (v) => {
|
||||
const retval = srcDescriptor.set.call(el, v);
|
||||
|
||||
this.triggerSourceset(v);
|
||||
|
||||
return retval;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: srcDescriptor.enumerable
|
||||
});
|
||||
|
||||
const oldSetAttribute = el.setAttribute;
|
||||
|
||||
el.setAttribute = (n, v) => {
|
||||
const retval = oldSetAttribute.call(el, n, v);
|
||||
|
||||
if (n === 'src') {
|
||||
this.triggerSourceset(v);
|
||||
}
|
||||
|
||||
return retval;
|
||||
};
|
||||
|
||||
const oldLoad = el.load;
|
||||
|
||||
el.load = () => {
|
||||
const retval = oldLoad.call(el);
|
||||
|
||||
this.triggerSourceset(el.src || el.currentSrc);
|
||||
|
||||
return retval;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* When a captions track is enabled in the iOS Safari native player, all other
|
||||
* tracks are disabled (including metadata tracks), which nulls all of their
|
||||
@ -897,6 +979,32 @@ Html5.canControlPlaybackRate = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if we can override a video/audio elements attributes, with
|
||||
* Object.defineProperty.
|
||||
*
|
||||
* @return {boolean}
|
||||
* - True if builtin attributes can be overriden
|
||||
* - False otherwise
|
||||
*/
|
||||
Html5.canOverrideAttributes = function() {
|
||||
if (browser.IS_IE8) {
|
||||
return false;
|
||||
}
|
||||
// if we cannot overwrite the src property, there is no support
|
||||
// iOS 7 safari for instance cannot do this.
|
||||
try {
|
||||
const noop = () => {};
|
||||
|
||||
Object.defineProperty(document.createElement('video'), 'src', {get: noop, set: noop});
|
||||
Object.defineProperty(document.createElement('audio'), 'src', {get: noop, set: noop});
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if native `TextTrack`s are supported by this browser/device.
|
||||
*
|
||||
@ -981,6 +1089,14 @@ Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
|
||||
*/
|
||||
Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
|
||||
|
||||
/**
|
||||
* Boolean indicating wether the `Tech` supports the `sourceset` event.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default
|
||||
*/
|
||||
Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
|
||||
|
||||
/**
|
||||
* Boolean indicating whether the `HTML5` tech currently supports the media element
|
||||
* moving in the DOM. iOS breaks if you move the media element, so this is set this to
|
||||
|
@ -155,6 +155,34 @@ class Tech extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A special function to trigger source set in a way that will allow player
|
||||
* to re-trigger if the player or tech are not ready yet.
|
||||
*
|
||||
* @fires Tech#sourceset
|
||||
* @param {string} src The source string at the time of the source changing.
|
||||
*/
|
||||
triggerSourceset(src) {
|
||||
if (!this.isReady_) {
|
||||
// on initial ready we have to trigger source set
|
||||
// 1ms after ready so that player can watch for it.
|
||||
this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the source is set on the tech causing the media element
|
||||
* to reload.
|
||||
*
|
||||
* @see {@link Player#event:sourceset}
|
||||
* @event Tech#sourceset
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger({
|
||||
src,
|
||||
type: 'sourceset'
|
||||
});
|
||||
}
|
||||
|
||||
/* Fallbacks for unsupported event types
|
||||
================================================================================ */
|
||||
|
||||
@ -989,6 +1017,18 @@ Tech.prototype.featuresPlaybackRate = false;
|
||||
*/
|
||||
Tech.prototype.featuresProgressEvents = false;
|
||||
|
||||
/**
|
||||
* Boolean indicating wether the `Tech` supports the `sourceset` event.
|
||||
*
|
||||
* A tech should set this to `true` and then use {@link Tech#triggerSourceset}
|
||||
* to trigger a {@link Tech#event:sourceset} at the earliest time after getting
|
||||
* a new source.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default
|
||||
*/
|
||||
Tech.prototype.featuresSourceset = false;
|
||||
|
||||
/**
|
||||
* Boolean indicating wether the `Tech` supports the `timeupdate` event. This is currently
|
||||
* not triggered by video-js-swf. This will be used to determine if
|
||||
|
550
test/unit/sourceset.test.js
Normal file
550
test/unit/sourceset.test.js
Normal file
@ -0,0 +1,550 @@
|
||||
/* eslint-env qunit */
|
||||
import videojs from '../../src/js/video.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import log from '../../src/js/utils/log.js';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const Html5 = videojs.getTech('Html5');
|
||||
const wait = 1;
|
||||
let qunitFn = 'module';
|
||||
const testSrc = {
|
||||
src: 'http://vjs.zencdn.net/v/oceans.mp4',
|
||||
type: 'video/mp4'
|
||||
};
|
||||
const sourceOne = {src: 'http://example.com/one.mp4', type: 'video/mp4'};
|
||||
const sourceTwo = {src: 'http://example.com/two.mp4', type: 'video/mp4'};
|
||||
|
||||
if (!Html5.canOverrideAttributes()) {
|
||||
qunitFn = 'skip';
|
||||
}
|
||||
|
||||
const oldMovingMedia = Html5.prototype.movingMediaElementInDOM;
|
||||
const validateSource = function(assert, player, sources, checkMediaElSource = true) {
|
||||
const tech = player.tech_;
|
||||
const mediaEl = tech.el();
|
||||
|
||||
if (checkMediaElSource) {
|
||||
assert.equal(mediaEl.src, sources[0].src, 'mediaEl.src is correct');
|
||||
assert.equal(mediaEl.getAttribute('src'), sources[0].src, 'mediaEl attribute is correct');
|
||||
assert.equal(tech.src(), sources[0].src, 'tech is correct');
|
||||
}
|
||||
};
|
||||
|
||||
const setupEnv = function(env, testName) {
|
||||
env.fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
if (testName === 'change video el' || testName === 'change audio el') {
|
||||
Html5.prototype.movingMediaElementInDOM = false;
|
||||
}
|
||||
|
||||
env.sourcesets = 0;
|
||||
env.hook = (player) => player.on('sourceset', () => env.sourcesets++);
|
||||
videojs.hook('setup', env.hook);
|
||||
|
||||
if ((/audio/i).test(testName)) {
|
||||
env.mediaEl = document.createElement('audio');
|
||||
} else {
|
||||
env.mediaEl = document.createElement('video');
|
||||
}
|
||||
env.testSrc = testSrc;
|
||||
env.sourceOne = sourceOne;
|
||||
env.sourceTwo = sourceTwo;
|
||||
|
||||
env.mediaEl.className = 'video-js';
|
||||
env.fixture.appendChild(env.mediaEl);
|
||||
};
|
||||
|
||||
const setupAfterEach = function(totalSourcesets) {
|
||||
return function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
if (typeof this.totalSourcesets === 'undefined') {
|
||||
this.totalSourcesets = totalSourcesets;
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
assert.equal(this.sourcesets, this.totalSourcesets, 'no additional sourcesets');
|
||||
|
||||
this.player.dispose();
|
||||
assert.equal(this.sourcesets, this.totalSourcesets, 'no source set on dispose');
|
||||
|
||||
videojs.removeHook('setup', this.hook);
|
||||
Html5.prototype.movingMediaElementInDOM = oldMovingMedia;
|
||||
log.error.restore();
|
||||
done();
|
||||
}, wait);
|
||||
};
|
||||
};
|
||||
|
||||
const testTypes = ['video el', 'change video el', 'audio el', 'change audio el'];
|
||||
|
||||
QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
QUnit.module('source before player', (subhooks) => testTypes.forEach((testName) => {
|
||||
QUnit.module(testName, {
|
||||
beforeEach() {
|
||||
sinon.stub(log, 'error');
|
||||
|
||||
setupEnv(this, testName);
|
||||
},
|
||||
afterEach: setupAfterEach(1)
|
||||
});
|
||||
|
||||
QUnit.test('data-setup one source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [this.testSrc]}));
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: unskip when https://github.com/videojs/video.js/pull/4861 is merged
|
||||
QUnit.skip('data-setup preload auto', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [this.testSrc]}));
|
||||
this.mediaEl.setAttribute('preload', 'auto');
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('data-setup two sources', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [this.sourceOne, this.sourceTwo]}));
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('videojs({sources: [...]}) one source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {sources: [this.testSrc]});
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('videojs({sources: [...]}) two sources', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {sources: [this.sourceOne, this.sourceTwo]});
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) one source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl);
|
||||
this.player.src(this.testSrc);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: unskip when https://github.com/videojs/video.js/pull/4861 is merged
|
||||
QUnit.skip('player.src({...}) preload auto', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('preload', 'auto');
|
||||
this.player = videojs(this.mediaEl);
|
||||
this.player.src(this.testSrc);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) two sources', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl);
|
||||
this.player.src([this.sourceOne, this.sourceTwo]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.src = ...;', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.src = this.testSrc.src;
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.setAttribute("src", ...)"', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('src', this.testSrc.src);
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('<source> one source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.mediaEl.appendChild(this.source);
|
||||
|
||||
this.player = videojs(this.mediaEl);
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('<source> two sources', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.sourceOne.src;
|
||||
this.source.type = this.sourceOne.type;
|
||||
|
||||
this.source2 = document.createElement('source');
|
||||
this.source2.src = this.sourceTwo.src;
|
||||
this.source2.type = this.sourceTwo.type;
|
||||
|
||||
this.mediaEl.appendChild(this.source);
|
||||
this.mediaEl.appendChild(this.source2);
|
||||
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('no source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.totalSourcesets = 0;
|
||||
|
||||
window.setTimeout(() => {
|
||||
assert.equal(this.sourcesets, 0, 'no sourceset');
|
||||
done();
|
||||
}, wait);
|
||||
});
|
||||
}));
|
||||
|
||||
QUnit.module('source change', (subhooks) => testTypes.forEach((testName) => {
|
||||
QUnit.module(testName, {
|
||||
beforeEach(assert) {
|
||||
sinon.stub(log, 'error');
|
||||
const done = assert.async();
|
||||
|
||||
setupEnv(this, testName);
|
||||
|
||||
this.mediaEl.src = this.testSrc.src;
|
||||
this.player = videojs(this.mediaEl);
|
||||
|
||||
this.player.ready(() => {
|
||||
this.mediaEl = this.player.tech_.el();
|
||||
});
|
||||
|
||||
// intial sourceset should happen on player.ready
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
},
|
||||
afterEach: setupAfterEach(3)
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...})', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src(this.sourceOne);
|
||||
});
|
||||
|
||||
this.player.src(this.testSrc);
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) x2 at the same time', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
this.player.src(this.sourceOne);
|
||||
this.player.src(this.sourceTwo);
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.src = ...', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.mediaEl.src = this.sourceOne.src;
|
||||
});
|
||||
|
||||
this.mediaEl.src = this.testSrc.src;
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.src = ... x2 at the same time', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
this.mediaEl.src = this.sourceOne.src;
|
||||
this.mediaEl.src = this.sourceTwo.src;
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.setAttribute("src", ...)', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.mediaEl.setAttribute('src', this.sourceOne.src);
|
||||
});
|
||||
|
||||
this.mediaEl.setAttribute('src', this.testSrc.src);
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.setAttribute("src", ...) x2 at the same time', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
this.mediaEl.setAttribute('src', this.sourceOne.src);
|
||||
this.mediaEl.setAttribute('src', this.sourceTwo.src);
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.load()', function(assert) {
|
||||
const done = assert.async();
|
||||
const source = document.createElement('source');
|
||||
|
||||
source.src = this.testSrc.src;
|
||||
source.type = this.testSrc.type;
|
||||
|
||||
// the only way to unset a source, so that we use the source
|
||||
// elements instead
|
||||
this.mediaEl.removeAttribute('src');
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc], false);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne], false);
|
||||
done();
|
||||
});
|
||||
|
||||
source.src = this.sourceOne.src;
|
||||
source.type = this.sourceOne.type;
|
||||
|
||||
this.mediaEl.load();
|
||||
});
|
||||
|
||||
this.mediaEl.appendChild(source);
|
||||
this.mediaEl.load();
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.load() x2 at the same time', function(assert) {
|
||||
const done = assert.async();
|
||||
const source = document.createElement('source');
|
||||
|
||||
source.src = this.sourceOne.src;
|
||||
source.type = this.sourceOne.type;
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne], false);
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceTwo], false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// the only way to unset a source, so that we use the source
|
||||
// elements instead
|
||||
this.mediaEl.removeAttribute('src');
|
||||
this.mediaEl.appendChild(source);
|
||||
this.mediaEl.load();
|
||||
|
||||
source.src = this.sourceTwo.src;
|
||||
source.type = this.sourceTwo.type;
|
||||
this.mediaEl.load();
|
||||
});
|
||||
|
||||
QUnit.test('adding a <source> without load()', function(assert) {
|
||||
const done = assert.async();
|
||||
const source = document.createElement('source');
|
||||
|
||||
source.src = this.testSrc.src;
|
||||
source.type = this.testSrc.type;
|
||||
|
||||
this.mediaEl.appendChild(source);
|
||||
|
||||
this.totalSourcesets = 1;
|
||||
|
||||
window.setTimeout(() => {
|
||||
assert.equal(this.sourcesets, 1, 'does not trigger sourceset');
|
||||
done();
|
||||
}, wait);
|
||||
});
|
||||
|
||||
QUnit.test('changing a <source>s src without load()', function(assert) {
|
||||
const done = assert.async();
|
||||
const source = document.createElement('source');
|
||||
|
||||
source.src = this.testSrc.src;
|
||||
source.type = this.testSrc.type;
|
||||
|
||||
this.mediaEl.appendChild(source);
|
||||
|
||||
source.src = this.testSrc.src;
|
||||
|
||||
this.totalSourcesets = 1;
|
||||
|
||||
window.setTimeout(() => {
|
||||
assert.equal(this.sourcesets, 1, 'does not trigger sourceset');
|
||||
done();
|
||||
}, wait);
|
||||
});
|
||||
}));
|
||||
|
||||
QUnit.test('sourceset event object has a src property', function(assert) {
|
||||
const done = assert.async();
|
||||
const fixture = document.querySelector('#qunit-fixture');
|
||||
const vid = document.createElement('video');
|
||||
const Tech = videojs.getTech('Tech');
|
||||
const flashSrc = {
|
||||
src: 'http://example.com/oceans.flv',
|
||||
type: 'video/flv'
|
||||
};
|
||||
let sourcesets = 0;
|
||||
|
||||
class FakeFlash extends Html5 {
|
||||
static isSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static canPlayType(type) {
|
||||
return type === 'video/flv' ? 'maybe' : '';
|
||||
}
|
||||
|
||||
static canPlaySource(srcObj) {
|
||||
return srcObj.type === 'video/flv';
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerTech('FakeFlash', FakeFlash);
|
||||
|
||||
fixture.appendChild(vid);
|
||||
|
||||
const player = videojs(vid, {
|
||||
techOrder: ['fakeFlash', 'html5']
|
||||
});
|
||||
|
||||
player.src(flashSrc);
|
||||
|
||||
player.ready(function() {
|
||||
// the first sourceset comes from our FakeFlash because it extends Html5 tech
|
||||
// which calls load() on dispose for various reasons
|
||||
player.one('sourceset', function(e1) {
|
||||
// ignore the first sourceset that gets called when disposing the original tech
|
||||
|
||||
// the second sourceset ends up being the second source because when the first source is set
|
||||
// the tech isn't ready so we delay it, then the second source comes and the tech is ready
|
||||
// so it ends up being triggered immediately.
|
||||
player.one('sourceset', function(e2) {
|
||||
assert.equal(e2.src, sourceTwo.src, 'the second sourceset ends up being the second source');
|
||||
sourcesets++;
|
||||
|
||||
// now that the tech is ready, we will re-trigger the original sourceset event
|
||||
// and get the first source
|
||||
player.one('sourceset', function(e3) {
|
||||
assert.equal(e3.src, sourceOne.src, 'the third sourceset is the first source');
|
||||
sourcesets++;
|
||||
|
||||
assert.equal(sourcesets, 2, 'two sourcesets');
|
||||
player.dispose();
|
||||
delete Tech.techs_.FakeFlash;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
player.src(sourceOne);
|
||||
player.src(sourceTwo);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -422,9 +422,11 @@ if (Html5.supportsNativeTextTracks()) {
|
||||
addEventListener: (type, fn) => events.push([type, fn]),
|
||||
removeEventListener: (type, fn) => events.push([type, fn])
|
||||
};
|
||||
const el = document.createElement('div');
|
||||
const el = document.createElement('video');
|
||||
|
||||
el.textTracks = tt;
|
||||
Object.defineProperty(el, 'textTracks', {
|
||||
get: () => tt
|
||||
});
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
const htmlTech = new Html5({el, nativeTextTracks: false});
|
||||
|
Loading…
Reference in New Issue
Block a user