Files
shaka-player/test/dash/dash_parser_content_protection_unit.js
T
Joey Parrish fbbd63d96b test: Late load tests, fix Chromecast test flake (#4115)
This change fixes tests on Chromecast by loading tests later in the process.  Test scripts are now dynamically inserted by boot.js, rather than loaded by Karma.  The bootstrapping code then awaits the completion of that before starting the Karma frameworks (Jasmine) to run the tests.

This also removes the use of goog.provide/goog.require in tests and test utils.  We don't need to load test utils or library sources dynamically in each test, and this gives us more explicit control over script loading and ordering.

Closes #4094
2022-04-11 15:47:48 -07:00

905 lines
32 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
// Test DRM-related parsing.
describe('DashParser ContentProtection', () => {
const Dash = shaka.test.Dash;
const ContentProtection = shaka.dash.ContentProtection;
const strToXml = (str) => {
const parser = new DOMParser();
return parser.parseFromString(str, 'application/xml').documentElement;
};
/**
* Tests that the parser produces the correct results.
*
* @param {string} manifestText
* @param {Object} expected A Manifest-like object. The parser output is
* expected to match this.
* @param {boolean=} ignoreDrmInfo
* @return {!Promise}
*/
async function testDashParser(manifestText, expected, ignoreDrmInfo = false) {
const netEngine = new shaka.test.FakeNetworkingEngine();
netEngine.setDefaultText(manifestText);
const dashParser = new shaka.dash.DashParser();
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
config.dash.ignoreDrmInfo = ignoreDrmInfo || false;
dashParser.configure(config);
const playerInterface = {
networkingEngine: netEngine,
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: () => {},
};
const actual = await dashParser.start(
'http://example.com', playerInterface);
expect(actual).toEqual(expected);
// When the above expectation fails, it is far too hard to read the output
// and debug the test failure. So we also do these more targetted
// comparisons below, which will be easier to read and debug. The full
// comparison above remains to catch anything we haven't written a more
// targetted expectation for below.
for (let i = 0; i < actual.variants.length; ++i) {
// NOTE: ['sample'] is how we get access to the partial object given to
// jasmine.objectContaining().
const actualVariant = actual.variants[i];
const expectedVariant = expected['sample'].variants[i];
const actualVideo = actualVariant.video;
const expectedVideo = expectedVariant['sample'].video;
const actualDrmInfos = actualVideo.drmInfos;
const expectedDrmInfos = expectedVideo['sample'].drmInfos;
expect(actualDrmInfos).withContext(`video drmInfos, i=${i}`)
.toEqual(expectedDrmInfos);
const actualKeyIds = actualVideo.keyIds;
const expectedKeyIds = expectedVideo['sample'].keyIds;
expect(actualKeyIds).withContext(`video keyIds, i=${i}`)
.toEqual(expectedKeyIds);
}
}
/**
* Build a simple manifest with ContentProtection lines inserted into the
* AdaptationSet and each Representation.
*
* @param {!Array.<string>} adaptationSetLines
* @param {!Array.<string>} representation1Lines
* @param {!Array.<string>} representation2Lines
* @return {string}
*/
function buildManifestText(
adaptationSetLines, representation1Lines, representation2Lines) {
const template = [
'<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011"',
' xmlns:cenc="urn:mpeg:cenc:2013"',
' xmlns:mspr="urn:microsoft:playready">',
' <Period duration="PT30S">',
' <SegmentTemplate media="s.mp4" duration="2" />',
' <AdaptationSet mimeType="video/mp4" codecs="avc1.4d401f">',
'%(adaptationSetLines)s',
' <Representation bandwidth="50" width="576" height="432">',
'%(representation1Lines)s',
' </Representation>',
' <Representation bandwidth="100" width="576" height="432">',
'%(representation2Lines)s',
' </Representation>',
' </AdaptationSet>',
' </Period>',
'</MPD>',
].join('\n');
return sprintf(template, {
adaptationSetLines: adaptationSetLines.join('\n'),
representation1Lines: representation1Lines.join('\n'),
representation2Lines: representation2Lines.join('\n'),
});
}
/**
* Build an expected manifest which checks DRM-related fields.
*
* @param {!Array.<!Object>} drmInfos A list of DrmInfo-like objects.
* @param {!Array.<string>=} keyIds The key IDs to attach to each variant.
* Will default to the keyIds from the first drmInfo object.
* @return {Object} A Manifest-like object.
*/
function buildExpectedManifest(drmInfos, keyIds) {
if (!keyIds) {
if (drmInfos.length) {
// NOTE: ['sample'] is how we get access to the partial object given to
// jasmine.objectContaining().
keyIds = Array.from(drmInfos[0]['sample'].keyIds);
} else {
keyIds = [];
}
}
const variants = [];
const numVariants = 2;
for (let i = 0; i < numVariants; i++) {
const variant = jasmine.objectContaining({
video: jasmine.objectContaining({
keyIds: new Set(keyIds[i] ? [keyIds[i]] : []),
drmInfos,
}),
});
variants.push(variant);
}
return jasmine.objectContaining({
variants: variants,
textStreams: [],
});
}
/**
* Build an expected DrmInfo based on a key system and optional key IDs and
* init data.
*
* @param {string} keySystem
* @param {!Array.<string>=} keyIds
* @param {!Array.<shaka.extern.InitDataOverride>=} initData
* @return {Object} A DrmInfo-like object.
*/
function buildDrmInfo(keySystem, keyIds = [], initData = []) {
return jasmine.objectContaining({
keySystem,
keyIds: new Set(keyIds),
initData,
});
}
/**
* Build an expected InitDataOverride based on base-64-encoded PSSHs and
* optional key IDs.
*
* @param {!Array.<string>} base64Psshs
* @param {!Array.<string>=} keyIds
* @return {!Array.<shaka.extern.InitDataOverride>}
*/
function buildInitData(base64Psshs, keyIds = []) {
return base64Psshs.map((base64, index) => {
/** @type {shaka.extern.InitDataOverride} */
const initData = {
initDataType: 'cenc',
initData: shaka.util.Uint8ArrayUtils.fromBase64(base64),
keyId: keyIds[index] || null,
};
return initData;
});
}
it('handles clear content', async () => {
const source = buildManifestText([], [], []);
const expected = buildExpectedManifest([]);
await testDashParser(source, expected);
});
describe('maps standard scheme IDs', () => {
/**
* @param {string} name A name for the test
* @param {!Array.<string>} uuids DRM scheme UUIDs
* @param {!Array.<string>} keySystems expected key system IDs
*/
function testKeySystemMappings(name, uuids, keySystems) {
it(name, async () => {
const adaptationSetLines = uuids.map((uri) => {
return sprintf('<ContentProtection schemeIdUri="urn:uuid:%s" />',
uri);
});
const source = buildManifestText(adaptationSetLines, [], []);
const drmInfos = keySystems.map((keySystem) => {
return buildDrmInfo(keySystem);
});
const expected = buildExpectedManifest(drmInfos);
await testDashParser(source, expected);
});
}
testKeySystemMappings('for Widevine',
['edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'], ['com.widevine.alpha']);
testKeySystemMappings('for PlayReady',
['9a04f079-9840-4286-ab92-e65be0885f95'], ['com.microsoft.playready']);
testKeySystemMappings('for old PlayReady',
['79f0049a-4098-8642-ab92-e65be0885f95'], ['com.microsoft.playready']);
testKeySystemMappings('for Adobe Primetime',
['f239e769-efa3-4850-9c16-a903c6932efb'], ['com.adobe.primetime']);
testKeySystemMappings('for multiple DRMs in the specified order',
[
'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
'9a04f079-9840-4286-ab92-e65be0885f95',
], [
'com.widevine.alpha',
'com.microsoft.playready',
]);
testKeySystemMappings('in a case-insensitive way',
[
'EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED',
'9A04F079-9840-4286-AB92-E65BE0885F95',
'F239E769-EFA3-4850-9C16-A903C6932EFB',
], [
'com.widevine.alpha',
'com.microsoft.playready',
'com.adobe.primetime',
]);
});
it('inherits key IDs from AdaptationSet to Representation', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha', [
// Representation 1 & 2 key ID deduplicated in DrmInfo
'deadbeeffeedbaadf00d000008675309',
]),
],
[
// Representation 1 key ID
'deadbeeffeedbaadf00d000008675309',
// Representation 2 key ID
'deadbeeffeedbaadf00d000008675309',
]);
await testDashParser(source, expected);
});
it('sets key IDs for the init data', async () => {
const source = buildManifestText([
// AdaptationSet lines
], [
// Representation 1 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
' value="cenc"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309">',
' <cenc:pssh>bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5</cenc:pssh>',
'</ContentProtection>',
], []);
const initData = buildInitData(
['bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5'], // PSSHs
['deadbeeffeedbaadf00d000008675309']); // key ID for init data
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha',
['deadbeeffeedbaadf00d000008675309'], // key ID
initData),
]);
await testDashParser(source, expected);
});
it('lets Representations override key IDs', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [
// Representation 1 lines
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-000004390116" />',
], [
// Representation 2 lines
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-018006492568" />',
]);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha', [
// Representation 1 key ID
'baadf00dfeeddeafbeef000004390116',
// Representation 2 key ID
'baadf00dfeeddeafbeef018006492568',
]),
]);
await testDashParser(source, expected);
});
it('extracts embedded PSSHs', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">',
' <cenc:pssh>ZmFrZSBXaWRldmluZSBQU1NI</cenc:pssh>',
'</ContentProtection>',
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
' <cenc:pssh>bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5</cenc:pssh>',
'</ContentProtection>',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha',
[], // key IDs
buildInitData(['ZmFrZSBXaWRldmluZSBQU1NI'])),
buildDrmInfo('com.microsoft.playready',
[], // key IDs
buildInitData(['bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5'])),
]);
await testDashParser(source, expected);
});
it('extracts embedded PSSHs with mspr:pro', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
' <mspr:pro>UGxheXJlYWR5</mspr:pro>',
'</ContentProtection>',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.microsoft.playready',
[], // key IDs
buildInitData([
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=',
])),
]);
await testDashParser(source, expected);
});
it('extracts embedded PSSHs and prefer cenc:pssh over mspr:pro', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
' <cenc:pssh>bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5</cenc:pssh>',
' <mspr:pro>ZmFrZSBQbGF5cmVhZHkgUFJP</mspr:pro>',
'</ContentProtection>',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.microsoft.playready',
[], // key IDs
buildInitData(['bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5'])),
]);
await testDashParser(source, expected);
});
it('assumes all known key systems for generic CENC', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" />',
], [], []);
// The order does not matter here, so use arrayContaining.
// NOTE: the buildDrmInfo calls here specify no init data
const drmInfos = jasmine.arrayContaining([
buildDrmInfo('com.widevine.alpha'),
buildDrmInfo('com.microsoft.playready'),
buildDrmInfo('com.adobe.primetime'),
]);
const expected = buildExpectedManifest(
/** @type {!Array.<shaka.extern.DrmInfo>} */(drmInfos),
[], // key IDs
);
await testDashParser(source, expected);
});
it('assumes all known key systems when ignoreDrmInfo is set', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">',
' <cenc:pssh>ZmFrZSBXaWRldmluZSBQU1NI</cenc:pssh>',
'</ContentProtection>',
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
' <cenc:pssh>bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcm</cenc:pssh>',
'</ContentProtection>',
], [], []);
// The order does not matter here, so use arrayContaining.
// NOTE: the buildDrmInfo calls here specify no init data
const drmInfos = jasmine.arrayContaining([
buildDrmInfo('com.widevine.alpha'),
buildDrmInfo('com.microsoft.playready'),
buildDrmInfo('com.adobe.primetime'),
]);
const expected = buildExpectedManifest(
/** @type {!Array.<shaka.extern.DrmInfo>} */(drmInfos),
[]); // key IDs
await testDashParser(source, expected, /* ignoreDrmInfo= */ true);
});
it('parses key IDs when ignoreDrmInfo flag is set', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [], []);
const keyIds = [
// Representation 1 & 2 key ID deduplicated in DrmInfo
'deadbeeffeedbaadf00d000008675309',
];
const variantKeyIds = [
// Representation 1 key ID
'deadbeeffeedbaadf00d000008675309',
// Representation 2 key ID
'deadbeeffeedbaadf00d000008675309',
];
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha', keyIds),
// PlayReady has two associated UUIDs, so it appears twice.
buildDrmInfo('com.microsoft.playready', keyIds),
buildDrmInfo('com.microsoft.playready', keyIds),
buildDrmInfo('com.adobe.primetime', keyIds),
], variantKeyIds);
await testDashParser(source, expected, /* ignoreDrmInfo= */ true);
});
it('inherits PSSH from generic CENC into all key systems', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc">',
' <cenc:pssh>b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs</cenc:pssh>',
'</ContentProtection>',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" />',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha',
[], // key IDs
buildInitData(['b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs'])),
buildDrmInfo('com.microsoft.playready',
[], // key IDs
buildInitData(['b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs'])),
]);
await testDashParser(source, expected);
});
it('lets key systems override generic PSSH', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc">',
' <cenc:pssh>b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs</cenc:pssh>',
'</ContentProtection>',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">',
' <cenc:pssh>',
' VGltZSBpcyBhbiBpbGx1c2lvbi4gTHVuY2h0aW1lIGRvdWJseSBzby4=',
' </cenc:pssh>',
'</ContentProtection>',
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" />',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha',
[], // key IDs
buildInitData(
['VGltZSBpcyBhbiBpbGx1c2lvbi4gTHVuY2h0aW1lIGRvdWJseSBzby4='])),
buildDrmInfo('com.microsoft.playready',
[], // key IDs
buildInitData(['b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs'])),
]);
await testDashParser(source, expected);
});
it('ignores custom or unknown schemes', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:feedbaad-f00d-2bee-baad-d00d00000000" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
'<ContentProtection',
' schemeIdUri="http://example.com/drm" />',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha'),
]);
await testDashParser(source, expected);
});
it('inserts a placeholder for unrecognized schemes', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:feedbaad-f00d-2bee-baad-d00d00000000" />',
'<ContentProtection',
' schemeIdUri="http://example.com/drm" />',
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [], []);
const expected = buildExpectedManifest([
// placeholder: only unrecognized schemes found
buildDrmInfo('', [
// Representation 1 & 2 key ID deduplicated in DrmInfo
'deadbeeffeedbaadf00d000008675309',
]),
],
[
// Representation 1 key ID
'deadbeeffeedbaadf00d000008675309',
// Representation 2 key ID
'deadbeeffeedbaadf00d000008675309',
]);
await testDashParser(source, expected);
});
it('can specify ContentProtection in Representation only', async () => {
const source = buildManifestText([
// AdaptationSet lines
], [
// Representation 1 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
], [
// Representation 2 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
]);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha'),
]);
await testDashParser(source, expected);
});
it('still keeps per-Representation key IDs when merging', async () => {
const source = buildManifestText([
// AdaptationSet lines
], [
// Representation 1 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [
// Representation 2 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-000004390116" />',
]);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha', [
// Representation 1 key ID
'deadbeeffeedbaadf00d000008675309',
// Representation 2 key ID
'baadf00dfeeddeafbeef000004390116',
]),
]);
await testDashParser(source, expected);
});
it('parses key IDs from non-cenc in Representation', async () => {
const source = buildManifestText([
// AdaptationSet lines
], [
// Representation 1 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [
// Representation 2 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-000004390116" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-000004390116" />',
]);
const keyIds = [
// Representation 1 key ID
'deadbeeffeedbaadf00d000008675309',
// Representation 2 key ID
'baadf00dfeeddeafbeef000004390116',
];
const expected = buildExpectedManifest([
buildDrmInfo('com.microsoft.playready', keyIds),
buildDrmInfo('com.widevine.alpha', keyIds),
]);
await testDashParser(source, expected);
});
it('parses key IDs from non-cenc in AdaptationSet', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
], [], []);
const keyIds = [
// Representation 1 & 2 key ID deduplicated in DrmInfo
'deadbeeffeedbaadf00d000008675309',
];
const variantKeyIds = [
// Representation 1 key ID
'deadbeeffeedbaadf00d000008675309',
// Representation 2 key ID
'deadbeeffeedbaadf00d000008675309',
];
const expected = buildExpectedManifest([
buildDrmInfo('com.microsoft.playready', keyIds),
buildDrmInfo('com.widevine.alpha', keyIds),
], variantKeyIds);
await testDashParser(source, expected);
});
it('ignores elements missing @schemeIdUri', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
], [], []);
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha'),
]);
await testDashParser(source, expected);
});
it('handles non-default namespace names', async () => {
const source = [
'<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011"',
' xmlns:foo="urn:mpeg:cenc:2013">',
' <Period duration="PT30S">',
' <SegmentTemplate media="s.mp4" duration="2" />',
' <AdaptationSet mimeType="video/mp4" codecs="avc1.4d401f">',
' <ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">',
' <foo:pssh>b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs</foo:pssh>',
' </ContentProtection>',
' <Representation bandwidth="50" width="576" height="432" />',
' <Representation bandwidth="100" width="576" height="432" />',
' </AdaptationSet>',
' </Period>',
'</MPD>',
].join('\n');
const expected = buildExpectedManifest([
buildDrmInfo('com.widevine.alpha',
[], // key IDs
buildInitData(['b25lIGhlYWRlciB0byBydWxlIHRoZW0gYWxs'])),
]);
await testDashParser(source, expected);
});
it('fails for no schemes common', async () => {
const source = buildManifestText([
// AdaptationSet lines
], [
// Representation 1 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" />',
], [
// Representation 2 lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
]);
const expected = new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_NO_COMMON_KEY_SYSTEM);
await Dash.testFails(source, expected);
});
it('fails for invalid PSSH encoding', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">',
' <cenc:pssh>foobar!</cenc:pssh>',
'</ContentProtection>',
], [], []);
const expected = new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_PSSH_BAD_ENCODING);
await Dash.testFails(source, expected);
});
it('fails for conflicting default key IDs', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"',
' cenc:default_KID="DEADBEEF-FEED-BAAD-F00D-000008675309" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-000004390116" />',
], [], []);
const expected = new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_CONFLICTING_KEY_IDS);
await Dash.testFails(source, expected);
});
it('fails for multiple key IDs', async () => {
const source = buildManifestText([
// AdaptationSet lines
'<ContentProtection',
' schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"',
' cenc:default_KID="BAADF00D-FEED-DEAF-BEEF-000004390116 foobar" />',
'<ContentProtection',
' schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" />',
], [], []);
const expected = new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_MULTIPLE_KEY_IDS_NOT_SUPPORTED);
await Dash.testFails(source, expected);
});
describe('getWidevineLicenseUrl', () => {
it('valid ms:laurl node', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml([
'<test xmlns:ms="urn:microsoft">',
' <ms:laurl licenseUrl="www.example.com"></ms:laurl>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getWidevineLicenseUrl(input);
expect(actual).toBe('www.example.com');
});
it('ms:laurl without license url', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml([
'<test xmlns:ms="urn:microsoft">',
' <ms:laurl></ms:laurl>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getWidevineLicenseUrl(input);
expect(actual).toBe('');
});
it('no ms:laurl node', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml('<test></test>'),
};
const actual = ContentProtection.getWidevineLicenseUrl(input);
expect(actual).toBe('');
});
});
describe('getClearKeyLicenseUrl', () => {
it('valid clearkey:Laurl node', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml([
'<test xmlns:clearkey="http://dashif.org/guidelines/clearKey">',
' <clearkey:Laurl ',
' Lic_type="EME-1.0">www.example.com</clearkey:Laurl>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getClearKeyLicenseUrl(input);
expect(actual).toBe('www.example.com');
});
it('clearkey:Laurl without license url', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml([
'<test xmlns:clearkey="http://dashif.org/guidelines/clearKey">',
' <clearkey:Laurl Lic_type="EME-1.0"></clearkey:Laurl>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getClearKeyLicenseUrl(input);
expect(actual).toBe('');
});
it('no clearkey:Laurl node', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml('<test></test>'),
};
const actual = ContentProtection.getClearKeyLicenseUrl(input);
expect(actual).toBe('');
});
});
describe('getPlayReadyLicenseURL', () => {
it('mspro', () => {
const laurl = [
'<WRMHEADER>',
' <DATA>',
' <LA_URL>www.example.com</LA_URL>',
' </DATA>',
'</WRMHEADER>',
].join('\n');
const laurlCodes = laurl.split('').map((c) => {
return c.charCodeAt();
});
const prBytes = new Uint16Array([
// pr object size (in num bytes).
// + 10 for PRO size, count, and type
laurl.length * 2 + 10, 0,
// record count
1,
// type
ContentProtection.PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT,
// record size (in num bytes)
laurl.length * 2,
// value
].concat(laurlCodes));
const encodedPrObject = shaka.util.Uint8ArrayUtils.toBase64(prBytes);
const input = {
init: null,
keyId: null,
schemeUri: '',
node:
strToXml([
'<test xmlns:mspr="urn:microsoft:playready">',
' <mspr:pro>' + encodedPrObject + '</mspr:pro>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getPlayReadyLicenseUrl(input);
expect(actual).toBe('www.example.com');
});
it('no mspro', () => {
const input = {
init: null,
keyId: null,
schemeUri: '',
node: strToXml('<test></test>'),
};
const actual = ContentProtection.getPlayReadyLicenseUrl(input);
expect(actual).toBe('');
});
});
});