UNPKG

shaka-player

Version:
1,556 lines (1,371 loc) 79.8 kB
/** * @license * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ describe('HlsParser', function() { 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 {shaka.extern.ManifestParser.PlayerInterface} */ let playerInterface; /** @type {shaka.extern.ManifestConfiguration} */ let config; /** @type {!ArrayBuffer} */ let initSegmentData; /** @type {!ArrayBuffer} */ let segmentData; /** @type {!ArrayBuffer} */ let selfInitializingSegmentData; afterEach(() => { shaka.log.alwaysWarn = originalAlwaysWarn; }); beforeEach(function() { // 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) ]).buffer; 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) ]).buffer; // segment starts at 0s. selfInitializingSegmentData = shaka.util.Uint8ArrayUtils.concat( new Uint8Array(initSegmentData), new Uint8Array(segmentData)).buffer; fakeNetEngine = new shaka.test.FakeNetworkingEngine(); config = shaka.util.PlayerConfiguration.createDefault().manifest; playerInterface = { filterNewPeriod: function() {}, filterAllPeriods: function() {}, networkingEngine: fakeNetEngine, onError: fail, onEvent: fail, onTimelineRegionAdded: fail, }; 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:/main.mp4', segmentData) .setResponseValue('test:/main.test', segmentData) .setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData); let actual = await parser.start('test:/master', playerInterface); expect(actual).toEqual(manifest); return actual; } it('parses manifest attributes', function(done) { 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-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 = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .language('en') .bandwidth(200) .addPartialStream(ContentType.VIDEO) .anyInitSegment() .presentationTimeOffset(0) .mime('video/mp4', 'avc1') .frameRate(60) .size(960, 540) .addPartialStream(ContentType.AUDIO) .language('en') .anyInitSegment() .presentationTimeOffset(0) .mime('audio/mp4', 'mp4a') .channelsCount(2) .addPartialStream(ContentType.TEXT) .language('en') .nullInitSegment() .presentationTimeOffset(0) .mime('text/vtt', '') .kind(TextStreamKind.SUBTITLE) .addPartialStream(ContentType.TEXT) .language('es') .nullInitSegment() .presentationTimeOffset(0) .mime('text/vtt', '') .kind(TextStreamKind.SUBTITLE) .build(); 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); parser.start('test:/master', playerInterface) .then(function(actual) { expect(actual).toEqual(manifest); }) .catch(fail).then(done); }); it('ignores duplicate CODECS', async function() { let master = [ '#EXTM3U\n', '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.4d001e,avc1.42000d",', 'RESOLUTION=960x540,FRAME-RATE=60\n', 'video', ].join(''); let 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1.4d001e') .build(); await testHlsParser(master, media, manifest); }); it('parses video-only variant', async function() { let master = [ '#EXTM3U\n', '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",', 'RESOLUTION=960x540,FRAME-RATE=60\n', 'video', ].join(''); let 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', 'mp4a') .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', 'mp4a') .build(); 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 = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .closedCaptions(closedCaptions) .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', 'mp4a') .build(); 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 = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', 'mp4a') .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', 'mp4a') .build(); 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) ]).buffer; fakeNetEngine .setResponseText('test:/master', master) .setResponseText('test:/video', media) .setResponseValue('test:/init.mp4', initSegmentData) .setResponseValue('test:/main.mp4', segmentData); let manifest = await parser.start('test:/master', playerInterface); let presentationTimeline = manifest.presentationTimeline; const stream = manifest.periods[0].variants[0].video; // baseMediaDecodeTime (655360) / timescale (1000) expect(stream.presentationTimeOffset).toBe(655.36); const pos = stream.findSegmentPosition(0); expect(pos).not.toBe(null); expect(stream.getSegmentReference(pos).startTime).toBe(0); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1,mp4a') .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', jasmine.any(String)) .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', jasmine.any(String)) .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', jasmine.any(String)) .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', jasmine.any(String)) .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', jasmine.any(String)) .build(); 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",', '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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .bandwidth(200) .addPartialStream(ContentType.VIDEO) .size(960, 540) .addPartialStream(ContentType.AUDIO) .language('en') .addPartialVariant() .bandwidth(300) .addPartialStream(ContentType.VIDEO) .size(960, 540) .addPartialStream(ContentType.AUDIO) .language('fr') .build(); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .language('en') .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.AUDIO) .language('en') .addPartialVariant() .language('fr') .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.AUDIO) .language('fr') .build(); 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 = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .language('en') .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.AUDIO) .language('en') .addPartialVariant() .language('en') .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.AUDIO) .language('en') .roles([ 'public.accessibility.describes-video', 'public.accessibility.describes-music-and-sound', ]) .build(); await testHlsParser(master, media, manifest); }); it('should call filterAllPeriods for parsing', function(done) { 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(''); fakeNetEngine .setResponseText('test:/master', master) .setResponseText('test:/audio', media) .setResponseText('test:/video', media) .setResponseValue('test:/init.mp4', initSegmentData) .setResponseValue('test:/main.mp4', segmentData); let filterAllPeriods = jasmine.createSpy('filterAllPeriods'); playerInterface.filterAllPeriods = Util.spyFunc(filterAllPeriods); parser.start('test:/master', playerInterface) .then(function(manifest) { expect(filterAllPeriods.calls.count()).toBe(1); }).catch(fail).then(done); }); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .build(); // 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 text streams', 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .addPartialStream(ContentType.AUDIO) .mime('audio/mp4', 'mp4a') .addPartialStream(ContentType.TEXT) .language('en') .mime('text/vtt', '') .kind(TextStreamKind.SUBTITLE) .addPartialStream(ContentType.TEXT) .language('es') .mime('text/vtt', '') .kind(TextStreamKind.SUBTITLE) .build(); 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); let actual = await parser.start('test:/master', playerInterface); expect(actual).toEqual(manifest); }); it('parses manifest with text streams without 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"\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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.AUDIO) .addPartialStream(ContentType.TEXT) .mime('text/vtt', '') .kind(TextStreamKind.SUBTITLE) .addPartialStream(ContentType.TEXT) .mime('text/vtt', '') .kind(TextStreamKind.SUBTITLE) .build(); 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); let 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); let actual = await parser.start('test:/master', playerInterface); // Duration should be the minimum of the streams, but ignore the text // stream. let timeline = actual.presentationTimeline; expect(timeline.getDuration()).toBe(10); let period = actual.periods[0]; expect(period.textStreams.length).toBe(1); expect(period.variants.length).toBe(1); expect(period.variants[0].audio).toBeTruthy(); expect(period.variants[0].video).toBeTruthy(); }); 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', ].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 config = shaka.util.PlayerConfiguration.createDefault().manifest; config.disableAudio = true; parser.configure(config); const actual = await parser.start('test:/master', playerInterface); const variant = actual.periods[0].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', ].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 config = shaka.util.PlayerConfiguration.createDefault().manifest; config.disableVideo = true; parser.configure(config); const actual = await parser.start('test:/master', playerInterface); const variant = actual.periods[0].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', ].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 config = shaka.util.PlayerConfiguration.createDefault().manifest; config.disableText = true; parser.configure(config); const actual = await parser.start('test:/master', playerInterface); const stream = actual.periods[0].textStreams[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,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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.TEXT) .language('en') .mime('application/mp4', 'stpp.ttml.im1t') .build(); 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); let actual = await parser.start('test:/master', playerInterface); 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,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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.TEXT) .mime('text/vtt', 'vtt') .build(); 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); let actual = await parser.start('test:/master', playerInterface); expect(actual).toEqual(manifest); }); it('allows init segments in text streams', function(done) { 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,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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(jasmine.any(Number)) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .addPartialStream(ContentType.TEXT) .anyInitSegment() .kind(TextStreamKind.SUBTITLE) .build(); 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); parser.start('test:/master', playerInterface) .then(function(actual) { expect(actual).toEqual(manifest); }) .catch(fail).then(done); }); it('drops failed text streams when configured to', 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,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(''); /* eslint-disable indent */ const manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .build(); /* eslint-enable indent */ fakeNetEngine .setResponseText('test:/master', master) .setResponseText('test:/audio', media) .setResponseText('test:/video', media) .setResponseText('test:/text', textMedia) .setResponseText('test:/main.foo', '') .setResponseValue('test:/init.mp4', initSegmentData) .setResponseValue('test:/main.mp4', segmentData); config.hls.ignoreTextStreamFailures = true; const actual = await parser.start('test:/master', playerInterface); 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",', '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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .mime('video/mp4', 'avc1') .frameRate(60) .size(960, 540) .addPartialStream(ContentType.AUDIO) .build(); await testHlsParser(master, media, manifest); }); it('constructs relative URIs', function(done) { 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); parser.start('test:/host/master.m3u8', playerInterface) .then(function(actual) { let video = actual.periods[0].variants[0].video; let audio = actual.periods[0].variants[0].audio; let videoPosition = video.findSegmentPosition(0); let audioPosition = audio.findSegmentPosition(0); goog.asserts.assert(videoPosition != null, 'Cannot find first video segment'); goog.asserts.assert(audioPosition != null, 'Cannot find first audio segment'); let videoReference = video.getSegmentReference(videoPosition); let audioReference = audio.getSegmentReference(audioPosition); expect(videoReference).not.toBe(null); expect(audioReference).not.toBe(null); if (videoReference) { expect(videoReference.getUris()[0]) .toEqual('test:/host/video/segment.mp4'); } if (audioReference) { expect(audioReference.getUris()[0]) .toEqual('test:/host/audio/segment.mp4'); } }).catch(fail).then(done); }); 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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .addPartialStream(ContentType.VIDEO) .nullInitSegment() .mime('video/mp4', 'avc1') .addPartialStream(ContentType.AUDIO) .nullInitSegment() .mime('audio/mp4', 'mp4a') .build(); await testHlsParser(master, media, manifest); }); it('drops variants encrypted with AES-128', 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",', '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 mediaWithAesEncryption = [ '#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(''); let manifest = new shaka.test.ManifestGenerator() .anyTimeline() .addPeriod(0) .addPartialVariant() .bandwidth(200) .addPartialStream(ContentType.VIDEO) .size(960, 540) .addPartialStream(ContentType.AUDIO) .language('en') .build(); fakeNetEngine .setRe