mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	fix: sourceset and browser behavior inconsistencies (#5054)
				
					
				
			* We now trigger `sourceset` any time a `<source>` element is appended to a mediaEl with no source. * `load` should always fire a `sourceset` * `sourceset` should always be the absolute url.
This commit is contained in:
		| @@ -2612,9 +2612,11 @@ class Player extends Component { | ||||
|  | ||||
|     if (!titleCaseEquals(sourceTech.tech, this.techName_)) { | ||||
|       this.changingSrc_ = true; | ||||
|  | ||||
|       // load this technology with the chosen source | ||||
|       this.loadTech_(sourceTech.tech, sourceTech.source); | ||||
|       this.tech_.ready(() => { | ||||
|         this.changingSrc_ = false; | ||||
|       }); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -35,10 +35,6 @@ class Html5 extends Tech { | ||||
|   constructor(options, ready) { | ||||
|     super(options, ready); | ||||
|  | ||||
|     if (options.enableSourceset) { | ||||
|       this.setupSourcesetHandling_(); | ||||
|     } | ||||
|  | ||||
|     const source = options.source; | ||||
|     let crossoriginTracks = false; | ||||
|  | ||||
| @@ -52,6 +48,11 @@ class Html5 extends Tech { | ||||
|       this.handleLateInit_(this.el_); | ||||
|     } | ||||
|  | ||||
|     // setup sourceset after late sourceset/init | ||||
|     if (options.enableSourceset) { | ||||
|       this.setupSourcesetHandling_(); | ||||
|     } | ||||
|  | ||||
|     if (this.el_.hasChildNodes()) { | ||||
|  | ||||
|       const nodes = this.el_.childNodes; | ||||
| @@ -117,6 +118,9 @@ class Html5 extends Tech { | ||||
|    * Dispose of `HTML5` media element and remove all tracks. | ||||
|    */ | ||||
|   dispose() { | ||||
|     if (this.el_.resetSourceset_) { | ||||
|       this.el_.resetSourceset_(); | ||||
|     } | ||||
|     Html5.disposeMediaElement(this.el_); | ||||
|     this.options_ = null; | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import window from 'global/window'; | ||||
| import document from 'global/document'; | ||||
| import mergeOptions from '../utils/merge-options'; | ||||
| import {getAbsoluteURL} from '../utils/url'; | ||||
|  | ||||
| /** | ||||
|  * This function is used to fire a sourceset when there is something | ||||
| @@ -19,7 +20,7 @@ const sourcesetLoad = (tech) => { | ||||
|   const el = tech.el(); | ||||
|  | ||||
|   // if `el.src` is set, that source will be loaded. | ||||
|   if (el.src) { | ||||
|   if (el.hasAttribute('src')) { | ||||
|     tech.triggerSourceset(el.src); | ||||
|     return true; | ||||
|   } | ||||
| @@ -56,7 +57,7 @@ const sourcesetLoad = (tech) => { | ||||
|  | ||||
|   // there were no valid sources | ||||
|   if (!srcUrls.length) { | ||||
|     return; | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // there is only one valid source element url | ||||
| @@ -70,114 +71,69 @@ const sourcesetLoad = (tech) => { | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 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. | ||||
|  * our implementation of an `innerHTML` descriptor for browsers | ||||
|  * that do not have one. | ||||
|  */ | ||||
| const getInnerHTMLDescriptor = (el) => { | ||||
|   const proto = window.Element.prototype; | ||||
|   let innerDescriptor = {}; | ||||
| const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { | ||||
|   get() { | ||||
|     return this.cloneNode(true).innerHTML; | ||||
|   }, | ||||
|   set(v) { | ||||
|     // make a dummy node to use innerHTML on | ||||
|     const dummy = document.createElement(this.nodeName.toLowerCase()); | ||||
|  | ||||
|   // 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'); | ||||
|     // 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]); | ||||
|     } | ||||
|  | ||||
|     // remove content | ||||
|     this.innerText = ''; | ||||
|  | ||||
|     // 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(this, docFrag); | ||||
|  | ||||
|     // then return the result that innerHTML's setter would | ||||
|     return this.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`. | ||||
|  * Get a property descriptor given a list of priorities and the | ||||
|  * property to get. | ||||
|  */ | ||||
| const getSrcDescriptor = (el) => { | ||||
|   const proto = window.HTMLMediaElement.prototype; | ||||
|   let srcDescriptor = {}; | ||||
| const getDescriptor = (priority, prop) => { | ||||
|   let descriptor = {}; | ||||
|  | ||||
|   // 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')); | ||||
|   for (let i = 0; i < priority.length; i++) { | ||||
|     descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); | ||||
|  | ||||
|     if (descriptor && descriptor.set && descriptor.get) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!srcDescriptor.get) { | ||||
|     srcDescriptor.get = function() { | ||||
|       return proto.getAttribute.call(el, 'src'); | ||||
|     }; | ||||
|   } | ||||
|   descriptor.enumerable = true; | ||||
|   descriptor.configurable = true; | ||||
|  | ||||
|   if (!srcDescriptor.set) { | ||||
|     srcDescriptor.set = function(v) { | ||||
|       return proto.setAttribute.call(el, 'src', v); | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   if (typeof srcDescriptor.enumerable === 'undefined') { | ||||
|     srcDescriptor.enumerable = true; | ||||
|   } | ||||
|  | ||||
|   srcDescriptor.configurable = true; | ||||
|  | ||||
|   return srcDescriptor; | ||||
|   return descriptor; | ||||
| }; | ||||
|  | ||||
| const getInnerHTMLDescriptor = (tech) => getDescriptor([ | ||||
|   tech.el(), | ||||
|   window.HTMLMediaElement.prototype, | ||||
|   window.Element.prototype, | ||||
|   innerHTMLDescriptorPolyfill | ||||
| ], 'innerHTML'); | ||||
|  | ||||
| /** | ||||
|  * Patches browser internal functions so that we can tell synchronously | ||||
|  * if a `<source>` was appended to the media element. For some reason this | ||||
| @@ -200,74 +156,71 @@ const firstSourceWatch = function(tech) { | ||||
|   const el = tech.el(); | ||||
|  | ||||
|   // make sure firstSourceWatch isn't setup twice. | ||||
|   if (el.firstSourceWatch_) { | ||||
|   if (el.resetSourceWatch_) { | ||||
|     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); | ||||
|   const old = {}; | ||||
|   const innerDescriptor = getInnerHTMLDescriptor(tech); | ||||
|   const appendWrapper = (appendFn) => (...args) => { | ||||
|     const retval = appendFn.apply(el, args); | ||||
|  | ||||
|     sourcesetLoad(tech); | ||||
|  | ||||
|     return retval; | ||||
|   }; | ||||
|  | ||||
|   if (oldAppend) { | ||||
|     el.append = function() { | ||||
|       const retval = oldAppend.apply(el, arguments); | ||||
|   ['append', 'appendChild', 'insertAdjacentHTML'].forEach((k) => { | ||||
|     if (!el[k]) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|       sourcesetLoad(tech); | ||||
|     // store the old function | ||||
|     old[k] = el[k]; | ||||
|  | ||||
|       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 | ||||
|     // call the old function with a sourceset if a source | ||||
|     // was loaded | ||||
|     el[k] = appendWrapper(old[k]); | ||||
|   }); | ||||
|  | ||||
|   // on the first sourceset, we need to revert | ||||
|   // our changes | ||||
|   tech.one('sourceset', (e) => { | ||||
|     el.firstSourceWatch_ = false; | ||||
|     el.appendChild = oldAppendChild; | ||||
|   Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { | ||||
|     set: appendWrapper(innerDescriptor.set) | ||||
|   })); | ||||
|  | ||||
|     if (oldAppend) { | ||||
|       el.append = oldAppend; | ||||
|     } | ||||
|     if (oldInsertAdjacentHTML) { | ||||
|       el.insertAdjacentHTML = oldInsertAdjacentHTML; | ||||
|     } | ||||
|   el.resetSourceWatch_ = () => { | ||||
|     el.resetSourceWatch_ = null; | ||||
|     Object.keys(old).forEach((k) => { | ||||
|       el[k] = old[k]; | ||||
|     }); | ||||
|  | ||||
|     Object.defineProperty(el, 'innerHTML', innerDescriptor); | ||||
|   }); | ||||
|   }; | ||||
|  | ||||
|   // on the first sourceset, we need to revert our changes | ||||
|   tech.one('sourceset', el.resetSourceWatch_); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * our implementation of a `src` descriptor for browsers | ||||
|  * that do not have one. | ||||
|  */ | ||||
| const srcDescriptorPolyfill = Object.defineProperty({}, 'src', { | ||||
|   get() { | ||||
|     if (this.hasAttribute('src')) { | ||||
|       return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src')); | ||||
|     } | ||||
|  | ||||
|     return ''; | ||||
|   }, | ||||
|   set(v) { | ||||
|     window.Element.prototype.setAttribute.call(this, 'src', v); | ||||
|  | ||||
|     return v; | ||||
|   } | ||||
| }); | ||||
|  | ||||
| const getSrcDescriptor = (tech) => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); | ||||
|  | ||||
| /** | ||||
|  * setup `sourceset` handling on the `Html5` tech. This function | ||||
|  * patches the following element properties/functions: | ||||
| @@ -291,35 +244,15 @@ const setupSourceset = function(tech) { | ||||
|   const el = tech.el(); | ||||
|  | ||||
|   // make sure sourceset isn't setup twice. | ||||
|   if (el.setupSourceset_) { | ||||
|   if (el.resetSourceset_) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   el.setupSourceset_ = true; | ||||
|  | ||||
|   const srcDescriptor = getSrcDescriptor(el); | ||||
|   const srcDescriptor = getSrcDescriptor(tech); | ||||
|   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), | ||||
|   Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { | ||||
|     set: (v) => { | ||||
|       const retval = srcDescriptor.set.call(el, v); | ||||
|  | ||||
| @@ -327,16 +260,14 @@ const setupSourceset = function(tech) { | ||||
|       tech.triggerSourceset(el.src); | ||||
|  | ||||
|       return retval; | ||||
|     }, | ||||
|     configurable: true, | ||||
|     enumerable: srcDescriptor.enumerable | ||||
|   }); | ||||
|     } | ||||
|   })); | ||||
|  | ||||
|   el.setAttribute = (n, v) => { | ||||
|     const retval = oldSetAttribute.call(el, n, v); | ||||
|  | ||||
|     if (n === 'src') { | ||||
|       tech.triggerSourceset(el.getAttribute('src')); | ||||
|     if ((/src/i).test(n)) { | ||||
|       tech.triggerSourceset(el.src); | ||||
|     } | ||||
|  | ||||
|     return retval; | ||||
| @@ -350,11 +281,28 @@ const setupSourceset = function(tech) { | ||||
|     // as that can trigger a `sourceset` when the media element | ||||
|     // has no source | ||||
|     if (!sourcesetLoad(tech)) { | ||||
|       tech.triggerSourceset(''); | ||||
|       firstSourceWatch(tech); | ||||
|     } | ||||
|  | ||||
|     return retval; | ||||
|   }; | ||||
|  | ||||
|   if (el.currentSrc) { | ||||
|     tech.triggerSourceset(el.currentSrc); | ||||
|   } else if (!sourcesetLoad(tech)) { | ||||
|     firstSourceWatch(tech); | ||||
|   } | ||||
|  | ||||
|   el.resetSourceset_ = () => { | ||||
|     el.resetSourceset_ = null; | ||||
|     el.load = oldLoad; | ||||
|     el.setAttribute = oldSetAttribute; | ||||
|     Object.defineProperty(el, 'src', srcDescriptor); | ||||
|     if (el.resetSourceWatch_) { | ||||
|       el.resetSourceWatch_(); | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default setupSourceset; | ||||
|   | ||||
| @@ -35,10 +35,10 @@ const filterSource = function(src) { | ||||
|     src = newsrc; | ||||
|   } else if (typeof src === 'string' && src.trim()) { | ||||
|     // convert string into object | ||||
|     src = [checkMimetype({src})]; | ||||
|     src = [fixSource({src})]; | ||||
|   } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { | ||||
|     // src is already valid | ||||
|     src = [checkMimetype(src)]; | ||||
|     src = [fixSource(src)]; | ||||
|   } else { | ||||
|     // invalid source, turn it into an empty array | ||||
|     src = []; | ||||
| @@ -55,7 +55,7 @@ const filterSource = function(src) { | ||||
|  * @return {Tech~SourceObject} | ||||
|  *        src Object with known type | ||||
|  */ | ||||
| function checkMimetype(src) { | ||||
| function fixSource(src) { | ||||
|   const mimetype = getMimetype(src.src); | ||||
|  | ||||
|   if (!src.type && mimetype) { | ||||
|   | ||||
| @@ -68,7 +68,9 @@ const setupEnv = function(env, testName) { | ||||
|   } | ||||
|  | ||||
|   env.sourcesets = 0; | ||||
|   env.hook = (player) => player.on('sourceset', () => env.sourcesets++); | ||||
|   env.hook = (player) => player.on('sourceset', (e) => { | ||||
|     env.sourcesets++; | ||||
|   }); | ||||
|   videojs.hook('setup', env.hook); | ||||
|  | ||||
|   if ((/audio/i).test(testName)) { | ||||
| @@ -269,6 +271,51 @@ QUnit[qunitFn]('sourceset', function(hooks) { | ||||
|         done(); | ||||
|       }, wait); | ||||
|     }); | ||||
|  | ||||
|     QUnit.test('relative sources are handled correctly', function(assert) { | ||||
|       const done = assert.async(); | ||||
|       const one = {src: 'relative-one.mp4', type: 'video/mp4'}; | ||||
|       const two = {src: '../relative-two.mp4', type: 'video/mp4'}; | ||||
|       const three = {src: './relative-three.mp4?test=test', type: 'video/mp4'}; | ||||
|  | ||||
|       const source = document.createElement('source'); | ||||
|  | ||||
|       source.src = one.src; | ||||
|       source.type = one.type; | ||||
|  | ||||
|       this.mediaEl.appendChild(source); | ||||
|       this.player = videojs(this.mediaEl, {enableSourceset: true}); | ||||
|  | ||||
|       // mediaEl changes on ready | ||||
|       this.player.ready(() => { | ||||
|         this.mediaEl = this.player.tech_.el(); | ||||
|       }); | ||||
|  | ||||
|       this.totalSourcesets = 3; | ||||
|       this.player.one('sourceset', (e) => { | ||||
|         assert.ok(true, '** sourceset with relative source and <source> el'); | ||||
|         // mediaEl attr is relative | ||||
|         validateSource(this.player, {src: getAbsoluteURL(one.src), type: one.type}, e, {attr: one.src}); | ||||
|  | ||||
|         this.player.one('sourceset', (e2) => { | ||||
|           assert.ok(true, '** sourceset with relative source and mediaEl.src'); | ||||
|           // mediaEl attr is relative | ||||
|           validateSource(this.player, {src: getAbsoluteURL(two.src), type: two.type}, e2, {attr: two.src}); | ||||
|  | ||||
|           // setAttribute makes the source absolute | ||||
|           this.player.one('sourceset', (e3) => { | ||||
|             assert.ok(true, '** sourceset with relative source and mediaEl.setAttribute'); | ||||
|             validateSource(this.player, {src: getAbsoluteURL(three.src), type: three.type}, e3, {attr: three.src}); | ||||
|             done(); | ||||
|           }); | ||||
|  | ||||
|           this.mediaEl.setAttribute('src', three.src); | ||||
|         }); | ||||
|  | ||||
|         this.mediaEl.src = two.src; | ||||
|       }); | ||||
|  | ||||
|     }); | ||||
|   })); | ||||
|  | ||||
|   QUnit.module('source after player', (subhooks) => testTypes.forEach((testName) => { | ||||
| @@ -540,7 +587,7 @@ QUnit[qunitFn]('sourceset', function(hooks) { | ||||
|       QUnit.test(`set, remove, load, and set again through ${appendObj.name}`, function(assert) { | ||||
|         const done = assert.async(); | ||||
|  | ||||
|         this.totalSourcesets = 2; | ||||
|         this.totalSourcesets = 3; | ||||
|         this.source = document.createElement('source'); | ||||
|         this.source.src = sourceTwo.src; | ||||
|         this.source.type = sourceTwo.type; | ||||
| @@ -551,8 +598,12 @@ QUnit[qunitFn]('sourceset', function(hooks) { | ||||
|           validateSource(this.player, [sourceOne], e1); | ||||
|  | ||||
|           this.player.one('sourceset', (e2) => { | ||||
|             validateSource(this.player, sourceTwo, e2, {prop: '', attr: ''}); | ||||
|             done(); | ||||
|             validateSource(this.player, [{src: '', type: ''}], e2); | ||||
|  | ||||
|             this.player.one('sourceset', (e3) => { | ||||
|               validateSource(this.player, sourceTwo, e3, {prop: '', attr: ''}); | ||||
|               done(); | ||||
|             }); | ||||
|           }); | ||||
|  | ||||
|           // reset to no source | ||||
| @@ -570,17 +621,10 @@ QUnit[qunitFn]('sourceset', function(hooks) { | ||||
|     }); | ||||
|  | ||||
|     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); | ||||
|       this.totalSourcesets = 1; | ||||
|     }); | ||||
|   })); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user