diff --git a/lib/dash/content_protection.js b/lib/dash/content_protection.js index ea5c335e6a..e733da5f35 100644 --- a/lib/dash/content_protection.js +++ b/lib/dash/content_protection.js @@ -21,6 +21,7 @@ goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.util.Error'); goog.require('shaka.util.ManifestParserUtils'); +goog.require('shaka.util.Pssh'); goog.require('shaka.util.Uint8ArrayUtils'); goog.require('shaka.util.XmlUtils'); @@ -442,6 +443,36 @@ shaka.dash.ContentProtection.getPlayReadyLicenseUrl = function(element) { }; +/** + * Gets a PlayReady initData from a content protection element + * containing a PlayReady Pro Object + * + * @param {shaka.dash.ContentProtection.Element} element + * @return {?Array.} + * @private + */ +shaka.dash.ContentProtection.getInitDataFromPro_ = function(element) { + const proNode = shaka.util.XmlUtils.findChildNS( + element.node, 'urn:microsoft:playready', 'pro'); + if (!proNode) { + return null; + } + const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + const data = Uint8ArrayUtils.fromBase64(proNode.textContent); + const systemId = new Uint8Array([ + 0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, + 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95, + ]); + const pssh = shaka.util.Pssh.createPssh(data, systemId); + return [ + { + initData: pssh, + initDataType: 'cenc', + keyId: element.keyId, + }, + ]; +}; + /** * Creates DrmInfo objects from the given element. * @@ -468,7 +499,8 @@ shaka.dash.ContentProtection.convertElements_ = function( !element.init || element.init.length, 'Init data must be null or non-empty.'); - const initData = element.init || defaultInit; + const proInitData = ContentProtection.getInitDataFromPro_(element); + const initData = element.init || defaultInit || proInitData; const info = ManifestParserUtils.createDrmInfo(keySystem, initData); const licenseParser = licenseUrlParsers.get(keySystem); if (licenseParser) { diff --git a/lib/util/pssh.js b/lib/util/pssh.js index b05da20b94..0bf586bd2e 100644 --- a/lib/util/pssh.js +++ b/lib/util/pssh.js @@ -104,3 +104,44 @@ shaka.util.Pssh.prototype.parseBox_ = function(box) { shaka.log.warning('Mismatch between box size and data size!'); } }; + + +/** + * Creates a pssh blob from the given system ID and data. + * + * @param {!Uint8Array} data + * @param {!Uint8Array} systemId + * @return {!Uint8Array} + */ +shaka.util.Pssh.createPssh = function(data, systemId) { + const dataLength = data.length; + const psshSize = 0x4 + 0x4 + 0x4 + systemId.length + 0x4 + dataLength; + + /** @type {!ArrayBuffer} */ + const psshBoxBuffer = new ArrayBuffer(psshSize); + + /** @type {!Uint8Array} */ + const psshBox = new Uint8Array(psshBoxBuffer); + + /** @type {!DataView} */ + const psshData = new DataView(psshBoxBuffer); + + let byteCursor = 0; + psshData.setUint32(byteCursor, psshSize); + byteCursor += 0x4; + psshData.setUint32(byteCursor, 0x70737368); // 'pssh' + byteCursor += 0x4; + psshData.setUint32(byteCursor, 0); // flags + byteCursor += 0x4; + psshBox.set(systemId, byteCursor); + byteCursor += systemId.length; + psshData.setUint32(byteCursor, dataLength); + byteCursor += 0x4; + psshBox.set(data, byteCursor); + byteCursor += dataLength; + + goog.asserts.assert(byteCursor === psshSize, + 'MS PRO invalid length.'); + + return psshBox; +}; diff --git a/test/dash/dash_parser_content_protection_unit.js b/test/dash/dash_parser_content_protection_unit.js index 836881afbc..27011f8e76 100644 --- a/test/dash/dash_parser_content_protection_unit.js +++ b/test/dash/dash_parser_content_protection_unit.js @@ -68,7 +68,8 @@ describe('DashParser ContentProtection', function() { adaptationSetLines, representation1Lines, representation2Lines) { let template = [ '', + ' xmlns:cenc="urn:mpeg:cenc:2013"', + ' xmlns:mspr="urn:microsoft:playready">', ' ', ' ', ' ', @@ -298,6 +299,39 @@ describe('DashParser ContentProtection', function() { await testDashParser(source, expected); }); + it('extracts embedded PSSHs with mspr:pro', async () => { + const source = buildManifestText([ + // AdaptationSet lines + '', + ' UGxheXJlYWR5', + '', + ], [], []); + const expected = buildExpectedManifest([ + buildDrmInfo('com.microsoft.playready', [], [ + 'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=', + ]), + ]); + await testDashParser(source, expected); + }); + + it('extracts embedded PSSHs and prefer cenc:pssh over mspr:pro', async () => { + const source = buildManifestText([ + // AdaptationSet lines + '', + ' bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5', + ' ZmFrZSBQbGF5cmVhZHkgUFJP', + '', + ], [], []); + const expected = buildExpectedManifest([ + buildDrmInfo('com.microsoft.playready', [], [ + 'bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5', + ]), + ]); + await testDashParser(source, expected); + }); + it('assumes all known key systems for generic CENC', async () => { let source = buildManifestText([ // AdaptationSet lines