UNPKG

shaka-player

Version:
422 lines (377 loc) 16.5 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ describe('DashParser SegmentBase', () => { const Dash = shaka.test.Dash; const indexSegmentUri = '/base/test/test/assets/index-segment.mp4'; /** @type {!shaka.test.FakeNetworkingEngine} */ let fakeNetEngine; /** @type {!shaka.dash.DashParser} */ let parser; /** @type {shaka.extern.ManifestParser.PlayerInterface} */ let playerInterface; /** @type {!ArrayBuffer} */ let indexSegment; beforeAll(async () => { indexSegment = await shaka.test.Util.fetch(indexSegmentUri); }); beforeEach(() => { fakeNetEngine = new shaka.test.FakeNetworkingEngine(); parser = shaka.test.Dash.makeDashParser(); playerInterface = { networkingEngine: fakeNetEngine, modifyManifestRequest: (request, manifestInfo) => {}, modifySegmentRequest: (request, segmentInfo) => {}, filter: (manifest) => Promise.resolve(), makeTextStreamsForClosedCaptions: (manifest) => {}, onTimelineRegionAdded: fail, // Should not have any EventStream elements. onEvent: fail, onError: fail, isLowLatencyMode: () => false, isAutoLowLatencyMode: () => false, enableLowLatencyMode: () => {}, updateDuration: () => {}, newDrmInfo: (stream) => {}, onManifestUpdated: () => {}, getBandwidthEstimate: () => 1e6, }; }); afterEach(() => { // Dash parser stop is synchronous. parser.stop(); }); it('requests init data for WebM', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <BaseURL>http://example.com</BaseURL>', ' <Period>', ' <AdaptationSet mimeType="video/webm">', ' <Representation id="1" bandwidth="1" frameRate="3000/3001">', ' <BaseURL>media-1.webm</BaseURL>', ' <SegmentBase indexRange="100-200" timescale="9000">', ' <Initialization sourceURL="init-1.webm" range="201-300" />', ' </SegmentBase>', ' </Representation>', ' <Representation id="2" bandwidth="1" frameRate="1500/1501">', ' <BaseURL>media-2.webm</BaseURL>', ' <SegmentBase indexRange="1100-1200" timescale="9000">', ' <Initialization sourceURL="init-2.webm" range="1201-1300" />', ' </SegmentBase>', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseText('http://example.com/media-1.webm', '') .setResponseText('http://example.com/media-2.webm', '') .setResponseText('http://example.com/init-1.webm', '') .setResponseText('http://example.com/init-2.webm', ''); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); // Call createSegmentIndex() on each stream to make the requests, but expect // failure from the actual parsing, since the data is bogus. const stream1 = manifest.variants[0].video; await expectAsync(stream1.createSegmentIndex()).toBeRejected(); const stream2 = manifest.variants[1].video; await expectAsync(stream2.createSegmentIndex()).toBeRejected(); expect(fakeNetEngine.request).toHaveBeenCalledTimes(5); // Expect calls to fetch part of the media and init segments of each stream. fakeNetEngine.expectRangeRequest( 'http://example.com/media-1.webm', 100, 200, /* isInit= */ false); fakeNetEngine.expectRangeRequest( 'http://example.com/init-1.webm', 201, 300, /* isInit= */ true); fakeNetEngine.expectRangeRequest( 'http://example.com/media-2.webm', 1100, 1200, /* isInit= */ false); fakeNetEngine.expectRangeRequest( 'http://example.com/init-2.webm', 1201, 1300, /* isInit= */ true); }); it('inherits from Period', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <BaseURL>http://example.com</BaseURL>', ' <SegmentBase indexRange="100-200" timescale="9000">', ' <Initialization sourceURL="init.mp4" range="201-300" />', ' </SegmentBase>', ' <AdaptationSet mimeType="video/mp4">', ' <Representation bandwidth="1" />', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const segmentReference = await Dash.getFirstVideoSegmentReference(manifest); const initSegmentReference = segmentReference.initSegmentReference; expect(initSegmentReference.getUris()).toEqual( ['http://example.com/init.mp4']); expect(initSegmentReference.getStartByte()).toBe(201); expect(initSegmentReference.getEndByte()).toBe(300); expect(fakeNetEngine.request).toHaveBeenCalledTimes(2); fakeNetEngine.expectRangeRequest( 'http://example.com', 100, 200, /* isInit= */ false); }); it('inherits from AdaptationSet', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <AdaptationSet mimeType="video/mp4">', ' <BaseURL>http://example.com</BaseURL>', ' <SegmentBase indexRange="100-200" timescale="9000">', ' <Initialization sourceURL="init.mp4" range="201-300" />', ' </SegmentBase>', ' <Representation bandwidth="1" />', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const segmentReference = await Dash.getFirstVideoSegmentReference(manifest); const initSegmentReference = segmentReference.initSegmentReference; expect(initSegmentReference.getUris()).toEqual( ['http://example.com/init.mp4']); expect(initSegmentReference.getStartByte()).toBe(201); expect(initSegmentReference.getEndByte()).toBe(300); expect(fakeNetEngine.request).toHaveBeenCalledTimes(2); fakeNetEngine.expectRangeRequest( 'http://example.com', 100, 200, /* isInit= */ false); }); it('does not require sourceURL in Initialization', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <AdaptationSet mimeType="video/mp4">', ' <Representation bandwidth="1">', ' <BaseURL>http://example.com/stream.mp4</BaseURL>', ' <SegmentBase indexRange="100-200" timescale="9000">', ' <Initialization range="201-300" />', ' </SegmentBase>', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com/stream.mp4', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const segmentReference = await Dash.getFirstVideoSegmentReference(manifest); const initSegmentReference = segmentReference.initSegmentReference; expect(initSegmentReference.getUris()).toEqual( ['http://example.com/stream.mp4']); expect(initSegmentReference.getStartByte()).toBe(201); expect(initSegmentReference.getEndByte()).toBe(300); expect(fakeNetEngine.request).toHaveBeenCalledTimes(2); fakeNetEngine.expectRangeRequest( 'http://example.com/stream.mp4', 100, 200, /* isInit= */ false); }); it('merges across levels', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <BaseURL>http://example.com</BaseURL>', ' <SegmentBase timescale="9000">', ' <Initialization sourceURL="init.mp4" range="201-300" />', ' </SegmentBase>', ' <AdaptationSet mimeType="video/mp4">', ' <SegmentBase presentationTimeOffset="90000">', ' <Initialization sourceURL="init.mp4" range="201-300" />', ' </SegmentBase>', ' <Representation bandwidth="1">', ' <SegmentBase>', ' <RepresentationIndex sourceURL="index.mp4" range="5-2000" />', ' </SegmentBase>', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com/index.mp4', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const segmentReference = await Dash.getFirstVideoSegmentReference(manifest); const initSegmentReference = segmentReference.initSegmentReference; expect(initSegmentReference.getUris()).toEqual( ['http://example.com/init.mp4']); expect(initSegmentReference.getStartByte()).toBe(201); expect(initSegmentReference.getEndByte()).toBe(300); expect(segmentReference.timestampOffset).toBe(-10); expect(fakeNetEngine.request).toHaveBeenCalledTimes(2); fakeNetEngine.expectRangeRequest( 'http://example.com/index.mp4', 5, 2000, /* isInit= */ false); }); it('merges and overrides across levels', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <BaseURL>http://example.com</BaseURL>', ' <SegmentBase indexRange="0-10" timescale="9000">', ' <Initialization sourceURL="init.mp4" range="201-300" />', ' </SegmentBase>', ' <AdaptationSet mimeType="video/mp4">', ' <SegmentBase timescale="10" presentationTimeOffset="10">', ' <Initialization sourceURL="special.mp4" />', ' </SegmentBase>', ' <Representation bandwidth="1">', ' <SegmentBase indexRange="30-900" ', ' presentationTimeOffset="200" />', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const segmentReference = await Dash.getFirstVideoSegmentReference(manifest); const initSegmentReference = segmentReference.initSegmentReference; expect(initSegmentReference.getUris()).toEqual( ['http://example.com/special.mp4']); expect(initSegmentReference.getStartByte()).toBe(0); expect(initSegmentReference.getEndByte()).toBe(null); expect(segmentReference.timestampOffset).toBe(-20); expect(fakeNetEngine.request).toHaveBeenCalledTimes(2); fakeNetEngine.expectRangeRequest( 'http://example.com', 30, 900, /* isInit= */ false); }); it('does not assume the same timescale as media', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <AdaptationSet mimeType="video/mp4">', ' <Representation bandwidth="1">', ' <BaseURL>http://example.com/index.mp4</BaseURL>', ' <SegmentBase indexRange="30-900" ', ' timescale="1000"', ' presentationTimeOffset="2000" />', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com/index.mp4', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const video = manifest.variants[0].video; await video.createSegmentIndex(); // real data, should succeed goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!'); const reference = Array.from(video.segmentIndex)[0]; expect(reference.startTime).toBe(-2); expect(reference.endTime).toBe(10); // would be 12 without PTO }); // https://github.com/shaka-project/shaka-player/issues/3230 it('works with multi-Period with eviction', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period duration="PT30S">', ' <AdaptationSet mimeType="video/mp4">', ' <Representation bandwidth="1">', ' <BaseURL>http://example.com/index.mp4</BaseURL>', ' <SegmentBase indexRange="30-900" />', ' </Representation>', ' </AdaptationSet>', ' </Period>', ' <Period>', ' <AdaptationSet mimeType="video/mp4">', ' <Representation bandwidth="1">', ' <BaseURL>http://example.com/index.mp4</BaseURL>', ' <SegmentBase indexRange="30-900" presentationTimeOffset="30" />', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); fakeNetEngine .setResponseText('dummy://foo', source) .setResponseValue('http://example.com/index.mp4', indexSegment); /** @type {shaka.extern.Manifest} */ const manifest = await parser.start('dummy://foo', playerInterface); const video = manifest.variants[0].video; await video.createSegmentIndex(); // real data, should succeed goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!'); // There are originally 5 references, but the segment that spans the Period // boundary is duplicated. In the bug, we'd stop references at the Period // boundary and only have 3 references. const references = Array.from(video.segmentIndex); expect(references.length).toBe(6); }); describe('fails for', () => { it('unsupported container', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <BaseURL>http://example.com</BaseURL>', ' <AdaptationSet mimeType="video/cat">', ' <Representation bandwidth="1">', ' <SegmentBase indexRange="30-900" />', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); const error = new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.DASH_UNSUPPORTED_CONTAINER); await Dash.testFails(source, error); }); it('missing init segment for WebM', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <BaseURL>http://example.com</BaseURL>', ' <AdaptationSet mimeType="video/webm">', ' <Representation bandwidth="1">', ' <SegmentBase indexRange="30-900" />', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); const error = new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.DASH_WEBM_MISSING_INIT); await Dash.testFails(source, error); }); it('no @indexRange nor RepresentationIndex', async () => { const source = [ '<MPD mediaPresentationDuration="PT75S">', ' <Period>', ' <BaseURL>http://example.com</BaseURL>', ' <AdaptationSet mimeType="video/webm">', ' <Representation bandwidth="1">', ' <SegmentBase>', ' <Initialization sourceURL="test.webm" />', ' </SegmentBase>', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>', ].join('\n'); const error = new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.DASH_NO_SEGMENT_INFO); await Dash.testFails(source, error); }); }); });