Skip to content

Commit

Permalink
Get PlayReady PSSH from <mspr:pro> in DASH (#2106)
Browse files Browse the repository at this point in the history
Creates "pssh" init data based on the <mspr:pro> element in the DASH manifest.

Closes #2058
  • Loading branch information
Álvaro Velad Galván authored and TheModMaker committed Aug 23, 2019
1 parent 47db0cf commit 5a5864c
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 2 deletions.
34 changes: 33 additions & 1 deletion lib/dash/content_protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ goog.require('shaka.log');
goog.require('shaka.util.BufferUtils');
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');

Expand Down Expand Up @@ -321,6 +322,36 @@ shaka.dash.ContentProtection = class {
return ContentProtection.getLaurl_(rootElement);
}

/**
* Gets a PlayReady initData from a content protection element
* containing a PlayReady Pro Object
*
* @param {shaka.dash.ContentProtection.Element} element
* @return {?Array.<shaka.extern.InitDataOverride>}
* @private
*/
static getInitDataFromPro_(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.
*
Expand All @@ -346,7 +377,8 @@ shaka.dash.ContentProtection = class {
!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) {
Expand Down
40 changes: 40 additions & 0 deletions lib/util/pssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,44 @@ shaka.util.Pssh = class {
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}
*/
static createPssh(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;
}
};
36 changes: 35 additions & 1 deletion test/dash/dash_parser_content_protection_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ describe('DashParser ContentProtection', () => {
adaptationSetLines, representation1Lines, representation2Lines) {
const template = [
'<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011"',
' xmlns:cenc="urn:mpeg:cenc:2013">',
' 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">',
Expand Down Expand Up @@ -299,6 +300,39 @@ describe('DashParser ContentProtection', () => {
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', [], [
'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', [], [
'bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5',
]),
]);
await testDashParser(source, expected);
});

it('assumes all known key systems for generic CENC', async () => {
const source = buildManifestText([
// AdaptationSet lines
Expand Down

0 comments on commit 5a5864c

Please sign in to comment.