mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-18 16:36:56 +03:00
444 lines
17 KiB
JavaScript
444 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,
|
|
onMetadata: () => {},
|
|
disableStream: (stream) => {},
|
|
};
|
|
});
|
|
|
|
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 manifestPreprocessorTXml 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.manifestPreprocessorTXml = (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());
|
|
});
|
|
});
|