/*! @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();
const config = shaka.util.PlayerConfiguration.createDefault();
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,
updateDuration: () => {},
newDrmInfo: (stream) => {},
onManifestUpdated: () => {},
getBandwidthEstimate: () => 1e6,
onMetadata: () => {},
disableStream: (stream) => {},
addFont: (name, url) => {},
getStreamingRetryParameters: () => config.streaming.retryParameters,
onSegmentReceived: (deltaTimeMs, numBytes) => {},
};
});
afterEach(() => {
// Dash parser stop is synchronous.
parser.stop();
});
it('requests init data for WebM', async () => {
const source = [
'',
' http://example.com',
' ',
' ',
' ',
' media-1.webm',
' ',
' ',
' ',
' ',
' ',
' media-2.webm',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' ',
' ',
' http://example.com/stream.mp4',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' ',
' ',
' http://example.com/index.mp4',
' ',
' ',
' ',
' ',
'',
].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
});
it('fetch RepresentationIndex from init, media from BaseURL', async () => {
// Matches the pattern seen in the wild: Representation points to media,
// but RepresentationIndex/Initialization live in a shared init file.
const source = [
'',
' ',
' ',
' ',
' https://media.example.com/video_1.mp4',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].join('\n');
// Minimal SIDX with one ref starting at byte 0, size 1000.
const makeSidx = () => {
const size = 44;
const buffer = new ArrayBuffer(size);
const dv = new DataView(buffer);
dv.setUint32(0, size);
dv.setUint8(4, 's'.charCodeAt(0));
dv.setUint8(5, 'i'.charCodeAt(0));
dv.setUint8(6, 'd'.charCodeAt(0));
dv.setUint8(7, 'x'.charCodeAt(0));
dv.setUint32(8, 0); // version/flags
dv.setUint32(12, 0); // reference_ID
dv.setUint32(16, 24000);// timescale
dv.setUint32(20, 0); // earliestPresentationTime
dv.setUint32(24, 0); // firstOffset
dv.setUint16(28, 0); // reserved
dv.setUint16(30, 1); // referenceCount
dv.setUint32(32, 1000); // referenceSize
dv.setUint32(36, 24000);// subsegmentDuration (1s)
dv.setUint32(40, 0); // SAP
return buffer;
};
fakeNetEngine
.setResponseText('dummy://foo', source)
.setResponseValue(
'https://media.example.com/video_init.mp4', makeSidx());
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
const video = manifest.variants[0].video;
await video.createSegmentIndex();
goog.asserts.assert(video.segmentIndex != null, 'Null segmentIndex!');
expect(fakeNetEngine.request).toHaveBeenCalledTimes(2);
fakeNetEngine.expectRangeRequest(
'https://media.example.com/video_init.mp4',
0, 79, /* isInit= */ false);
const reference = Array.from(video.segmentIndex)[0];
expect(reference.getUris()).toEqual(
['https://media.example.com/video_1.mp4']);
expect(reference.startByte).toBe(0);
expect(reference.endByte).toBe(999);
});
// https://github.com/shaka-project/shaka-player/issues/3230
it('works with multi-Period with eviction', async () => {
const source = [
'',
' ',
' ',
' ',
' http://example.com/index.mp4',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' http://example.com/index.mp4',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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 = [
'',
' ',
' http://example.com',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].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);
});
});
});