UNPKG

shaka-player

Version:
1,554 lines (1,378 loc) 114 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ describe('HlsParser', () => { const ContentType = shaka.util.ManifestParserUtils.ContentType; const ManifestParser = shaka.test.ManifestParser; const TextStreamKind = shaka.util.ManifestParserUtils.TextStreamKind; const Util = shaka.test.Util; const originalAlwaysWarn = shaka.log.alwaysWarn; 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 {shaka.extern.ManifestParser.PlayerInterface} */ let playerInterface; /** @type {shaka.extern.ManifestConfiguration} */ let config; /** @type {!Uint8Array} */ let initSegmentData; /** @type {!Uint8Array} */ let segmentData; /** @type {!Uint8Array} */ let selfInitializingSegmentData; afterEach(() => { shaka.log.alwaysWarn = originalAlwaysWarn; }); beforeEach(() => { // TODO: use StreamGenerator? initSegmentData = new Uint8Array([ 0x00, 0x00, 0x00, 0x30, // size (48) 0x6D, 0x6F, 0x6F, 0x76, // type (moov) 0x00, 0x00, 0x00, 0x28, // trak size (40) 0x74, 0x72, 0x61, 0x6B, // type (trak) 0x00, 0x00, 0x00, 0x20, // mdia size (32) 0x6D, 0x64, 0x69, 0x61, // type (mdia) 0x00, 0x00, 0x00, 0x18, // mdhd size (24) 0x6D, 0x64, 0x68, 0x64, // type (mdhd) 0x00, 0x00, 0x00, 0x00, // version and flags 0x00, 0x00, 0x00, 0x00, // creation time (0) 0x00, 0x00, 0x00, 0x00, // modification time (0) 0x00, 0x00, 0x03, 0xe8, // timescale (1000) ]); segmentData = new Uint8Array([ 0x00, 0x00, 0x00, 0x24, // size (36) 0x6D, 0x6F, 0x6F, 0x66, // type (moof) 0x00, 0x00, 0x00, 0x1C, // traf size (28) 0x74, 0x72, 0x61, 0x66, // type (traf) 0x00, 0x00, 0x00, 0x14, // tfdt size (20) 0x74, 0x66, 0x64, 0x74, // type (tfdt) 0x01, 0x00, 0x00, 0x00, // version and flags 0x00, 0x00, 0x00, 0x00, // baseMediaDecodeTime first 4 bytes (0) 0x00, 0x00, 0x00, 0x00, // baseMediaDecodeTime last 4 bytes (0) ]); // segment starts at 0s. selfInitializingSegmentData = shaka.util.Uint8ArrayUtils.concat(initSegmentData, segmentData); fakeNetEngine = new shaka.test.FakeNetworkingEngine(); config = shaka.util.PlayerConfiguration.createDefault().manifest; onEventSpy = jasmine.createSpy('onEvent'); playerInterface = { filter: () => Promise.resolve(), makeTextStreamsForClosedCaptions: (manifest) => {}, networkingEngine: fakeNetEngine, onError: fail, onEvent: shaka.test.Util.spyFunc(onEventSpy), onTimelineRegionAdded: fail, isLowLatencyMode: () => false, isAutoLowLatencyMode: () => false, enableLowLatencyMode: () => {}, }; parser = new shaka.hls.HlsParser(); parser.configure(config); }); /** * @param {string} master * @param {string} media * @param {shaka.extern.Manifest} manifest * @return {!Promise.<shaka.extern.Manifest>} */ async function testHlsParser(master, media, manifest) { fakeNetEngine .setResponseText('test:/master', master) .setResponseText('test:/audio', media) .setResponseText('test:/audio2', media) .setResponseText('test:/video', media) .setResponseText('test:/video2', media) .setResponseText('test:/main.vtt', vttText) .setResponseValue('test:/init.mp4', initSegmentData) .setResponseValue('test:/init2.mp4', 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); expect(actual).toEqual(manifest); return actual; } it('parses manifest attributes', 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.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.channelsCount = 16; stream.spatialAudio = true; stream.mime('audio/mp4', 'mp4a'); }); }); manifest.addPartialTextStream((stream) => { stream.language = 'en'; stream.kind = TextStreamKind.SUBTITLE; stream.mime('text/vtt', ''); }); manifest.addPartialTextStream((stream) => { stream.language = '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); 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.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.channelsCount = 16; stream.spatialAudio = true; stream.mime('audio/mp4', 'mp4a'); }); }); manifest.addPartialTextStream((stream) => { stream.language = 'en'; stream.kind = TextStreamKind.SUBTITLE; stream.mime('text/vtt', ''); }); manifest.addPartialTextStream((stream) => { stream.language = '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); 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); }); }); }); 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); 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'); }); }); }); 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'); }); }); }); 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'); }); }); }); 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'); }); }); }); 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'); }); }); }); 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'); }); }); }); 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', ''); }); }); }); 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'); }); }); }); 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'); }); }); }); 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'); }); }); }); await testHlsParser(master, media, manifest); }); it('sets seek range correctly for non-zero start', 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-MEDIA-SEQUENCE:131\n', '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n', '#EXTINF:5,\n', '#EXT-X-BYTERANGE:121090@616\n', 'main.mp4', ].join(''); segmentData = new Uint8Array([ 0x00, 0x00, 0x00, 0x24, // size (36) 0x6D, 0x6F, 0x6F, 0x66, // type (moof) 0x00, 0x00, 0x00, 0x1C, // traf size (28) 0x74, 0x72, 0x61, 0x66, // type (traf) 0x00, 0x00, 0x00, 0x14, // tfdt size (20) 0x74, 0x66, 0x64, 0x74, // type (tfdt) 0x01, 0x00, 0x00, 0x00, // version and flags 0x00, 0x00, 0x00, 0x00, // baseMediaDecodeTime first 4 bytes (0) 0x00, 0x0A, 0x00, 0x00, // baseMediaDecodeTime last 4 bytes (655360) ]); 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 presentationTimeline = manifest.presentationTimeline; const stream = manifest.variants[0].video; await stream.createSegmentIndex(); goog.asserts.assert(stream.segmentIndex != null, 'Null segmentIndex!'); const ref = Array.from(stream.segmentIndex)[0]; expect(ref).not.toBe(null); if (ref) { expect(ref.startTime).toBe(0); // baseMediaDecodeTime (655360) / timescale (1000) expect(ref.timestampOffset).toBe(-655.36); } expect(presentationTimeline.getSeekRangeStart()).toBe(0); expect(presentationTimeline.getSeekRangeEnd()).toBe(5); }); 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'); }); }); }); 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))); }); }); }); 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))); }); }); }); 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))); }); }); }); 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))); }); }); }); 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'; }); }); 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'; }); }); }); 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'; }); }); manifest.addPartialVariant((variant) => { variant.language = 'fr'; variant.addPartialStream(ContentType.VIDEO); variant.addPartialStream(ContentType.AUDIO, (stream) => { stream.language = 'fr'; }); }); }); await testHlsParser(master, media, manifest); }); 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",', 'URI="audio"\n', '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",', 'CHARACTERISTICS="public.accessibility.describes-video,', 'public.accessibility.describes-music-and-sound",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'; }); }); 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', ]; }); }); }); await testHlsParser(master, media, manifest); }); it('fetch the start time for one audio/video stream and reuse for the others', async () => { const SEGMENT = shaka.net.NetworkingEngine.RequestType.SEGMENT; const master = [ '#EXTM3U\n', '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",', 'CHANNELS="2",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"\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(''); 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); await parser.start('test:/master', playerInterface); // The start time of audio should be fetched first, and then video and // text streams should reuse the start time from audio. // Thus, there should be 2 segment requests, for fetching audio init // and main segments, and not for video and text segments. expect(fakeNetEngine.request.calls.allArgs().filter((args) => { return args[0] == SEGMENT; }).length).toBe(2); fakeNetEngine.expectRequest('test:/init.mp4', SEGMENT); fakeNetEngine.expectRequest('test:/main.mp4', SEGMENT); }); 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'); }); }); }); // 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.forced = true; 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:/main.vtt', vttText) .setResponseValue('test:/init.mp4', initSegmentData) .setResponseValue('test:/main.mp4', segmentData); const actual = await parser.start('test:/master', playerInterface); 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'); }); variant.addPartialStream(ContentType.AUDIO, (stream) => { stream.mime('audio/mp4', 'mp4a'); }); }); manifest.addPartialTextStream((stream) => { stream.language = 'en'; stream.kind = TextStreamKind.SUBTITLE; stream.mime('text/vtt', ''); }); manifest.addPartialTextStream((stream) => { stream.language = '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); 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.forced = true; 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:/main.vtt', vttText) .setResponseValue('test:/init.mp4', initSegmentData) .setResponseValue('test:/main.mp4', segmentData); const actual = await parser.start('test:/master', playerInterface); 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,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.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', ''); }); }); 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); 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); // 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); 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('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); const variant = actual.variants[0]; expect(variant.audio).toBe(null); expect(variant.video).toBeTruthy(); }); it('Disable video does not create video streams', async () => {