mirror of
https://github.com/videojs/video.js.git
synced 2025-04-07 07:19:54 +02:00
fix: fire sourceset on initial source append (#5038)
In Chrome/Firefox/Safari appending a <source> element when the media element has no source, causes what we think of as a `sourceset`. These changes make our code actual fire that event.
This commit is contained in:
parent
fdcae1b469
commit
9eb5de7ec7
@ -919,13 +919,15 @@ Html5.canControlPlaybackRate = function() {
|
||||
* - False otherwise
|
||||
*/
|
||||
Html5.canOverrideAttributes = function() {
|
||||
// if we cannot overwrite the src property, there is no support
|
||||
// if we cannot overwrite the src/innerHTML 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});
|
||||
Object.defineProperty(document.createElement('video'), 'innerHTML', {get: noop, set: noop});
|
||||
Object.defineProperty(document.createElement('audio'), 'innerHTML', {get: noop, set: noop});
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,21 +1,152 @@
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import mergeOptions from '../utils/merge-options';
|
||||
|
||||
const setupSourceset = function(tech) {
|
||||
/**
|
||||
* This function is used to fire a sourceset when there is something
|
||||
* similar to `mediaEl.load()` being called. It will try to find the source via
|
||||
* the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
|
||||
* with the source that was found or empty string if we cannot know. If it cannot
|
||||
* find a source then `sourceset` will not be fired.
|
||||
*
|
||||
* @param {Html5} tech
|
||||
* The tech object that sourceset was setup on
|
||||
*
|
||||
* @return {boolean}
|
||||
* returns false if the sourceset was not fired and true otherwise.
|
||||
*/
|
||||
const sourcesetLoad = (tech) => {
|
||||
const el = tech.el();
|
||||
|
||||
if (!tech.featuresSourceset) {
|
||||
// if `el.src` is set, that source will be loaded.
|
||||
if (el.src) {
|
||||
tech.triggerSourceset(el.src);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since there isn't a src property on the media element, source elements will be used for
|
||||
* implementing the source selection algorithm. This happens asynchronously and
|
||||
* for most cases were there is more than one source we cannot tell what source will
|
||||
* be loaded, without re-implementing the source selection algorithm. At this time we are not
|
||||
* going to do that. There are three special cases that we do handle here though:
|
||||
*
|
||||
* 1. If there are no sources, do not fire `sourceset`.
|
||||
* 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
|
||||
* 3. If there is more than one `<source>` but all of them have the same `src` url.
|
||||
* That will be our src.
|
||||
*/
|
||||
const sources = tech.$$('source');
|
||||
const srcUrls = [];
|
||||
let src = '';
|
||||
|
||||
// if there are no sources, do not fire sourceset
|
||||
if (!sources.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only count valid/non-duplicate source elements
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const url = sources[i].src;
|
||||
|
||||
if (url && srcUrls.indexOf(url) === -1) {
|
||||
srcUrls.push(url);
|
||||
}
|
||||
}
|
||||
|
||||
// there were no valid sources
|
||||
if (!srcUrls.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = tech.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 && tech.el().initNetworkState_ !== 3) {
|
||||
tech.triggerSourceset(el.src || el.currentSrc);
|
||||
// there is only one valid source element url
|
||||
// use that
|
||||
if (srcUrls.length === 1) {
|
||||
src = srcUrls[0];
|
||||
}
|
||||
|
||||
tech.triggerSourceset(src);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the browsers property descriptor for the `innerHTML`
|
||||
* property. This will allow us to overwrite it without
|
||||
* destroying native functionality.
|
||||
*
|
||||
* @param {HTMLMediaElement} el
|
||||
* The tech element that should be used to get the descriptor
|
||||
*
|
||||
* @return {Object}
|
||||
* The property descriptor for innerHTML.
|
||||
*/
|
||||
const getInnerHTMLDescriptor = (el) => {
|
||||
const proto = window.Element.prototype;
|
||||
let innerDescriptor = {};
|
||||
|
||||
// preserve getters/setters already on `el.innerHTML` if they exist
|
||||
if (Object.getOwnPropertyDescriptor(el, 'innerHTML')) {
|
||||
innerDescriptor = Object.getOwnPropertyDescriptor(el, 'innerHTML');
|
||||
} else if (Object.getOwnPropertyDescriptor(proto, 'innerHTML')) {
|
||||
innerDescriptor = Object.getOwnPropertyDescriptor(proto, 'innerHTML');
|
||||
}
|
||||
|
||||
if (!innerDescriptor.get) {
|
||||
innerDescriptor.get = function() {
|
||||
return el.cloneNode().innerHTML;
|
||||
};
|
||||
}
|
||||
|
||||
if (!innerDescriptor.set) {
|
||||
innerDescriptor.set = function(v) {
|
||||
// remove all current content from inside
|
||||
el.innerText = '';
|
||||
|
||||
// make a dummy node to use innerHTML on
|
||||
const dummy = document.createElement(el.nodeName.toLowerCase());
|
||||
|
||||
// set innerHTML to the value provided
|
||||
dummy.innerHTML = v;
|
||||
|
||||
// make a document fragment to hold the nodes from dummy
|
||||
const docFrag = document.createDocumentFragment();
|
||||
|
||||
// copy all of the nodes created by the innerHTML on dummy
|
||||
// to the document fragment
|
||||
while (dummy.childNodes.length) {
|
||||
docFrag.appendChild(dummy.childNodes[0]);
|
||||
}
|
||||
|
||||
// now we add all of that html in one by appending the
|
||||
// document fragment. This is how innerHTML does it.
|
||||
window.Element.prototype.appendChild.call(el, docFrag);
|
||||
|
||||
// then return the result that innerHTML's setter would
|
||||
return el.innerHTML;
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof innerDescriptor.enumerable === 'undefined') {
|
||||
innerDescriptor.enumerable = true;
|
||||
}
|
||||
|
||||
innerDescriptor.configurable = true;
|
||||
|
||||
return innerDescriptor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the browsers property descriptor for the `src`
|
||||
* property. This will allow us to overwrite it without
|
||||
* destroying native functionality.
|
||||
*
|
||||
* @param {HTMLMediaElement} el
|
||||
* The tech element that should be used to get the descriptor
|
||||
*
|
||||
* @return {Object}
|
||||
* The property descriptor for `src`.
|
||||
*/
|
||||
const getSrcDescriptor = (el) => {
|
||||
const proto = window.HTMLMediaElement.prototype;
|
||||
let srcDescriptor = {};
|
||||
|
||||
@ -42,12 +173,158 @@ const setupSourceset = function(tech) {
|
||||
srcDescriptor.enumerable = true;
|
||||
}
|
||||
|
||||
srcDescriptor.configurable = true;
|
||||
|
||||
return srcDescriptor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Patches browser internal functions so that we can tell syncronously
|
||||
* if a `<source>` was appended to the media element. For some reason this
|
||||
* causes a `sourceset` if the the media element is ready and has no source.
|
||||
* This happens when:
|
||||
* - The page has just loaded and the media element does not have a source.
|
||||
* - The media element was emptied of all sources, then `load()` was called.
|
||||
*
|
||||
* It does this by patching the following functions/properties when they are supported:
|
||||
*
|
||||
* - `append()` - can be used to add a `<source>` element to the media element
|
||||
* - `appendChild()` - can be used to add a `<source>` element to the media element
|
||||
* - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
|
||||
* - `innerHTML` - can be used to add a `<source>` element to the media element
|
||||
*
|
||||
* @param {Html5} tech
|
||||
* The tech object that sourceset is being setup on.
|
||||
*/
|
||||
const firstSourceWatch = function(tech) {
|
||||
const el = tech.el();
|
||||
|
||||
// make sure firstSourceWatch isn't setup twice.
|
||||
if (el.firstSourceWatch_) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.firstSourceWatch_ = true;
|
||||
const oldAppend = el.append;
|
||||
const oldAppendChild = el.appendChild;
|
||||
const oldInsertAdjacentHTML = el.insertAdjacentHTML;
|
||||
const innerDescriptor = getInnerHTMLDescriptor(el);
|
||||
|
||||
el.appendChild = function() {
|
||||
const retval = oldAppendChild.apply(el, arguments);
|
||||
|
||||
sourcesetLoad(tech);
|
||||
|
||||
return retval;
|
||||
};
|
||||
|
||||
if (oldAppend) {
|
||||
el.append = function() {
|
||||
const retval = oldAppend.apply(el, arguments);
|
||||
|
||||
sourcesetLoad(tech);
|
||||
|
||||
return retval;
|
||||
};
|
||||
}
|
||||
|
||||
if (oldInsertAdjacentHTML) {
|
||||
el.insertAdjacentHTML = function() {
|
||||
const retval = oldInsertAdjacentHTML.apply(el, arguments);
|
||||
|
||||
sourcesetLoad(tech);
|
||||
|
||||
return retval;
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperty(el, 'innerHTML', {
|
||||
get: innerDescriptor.get.bind(el),
|
||||
set(v) {
|
||||
const retval = innerDescriptor.set.call(el, v);
|
||||
|
||||
sourcesetLoad(tech);
|
||||
|
||||
return retval;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: innerDescriptor.enumerable
|
||||
});
|
||||
|
||||
// on the first sourceset, we need to revert
|
||||
// our changes
|
||||
tech.one('sourceset', (e) => {
|
||||
el.firstSourceWatch_ = false;
|
||||
el.appendChild = oldAppendChild;
|
||||
|
||||
if (oldAppend) {
|
||||
el.append = oldAppend;
|
||||
}
|
||||
if (oldInsertAdjacentHTML) {
|
||||
el.insertAdjacentHTML = oldInsertAdjacentHTML;
|
||||
}
|
||||
|
||||
Object.defineProperty(el, 'innerHTML', innerDescriptor);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* setup `sourceset` handling on the `Html5` tech. This function
|
||||
* patches the following element properties/functions:
|
||||
*
|
||||
* - `src` - to determine when `src` is set
|
||||
* - `setAttribute()` - to determine when `src` is set
|
||||
* - `load()` - this re-triggers the source selection algorithm, and can
|
||||
* cause a sourceset.
|
||||
*
|
||||
* If there is no source when we are adding `sourceset` support or during a `load()`
|
||||
* we also patch the functions listed in `firstSourceWatch`.
|
||||
*
|
||||
* @param {Html5} tech
|
||||
* The tech to patch
|
||||
*/
|
||||
const setupSourceset = function(tech) {
|
||||
if (!tech.featuresSourceset) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = tech.el();
|
||||
|
||||
// make sure sourceset isn't setup twice.
|
||||
if (el.setupSourceset_) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.setupSourceset_ = true;
|
||||
|
||||
const srcDescriptor = getSrcDescriptor(el);
|
||||
const oldSetAttribute = el.setAttribute;
|
||||
const oldLoad = el.load;
|
||||
|
||||
// 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 && el.initNetworkState_ !== 3) {
|
||||
if (el.currentSrc) {
|
||||
tech.triggerSourceset(el.currentSrc);
|
||||
} else {
|
||||
sourcesetLoad(tech);
|
||||
}
|
||||
}
|
||||
|
||||
// for some reason adding a source element when a mediaElement has no source
|
||||
// calls `load` internally right away. We need to handle that.
|
||||
if (!el.src && !el.currentSrc && !tech.$$('source').length) {
|
||||
firstSourceWatch(tech);
|
||||
}
|
||||
|
||||
Object.defineProperty(el, 'src', {
|
||||
get: srcDescriptor.get.bind(el),
|
||||
set: (v) => {
|
||||
const retval = srcDescriptor.set.call(el, v);
|
||||
|
||||
tech.triggerSourceset(v);
|
||||
// we use the getter here to get the actual value set on src
|
||||
tech.triggerSourceset(el.src);
|
||||
|
||||
return retval;
|
||||
},
|
||||
@ -55,29 +332,26 @@ const setupSourceset = function(tech) {
|
||||
enumerable: srcDescriptor.enumerable
|
||||
});
|
||||
|
||||
const oldSetAttribute = el.setAttribute;
|
||||
|
||||
el.setAttribute = (n, v) => {
|
||||
const retval = oldSetAttribute.call(el, n, v);
|
||||
|
||||
if (n === 'src') {
|
||||
tech.triggerSourceset(v);
|
||||
tech.triggerSourceset(el.getAttribute('src'));
|
||||
}
|
||||
|
||||
return retval;
|
||||
};
|
||||
|
||||
const oldLoad = el.load;
|
||||
|
||||
el.load = () => {
|
||||
const retval = oldLoad.call(el);
|
||||
|
||||
// if `el.src` is set, that source will be loaded
|
||||
// otherwise, we can't know for sure what source will be set because
|
||||
// source elements will be used but implementing the source selection algorithm
|
||||
// is laborious and asynchronous, so,
|
||||
// instead return an empty string to basically indicate source may change
|
||||
tech.triggerSourceset(el.src || '');
|
||||
// if load was called, but there was no source to fire
|
||||
// sourceset on. We have to watch for a source append
|
||||
// as that can trigger a `sourceset` when the media element
|
||||
// has no source
|
||||
if (!sourcesetLoad(tech)) {
|
||||
firstSourceWatch(tech);
|
||||
}
|
||||
|
||||
return retval;
|
||||
};
|
||||
|
@ -161,51 +161,6 @@ QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) one source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {
|
||||
enableSourceset: true
|
||||
});
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src(this.testSrc);
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) preload auto', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('preload', 'auto');
|
||||
this.player = videojs(this.mediaEl, {
|
||||
enableSourceset: true
|
||||
});
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src(this.testSrc);
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) two sources', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {
|
||||
enableSourceset: true
|
||||
});
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src([this.sourceOne, this.sourceTwo]);
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.src = ...;', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
@ -292,6 +247,320 @@ QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
});
|
||||
}));
|
||||
|
||||
QUnit.module('source after player', (subhooks) => testTypes.forEach((testName) => {
|
||||
QUnit.module(testName, {
|
||||
beforeEach() {
|
||||
sinon.stub(log, 'error');
|
||||
|
||||
setupEnv(this, testName);
|
||||
},
|
||||
afterEach: setupAfterEach(1)
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) one source', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {
|
||||
enableSourceset: true
|
||||
});
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src(this.testSrc);
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) preload auto', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.mediaEl.setAttribute('preload', 'auto');
|
||||
this.player = videojs(this.mediaEl, {
|
||||
enableSourceset: true
|
||||
});
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src(this.testSrc);
|
||||
});
|
||||
|
||||
QUnit.test('player.src({...}) two sources', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {
|
||||
enableSourceset: true
|
||||
});
|
||||
|
||||
this.player.one('sourceset', () => {
|
||||
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.src([this.sourceOne, this.sourceTwo]);
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.src = ...;', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.tech_.el_.src = this.testSrc.src;
|
||||
});
|
||||
|
||||
QUnit.test('mediaEl.setAttribute("src", ...)"', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
validateSource(assert, this.player, [this.testSrc]);
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.tech_.el_.setAttribute('src', this.testSrc.src);
|
||||
});
|
||||
|
||||
const appendTypes = [
|
||||
{name: 'appendChild', fn: (el, obj) => el.appendChild(obj)},
|
||||
{name: 'innerHTML', fn: (el, obj) => {el.innerHTML = obj.outerHTML;}}, // eslint-disable-line
|
||||
];
|
||||
|
||||
// ie does not support this and safari < 10 does not either
|
||||
if (window.Element.prototype.append) {
|
||||
appendTypes.push({name: 'append', fn: (el, obj) => el.append(obj)});
|
||||
}
|
||||
|
||||
if (window.Element.prototype.insertAdjacentHTML) {
|
||||
appendTypes.push({name: 'insertAdjacentHTML', fn: (el, obj) => el.insertAdjacentHTML('afterbegin', obj.outerHTML)});
|
||||
}
|
||||
|
||||
appendTypes.forEach((appendObj) => {
|
||||
|
||||
QUnit.test(`<source> one source through ${appendObj.name}`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
assert.equal(e.src, this.testSrc.src, 'source is as expected');
|
||||
done();
|
||||
});
|
||||
|
||||
// since the media el has no source, just appending will
|
||||
// change the source without calling load
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
});
|
||||
|
||||
QUnit.test(`<source> one source through ${appendObj.name} and load`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.totalSourcesets = 2;
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e1) => {
|
||||
assert.equal(e1.src, this.testSrc.src, 'event has expected source');
|
||||
|
||||
this.player.one('sourceset', (e2) => {
|
||||
assert.equal(e2.src, this.testSrc.src, 'second event has expected source');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// since the media el has no source, just appending will
|
||||
// change the source without calling load
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
|
||||
// should fire an additional sourceset
|
||||
this.player.tech_.el_.load();
|
||||
});
|
||||
|
||||
QUnit.test(`one <source> through ${appendObj.name} and then mediaEl.src`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.totalSourcesets = 2;
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
assert.equal(e.src, this.testSrc.src, 'source is as expected');
|
||||
|
||||
this.player.one('sourceset', (e2) => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// since the media el has no source, just appending will
|
||||
// change the source without calling load
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
|
||||
// should fire an additional sourceset
|
||||
this.player.tech_.el_.src = this.sourceOne.src;
|
||||
});
|
||||
|
||||
QUnit.test(`one <source> through ${appendObj.name} and then mediaEl.setAttribute`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.totalSourcesets = 2;
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
assert.equal(e.src, this.testSrc.src, 'source is as expected');
|
||||
|
||||
this.player.one('sourceset', (e2) => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// since the media el has no source, just appending will
|
||||
// change the source without calling load
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
|
||||
// should fire an additional sourceset
|
||||
this.player.tech_.el_.setAttribute('src', this.sourceOne.src);
|
||||
});
|
||||
|
||||
QUnit.test(`mediaEl.src and then <source> through ${appendObj.name}`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.tech_.el_.src = this.sourceOne.src;
|
||||
|
||||
// should not fire sourceset
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
});
|
||||
|
||||
QUnit.test(`mediaEl.setAttribute and then <source> through ${appendObj.name}`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.testSrc.src;
|
||||
this.source.type = this.testSrc.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
this.player.tech_.el_.setAttribute('src', this.sourceOne.src);
|
||||
|
||||
// should not fire sourceset
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
});
|
||||
|
||||
QUnit.test(`<source> two sources through ${appendObj.name}`, 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.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e) => {
|
||||
assert.equal(e.src, this.sourceOne.src, 'source is as expected');
|
||||
done();
|
||||
});
|
||||
|
||||
// since the media el has no source, just appending will
|
||||
// change the source without calling load
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
|
||||
// this should not be in the source list or fire a sourceset
|
||||
appendObj.fn(this.player.tech_.el_, this.source2);
|
||||
});
|
||||
|
||||
QUnit.test(`set, remove, load, and set again through ${appendObj.name}`, function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.totalSourcesets = 2;
|
||||
this.source = document.createElement('source');
|
||||
this.source.src = this.sourceTwo.src;
|
||||
this.source.type = this.sourceTwo.type;
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
|
||||
this.player.one('sourceset', (e1) => {
|
||||
validateSource(assert, this.player, [this.sourceOne]);
|
||||
|
||||
this.player.one('sourceset', (e2) => {
|
||||
validateSource(assert, this.player, [this.sourceTwo], false);
|
||||
done();
|
||||
});
|
||||
|
||||
// reset to no source
|
||||
this.player.tech_.el_.removeAttribute('src');
|
||||
this.player.tech_.el_.load();
|
||||
|
||||
// since the media el has no source, just appending will
|
||||
// change the source without calling load
|
||||
appendObj.fn(this.player.tech_.el_, this.source);
|
||||
|
||||
});
|
||||
|
||||
this.player.tech_.el_.setAttribute('src', this.sourceOne.src);
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('no source and load', function(assert) {
|
||||
const done = assert.async();
|
||||
|
||||
this.player = videojs(this.mediaEl, {enableSourceset: true});
|
||||
this.player.tech_.el_.load();
|
||||
|
||||
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) {
|
||||
@ -451,10 +720,10 @@ QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
this.mediaEl.removeAttribute('src');
|
||||
|
||||
this.player.one('sourceset', (e1) => {
|
||||
assert.equal(e1.src, '', 'we got a sourceset with an empty src');
|
||||
assert.equal(e1.src, this.testSrc.src, 'we got a sourceset with the expected src');
|
||||
|
||||
this.player.one('sourceset', (e2) => {
|
||||
assert.equal(e2.src, '', 'we got a sourceset with an empty src');
|
||||
assert.equal(e2.src, this.sourceOne.src, 'we got a sourceset with the expected src');
|
||||
});
|
||||
|
||||
source.src = this.sourceOne.src;
|
||||
@ -474,10 +743,10 @@ QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
source.type = this.sourceOne.type;
|
||||
|
||||
this.player.one('sourceset', (e1) => {
|
||||
assert.equal(e1.src, '', 'we got a sourceset with an empty src');
|
||||
assert.equal(e1.src, this.sourceOne.src, 'we got a sourceset with the expected src');
|
||||
|
||||
this.player.one('sourceset', (e2) => {
|
||||
assert.equal(e2.src, '', 'we got a sourceset with an empty src');
|
||||
assert.equal(e2.src, this.sourceTwo.src, 'we got a sourceset with the expected src');
|
||||
});
|
||||
});
|
||||
|
||||
@ -538,7 +807,7 @@ QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
src: 'http://example.com/oceans.flv',
|
||||
type: 'video/flv'
|
||||
};
|
||||
let sourcesets = 0;
|
||||
const sourcesets = [];
|
||||
|
||||
class FakeFlash extends Html5 {
|
||||
static isSupported() {
|
||||
@ -563,38 +832,27 @@ QUnit[qunitFn]('sourceset', function(hooks) {
|
||||
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 first 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.on('sourceset', (e) => {
|
||||
sourcesets.push(e.src);
|
||||
|
||||
// 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++;
|
||||
if (sourcesets.length === 3) {
|
||||
assert.deepEqual([flashSrc.src, sourceTwo.src, sourceOne.src], sourcesets, 'sourceset as expected');
|
||||
|
||||
// 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.dispose();
|
||||
delete Tech.techs_.FakeFlash;
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
player.src(sourceOne);
|
||||
player.src(sourceTwo);
|
||||
});
|
||||
|
||||
player.src(flashSrc);
|
||||
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user