/* 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'; import {getAbsoluteURL} from '../../src/js/utils/url.js'; const Html5 = videojs.getTech('Html5'); const wait = 1; let qunitFn = 'module'; const blobSrc = { src: 'blob:something', type: 'video/mp4' }; const testSrc = { src: 'http://example.com/testSrc.mp4', type: 'video/mp4' }; // Using a real URL here makes the tests work with retryOnError by default // however, this means that it may fail offline const sourceOne = {src: 'https://vjs.zencdn.net/v/oceans.mp4?one', type: 'video/mp4'}; const sourceTwo = {src: 'https://vjs.zencdn.net/v/oceans.mp4?two', type: 'video/mp4'}; const sourceThree = {src: 'https://vjs.zencdn.net/v/oceans.mp4?three', type: 'video/mp4'}; if (!Html5.canOverrideAttributes()) { qunitFn = 'skip'; } const oldMovingMedia = Html5.prototype.movingMediaElementInDOM; const validateSource = function(player, expectedSources, event, srcOverrides = {}) { expectedSources = Array.isArray(expectedSources) ? expectedSources : [expectedSources]; const mediaEl = player.tech_.el(); const assert = QUnit.assert; const expected = { // player cache checks currentSources: expectedSources, currentSource: expectedSources[0], src: expectedSources[0].src, // tech checks event: expectedSources[0].src, attr: expectedSources[0].src, prop: expectedSources[0].src }; Object.keys(srcOverrides).forEach((k) => { // only override known properties if (!expected.hasOwnProperty(k)) { return; } expected[k] = srcOverrides[k]; }); assert.deepEqual(player.currentSource(), expected.currentSource, 'player.currentSource() is correct'); assert.deepEqual(player.currentSources(), expected.currentSources, 'player.currentSources() is correct'); assert.equal(player.src(), expected.src, 'player.src() is correct'); assert.equal(event.src, expected.event, 'event src is correct'); // if we expect a blank attr it will be null instead assert.equal(mediaEl.getAttribute('src'), expected.attr || null, 'mediaEl attribute is correct'); // mediaEl.src source is always absolute, but can be empty string // getAbsoluteURL would return the current url of the page for empty string // so we have to check expected.prop = expected.prop ? getAbsoluteURL(expected.prop) : expected.prop; assert.equal(mediaEl.src, expected.prop, 'mediaEl src property is correct'); }; const setupEnv = function(env, testName) { sinon.stub(log, 'error'); env.fixture = document.getElementById('qunit-fixture'); if ((/^change/i).test(testName)) { Html5.prototype.movingMediaElementInDOM = false; } env.sourcesets = 0; env.hook = (player) => player.on('sourceset', (e) => { env.sourcesets++; }); videojs.hook('setup', env.hook); if ((/video-js/i).test(testName)) { env.mediaEl = document.createElement('video-js'); } else if ((/audio/i).test(testName)) { env.mediaEl = document.createElement('audio'); } else { env.mediaEl = document.createElement('video'); } 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', 'video-js', 'change video-js el']; QUnit[qunitFn]('sourceset', function(hooks) { QUnit.module('sourceset option', (subhooks) => testTypes.forEach((testName) => { QUnit.module(testName, { beforeEach() { setupEnv(this, testName); }, afterEach: setupAfterEach(1) }); QUnit.test('sourceset enabled by default', function(assert) { const done = assert.async(); this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [testSrc]})); this.player = videojs(this.mediaEl, {}); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test('sourceset not triggered if turned off', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: false }); this.totalSourcesets = 0; this.player.one('sourceset', (e) => { this.totalSourcesets = 1; }); this.player.on('loadstart', () => { done(); }); this.player.src(testSrc); }); })); QUnit.module('source before player', (subhooks) => testTypes.forEach((testName) => { QUnit.module(testName, { beforeEach() { 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: [testSrc]})); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test('data-setup one blob', function(assert) { const done = assert.async(); this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [blobSrc]})); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [blobSrc], e); done(); }); }); QUnit.test('data-setup preload auto', function(assert) { const done = assert.async(); this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [testSrc]})); this.mediaEl.setAttribute('preload', 'auto'); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test('data-setup two sources', function(assert) { const done = assert.async(); this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [sourceOne, sourceTwo]})); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [sourceOne, sourceTwo], e); done(); }); }); QUnit.test('videojs({sources: [...]}) one source', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: true, sources: [testSrc] }); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test('videojs({sources: [...]}) one blob', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: true, sources: [blobSrc] }); this.player.one('sourceset', (e) => { validateSource(this.player, [blobSrc], e); done(); }); }); QUnit.test('videojs({sources: [...]}) two sources', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: true, sources: [sourceOne, sourceTwo] }); this.player.one('sourceset', (e) => { validateSource(this.player, [sourceOne, sourceTwo], e); done(); }); }); QUnit.test('mediaEl.src = ...;', function(assert) { const done = assert.async(); this.mediaEl.src = testSrc.src; this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test('mediaEl.src = blob;', function(assert) { const done = assert.async(); this.mediaEl.src = blobSrc.src; this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [{src: blobSrc.src, type: ''}], e); done(); }); }); QUnit.test('mediaEl.setAttribute("src", ...)"', function(assert) { const done = assert.async(); this.mediaEl.setAttribute('src', testSrc.src); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test('mediaEl.setAttribute("src", blob)', function(assert) { const done = assert.async(); this.mediaEl.setAttribute('src', blobSrc.src); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [{src: blobSrc.src, type: ''}], e); done(); }); }); QUnit.test(' one source', function(assert) { const done = assert.async(); this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.mediaEl.appendChild(this.source); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }); QUnit.test(' two sources', function(assert) { const done = assert.async(); this.source = document.createElement('source'); this.source.src = sourceOne.src; this.source.type = sourceOne.type; this.source2 = document.createElement('source'); this.source2.src = sourceTwo.src; this.source2.type = sourceTwo.type; this.mediaEl.appendChild(this.source); this.mediaEl.appendChild(this.source2); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [sourceOne, sourceTwo], e); done(); }); }); QUnit.test('no source', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.totalSourcesets = 0; window.setTimeout(() => { assert.equal(this.sourcesets, 0, 'no sourceset'); 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 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) => { QUnit.module(testName, { beforeEach() { 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', (e) => { validateSource(this.player, [testSrc], e); done(); }); this.player.src(testSrc); }); QUnit.test('player.src({...}) one blob', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [blobSrc], e); done(); }); this.player.src(blobSrc); }); 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', (e) => { validateSource(this.player, [testSrc], e); done(); }); this.player.src(testSrc); }); QUnit.test('player.src({...}) two sources', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.one('sourceset', (e) => { validateSource(this.player, [sourceOne, sourceTwo], e); done(); }); this.player.src([sourceOne, sourceTwo]); }); QUnit.test('mediaEl.src = ...;', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); this.player.tech_.el_.src = testSrc.src; }); QUnit.test('mediaEl.src = blob', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, [{src: blobSrc.src, type: ''}], e); done(); }); this.player.tech_.el_.src = blobSrc.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(this.player, [testSrc], e); done(); }); this.player.tech_.el_.setAttribute('src', testSrc.src); }); QUnit.test('mediaEl.setAttribute("src", blob)"', function(assert) { const done = assert.async(); this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, [{src: blobSrc.src, type: ''}], e); done(); }); this.player.tech_.el_.setAttribute('src', blobSrc.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(` one source through ${appendObj.name}`, function(assert) { const done = assert.async(); this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, testSrc, e, {prop: '', attr: ''}); 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(` one source through ${appendObj.name} and load`, function(assert) { const done = assert.async(); this.totalSourcesets = 2; this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e1) => { validateSource(this.player, testSrc, e1, {prop: '', attr: ''}); this.player.one('sourceset', (e2) => { validateSource(this.player, testSrc, e2, {prop: '', attr: ''}); 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 through ${appendObj.name} and then mediaEl.src`, function(assert) { const done = assert.async(); this.totalSourcesets = 2; this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e1) => { validateSource(this.player, testSrc, e1, {prop: '', attr: ''}); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceOne], e2); 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 = sourceOne.src; }); QUnit.test(`one through ${appendObj.name} and then mediaEl.setAttribute`, function(assert) { const done = assert.async(); this.totalSourcesets = 2; this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e1) => { validateSource(this.player, testSrc, e1, {prop: '', attr: ''}); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceOne], e2); 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', sourceOne.src); }); QUnit.test(`mediaEl.src and then through ${appendObj.name}`, function(assert) { const done = assert.async(); this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, [sourceOne], e); done(); }); this.player.tech_.el_.src = sourceOne.src; // should not fire sourceset appendObj.fn(this.player.tech_.el_, this.source); }); QUnit.test(`mediaEl.setAttribute and then through ${appendObj.name}`, function(assert) { const done = assert.async(); this.source = document.createElement('source'); this.source.src = testSrc.src; this.source.type = testSrc.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, [sourceOne], e); done(); }); this.player.tech_.el_.setAttribute('src', sourceOne.src); // should not fire sourceset appendObj.fn(this.player.tech_.el_, this.source); }); QUnit.test(` two sources through ${appendObj.name}`, function(assert) { const done = assert.async(); this.source = document.createElement('source'); this.source.src = sourceOne.src; this.source.type = sourceOne.type; this.source2 = document.createElement('source'); this.source2.src = sourceTwo.src; this.source2.type = sourceTwo.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e) => { validateSource(this.player, sourceOne, e, {prop: '', attr: ''}); 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 = 3; this.source = document.createElement('source'); this.source.src = sourceTwo.src; this.source.type = sourceTwo.type; this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.one('sourceset', (e1) => { validateSource(this.player, [sourceOne], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [{src: '', type: ''}], e2); this.player.one('sourceset', (e3) => { validateSource(this.player, sourceTwo, e3, {prop: '', attr: ''}); 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', sourceOne.src); }); }); QUnit.test('no source and load', function(assert) { this.player = videojs(this.mediaEl, {enableSourceset: true}); this.player.tech_.el_.load(); this.totalSourcesets = 1; }); })); QUnit.module('source change', (subhooks) => testTypes.forEach((testName) => { QUnit.module(testName, { beforeEach(assert) { const done = assert.async(); setupEnv(this, testName); this.mediaEl.src = testSrc.src; this.player = videojs(this.mediaEl, { enableSourceset: true }); this.player.ready(() => { this.mediaEl = this.player.tech_.el(); }); // initial sourceset should happen on player.ready this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); }, afterEach: setupAfterEach(3) }); QUnit.test('player.src({...})', function(assert) { const done = assert.async(); this.player.one('sourceset', (e1) => { validateSource(this.player, [testSrc], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceOne], e2); done(); }); this.player.src(sourceOne); }); this.player.src(testSrc); }); QUnit.test('hls -> hls -> blob -> hls', function(assert) { this.totalSourcesets = 5; // we have to force techFaker here as some browsers, ie edge/safari support // native HLS. this.player.options_.techOrder = ['techFaker']; this.player.options_.techFaker = this.player.options_.techFaker || {}; const done = assert.async(); const m3u8One = { src: 'http://vjs.zencdn.net/v/oceans.m3u8', type: 'application/x-mpegURL' }; const blobOne = 'blob:one'; const m3u8Two = { src: 'http://vjs.zencdn.net/v/oceans-two.m3u8', type: 'application/x-mpegURL' }; const blobTwo = 'blob:two'; const setTechFaker = (src) => { this.player.options_.techFaker = this.player.options_.techFaker || {}; this.player.tech_.options_ = this.player.tech_.options_ || {}; this.player.tech_.options_.sourceset = src; this.player.options_.techFaker.sourceset = src; }; this.player.one('sourceset', (e1) => { validateSource(this.player, [m3u8One], e1, {event: blobOne, attr: blobOne, prop: blobOne}); this.player.one('sourceset', (e2) => { validateSource(this.player, [m3u8Two], e2, {event: blobTwo, attr: blobTwo, prop: blobTwo}); // should change to blobSrc now this.player.one('sourceset', (e3) => { validateSource(this.player, [blobSrc], e3); this.player.one('sourceset', (e4) => { validateSource(this.player, [m3u8Two], e2, {event: blobTwo, attr: blobTwo, prop: blobTwo}); done(); }); setTechFaker(blobTwo); this.player.src(m3u8Two); }); setTechFaker(blobSrc.src); this.player.src(blobSrc); }); setTechFaker(blobTwo); this.player.src(m3u8Two); }); setTechFaker(blobOne); this.player.src(m3u8One); }); QUnit.test('hls -> mp4 -> hls -> blob', function(assert) { this.totalSourcesets = 5; // we have to force techFaker here as some browsers, ie edge/safari support // native HLS. this.player.options_.techOrder = ['techFaker']; this.player.options_.techFaker = this.player.options_.techFaker || {}; const done = assert.async(); const m3u8One = { src: 'http://vjs.zencdn.net/v/oceans.m3u8', type: 'application/x-mpegURL' }; const blobOne = 'blob:one'; const setTechFaker = (src) => { this.player.options_.techFaker = this.player.options_.techFaker || {}; this.player.tech_.options_ = this.player.tech_.options_ || {}; this.player.tech_.options_.sourceset = src; this.player.options_.techFaker.sourceset = src; }; this.player.one('sourceset', (e1) => { validateSource(this.player, [m3u8One], e1, {event: blobOne, attr: blobOne, prop: blobOne}); this.player.one('sourceset', (e2) => { validateSource(this.player, [testSrc], e2); // should change to blobSrc now this.player.one('sourceset', (e3) => { validateSource(this.player, [m3u8One], e3, {event: blobOne, attr: blobOne, prop: blobOne}); this.player.one('sourceset', (e4) => { validateSource(this.player, [blobSrc], e4); done(); }); setTechFaker(blobSrc.src); this.player.src(blobSrc); }); setTechFaker(blobOne); this.player.src(m3u8One); }); setTechFaker(testSrc.src); this.player.src(testSrc); }); setTechFaker(blobOne); this.player.src(m3u8One); }); QUnit.test('player.src({...}) x2 at the same time', function(assert) { const done = assert.async(); this.player.one('sourceset', (e1) => { validateSource(this.player, [sourceOne], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceTwo], e2); done(); }); }); this.player.src(sourceOne); this.player.src(sourceTwo); }); QUnit.test('player.src({...}) x3 at the same time', function(assert) { const done = assert.async(); // we have one more sourceset then other tests this.totalSourcesets = 4; this.player.one('sourceset', (e1) => { validateSource(this.player, sourceOne, e1); this.player.one('sourceset', (e2) => { validateSource(this.player, sourceTwo, e2); this.player.one('sourceset', (e3) => { validateSource(this.player, sourceThree, e3); done(); }); }); }); this.player.src(sourceOne); this.player.src(sourceTwo); this.player.src(sourceThree); }); QUnit.test('mediaEl.src = ...', function(assert) { const done = assert.async(); this.player.one('sourceset', (e1) => { validateSource(this.player, [testSrc], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceOne], e2); done(); }); this.mediaEl.src = sourceOne.src; }); this.mediaEl.src = testSrc.src; }); QUnit.test('mediaEl.src = ... x2 at the same time', function(assert) { const done = assert.async(); this.player.one('sourceset', (e1) => { validateSource(this.player, [sourceOne], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceTwo], e2); done(); }); }); this.mediaEl.src = sourceOne.src; this.mediaEl.src = sourceTwo.src; }); QUnit.test('mediaEl.setAttribute("src", ...)', function(assert) { const done = assert.async(); this.player.one('sourceset', (e1) => { validateSource(this.player, [testSrc], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceOne], e2); done(); }); this.mediaEl.setAttribute('src', sourceOne.src); }); this.mediaEl.setAttribute('src', testSrc.src); }); QUnit.test('mediaEl.setAttribute("src", ...) x2 at the same time', function(assert) { const done = assert.async(); this.player.one('sourceset', (e1) => { validateSource(this.player, [sourceOne], e1); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceTwo], e2); done(); }); }); this.mediaEl.setAttribute('src', sourceOne.src); this.mediaEl.setAttribute('src', sourceTwo.src); }); QUnit.test('mediaEl.load() with a src attribute', function(assert) { const done = assert.async(); this.totalSourcesets = 1; window.setTimeout(() => { this.sourcesets = 0; this.totalSourcesets = 1; this.player.one('sourceset', (e) => { validateSource(this.player, [testSrc], e); done(); }); this.player.load(); }, wait); }); QUnit.test('mediaEl.load()', function(assert) { const source = document.createElement('source'); source.src = testSrc.src; source.type = 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', (e1) => { validateSource(this.player, [testSrc], e1, {attr: '', prop: ''}); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceOne], e2, {attr: '', prop: ''}); }); source.src = sourceOne.src; source.type = sourceOne.type; this.mediaEl.load(); }); this.mediaEl.appendChild(source); this.mediaEl.load(); }); QUnit.test('mediaEl.load() x2 at the same time', function(assert) { const source = document.createElement('source'); source.src = sourceOne.src; source.type = sourceOne.type; this.player.one('sourceset', (e1) => { validateSource(this.player, [sourceOne], e1, {attr: '', prop: ''}); this.player.one('sourceset', (e2) => { validateSource(this.player, [sourceTwo], e2, {attr: '', prop: ''}); }); }); // 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 = sourceTwo.src; source.type = sourceTwo.type; this.mediaEl.load(); }); QUnit.test('adding a without load()', function(assert) { const done = assert.async(); const source = document.createElement('source'); source.src = testSrc.src; source.type = 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 s src without load()', function(assert) { const done = assert.async(); const source = document.createElement('source'); source.src = testSrc.src; source.type = testSrc.type; this.mediaEl.appendChild(source); source.src = 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 youtubeSrc = { src: 'https://www.youtube.com/watch?v=C0DPdy98e4c', type: 'video/youtube' }; const sourcesets = []; class FakeYoutube extends Html5 { static isSupported() { return true; } static canPlayType(type) { return type === 'video/youtube' ? 'maybe' : ''; } static canPlaySource(srcObj) { return srcObj.type === 'video/youtube'; } } videojs.registerTech('FakeYoutube', FakeYoutube); fixture.appendChild(vid); const player = videojs(vid, { enableSourceset: true, techOrder: ['fakeYoutube', 'html5'] }); player.ready(function() { // 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); if (sourcesets.length === 3) { assert.deepEqual([youtubeSrc.src, sourceTwo.src, sourceOne.src], sourcesets, 'sourceset as expected'); player.dispose(); delete Tech.techs_.FakeYoutube; done(); } }); player.src(sourceOne); player.src(sourceTwo); }); player.src(youtubeSrc); }); QUnit.test('tech sourceset update sources cache when changingSrc_ is false', function(assert) { const clock = sinon.useFakeTimers(); const fixture = document.querySelector('#qunit-fixture'); const vid = document.createElement('video'); fixture.appendChild(vid); const player = videojs(vid); player.src(sourceOne); clock.tick(1); // Declaring the spies here avoids listening to the changes that took place when loading the source. const handleTechSourceset_ = sinon.spy(player, 'handleTechSourceset_'); const techGet_ = sinon.spy(player, 'techGet_'); const updateSourceCaches_ = sinon.spy(player, 'updateSourceCaches_'); player.tech_.trigger('sourceset'); assert.ok(handleTechSourceset_.calledOnce, 'handleTechSourceset_ was called'); assert.ok(!techGet_.called, 'techGet_ was not called'); assert.ok(updateSourceCaches_.calledOnce, 'updateSourceCaches_ was called'); assert.equal(player.cache_.src, '', 'player.cache_.src was reset'); player.tech_.trigger('loadstart'); assert.ok(techGet_.called, 'techGet_ was called'); assert.equal(updateSourceCaches_.callCount, 2, 'updateSourceCaches_ was called twice'); handleTechSourceset_.restore(); techGet_.restore(); updateSourceCaches_.restore(); player.dispose(); clock.restore(); }); });