Skip to content

Commit

Permalink
feat(dash): Construct ClearKey PSSH based on MPD ContentProtection (#…
Browse files Browse the repository at this point in the history
…4104)

This PR parses default_KID from ContentProtection in the manifest and constructs a PSSH box from it to feed to the ClearKey CDM only if clear key content is detected.

Example: 

```
<ContentProtection
    schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"
    cenc:default_KID="9eb4050d-e44b-4802-932e-27d75083e266" />
<ContentProtection
    value="ClearKey1.0" schemeIdUri="urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e">
         <clearkey:Laurl Lic_type="EME-1.0">https://drm-clearkey-testvectors.axtest.net/AcquireLicense</clearkey:Laurl>
 </ContentProtection>
```

PSSH is based on https://www.w3.org/TR/eme-initdata-cenc/

Tested content: 
https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd
  • Loading branch information
sr1990 committed Apr 13, 2022
1 parent 85ee031 commit b83b412
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 9 deletions.
58 changes: 54 additions & 4 deletions lib/dash/content_protection.js
Expand Up @@ -68,7 +68,7 @@ shaka.dash.ContentProtection = class {

if (parsedNonCenc.length) {
drmInfos = ContentProtection.convertElements_(
defaultInit, parsedNonCenc, keySystemsByURI);
defaultInit, parsedNonCenc, keySystemsByURI, keyIds);

// If there are no drmInfos after parsing, then add a dummy entry.
// This may be removed in parseKeyIds.
Expand Down Expand Up @@ -356,7 +356,41 @@ shaka.dash.ContentProtection = class {
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
]);
const pssh = shaka.util.Pssh.createPssh(data, systemId);
const keyIds = new Set();
const psshVersion = 0;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
return [
{
initData: pssh,
initDataType: 'cenc',
keyId: element.keyId,
},
];
}

/**
* Creates ClearKey initData from Default_KID value retrieved from previously
* parsed ContentProtection tag.
* @param {shaka.dash.ContentProtection.Element} element
* @param {!Set.<string>} keyIds
* @return {?Array.<shaka.extern.InitDataOverride>}
* @private
*/
static getInitDataClearKey_(element, keyIds) {
if (keyIds.size == 0) {
return null;
}

const systemId = new Uint8Array([
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
]);
const data = new Uint8Array([]);
const psshVersion = 1;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);

return [
{
initData: pssh,
Expand All @@ -372,10 +406,11 @@ shaka.dash.ContentProtection = class {
* @param {Array.<shaka.extern.InitDataOverride>} defaultInit
* @param {!Array.<shaka.dash.ContentProtection.Element>} elements
* @param {!Object.<string, string>} keySystemsByURI
* @param {!Set.<string>} keyIds
* @return {!Array.<shaka.extern.DrmInfo>}
* @private
*/
static convertElements_(defaultInit, elements, keySystemsByURI) {
static convertElements_(defaultInit, elements, keySystemsByURI, keyIds) {
const ContentProtection = shaka.dash.ContentProtection;
const ManifestParserUtils = shaka.util.ManifestParserUtils;
const licenseUrlParsers = ContentProtection.licenseUrlParsers_;
Expand All @@ -391,7 +426,14 @@ shaka.dash.ContentProtection = class {
'Init data must be null or non-empty.');

const proInitData = ContentProtection.getInitDataFromPro_(element);
const initData = element.init || defaultInit || proInitData;
let clearKeyInitData = null;
if (element.schemeUri ===
shaka.dash.ContentProtection.ClearKeySchemeUri_) {
clearKeyInitData =
ContentProtection.getInitDataClearKey_(element, keyIds);
}
const initData = element.init || defaultInit || proInitData ||
clearKeyInitData;
const info = ManifestParserUtils.createDrmInfo(keySystem, initData);
const licenseParser = licenseUrlParsers.get(keySystem);
if (licenseParser) {
Expand Down Expand Up @@ -607,3 +649,11 @@ shaka.dash.ContentProtection.CencNamespaceUri_ = 'urn:mpeg:cenc:2013';
*/
shaka.dash.ContentProtection.ClearKeyNamespaceUri_ =
'http://dashif.org/guidelines/clearKey';


/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.ClearKeySchemeUri_ =
'urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e';
5 changes: 4 additions & 1 deletion lib/hls/hls_parser.js
Expand Up @@ -2529,7 +2529,10 @@ shaka.hls.HlsParser = class {
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
]);
const pssh = shaka.util.Pssh.createPssh(data, systemId);
const keyIds = new Set();
const psshVersion = 0;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
'com.microsoft.playready', [
{initDataType: 'cenc', initData: pssh},
Expand Down
27 changes: 23 additions & 4 deletions lib/util/pssh.js
Expand Up @@ -90,16 +90,21 @@ shaka.util.Pssh = class {
}

/**
* Creates a pssh blob from the given system ID and data.
* Creates a pssh blob from the given system ID, data, keyIds and version.
*
* @param {!Uint8Array} data
* @param {!Uint8Array} systemId
* @param {!Set.<string>} keyIds
* @param {number} version
* @return {!Uint8Array}
*/
static createPssh(data, systemId) {
static createPssh(data, systemId, keyIds, version) {
goog.asserts.assert(systemId.byteLength == 16, 'Invalid system ID length');
const dataLength = data.length;
const psshSize = 0x4 + 0x4 + 0x4 + systemId.length + 0x4 + dataLength;
let psshSize = 0x4 + 0x4 + 0x4 + systemId.length + 0x4 + dataLength;
if (version > 0) {
psshSize += 0x4 + (16 * keyIds.size);
}

/** @type {!Uint8Array} */
const psshBox = new Uint8Array(psshSize);
Expand All @@ -111,10 +116,24 @@ shaka.util.Pssh = class {
byteCursor += 0x4;
psshData.setUint32(byteCursor, 0x70737368); // 'pssh'
byteCursor += 0x4;
psshData.setUint32(byteCursor, 0); // flags
(version < 1) ? psshData.setUint32(byteCursor, 0) :
psshData.setUint32(byteCursor, 0x01000000); // version + flags
byteCursor += 0x4;
psshBox.set(systemId, byteCursor);
byteCursor += systemId.length;

// if version > 0, add KID count and kid values.
if (version > 0) {
psshData.setUint32(byteCursor, keyIds.size); // KID_count
byteCursor += 0x4;
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
for (const keyId of keyIds) {
const KID = Uint8ArrayUtils.fromHex(keyId);
psshBox.set(KID, byteCursor);
byteCursor += KID.length;
}
}

psshData.setUint32(byteCursor, dataLength);
byteCursor += 0x4;
psshBox.set(data, byteCursor);
Expand Down

0 comments on commit b83b412

Please sign in to comment.