streaming_protection_controllers_ProtectionKeyController.js
/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2013, Dash Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import CommonEncryption from './../CommonEncryption.js';
import KeySystemClearKey from './../drm/KeySystemClearKey.js';
import KeySystemW3CClearKey from './../drm/KeySystemW3CClearKey.js';
import KeySystemWidevine from './../drm/KeySystemWidevine.js';
import KeySystemPlayReady from './../drm/KeySystemPlayReady.js';
import DRMToday from './../servers/DRMToday.js';
import PlayReady from './../servers/PlayReady.js';
import Widevine from './../servers/Widevine.js';
import ClearKey from './../servers/ClearKey.js';
import ProtectionConstants from '../../constants/ProtectionConstants.js';
import FactoryMaker from '../../../core/FactoryMaker.js';
import KeySystemMetadata from '../vo/KeySystemMetadata.js';
/**
* @module ProtectionKeyController
* @ignore
* @description Media protection key system functionality that can be modified/overridden by applications
*/
function ProtectionKeyController() {
let context = this.context;
let instance,
debug,
logger,
keySystems,
BASE64,
settings,
clearkeyKeySystem,
clearkeyW3CKeySystem;
function setConfig(config) {
if (!config) {
return;
}
if (config.debug) {
debug = config.debug;
logger = debug.getLogger(instance);
}
if (config.BASE64) {
BASE64 = config.BASE64;
}
if (config.settings) {
settings = config.settings
}
}
function initialize() {
keySystems = [];
let keySystem;
// PlayReady
keySystem = KeySystemPlayReady(context).getInstance({BASE64: BASE64, settings: settings});
keySystems.push(keySystem);
// Widevine
keySystem = KeySystemWidevine(context).getInstance({BASE64: BASE64});
keySystems.push(keySystem);
// ClearKey
keySystem = KeySystemClearKey(context).getInstance({BASE64: BASE64});
keySystems.push(keySystem);
clearkeyKeySystem = keySystem;
// W3C ClearKey
keySystem = KeySystemW3CClearKey(context).getInstance({BASE64: BASE64, debug: debug});
keySystems.push(keySystem);
clearkeyW3CKeySystem = keySystem;
}
/**
* Returns a prioritized list of key systems supported
* by this player (not necessarily those supported by the
* user agent)
*
* @returns {Array.<KeySystem>} a prioritized
* list of key systems
* @memberof module:ProtectionKeyController
* @instance
*/
function getKeySystems() {
return keySystems;
}
/**
* Sets the prioritized list of key systems to be supported
* by this player.
*
* @param {Array.<KeySystem>} newKeySystems the new prioritized
* list of key systems
* @memberof module:ProtectionKeyController
* @instance
*/
function setKeySystems(newKeySystems) {
keySystems = newKeySystems;
}
/**
* Returns the key system associated with the given key system string
* name (i.e. 'org.w3.clearkey')
*
* @param {string} systemString the system string
* @returns {KeySystem|null} the key system
* or null if no supported key system is associated with the given key
* system string
* @memberof module:ProtectionKeyController
* @instance
*/
function getKeySystemBySystemString(systemString) {
for (let i = 0; i < keySystems.length; i++) {
if (keySystems[i].systemString === systemString) {
return keySystems[i];
}
}
return null;
}
/**
* Determines whether the given key system is ClearKey. This is
* necessary because the EME spec defines ClearKey and its method
* for providing keys to the key session; and this method has changed
* between the various API versions. Our EME-specific ProtectionModels
* must know if the system is ClearKey so that it can format the keys
* according to the particular spec version.
*
* @param {Object} keySystem the key
* @returns {boolean} true if this is the ClearKey key system, false
* otherwise
* @memberof module:ProtectionKeyController
* @instance
*/
function isClearKey(keySystem) {
return (keySystem === clearkeyKeySystem || keySystem === clearkeyW3CKeySystem);
}
/**
* Check equality of initData array buffers.
*
* @param {ArrayBuffer} initData1 - first initData
* @param {ArrayBuffer} initData2 - second initData
* @returns {boolean} true if the initData arrays are equal in size and
* contents, false otherwise
* @memberof module:ProtectionKeyController
* @instance
*/
function initDataEquals(initData1, initData2) {
if (initData1.byteLength === initData2.byteLength) {
let data1 = new Uint8Array(initData1);
let data2 = new Uint8Array(initData2);
for (let j = 0; j < data1.length; j++) {
if (data1[j] !== data2[j]) {
return false;
}
}
return true;
}
return false;
}
/**
* Returns a set of supported key systems and CENC initialization data
* from the given array of ContentProtection elements. Only
* key systems that are supported by this player will be returned.
* Key systems are returned in priority order (highest first).
*
* @param {Array.<Object>} contentProtectionElements - array of content protection elements parsed
* from the manifest
* @param {ProtectionData} applicationSpecifiedProtectionData user specified protection data - license server url etc
* supported by the content
* @param {string} sessionType session type
* @returns {Array.<Object>} array of objects indicating which supported key
* systems were found. Empty array is returned if no supported key systems were found
* @memberof module:ProtectionKeyController
* @instance
*/
function getSupportedKeySystemMetadataFromContentProtection(contentProtectionElements, applicationSpecifiedProtectionData, sessionType) {
let contentProtectionElement, keySystem, ksIdx, cpIdx;
let supportedKS = [];
if (!contentProtectionElements || !contentProtectionElements.length) {
return supportedKS
}
const mp4ProtectionElement = CommonEncryption.findMp4ProtectionElement(contentProtectionElements);
for (ksIdx = 0; ksIdx < keySystems.length; ksIdx++) {
keySystem = keySystems[ksIdx];
// Get protection data that applies for current key system
const protData = _getProtDataForKeySystem(keySystem.systemString, applicationSpecifiedProtectionData);
for (cpIdx = 0; cpIdx < contentProtectionElements.length; cpIdx++) {
contentProtectionElement = contentProtectionElements[cpIdx];
if (contentProtectionElement.schemeIdUri.toLowerCase() === keySystem.schemeIdURI) {
// Look for DRM-specific ContentProtection
let initData = keySystem.getInitData(contentProtectionElement, mp4ProtectionElement);
const keySystemMetadata = new KeySystemMetadata({
ks: keySystems[ksIdx],
keyId: contentProtectionElement.keyId,
initData: initData,
protData: protData,
cdmData: keySystem.getCDMData(protData ? protData.cdmData : null),
sessionId: _getSessionId(protData, contentProtectionElement),
sessionType: _getSessionType(protData, sessionType)
})
if (protData) {
supportedKS.unshift(keySystemMetadata);
} else {
supportedKS.push(keySystemMetadata);
}
}
}
}
return supportedKS;
}
/**
* Returns key systems supported by this player for the given PSSH
* initializationData. Key systems are returned in priority order
* (highest priority first)
*
* @param {ArrayBuffer} initData Concatenated PSSH data for all DRMs
* supported by the content
* @param {ProtectionData} protDataSet user specified protection data - license server url etc
* supported by the content
* @param {string} default session type
* @returns {Array.<Object>} array of objects indicating which supported key
* systems were found. Empty array is returned if no
* supported key systems were found
* @memberof module:ProtectionKeyController
* @instance
*/
function getSupportedKeySystemMetadataFromSegmentPssh(initData, protDataSet, sessionType) {
let supportedKS = [];
let pssh = CommonEncryption.parsePSSHList(initData);
let ks, keySystemString;
for (let ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) {
ks = keySystems[ksIdx];
keySystemString = ks.systemString;
// Get protection data that applies for current key system
const protData = _getProtDataForKeySystem(keySystemString, protDataSet);
if (ks.uuid in pssh) {
supportedKS.push({
ks: ks,
initData: pssh[ks.uuid],
protData: protData,
cdmData: ks.getCDMData(protData ? protData.cdmData : null),
sessionId: _getSessionId(protData),
sessionType: _getSessionType(protData, sessionType)
});
}
}
return supportedKS;
}
/**
* Returns the license server implementation data that should be used for this request.
*
* @param {KeySystem} keySystem the key system
* associated with this license request
* @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
* {@link https://w3c.github.io/encrypted-media/#idl-def-MediaKeyMessageType|here}.
* @returns {LicenseServer|null} the license server
* implementation that should be used for this request or null if the player should not
* pass messages of the given type to a license server
* @memberof module:ProtectionKeyController
* @instance
*
*/
function getLicenseServerModelInstance(keySystem, protData, messageType) {
// Our default server implementations do not do anything with "license-release" or
// "individualization-request" messages, so we just send a success event
if (messageType === ProtectionConstants.MEDIA_KEY_MESSAGE_TYPES.LICENSE_RELEASE || messageType === ProtectionConstants.MEDIA_KEY_MESSAGE_TYPES.INDIVIDUALIZATION_REQUEST) {
return null;
}
let licenseServerData = null;
if (protData && protData.hasOwnProperty('drmtoday')) {
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) {
licenseServerData = PlayReady(context).getInstance();
} else if (keySystem.systemString === ProtectionConstants.CLEARKEY_KEYSTEM_STRING) {
licenseServerData = ClearKey(context).getInstance();
}
return licenseServerData;
}
/**
* Allows application-specific retrieval of ClearKey keys.
*
* @param {KeySystem} clearkeyKeySystem They exact ClearKey System to be used
* @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
* the request or null if no keys can be returned by this function
* @memberof module:ProtectionKeyController
* @instance
*/
function processClearKeyLicenseRequest(clearkeyKeySystem, protData, message) {
try {
return clearkeyKeySystem.getClearKeysFromProtectionData(protData, message);
} catch (error) {
logger.error('Failed to retrieve clearkeys from ProtectionData');
return null;
}
}
function setProtectionData(protectionDataSet) {
var getProtectionData = function (keySystemString) {
var protData = null;
if (protectionDataSet) {
protData = (keySystemString in protectionDataSet) ? protectionDataSet[keySystemString] : null;
}
return protData;
};
for (var i = 0; i < keySystems.length; i++) {
var keySystem = keySystems[i];
if (keySystem.hasOwnProperty('init')) {
keySystem.init(getProtectionData(keySystem.systemString));
}
}
}
function _getProtDataForKeySystem(systemString, protDataSet) {
if (!protDataSet) {
return null;
}
return (systemString in protDataSet) ? protDataSet[systemString] : null;
}
function _getSessionId(protData, cp) {
// Get sessionId from protectionData or from manifest (ContentProtection)
if (protData && protData.sessionId) {
return protData.sessionId;
} else if (cp && cp.sessionId) {
return cp.sessionId;
}
return null;
}
function _getSessionType(protData, sessionType) {
return (protData && protData.sessionType) ? protData.sessionType : sessionType;
}
instance = {
getKeySystemBySystemString,
getKeySystems,
getLicenseServerModelInstance,
getSupportedKeySystemMetadataFromContentProtection,
getSupportedKeySystemMetadataFromSegmentPssh,
initDataEquals,
initialize,
isClearKey,
processClearKeyLicenseRequest,
setConfig,
setKeySystems,
setProtectionData,
};
return instance;
}
ProtectionKeyController.__dashjs_factory_name = 'ProtectionKeyController';
export default FactoryMaker.getSingletonFactory(ProtectionKeyController);