shaka-player
Version:
DASH/EME video player library
1,531 lines (1,364 loc) • 162 kB
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 {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 aes128Key;
/** @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];
aes128Key = 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');
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,
};
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-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.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.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.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', 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 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', /** @type {?} */ (jasmine.any(String)));
});
});
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', /** @type {?} */ (jasmine.any(String)));
});
variant.addPartialStream(ContentType.AUDIO, (stream) => {
stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
});
});
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",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', /** @type {?} */ (jasmine.any(String)));
});
});
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', /** @type {?} */ (jasmine.any(String)));
});
});
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,mp4a",',
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
'video\n',
'#EXT-X-STREAM-INF:BANDWIDTH=300,CODECS="avc1,mp4a",',
'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,mp4a",',
'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"\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: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,mp4a",',
'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",',
'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",',
'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",',
'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 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=60,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');
});