mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
9e2b15ead4
Simplifies subtitle management for sequence mode and segments mode Runs subtitle tests in segments mode as well (Tizen 3.0) Fixes: https://github.com/shaka-project/shaka-player/issues/7447
6055 lines
197 KiB
JavaScript
6055 lines
197 KiB
JavaScript
/*! @license
|
||
* Shaka Player
|
||
* Copyright 2016 Google LLC
|
||
* SPDX-License-Identifier: Apache-2.0
|
||
*/
|
||
|
||
describe('HlsParser', () => {
|
||
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
||
const TextStreamKind = shaka.util.ManifestParserUtils.TextStreamKind;
|
||
const Util = shaka.test.Util;
|
||
const originalAlwaysWarn = shaka.log.alwaysWarn;
|
||
|
||
const videoInitSegmentUri = '/base/test/test/assets/sintel-video-init.mp4';
|
||
const videoSegmentUri = '/base/test/test/assets/sintel-video-segment.mp4';
|
||
const videoTsSegmentUri = '/base/test/test/assets/video.ts';
|
||
|
||
const vttText = [
|
||
'WEBVTT\n',
|
||
'\n',
|
||
'00:03.837 --> 00:07.297\n',
|
||
'Hello, world!\n',
|
||
].join('');
|
||
|
||
/** @type {!shaka.test.FakeNetworkingEngine} */
|
||
let fakeNetEngine;
|
||
/** @type {!shaka.hls.HlsParser} */
|
||
let parser;
|
||
/** @type {!jasmine.Spy} */
|
||
let onEventSpy;
|
||
/** @type {!jasmine.Spy} */
|
||
let newDrmInfoSpy;
|
||
/** @type {!jasmine.Spy} */
|
||
let onMetadataSpy;
|
||
/** @type {shaka.extern.ManifestParser.PlayerInterface} */
|
||
let playerInterface;
|
||
/** @type {shaka.extern.ManifestConfiguration} */
|
||
let config;
|
||
/** @type {!Uint8Array} */
|
||
let initSegmentData;
|
||
/** @type {!Uint8Array} */
|
||
let segmentData;
|
||
/** @type {!Uint8Array} */
|
||
let tsSegmentData;
|
||
/** @type {!Uint8Array} */
|
||
let selfInitializingSegmentData;
|
||
/** @type {!Uint8Array} */
|
||
let aesKey;
|
||
/** @type {!boolean} */
|
||
let sequenceMode;
|
||
|
||
afterEach(() => {
|
||
shaka.log.alwaysWarn = originalAlwaysWarn;
|
||
parser.stop();
|
||
});
|
||
|
||
beforeEach(async () => {
|
||
const responses = await Promise.all([
|
||
shaka.test.Util.fetch(videoInitSegmentUri),
|
||
shaka.test.Util.fetch(videoSegmentUri),
|
||
shaka.test.Util.fetch(videoTsSegmentUri),
|
||
]);
|
||
initSegmentData = responses[0];
|
||
segmentData = responses[1];
|
||
|
||
selfInitializingSegmentData =
|
||
shaka.util.Uint8ArrayUtils.concat(initSegmentData, segmentData);
|
||
|
||
tsSegmentData = responses[2];
|
||
|
||
aesKey = new Uint8Array([
|
||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||
]);
|
||
|
||
fakeNetEngine = new shaka.test.FakeNetworkingEngine();
|
||
|
||
config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
sequenceMode = config.hls.sequenceMode;
|
||
onEventSpy = jasmine.createSpy('onEvent');
|
||
newDrmInfoSpy = jasmine.createSpy('newDrmInfo');
|
||
onMetadataSpy = jasmine.createSpy('onMetadata');
|
||
playerInterface = {
|
||
modifyManifestRequest: (request, manifestInfo) => {},
|
||
modifySegmentRequest: (request, segmentInfo) => {},
|
||
filter: () => Promise.resolve(),
|
||
makeTextStreamsForClosedCaptions: (manifest) => {},
|
||
networkingEngine: fakeNetEngine,
|
||
onError: fail,
|
||
onEvent: shaka.test.Util.spyFunc(onEventSpy),
|
||
onTimelineRegionAdded: fail,
|
||
isLowLatencyMode: () => false,
|
||
isAutoLowLatencyMode: () => false,
|
||
enableLowLatencyMode: () => {},
|
||
updateDuration: () => {},
|
||
newDrmInfo: shaka.test.Util.spyFunc(newDrmInfoSpy),
|
||
onManifestUpdated: () => {},
|
||
getBandwidthEstimate: () => 1e6,
|
||
onMetadata: shaka.test.Util.spyFunc(onMetadataSpy),
|
||
disableStream: (stream) => {},
|
||
addFont: (name, url) => {},
|
||
};
|
||
|
||
parser = new shaka.hls.HlsParser();
|
||
parser.configure(config);
|
||
});
|
||
|
||
/**
|
||
* @param {string} master
|
||
* @param {string} media
|
||
* @param {shaka.extern.Manifest} manifest
|
||
* @param {string=} media2
|
||
* @return {!Promise.<shaka.extern.Manifest>}
|
||
*/
|
||
async function testHlsParser(master, media, manifest, media2) {
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/audio2', media2 || media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/video2', media2 || media)
|
||
.setResponseText('test:/text', media)
|
||
.setResponseText('test:/text2', media2 || media)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/init2.mp4', initSegmentData)
|
||
.setResponseValue('test:/init.test', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData)
|
||
.setResponseValue('test:/main2.mp4', segmentData)
|
||
.setResponseValue('test:/main.test', segmentData)
|
||
.setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
return actual;
|
||
}
|
||
|
||
/** @param {!shaka.extern.Manifest} manifest */
|
||
async function loadAllStreamsFor(manifest) {
|
||
const promises = [];
|
||
for (const variant of manifest.variants) {
|
||
for (const stream of [variant.video, variant.audio]) {
|
||
if (stream) {
|
||
promises.push(stream.createSegmentIndex());
|
||
}
|
||
}
|
||
}
|
||
for (const text of manifest.textStreams) {
|
||
promises.push(text.createSegmentIndex());
|
||
}
|
||
for (const image of manifest.imageStreams) {
|
||
promises.push(image.createSegmentIndex());
|
||
}
|
||
await Promise.all(promises);
|
||
}
|
||
|
||
it('parses manifest attributes', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-START:TIME-OFFSET=2\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="16/JOC",SAMPLE-RATE="48000",URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",LANGUAGE="es",',
|
||
'URI="text2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.startTime = 2;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.bandwidth = 200;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.frameRate = 60;
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.channelsCount = 16;
|
||
stream.audioSamplingRate = 48000;
|
||
stream.spatialAudio = true;
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'es';
|
||
stream.originalLanguage = 'es';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/text2', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses manifest attributes with space', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO, GROUP-ID="aud1", LANGUAGE="eng", ',
|
||
'CHANNELS="16/JOC", URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub1", LANGUAGE="eng", ',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub2", LANGUAGE="es", ',
|
||
'URI="text2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200, CODECS="avc1,mp4a", ',
|
||
'RESOLUTION=960x540, FRAME-RATE=60, AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.bandwidth = 200;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.frameRate = 60;
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.channelsCount = 16;
|
||
stream.spatialAudio = true;
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'es';
|
||
stream.originalLanguage = 'es';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/text2', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('prioritize AVERAGE-BANDWIDTH to BANDWIDTH', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,',
|
||
'AVERAGE-BANDWIDTH=100\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 100;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.bandwidth = 100;
|
||
stream.frameRate = 60;
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.size(960, 540);
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('ignores duplicate CODECS', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d001e,avc1.42000d",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1.4d001e');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses video-only variant', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('guesses video-only variant by codecs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1"\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('guesses video-only variant when text codecs are present', async () => {
|
||
const master = [
|
||
// NOTE: This manifest is technically invalid. It has text codecs, but
|
||
// no text stream. We're tesing text stream parsing elswhere, so this
|
||
// only has the stream we're interested in (video) for simplicity.
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,stpp.ttml.im1t"\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio-only variant', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a"\n',
|
||
'audio',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 200;
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
stream.bandwidth = 200;
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio+video variant', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio+video variant with legacy codecs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a.40.34",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a.40.34');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('accepts containerless streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=41457,CODECS="mp4a.40.2"\n',
|
||
'audio\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.aac',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/aac', 'mp4a.40.2');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('accepts mp4a.40.34 codec as audio/mpeg', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=63701,CODECS="mp4a.40.34"\n',
|
||
'audio\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp3',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mpeg', 'mp4a.40.34');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('accepts fLaC codec as audio/mp4', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=1000000,CODECS="fLaC"\n',
|
||
'audio\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=1000000,CODECS="flac"\n',
|
||
'audio2\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'fLaC');
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'flac');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('accepts Opus codec as audio/mp4', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="Opus"\n',
|
||
'audio\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="opus"\n',
|
||
'audio2\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'Opus');
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'opus');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio+video variant with closed captions', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
|
||
'INSTREAM-ID="CC1"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,CLOSED-CAPTIONS="cap1",AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const closedCaptions = new Map([['CC1', 'en']]);
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.closedCaptions = closedCaptions;
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio+video variant with global closed captions', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
|
||
'INSTREAM-ID="CC1"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const closedCaptions = new Map([['CC1', 'en']]);
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.closedCaptions = closedCaptions;
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio+video variant with no closed captions', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
|
||
'INSTREAM-ID="CC1"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,CLOSED-CAPTIONS="NONE",AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('handles audio tags on audio streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a",AUDIO="aud1"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses multiplexed variant', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1,mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses multiplexed variant without codecs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1.42E01E,mp4a.40.2');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio+video variant without codecs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1.42E01E');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a.40.2');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses audio variant without URI', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",NAME="audio"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1.42E01E');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('video/mp2t', 'mp4a.40.2');
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.label = 'audio';
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
|
||
it('parses video variant without URI', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a",VIDEO="vid1"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid1",NAME="video"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses multiple variants', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
|
||
'video2\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'DEFAULT=YES,URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
|
||
'URI="audio2"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 200;
|
||
variant.primary = true;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 300;
|
||
variant.primary = false;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'fr';
|
||
stream.originalLanguage = 'fr';
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses multiple streams with the same group id', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="fr",',
|
||
'URI="audio2"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'en';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'fr';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'fr';
|
||
stream.originalLanguage = 'fr';
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses discontinuity tags', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1",',
|
||
'CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
||
'#EXT-X-DISCONTINUITY-SEQUENCE:0\n',
|
||
'#EXTINF:3,\n',
|
||
'clip0-video-0.ts\n',
|
||
'#EXTINF:1,\n',
|
||
'clip0-video-1.ts\n',
|
||
'#EXT-X-DISCONTINUITY\n',
|
||
'#EXTINF:2,\n',
|
||
'clip1-video-1.ts\n',
|
||
'#EXTINF:3,\n',
|
||
'clip1-video-2.ts\n',
|
||
'#EXT-X-DISCONTINUITY\n',
|
||
'#EXTINF:1,\n',
|
||
'media-clip2-video-0.ts\n',
|
||
'#EXTINF:1,\n',
|
||
'media-clip2-video-1.ts\n',
|
||
'#EXT-X-DISCONTINUITY\n',
|
||
'#EXTINF:4,\n',
|
||
'media-clip3-video-1.ts\n',
|
||
'#EXT-X-ENDLIST\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media);
|
||
|
||
const manifest = await parser.start('test:/master', playerInterface);
|
||
await manifest.variants[0].video.createSegmentIndex();
|
||
|
||
const segmentIndex = manifest.variants[0].video.segmentIndex;
|
||
const references = [];
|
||
|
||
for (let i = 0; i < 7; i++) {
|
||
references.push(segmentIndex.get(i));
|
||
}
|
||
|
||
expect(references[0].discontinuitySequence).toBe(0);
|
||
expect(references[1].discontinuitySequence).toBe(0);
|
||
expect(references[2].discontinuitySequence).toBe(1);
|
||
expect(references[3].discontinuitySequence).toBe(1);
|
||
expect(references[4].discontinuitySequence).toBe(2);
|
||
expect(references[5].discontinuitySequence).toBe(2);
|
||
expect(references[6].discontinuitySequence).toBe(3);
|
||
});
|
||
|
||
it('parses characteristics from audio tags', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'NAME="English",URI="audio"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'CHARACTERISTICS="public.accessibility.describes-video,',
|
||
'public.accessibility.describes-music-and-sound",',
|
||
'NAME="English (describes-video)",URI="audio2"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'en';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'en';
|
||
stream.roles = [
|
||
'public.accessibility.describes-video',
|
||
'public.accessibility.describes-music-and-sound',
|
||
];
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('adds subtitle role when characteristics are empty', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'NAME="English",URI="audio"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'CHARACTERISTICS="public.accessibility.describes-video,',
|
||
'public.accessibility.describes-music-and-sound",',
|
||
'NAME="English (describes-video)",URI="audio2"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
||
'NAME="English (subtitle)",DEFAULT=YES,AUTOSELECT=YES,',
|
||
'URI="text"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
||
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
||
'CHARACTERISTICS="public.accessibility.describes-music-and-sound",',
|
||
'URI="text2"\n',
|
||
].join('');
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.roles = [];
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.roles = [
|
||
'public.accessibility.describes-video',
|
||
'public.accessibility.describes-music-and-sound',
|
||
];
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.roles = [
|
||
'subtitle',
|
||
];
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.roles = [
|
||
'public.accessibility.describes-music-and-sound',
|
||
];
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses characteristics from text tags', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
||
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
||
'URI="text"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
||
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
||
'CHARACTERISTICS="public.accessibility.describes-spoken-dialog,',
|
||
'public.accessibility.describes-music-and-sound",',
|
||
'URI="text2"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('application/mp4', '');
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('application/mp4', '');
|
||
stream.roles = [
|
||
'public.accessibility.describes-spoken-dialog',
|
||
'public.accessibility.describes-music-and-sound',
|
||
];
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
// https://github.com/shaka-project/shaka-player/issues/4759
|
||
it('makes roles available without loading tracks', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'NAME="English",URI="audio"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'CHARACTERISTICS="public.accessibility.describes-video,',
|
||
'public.accessibility.describes-music-and-sound",',
|
||
'NAME="English (describes-video)",URI="audio2"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
||
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
||
'URI="text"\n',
|
||
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
|
||
'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
|
||
'CHARACTERISTICS="public.accessibility.describes-spoken-dialog,',
|
||
'public.accessibility.describes-music-and-sound",',
|
||
'URI="text2"\n',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.roles = [
|
||
'public.accessibility.describes-video',
|
||
'public.accessibility.describes-music-and-sound',
|
||
];
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.roles = [
|
||
'public.accessibility.describes-spoken-dialog',
|
||
'public.accessibility.describes-music-and-sound',
|
||
];
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
|
||
// NOTE: Not using testHlsParser here because that unconditionally loads all
|
||
// streams. We need to test the behavior specifically when streams are
|
||
// _not_ loaded.
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('gets mime type from header request', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.test',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
// The extra parameters should be stripped by the parser.
|
||
fakeNetEngine.setHeaders(
|
||
'test:/main.test', {
|
||
'content-type': 'video/mp4; foo=bar',
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('parses manifest with HDR metadata', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'FORCED=YES,URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",VIDEO-RANGE=PQ,',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.hdr = 'PQ';
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.forced = true;
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses manifest with video layout metadata', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'FORCED=YES,URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'REQ-VIDEO-LAYOUT=CH-STEREO,RESOLUTION=960x540,FRAME-RATE=60,',
|
||
'AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.videoLayout = 'CH-STEREO';
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.forced = true;
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses manifest with SUBTITLES', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",LANGUAGE="es",',
|
||
'URI="text2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=90,AUDIO="aud1",SUBTITLES="sub2"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'es';
|
||
stream.originalLanguage = 'es';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/text2', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('gets mime type of SUBTITLES from header request', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.subs',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('application/mp4', '');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/main.subs', vttText)
|
||
.setHeaders('test:/main.subs', {
|
||
'content-type': 'application/mp4; foo=bar',
|
||
})
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses manifest with FORCED SUBTITLES', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'FORCED=YES,URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.forced = true;
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses manifest with text streams without SUBTITLES', async () => {
|
||
// The variant tag doesn't contain a 'SUBTITLES' attribute.
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",LANGUAGE="es",',
|
||
'URI="text2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
variant.addPartialStream(ContentType.AUDIO);
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('text/vtt', '');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/text2', textMedia)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('calculates duration from stream lengths', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
// Duration should be the minimum of the streams, but ignore the text
|
||
// stream.
|
||
const timeline = actual.presentationTimeline;
|
||
expect(timeline.getDuration()).toBe(10);
|
||
expect(timeline.getSeekRangeStart()).toBe(0);
|
||
expect(timeline.getSeekRangeEnd()).toBe(10);
|
||
|
||
expect(actual.textStreams.length).toBe(1);
|
||
expect(actual.variants.length).toBe(1);
|
||
expect(actual.variants[0].audio).toBeTruthy();
|
||
expect(actual.variants[0].video).toBeTruthy();
|
||
});
|
||
|
||
it('parse image streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
|
||
'URI="image"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const image = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-TILES:RESOLUTION=640x360,LAYOUT=5x2,DURATION=6.006\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/image', image)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
|
||
expect(actual.imageStreams.length).toBe(1);
|
||
expect(actual.textStreams.length).toBe(1);
|
||
expect(actual.variants.length).toBe(1);
|
||
|
||
const thumbnails = actual.imageStreams[0];
|
||
|
||
await thumbnails.createSegmentIndex();
|
||
goog.asserts.assert(thumbnails.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
const firstThumbnailReference = thumbnails.segmentIndex.get(0);
|
||
const secondThumbnailReference = thumbnails.segmentIndex.get(1);
|
||
const thirdThumbnailReference = thumbnails.segmentIndex.get(2);
|
||
|
||
expect(firstThumbnailReference).not.toBe(null);
|
||
expect(secondThumbnailReference).not.toBe(null);
|
||
expect(thirdThumbnailReference).not.toBe(null);
|
||
if (firstThumbnailReference) {
|
||
expect(firstThumbnailReference.getTilesLayout()).toBe('1x1');
|
||
}
|
||
if (secondThumbnailReference) {
|
||
expect(secondThumbnailReference.getTilesLayout()).toBe('5x2');
|
||
}
|
||
if (thirdThumbnailReference) {
|
||
expect(thirdThumbnailReference.getTilesLayout()).toBe('1x1');
|
||
}
|
||
});
|
||
|
||
it('supports EXT-X-I-FRAME-STREAM-INF with mjpg codec', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="mjpg",',
|
||
'URI="iframe"\n',
|
||
'#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="avc",',
|
||
'URI="iframeAvc"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const iframe = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/iframe', iframe)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
|
||
expect(actual.imageStreams.length).toBe(1);
|
||
expect(actual.textStreams.length).toBe(1);
|
||
expect(actual.variants.length).toBe(1);
|
||
|
||
const thumbnails = actual.imageStreams[0];
|
||
|
||
expect(thumbnails.mimeType).toBe('application/mp4');
|
||
expect(thumbnails.codecs).toBe('mjpg');
|
||
|
||
await thumbnails.createSegmentIndex();
|
||
goog.asserts.assert(thumbnails.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
const firstThumbnailReference = thumbnails.segmentIndex.get(0);
|
||
const secondThumbnailReference = thumbnails.segmentIndex.get(1);
|
||
const thirdThumbnailReference = thumbnails.segmentIndex.get(2);
|
||
|
||
expect(firstThumbnailReference).not.toBe(null);
|
||
expect(secondThumbnailReference).not.toBe(null);
|
||
expect(thirdThumbnailReference).not.toBe(null);
|
||
});
|
||
|
||
it('supports EXT-X-I-FRAME-STREAM-INF for trick play', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=960x540,CODECS="avc1",',
|
||
'URI="iframe"\n',
|
||
'#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="avc1",',
|
||
'URI="iframeAvc"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const iframe = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/iframe', iframe)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
|
||
expect(actual.textStreams.length).toBe(1);
|
||
expect(actual.variants.length).toBe(1);
|
||
|
||
const trickModeVideo = actual.variants[0].video.trickModeVideo;
|
||
expect(trickModeVideo).toBeDefined();
|
||
expect(trickModeVideo.width).toBe(960);
|
||
expect(trickModeVideo.height).toBe(540);
|
||
});
|
||
|
||
it('Disable I-Frame does not create I-Frame streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=960x540,CODECS="avc1",',
|
||
'URI="iframe"\n',
|
||
'#EXT-X-I-FRAME-STREAM-INF:RESOLUTION=240×135,CODECS="avc1",',
|
||
'URI="iframeAvc"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const iframe = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/iframe', iframe)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.disableIFrames = true;
|
||
parser.configure(config);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
|
||
expect(actual.textStreams.length).toBe(1);
|
||
expect(actual.variants.length).toBe(1);
|
||
|
||
const trickModeVideo = actual.variants[0].video.trickModeVideo;
|
||
expect(trickModeVideo).toBeNull();
|
||
});
|
||
|
||
it('parse EXT-X-GAP', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-GAP\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-GAP\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
|
||
expect(actual.gapCount).toBe(2);
|
||
expect(actual.variants.length).toBe(1);
|
||
|
||
const variant = actual.variants[0];
|
||
expect(variant.video).toBeTruthy();
|
||
expect(variant.audio).toBeTruthy();
|
||
|
||
const available = shaka.media.SegmentReference.Status.AVAILABLE;
|
||
const missing = shaka.media.SegmentReference.Status.MISSING;
|
||
|
||
await variant.video.createSegmentIndex();
|
||
goog.asserts.assert(variant.video.segmentIndex != null,
|
||
'Null segmentIndex!');
|
||
|
||
const firstVideoReference = variant.video.segmentIndex.get(0);
|
||
const secondVideoReference = variant.video.segmentIndex.get(1);
|
||
const thirdVideoReference = variant.video.segmentIndex.get(2);
|
||
|
||
expect(firstVideoReference).not.toBe(null);
|
||
expect(secondVideoReference).not.toBe(null);
|
||
expect(thirdVideoReference).not.toBe(null);
|
||
|
||
if (firstVideoReference) {
|
||
expect(firstVideoReference.getStatus()).toBe(available);
|
||
}
|
||
if (secondVideoReference) {
|
||
expect(secondVideoReference.getStatus()).toBe(missing);
|
||
}
|
||
if (thirdVideoReference) {
|
||
expect(thirdVideoReference.getStatus()).toBe(available);
|
||
}
|
||
|
||
await variant.audio.createSegmentIndex();
|
||
goog.asserts.assert(variant.audio.segmentIndex != null,
|
||
'Null segmentIndex!');
|
||
|
||
const firstAudioReference = variant.audio.segmentIndex.get(0);
|
||
const secondAudioReference = variant.audio.segmentIndex.get(1);
|
||
const thirdAudioReference = variant.audio.segmentIndex.get(2);
|
||
|
||
expect(firstAudioReference).not.toBe(null);
|
||
expect(secondAudioReference).not.toBe(null);
|
||
expect(thirdAudioReference).not.toBe(null);
|
||
|
||
if (firstAudioReference) {
|
||
expect(firstAudioReference.getStatus()).toBe(available);
|
||
}
|
||
if (secondAudioReference) {
|
||
expect(secondAudioReference.getStatus()).toBe(missing);
|
||
}
|
||
if (thirdAudioReference) {
|
||
expect(thirdAudioReference.getStatus()).toBe(available);
|
||
}
|
||
});
|
||
|
||
it('ignore segments with #EXTINF:0', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:0,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:0,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
|
||
expect(actual.variants.length).toBe(1);
|
||
|
||
const variant = actual.variants[0];
|
||
expect(variant.video).toBeTruthy();
|
||
expect(variant.audio).toBeTruthy();
|
||
|
||
await variant.video.createSegmentIndex();
|
||
goog.asserts.assert(variant.video.segmentIndex != null,
|
||
'Null segmentIndex!');
|
||
|
||
const firstVideoReference = variant.video.segmentIndex.get(0);
|
||
const secondVideoReference = variant.video.segmentIndex.get(1);
|
||
const thirdVideoReference = variant.video.segmentIndex.get(2);
|
||
|
||
expect(firstVideoReference).not.toBe(null);
|
||
expect(secondVideoReference).not.toBe(null);
|
||
expect(thirdVideoReference).toBe(null);
|
||
|
||
|
||
await variant.audio.createSegmentIndex();
|
||
goog.asserts.assert(variant.audio.segmentIndex != null,
|
||
'Null segmentIndex!');
|
||
|
||
const firstAudioReference = variant.audio.segmentIndex.get(0);
|
||
const secondAudioReference = variant.audio.segmentIndex.get(1);
|
||
const thirdAudioReference = variant.audio.segmentIndex.get(2);
|
||
|
||
expect(firstAudioReference).not.toBe(null);
|
||
expect(secondAudioReference).not.toBe(null);
|
||
expect(thirdAudioReference).toBe(null);
|
||
});
|
||
|
||
it('Disable audio does not create audio streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
|
||
'URI="image"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const image = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/image', image)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.disableAudio = true;
|
||
parser.configure(config);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const variant = actual.variants[0];
|
||
expect(variant.audio).toBe(null);
|
||
expect(variant.video).toBeTruthy();
|
||
});
|
||
|
||
it('Disable video does not create video streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
|
||
'URI="image"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const image = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/image', image)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.disableVideo = true;
|
||
parser.configure(config);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const variant = actual.variants[0];
|
||
expect(variant.audio).toBeTruthy();
|
||
expect(variant.video).toBe(null);
|
||
});
|
||
|
||
it('Disable text does not create text streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
|
||
'URI="image"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const image = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/image', image)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.disableText = true;
|
||
parser.configure(config);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const stream = actual.textStreams[0];
|
||
expect(stream).toBeUndefined();
|
||
});
|
||
|
||
it('Disable thumbnails does not create image streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'CHANNELS="2",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
'#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
|
||
'URI="image"\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
].join('');
|
||
|
||
const text = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.vtt',
|
||
].join('');
|
||
|
||
const image = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
'#EXTINF:5,\n',
|
||
'image.jpg\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/text', text)
|
||
.setResponseText('test:/image', image)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.disableThumbnails = true;
|
||
parser.configure(config);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const stream = actual.imageStreams[0];
|
||
expect(stream).toBeUndefined();
|
||
});
|
||
|
||
it('parses manifest with MP4+TTML streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,stpp.ttml.im1t",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
stream.mime('application/mp4', 'stpp.ttml.im1t');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses manifest with MP4+WEBVTT streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'CODECS="wvtt",URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.language = 'en';
|
||
stream.mime('application/mp4', 'wvtt');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('detects VTT streams by codec', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,vtt",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const textMedia = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.foo',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.mime('text/vtt', 'vtt');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', textMedia)
|
||
.setResponseText('test:/main.foo', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('allows init segments in text streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
||
'URI="text"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,wvtt",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.addPartialTextStream((stream) => {
|
||
stream.kind = TextStreamKind.SUBTITLE;
|
||
stream.mime('application/mp4', 'wvtt');
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/text', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
describe('When config.hls.disableCodecGuessing is set to true', () => {
|
||
beforeEach(() => {
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.hls.disableCodecGuessing = true;
|
||
parser.configure(config);
|
||
});
|
||
|
||
it('gets codec info from media if omitted in playlist', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=2000000\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
||
'#EXTINF:5,\n',
|
||
'video-0.ts\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/video-0.ts', tsSegmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
const variant = actual.variants[0];
|
||
|
||
expect(variant.audio).toBe(null);
|
||
expect(variant.video).toBeDefined();
|
||
expect(variant.video.codecs).toBe('avc1.42C01E');
|
||
});
|
||
|
||
it('gets codecs from playlist if CODECS attribute present', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="foo"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
||
'#EXTINF:5,\n',
|
||
'video-0.ts\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/video-0.ts', tsSegmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
const variant = actual.variants[0];
|
||
|
||
expect(variant.audio).toBe(null);
|
||
expect(variant.video).toBeDefined();
|
||
expect(variant.video.codecs).toBe('foo');
|
||
});
|
||
|
||
it('falls back to default codecs if it could not find codec', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=2000000\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-VERSION:3\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
||
'#EXTINF:5,\n',
|
||
'video-0.ts\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/video-0.ts', new Uint8Array([]));
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
const variant = actual.variants[0];
|
||
|
||
expect(variant.video).toBeDefined();
|
||
|
||
const codecs = variant.video.codecs.split(',').map((c) => c.trim());
|
||
|
||
expect(codecs).toEqual(['avc1.42E01E', 'mp4a.40.2']);
|
||
});
|
||
});
|
||
|
||
describe('produces syncTime', () => {
|
||
// Corresponds to "2000-01-01T00:00:00.00Z".
|
||
// All the PROGRAM-DATE-TIME values in the tests below are at or after this.
|
||
const syncTimeBase = 946684800;
|
||
|
||
/**
|
||
* @param {number} startTime
|
||
* @param {number} endTime
|
||
* @param {number} syncTime
|
||
* @return {!shaka.media.SegmentReference}
|
||
*/
|
||
function makeReference(startTime, endTime, syncTime) {
|
||
const initUris = () => ['test:/init.mp4'];
|
||
const mediaQuality = {
|
||
bandwidth: 200,
|
||
audioSamplingRate: null,
|
||
codecs: 'avc1.4d401f',
|
||
contentType: 'video',
|
||
frameRate: 60,
|
||
height: 540,
|
||
mimeType: 'video/mp4',
|
||
channelsCount: null,
|
||
pixelAspectRatio: null,
|
||
width: 960,
|
||
label: null,
|
||
roles: [],
|
||
language: null,
|
||
};
|
||
const init = new shaka.media.InitSegmentReference(initUris, 0, 615);
|
||
init.mediaQuality = mediaQuality;
|
||
const uris = () => ['test:/main.mp4'];
|
||
return new shaka.media.SegmentReference(
|
||
startTime, endTime, uris, 0, null, init, 0, 0, Infinity,
|
||
[], undefined, undefined, syncTime);
|
||
}
|
||
|
||
/**
|
||
* @param {string} media
|
||
* @param {!Array.<number>} startTimes
|
||
* @param {number} syncTimeOffset
|
||
* @param {(function(!shaka.media.SegmentReference))=} modifyFn
|
||
* @param {boolean=} isLowLatency
|
||
*/
|
||
async function test(media, startTimes, syncTimeOffset, modifyFn,
|
||
isLowLatency = false) {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,vtt",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const segments = [];
|
||
for (let i = 0; i < startTimes.length - 1; i++) {
|
||
const startTime = startTimes[i];
|
||
const endTime = startTimes[i + 1];
|
||
const reference = makeReference(
|
||
startTime, endTime, syncTimeOffset + startTime);
|
||
if (modifyFn) {
|
||
modifyFn(reference);
|
||
}
|
||
segments.push(reference);
|
||
}
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.segmentIndex = new shaka.media.SegmentIndex(segments);
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.isLowLatency = !!isLowLatency;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
}
|
||
|
||
it('from EXT-X-PROGRAM-DATE-TIME', async () => {
|
||
await test([
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:05.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:15.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:20.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5);
|
||
});
|
||
|
||
it('when some EXT-X-PROGRAM-DATE-TIME values are missing', async () => {
|
||
await test([
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:2,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
||
'#EXTINF:2,\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:4,\n',
|
||
'main.mp4',
|
||
].join(''), [0, 2, 7, 12, 17, 19, 23], syncTimeBase + 8);
|
||
});
|
||
|
||
it('except when ignoreManifestProgramDateTime is set', async () => {
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.hls.ignoreManifestProgramDateTime = true;
|
||
parser.configure(config);
|
||
await test([
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:05.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:15.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:20.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
|
||
reference.syncTime = null;
|
||
});
|
||
});
|
||
|
||
it('when there are partial segments', async () => {
|
||
playerInterface.isLowLatencyMode = () => true;
|
||
await test([
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:05.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:10.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:15.00Z\n',
|
||
'#EXT-X-PART:DURATION=2.5,URI="main.mp4",INDEPENDENT=YES\n',
|
||
'#EXT-X-PART:DURATION=2.5,URI="main.mp4",INDEPENDENT=YES\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:20.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
|
||
if (reference.startTime == 10) {
|
||
const partialRef = makeReference(10, 12.5, syncTimeBase + 15);
|
||
partialRef.partial = true;
|
||
const partialRef2 = makeReference(12.5, 15, syncTimeBase + 17.5);
|
||
partialRef2.partial = true;
|
||
partialRef2.lastPartial = true;
|
||
|
||
reference.partialReferences = [partialRef, partialRef2];
|
||
reference.allPartialSegments = true;
|
||
}
|
||
}, /* isLowLatency= */ true);
|
||
});
|
||
});
|
||
|
||
it('drops failed text streams when configured to', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,vtt",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
config.hls.ignoreTextStreamFailures = true;
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('drops failed image streams when configured to', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
'#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
config.hls.ignoreImageStreamFailures = true;
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('parses video described by a media tag', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.frameRate = 60;
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO);
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('constructs relative URIs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio/audio.m3u8\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video/video.m3u8"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'segment.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/host/master.m3u8', master)
|
||
.setResponseText('test:/host/audio/audio.m3u8', media)
|
||
.setResponseText('test:/host/video/video.m3u8', media)
|
||
.setResponseValue('test:/host/audio/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/host/audio/segment.mp4', segmentData)
|
||
.setResponseValue('test:/host/video/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/host/video/segment.mp4', segmentData);
|
||
|
||
const actual =
|
||
await parser.start('test:/host/master.m3u8', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const video = actual.variants[0].video;
|
||
const audio = actual.variants[0].audio;
|
||
|
||
await video.createSegmentIndex();
|
||
await audio.createSegmentIndex();
|
||
goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
|
||
goog.asserts.assert(audio.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
const videoReference = Array.from(video.segmentIndex)[0];
|
||
const audioReference = Array.from(audio.segmentIndex)[0];
|
||
|
||
expect(videoReference).not.toBe(null);
|
||
expect(audioReference).not.toBe(null);
|
||
if (videoReference) {
|
||
expect(videoReference.getUris())
|
||
.toEqual(['test:/host/video/segment.mp4']);
|
||
}
|
||
if (audioReference) {
|
||
expect(audioReference.getUris())
|
||
.toEqual(['test:/host/audio/segment.mp4']);
|
||
}
|
||
});
|
||
|
||
it('allows streams with no init segment', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'selfInit.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
it('allows multiple init segments', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-BYTERANGE:616@0\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-MAP:URI="init2.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main2.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/init2.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData)
|
||
.setResponseValue('test:/main2.mp4', segmentData);
|
||
|
||
const actualManifest = await parser.start('test:/master', playerInterface);
|
||
const actualVideo = actualManifest.variants[0].video;
|
||
await actualVideo.createSegmentIndex();
|
||
goog.asserts.assert(actualVideo.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
// Verify that the stream contains two segment references, each of the
|
||
// SegmentReference object contains the InitSegmentReference with expected
|
||
// uri.
|
||
const initSegments = Array.from(actualVideo.segmentIndex).map(
|
||
(seg) => seg.initSegmentReference);
|
||
expect(initSegments.length).toBe(2);
|
||
const firstInitSegment = initSegments[0];
|
||
expect(firstInitSegment.getUris()[0]).toBe('test:/init.mp4');
|
||
expect(firstInitSegment.startByte).toBe(0);
|
||
expect(firstInitSegment.endByte).toBe(615);
|
||
const secondInitSegment = initSegments[1];
|
||
expect(secondInitSegment.getUris()[0]).toBe('test:/init2.mp4');
|
||
expect(secondInitSegment.startByte).toBe(0);
|
||
expect(secondInitSegment.endByte).toBe(615);
|
||
});
|
||
|
||
it('parses variants encrypted with AES-128', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=90,AUDIO="aud2"\n',
|
||
'video2\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud3"\n',
|
||
'video3\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
|
||
'URI="audio2"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",LANGUAGE="de",',
|
||
'URI="audio3"\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const mediaWithMp4AesEncryption = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=AES-128,',
|
||
'URI="800k.key"\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'#EXT-X-KEY:METHOD=NONE\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const mediaWithTSAesEncryption = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=AES-128,',
|
||
'URI="800k.key"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.ts\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-KEY:METHOD=NONE\n',
|
||
'main.ts',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 200;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'eng';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 300;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(960, 540);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'fr';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 300;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(960, 540);
|
||
stream.mime('video/mp2t', 'avc1.4d401f');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'de';
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/audio2', media)
|
||
.setResponseText('test:/audio3', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseText('test:/video2', mediaWithMp4AesEncryption)
|
||
.setResponseText('test:/video3', mediaWithTSAesEncryption)
|
||
.setResponseText('test:/main.vtt', vttText)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData)
|
||
.setResponseValue('test:/main.test', segmentData)
|
||
.setResponseValue('test:/800k.key', aesKey)
|
||
.setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual).toEqual(manifest);
|
||
|
||
const mp4AesEncryptionVideo = actual.variants[1].video;
|
||
await mp4AesEncryptionVideo.createSegmentIndex();
|
||
goog.asserts.assert(mp4AesEncryptionVideo.segmentIndex != null,
|
||
'Null segmentIndex!');
|
||
|
||
const firstMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(0);
|
||
expect(firstMp4Segment.aesKey).toBeDefined();
|
||
expect(firstMp4Segment.initSegmentReference.aesKey).toBeDefined();
|
||
const secondMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(1);
|
||
expect(secondMp4Segment.aesKey).toBeNull();
|
||
expect(secondMp4Segment.initSegmentReference.aesKey).toBeDefined();
|
||
|
||
const tsAesEncryptionVideo = actual.variants[2].video;
|
||
await tsAesEncryptionVideo.createSegmentIndex();
|
||
goog.asserts.assert(tsAesEncryptionVideo.segmentIndex != null,
|
||
'Null segmentIndex!');
|
||
|
||
const firstTsSegment = tsAesEncryptionVideo.segmentIndex.get(0);
|
||
expect(firstTsSegment.aesKey).toBeDefined();
|
||
const secondTsSegment = tsAesEncryptionVideo.segmentIndex.get(1);
|
||
expect(secondTsSegment.aesKey).toBeNull();
|
||
});
|
||
|
||
it('fails on AES-128 if WebCrypto APIs are not available', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
||
'URI="audio"\n',
|
||
].join('');
|
||
|
||
const mediaWithMp4AesEncryption = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=AES-128,',
|
||
'URI="800k.key"\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', mediaWithMp4AesEncryption)
|
||
.setResponseText('test:/video', mediaWithMp4AesEncryption)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData)
|
||
.setResponseValue('test:/800k.key', aesKey);
|
||
|
||
let originalWebCrypto = null;
|
||
try {
|
||
originalWebCrypto = window.crypto;
|
||
Object.defineProperty(window, 'crypto', {
|
||
configurable: true,
|
||
value: null,
|
||
});
|
||
|
||
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
shaka.util.Error.Code.NO_WEB_CRYPTO_API));
|
||
await expectAsync(parser.start('test:/master', playerInterface))
|
||
.toBeRejectedWith(expectedError);
|
||
} finally {
|
||
Object.defineProperty(window, 'crypto', {
|
||
configurable: true,
|
||
value: originalWebCrypto,
|
||
});
|
||
}
|
||
});
|
||
|
||
it('constructs DrmInfo for Widevine', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const initDataBase64 =
|
||
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
|
||
|
||
const keyId = 'abc123';
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYID=0X' + keyId + ',',
|
||
'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
|
||
'URI="data:text/plain;base64,',
|
||
initDataBase64, '",\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.encrypted = true;
|
||
stream.addDrmInfo('com.widevine.alpha', (drmInfo) => {
|
||
drmInfo.addCencInitData(initDataBase64);
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
expect(newDrmInfoSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
it('constructs DrmInfo for PlayReady', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const initDataBase64 =
|
||
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="com.microsoft.playready",',
|
||
'URI="data:text/plain;base64,UGxheXJlYWR5",\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.encrypted = true;
|
||
stream.addDrmInfo('com.microsoft.playready', (drmInfo) => {
|
||
drmInfo.addCencInitData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
expect(newDrmInfoSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
it('constructs DrmInfo for FairPlay', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="com.apple.streamingkeydelivery",',
|
||
'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.encrypted = true;
|
||
stream.addDrmInfo('com.apple.fps', (drmInfo) => {
|
||
drmInfo.addInitData('sinf', new Uint8Array(0));
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
drmInfo.addKeySystemUris(new Set(
|
||
['skd://f93d4e700d7ddde90529a27735d9e7cb']));
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
expect(newDrmInfoSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
it('constructs DrmInfo for ClearKey with explicit KEYFORMAT', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="identity",',
|
||
'URI="key.bin",\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const initDataBase64 = 'eyJraWRzIjpbIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiXX0=';
|
||
const keyId = '00000000000000000000000000000000';
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.encrypted = true;
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = 'data:application/json;base64,eyJrZXl' +
|
||
'zIjpbeyJrdHkiOiJvY3QiLCJraWQiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFB' +
|
||
'IiwiayI6IlVHbzJhRVpuZERWcFJscDBaa0pNVGpadmNUaEZaejA5In1dfQ==';
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/key.bin', 'Pj6hFgt5iFZtfBLN6oq8Eg==');
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
expect(newDrmInfoSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
it('constructs DrmInfo for ClearKey without explicit KEYFORMAT', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'URI="key.bin",\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const initDataBase64 = 'eyJraWRzIjpbIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiXX0=';
|
||
const keyId = '00000000000000000000000000000000';
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.encrypted = true;
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = 'data:application/json;base64,eyJrZXl' +
|
||
'zIjpbeyJrdHkiOiJvY3QiLCJraWQiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFB' +
|
||
'IiwiayI6IlVHbzJhRVpuZERWcFJscDBaa0pNVGpadmNUaEZaejA5In1dfQ==';
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/key.bin', 'Pj6hFgt5iFZtfBLN6oq8Eg==');
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
expect(newDrmInfoSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
it('constructs DrmInfo for ClearKey with raw key', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="identity",',
|
||
'URI="data:text/plain;base64,Pj6hFgt5iFZtfBLN6oq8Eg==",\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const initDataBase64 = 'eyJraWRzIjpbIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiXX0=';
|
||
const keyId = '00000000000000000000000000000000';
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.encrypted = true;
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = 'data:application/json;base64,eyJrZXl' +
|
||
'zIjpbeyJrdHkiOiJvY3QiLCJraWQiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFB' +
|
||
'IiwiayI6IlBqNmhGZ3Q1aUZadGZCTE42b3E4RWcifV19';
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
expect(newDrmInfoSpy).toHaveBeenCalled();
|
||
});
|
||
|
||
describe('constructs DrmInfo with EXT-X-SESSION-KEY', () => {
|
||
it('for Widevine', async () => {
|
||
const initDataBase64 =
|
||
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
|
||
|
||
const keyId = 'abc123';
|
||
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=30\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video2\n',
|
||
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYID=0X' + keyId + ',',
|
||
'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
|
||
'URI="data:text/plain;base64,',
|
||
initDataBase64, '",\n',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('com.widevine.alpha', (drmInfo) => {
|
||
drmInfo.addCencInitData(initDataBase64);
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('com.widevine.alpha', (drmInfo) => {
|
||
drmInfo.addCencInitData(initDataBase64);
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('for PlayReady', async () => {
|
||
const initDataBase64 =
|
||
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';
|
||
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=30\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video2\n',
|
||
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="com.microsoft.playready",',
|
||
'URI="data:text/plain;base64,UGxheXJlYWR5",\n',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('com.microsoft.playready', (drmInfo) => {
|
||
drmInfo.addCencInitData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('com.microsoft.playready', (drmInfo) => {
|
||
drmInfo.addCencInitData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('for FairPlay', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=30,CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,CLOSED-CAPTIONS=NONE\n',
|
||
'video2\n',
|
||
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="com.apple.streamingkeydelivery",',
|
||
'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('com.apple.fps', (drmInfo) => {
|
||
drmInfo.addInitData('sinf', new Uint8Array(0));
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
drmInfo.addKeySystemUris(new Set(
|
||
['skd://f93d4e700d7ddde90529a27735d9e7cb']));
|
||
});
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('com.apple.fps', (drmInfo) => {
|
||
drmInfo.addInitData('sinf', new Uint8Array(0));
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
drmInfo.addKeySystemUris(new Set(
|
||
['skd://f93d4e700d7ddde90529a27735d9e7cb']));
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('for ClearKey with explicit KEYFORMAT', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=30\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video2\n',
|
||
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="identity",',
|
||
'URI="key.bin",\n',
|
||
].join('');
|
||
|
||
const licenseServerUri ='data:application/json;base64,eyJrZXlzIjpbeyJr' +
|
||
'dHkiOiJvY3QiLCJraWQiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBIiwiayI6IlVHb' +
|
||
'zJhRVpuZERWcFJscDBaa0pNVGpadmNUaEZaejA5In1dfQ==';
|
||
const initDataBase64 =
|
||
'eyJraWRzIjpbIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiXX0=';
|
||
const keyId = '00000000000000000000000000000000';
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = licenseServerUri;
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = licenseServerUri;
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
fakeNetEngine.setResponseText('test:/key.bin',
|
||
'Pj6hFgt5iFZtfBLN6oq8Eg==');
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('for ClearKey without explicit KEYFORMAT', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=30\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video2\n',
|
||
'#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'URI="key.bin",\n',
|
||
].join('');
|
||
|
||
const licenseServerUri ='data:application/json;base64,eyJrZXlzIjpbeyJr' +
|
||
'dHkiOiJvY3QiLCJraWQiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBIiwiayI6IlVHb' +
|
||
'zJhRVpuZERWcFJscDBaa0pNVGpadmNUaEZaejA5In1dfQ==';
|
||
const initDataBase64 =
|
||
'eyJraWRzIjpbIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiXX0=';
|
||
const keyId = '00000000000000000000000000000000';
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = licenseServerUri;
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.addDrmInfo('org.w3.clearkey', (drmInfo) => {
|
||
drmInfo.licenseServerUri = licenseServerUri;
|
||
drmInfo.keyIds.add(keyId);
|
||
drmInfo.addKeyIdsData(initDataBase64);
|
||
drmInfo.encryptionScheme = 'cenc';
|
||
});
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master);
|
||
fakeNetEngine.setResponseText('test:/key.bin',
|
||
'Pj6hFgt5iFZtfBLN6oq8Eg==');
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
});
|
||
|
||
it('Preload AES key with EXT-X-SESSION-KEY', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=30\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video2\n',
|
||
'#EXT-X-SESSION-KEY:METHOD=AES-128,',
|
||
'URI="800k.key"\n',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO);
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setResponseText('test:/master', master)
|
||
.setResponseValue('test:/800k.key', aesKey);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
it('falls back to mp4 if HEAD request fails', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.test',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine.setHeaders(
|
||
'test:/main.test', {
|
||
'content-type': '',
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
describe('Errors out', () => {
|
||
const Code = shaka.util.Error.Code;
|
||
|
||
/**
|
||
* @param {string} master
|
||
* @param {string} media
|
||
* @param {!shaka.util.Error} error
|
||
* @param {boolean=} onCreateSegmentIndex
|
||
*/
|
||
async function verifyError(master, media, error, onCreateSegmentIndex) {
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/main.exe', segmentData)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData)
|
||
.setResponseValue('data:text/plain;base64,AAECAwQFBgcICQoLDA0ODw==',
|
||
aesKey);
|
||
|
||
if (onCreateSegmentIndex) {
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await expectAsync(loadAllStreamsFor(actual))
|
||
.toBeRejectedWith(Util.jasmineError(error));
|
||
} else {
|
||
await expectAsync(parser.start('test:/master', playerInterface))
|
||
.toBeRejectedWith(Util.jasmineError(error));
|
||
}
|
||
}
|
||
|
||
it('if unable to guess codecs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="aaa,bbb",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",',
|
||
'URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const error = new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
Code.HLS_COULD_NOT_GUESS_CODECS,
|
||
['aaa', 'bbb']);
|
||
|
||
await verifyError(master, media, error);
|
||
});
|
||
|
||
describe('if required attributes are missing', () => {
|
||
/**
|
||
* @param {string} master
|
||
* @param {string} media
|
||
* @param {string} attributeName
|
||
*/
|
||
async function verifyMissingAttribute(master, media, attributeName) {
|
||
const error = new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
Code.HLS_REQUIRED_ATTRIBUTE_MISSING,
|
||
attributeName);
|
||
|
||
await verifyError(master, media, error);
|
||
}
|
||
|
||
it('bandwidth', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
await verifyMissingAttribute(master, media, 'BANDWIDTH');
|
||
});
|
||
|
||
it('uri', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:CODECS="avc1,mp4a",BANDWIDTH=200,',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
await verifyMissingAttribute(master, media, 'URI');
|
||
});
|
||
|
||
it('text uri if not ignoring text stream failure', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
config.hls.ignoreTextStreamFailures = false;
|
||
await verifyMissingAttribute(master, media, 'URI');
|
||
});
|
||
});
|
||
|
||
it('if FairPlay encryption with MSE and mp2t content', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
|
||
'KEYFORMAT="com.apple.streamingkeydelivery",',
|
||
'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.ts',
|
||
].join('');
|
||
|
||
const error = new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
||
|
||
await verifyError(master, media, error);
|
||
});
|
||
|
||
it('if SAMPLE-AES encryption with MSE and mp2t content', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:6\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-KEY:METHOD=SAMPLE-AES,',
|
||
'URI="fake",\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.ts',
|
||
].join('');
|
||
|
||
const error = new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
||
|
||
await verifyError(master, media, error);
|
||
});
|
||
|
||
|
||
describe('if required tags are missing', () => {
|
||
/**
|
||
* @param {string} master
|
||
* @param {string} media
|
||
* @param {string} tagName
|
||
*/
|
||
async function verifyMissingTag(master, media, tagName) {
|
||
const error = new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
Code.HLS_REQUIRED_TAG_MISSING,
|
||
tagName);
|
||
|
||
await verifyError(master, media, error);
|
||
}
|
||
|
||
it('EXTINF', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
await verifyMissingTag(master, media, 'EXTINF');
|
||
});
|
||
});
|
||
}); // Errors out
|
||
|
||
it('correctly detects VOD streams as non-live', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const manifest = await parser.start('test:/master', playerInterface);
|
||
expect(manifest.presentationTimeline.isLive()).toBe(false);
|
||
});
|
||
|
||
it('correctly detects streams with ENDLIST as non-live', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-ENDLIST',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const manifest = await parser.start('test:/master', playerInterface);
|
||
expect(manifest.presentationTimeline.isLive()).toBe(false);
|
||
});
|
||
|
||
it('guesses MIME types for known extensions', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-ENDLIST',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const manifest = await parser.start('test:/master', playerInterface);
|
||
const video = manifest.variants[0].video;
|
||
expect(video.mimeType).toBe('video/mp4');
|
||
});
|
||
|
||
it('guesses MIME types for known extensions with parameters', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
||
'video',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'main.mp4?foo=bar\n',
|
||
'#EXT-X-ENDLIST',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4?foo=bar', segmentData);
|
||
|
||
const manifest = await parser.start('test:/master', playerInterface);
|
||
const video = manifest.variants[0].video;
|
||
expect(video.mimeType).toBe('video/mp4');
|
||
});
|
||
|
||
it('does not produce multiple Streams for one playlist', async () => {
|
||
// Regression test for a bug in our initial HLS live implementation
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=1280x720,AUDIO="audio"\n',
|
||
'video0\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=768x432,AUDIO="audio"\n',
|
||
'video1\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/video0', media)
|
||
.setResponseText('test:/video1', media)
|
||
.setResponseText('test:/audio', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const manifest = await parser.start('test:/master', playerInterface);
|
||
expect(manifest.variants.length).toBe(2);
|
||
const audio0 = manifest.variants[0].audio;
|
||
const audio1 = manifest.variants[1].audio;
|
||
// These should be the exact same memory address, not merely equal.
|
||
// Otherwise, the parser will only be replacing one of the SegmentIndexes
|
||
// on update, which will lead to live streaming issues.
|
||
expect(audio0).toBe(audio1);
|
||
});
|
||
|
||
// https://github.com/shaka-project/shaka-player/issues/1664
|
||
it('correctly resolves relative playlist URIs', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=1280x720,AUDIO="audio"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('media/master', master) // Relative master URI
|
||
.setResponseText('http://foo/media/audio', media)
|
||
.setResponseText('http://foo/media/video', media)
|
||
.setResponseValue('http://foo/media/init.mp4', initSegmentData)
|
||
.setResponseValue('http://foo/media/main.mp4', segmentData);
|
||
|
||
fakeNetEngine.setResponseFilter((type, response) => {
|
||
// Simulate support for relative URIs in the browser by setting the
|
||
// absolute URI in response.uri.
|
||
if (response.uri == 'media/master') {
|
||
response.uri = 'http://foo/media/master';
|
||
}
|
||
});
|
||
|
||
// When this test fails, parser.start() fails. The relative playlist URI was
|
||
// being resolved to a bogus location ('media/media/audio'), which resulted
|
||
// in a failed request. Even if that bogus location were made absolute, it
|
||
// would still be wrong.
|
||
const manifest =
|
||
await parser.start('media/master', playerInterface);
|
||
expect(manifest.variants.length).toBe(1);
|
||
});
|
||
|
||
// https://github.com/shaka-project/shaka-player/issues/1908
|
||
it('correctly pairs variants with multiple video and audio', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
|
||
'URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="fr",',
|
||
'URI="audio2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=1280x720,FRAME-RATE=30,AUDIO="aud1"\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1.4d401f,mp4a.40.2",',
|
||
'RESOLUTION=1920x1080,FRAME-RATE=30,AUDIO="aud1"\n',
|
||
'video2\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(1280, 720);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'en';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'fr';
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(1280, 720);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'fr';
|
||
stream.originalLanguage = 'fr';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(1920, 1080);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.originalLanguage = 'en';
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'fr';
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(1920, 1080);
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'fr';
|
||
stream.originalLanguage = 'fr';
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
});
|
||
|
||
// https://github.com/shaka-project/shaka-player/issues/4308
|
||
it('handles unaligned streams', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=1280x720,FRAME-RATE=30\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1.4d401f",',
|
||
'RESOLUTION=1920x1080,FRAME-RATE=30\n',
|
||
'video2\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:5\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const media2 = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXTINF:4\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:1\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(1280, 720);
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.size(1920, 1080);
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
const actual = await testHlsParser(master, media, manifest, media2);
|
||
|
||
const videoSegments =
|
||
Array.from(actual.variants[0].video.segmentIndex || '');
|
||
expect(videoSegments[0].endTime).toBe(5);
|
||
expect(videoSegments[1].endTime).toBe(10);
|
||
|
||
const videoSegments2 =
|
||
Array.from(actual.variants[1].video.segmentIndex || '');
|
||
expect(videoSegments2[0].endTime).toBe(4);
|
||
expect(videoSegments2[1].endTime).toBe(9);
|
||
expect(videoSegments2[2].endTime).toBe(10);
|
||
});
|
||
|
||
it('allow audio groups on audio-only content', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",LANGUAGE="en",URI="audio1"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",LANGUAGE="eo",URI="audio2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a",AUDIO="aud"\n',
|
||
'audio3\n',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 200;
|
||
variant.language = 'en';
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 200;
|
||
variant.language = 'eo';
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio1', media)
|
||
.setResponseText('test:/audio2', media)
|
||
.setResponseText('test:/audio3', media)
|
||
.setResponseValue('test:/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/main.mp4', segmentData);
|
||
|
||
const actual = await parser.start('test:/master', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
expect(actual.variants.length).toBe(2);
|
||
expect(actual).toEqual(manifest);
|
||
});
|
||
|
||
describe('Variable substitution', () => {
|
||
it('parse variables master playlist', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-DEFINE:NAME="auth",VALUE="token=1"\n',
|
||
'#EXT-X-DEFINE:QUERYPARAM="a"\n',
|
||
'#EXT-X-DEFINE:QUERYPARAM="b"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio.m3u8?{$auth}&a={$a}&b={$b}\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",',
|
||
'URI="video.m3u8?{$auth}&a={$a}&b={$b}"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'segment.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/host/master.m3u8?a=1&b=2', master)
|
||
.setResponseText('test:/host/audio.m3u8?token=1&a=1&b=2', media)
|
||
.setResponseText('test:/host/video.m3u8?token=1&a=1&b=2', media)
|
||
.setResponseValue('test:/host/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/host/segment.mp4', segmentData);
|
||
|
||
const actual = await parser.start(
|
||
'test:/host/master.m3u8?a=1&b=2', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const video = actual.variants[0].video;
|
||
const audio = actual.variants[0].audio;
|
||
|
||
await video.createSegmentIndex();
|
||
await audio.createSegmentIndex();
|
||
goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
|
||
goog.asserts.assert(audio.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
// We check that the references are correct to check that the entire
|
||
// flow has gone well.
|
||
const videoReference = Array.from(video.segmentIndex)[0];
|
||
expect(videoReference.getUris())
|
||
.toEqual(['test:/host/segment.mp4']);
|
||
|
||
const audioReference = Array.from(audio.segmentIndex)[0];
|
||
expect(audioReference.getUris())
|
||
.toEqual(['test:/host/segment.mp4']);
|
||
});
|
||
|
||
it('parse variables in media playlist', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio.m3u8?fooParam=1\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8?fooParam=1"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-DEFINE:NAME="auth",VALUE="token=1"\n',
|
||
'#EXT-X-DEFINE:NAME="path",VALUE="test/"\n',
|
||
'#EXT-X-DEFINE:QUERYPARAM="fooParam"\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="{$path}init.mp4"\n',
|
||
'#EXTINF:5,\n',
|
||
'{$path}segment.mp4?{$auth}&fooParam={$fooParam}',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/host/master.m3u8', master)
|
||
.setResponseText('test:/host/audio.m3u8?fooParam=1', media)
|
||
.setResponseText('test:/host/video.m3u8?fooParam=1', media)
|
||
.setResponseValue('test:/host/test/init.mp4', initSegmentData)
|
||
.setResponseValue('test:/host/test/segment.mp4?token=1&fooParam=1',
|
||
segmentData);
|
||
|
||
const actual =
|
||
await parser.start('test:/host/master.m3u8', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const video = actual.variants[0].video;
|
||
const audio = actual.variants[0].audio;
|
||
|
||
await video.createSegmentIndex();
|
||
await audio.createSegmentIndex();
|
||
goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
|
||
goog.asserts.assert(audio.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
// We check that the references are correct to check that the entire
|
||
// flow has gone well.
|
||
const videoReference = Array.from(video.segmentIndex)[0];
|
||
expect(videoReference.getUris())
|
||
.toEqual(['test:/host/test/segment.mp4?token=1&fooParam=1']);
|
||
|
||
const audioReference = Array.from(audio.segmentIndex)[0];
|
||
expect(audioReference.getUris())
|
||
.toEqual(['test:/host/test/segment.mp4?token=1&fooParam=1']);
|
||
});
|
||
|
||
it('import variables in media from master playlist', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-DEFINE:NAME="auth",VALUE="?token=1"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,VIDEO="vid"\n',
|
||
'audio.m3u8{$auth}\n',
|
||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="vid",URI="video.m3u8{$auth}"',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-DEFINE:IMPORT="auth"\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4{$auth}"\n',
|
||
'#EXTINF:5,\n',
|
||
'segment.mp4{$auth}',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/host/master.m3u8', master)
|
||
.setResponseText('test:/host/audio.m3u8?token=1', media)
|
||
.setResponseText('test:/host/video.m3u8?token=1', media)
|
||
.setResponseValue('test:/host/init.mp4?token=1', initSegmentData)
|
||
.setResponseValue('test:/host/segment.mp4?token=1', segmentData);
|
||
|
||
const actual =
|
||
await parser.start('test:/host/master.m3u8', playerInterface);
|
||
await loadAllStreamsFor(actual);
|
||
const video = actual.variants[0].video;
|
||
const audio = actual.variants[0].audio;
|
||
|
||
await video.createSegmentIndex();
|
||
await audio.createSegmentIndex();
|
||
goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
|
||
goog.asserts.assert(audio.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
// We check that the references are correct to check that the entire
|
||
// flow has gone well.
|
||
const videoReference = Array.from(video.segmentIndex)[0];
|
||
expect(videoReference.getUris())
|
||
.toEqual(['test:/host/segment.mp4?token=1']);
|
||
|
||
const audioReference = Array.from(audio.segmentIndex)[0];
|
||
expect(audioReference.getUris())
|
||
.toEqual(['test:/host/segment.mp4?token=1']);
|
||
});
|
||
});
|
||
|
||
describe('EXT-X-SESSION-DATA', () => {
|
||
it('parses value data', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-SESSION-DATA:DATA-ID="fooId",VALUE="fooValue"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a"\n',
|
||
'audio',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
|
||
const eventValue = {
|
||
type: 'sessiondata',
|
||
id: 'fooId',
|
||
value: 'fooValue',
|
||
};
|
||
expect(onEventSpy).toHaveBeenCalledWith(
|
||
jasmine.objectContaining(eventValue));
|
||
});
|
||
|
||
it('parses value data with language', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-SESSION-DATA:DATA-ID="fooId",LANGUAGE="en",VALUE="fooValue"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a"\n',
|
||
'audio',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
|
||
const eventValue = {
|
||
type: 'sessiondata',
|
||
id: 'fooId',
|
||
language: 'en',
|
||
value: 'fooValue',
|
||
};
|
||
expect(onEventSpy).toHaveBeenCalledWith(
|
||
jasmine.objectContaining(eventValue));
|
||
});
|
||
|
||
it('parses uri data', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-SESSION-DATA:DATA-ID="fooId",URI="foo.json"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a"\n',
|
||
'audio',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
|
||
const eventValue = {
|
||
type: 'sessiondata',
|
||
id: 'fooId',
|
||
uri: 'test:/foo.json',
|
||
};
|
||
expect(onEventSpy).toHaveBeenCalledWith(
|
||
jasmine.objectContaining(eventValue));
|
||
});
|
||
|
||
it('parses mutiple data', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-SESSION-DATA:DATA-ID="fooId",LANGUAGE="en",VALUE="fooValue"\n',
|
||
'#EXT-X-SESSION-DATA:DATA-ID="fooId",LANGUAGE="es",VALUE="fooValue"\n',
|
||
'#EXT-X-SESSION-DATA:DATA-ID="fooId",URI="foo.json"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a"\n',
|
||
'audio',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
});
|
||
|
||
await testHlsParser(master, media, manifest);
|
||
|
||
expect(onEventSpy).toHaveBeenCalledTimes(3);
|
||
const eventValue1 = {
|
||
type: 'sessiondata',
|
||
id: 'fooId',
|
||
language: 'en',
|
||
value: 'fooValue',
|
||
};
|
||
expect(onEventSpy).toHaveBeenCalledWith(
|
||
jasmine.objectContaining(eventValue1));
|
||
const eventValue2 = {
|
||
type: 'sessiondata',
|
||
id: 'fooId',
|
||
language: 'es',
|
||
value: 'fooValue',
|
||
};
|
||
expect(onEventSpy).toHaveBeenCalledWith(
|
||
jasmine.objectContaining(eventValue2));
|
||
const eventValue3 = {
|
||
type: 'sessiondata',
|
||
id: 'fooId',
|
||
uri: 'test:/foo.json',
|
||
};
|
||
expect(onEventSpy).toHaveBeenCalledWith(
|
||
jasmine.objectContaining(eventValue3));
|
||
});
|
||
});
|
||
|
||
it('parses media playlists directly', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-START:TIME-OFFSET=-2\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.startTime = -2;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1.42C01E');
|
||
});
|
||
});
|
||
});
|
||
|
||
const actualManifest = await testHlsParser(media, '', manifest);
|
||
|
||
expect(actualManifest.presentationTimeline.getDuration()).toBe(5);
|
||
});
|
||
|
||
it('throw error when no segments', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/media', media);
|
||
|
||
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
shaka.util.Error.Code.HLS_EMPTY_MEDIA_PLAYLIST));
|
||
await expectAsync(parser.start('test:/media', playerInterface))
|
||
.toBeRejectedWith(expectedError);
|
||
});
|
||
|
||
it('throw error when all segments are gap', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-START:TIME-OFFSET=-2\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-GAP\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/media', media);
|
||
|
||
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
||
shaka.util.Error.Severity.CRITICAL,
|
||
shaka.util.Error.Category.MANIFEST,
|
||
shaka.util.Error.Code.HLS_EMPTY_MEDIA_PLAYLIST));
|
||
await expectAsync(parser.start('test:/media', playerInterface))
|
||
.toBeRejectedWith(expectedError);
|
||
});
|
||
|
||
it('parses #EXT-X-BITRATE', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'#EXT-X-BITRATE:385\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'#EXT-X-BITRATE:340\n',
|
||
'main.mp4\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BITRATE:300\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.bandwidth = 359000;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1.42C01E');
|
||
stream.bandwidth = 359000;
|
||
});
|
||
});
|
||
});
|
||
|
||
await testHlsParser(media, '', manifest);
|
||
});
|
||
|
||
it('honors hls.mediaPlaylistFullMimeType', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.test",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.test',
|
||
].join('');
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.hls.mediaPlaylistFullMimeType = 'audio/webm; codecs="vorbis"';
|
||
parser.configure(config);
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/webm', 'vorbis');
|
||
});
|
||
});
|
||
});
|
||
|
||
await testHlsParser(media, '', manifest);
|
||
});
|
||
|
||
it('honors hls.mediaPlaylistFullMimeType but detects AAC', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.aac',
|
||
].join('');
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
parser.configure(config);
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/aac');
|
||
});
|
||
});
|
||
});
|
||
|
||
await testHlsParser(media, '', manifest);
|
||
});
|
||
|
||
it('honors hls.mediaPlaylistFullMimeType but detects MPEG', async () => {
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp3',
|
||
].join('');
|
||
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
parser.configure(config);
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.mime('audio/mpeg');
|
||
});
|
||
});
|
||
});
|
||
|
||
await testHlsParser(media, '', manifest);
|
||
});
|
||
|
||
it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
|
||
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
||
config.hls.ignoreManifestProgramDateTime = true;
|
||
parser.configure(config);
|
||
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'video2.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'video3.mp4\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
||
'#EXTINF:5,\n',
|
||
'audio3.mp4\n',
|
||
'#EXTINF:5,\n',
|
||
'audio4.mp4\n',
|
||
].join('');
|
||
|
||
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
||
manifest.sequenceMode = sequenceMode;
|
||
manifest.type = shaka.media.ManifestParser.HLS;
|
||
manifest.anyTimeline();
|
||
manifest.addPartialVariant((variant) => {
|
||
variant.language = 'en';
|
||
variant.bandwidth = 200;
|
||
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
||
stream.mime('video/mp4', 'avc1');
|
||
stream.size(960, 540);
|
||
stream.frameRate = 60;
|
||
});
|
||
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
||
stream.language = 'en';
|
||
stream.mime('audio/mp4', 'mp4a');
|
||
});
|
||
});
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseValue('test:/init.mp4', initSegmentData);
|
||
|
||
const actualManifest = await parser.start('test:/master', playerInterface);
|
||
expect(actualManifest).toEqual(manifest);
|
||
|
||
const actualVideo = actualManifest.variants[0].video;
|
||
await actualVideo.createSegmentIndex();
|
||
goog.asserts.assert(actualVideo.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
const actualAudio = actualManifest.variants[0].audio;
|
||
await actualAudio.createSegmentIndex();
|
||
goog.asserts.assert(actualAudio.segmentIndex != null, 'Null segmentIndex!');
|
||
|
||
// Verify that the references are aligned on sequence number.
|
||
const videoSegments = Array.from(actualVideo.segmentIndex);
|
||
const audioSegments = Array.from(actualAudio.segmentIndex);
|
||
|
||
// The first two were dropped to align with the audio.
|
||
expect(videoSegments.map((ref) => ref.getUris()[0])).toEqual([
|
||
'test:/video3.mp4',
|
||
]);
|
||
// Audio has a 4th segment that video doesn't, but that doesn't get clipped
|
||
// or used as the base. Alignment is truly based on media sequence number.
|
||
expect(audioSegments.map((ref) => ref.getUris()[0])).toEqual([
|
||
'test:/audio3.mp4',
|
||
'test:/audio4.mp4',
|
||
]);
|
||
});
|
||
|
||
it('lazy-loads TS content without filtering it out', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",CLOSED-CAPTIONS=NONE\n',
|
||
'video\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud1",CLOSED-CAPTIONS=NONE\n',
|
||
'video2\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXTINF:5,\n',
|
||
'video2.ts\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
||
'#EXTINF:5,\n',
|
||
'audio1.ts\n',
|
||
'#EXTINF:5,\n',
|
||
'audio2.ts\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseText('test:/video2', video)
|
||
.setResponseValue('test:/init.mp4', initSegmentData);
|
||
|
||
const actualManifest = await parser.start('test:/master', playerInterface);
|
||
expect(actualManifest.variants.length).toBe(2);
|
||
|
||
const actualVideo0 = actualManifest.variants[0].video;
|
||
const actualVideo1 = actualManifest.variants[1].video;
|
||
|
||
// Before loading, all MIME types agree, and are defaulted to video/mp4.
|
||
expect(actualVideo0.mimeType).toBe('video/mp4');
|
||
expect(actualVideo1.mimeType).toBe('video/mp4');
|
||
|
||
await actualVideo0.createSegmentIndex();
|
||
|
||
// After loading just ONE stream, all MIME types agree again, and have been
|
||
// updated to reflect the TS content found inside the loaded playlist.
|
||
// This is how we avoid having the unloaded tracks filtered out during
|
||
// startup.
|
||
expect(actualVideo0.mimeType).toBe('video/mp2t');
|
||
expect(actualVideo1.mimeType).toBe('video/mp2t');
|
||
});
|
||
|
||
it('lazy-loads AAC content without filtering it out', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="spa",URI="audio2"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
||
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
||
'video\n',
|
||
].join('');
|
||
|
||
const video = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXTINF:5,\n',
|
||
'video2.ts\n',
|
||
].join('');
|
||
|
||
const audio = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
||
'#EXTINF:5,\n',
|
||
'audio1.aac\n',
|
||
'#EXTINF:5,\n',
|
||
'audio2.aac\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', master)
|
||
.setResponseText('test:/audio', audio)
|
||
.setResponseText('test:/audio2', audio)
|
||
.setResponseText('test:/video', video)
|
||
.setResponseValue('test:/init.mp4', initSegmentData);
|
||
|
||
const actualManifest = await parser.start('test:/master', playerInterface);
|
||
expect(actualManifest.variants.length).toBe(2);
|
||
|
||
const actualAudio0 = actualManifest.variants[0].audio;
|
||
const actualAudio1 = actualManifest.variants[1].audio;
|
||
|
||
// Before loading, all MIME types agree, and are defaulted to audio/mp4.
|
||
expect(actualAudio0.mimeType).toBe('audio/mp4');
|
||
expect(actualAudio0.codecs).toBe('mp4a');
|
||
expect(actualAudio1.mimeType).toBe('audio/mp4');
|
||
expect(actualAudio1.codecs).toBe('mp4a');
|
||
|
||
await actualAudio0.createSegmentIndex();
|
||
|
||
// After loading just ONE stream, all MIME types agree again, and have been
|
||
// updated to reflect the AAC content found inside the loaded playlist.
|
||
// This is how we avoid having the unloaded tracks filtered out during
|
||
// startup.
|
||
expect(actualAudio0.mimeType).toBe('audio/aac');
|
||
expect(actualAudio0.codecs).toBe('mp4a');
|
||
expect(actualAudio1.mimeType).toBe('audio/aac');
|
||
expect(actualAudio1.codecs).toBe('mp4a');
|
||
});
|
||
|
||
it('parses media playlists directly', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
const actualManifest = await parser.start('test:/master', playerInterface);
|
||
expect(actualManifest.variants.length).toBe(1);
|
||
|
||
const video = actualManifest.variants[0].video;
|
||
|
||
expect(video.width).toBe(256);
|
||
expect(video.height).toBe(110);
|
||
});
|
||
|
||
it('supports ContentSteering', async () => {
|
||
const master = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-CONTENT-STEERING:SERVER-URI="http://contentsteering",',
|
||
'PATHWAY-ID="a"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a",LANGUAGE="eng",',
|
||
'URI="audio/a/media.m3u8"\n',
|
||
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="b",LANGUAGE="eng",',
|
||
'URI="audio/b/media.m3u8"\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc,mp4a",',
|
||
'AUDIO="a",PATHWAY-ID="a",CLOSED-CAPTIONS=NONE\n',
|
||
'a/media.m3u8\n',
|
||
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc,mp4a",',
|
||
'AUDIO="b",PATHWAY-ID="b",CLOSED-CAPTIONS=NONE\n',
|
||
'b/media.m3u8',
|
||
].join('');
|
||
|
||
const media = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
||
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
||
'#EXTINF:5,\n',
|
||
'#EXT-X-BYTERANGE:121090@616\n',
|
||
'main.mp4',
|
||
].join('');
|
||
|
||
const contentSteering = JSON.stringify({
|
||
'VERSION': 1,
|
||
'TTL': 1,
|
||
'RELOAD-URI': 'http://contentsteering/update',
|
||
'PATHWAY-PRIORITY': [
|
||
'b',
|
||
'a',
|
||
],
|
||
});
|
||
|
||
fakeNetEngine
|
||
.setResponseText('http://master', master)
|
||
.setResponseText('http://contentsteering', contentSteering)
|
||
.setResponseText('http://master/a/media.m3u8', media)
|
||
.setResponseText('http://master/b/media.m3u8', media)
|
||
.setResponseText('http://master/audio/a/media.m3u8', media)
|
||
.setResponseText('http://master/audio/b/media.m3u8', media)
|
||
.setMaxUris(2);
|
||
|
||
/** @type {shaka.extern.Manifest} */
|
||
const manifest = await parser.start('http://master', playerInterface);
|
||
expect(manifest.variants.length).toBe(1);
|
||
|
||
const audio0 = manifest.variants[0].audio;
|
||
await audio0.createSegmentIndex();
|
||
goog.asserts.assert(audio0.segmentIndex, 'Null segmentIndex!');
|
||
const audioSegment0 = Array.from(audio0.segmentIndex)[0];
|
||
const audioUri0 = audioSegment0.getUris()[0];
|
||
const audioUri1 = audioSegment0.getUris()[1];
|
||
|
||
expect(audioUri0).toBe('http://master/audio/b/main.mp4');
|
||
expect(audioUri1).toBe('http://master/audio/a/main.mp4');
|
||
|
||
const video0 = manifest.variants[0].video;
|
||
await video0.createSegmentIndex();
|
||
goog.asserts.assert(video0.segmentIndex, 'Null segmentIndex!');
|
||
const videoSegment0 = Array.from(video0.segmentIndex)[0];
|
||
const videoUri0 = videoSegment0.getUris()[0];
|
||
const videoUri1 = videoSegment0.getUris()[1];
|
||
|
||
expect(videoUri0).toBe('http://master/b/main.mp4');
|
||
expect(videoUri1).toBe('http://master/a/main.mp4');
|
||
});
|
||
|
||
describe('EXT-X-DATERANGE', () => {
|
||
it('supports multiples tags', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'DURATION=1,X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="1",START-DATE="2000-01-01T00:00:05.00Z",',
|
||
'END-DATE="2000-01-01T00:00:06.00Z",X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="2",START-DATE="2000-01-01T00:00:10.00Z",',
|
||
'PLANNED-DURATION=1,X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="3",START-DATE="2000-01-01T00:00:15.00Z",',
|
||
'X-SHAKA="FOREVER"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
const metadataType = 'com.apple.quicktime.HLS';
|
||
const firstValues = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '0',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SHAKA',
|
||
data: 'FOREVER',
|
||
}),
|
||
];
|
||
const secondValues = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '1',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SHAKA',
|
||
data: 'FOREVER',
|
||
}),
|
||
];
|
||
const thirdValues = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '2',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'PLANNED-DURATION',
|
||
data: '1',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SHAKA',
|
||
data: 'FOREVER',
|
||
}),
|
||
];
|
||
const forthValues = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '3',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SHAKA',
|
||
data: 'FOREVER',
|
||
}),
|
||
];
|
||
expect(onMetadataSpy).toHaveBeenCalledTimes(4);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 0, 1,
|
||
firstValues);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 5, 6,
|
||
secondValues);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 10, 11,
|
||
thirdValues);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 15, null,
|
||
forthValues);
|
||
});
|
||
|
||
it('supports END-ON-NEXT', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'END-ON-NEXT=YES,X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="1",START-DATE="2000-01-01T00:00:05.00Z",',
|
||
'END-ON-NEXT=YES,X-SHAKA="FOREVER"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
const metadataType = 'com.apple.quicktime.HLS';
|
||
const values = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '0',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SHAKA',
|
||
data: 'FOREVER',
|
||
}),
|
||
];
|
||
expect(onMetadataSpy).toHaveBeenCalledTimes(1);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 0, 5, values);
|
||
});
|
||
|
||
it('skip duplicate IDs', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'DURATION=1,X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'DURATION=1,X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'DURATION=1,X-SHAKA="FOREVER"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
const metadataType = 'com.apple.quicktime.HLS';
|
||
const values = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '0',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SHAKA',
|
||
data: 'FOREVER',
|
||
}),
|
||
];
|
||
expect(onMetadataSpy).toHaveBeenCalledTimes(1);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 0, 1, values);
|
||
});
|
||
|
||
it('with no EXT-X-PROGRAM-DATE-TIME', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'DURATION=1,X-SHAKA="FOREVER"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
expect(onMetadataSpy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('ignores without useful value', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="2000-01-01T00:00:00.00Z",',
|
||
'DURATION=1\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
expect(onMetadataSpy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('ignores if date ranges are in the past', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="0",START-DATE="1999-01-01T00:00:00.00Z",',
|
||
'DURATION=1,X-SHAKA="FOREVER"\n',
|
||
'#EXT-X-DATERANGE:ID="1",START-DATE="2000-01-01T00:00:05.00Z",',
|
||
'END-DATE="1999-01-01T00:00:06.00Z",X-SHAKA="FOREVER"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
expect(onMetadataSpy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('supports interstitial', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="1",CLASS="com.apple.hls.interstitial",',
|
||
'START-DATE="2000-01-01T00:00:05.00Z",DURATION=30.0,',
|
||
'X-ASSET-URI="fake",CUE="PRE,ONCE",X-RESTRICT="SKIP,JUMP",',
|
||
'X-SNAP="IN"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
const metadataType = 'com.apple.hls.interstitial';
|
||
const values = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '1',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-ASSET-URI',
|
||
data: 'test:/fake',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'CUE',
|
||
data: 'PRE,ONCE',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-RESTRICT',
|
||
data: 'SKIP,JUMP',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SNAP',
|
||
data: 'IN',
|
||
}),
|
||
];
|
||
expect(onMetadataSpy).toHaveBeenCalledTimes(1);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 5, 35, values);
|
||
});
|
||
|
||
it('supports interstitial', async () => {
|
||
const mediaPlaylist = [
|
||
'#EXTM3U\n',
|
||
'#EXT-X-TARGETDURATION:5\n',
|
||
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:00.00Z\n',
|
||
'#EXTINF:5,\n',
|
||
'video1.ts\n',
|
||
'#EXT-X-DATERANGE:ID="1",CLASS="com.apple.hls.interstitial",',
|
||
'START-DATE="2000-01-01T00:00:05.00Z",DURATION=30.0,',
|
||
'X-ASSET-LIST="fake",CUE="PRE,ONCE",X-RESTRICT="SKIP,JUMP",',
|
||
'X-SNAP="IN"\n',
|
||
].join('');
|
||
|
||
fakeNetEngine
|
||
.setResponseText('test:/master', mediaPlaylist)
|
||
.setResponseValue('test:/video1.ts', tsSegmentData);
|
||
|
||
await parser.start('test:/master', playerInterface);
|
||
|
||
const metadataType = 'com.apple.hls.interstitial';
|
||
const values = [
|
||
jasmine.objectContaining({
|
||
key: 'ID',
|
||
data: '1',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-ASSET-LIST',
|
||
data: 'test:/fake',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'CUE',
|
||
data: 'PRE,ONCE',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-RESTRICT',
|
||
data: 'SKIP,JUMP',
|
||
}),
|
||
jasmine.objectContaining({
|
||
key: 'X-SNAP',
|
||
data: 'IN',
|
||
}),
|
||
];
|
||
expect(onMetadataSpy).toHaveBeenCalledTimes(1);
|
||
expect(onMetadataSpy).toHaveBeenCalledWith(metadataType, 5, 35, values);
|
||
});
|
||
});
|
||
});
|