mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-16 16:16:40 +03:00
7116a34ec2
## Background: The native DOM Parser can perform poorly on some older devices, this approach is faster on newer devices but is considerably better on older devices. This PR replaces the usage of the DOM Parser for DASH, MSS, VTT and TTML. The draw back of this approach that it does not include any validation at the cost of better performance.
442 lines
17 KiB
JavaScript
442 lines
17 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// Test basic manifest parsing functionality.
|
|
describe('MssParser Manifest', () => {
|
|
// const ManifestParser = shaka.test.ManifestParser;
|
|
const Mss = shaka.test.Mss;
|
|
|
|
/** @type {!shaka.test.FakeNetworkingEngine} */
|
|
let fakeNetEngine;
|
|
/** @type {!shaka.mss.MssParser} */
|
|
let parser;
|
|
/** @type {!jasmine.Spy} */
|
|
let onEventSpy;
|
|
/** @type {shaka.extern.ManifestParser.PlayerInterface} */
|
|
let playerInterface;
|
|
|
|
const h264CodecPrivateData = '000000016764001FAC2CA5014016EFFC100010014808' +
|
|
'080A000007D200017700C100005A648000B4C9FE31C6080002D3240005A64FF18E1DA' +
|
|
'12251600000000168E9093525';
|
|
|
|
const aacCodecPrivateData = '1210';
|
|
|
|
// From https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264PR/S
|
|
// uperSpeedway_720.ism/Manifest
|
|
const protectionHeader = 'jAMAAAEAAQCCAzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0A' +
|
|
'bABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzA' +
|
|
'G8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQ' +
|
|
'BhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4' +
|
|
'AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZ' +
|
|
'AEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQ' +
|
|
'wBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AE' +
|
|
'sASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA' +
|
|
'9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEA' +
|
|
'WQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABDAFUAUwBUAE8ATQBBAFQAVABSA' +
|
|
'EkAQgBVAFQARQBTAD4APABJAEkAUwBfAEQAUgBNAF8AVgBFAFIAUwBJAE8ATgA+ADcALg' +
|
|
'AxAC4AMQAwADYANAAuADAAPAAvAEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4' +
|
|
'APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AEwAQQBfAFUAUgBM' +
|
|
'AD4AaAB0AHQAcAA6AC8ALwBwAGwAYQB5AHIAZQBhAGQAeQAuAGQAaQByAGUAYwB0AHQAY' +
|
|
'QBwAHMALgBuAGUAdAAvAHAAcgAvAHMAdgBjAC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAG' +
|
|
'UAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APABEAFMAXwBJAEQAPgBBAEgAKwA' +
|
|
'wADMAagB1AEsAYgBVAEcAYgBIAGwAMQBWAC8AUQBJAHcAUgBBAD0APQA8AC8ARABTAF8A' +
|
|
'SQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=';
|
|
|
|
/** @param {!shaka.extern.Manifest} manifest */
|
|
async function loadAllStreamsFor(manifest) {
|
|
const promises = [];
|
|
for (const variant of manifest.variants) {
|
|
for (const stream of [variant.video, variant.audio]) {
|
|
if (stream) {
|
|
promises.push(stream.createSegmentIndex());
|
|
}
|
|
}
|
|
}
|
|
for (const text of manifest.textStreams) {
|
|
promises.push(text.createSegmentIndex());
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
fakeNetEngine = new shaka.test.FakeNetworkingEngine();
|
|
parser = Mss.makeMssParser();
|
|
onEventSpy = jasmine.createSpy('onEvent');
|
|
playerInterface = {
|
|
networkingEngine: fakeNetEngine,
|
|
modifyManifestRequest: (request, manifestInfo) => {},
|
|
modifySegmentRequest: (request, segmentInfo) => {},
|
|
filter: (manifest) => Promise.resolve(),
|
|
makeTextStreamsForClosedCaptions: (manifest) => {},
|
|
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
|
|
onEvent: shaka.test.Util.spyFunc(onEventSpy),
|
|
onError: fail,
|
|
isLowLatencyMode: () => false,
|
|
isAutoLowLatencyMode: () => false,
|
|
enableLowLatencyMode: () => {},
|
|
updateDuration: () => {},
|
|
newDrmInfo: (stream) => {},
|
|
onManifestUpdated: () => {},
|
|
getBandwidthEstimate: () => 1e6,
|
|
};
|
|
});
|
|
|
|
describe('fails for', () => {
|
|
it('failed network requests', async () => {
|
|
const expectedError = new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.NETWORK,
|
|
shaka.util.Error.Code.BAD_HTTP_STATUS);
|
|
|
|
fakeNetEngine.request.and.returnValue(
|
|
shaka.util.AbortableOperation.failed(expectedError));
|
|
await expectAsync(parser.start('', playerInterface))
|
|
.toBeRejectedWith(shaka.test.Util.jasmineError(expectedError));
|
|
});
|
|
|
|
it('missing SmoothStreamingMedia element', async () => {
|
|
const source = '<XML></XML>';
|
|
const error = new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.MANIFEST,
|
|
shaka.util.Error.Code.MSS_INVALID_XML,
|
|
'dummy://foo');
|
|
await Mss.testFails(source, error);
|
|
});
|
|
|
|
it('live content ', async () => {
|
|
const source = [
|
|
'<SmoothStreamingMedia Duration="1209510000" IsLive="true">',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
const error = new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.MANIFEST,
|
|
shaka.util.Error.Code.MSS_LIVE_CONTENT_NOT_SUPPORTED);
|
|
await Mss.testFails(source, error);
|
|
});
|
|
});
|
|
|
|
it('Disable audio does not create audio streams', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
config.disableAudio = true;
|
|
parser.configure(config);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBe(null);
|
|
expect(variant.video).toBeTruthy();
|
|
});
|
|
|
|
it('Disable video does not create video streams', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
config.disableVideo = true;
|
|
parser.configure(config);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBeTruthy();
|
|
expect(variant.video).toBe(null);
|
|
});
|
|
|
|
it('Disable text does not create text streams', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="text" Type="text" Url="uri">',
|
|
' <QualityLevel Bitrate="1000" FourCC="TTML"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
config.disableText = true;
|
|
parser.configure(config);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const stream = manifest.textStreams[0];
|
|
expect(stream).toBeUndefined();
|
|
});
|
|
|
|
it('Invokes manifestPreprocessor in config', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="text" Type="text" Url="uri">',
|
|
' <QualityLevel Bitrate="1000" FourCC="TTML"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
config.mss.manifestPreprocessor = (mss) => {
|
|
/** @type{shaka.extern.xml.Node} */ (mss).children.pop();
|
|
};
|
|
parser.configure(config);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const stream = manifest.textStreams[0];
|
|
expect(stream).toBeUndefined();
|
|
});
|
|
|
|
it('generate a fake init segment', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="3600000000">',
|
|
' <StreamIndex Name="audio" Type="audio" Url="{bitrate}/{start time}">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c t="0" d="30000000" r="12"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const segmentReference =
|
|
await Mss.getFirstAudioSegmentReference(manifest);
|
|
const initSegmentReference = segmentReference.initSegmentReference;
|
|
expect(initSegmentReference.getUris()).toEqual([]);
|
|
expect(initSegmentReference.getStartByte()).toBe(0);
|
|
expect(initSegmentReference.getEndByte()).toBe(null);
|
|
expect(initSegmentReference.getSegmentData()).toBeDefined();
|
|
});
|
|
|
|
it('skip video stream without CodecPrivateData', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" FourCC="H264" MaxHeight="720" ',
|
|
' MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBeTruthy();
|
|
expect(variant.video).toBe(null);
|
|
});
|
|
|
|
it('skip video stream without FourCC', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBeTruthy();
|
|
expect(variant.video).toBe(null);
|
|
});
|
|
|
|
it('supports audio stream without FourCC', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="',
|
|
aacCodecPrivateData,
|
|
'"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBeTruthy();
|
|
expect(variant.video).toBeTruthy();
|
|
});
|
|
|
|
it('supports AACL stream without CodecPrivateData', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" FourCC="AACL"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBeTruthy();
|
|
expect(variant.video).toBeTruthy();
|
|
});
|
|
|
|
it('supports AACH stream without CodecPrivateData', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <StreamIndex Name="audio" Type="audio" Url="uri">',
|
|
' <QualityLevel Bitrate="128000" Channels="2" FourCC="AACH"/>',
|
|
' <c d="20201360"/>',
|
|
' </StreamIndex>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.audio).toBeTruthy();
|
|
expect(variant.video).toBeTruthy();
|
|
});
|
|
|
|
it('recognizes PlayReady System ID with mixed cases', async () => {
|
|
const manifestText = [
|
|
'<SmoothStreamingMedia Duration="1209510000">',
|
|
' <StreamIndex Type="video" Url="uri">',
|
|
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
|
|
h264CodecPrivateData,
|
|
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
|
|
' <c d="20020000"/>',
|
|
' </StreamIndex>',
|
|
' <Protection>',
|
|
' <ProtectionHeader SystemID="9a04F079-9840-4286-aB92-e65BE0885f95">',
|
|
protectionHeader,
|
|
' </ProtectionHeader>',
|
|
' </Protection>',
|
|
'</SmoothStreamingMedia>',
|
|
].join('\n');
|
|
|
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
const variant = manifest.variants[0];
|
|
expect(variant.video.drmInfos.length).toBe(1);
|
|
expect(variant.video.drmInfos[0].keySystem).toBe('com.microsoft.playready');
|
|
// Also able to parse KID correctly
|
|
expect(variant.video.drmInfos[0].keyIds.size).toBe(1);
|
|
// Expected KID: https://testweb.playready.microsoft.com/Content/Content2X
|
|
expect([...(variant.video.drmInfos[0].keyIds.values())][0]).toBe(
|
|
'09E367028F33436CA5DD60FFE6671E70'.toLowerCase());
|
|
});
|
|
});
|