UNPKG

shaka-player

Version:
475 lines (428 loc) 18.5 kB
/*! @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('invalid XML', async () => { const source = '<not 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('XML with inner errors', async () => { const source = [ '<SmoothStreamingMedia Duration="1209510000">', ' <StreamIndex Name="audio" Type="audio" Url="uri">', ' <QualityLevel Bitrate="128000" Channels="2" CodecPrivateData="', aacCodecPrivateData, '" FourCC="AACL"/>', ' <c d="20201360"/>', ' </StreamIndex', // Missing a close bracket. '</SmoothStreamingMedia>', ].join('\n'); 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('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) => { const selector = 'StreamIndex[Name="text"'; const vttElements = mss.querySelectorAll(selector); for (const element of vttElements) { element.parentNode.removeChild(element); } }; 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()); }); });