diff --git a/lib/dash/content_protection.js b/lib/dash/content_protection.js index 8e3fe75b39f..cd6428a6965 100644 --- a/lib/dash/content_protection.js +++ b/lib/dash/content_protection.js @@ -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. @@ -366,16 +366,48 @@ shaka.dash.ContentProtection = class { ]; } + /** + * Creates ClearKey initData from Default_KID value. + * @param {shaka.dash.ContentProtection.Element} element + * @param {!Set.} keyIds + * @return {?Array.} + * @private + */ + static getInitDataClearKey_(element, keyIds) { + if (keyIds.size == 0) { + return null; + } + + const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + const [first] = keyIds; + const data = Uint8ArrayUtils.fromHex(first); + + const systemId = new Uint8Array([ + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + ]); + + const pssh = shaka.util.Pssh.createPsshClearKey(data, systemId); + return [ + { + initData: pssh, + initDataType: 'cenc', + keyId: element.keyId, + }, + ]; + } + /** * Creates DrmInfo objects from the given element. * * @param {Array.} defaultInit * @param {!Array.} elements * @param {!Object.} keySystemsByURI + * @param {!Set.} keyIds * @return {!Array.} * @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_; @@ -391,7 +423,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) { @@ -607,3 +646,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'; diff --git a/lib/util/pssh.js b/lib/util/pssh.js index 0f829fb09d2..5512f532ddb 100644 --- a/lib/util/pssh.js +++ b/lib/util/pssh.js @@ -124,6 +124,49 @@ shaka.util.Pssh = class { return psshBox; } + /** + * Creates a ClearKey cenc pssh blob from the given system ID and data. + * Data will contain one key id. + * Based on https://www.w3.org/TR/eme-initdata-cenc/ + * @param {!Uint8Array} data + * @param {!Uint8Array} systemId + * @return {!Uint8Array} + */ + static createPsshClearKey(data, systemId) { + goog.asserts.assert(systemId.byteLength == 16, 'Invalid system ID length'); + const dataLength = data.length; + const psshSize = 0x4 + // pssh size (4 bytes) + 0x4 + // 'pssh' (4 bytes) + 0x4 + // version (1 byte) + flags (3 bytes) + systemId.length + // system id (16 bytes) + 0x4 + // KID count (4 bytes) + dataLength + // KID value (16 bytes) + 0x4; // size of data (4 bytes) + + /** @type {!Uint8Array} */ + const psshBox = new Uint8Array(psshSize); + /** @type {!DataView} */ + const psshData = shaka.util.BufferUtils.toDataView(psshBox); + + let byteCursor = 0; + psshData.setUint32(byteCursor, psshSize); + byteCursor += 0x4; + psshData.setUint32(byteCursor, 0x70737368); // 'pssh' + byteCursor += 0x4; + psshData.setUint32(byteCursor, 0x01000000); // version = 1 + byteCursor += 0x4; + psshBox.set(systemId, byteCursor); + byteCursor += systemId.length; + psshData.setUint32(byteCursor, 0x00000001); // KID_count + byteCursor += 0x4; + psshBox.set(data, byteCursor); // add kid + byteCursor += dataLength; + psshData.setUint32(byteCursor, 0x00000000); + byteCursor += 0x4; + + goog.asserts.assert(byteCursor === psshSize, 'PSSH invalid length.'); + return psshBox; + } /** * Normalise the initData array. This is to apply browser specific