You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2495 lines
78 KiB
2495 lines
78 KiB
import QUnit from 'qunit';
|
|
import sinon from 'sinon';
|
|
import {
|
|
default as DashPlaylistLoader,
|
|
updateMaster,
|
|
requestSidx_,
|
|
generateSidxKey,
|
|
compareSidxEntry,
|
|
filterChangedSidxMappings,
|
|
parseMasterXml
|
|
} from '../src/dash-playlist-loader';
|
|
import xhrFactory from '../src/xhr';
|
|
import {
|
|
useFakeEnvironment,
|
|
standardXHRResponse,
|
|
urlTo
|
|
} from './test-helpers';
|
|
// needed for plugin registration
|
|
import '../src/videojs-http-streaming';
|
|
import testDataManifests from 'create-test-data!manifests';
|
|
import { sidx as sidxResponse } from 'create-test-data!segments';
|
|
import {mp4VideoInit as mp4VideoInitSegment} from 'create-test-data!segments';
|
|
|
|
QUnit.module('DASH Playlist Loader: unit', {
|
|
beforeEach(assert) {
|
|
this.env = useFakeEnvironment(assert);
|
|
this.clock = this.env.clock;
|
|
this.requests = this.env.requests;
|
|
this.fakeVhs = {
|
|
xhr: xhrFactory()
|
|
};
|
|
this.standardXHRResponse = (request, data) => {
|
|
standardXHRResponse(request, data);
|
|
|
|
// Because SegmentLoader#fillBuffer_ is now scheduled asynchronously
|
|
// we have to use clock.tick to get the expected side effects of
|
|
// SegmentLoader#handleUpdateEnd_
|
|
this.clock.tick(1);
|
|
};
|
|
},
|
|
afterEach() {
|
|
this.env.restore();
|
|
}
|
|
});
|
|
|
|
QUnit.test('updateMaster: returns falsy when there are no changes', function(assert) {
|
|
const master = {
|
|
playlists: {
|
|
length: 1,
|
|
0: {
|
|
uri: '0',
|
|
id: '0',
|
|
segments: []
|
|
}
|
|
},
|
|
mediaGroups: {
|
|
AUDIO: {
|
|
audio: {
|
|
'audio-main': {
|
|
attributes: {
|
|
NAME: 'audio'
|
|
},
|
|
playlists: {
|
|
length: 1,
|
|
0: {
|
|
playlists: {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
SUBTITLES: {}
|
|
},
|
|
duration: 0,
|
|
minimumUpdatePeriod: 0
|
|
};
|
|
|
|
assert.deepEqual(updateMaster(master, master), null);
|
|
});
|
|
|
|
QUnit.test('updateMaster: updates playlists', function(assert) {
|
|
const master = {
|
|
playlists: {
|
|
length: 1,
|
|
0: { uri: '0', id: '0' }
|
|
},
|
|
mediaGroups: {
|
|
AUDIO: {},
|
|
SUBTITLES: {}
|
|
},
|
|
duration: 0,
|
|
minimumUpdatePeriod: 0
|
|
};
|
|
|
|
const update = {
|
|
playlists: {
|
|
length: 1,
|
|
0: {
|
|
id: '0',
|
|
uri: '0',
|
|
segments: []
|
|
}
|
|
},
|
|
mediaGroups: {
|
|
AUDIO: {},
|
|
SUBTITLES: {}
|
|
},
|
|
duration: 0,
|
|
minimumUpdatePeriod: 0
|
|
};
|
|
|
|
assert.deepEqual(
|
|
updateMaster(master, update),
|
|
{
|
|
playlists: {
|
|
length: 1,
|
|
0: {
|
|
id: '0',
|
|
uri: '0',
|
|
segments: []
|
|
}
|
|
},
|
|
mediaGroups: {
|
|
AUDIO: {},
|
|
SUBTITLES: {}
|
|
},
|
|
duration: 0,
|
|
minimumUpdatePeriod: 0
|
|
}
|
|
);
|
|
});
|
|
|
|
QUnit.test('updateMaster: updates mediaGroups', function(assert) {
|
|
const master = {
|
|
playlists: {
|
|
length: 1,
|
|
0: {
|
|
id: '0',
|
|
uri: '0',
|
|
segments: []
|
|
}
|
|
},
|
|
mediaGroups: {
|
|
AUDIO: {
|
|
audio: {
|
|
'audio-main': {
|
|
playlists: [{
|
|
id: '0',
|
|
uri: '0',
|
|
test: 'old text',
|
|
segments: []
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
SUBTITLES: {}
|
|
},
|
|
duration: 0,
|
|
minimumUpdatePeriod: 0
|
|
};
|
|
|
|
const update = {
|
|
playlists: {
|
|
length: 1,
|
|
0: {
|
|
uri: '0',
|
|
segments: []
|
|
}
|
|
},
|
|
mediaGroups: {
|
|
AUDIO: {
|
|
audio: {
|
|
'audio-main': {
|
|
playlists: [{
|
|
id: '0',
|
|
uri: '0',
|
|
resolvedUri: '0',
|
|
test: 'new text',
|
|
segments: [{
|
|
uri: 's'
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
SUBTITLES: {}
|
|
},
|
|
duration: 0,
|
|
minimumUpdatePeriod: 0
|
|
};
|
|
|
|
assert.ok(
|
|
updateMaster(master, update),
|
|
'the mediaGroups were updated'
|
|
);
|
|
});
|
|
|
|
QUnit.test('updateMaster: updates playlists and mediaGroups', function(assert) {
|
|
const master = {
|
|
duration: 10,
|
|
minimumUpdatePeriod: 0,
|
|
mediaGroups: {
|
|
AUDIO: {
|
|
audio: {
|
|
main: {
|
|
playlists: [{
|
|
mediaSequence: 0,
|
|
attributes: {},
|
|
id: 'audio-0-uri',
|
|
uri: 'audio-0-uri',
|
|
resolvedUri: urlTo('audio-0-uri'),
|
|
segments: [{
|
|
duration: 10,
|
|
uri: 'audio-segment-0-uri',
|
|
resolvedUri: urlTo('audio-segment-0-uri')
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
playlists: [{
|
|
mediaSequence: 0,
|
|
attributes: {
|
|
BANDWIDTH: 9
|
|
},
|
|
id: 'playlist-0-uri',
|
|
uri: 'playlist-0-uri',
|
|
resolvedUri: urlTo('playlist-0-uri'),
|
|
segments: [{
|
|
duration: 10,
|
|
uri: 'segment-0-uri',
|
|
resolvedUri: urlTo('segment-0-uri')
|
|
}]
|
|
}]
|
|
};
|
|
const update = {
|
|
duration: 20,
|
|
minimumUpdatePeriod: 0,
|
|
mediaGroups: {
|
|
AUDIO: {
|
|
audio: {
|
|
main: {
|
|
playlists: [{
|
|
mediaSequence: 1,
|
|
attributes: {},
|
|
id: 'audio-0-uri',
|
|
uri: 'audio-0-uri',
|
|
resolvedUri: urlTo('audio-0-uri'),
|
|
segments: [{
|
|
duration: 10,
|
|
uri: 'audio-segment-0-uri',
|
|
resolvedUri: urlTo('audio-segment-0-uri')
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
playlists: [{
|
|
mediaSequence: 1,
|
|
attributes: {
|
|
BANDWIDTH: 9
|
|
},
|
|
id: 'playlist-0-uri',
|
|
uri: 'playlist-0-uri',
|
|
resolvedUri: urlTo('playlist-0-uri'),
|
|
segments: [{
|
|
duration: 10,
|
|
uri: 'segment-0-uri',
|
|
resolvedUri: urlTo('segment-0-uri')
|
|
}]
|
|
}]
|
|
};
|
|
|
|
master.playlists['playlist-0-uri'] = master.playlists[0];
|
|
master.playlists['audio-0-uri'] = master.mediaGroups.AUDIO.audio.main.playlists[0];
|
|
|
|
assert.deepEqual(
|
|
updateMaster(master, update),
|
|
{
|
|
duration: 20,
|
|
minimumUpdatePeriod: 0,
|
|
mediaGroups: {
|
|
AUDIO: {
|
|
audio: {
|
|
main: {
|
|
playlists: [{
|
|
mediaSequence: 1,
|
|
attributes: {},
|
|
id: 'audio-0-uri',
|
|
uri: 'audio-0-uri',
|
|
resolvedUri: urlTo('audio-0-uri'),
|
|
segments: [{
|
|
duration: 10,
|
|
uri: 'audio-segment-0-uri',
|
|
resolvedUri: urlTo('audio-segment-0-uri')
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
playlists: [{
|
|
mediaSequence: 1,
|
|
attributes: {
|
|
BANDWIDTH: 9
|
|
},
|
|
id: 'playlist-0-uri',
|
|
uri: 'playlist-0-uri',
|
|
resolvedUri: urlTo('playlist-0-uri'),
|
|
segments: [{
|
|
duration: 10,
|
|
uri: 'segment-0-uri',
|
|
resolvedUri: urlTo('segment-0-uri')
|
|
}]
|
|
}]
|
|
},
|
|
'updates playlists and media groups'
|
|
);
|
|
});
|
|
|
|
QUnit.test('generateSidxKey: generates correct key', function(assert) {
|
|
const sidxInfo = {
|
|
byterange: {
|
|
offset: 1,
|
|
length: 5
|
|
},
|
|
uri: 'uri'
|
|
};
|
|
|
|
assert.strictEqual(
|
|
generateSidxKey(sidxInfo),
|
|
'uri-1-5',
|
|
'the key byterange should have a inclusive end'
|
|
);
|
|
});
|
|
|
|
QUnit.test('compareSidxEntry: will not add new sidx info to a mapping', function(assert) {
|
|
const playlists = {
|
|
0: {
|
|
sidx: {
|
|
byterange: {
|
|
offset: 0,
|
|
length: 10
|
|
},
|
|
uri: '0'
|
|
}
|
|
},
|
|
1: {
|
|
sidx: {
|
|
byterange: {
|
|
offset: 10,
|
|
length: 29
|
|
},
|
|
uri: '1'
|
|
}
|
|
}
|
|
};
|
|
const oldSidxMapping = {
|
|
'0-0-9': {
|
|
sidx: new Uint8Array(),
|
|
sidxInfo: playlists[0].sidx
|
|
}
|
|
};
|
|
const result = compareSidxEntry(playlists, oldSidxMapping);
|
|
|
|
assert.notOk(result['1-10-29'], 'new playlists are not returned');
|
|
assert.ok(result['0-0-9'], 'matching playlists are returned');
|
|
assert.strictEqual(Object.keys(result).length, 1, 'only one sidx');
|
|
});
|
|
|
|
QUnit.test('compareSidxEntry: will remove non-matching sidxes from a mapping', function(assert) {
|
|
const playlists = [
|
|
{
|
|
uri: '0',
|
|
id: '0',
|
|
sidx: {
|
|
byterange: {
|
|
offset: 0,
|
|
length: 10
|
|
}
|
|
}
|
|
}
|
|
];
|
|
const oldSidxMapping = {
|
|
'0-0-9': {
|
|
sidx: new Uint8Array(),
|
|
sidxInfo: {
|
|
byterange: {
|
|
offset: 1,
|
|
length: 3
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const result = compareSidxEntry(playlists, oldSidxMapping);
|
|
|
|
assert.strictEqual(Object.keys(result).length, 0, 'no sidxes in mapping');
|
|
});
|
|
|
|
QUnit.test('filterChangedSidxMappings: removes change sidx info from mapping', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
|
|
let masterXml;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const childPlaylist = loader.master.mediaGroups.AUDIO.audio.en.playlists[0];
|
|
|
|
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);
|
|
|
|
childLoader.load();
|
|
this.clock.tick(1);
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
const oldSidxMapping = loader.sidxMapping_;
|
|
let newSidxMapping = filterChangedSidxMappings(
|
|
loader.masterXml_,
|
|
loader.srcUrl,
|
|
loader.clientOffset_,
|
|
loader.sidxMapping_
|
|
);
|
|
|
|
assert.deepEqual(
|
|
newSidxMapping,
|
|
oldSidxMapping,
|
|
'if no sidx info changed, return the same object'
|
|
);
|
|
const playlists = loader.master.playlists;
|
|
const oldVideoKey = generateSidxKey(playlists['0-placeholder-uri-0'].sidx);
|
|
const oldAudioEnKey = generateSidxKey(playlists['0-placeholder-uri-AUDIO-audio-en'].sidx);
|
|
|
|
// should change the video playlist
|
|
masterXml = loader.masterXml_.replace(/(indexRange)=\"\d+-\d+\"/, '$1="201-400"');
|
|
newSidxMapping = filterChangedSidxMappings(
|
|
masterXml,
|
|
loader.srcUrl,
|
|
loader.clientOffset_,
|
|
loader.sidxMapping_
|
|
);
|
|
const newVideoKey = `${playlists['0-placeholder-uri-0'].sidx.uri}-201-400`;
|
|
|
|
assert.notOk(
|
|
newSidxMapping[oldVideoKey],
|
|
'old video playlist mapping is not returned'
|
|
);
|
|
assert.notOk(
|
|
newSidxMapping[newVideoKey],
|
|
'new video playlists are not returned'
|
|
);
|
|
assert.ok(
|
|
newSidxMapping[oldAudioEnKey],
|
|
'audio group mapping is returned as it is unchanged'
|
|
);
|
|
|
|
// should change the English audio group
|
|
masterXml = masterXml.replace(/(indexRange)=\"\d+-\d+\"/g, '$1="201-400"');
|
|
newSidxMapping = filterChangedSidxMappings(
|
|
masterXml,
|
|
loader.srcUrl,
|
|
loader.clientOffset_,
|
|
loader.sidxMapping_
|
|
);
|
|
assert.notOk(
|
|
newSidxMapping[oldAudioEnKey],
|
|
'audio group English is removed'
|
|
);
|
|
});
|
|
|
|
QUnit.test('requestSidx_: creates an XHR request for a sidx range', function(assert) {
|
|
const sidxInfo = {
|
|
resolvedUri: 'sidx.mp4',
|
|
byterange: {
|
|
offset: 10,
|
|
length: 10
|
|
}
|
|
};
|
|
const playlist = {
|
|
uri: 'fakeplaylist',
|
|
id: 'fakeplaylist',
|
|
segments: [sidxInfo],
|
|
sidx: sidxInfo
|
|
};
|
|
const callback = sinon.stub();
|
|
const request = requestSidx_(
|
|
{},
|
|
sidxInfo,
|
|
playlist,
|
|
this.fakeVhs.xhr,
|
|
{ handleManifestRedirects: false },
|
|
callback
|
|
);
|
|
|
|
assert.ok(request, 'a request was returned');
|
|
assert.strictEqual(request.uri, sidxInfo.resolvedUri, 'uri requested is correct');
|
|
assert.strictEqual(this.requests.length, 1, 'one xhr request');
|
|
|
|
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(callback.callCount, 1, 'callback was called');
|
|
});
|
|
|
|
QUnit.test('requestSidx_: does not re-request bytes from container request', function(assert) {
|
|
const sidxInfo = {
|
|
resolvedUri: 'sidx.mp4',
|
|
byterange: {
|
|
offset: 0,
|
|
length: 10
|
|
}
|
|
};
|
|
const playlist = {
|
|
uri: 'fakeplaylist',
|
|
id: 'fakeplaylist',
|
|
segments: [sidxInfo],
|
|
sidx: sidxInfo
|
|
};
|
|
const callback = sinon.stub();
|
|
const request = requestSidx_(
|
|
{},
|
|
sidxInfo,
|
|
playlist,
|
|
this.fakeVhs.xhr,
|
|
{ handleManifestRedirects: false },
|
|
callback
|
|
);
|
|
|
|
assert.ok(request, 'a request was returned');
|
|
assert.strictEqual(request.uri, sidxInfo.resolvedUri, 'uri requested is correct');
|
|
assert.strictEqual(this.requests.length, 1, 'one xhr request');
|
|
|
|
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
|
|
|
|
assert.equal(this.requests.length, 0, 'no more requests');
|
|
assert.strictEqual(callback.callCount, 1, 'callback was called');
|
|
});
|
|
|
|
QUnit.test('requestSidx_: callsback with error on invalid container', function(assert) {
|
|
const sidxInfo = {
|
|
resolvedUri: 'sidx.mp4',
|
|
byterange: {
|
|
offset: 0,
|
|
length: 10
|
|
}
|
|
};
|
|
const playlist = {
|
|
uri: 'fakeplaylist',
|
|
id: 'fakeplaylist',
|
|
segments: [sidxInfo],
|
|
sidx: sidxInfo
|
|
};
|
|
const callback = sinon.stub();
|
|
const request = requestSidx_(
|
|
{},
|
|
sidxInfo,
|
|
playlist,
|
|
this.fakeVhs.xhr,
|
|
{ handleManifestRedirects: false },
|
|
callback
|
|
);
|
|
|
|
assert.ok(request, 'a request was returned');
|
|
assert.strictEqual(request.uri, sidxInfo.resolvedUri, 'uri requested is correct');
|
|
assert.strictEqual(this.requests.length, 1, 'one xhr request');
|
|
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
assert.equal(this.requests.length, 0, 'no more requests');
|
|
assert.strictEqual(callback.callCount, 1, 'callback was called');
|
|
assert.deepEqual(callback.args[0][0], {
|
|
blacklistDuration: Infinity,
|
|
code: 2,
|
|
internal: true,
|
|
message: 'Unsupported unknown container type for sidx segment at URL: sidx.mp4',
|
|
playlist,
|
|
response: '',
|
|
status: 200
|
|
}, 'error as expected');
|
|
});
|
|
|
|
QUnit.test('constructor throws if the playlist url is empty or undefined', function(assert) {
|
|
assert.throws(function() {
|
|
DashPlaylistLoader();
|
|
}, 'requires an argument');
|
|
assert.throws(function() {
|
|
DashPlaylistLoader('');
|
|
}, 'does not accept the empty string');
|
|
});
|
|
|
|
QUnit.test('constructor sets srcUrl and other properties', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
assert.strictEqual(loader.state, 'HAVE_NOTHING', 'correct state');
|
|
assert.deepEqual(loader.loadedPlaylists_, {}, 'correct loadedPlaylist state');
|
|
assert.notOk(loader.masterPlaylistLoader_, 'should be no masterPlaylistLoader');
|
|
assert.notOk(loader.childPlaylist_, 'should be no childPlaylist_');
|
|
assert.strictEqual(loader.srcUrl, 'dash.mpd', 'set the srcUrl');
|
|
|
|
const childLoader = new DashPlaylistLoader({}, this.fakeVhs, false, loader);
|
|
|
|
assert.strictEqual(childLoader.state, 'HAVE_NOTHING', 'correct state');
|
|
assert.deepEqual(childLoader.loadedPlaylists_, {}, 'correct loadedPlaylist state');
|
|
assert.ok(childLoader.masterPlaylistLoader_, 'should be a masterPlaylistLoader');
|
|
assert.deepEqual(
|
|
childLoader.childPlaylist_, {},
|
|
'should be a childPlaylist_'
|
|
);
|
|
assert.notOk(childLoader.srcUrl, 'should be no srcUrl');
|
|
});
|
|
|
|
QUnit.test('dispose: aborts pending manifest request', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
assert.equal(this.requests.length, 1, 'one request');
|
|
assert.notOk(this.requests[0].aborted, 'request not aborted');
|
|
assert.ok(this.requests[0].onreadystatechange, 'onreadystatechange handler exists');
|
|
loader.dispose();
|
|
assert.equal(this.requests.length, 1, 'one request');
|
|
assert.ok(this.requests[0].aborted, 'request aborted');
|
|
assert.notOk(
|
|
this.requests[0].onreadystatechange,
|
|
'onreadystatechange handler does not exist'
|
|
);
|
|
});
|
|
|
|
QUnit.test('load: will start an unstarted loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
loader.on('loadedmetadata', () => {
|
|
loadedMetadata++;
|
|
});
|
|
assert.notOk(loader.started, 'begins unstarted');
|
|
|
|
loader.load();
|
|
assert.strictEqual(loader.started, true, 'load should start the loader');
|
|
assert.strictEqual(this.requests.length, 1, 'should request the manifest');
|
|
assert.strictEqual(loader.state, 'HAVE_NOTHING', 'state has not changed');
|
|
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state is updated');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.load();
|
|
assert.strictEqual(loader.started, true, 'still loaded');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
assert.strictEqual(this.requests.length, 0, 'no request made');
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'no state change');
|
|
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(loadedPlaylists, 3, '3 loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
|
|
loader.load(true);
|
|
assert.strictEqual(loadedPlaylists, 3, 'does not fire 4th loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 1, 'does not fire 2nd loadedmetadata');
|
|
|
|
const loadSpy = sinon.spy(loader, 'load');
|
|
|
|
// half of one target duration = 1s
|
|
this.clock.tick(1000);
|
|
assert.strictEqual(loadSpy.callCount, 1, 'load was called again');
|
|
});
|
|
|
|
QUnit.test('load: will not request manifest when started', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
loader.on('loadedmetadata', () => {
|
|
loadedMetadata++;
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state is updated');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.load();
|
|
assert.strictEqual(loader.started, true, 'still loaded');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
assert.strictEqual(this.requests.length, 0, 'no request made');
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'no state change');
|
|
});
|
|
|
|
QUnit.test('load: will retry if this is the final rendition', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
loader.on('loadedmetadata', () => {
|
|
loadedMetadata++;
|
|
});
|
|
assert.notOk(loader.started, 'begins unstarted');
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
|
|
loader.load(true);
|
|
assert.strictEqual(loadedPlaylists, 2, 'does not fire 3rd loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 1, 'does not fire 2nd loadedmetadata');
|
|
|
|
const loadSpy = sinon.spy(loader, 'load');
|
|
|
|
// half of one target duration = 1s
|
|
this.clock.tick(1000);
|
|
assert.strictEqual(loadSpy.callCount, 1, 'load was called again');
|
|
});
|
|
|
|
QUnit.test('media: get returns currently active media playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
|
|
// setup loader
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loader.media(), undefined, 'no media set yet');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'set the correct media playlist'
|
|
);
|
|
});
|
|
|
|
QUnit.test('media: does not set media if getter is called', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on(['loadedplaylist', 'loadedmetadata'], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
}
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.media(null);
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should stay HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'still one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'still no loadedmetadata');
|
|
|
|
loader.media(undefined);
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should stay HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'still one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'still no loadedmetadata');
|
|
});
|
|
|
|
QUnit.test('media: errors if called in incorrect state', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
assert.strictEqual(loader.state, 'HAVE_NOTHING', 'state should be HAVE_NOTHING');
|
|
assert.throws(
|
|
() => loader.media('0'),
|
|
/Cannot switch media playlist from HAVE_NOTHING/,
|
|
'should throw an error if media is called without a master playlist'
|
|
);
|
|
});
|
|
|
|
QUnit.test('media: setting media causes an asynchronous action', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on(['loadedplaylist', 'loadedmetadata'], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
}
|
|
});
|
|
|
|
// setup loader
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'correct state before media call');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist before media is loaded');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata before media is loaded');
|
|
assert.notOk(loader.hasPendingRequest(), 'no pending asynchronous actions');
|
|
|
|
// set initial media
|
|
loader.media(loader.master.playlists[0]);
|
|
assert.ok(loader.hasPendingRequest(), 'has asynchronous action pending');
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state is still HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'still one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'still no loadedmetadata');
|
|
|
|
// runs any pending async actions
|
|
this.clock.tick(0);
|
|
assert.notOk(loader.hasPendingRequest(), 'no asynchronous action pending');
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state is now HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
});
|
|
|
|
QUnit.test('media: sets initial media playlist on master loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on(['loadedplaylist', 'loadedmetadata'], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
}
|
|
});
|
|
|
|
// setup loader
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
// set initial media
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'media set correctly'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(loader.loadedPlaylists_),
|
|
[loader.master.playlists[0].id],
|
|
'updated the loadedPlaylists_'
|
|
);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
});
|
|
|
|
QUnit.test('media: sets a playlist from a string reference', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on(['loadedplaylist', 'loadedmetadata'], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
}
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.media('0');
|
|
this.clock.tick(1);
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'set media correctly'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(loader.loadedPlaylists_),
|
|
[loader.master.playlists[0].id],
|
|
'updated the loadedPlaylists_'
|
|
);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
});
|
|
|
|
QUnit.test('media: switches to a new playlist from a loaded one', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
let mediaChange = 0;
|
|
let mediaChanging = 0;
|
|
|
|
loader.on([
|
|
'loadedplaylist',
|
|
'loadedmetadata',
|
|
'mediachange',
|
|
'mediachanging'
|
|
], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
} else if (e.type === 'mediachange') {
|
|
mediaChange++;
|
|
} else if (e.type === 'mediachanging') {
|
|
mediaChanging++;
|
|
}
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
// initial selection
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
assert.strictEqual(mediaChange, 0, 'no mediachanges');
|
|
assert.strictEqual(mediaChanging, 0, 'no mediachangings');
|
|
|
|
// different selection
|
|
loader.media(loader.master.playlists[1]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[1].uri,
|
|
'media changed successfully'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(loader.loadedPlaylists_),
|
|
[
|
|
loader.master.playlists[0].id,
|
|
loader.master.playlists[1].id
|
|
],
|
|
'updated loadedPlaylists_'
|
|
);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 3, '3 loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
assert.strictEqual(mediaChange, 1, 'one mediachanges');
|
|
assert.strictEqual(mediaChanging, 1, 'one mediachangings');
|
|
});
|
|
|
|
QUnit.test('media: switches to a previously loaded playlist immediately', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
let mediaChange = 0;
|
|
let mediaChanging = 0;
|
|
|
|
loader.on([
|
|
'loadedplaylist',
|
|
'loadedmetadata',
|
|
'mediachange',
|
|
'mediachanging'
|
|
], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
} else if (e.type === 'mediachange') {
|
|
mediaChange++;
|
|
} else if (e.type === 'mediachanging') {
|
|
mediaChanging++;
|
|
}
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
// initial selection
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
assert.strictEqual(mediaChange, 0, 'no mediachanges');
|
|
assert.strictEqual(mediaChanging, 0, 'no mediachangings');
|
|
|
|
// different selection
|
|
loader.media(loader.master.playlists[1]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[1].uri,
|
|
'switched to new playlist'
|
|
);
|
|
|
|
// previous selection
|
|
loader.media(loader.master.playlists[0]);
|
|
// no waiting for async action
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'correct media set'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(loader.loadedPlaylists_),
|
|
[
|
|
loader.master.playlists[0].id,
|
|
loader.master.playlists[1].id
|
|
],
|
|
'loadedPlaylists_ only updated for new playlists'
|
|
);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 3, '3 loadedplaylists');
|
|
assert.strictEqual(
|
|
loadedMetadata, 1,
|
|
'still one loadedmetadata since this is a loadedPlaylist'
|
|
);
|
|
assert.strictEqual(mediaChange, 2, 'two mediachanges');
|
|
assert.strictEqual(mediaChanging, 2, 'two mediachangings');
|
|
});
|
|
|
|
QUnit.test('media: does not switch to same playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
let mediaChange = 0;
|
|
let mediaChanging = 0;
|
|
|
|
loader.on([
|
|
'loadedplaylist',
|
|
'loadedmetadata',
|
|
'mediachange',
|
|
'mediachanging'
|
|
], (e) => {
|
|
if (e.type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (e.type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
} else if (e.type === 'mediachange') {
|
|
mediaChange++;
|
|
} else if (e.type === 'mediachanging') {
|
|
mediaChanging++;
|
|
}
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state should be HAVE_MASTER');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
// initial selection
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
assert.strictEqual(mediaChange, 0, 'no mediachanges');
|
|
assert.strictEqual(mediaChanging, 0, 'no mediachangings');
|
|
|
|
// to same playlist
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should be HAVE_METADATA');
|
|
assert.strictEqual(loadedPlaylists, 2, 'two loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
assert.strictEqual(mediaChange, 0, 'no mediachanges');
|
|
assert.strictEqual(mediaChanging, 0, 'no mediachangings');
|
|
});
|
|
|
|
QUnit.test('haveMetadata: triggers loadedplaylist if initial selection', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
let mediaChanges = 0;
|
|
let mediaChangings = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
loader.on([
|
|
'loadedplaylist',
|
|
'loadedmetadata',
|
|
'mediachange',
|
|
'mediachanging'
|
|
], (e) => {
|
|
const type = e.type;
|
|
|
|
if (type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
} else if (type === 'mediachange') {
|
|
mediaChanges++;
|
|
} else if (type === 'mediachanging') {
|
|
mediaChangings++;
|
|
}
|
|
});
|
|
|
|
loader.haveMetadata({
|
|
startingState: 'HAVE_MASTER',
|
|
playlist: loader.master.playlists[0]
|
|
});
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should advance');
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'media set correctly'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(loader.loadedPlaylists_),
|
|
[loader.master.playlists[0].id],
|
|
'updated loadedPlaylists_'
|
|
);
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'one loadedmetadata');
|
|
assert.strictEqual(mediaChanges, 0, 'no mediachange');
|
|
assert.strictEqual(mediaChangings, 0, 'no mediachanging');
|
|
});
|
|
|
|
QUnit.test('haveMetadata: triggers mediachange if new selection', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
let mediaChanges = 0;
|
|
let mediaChangings = 0;
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[1]);
|
|
this.clock.tick(1);
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.on([
|
|
'loadedplaylist',
|
|
'loadedmetadata',
|
|
'mediachange',
|
|
'mediachanging'
|
|
], (e) => {
|
|
const type = e.type;
|
|
|
|
if (type === 'loadedplaylist') {
|
|
loadedPlaylists++;
|
|
} else if (type === 'loadedmetadata') {
|
|
loadedMetadata++;
|
|
} else if (type === 'mediachange') {
|
|
mediaChanges++;
|
|
} else if (type === 'mediachanging') {
|
|
mediaChangings++;
|
|
}
|
|
});
|
|
|
|
loader.haveMetadata({
|
|
startingState: 'HAVE_METADATA',
|
|
playlist: loader.master.playlists[0]
|
|
});
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state should stay the same');
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'media set correctly'
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(loader.loadedPlaylists_),
|
|
[
|
|
loader.master.playlists[1].id,
|
|
loader.master.playlists[0].id
|
|
],
|
|
'updated loadedPlaylists_'
|
|
);
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
assert.strictEqual(mediaChanges, 1, 'one mediachange');
|
|
assert.strictEqual(mediaChangings, 0, 'no mediachanging');
|
|
});
|
|
|
|
QUnit.test('haveMaster: triggers loadedplaylist for loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origMediaFn = loader.media;
|
|
let loadedPlaylists = 0;
|
|
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
|
|
// fake already having master XML loaded
|
|
loader.masterXml_ = testDataManifests.dash;
|
|
loader.haveMaster_();
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist triggered');
|
|
|
|
loader.media = origMediaFn;
|
|
});
|
|
|
|
QUnit.test('haveMaster: sets media on child loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
|
|
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);
|
|
|
|
const mediaStub = sinon.stub(childLoader, 'media');
|
|
|
|
childLoader.haveMaster_();
|
|
assert.strictEqual(mediaStub.callCount, 1, 'calls media on childLoader');
|
|
assert.deepEqual(
|
|
mediaStub.getCall(0).args[0],
|
|
childPlaylist,
|
|
'sets media to passed in playlist object'
|
|
);
|
|
});
|
|
|
|
QUnit.test('parseMasterXml: setup phony playlists and resolves uris', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
const masterPlaylist = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
|
|
assert.strictEqual(masterPlaylist.uri, loader.srcUrl, 'master playlist uri set correctly');
|
|
assert.strictEqual(masterPlaylist.playlists[0].uri, 'placeholder-uri-0');
|
|
assert.strictEqual(masterPlaylist.playlists[0].id, '0-placeholder-uri-0');
|
|
assert.deepEqual(
|
|
masterPlaylist.playlists['0-placeholder-uri-0'],
|
|
masterPlaylist.playlists[0],
|
|
'phony id setup correctly for playlist'
|
|
);
|
|
assert.ok(
|
|
Object.keys(masterPlaylist.mediaGroups.AUDIO).length,
|
|
'has audio group'
|
|
);
|
|
assert.ok(masterPlaylist.playlists[0].resolvedUri, 'resolved playlist uris');
|
|
});
|
|
|
|
QUnit.test('parseMasterXml: includes sidx info if available and matches playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const origParsedMaster = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
|
|
loader.sidxMapping_ = {};
|
|
|
|
let newParsedMaster = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
|
|
assert.deepEqual(
|
|
newParsedMaster,
|
|
origParsedMaster,
|
|
'empty sidxMapping will not affect master xml parsing'
|
|
);
|
|
|
|
// Allow sidx request to finish
|
|
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const key = generateSidxKey(loader.media().sidx);
|
|
|
|
loader.sidxMapping_[key] = {
|
|
sidxInfo: loader.media().sidx,
|
|
sidx: {
|
|
timescale: 90000,
|
|
firstOffset: 0,
|
|
references: [{
|
|
referenceType: 0,
|
|
referencedSize: 10,
|
|
subSegmentDuration: 90000
|
|
}]
|
|
}
|
|
};
|
|
newParsedMaster = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
|
|
assert.deepEqual(
|
|
newParsedMaster.playlists[0].segments[0].byterange,
|
|
{
|
|
length: 10,
|
|
offset: 400
|
|
},
|
|
'byte range from sidx is applied to playlist segment'
|
|
);
|
|
assert.deepEqual(
|
|
newParsedMaster.playlists[0].segments[0].map.byterange,
|
|
{
|
|
length: 200,
|
|
offset: 0
|
|
},
|
|
'init segment is included in updated segment'
|
|
);
|
|
});
|
|
|
|
QUnit.test('refreshMedia: updates master and media playlists for master loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylists = 0;
|
|
let playlistUnchanged = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'initial media set'
|
|
);
|
|
assert.ok(loader.master, 'master playlist set');
|
|
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
loader.on('playlistunchanged', () => {
|
|
playlistUnchanged++;
|
|
});
|
|
|
|
const oldMaster = loader.master;
|
|
const newMasterXml = testDataManifests['dash-live'];
|
|
|
|
loader.masterXml_ = newMasterXml;
|
|
loader.refreshMedia_(loader.media().id);
|
|
|
|
assert.notEqual(loader.master, oldMaster, 'new master set');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(playlistUnchanged, 0, 'no playlistunchanged');
|
|
});
|
|
|
|
QUnit.test('refreshMedia: triggers playlistunchanged for master loader' +
|
|
' if master stays the same', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylists = 0;
|
|
let playlistUnchanged = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
|
|
assert.strictEqual(
|
|
loader.media().uri,
|
|
loader.master.playlists[0].uri,
|
|
'initial media set'
|
|
);
|
|
assert.ok(loader.master, 'master playlist set');
|
|
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
loader.on('playlistunchanged', () => {
|
|
playlistUnchanged++;
|
|
});
|
|
|
|
const master = loader.master;
|
|
const media = loader.media();
|
|
|
|
loader.refreshMedia_(loader.media().id);
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylists');
|
|
assert.strictEqual(playlistUnchanged, 1, 'one playlistunchanged');
|
|
|
|
const newMaster = loader.master;
|
|
const newMedia = loader.media();
|
|
|
|
assert.equal(master, newMaster, 'master is unchanged');
|
|
assert.equal(media, newMedia, 'media is unchanged');
|
|
});
|
|
|
|
QUnit.test('refreshMedia: updates master and media playlists for child loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylists = 0;
|
|
let playlistUnchanged = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
const childLoader = new DashPlaylistLoader(loader.master.playlists[0], this.fakeVhs, false, loader);
|
|
|
|
childLoader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.ok(loader.master, 'master loader has master playlist');
|
|
assert.ok(loader.media_, 'master loader has selected media');
|
|
assert.notOk(childLoader.master, 'childLoader does not have master');
|
|
assert.ok(childLoader.media_, 'childLoader media selected');
|
|
|
|
childLoader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
childLoader.on('playlistunchanged', () => {
|
|
playlistUnchanged++;
|
|
});
|
|
|
|
const oldMaster = loader.master;
|
|
const newMasterXml = testDataManifests['dash-live'];
|
|
|
|
loader.masterXml_ = newMasterXml;
|
|
childLoader.refreshMedia_(loader.media().id);
|
|
|
|
assert.notEqual(loader.master, oldMaster, 'new master set on master loader');
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(playlistUnchanged, 0, 'no playlistunchanged');
|
|
});
|
|
|
|
QUnit.test('refreshMedia: triggers playlistunchanged for child loader' +
|
|
' if master stays the same', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylists = 0;
|
|
let playlistUnchanged = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
const childLoader = new DashPlaylistLoader(loader.master.playlists[0], this.fakeVhs, false, loader);
|
|
|
|
childLoader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.ok(loader.master, 'master loader has master playlist');
|
|
assert.ok(loader.media_, 'master loader has selected media');
|
|
assert.notOk(childLoader.master, 'childLoader does not have master');
|
|
assert.ok(childLoader.media_, 'childLoader media selected');
|
|
|
|
childLoader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
childLoader.on('playlistunchanged', () => {
|
|
playlistUnchanged++;
|
|
});
|
|
|
|
childLoader.refreshMedia_(loader.media().id);
|
|
|
|
assert.strictEqual(loadedPlaylists, 1, 'one loadedplaylist');
|
|
assert.strictEqual(playlistUnchanged, 1, 'one playlistunchanged');
|
|
});
|
|
|
|
QUnit.test('refreshXml_: re-requests the MPD', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
assert.strictEqual(this.requests.length, 0, 'no requests');
|
|
loader.refreshXml_();
|
|
assert.strictEqual(this.requests.length, 1, 'made a request');
|
|
const spy = sinon.spy(loader, 'refreshXml_');
|
|
|
|
loader.trigger('minimumUpdatePeriod');
|
|
assert.strictEqual(this.requests.length, 2, 'minimumUpdatePeriod event make a request');
|
|
assert.strictEqual(spy.callCount, 1, 'refreshXml_ was called due to minimumUpdatePeriod event');
|
|
});
|
|
|
|
QUnit.test('refreshXml_: requests the sidx if it changed', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
// initial manifest
|
|
this.standardXHRResponse(this.requests.shift());
|
|
// child playlist
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
const oldMaster = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
const newMasterXml = loader.masterXml_.replace(/(indexRange)=\"\d+-\d+\"/g, '$1="400-599"');
|
|
|
|
loader.masterXml_ = newMasterXml;
|
|
assert.deepEqual(
|
|
oldMaster.playlists[0].sidx.byterange, {
|
|
offset: 200,
|
|
length: 200
|
|
},
|
|
'sidx is the original in the xml'
|
|
);
|
|
let newMaster = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
|
|
assert.notEqual(
|
|
newMaster.playlists[0].sidx.byterange.offset,
|
|
oldMaster.playlists[0].sidx.byterange.offset,
|
|
'the sidx has been changed'
|
|
);
|
|
loader.refreshXml_();
|
|
|
|
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
|
|
newMaster = parseMasterXml({
|
|
masterXml: loader.masterXml_,
|
|
srcUrl: loader.srcUrl,
|
|
clientOffset: loader.clientOffset_,
|
|
sidxMapping: loader.sidxMapping_
|
|
});
|
|
assert.deepEqual(
|
|
newMaster.playlists[0].sidx.byterange,
|
|
{
|
|
offset: 400,
|
|
length: 200
|
|
},
|
|
'the sidx byterange has changed to reflect the new manifest'
|
|
);
|
|
});
|
|
|
|
QUnit.test('refreshXml_: updates media playlist reference if master changed', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
const oldMaster = loader.master;
|
|
const oldMedia = loader.media();
|
|
const newMasterXml = loader.masterXml_.replace(
|
|
'mediaPresentationDuration="PT4S"',
|
|
'mediaPresentationDuration="PT5S"'
|
|
);
|
|
|
|
loader.refreshXml_();
|
|
|
|
assert.strictEqual(this.requests.length, 1, 'manifest is being requested');
|
|
|
|
this.requests.shift().respond(200, null, newMasterXml);
|
|
|
|
const newMaster = loader.master;
|
|
const newMedia = loader.media();
|
|
|
|
assert.notEqual(newMaster, oldMaster, 'master changed');
|
|
assert.notEqual(newMedia, oldMedia, 'media changed');
|
|
assert.equal(
|
|
newMedia,
|
|
newMaster.playlists[newMedia.id],
|
|
'media from updated master'
|
|
);
|
|
});
|
|
|
|
QUnit.test('sidxRequestFinished_: updates master with sidx information', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const fakePlaylist = {
|
|
segments: [],
|
|
id: 'fakeplaylist',
|
|
uri: 'fakeplaylist',
|
|
sidx: {
|
|
byterange: {
|
|
offset: 0,
|
|
length: sidxResponse().byteLength
|
|
},
|
|
resolvedUri: 'sidx.mp4'
|
|
}
|
|
};
|
|
const fakeMaster = {
|
|
playlists: {
|
|
0: fakePlaylist,
|
|
fakeplaylist: fakePlaylist
|
|
}
|
|
};
|
|
const stubDone = sinon.stub();
|
|
const handleSidxResponse = loader.sidxRequestFinished_(fakePlaylist, fakeMaster, 'HAVE_MASTER', stubDone);
|
|
const fakeRequest = {
|
|
response: sidxResponse()
|
|
};
|
|
|
|
// fake the loader active request for sidx
|
|
loader.request = true;
|
|
handleSidxResponse(null, fakeRequest);
|
|
assert.strictEqual(stubDone.callCount, 1, 'callback was called');
|
|
assert.ok(
|
|
stubDone.getCall(0).args[0].playlists,
|
|
'returned master playlist object'
|
|
);
|
|
assert.ok(
|
|
stubDone.getCall(0).args[1].references,
|
|
'returned a parsed sidx box'
|
|
);
|
|
assert.strictEqual(
|
|
stubDone.getCall(0).args[1].references[0].referencedSize,
|
|
13001,
|
|
'sidx box returned has been parsed'
|
|
);
|
|
});
|
|
|
|
QUnit.test('sidxRequestFinished_: errors if request for sidx fails', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const fakePlaylist = {
|
|
segments: [{
|
|
uri: 'fake-segment',
|
|
duration: 15360
|
|
}],
|
|
id: 'fakeplaylist',
|
|
uri: 'fakeplaylist',
|
|
sidx: {
|
|
byterange: {
|
|
offset: 0,
|
|
length: sidxResponse().byteLength
|
|
},
|
|
resolvedUri: 'sidx.mp4'
|
|
}
|
|
};
|
|
const fakeMaster = {
|
|
playlists: {
|
|
0: fakePlaylist,
|
|
fakeplaylist: fakePlaylist
|
|
}
|
|
};
|
|
const stubDone = sinon.stub();
|
|
const handleSidxResponse = loader.sidxRequestFinished_(fakePlaylist, fakeMaster, 'HAVE_MASTER', stubDone);
|
|
const fakeRequest = {
|
|
response: 'fake error msg',
|
|
status: 400
|
|
};
|
|
let errors = 0;
|
|
|
|
loader.on('error', () => {
|
|
errors++;
|
|
});
|
|
|
|
// fake xhr request being active
|
|
loader.request = true;
|
|
handleSidxResponse(true, fakeRequest);
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state is returned to state passed in');
|
|
assert.deepEqual(
|
|
loader.error,
|
|
{
|
|
status: fakeRequest.status,
|
|
message: 'DASH playlist request error at URL: fakeplaylist',
|
|
response: fakeRequest.response,
|
|
code: 2
|
|
},
|
|
'error object is filled out correctly'
|
|
);
|
|
assert.strictEqual(errors, 1, 'triggered an error event');
|
|
});
|
|
|
|
QUnit.test('sidxRequestFinished_: uses given error object', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const fakePlaylist = {
|
|
segments: [{
|
|
uri: 'fake-segment',
|
|
duration: 15360
|
|
}],
|
|
id: 'fakeplaylist',
|
|
uri: 'fakeplaylist',
|
|
sidx: {
|
|
byterange: {
|
|
offset: 0,
|
|
length: sidxResponse().byteLength
|
|
},
|
|
resolvedUri: 'sidx.mp4'
|
|
}
|
|
};
|
|
const fakeMaster = {
|
|
playlists: {
|
|
0: fakePlaylist,
|
|
fakeplaylist: fakePlaylist
|
|
}
|
|
};
|
|
const stubDone = sinon.stub();
|
|
const handleSidxResponse = loader.sidxRequestFinished_(fakePlaylist, fakeMaster, 'HAVE_MASTER', stubDone);
|
|
const fakeRequest = {
|
|
response: '',
|
|
status: 200
|
|
};
|
|
let errors = 0;
|
|
|
|
loader.on('error', () => {
|
|
errors++;
|
|
});
|
|
|
|
// fake xhr request being active
|
|
loader.request = true;
|
|
const error = {
|
|
status: fakeRequest.status,
|
|
message: 'Unsupported webm container type for sidx segment at URL: sidx.mp4',
|
|
playlist: fakePlaylist,
|
|
internal: true,
|
|
response: '',
|
|
blacklistDuration: Infinity,
|
|
// MEDIA_ERR_NETWORK
|
|
code: 2
|
|
};
|
|
|
|
handleSidxResponse(error, fakeRequest);
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state is returned to state passed in');
|
|
assert.deepEqual(
|
|
loader.error,
|
|
error,
|
|
'error object is filled out correctly'
|
|
);
|
|
assert.strictEqual(errors, 1, 'triggered an error event');
|
|
});
|
|
|
|
QUnit.test('setupChildLoader: sets masterPlaylistLoader and ' +
|
|
'playlist on child loader', function(assert) {
|
|
const fakePlaylist = { uri: 'fakeplaylist1', id: 'fakeplaylist1' };
|
|
const newPlaylist = { uri: 'fakeplaylist2', id: 'fakeplaylist2' };
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const newLoader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
|
|
const childLoader = new DashPlaylistLoader(fakePlaylist, this.fakeVhs, false, loader);
|
|
|
|
assert.deepEqual(
|
|
childLoader.masterPlaylistLoader_,
|
|
loader,
|
|
'starts with loader passed into constructor'
|
|
);
|
|
assert.deepEqual(
|
|
childLoader.childPlaylist_,
|
|
fakePlaylist,
|
|
'starts with playlist passed in constructor'
|
|
);
|
|
|
|
childLoader.setupChildLoader(newLoader, newPlaylist);
|
|
assert.deepEqual(
|
|
childLoader.masterPlaylistLoader_,
|
|
newLoader,
|
|
'masterPlaylistLoader correctly set'
|
|
);
|
|
assert.deepEqual(
|
|
childLoader.childPlaylist_,
|
|
newPlaylist,
|
|
'child playlist correctly set'
|
|
);
|
|
});
|
|
|
|
QUnit.test('hasPendingRequest: returns true if async code is running in master loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
assert.notOk(loader.hasPendingRequest(), 'no requests on construction');
|
|
|
|
loader.load();
|
|
assert.ok(loader.hasPendingRequest(), 'should make a request after loading');
|
|
assert.ok(loader.request, 'xhr request is being made');
|
|
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.notOk(loader.hasPendingRequest(), 'no pending request before setting media');
|
|
|
|
loader.media(loader.master.playlists[1]);
|
|
assert.ok(loader.hasPendingRequest(), 'pending request while loading media playlist');
|
|
assert.ok(loader.mediaRequest_, 'media request is being made');
|
|
assert.notOk(loader.request, 'xhr request is not being made');
|
|
|
|
this.clock.tick(1);
|
|
assert.ok(loader.state, 'HAVE_METADATA', 'in HAVE_METADATA state once media is loaded');
|
|
assert.notOk(loader.hasPendingRequest(), 'no pending request once media is loaded');
|
|
});
|
|
|
|
QUnit.test('hasPendingRequest: returns true if async code is running in child loader', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
|
|
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);
|
|
|
|
assert.notOk(childLoader.hasPendingRequest(), 'no pending requests on construction');
|
|
|
|
childLoader.load();
|
|
assert.ok(childLoader.hasPendingRequest(), 'pending request while loading master playlist');
|
|
assert.ok(childLoader.mediaRequest_, 'media request is being made');
|
|
assert.notOk(childLoader.request, 'xhr request is not being made');
|
|
|
|
// this starts a request for the media playlist
|
|
childLoader.haveMaster_();
|
|
assert.ok(childLoader.hasPendingRequest(), 'pending request while loading media playlist');
|
|
assert.ok(childLoader.mediaRequest_, 'media request is being made');
|
|
assert.notOk(childLoader.request, 'xhr request is not being made');
|
|
|
|
childLoader.haveMetadata({
|
|
startingState: 'HAVE_MASTER',
|
|
playlist: childLoader.childPlaylist_
|
|
});
|
|
assert.strictEqual(childLoader.state, 'HAVE_METADATA', 'state is in HAVE_METADATA');
|
|
assert.notOk(childLoader.hasPendingRequest(), 'no pending requests once media is loaded');
|
|
});
|
|
|
|
QUnit.module('DASH Playlist Loader: functional', {
|
|
beforeEach(assert) {
|
|
this.env = useFakeEnvironment(assert);
|
|
this.clock = this.env.clock;
|
|
this.requests = this.env.requests;
|
|
this.fakeVhs = {
|
|
xhr: xhrFactory()
|
|
};
|
|
this.standardXHRResponse = (request, data) => {
|
|
standardXHRResponse(request, data);
|
|
|
|
// Because SegmentLoader#fillBuffer_ is now scheduled asynchronously
|
|
// we have to use clock.tick to get the expected side effects of
|
|
// SegmentLoader#handleUpdateEnd_
|
|
this.clock.tick(1);
|
|
};
|
|
},
|
|
afterEach() {
|
|
this.env.restore();
|
|
}
|
|
});
|
|
|
|
QUnit.test('requests the manifest immediately when given a URL', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
assert.equal(this.requests.length, 1, 'made a request');
|
|
assert.equal(this.requests[0].url, 'dash.mpd', 'requested the manifest');
|
|
});
|
|
|
|
QUnit.test('redirect manifest request when handleManifestRedirects is true', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs, { handleManifestRedirects: true });
|
|
|
|
loader.load();
|
|
|
|
const modifiedRequest = this.requests.shift();
|
|
|
|
modifiedRequest.responseURL = 'http://differenturi.com/test.mpd';
|
|
|
|
this.standardXHRResponse(modifiedRequest);
|
|
|
|
assert.equal(loader.srcUrl, 'http://differenturi.com/test.mpd', 'url has redirected');
|
|
});
|
|
|
|
QUnit.test('redirect src request when handleManifestRedirects is true', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs, { handleManifestRedirects: true });
|
|
|
|
loader.load();
|
|
|
|
const modifiedRequest = this.requests.shift();
|
|
|
|
modifiedRequest.responseURL = 'http://differenturi.com/test.mpd';
|
|
this.standardXHRResponse(modifiedRequest);
|
|
|
|
const childLoader = new DashPlaylistLoader(loader.master.playlists['0-placeholder-uri-0'], this.fakeVhs, false, loader);
|
|
|
|
childLoader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(childLoader.media_.resolvedUri, 'http://differenturi.com/placeholder-uri-0', 'url has redirected');
|
|
});
|
|
|
|
QUnit.test('do not redirect src request when handleManifestRedirects is not set', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
|
|
const modifiedRequest = this.requests.shift();
|
|
|
|
modifiedRequest.responseURL = 'http://differenturi.com/test.mpd';
|
|
|
|
this.standardXHRResponse(modifiedRequest);
|
|
|
|
assert.equal(loader.srcUrl, 'dash.mpd', 'url has not redirected');
|
|
});
|
|
|
|
QUnit.test('starts without any metadata', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
assert.notOk(loader.started, 'not started');
|
|
|
|
loader.load();
|
|
assert.equal(loader.state, 'HAVE_NOTHING', 'no metadata has loaded yet');
|
|
assert.ok(loader.started, 'started');
|
|
});
|
|
|
|
QUnit.test('moves to HAVE_MASTER after loading a master playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
|
|
loader.load();
|
|
assert.strictEqual(loader.state, 'HAVE_NOTHING', 'the state at loadedplaylist correct');
|
|
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.ok(loader.master, 'the master playlist is available');
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'the state at loadedplaylist correct');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
});
|
|
|
|
QUnit.test('moves to HAVE_METADATA after loading a media playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedPlaylist = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on('loadedplaylist', function() {
|
|
loadedPlaylist++;
|
|
});
|
|
loader.on('loadedmetadata', function() {
|
|
loadedMetadata++;
|
|
});
|
|
|
|
loader.load();
|
|
assert.strictEqual(loadedPlaylist, 0, 'loadedplaylist not fired');
|
|
assert.strictEqual(loadedMetadata, 0, 'loadedmetadata not fired');
|
|
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loadedPlaylist, 1, 'fired loadedplaylist once');
|
|
assert.strictEqual(loadedMetadata, 0, 'fired loadedmetadata once');
|
|
assert.strictEqual(
|
|
loader.state, 'HAVE_MASTER',
|
|
'the loader state is correct before setting the media'
|
|
);
|
|
assert.ok(loader.master, 'sets the master playlist');
|
|
assert.strictEqual(this.requests.length, 0, 'no further requests are needed');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
// Initial media selection happens here as a result of calling load
|
|
// and receiving the master xml
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'the loader state is correct');
|
|
assert.strictEqual(loadedPlaylist, 2, 'fired loadedplaylist twice');
|
|
assert.strictEqual(loadedMetadata, 1, 'fired loadedmetadata once');
|
|
assert.ok(loader.media(), 'sets the media playlist');
|
|
});
|
|
|
|
QUnit.test('child loader moves to HAVE_METADATA when initialized with a master playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let loadedPlaylist = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const playlist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
|
|
const childLoader = new DashPlaylistLoader(playlist, this.fakeVhs, false, loader);
|
|
|
|
childLoader.on('loadedplaylist', function() {
|
|
loadedPlaylist++;
|
|
});
|
|
childLoader.on('loadedmetadata', function() {
|
|
loadedMetadata++;
|
|
});
|
|
|
|
assert.strictEqual(loadedPlaylist, 0, 'childLoader creation does not fire loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'childLoader creation should not fire loadedmetadata');
|
|
assert.strictEqual(childLoader.state, 'HAVE_NOTHING', 'childLoader state is HAVE_NOTHING before load');
|
|
assert.strictEqual(childLoader.media(), undefined, 'childLoader media not yet set');
|
|
|
|
childLoader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.strictEqual(childLoader.started, true, 'childLoader has started');
|
|
assert.strictEqual(childLoader.state, 'HAVE_METADATA', 'childLoader state is correct');
|
|
assert.strictEqual(loadedPlaylist, 1, 'triggered loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 1, 'triggered loadedmetadata');
|
|
assert.ok(childLoader.media(), 'sets the childLoader media playlist');
|
|
assert.ok(childLoader.media().attributes, 'sets the childLoader media attributes');
|
|
});
|
|
|
|
QUnit.test('child playlist moves to HAVE_METADATA when initialized with a live master playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-live.mpd', this.fakeVhs);
|
|
let loadedPlaylist = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const playlist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
|
|
const childLoader = new DashPlaylistLoader(playlist, this.fakeVhs, false, loader);
|
|
|
|
childLoader.on('loadedplaylist', function() {
|
|
loadedPlaylist++;
|
|
});
|
|
childLoader.on('loadedmetadata', function() {
|
|
loadedMetadata++;
|
|
});
|
|
|
|
assert.strictEqual(loadedPlaylist, 0, 'childLoader creation does not fire loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 0, 'childLoader creation should not fire loadedmetadata');
|
|
assert.strictEqual(childLoader.state, 'HAVE_NOTHING', 'childLoader state is HAVE_NOTHING before load');
|
|
assert.strictEqual(childLoader.media(), undefined, 'childLoader media not yet set');
|
|
|
|
childLoader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.strictEqual(childLoader.started, true, 'childLoader has started');
|
|
assert.strictEqual(childLoader.state, 'HAVE_METADATA', 'childLoader state is correct');
|
|
assert.strictEqual(loadedPlaylist, 1, 'triggered loadedplaylist');
|
|
assert.strictEqual(loadedMetadata, 1, 'triggered loadedmetadata');
|
|
assert.ok(childLoader.media(), 'sets the childLoader media playlist');
|
|
assert.ok(childLoader.media().attributes, 'sets the childLoader media attributes');
|
|
});
|
|
|
|
QUnit.test('returns to HAVE_METADATA after refreshing the playlist', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-live.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
|
|
// 10s, one target duration
|
|
this.clock.tick(10 * 1000);
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct');
|
|
});
|
|
|
|
QUnit.test('triggers an event when the active media changes', function(assert) {
|
|
// NOTE: this test relies upon calls to media behaving as though they are
|
|
// asynchronous operations.
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let mediaChanges = 0;
|
|
let mediaChangings = 0;
|
|
let loadedPlaylists = 0;
|
|
let loadedMetadata = 0;
|
|
|
|
loader.on('mediachange', () => {
|
|
mediaChanges++;
|
|
});
|
|
loader.on('mediachanging', () => {
|
|
mediaChangings++;
|
|
});
|
|
loader.on('loadedplaylist', () => {
|
|
loadedPlaylists++;
|
|
});
|
|
loader.on('loadedmetadata', () => {
|
|
loadedMetadata++;
|
|
});
|
|
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loadedPlaylists, 1, 'loadedplaylist triggered');
|
|
assert.strictEqual(loadedMetadata, 0, 'no loadedmetadata');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(mediaChangings, 0, 'initial selection is not a media changing');
|
|
assert.strictEqual(mediaChanges, 0, 'initial selection is not a media change');
|
|
assert.strictEqual(loadedPlaylists, 2, 'loadedplaylist triggered twice');
|
|
assert.strictEqual(loadedMetadata, 1, 'loadedmetadata triggered');
|
|
|
|
// switching to a different playlist
|
|
loader.media(loader.master.playlists[1]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(mediaChangings, 1, 'mediachanging fires immediately');
|
|
// Note: does not match PlaylistLoader behavior
|
|
assert.strictEqual(mediaChanges, 1, 'mediachange fires immediately');
|
|
assert.strictEqual(loadedPlaylists, 3, 'three loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'still one loadedmetadata');
|
|
|
|
// switch back to an already loaded playlist
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(mediaChangings, 2, 'mediachanging fires');
|
|
assert.strictEqual(mediaChanges, 2, 'fired a mediachange');
|
|
assert.strictEqual(loadedPlaylists, 3, 'still three loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'still one loadedmetadata');
|
|
|
|
// trigger a no-op switch
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.strictEqual(mediaChangings, 2, 'mediachanging ignored the no-op');
|
|
assert.strictEqual(mediaChanges, 2, 'ignored a no-op media change');
|
|
assert.strictEqual(loadedPlaylists, 3, 'still three loadedplaylists');
|
|
assert.strictEqual(loadedMetadata, 1, 'still one loadedmetadata');
|
|
});
|
|
|
|
QUnit.test('throws an error when initial manifest request fails', function(assert) {
|
|
const errors = [];
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function() {
|
|
errors.push(loader.error);
|
|
});
|
|
this.requests.pop().respond(500);
|
|
|
|
assert.equal(errors.length, 1, 'threw an error');
|
|
assert.equal(errors[0].status, 500, 'captured http status');
|
|
});
|
|
|
|
QUnit.test('throws an error if a media switch is initiated too early', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.throws(
|
|
function() {
|
|
loader.media('1080p');
|
|
},
|
|
new Error('Cannot switch media playlist from HAVE_NOTHING'),
|
|
'threw an error from HAVE_NOTHING'
|
|
);
|
|
});
|
|
|
|
QUnit.test(
|
|
'throws an error if a switch to an unrecognized playlist is requested',
|
|
function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
assert.throws(function() {
|
|
loader.media('unrecognized');
|
|
}, new Error('Unknown playlist URI: unrecognized'), 'throws an error');
|
|
}
|
|
);
|
|
|
|
QUnit.test('can switch playlists after the master is downloaded', function(assert) {
|
|
const clock = this.clock;
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media('0-placeholder-uri-0');
|
|
clock.tick(1);
|
|
|
|
assert.equal(loader.media().uri, 'placeholder-uri-0', 'changed to new playlist');
|
|
loader.media('1-placeholder-uri-1');
|
|
clock.tick(1);
|
|
assert.equal(loader.media().uri, 'placeholder-uri-1', 'changed to new playlist');
|
|
});
|
|
|
|
QUnit.test('can switch playlists based on object or URI', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
loader.media('0-placeholder-uri-0');
|
|
this.clock.tick(1);
|
|
assert.equal(loader.media().uri, 'placeholder-uri-0', 'changed to playlist by uri');
|
|
|
|
loader.media('1-placeholder-uri-1');
|
|
this.clock.tick(1);
|
|
assert.equal(loader.media().uri, 'placeholder-uri-1', 'changed to playlist by uri');
|
|
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.equal(loader.media().uri, 'placeholder-uri-0', 'changed to playlist by object');
|
|
});
|
|
|
|
QUnit.test('errors if requests take longer than 45s', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
let errors = 0;
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function() {
|
|
errors++;
|
|
});
|
|
this.clock.tick(45 * 1000);
|
|
|
|
assert.strictEqual(errors, 1, 'fired one error');
|
|
assert.strictEqual(loader.error.code, 2, 'fired a network error');
|
|
});
|
|
|
|
QUnit.test(
|
|
'parseMasterXml parses master manifest and sets up uri references',
|
|
function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
|
|
this.standardXHRResponse(this.requests.shift());
|
|
|
|
assert.equal(
|
|
loader.master.playlists[0].uri, 'placeholder-uri-0',
|
|
'setup phony uri for media playlist'
|
|
);
|
|
assert.equal(
|
|
loader.master.playlists[0].id, '0-placeholder-uri-0',
|
|
'setup phony id for media playlist'
|
|
);
|
|
assert.strictEqual(
|
|
loader.master.playlists['0-placeholder-uri-0'],
|
|
loader.master.playlists[0], 'set reference by uri for easy access'
|
|
);
|
|
assert.equal(
|
|
loader.master.playlists[1].uri, 'placeholder-uri-1',
|
|
'setup phony uri for media playlist'
|
|
);
|
|
assert.equal(
|
|
loader.master.playlists[1].id, '1-placeholder-uri-1',
|
|
'setup phony id for media playlist'
|
|
);
|
|
assert.strictEqual(
|
|
loader.master.playlists['1-placeholder-uri-1'],
|
|
loader.master.playlists[1], 'set reference by uri for easy access'
|
|
);
|
|
assert.equal(
|
|
loader.master.mediaGroups.AUDIO.audio.main.playlists[0].uri,
|
|
'placeholder-uri-AUDIO-audio-main', 'setup phony uri for media groups'
|
|
);
|
|
assert.equal(
|
|
loader.master.mediaGroups.AUDIO.audio.main.playlists[0].id,
|
|
'0-placeholder-uri-AUDIO-audio-main', 'setup phony id for media groups'
|
|
);
|
|
assert.strictEqual(
|
|
loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'],
|
|
loader.master.mediaGroups.AUDIO.audio.main.playlists[0],
|
|
'set reference by uri for easy access'
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test('refreshes the xml if there is a minimumUpdatePeriod', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-live.mpd', this.fakeVhs);
|
|
let minimumUpdatePeriods = 0;
|
|
|
|
loader.on('minimumUpdatePeriod', () => minimumUpdatePeriods++);
|
|
|
|
loader.load();
|
|
assert.equal(minimumUpdatePeriods, 0, 'no refreshes to start');
|
|
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.equal(minimumUpdatePeriods, 0, 'no refreshes immediately after response');
|
|
|
|
this.clock.tick(4 * 1000);
|
|
|
|
assert.equal(this.requests.length, 1, 'refreshed manifest');
|
|
assert.equal(this.requests[0].uri, 'dash-live.mpd', 'refreshed manifest');
|
|
assert.equal(minimumUpdatePeriods, 1, 'refreshed manifest');
|
|
});
|
|
|
|
QUnit.test('stop xml refresh if minimumUpdatePeriod changes from `mUP > 0` to `mUP == 0`', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-live.mpd', this.fakeVhs);
|
|
let minimumUpdatePeriods = 0;
|
|
|
|
loader.on('minimumUpdatePeriod', () => minimumUpdatePeriods++);
|
|
|
|
loader.load();
|
|
|
|
// Start Request
|
|
assert.equal(minimumUpdatePeriods, 0, 'no refreshes to start');
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.equal(minimumUpdatePeriods, 0, 'no refreshes immediately after response');
|
|
|
|
// First Refresh Tick
|
|
this.clock.tick(4 * 1000);
|
|
this.standardXHRResponse(this.requests[0], loader.masterXml_);
|
|
assert.equal(this.requests.length, 1, 'refreshed manifest');
|
|
assert.equal(this.requests[0].uri, 'dash-live.mpd', 'refreshed manifest');
|
|
assert.equal(minimumUpdatePeriods, 1, 'total minimumUpdatePeriods');
|
|
|
|
// Second Refresh Tick: MinimumUpdatePeriod Removed
|
|
this.clock.tick(4 * 1000);
|
|
this.standardXHRResponse(this.requests[1], loader.masterXml_.replace('minimumUpdatePeriod="PT4S"', ''));
|
|
this.clock.tick(4 * 1000);
|
|
this.standardXHRResponse(this.requests[2]);
|
|
assert.equal(this.requests.length, 3, 'final manifest refresh');
|
|
assert.equal(minimumUpdatePeriods, 3, 'final minimumUpdatePeriods');
|
|
|
|
// Third Refresh Tick: No Additional Requests Expected
|
|
this.clock.tick(4 * 1000);
|
|
assert.equal(this.requests.length, 3, 'final manifest refresh');
|
|
assert.equal(minimumUpdatePeriods, 3, 'final minimumUpdatePeriods');
|
|
});
|
|
|
|
QUnit.test('media playlists "refresh" by re-parsing master xml', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-live.mpd', this.fakeVhs);
|
|
let refreshes = 0;
|
|
|
|
loader.on('mediaupdatetimeout', () => refreshes++);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
|
|
// 1s, half segment target duration, since the playlist didn't change
|
|
this.clock.tick(2 * 500);
|
|
|
|
assert.equal(refreshes, 1, 'refreshed playlist after last segment duration');
|
|
});
|
|
|
|
QUnit.test('delays load when on final rendition', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
const origHasPendingRequest = loader.hasPendingRequest;
|
|
let loadedplaylistEvents = 0;
|
|
|
|
loader.on('loadedplaylist', () => loadedplaylistEvents++);
|
|
|
|
// do an initial load to start the loader
|
|
loader.load();
|
|
// pretend there's a pending media request so
|
|
// media isn't selected automatically
|
|
loader.hasPendingRequest = () => true;
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.equal(loadedplaylistEvents, 1, 'one loadedplaylist event after first load');
|
|
loader.hasPendingRequest = origHasPendingRequest;
|
|
|
|
loader.media(loader.master.playlists[0]);
|
|
this.clock.tick(1);
|
|
assert.equal(loadedplaylistEvents, 2, 'one more loadedplaylist event after media selected');
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
assert.equal(loadedplaylistEvents, 3, 'one more loadedplaylist event after load');
|
|
|
|
loader.load(false);
|
|
this.clock.tick(1);
|
|
assert.equal(
|
|
loadedplaylistEvents,
|
|
4,
|
|
'one more loadedplaylist event after load with isFinalRendition false'
|
|
);
|
|
|
|
loader.load(true);
|
|
this.clock.tick(1);
|
|
assert.equal(
|
|
loadedplaylistEvents,
|
|
4,
|
|
'no loadedplaylist event after load with isFinalRendition false'
|
|
);
|
|
|
|
this.clock.tick(loader.media().targetDuration / 2 * 1000);
|
|
assert.equal(
|
|
loadedplaylistEvents,
|
|
5,
|
|
'one more loadedplaylist event after final rendition delay'
|
|
);
|
|
});
|
|
|
|
QUnit.test('requests sidx if master xml includes it', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-sidx.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
assert.strictEqual(loader.state, 'HAVE_MASTER', 'state is HAVE_MASTER');
|
|
assert.ok(loader.master.playlists[0].sidx, 'sidx info is returned from parser');
|
|
|
|
// initial media selection happens automatically
|
|
// as there was no pending request
|
|
assert.ok(loader.hasPendingRequest(), 'request is pending');
|
|
assert.strictEqual(this.requests.length, 1, 'one request for sidx has been made');
|
|
assert.notOk(loader.media(), 'media playlist is not yet set');
|
|
|
|
this.standardXHRResponse(this.requests.shift(), mp4VideoInitSegment().subarray(0, 10));
|
|
this.standardXHRResponse(this.requests.shift(), sidxResponse());
|
|
assert.strictEqual(loader.state, 'HAVE_METADATA', 'state is HAVE_METADATA');
|
|
assert.ok(loader.media(), 'media playlist is set');
|
|
assert.ok(loader.media().sidx, 'sidx info attribute is preserved');
|
|
assert.deepEqual(
|
|
loader.media().segments[0].byterange, {
|
|
offset: 400,
|
|
length: 13001
|
|
},
|
|
'sidx was correctly applied'
|
|
);
|
|
});
|
|
|
|
QUnit.test('child loaders wait for async action before moving to HAVE_MASTER', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
const childPlaylist = loader.master.playlists['0-placeholder-uri-AUDIO-audio-main'];
|
|
const childLoader = new DashPlaylistLoader(childPlaylist, this.fakeVhs, false, loader);
|
|
|
|
childLoader.load();
|
|
assert.strictEqual(childLoader.state, 'HAVE_NOTHING');
|
|
|
|
this.clock.tick(1);
|
|
// media playlist is chosen automatically
|
|
assert.strictEqual(childLoader.state, 'HAVE_METADATA');
|
|
});
|
|
|
|
QUnit.test('load resumes the media update timer for live playlists', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash-live.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
this.clock.tick(1);
|
|
|
|
const origMediaUpdateTimeout = loader.mediaUpdateTimeout;
|
|
|
|
assert.ok(origMediaUpdateTimeout, 'media update timeout set');
|
|
|
|
loader.pause();
|
|
loader.load();
|
|
|
|
const newMediaUpdateTimeout = loader.mediaUpdateTimeout;
|
|
|
|
assert.ok(newMediaUpdateTimeout, 'media update timeout set');
|
|
assert.notEqual(
|
|
origMediaUpdateTimeout,
|
|
newMediaUpdateTimeout,
|
|
'media update timeout is different'
|
|
);
|
|
});
|
|
|
|
QUnit.test('load does not resume the media update timer for non live playlists', function(assert) {
|
|
const loader = new DashPlaylistLoader('dash.mpd', this.fakeVhs);
|
|
|
|
loader.load();
|
|
this.standardXHRResponse(this.requests.shift());
|
|
this.clock.tick(1);
|
|
|
|
assert.notOk(loader.mediaUpdateTimeout, 'media update timeout not set');
|
|
|
|
loader.pause();
|
|
loader.load();
|
|
|
|
assert.notOk(loader.mediaUpdateTimeout, 'media update timeout not set');
|
|
});
|