Skip to content

Commit

Permalink
Feature clearkey by server (#3381)
Browse files Browse the repository at this point in the history
* Initial support for clearkey server

* Check for proData when setting KeySession type
  • Loading branch information
dsilhavy authored Aug 25, 2020
1 parent 53e5b6b commit 35fc841
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 60 deletions.
12 changes: 6 additions & 6 deletions src/streaming/protection/CommonEncryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
* POSSIBILITY OF SUCH DAMAGE.
*/

/**
* @class
* @ignore
*/
/**
* @class
* @ignore
*/
class CommonEncryption {
/**
* Find and return the ContentProtection element in the given array
Expand All @@ -47,7 +47,7 @@ class CommonEncryption {
for (let i = 0; i < cpArray.length; ++i) {
let cp = cpArray[i];
if (cp.schemeIdUri.toLowerCase() === 'urn:mpeg:dash:mp4protection:2011' &&
cp.value.toLowerCase() === 'cenc')
(cp.value.toLowerCase() === 'cenc' || cp.value.toLowerCase() === 'cbcs'))
retVal = cp;
}
return retVal;
Expand Down Expand Up @@ -106,7 +106,7 @@ class CommonEncryption {
if ('pssh' in cpData) {

// Remove whitespaces and newlines from pssh text
cpData.pssh.__text = cpData.pssh.__text.replace(/\r?\n|\r/g,'').replace(/\s+/g,'');
cpData.pssh.__text = cpData.pssh.__text.replace(/\r?\n|\r/g, '').replace(/\s+/g, '');

return BASE64.decodeArray(cpData.pssh.__text).buffer;
}
Expand Down
64 changes: 45 additions & 19 deletions src/streaming/protection/controllers/ProtectionController.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,18 @@ function ProtectionController(config) {
try {
protectionModel.createKeySession(initDataForKS, protData, getSessionType(keySystem), cdmData);
} catch (error) {
eventBus.trigger(events.KEY_SESSION_CREATED, {data: null, error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + error.message)});
eventBus.trigger(events.KEY_SESSION_CREATED, {
data: null,
error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + error.message)
});
}
} else if (initData) {
protectionModel.createKeySession(initData, protData, getSessionType(keySystem), cdmData);
} else {
eventBus.trigger(events.KEY_SESSION_CREATED, {data: null, error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Selected key system is ' + (keySystem ? keySystem.systemString : null) + '. needkey/encrypted event contains no initData corresponding to that key system!')});
eventBus.trigger(events.KEY_SESSION_CREATED, {
data: null,
error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Selected key system is ' + (keySystem ? keySystem.systemString : null) + '. needkey/encrypted event contains no initData corresponding to that key system!')
});
}
}

Expand Down Expand Up @@ -351,7 +357,7 @@ function ProtectionController(config) {
protectionModel = null;
}

needkeyRetries.forEach( retryTimeout => clearTimeout(retryTimeout));
needkeyRetries.forEach(retryTimeout => clearTimeout(retryTimeout));
needkeyRetries = [];

mediaInfoArr = [];
Expand Down Expand Up @@ -420,7 +426,10 @@ function ProtectionController(config) {
for (ksIdx = 0; ksIdx < supportedKS.length; ksIdx++) {
if (keySystem === supportedKS[ksIdx].ks) {

requestedKeySystems.push({ks: supportedKS[ksIdx].ks, configs: [getKeySystemConfiguration(keySystem)]});
requestedKeySystems.push({
ks: supportedKS[ksIdx].ks,
configs: [getKeySystemConfiguration(keySystem)]
});

// Ensure that we would be granted key system access using the key
// system and codec information
Expand All @@ -447,15 +456,17 @@ function ProtectionController(config) {
break;
}
}
}
else if (keySystem === undefined) {
} else if (keySystem === undefined) {
// First time through, so we need to select a key system
keySystem = null;
pendingNeedKeyData.push(supportedKS);

// Add all key systems to our request list since we have yet to select a key system
for (let i = 0; i < supportedKS.length; i++) {
requestedKeySystems.push({ks: supportedKS[i].ks, configs: [getKeySystemConfiguration(supportedKS[i].ks)]});
requestedKeySystems.push({
ks: supportedKS[i].ks,
configs: [getKeySystemConfiguration(supportedKS[i].ks)]
});
}

let keySystemAccess;
Expand All @@ -465,7 +476,10 @@ function ProtectionController(config) {
keySystem = undefined;
eventBus.off(events.INTERNAL_KEY_SYSTEM_SELECTED, onKeySystemSelected, self);
if (!fromManifest) {
eventBus.trigger(events.KEY_SYSTEM_SELECTED, {data: null, error: new DashJSError(ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE, ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_MESSAGE + event.error)});
eventBus.trigger(events.KEY_SYSTEM_SELECTED, {
data: null,
error: new DashJSError(ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE, ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_MESSAGE + event.error)
});
}
} else {
keySystemAccess = event.data;
Expand All @@ -490,11 +504,13 @@ function ProtectionController(config) {
for (let i = 0; i < pendingNeedKeyData.length; i++) {
for (ksIdx = 0; ksIdx < pendingNeedKeyData[i].length; ksIdx++) {
if (keySystem === pendingNeedKeyData[i][ksIdx].ks) {
// For Clearkey: if parameters for generating init data was provided by the user, use them for generating
// initData and overwrite possible initData indicated in encrypted event (EME)
if (protectionKeyController.isClearKey(keySystem) && protData && protData.hasOwnProperty('clearkeys')) {
const initData = { kids: Object.keys(protData.clearkeys) };
pendingNeedKeyData[i][ksIdx].initData = new TextEncoder().encode(JSON.stringify(initData));
if (protectionKeyController.isClearKey(keySystem)) {
// For Clearkey: if parameters for generating init data was provided by the user, use them for generating
// initData and overwrite possible initData indicated in encrypted event (EME)
if (protData && protData.hasOwnProperty('clearkeys')) {
const initData = {kids: Object.keys(protData.clearkeys)};
pendingNeedKeyData[i][ksIdx].initData = new TextEncoder().encode(JSON.stringify(initData));
}
}
if (pendingNeedKeyData[i][ksIdx].sessionId) {
// Load MediaKeySession with sessionId
Expand All @@ -510,7 +526,10 @@ function ProtectionController(config) {
} else {
keySystem = undefined;
if (!fromManifest) {
eventBus.trigger(events.KEY_SYSTEM_SELECTED, {data: null, error: new DashJSError(ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE, ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_MESSAGE + 'Error selecting key system! -- ' + event.error)});
eventBus.trigger(events.KEY_SYSTEM_SELECTED, {
data: null,
error: new DashJSError(ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE, ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_MESSAGE + 'Error selecting key system! -- ' + event.error)
});
}
}
};
Expand Down Expand Up @@ -547,7 +566,7 @@ function ProtectionController(config) {
const protData = getProtData(keySystem);
const keySystemString = keySystem ? keySystem.systemString : null;
const licenseServerData = protectionKeyController.getLicenseServer(keySystem, protData, messageType);
const eventData = { sessionToken: sessionToken, messageType: messageType };
const eventData = {sessionToken: sessionToken, messageType: messageType};

// Ensure message from CDM is not empty
if (!message || message.byteLength === 0) {
Expand All @@ -565,7 +584,7 @@ function ProtectionController(config) {
// Perform any special handling for ClearKey
if (protectionKeyController.isClearKey(keySystem)) {
const clearkeys = protectionKeyController.processClearKeyLicenseRequest(keySystem, protData, message);
if (clearkeys) {
if (clearkeys) {
logger.debug('DRM: ClearKey license request handled by application!');
sendLicenseRequestCompleteEvent(eventData);
protectionModel.updateKeySession(sessionToken, clearkeys);
Expand All @@ -587,9 +606,15 @@ function ProtectionController(config) {
// TODO: Deprecated!
url = protData.laURL;
} else {
url = keySystem.getLicenseServerURLFromInitData(CommonEncryption.getPSSHData(sessionToken.initData));
if (!url) {
url = e.data.laURL;
// For clearkey use the url defined in the manifest
if (protectionKeyController.isClearKey(keySystem)) {
url = keySystem.getLicenseServerUrlFromMediaInfo(mediaInfoArr);
} else {
const psshData = CommonEncryption.getPSSHData(sessionToken.initData);
url = keySystem.getLicenseServerURLFromInitData(psshData);
if (!url) {
url = e.data.laURL;
}
}
}
// Possibly update or override the URL based on the message
Expand Down Expand Up @@ -661,6 +686,7 @@ function ProtectionController(config) {
xhr.statusText + '" (' + xhr.status + '), readyState is ' + xhr.readyState));
};

//const reqPayload = keySystem.getLicenseRequestFromMessage(message);
const reqPayload = keySystem.getLicenseRequestFromMessage(message);
const reqMethod = licenseServerData.getHTTPMethod(messageType);
const responseType = licenseServerData.getResponseType(keySystemString, messageType);
Expand Down
19 changes: 10 additions & 9 deletions src/streaming/protection/controllers/ProtectionKeyController.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,20 @@ function ProtectionKeyController() {
let keySystem;

// PlayReady
keySystem = KeySystemPlayReady(context).getInstance({ BASE64: BASE64 });
keySystem = KeySystemPlayReady(context).getInstance({BASE64: BASE64});
keySystems.push(keySystem);

// Widevine
keySystem = KeySystemWidevine(context).getInstance({ BASE64: BASE64 });
keySystem = KeySystemWidevine(context).getInstance({BASE64: BASE64});
keySystems.push(keySystem);

// ClearKey
keySystem = KeySystemClearKey(context).getInstance({ BASE64: BASE64 });
keySystem = KeySystemClearKey(context).getInstance({BASE64: BASE64});
keySystems.push(keySystem);
clearkeyKeySystem = keySystem;

// W3C ClearKey
keySystem = KeySystemW3CClearKey(context).getInstance({ BASE64: BASE64, debug: debug });
keySystem = KeySystemW3CClearKey(context).getInstance({BASE64: BASE64, debug: debug});
keySystems.push(keySystem);
clearkeyW3CKeySystem = keySystem;
}
Expand Down Expand Up @@ -202,13 +202,14 @@ function ProtectionKeyController() {
let supportedKS = [];

if (cps) {
const cencContentProtection = CommonEncryption.findCencContentProtection(cps);
for (ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) {
ks = keySystems[ksIdx];
for (cpIdx = 0; cpIdx < cps.length; ++cpIdx) {
cp = cps[cpIdx];
if (cp.schemeIdUri.toLowerCase() === ks.schemeIdURI) {
// Look for DRM-specific ContentProtection
let initData = ks.getInitData(cp);
let initData = ks.getInitData(cp, cencContentProtection);

supportedKS.push({
ks: keySystems[ksIdx],
Expand All @@ -231,7 +232,7 @@ function ProtectionKeyController() {
*
* @param {ArrayBuffer} initData Concatenated PSSH data for all DRMs
* supported by the content
* @param {ProtectionDataSet} protDataSet user specified protection data - license server url etc
* @param {ProtectionData} protDataSet user specified protection data - license server url etc
* supported by the content
* @returns {Array.<Object>} array of objects indicating which supported key
* systems were found. Empty array is returned if no
Expand Down Expand Up @@ -266,7 +267,7 @@ function ProtectionKeyController() {
*
* @param {KeySystem} keySystem the key system
* associated with this license request
* @param {ProtectionDataSet} protData protection data to use for the
* @param {ProtectionData} protData protection data to use for the
* request
* @param {string} [messageType="license-request"] the message type associated with this
* request. Supported message types can be found
Expand All @@ -288,7 +289,7 @@ function ProtectionKeyController() {

let licenseServerData = null;
if (protData && protData.hasOwnProperty('drmtoday')) {
licenseServerData = DRMToday(context).getInstance({ BASE64: BASE64 });
licenseServerData = DRMToday(context).getInstance({BASE64: BASE64});
} else if (keySystem.systemString === ProtectionConstants.WIDEVINE_KEYSTEM_STRING) {
licenseServerData = Widevine(context).getInstance();
} else if (keySystem.systemString === ProtectionConstants.PLAYREADY_KEYSTEM_STRING) {
Expand All @@ -304,7 +305,7 @@ function ProtectionKeyController() {
* Allows application-specific retrieval of ClearKey keys.
*
* @param {KeySystem} clearkeyKeySystem They exact ClearKey System to be used
* @param {ProtectionDataSet} protData protection data to use for the
* @param {ProtectionData} protData protection data to use for the
* request
* @param {ArrayBuffer} message the key message from the CDM
* @return {ClearKeyKeySet|null} the clear keys associated with
Expand Down
6 changes: 4 additions & 2 deletions src/streaming/protection/drm/KeySystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
* @param {Object} contentProtection the json-style contentProtection
* object representing the ContentProtection element parsed from the
* MPD XML document.
* @param {Object} cencContentProtection the Common Encryption content protection element or
* null if not specified.
* @returns {ArrayBuffer} EME initialization data
*/

Expand Down Expand Up @@ -117,15 +119,15 @@
* @returns {?string} The license server URL or null if URL is not available in initData
*/

/**
/**
* Returns specific CDM (custom) data.
*
* @function
* @name MediaPlayer.dependencies.protection.KeySystem#getCDMData
* @returns {ArrayBuffer} the CDM (custom) data
*/

/**
/**
* Returns MediaKeySession session ID.
*
* @function
Expand Down
Loading

0 comments on commit 35fc841

Please sign in to comment.