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.
1403 lines
43 KiB
1403 lines
43 KiB
import QUnit from 'qunit';
|
|
import videojs from 'video.js';
|
|
import xhrFactory from '../src/xhr';
|
|
import Config from '../src/config';
|
|
import {
|
|
playlistWithDuration,
|
|
useFakeEnvironment,
|
|
createResponseText,
|
|
standardXHRResponse,
|
|
setupMediaSource
|
|
} from './test-helpers.js';
|
|
import { MasterPlaylistController } from '../src/master-playlist-controller';
|
|
import SourceUpdater from '../src/source-updater';
|
|
import SyncController from '../src/sync-controller';
|
|
import TimelineChangeController from '../src/timeline-change-controller';
|
|
import Decrypter from 'worker!../src/decrypter-worker.worker.js';
|
|
import window from 'global/window';
|
|
/* eslint-disable no-unused-vars */
|
|
// we need this so that it can register VHS with videojs
|
|
import { Vhs } from '../src/videojs-http-streaming';
|
|
/* eslint-enable no-unused-vars */
|
|
import {
|
|
muxed as muxedSegment,
|
|
mp4Video as mp4VideoSegment,
|
|
mp4VideoInit as mp4VideoInitSegment
|
|
} from 'create-test-data!segments';
|
|
|
|
/**
|
|
* beforeEach and afterEach hooks that should be run segment loader tests regardless of
|
|
* the type of loader.
|
|
*/
|
|
export const LoaderCommonHooks = {
|
|
beforeEach(assert) {
|
|
this.env = useFakeEnvironment(assert);
|
|
this.clock = this.env.clock;
|
|
this.requests = this.env.requests;
|
|
this.currentTime = 0;
|
|
this.seekable = {
|
|
length: 0
|
|
};
|
|
this.seeking = false;
|
|
this.hasPlayed = true;
|
|
this.paused = false;
|
|
this.playbackRate = 1;
|
|
this.fakeVhs = {
|
|
xhr: xhrFactory(),
|
|
tech_: {
|
|
paused: () => this.paused,
|
|
playbackRate: () => this.playbackRate,
|
|
currentTime: () => this.currentTime,
|
|
textTracks: () => {},
|
|
addRemoteTextTrack: () => {},
|
|
trigger: () => {}
|
|
}
|
|
};
|
|
this.tech_ = this.fakeVhs.tech_;
|
|
this.goalBufferLength =
|
|
MasterPlaylistController.prototype.goalBufferLength.bind(this);
|
|
this.mediaSource = new window.MediaSource();
|
|
this.sourceUpdater = new SourceUpdater(this.mediaSource);
|
|
this.syncController = new SyncController();
|
|
this.decrypter = new Decrypter();
|
|
this.timelineChangeController = new TimelineChangeController();
|
|
},
|
|
afterEach(assert) {
|
|
this.env.restore();
|
|
this.decrypter.terminate();
|
|
this.sourceUpdater.dispose();
|
|
this.timelineChangeController.dispose();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns a settings object containing the custom settings provided merged with defaults
|
|
* for use in constructing a segment loader. This function should be called with the QUnit
|
|
* test environment the loader will be constructed in for proper this reference.
|
|
*
|
|
* @param {Object} settings
|
|
* custom settings for the loader
|
|
* @return {Object}
|
|
* Settings object containing custom settings merged with defaults
|
|
*/
|
|
export const LoaderCommonSettings = function(settings) {
|
|
return videojs.mergeOptions({
|
|
vhs: this.fakeVhs,
|
|
currentTime: () => this.currentTime,
|
|
seekable: () => this.seekable,
|
|
seeking: () => this.seeking,
|
|
hasPlayed: () => this.hasPlayed,
|
|
duration: () => this.mediaSource.duration,
|
|
goalBufferLength: () => this.goalBufferLength(),
|
|
mediaSource: this.mediaSource,
|
|
sourceUpdater: this.sourceUpdater,
|
|
syncController: this.syncController,
|
|
decrypter: this.decrypter,
|
|
timelineChangeController: this.timelineChangeController
|
|
}, settings);
|
|
};
|
|
|
|
/**
|
|
* Sets up a QUnit module to run tests that should be run on all segment loader types.
|
|
* Currently only two types, SegmentLoader and VTTSegmentLoader.
|
|
*
|
|
* @param {function(new:SegmentLoader|VTTLoader, Object)} LoaderConstructor
|
|
* Constructor for segment loader. Takes one parameter, a settings object
|
|
* @param {Object} loaderSettings
|
|
* Custom settings to merge with defaults for the provided loader constructor
|
|
* @param {function(SegmentLoader|VTTLoader)} loaderBeforeEach
|
|
* Function to be run in the beforeEach after loader creation. Takes one parameter,
|
|
* the loader for custom modifications to the loader object.
|
|
*/
|
|
|
|
export const LoaderCommonFactory = ({
|
|
LoaderConstructor,
|
|
loaderSettings,
|
|
loaderBeforeEach,
|
|
usesAsyncAppends = true,
|
|
initSegments = true,
|
|
testData = muxedSegment
|
|
}) => {
|
|
let loader;
|
|
|
|
QUnit.module('Loader Common', function(hooks) {
|
|
hooks.beforeEach(function(assert) {
|
|
// Assume this module is nested and the parent module uses CommonHooks.beforeEach
|
|
|
|
loader = new LoaderConstructor(LoaderCommonSettings.call(this, loaderSettings), {});
|
|
|
|
if (loaderBeforeEach) {
|
|
loaderBeforeEach(loader);
|
|
}
|
|
});
|
|
|
|
hooks.afterEach(function(assert) {
|
|
loader.dispose();
|
|
});
|
|
|
|
QUnit.test('fails without required initialization options', function(assert) {
|
|
/* eslint-disable no-new */
|
|
assert.throws(function() {
|
|
new LoaderConstructor();
|
|
}, 'requires options');
|
|
assert.throws(function() {
|
|
new LoaderConstructor({});
|
|
}, 'requires a currentTime callback');
|
|
assert.throws(function() {
|
|
new LoaderConstructor({
|
|
currentTime() {}
|
|
});
|
|
}, 'requires a media source');
|
|
/* eslint-enable */
|
|
});
|
|
|
|
QUnit.test('calling load is idempotent', function(assert) {
|
|
loader.playlist(playlistWithDuration(20));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.state, 'WAITING', 'moves to the ready state');
|
|
assert.equal(this.requests.length, 1, 'made one request');
|
|
|
|
loader.load();
|
|
assert.equal(loader.state, 'WAITING', 'still in the ready state');
|
|
assert.equal(this.requests.length, 1, 'still one request');
|
|
|
|
// some time passes and a response is received
|
|
this.clock.tick(100);
|
|
this.requests[0].response = new Uint8Array(10).buffer;
|
|
this.requests.shift().respond(200, null, createResponseText(10));
|
|
loader.load();
|
|
assert.equal(this.requests.length, 0, 'load has no effect');
|
|
});
|
|
|
|
QUnit.test('calling load should unpause', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
loader.playlist(playlistWithDuration(20));
|
|
loader.pause();
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
assert.equal(loader.paused(), false, 'loading unpauses');
|
|
|
|
loader.pause();
|
|
this.clock.tick(1);
|
|
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
assert.equal(loader.paused(), true, 'stayed paused');
|
|
loader.load();
|
|
assert.equal(loader.paused(), false, 'unpaused during processing');
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appending', loader.pause);
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
loader.pause();
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
|
|
assert.equal(loader.state, 'READY', 'finished processing');
|
|
assert.ok(loader.paused(), 'stayed paused');
|
|
|
|
loader.load();
|
|
assert.equal(loader.paused(), false, 'unpaused');
|
|
});
|
|
});
|
|
|
|
QUnit.test('regularly checks the buffer while unpaused', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
loader.playlist(playlistWithDuration(90));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
// fill the buffer
|
|
this.clock.tick(1);
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
loader.buffered_ = () => videojs.createTimeRanges([[
|
|
0, Config.GOAL_BUFFER_LENGTH
|
|
]]);
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
assert.notOk(loader.error(), 'loader has no error');
|
|
assert.equal(this.requests.length, 0, 'no outstanding requests');
|
|
|
|
// play some video to drain the buffer
|
|
this.currentTime = Config.GOAL_BUFFER_LENGTH;
|
|
this.clock.tick(10 * 1000);
|
|
assert.equal(this.requests.length, 1, 'requested another segment');
|
|
});
|
|
});
|
|
|
|
QUnit.test('does not check the buffer while paused', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
loader.playlist(playlistWithDuration(90));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.pause();
|
|
this.clock.tick(1);
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
this.clock.tick(10 * 1000);
|
|
assert.equal(this.requests.length, 0, 'did not make a request');
|
|
});
|
|
});
|
|
|
|
QUnit.test('calculates bandwidth after downloading a segment', function(assert) {
|
|
const segment = testData();
|
|
const segmentBytes = segment.byteLength;
|
|
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
// some time passes and a response is received
|
|
this.clock.tick(100);
|
|
standardXHRResponse(this.requests.shift(), segment);
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
assert.equal(
|
|
loader.bandwidth,
|
|
(segmentBytes / 100) * 8 * 1000,
|
|
'calculated bandwidth'
|
|
);
|
|
assert.equal(loader.roundTrip, 100, 'saves request round trip time');
|
|
assert.equal(
|
|
loader.mediaBytesTransferred,
|
|
segmentBytes,
|
|
'saved mediaBytesTransferred'
|
|
);
|
|
assert.equal(loader.mediaTransferDuration, 100, '100 ms (clock above)');
|
|
});
|
|
});
|
|
|
|
QUnit.test('segment request timeouts reset bandwidth', function(assert) {
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
// a lot of time passes so the request times out
|
|
this.requests[0].timedout = true;
|
|
this.clock.tick(100 * 1000);
|
|
|
|
assert.equal(loader.bandwidth, 1, 'reset bandwidth');
|
|
assert.ok(isNaN(loader.roundTrip), 'reset round trip time');
|
|
});
|
|
|
|
QUnit.test('progress on segment requests are redispatched', function(assert) {
|
|
let progressEvents = 0;
|
|
|
|
loader.on('progress', function() {
|
|
progressEvents++;
|
|
});
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
this.requests[0].responseText = '';
|
|
this.requests[0].dispatchEvent({ type: 'progress', target: this.requests[0] });
|
|
assert.equal(progressEvents, 1, 'triggered progress');
|
|
});
|
|
|
|
QUnit.test(
|
|
'aborts request at progress events if bandwidth is too low',
|
|
function(assert) {
|
|
const playlist1 = playlistWithDuration(10, { uri: 'playlist1.m3u8' });
|
|
const playlist2 = playlistWithDuration(10, { uri: 'playlist2.m3u8' });
|
|
const playlist3 = playlistWithDuration(10, { uri: 'playlist3.m3u8' });
|
|
const playlist4 = playlistWithDuration(10, { uri: 'playlist4.m3u8' });
|
|
const xhrOptions = {
|
|
timeout: 15000
|
|
};
|
|
let bandwidthupdates = 0;
|
|
let firstProgress = false;
|
|
|
|
playlist1.attributes.BANDWIDTH = 18000;
|
|
playlist2.attributes.BANDWIDTH = 10000;
|
|
playlist3.attributes.BANDWIDTH = 8888;
|
|
playlist4.attributes.BANDWIDTH = 7777;
|
|
|
|
loader.vhs_.playlists = {
|
|
master: {
|
|
playlists: [
|
|
playlist1,
|
|
playlist2,
|
|
playlist3,
|
|
playlist4
|
|
]
|
|
}
|
|
};
|
|
|
|
const oldHandleProgress = loader.handleProgress_.bind(loader);
|
|
|
|
loader.handleProgress_ = (event, simpleSegment) => {
|
|
if (!firstProgress) {
|
|
firstProgress = true;
|
|
assert.equal(
|
|
simpleSegment.stats.firstBytesReceivedAt, Date.now(),
|
|
'firstBytesReceivedAt timestamp added on first progress event with bytes'
|
|
);
|
|
}
|
|
oldHandleProgress(event, simpleSegment);
|
|
};
|
|
|
|
let earlyAborts = 0;
|
|
|
|
loader.on('earlyabort', () => earlyAborts++);
|
|
|
|
loader.on('bandwidthupdate', () => bandwidthupdates++);
|
|
loader.playlist(playlist1, xhrOptions);
|
|
loader.load();
|
|
|
|
this.clock.tick(1);
|
|
|
|
// TODO, probably want to repeat this test for
|
|
// both partial appends and full segment playback
|
|
this.requests[0].responseText = '';
|
|
this.requests[0].dispatchEvent({
|
|
type: 'progress',
|
|
target: this.requests[0],
|
|
loaded: 1
|
|
});
|
|
|
|
assert.equal(bandwidthupdates, 0, 'no bandwidth updates yet');
|
|
assert.notOk(this.requests[0].aborted, 'request not prematurely aborted');
|
|
assert.equal(earlyAborts, 0, 'no earlyabort events');
|
|
|
|
this.clock.tick(999);
|
|
|
|
this.requests[0].dispatchEvent({
|
|
type: 'progress',
|
|
target: this.requests[0],
|
|
loaded: 2000
|
|
});
|
|
|
|
assert.equal(bandwidthupdates, 0, 'no bandwidth updates yet');
|
|
assert.notOk(this.requests[0].aborted, 'request not prematurely aborted');
|
|
assert.equal(earlyAborts, 0, 'no earlyabort events');
|
|
|
|
this.clock.tick(2);
|
|
|
|
this.requests[0].dispatchEvent({
|
|
type: 'progress',
|
|
target: this.requests[0],
|
|
loaded: 2001
|
|
});
|
|
|
|
assert.equal(bandwidthupdates, 0, 'bandwidth not updated');
|
|
assert.ok(this.requests[0].aborted, 'request aborted');
|
|
assert.equal(earlyAborts, 1, 'earlyabort event triggered');
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
'appending a segment when loader is in walk-forward mode triggers bandwidthupdate',
|
|
function(assert) {
|
|
let progresses = 0;
|
|
|
|
loader.on('bandwidthupdate', function() {
|
|
progresses++;
|
|
});
|
|
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
loader.playlist(playlistWithDuration(20));
|
|
loader.load();
|
|
|
|
this.clock.tick(1);
|
|
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
assert.equal(progresses, 0, 'no bandwidthupdate fired');
|
|
|
|
this.clock.tick(2);
|
|
// if mediaIndex is set, then the SegmentLoader is in walk-forward mode
|
|
loader.mediaIndex = 1;
|
|
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(function() {
|
|
assert.equal(progresses, 1, 'fired bandwidthupdate');
|
|
});
|
|
}
|
|
);
|
|
|
|
QUnit.test('only requests one segment at a time', function(assert) {
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
// a bunch of time passes without recieving a response
|
|
this.clock.tick(20 * 1000);
|
|
assert.equal(this.requests.length, 1, 'only one request was made');
|
|
});
|
|
|
|
if (initSegments) {
|
|
QUnit.test('downloads init segments if specified', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_, {isVideoOnly: true}).then(() => {
|
|
const playlist = playlistWithDuration(20);
|
|
const map = {
|
|
resolvedUri: 'mainInitSegment',
|
|
byterange: {
|
|
length: 20,
|
|
offset: 0
|
|
}
|
|
};
|
|
|
|
playlist.segments[0].map = map;
|
|
playlist.segments[1].map = map;
|
|
loader.playlist(playlist);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(this.requests.length, 2, 'made requests');
|
|
assert.equal(this.requests[0].url, 'mainInitSegment', 'requested the init segment');
|
|
|
|
standardXHRResponse(this.requests.shift(), mp4VideoInitSegment());
|
|
|
|
assert.equal(this.requests[0].url, '0.ts', 'requested the segment');
|
|
|
|
standardXHRResponse(this.requests.shift(), mp4VideoSegment());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(this.requests.length, 1, 'made a request');
|
|
assert.equal(
|
|
this.requests[0].url, '1.ts',
|
|
'did not re-request the init segment'
|
|
);
|
|
});
|
|
});
|
|
|
|
QUnit.test('detects init segment changes and downloads it', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_, {isVideoOnly: true}).then(() => {
|
|
const playlist = playlistWithDuration(20);
|
|
const buffered = videojs.createTimeRanges();
|
|
|
|
playlist.segments[0].map = {
|
|
resolvedUri: 'init0',
|
|
byterange: {
|
|
length: 20,
|
|
offset: 0
|
|
}
|
|
};
|
|
playlist.segments[1].map = {
|
|
resolvedUri: 'init0',
|
|
byterange: {
|
|
length: 20,
|
|
offset: 20
|
|
}
|
|
};
|
|
|
|
loader.buffered_ = () => buffered;
|
|
loader.playlist(playlist);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(this.requests.length, 2, 'made requests');
|
|
|
|
assert.equal(this.requests[0].url, 'init0', 'requested the init segment');
|
|
assert.equal(
|
|
this.requests[0].headers.Range, 'bytes=0-19',
|
|
'requested the init segment byte range'
|
|
);
|
|
standardXHRResponse(this.requests.shift(), mp4VideoInitSegment());
|
|
assert.equal(
|
|
this.requests[0].url, '0.ts',
|
|
'requested the segment'
|
|
);
|
|
standardXHRResponse(this.requests.shift(), mp4VideoSegment());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(this.requests.length, 2, 'made requests');
|
|
assert.equal(this.requests[0].url, 'init0', 'requested the init segment');
|
|
assert.equal(
|
|
this.requests[0].headers.Range, 'bytes=20-39',
|
|
'requested the init segment byte range'
|
|
);
|
|
assert.equal(
|
|
this.requests[1].url, '1.ts',
|
|
'did not re-request the init segment'
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
QUnit.test('request error increments mediaRequestsErrored stat', function(assert) {
|
|
loader.playlist(playlistWithDuration(20));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
this.requests.shift().respond(404, null, '');
|
|
|
|
// verify stats
|
|
assert.equal(loader.mediaRequests, 1, '1 request');
|
|
assert.equal(loader.mediaRequestsErrored, 1, '1 errored request');
|
|
});
|
|
|
|
QUnit.test('request timeout increments mediaRequestsTimedout stat', function(assert) {
|
|
loader.playlist(playlistWithDuration(20));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
this.requests[0].timedout = true;
|
|
this.clock.tick(100 * 1000);
|
|
|
|
// verify stats
|
|
assert.equal(loader.mediaRequests, 1, '1 request');
|
|
assert.equal(loader.mediaRequestsTimedout, 1, '1 timed-out request');
|
|
});
|
|
|
|
QUnit.test('request abort increments mediaRequestsAborted stat', function(assert) {
|
|
loader.playlist(playlistWithDuration(20));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.abort();
|
|
this.clock.tick(1);
|
|
|
|
// verify stats
|
|
// right now, aborted requests are not counted in media requests, but this may be
|
|
// changed in the future
|
|
assert.equal(loader.mediaRequests, 0, '0 requests');
|
|
assert.equal(loader.mediaRequestsAborted, 1, '1 aborted request');
|
|
});
|
|
|
|
QUnit.test('SegmentLoader.mediaIndex is adjusted when live playlist is updated', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 0,
|
|
endList: false
|
|
}));
|
|
|
|
loader.load();
|
|
// Start at mediaIndex 2 which means that the next segment we request
|
|
// should mediaIndex 3
|
|
loader.mediaIndex = 2;
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.mediaIndex, 2, 'SegmentLoader.mediaIndex starts at 2');
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'3.ts',
|
|
'requesting the segment at mediaIndex 3'
|
|
);
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
assert.equal(loader.mediaIndex, 3, 'mediaIndex ends at 3');
|
|
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.mediaIndex, 3, 'SegmentLoader.mediaIndex starts at 3');
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'4.ts',
|
|
'requesting the segment at mediaIndex 4'
|
|
);
|
|
|
|
// Update the playlist shifting the mediaSequence by 2 which will result
|
|
// in a decrement of the mediaIndex by 2 to 1
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 2,
|
|
endList: false
|
|
}));
|
|
|
|
assert.equal(loader.mediaIndex, 1, 'SegmentLoader.mediaIndex is updated to 1');
|
|
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
assert.equal(loader.mediaIndex, 2, 'SegmentLoader.mediaIndex ends at 2');
|
|
});
|
|
});
|
|
|
|
QUnit.test('segmentInfo.mediaIndex is adjusted when live playlist is updated', function(assert) {
|
|
let expectedLoaderIndex = 3;
|
|
const handleAppendsDone_ = loader.handleAppendsDone_.bind(loader);
|
|
|
|
loader.handleAppendsDone_ = function() {
|
|
handleAppendsDone_();
|
|
|
|
assert.equal(
|
|
loader.mediaIndex,
|
|
expectedLoaderIndex,
|
|
'SegmentLoader.mediaIndex ends at ' + expectedLoaderIndex
|
|
);
|
|
loader.mediaIndex = null;
|
|
loader.fetchAtBuffer_ = false;
|
|
// remove empty flag that may be added by vtt loader
|
|
loader.playlist_.segments.forEach(segment => {
|
|
segment.empty = false;
|
|
});
|
|
};
|
|
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
// Setting currentTime to 31 so that we start requesting at segment #3
|
|
this.currentTime = 31;
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 0,
|
|
endList: false
|
|
}));
|
|
|
|
loader.load();
|
|
// Start at mediaIndex null which means that the next segment we request
|
|
// should be based on currentTime (mediaIndex 3)
|
|
loader.mediaIndex = null;
|
|
loader.syncPoint_ = {
|
|
segmentIndex: 0,
|
|
time: 0
|
|
};
|
|
this.clock.tick(1);
|
|
|
|
const segmentInfo = loader.pendingSegment_;
|
|
|
|
assert.equal(segmentInfo.mediaIndex, 3, 'segmentInfo.mediaIndex starts at 3');
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'3.ts',
|
|
'requesting the segment at mediaIndex 3'
|
|
);
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
this.clock.tick(1);
|
|
|
|
const segmentInfo = loader.pendingSegment_;
|
|
|
|
assert.equal(segmentInfo.mediaIndex, 3, 'segmentInfo.mediaIndex starts at 3');
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'3.ts',
|
|
'requesting the segment at mediaIndex 3'
|
|
);
|
|
|
|
// Update the playlist shifting the mediaSequence by 2 which will result
|
|
// in a decrement of the mediaIndex by 2 to 1
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 2,
|
|
endList: false
|
|
}));
|
|
|
|
assert.equal(segmentInfo.mediaIndex, 1, 'segmentInfo.mediaIndex is updated to 1');
|
|
expectedLoaderIndex = 1;
|
|
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
});
|
|
});
|
|
|
|
QUnit.test('segment 404s should trigger an error', function(assert) {
|
|
const errors = [];
|
|
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function(error) {
|
|
errors.push(error);
|
|
});
|
|
this.requests.shift().respond(404, null, '');
|
|
|
|
assert.equal(errors.length, 1, 'triggered an error');
|
|
assert.equal(loader.error().code, 2, 'triggered MEDIA_ERR_NETWORK');
|
|
assert.ok(loader.error().xhr, 'included the request object');
|
|
assert.ok(loader.paused(), 'paused the loader');
|
|
assert.equal(loader.state, 'READY', 'returned to the ready state');
|
|
});
|
|
|
|
QUnit.test('empty segments should trigger an error', function(assert) {
|
|
const errors = [];
|
|
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function(error) {
|
|
errors.push(error);
|
|
});
|
|
this.requests[0].response = new Uint8Array(0).buffer;
|
|
this.requests.shift().respond(200, null, '');
|
|
|
|
assert.equal(errors.length, 1, 'triggered an error');
|
|
assert.equal(loader.error().code, 2, 'triggered MEDIA_ERR_NETWORK');
|
|
assert.ok(loader.error().xhr, 'included the request object');
|
|
assert.ok(loader.paused(), 'paused the loader');
|
|
assert.equal(loader.state, 'READY', 'returned to the ready state');
|
|
});
|
|
|
|
QUnit.test('segment 5xx status codes trigger an error', function(assert) {
|
|
const errors = [];
|
|
|
|
loader.playlist(playlistWithDuration(10));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function(error) {
|
|
errors.push(error);
|
|
});
|
|
this.requests.shift().respond(500, null, '');
|
|
|
|
assert.equal(errors.length, 1, 'triggered an error');
|
|
assert.equal(loader.error().code, 2, 'triggered MEDIA_ERR_NETWORK');
|
|
assert.ok(loader.error().xhr, 'included the request object');
|
|
assert.ok(loader.paused(), 'paused the loader');
|
|
assert.equal(loader.state, 'READY', 'returned to the ready state');
|
|
});
|
|
|
|
QUnit.test('remains ready if there are no segments', function(assert) {
|
|
loader.playlist(playlistWithDuration(0));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.state, 'READY', 'in the ready state');
|
|
});
|
|
|
|
QUnit.test('dispose cleans up outstanding work', function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
loader.playlist(playlistWithDuration(20));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
loader.dispose();
|
|
|
|
assert.ok(this.requests[0].aborted, 'aborted segment request');
|
|
assert.equal(this.requests.length, 1, 'did not open another request');
|
|
});
|
|
});
|
|
|
|
// ----------
|
|
// Decryption
|
|
// ----------
|
|
|
|
QUnit.test(
|
|
'calling load with an encrypted segment requests key and segment',
|
|
function(assert) {
|
|
assert.equal(loader.state, 'INIT', 'starts in the init state');
|
|
loader.playlist(playlistWithDuration(10, {isEncrypted: true}));
|
|
assert.equal(loader.state, 'INIT', 'starts in the init state');
|
|
assert.ok(loader.paused(), 'starts paused');
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.state, 'WAITING', 'moves to the ready state');
|
|
assert.ok(!loader.paused(), 'loading is not paused');
|
|
assert.equal(this.requests.length, 2, 'requested a segment and key');
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'0-key.php',
|
|
'requested the first segment\'s key'
|
|
);
|
|
assert.equal(this.requests[1].url, '0.ts', 'requested the first segment');
|
|
}
|
|
);
|
|
|
|
QUnit.test('dispose cleans up key requests for encrypted segments', function(assert) {
|
|
loader.playlist(playlistWithDuration(20, {isEncrypted: true}));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.dispose();
|
|
assert.equal(this.requests.length, 2, 'requested a segment and key');
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'0-key.php',
|
|
'requested the first segment\'s key'
|
|
);
|
|
assert.ok(this.requests[0].aborted, 'aborted the first segment\s key request');
|
|
assert.equal(this.requests.length, 2, 'did not open another request');
|
|
});
|
|
|
|
QUnit.test('key 404s pauses the loader and triggers error', function(assert) {
|
|
const errors = [];
|
|
|
|
loader.playlist(playlistWithDuration(10, {isEncrypted: true}));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function(error) {
|
|
errors.push(error);
|
|
});
|
|
this.requests.shift().respond(404, null, '');
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(errors.length, 1, 'triggered an error');
|
|
assert.equal(loader.error().code, 2, 'triggered MEDIA_ERR_NETWORK');
|
|
assert.equal(
|
|
loader.error().message, 'HLS request errored at URL: 0-key.php',
|
|
'receieved a key error message'
|
|
);
|
|
assert.ok(loader.error().xhr, 'included the request object');
|
|
assert.ok(loader.paused(), 'paused the loader');
|
|
assert.equal(loader.state, 'READY', 'returned to the ready state');
|
|
});
|
|
|
|
QUnit.test('key 500 status code pauses loader and triggers error', function(assert) {
|
|
const errors = [];
|
|
|
|
loader.playlist(playlistWithDuration(10, {isEncrypted: true}));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
loader.on('error', function(error) {
|
|
errors.push(error);
|
|
});
|
|
this.requests.shift().respond(500, null, '');
|
|
|
|
assert.equal(errors.length, 1, 'triggered an error');
|
|
assert.equal(loader.error().code, 2, 'triggered MEDIA_ERR_NETWORK');
|
|
assert.equal(
|
|
loader.error().message, 'HLS request errored at URL: 0-key.php',
|
|
'receieved a key error message'
|
|
);
|
|
assert.ok(loader.error().xhr, 'included the request object');
|
|
assert.ok(loader.paused(), 'paused the loader');
|
|
assert.equal(loader.state, 'READY', 'returned to the ready state');
|
|
});
|
|
|
|
QUnit.test('key request timeouts reset bandwidth', function(assert) {
|
|
loader.playlist(playlistWithDuration(10, {isEncrypted: true}));
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(
|
|
this.requests[0].url,
|
|
'0-key.php',
|
|
'requested the first segment\'s key'
|
|
);
|
|
assert.equal(this.requests[1].url, '0.ts', 'requested the first segment');
|
|
// a lot of time passes so the request times out
|
|
this.requests[0].timedout = true;
|
|
this.clock.tick(100 * 1000);
|
|
|
|
assert.equal(loader.bandwidth, 1, 'reset bandwidth');
|
|
assert.ok(isNaN(loader.roundTrip), 'reset round trip time');
|
|
});
|
|
|
|
QUnit.test(
|
|
'checks the goal buffer configuration every loading opportunity',
|
|
function(assert) {
|
|
const playlist = playlistWithDuration(20);
|
|
const defaultGoal = Config.GOAL_BUFFER_LENGTH;
|
|
|
|
Config.GOAL_BUFFER_LENGTH = 1;
|
|
loader.playlist(playlist);
|
|
|
|
loader.load();
|
|
|
|
const segmentInfo = loader.checkBuffer_(
|
|
videojs.createTimeRanges([[0, 1]]),
|
|
playlist,
|
|
null,
|
|
loader.hasPlayed_(),
|
|
0,
|
|
null
|
|
);
|
|
|
|
assert.ok(!segmentInfo, 'no request generated');
|
|
Config.GOAL_BUFFER_LENGTH = defaultGoal;
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
'does not skip over segment if live playlist update occurs while processing',
|
|
function(assert) {
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
const playlist = playlistWithDuration(40);
|
|
|
|
playlist.endList = false;
|
|
|
|
loader.playlist(playlist);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.pendingSegment_.uri, '0.ts', 'retrieving first segment');
|
|
assert.equal(
|
|
loader.pendingSegment_.segment.uri,
|
|
'0.ts',
|
|
'correct segment reference'
|
|
);
|
|
assert.equal(loader.state, 'WAITING', 'waiting for response');
|
|
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
// playlist updated during append
|
|
const playlistUpdated = playlistWithDuration(40);
|
|
|
|
playlistUpdated.segments.shift();
|
|
playlistUpdated.mediaSequence++;
|
|
loader.playlist(playlistUpdated);
|
|
// finish append
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.pendingSegment_.uri, '1.ts', 'retrieving second segment');
|
|
assert.equal(
|
|
loader.pendingSegment_.segment.uri,
|
|
'1.ts',
|
|
'correct segment reference'
|
|
);
|
|
assert.equal(loader.state, 'WAITING', 'waiting for response');
|
|
});
|
|
}
|
|
);
|
|
|
|
QUnit.test('processing segment reachable even after playlist update removes it', function(assert) {
|
|
const handleAppendsDone_ = loader.handleAppendsDone_.bind(loader);
|
|
let expectedURI = '0.ts';
|
|
|
|
loader.handleAppendsDone_ = () => {
|
|
// we need to check for the right state, as normally handleResponse would throw an
|
|
// error under failing cases, but sinon swallows it as part of fake XML HTTP
|
|
// request's response
|
|
assert.equal(loader.state, 'APPENDING', 'moved to appending state');
|
|
assert.equal(loader.pendingSegment_.uri, expectedURI, 'correct pending segment');
|
|
assert.equal(
|
|
loader.pendingSegment_.segment.uri,
|
|
expectedURI,
|
|
'correct segment reference'
|
|
);
|
|
|
|
handleAppendsDone_();
|
|
};
|
|
|
|
return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => {
|
|
|
|
const playlist = playlistWithDuration(40);
|
|
|
|
playlist.endList = false;
|
|
|
|
loader.playlist(playlist);
|
|
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.state, 'WAITING', 'in waiting state');
|
|
assert.equal(loader.pendingSegment_.uri, '0.ts', 'first segment pending');
|
|
assert.equal(
|
|
loader.pendingSegment_.segment.uri,
|
|
'0.ts',
|
|
'correct segment reference'
|
|
);
|
|
|
|
// wrap up the first request to set mediaIndex and start normal live streaming
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}).then(() => {
|
|
this.clock.tick(1);
|
|
|
|
assert.equal(loader.state, 'WAITING', 'in waiting state');
|
|
assert.equal(loader.pendingSegment_.uri, '1.ts', 'second segment pending');
|
|
assert.equal(
|
|
loader.pendingSegment_.segment.uri,
|
|
'1.ts',
|
|
'correct segment reference'
|
|
);
|
|
|
|
// playlist updated during waiting
|
|
const playlistUpdated = playlistWithDuration(40);
|
|
|
|
playlistUpdated.segments.shift();
|
|
playlistUpdated.segments.shift();
|
|
playlistUpdated.mediaSequence += 2;
|
|
loader.playlist(playlistUpdated);
|
|
|
|
assert.equal(loader.pendingSegment_.uri, '1.ts', 'second segment still pending');
|
|
assert.equal(
|
|
loader.pendingSegment_.segment.uri,
|
|
'1.ts',
|
|
'correct segment reference'
|
|
);
|
|
|
|
expectedURI = '1.ts';
|
|
standardXHRResponse(this.requests.shift(), testData());
|
|
|
|
if (usesAsyncAppends) {
|
|
return new Promise((resolve, reject) => {
|
|
loader.one('appended', resolve);
|
|
loader.one('error', reject);
|
|
});
|
|
}
|
|
|
|
return Promise.resolve();
|
|
});
|
|
});
|
|
|
|
QUnit.test('new playlist always triggers syncinfoupdate', function(assert) {
|
|
let playlist = playlistWithDuration(100, { endList: false });
|
|
let syncInfoUpdates = 0;
|
|
|
|
loader.on('syncinfoupdate', () => syncInfoUpdates++);
|
|
|
|
loader.playlist(playlist);
|
|
|
|
loader.load();
|
|
|
|
assert.equal(syncInfoUpdates, 1, 'first playlist triggers an update');
|
|
loader.playlist(playlist);
|
|
assert.equal(syncInfoUpdates, 2, 'same playlist triggers an update');
|
|
playlist = playlistWithDuration(100, { endList: false });
|
|
loader.playlist(playlist);
|
|
assert.equal(syncInfoUpdates, 3, 'new playlist with same info triggers an update');
|
|
playlist.segments[0].start = 10;
|
|
playlist = playlistWithDuration(100, { endList: false, mediaSequence: 1 });
|
|
loader.playlist(playlist);
|
|
assert.equal(
|
|
syncInfoUpdates,
|
|
5,
|
|
'new playlist after expiring segment triggers two updates'
|
|
);
|
|
});
|
|
|
|
QUnit.module('Loading Calculation');
|
|
|
|
QUnit.test('requests the first segment with an empty buffer', function(assert) {
|
|
|
|
const segmentInfo = loader.checkBuffer_(
|
|
videojs.createTimeRanges(),
|
|
playlistWithDuration(20),
|
|
null,
|
|
loader.hasPlayed_(),
|
|
0,
|
|
null
|
|
);
|
|
|
|
assert.ok(segmentInfo, 'generated a request');
|
|
assert.equal(segmentInfo.uri, '0.ts', 'requested the first segment');
|
|
});
|
|
|
|
QUnit.test(
|
|
'no request if video not played and 1 segment is buffered',
|
|
function(assert) {
|
|
this.hasPlayed = false;
|
|
|
|
const segmentInfo = loader.checkBuffer_(
|
|
videojs.createTimeRanges([[0, 1]]),
|
|
playlistWithDuration(20),
|
|
0,
|
|
loader.hasPlayed_(),
|
|
0,
|
|
null
|
|
);
|
|
|
|
assert.ok(!segmentInfo, 'no request generated');
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
'does not download the next segment if the buffer is full',
|
|
function(assert) {
|
|
const buffered = videojs.createTimeRanges([
|
|
[0, 30 + Config.GOAL_BUFFER_LENGTH]
|
|
]);
|
|
const segmentInfo = loader.checkBuffer_(
|
|
buffered,
|
|
playlistWithDuration(30),
|
|
null,
|
|
true,
|
|
15,
|
|
{ segmentIndex: 0, time: 0 }
|
|
);
|
|
|
|
assert.ok(!segmentInfo, 'no segment request generated');
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
'downloads the next segment if the buffer is getting low',
|
|
function(assert) {
|
|
const playlist = playlistWithDuration(30);
|
|
|
|
loader.playlist(playlist);
|
|
|
|
const buffered = videojs.createTimeRanges([[0, 19.999]]);
|
|
const segmentInfo = loader.checkBuffer_(
|
|
buffered,
|
|
playlist,
|
|
1,
|
|
true,
|
|
15,
|
|
{ segmentIndex: 0, time: 0 }
|
|
);
|
|
|
|
assert.ok(segmentInfo, 'made a request');
|
|
assert.equal(segmentInfo.uri, '2.ts', 'requested the third segment');
|
|
}
|
|
);
|
|
|
|
QUnit.test('stops downloading segments at the end of the playlist', function(assert) {
|
|
const buffered = videojs.createTimeRanges([[0, 60]]);
|
|
const segmentInfo = loader.checkBuffer_(
|
|
buffered,
|
|
playlistWithDuration(60),
|
|
null,
|
|
true,
|
|
0,
|
|
null
|
|
);
|
|
|
|
assert.ok(!segmentInfo, 'no request was made');
|
|
});
|
|
|
|
QUnit.test(
|
|
'stops downloading segments if buffered past reported end of the playlist',
|
|
function(assert) {
|
|
|
|
const buffered = videojs.createTimeRanges([[0, 59.9]]);
|
|
const playlist = playlistWithDuration(60);
|
|
|
|
playlist.segments[playlist.segments.length - 1].end = 59.9;
|
|
const segmentInfo = loader.checkBuffer_(
|
|
buffered,
|
|
playlist,
|
|
playlist.segments.length - 1,
|
|
true,
|
|
50,
|
|
{ segmentIndex: 0, time: 0 }
|
|
);
|
|
|
|
assert.ok(!segmentInfo, 'no request was made');
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
'doesn\'t allow more than one monitor buffer timer to be set',
|
|
function(assert) {
|
|
const timeoutCount = this.clock.methods.length;
|
|
|
|
loader.monitorBuffer_();
|
|
|
|
assert.equal(
|
|
this.clock.methods.length,
|
|
timeoutCount,
|
|
'timeout count remains the same'
|
|
);
|
|
|
|
loader.monitorBuffer_();
|
|
|
|
assert.equal(
|
|
this.clock.methods.length,
|
|
timeoutCount,
|
|
'timeout count remains the same'
|
|
);
|
|
|
|
loader.monitorBuffer_();
|
|
loader.monitorBuffer_();
|
|
|
|
assert.equal(
|
|
this.clock.methods.length,
|
|
timeoutCount,
|
|
'timeout count remains the same'
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test('maintains initial sync info if playlist is changed before playback starts', function(assert) {
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 1,
|
|
endList: false
|
|
}));
|
|
|
|
assert.deepEqual(
|
|
loader.playlist_.syncInfo,
|
|
{
|
|
mediaSequence: 1,
|
|
time: 0
|
|
},
|
|
'updated sync info to start at media sequence 1 and time 0'
|
|
);
|
|
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 2,
|
|
endList: false
|
|
}));
|
|
|
|
assert.deepEqual(
|
|
loader.playlist_.syncInfo,
|
|
{
|
|
mediaSequence: 2,
|
|
time: 0
|
|
},
|
|
'updated sync info to start at media sequence 2 and time 0'
|
|
);
|
|
|
|
loader.load();
|
|
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 2,
|
|
endList: false
|
|
}));
|
|
|
|
assert.notOk(loader.playlist_.syncInfo, 'did not set sync info on new playlist');
|
|
});
|
|
|
|
QUnit.test('maintains initial sync info if playlist is changed while segment in-flight', function(assert) {
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 1,
|
|
endList: false
|
|
}));
|
|
|
|
assert.deepEqual(
|
|
loader.playlist_.syncInfo,
|
|
{
|
|
mediaSequence: 1,
|
|
time: 0
|
|
},
|
|
'updated sync info to start at media sequence 1 and time 0'
|
|
);
|
|
|
|
assert.equal(this.requests.length, 0, 'no in-flight requests');
|
|
loader.load();
|
|
this.clock.tick(1);
|
|
assert.equal(this.requests.length, 1, 'one in-flight requests');
|
|
|
|
loader.playlist(playlistWithDuration(50, {
|
|
mediaSequence: 2,
|
|
endList: false
|
|
}));
|
|
|
|
assert.notOk(loader.playlist_.syncInfo, 'did not set sync info on new playlist');
|
|
});
|
|
});
|
|
};
|