diff --git a/index.d.ts b/index.d.ts index 9d4a502949..8038982e4d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -45,9 +45,9 @@ declare namespace dashjs { clearMediaInfoArray(): void; - createKeySession(initData: ArrayBuffer, cdmData: Uint8Array): void; + createKeySession(ksInfo: KeySystemInfo): void; - loadKeySession(sessionId: string, initData: ArrayBuffer): void; + loadKeySession(ksInfo: KeySystemInfo): void; removeKeySession(session: SessionToken): void; @@ -63,7 +63,7 @@ declare namespace dashjs { setProtectionData(protDataSet: ProtectionDataSet): void; - getSupportedKeySystemsFromContentProtection(cps: any[]): SupportedKeySystem[]; + getSupportedKeySystemsFromContentProtection(cps: any[]): KeySystemInfo[]; getKeySystems(): KeySystem[]; @@ -1362,16 +1362,17 @@ declare namespace dashjs { getLicenseServerURLFromInitData(initData: ArrayBuffer): string | null; - getCDMData(): ArrayBuffer | null; - - getSessionId(): string | null; + getCDMData(cdmData: string | null): ArrayBuffer | null; } - export interface SupportedKeySystem { + export interface KeySystemInfo { ks: KeySystem; - initData: ArrayBuffer; - cdmData: ArrayBuffer | null; - sessionId: string | null; + sessionId?: string, + sessionType?: string, + keyId?: string, + initData?: ArrayBuffer; + cdmData?: ArrayBuffer; + protData?: ProtectionData } export interface LicenseRequest { diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index 17a0c8861b..67d23f7e92 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -1026,9 +1026,12 @@ function DashAdapter() { mediaInfo.selectionPriority = dashManifestModel.getSelectionPriority(realAdaptation); if (mediaInfo.contentProtection) { - mediaInfo.contentProtection.forEach(function (item) { - item.KID = dashManifestModel.getKID(item); - }); + // Get the default key ID and apply it to all key systems + const keyIds = mediaInfo.contentProtection.map(cp => dashManifestModel.getKID(cp)).filter(kid => kid !== null); + if (keyIds.length) { + const keyId = keyIds[0]; + mediaInfo.contentProtection.forEach(cp => { cp.keyId = keyId; }); + } } mediaInfo.isText = dashManifestModel.getIsText(realAdaptation); diff --git a/src/streaming/protection/controllers/ProtectionController.js b/src/streaming/protection/controllers/ProtectionController.js index 1afe93d93f..7b213c89d5 100644 --- a/src/streaming/protection/controllers/ProtectionController.js +++ b/src/streaming/protection/controllers/ProtectionController.js @@ -129,7 +129,7 @@ function ProtectionController(config) { // ContentProtection elements are specified at the AdaptationSet level, so the CP for audio // and video will be the same. Just use one valid MediaInfo object - let supportedKS = protectionKeyController.getSupportedKeySystemsFromContentProtection(mediaInfo.contentProtection); + let supportedKS = protectionKeyController.getSupportedKeySystemsFromContentProtection(mediaInfo.contentProtection, protDataSet, sessionType); // Reorder key systems according to priority order provided in protectionData supportedKS = supportedKS.sort((ksA, ksB) => { @@ -181,7 +181,7 @@ function ProtectionController(config) { // 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++) { - const keySystemConfiguration = _getKeySystemConfiguration(supportedKS[i].ks); + const keySystemConfiguration = _getKeySystemConfiguration(supportedKS[i]); requestedKeySystems.push({ ks: supportedKS[i].ks, configs: [keySystemConfiguration] @@ -218,7 +218,7 @@ function ProtectionController(config) { for (ksIdx = 0; ksIdx < pendingKeySystemData[i].length; ksIdx++) { if (selectedKeySystem === pendingKeySystemData[i][ksIdx].ks) { const current = pendingKeySystemData[i][ksIdx] - _loadOrCreateKeySession(protData, current) + _loadOrCreateKeySession(current) break; } } @@ -241,7 +241,7 @@ function ProtectionController(config) { * @param {array} supportedKS * @private */ - function _initiateWithExistingKeySystem(supportedKS,) { + function _initiateWithExistingKeySystem(supportedKS) { const ksIdx = supportedKS.findIndex((entry) => { return entry.ks === selectedKeySystem; }); @@ -251,29 +251,21 @@ function ProtectionController(config) { return; } - // we only need to create or load a new key session if the init data has changed - const initDataForKs = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, current.initData); - if (_isInitDataDuplicate(initDataForKs)) { - return; - } - - const protData = _getProtDataForKeySystem(selectedKeySystem); - _loadOrCreateKeySession(protData, current); + _loadOrCreateKeySession(current); } /** * Loads an existing key session if we already have a session id. Otherwise we create a new key session - * @param {object} protData * @param {object} keySystemInfo * @private */ - function _loadOrCreateKeySession(protData, keySystemInfo) { + function _loadOrCreateKeySession(keySystemInfo) { // Clearkey if (protectionKeyController.isClearKey(selectedKeySystem)) { // 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) }; + if (keySystemInfo.protData && keySystemInfo.protData.hasOwnProperty('clearkeys')) { + const initData = { kids: Object.keys(keySystemInfo.protData.clearkeys) }; keySystemInfo.initData = new TextEncoder().encode(JSON.stringify(initData)); } } @@ -281,29 +273,28 @@ function ProtectionController(config) { // Reuse existing KeySession if (keySystemInfo.sessionId) { // Load MediaKeySession with sessionId - loadKeySession(keySystemInfo.sessionId, keySystemInfo.initData); + loadKeySession(keySystemInfo); } // Create a new KeySession else if (keySystemInfo.initData !== null) { // Create new MediaKeySession with initData - createKeySession(keySystemInfo.initData, keySystemInfo.cdmData); + createKeySession(keySystemInfo); } } /** * Loads a key session with the given session ID from persistent storage. This essentially creates a new key session * - * @param {string} sessionID - * @param {string} initData + * @param {object} ksInfo * @memberof module:ProtectionController * @instance * @fires ProtectionController#KeySessionCreated * @ignore */ - function loadKeySession(sessionID, initData) { + function loadKeySession(keySystemInfo) { checkConfig(); - protectionModel.loadKeySession(sessionID, initData, _getSessionType(selectedKeySystem)); + protectionModel.loadKeySession(keySystemInfo); } /** @@ -316,29 +307,32 @@ function ProtectionController(config) { * @fires ProtectionController#KeySessionCreated * @ignore */ - function createKeySession(initData, cdmData) { - const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, initData); - const protData = _getProtDataForKeySystem(selectedKeySystem); + function createKeySession(keySystemInfo) { + const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, keySystemInfo ? keySystemInfo.initData : null); if (initDataForKS) { + // Check for duplicate key id + if (_isKeyIdDuplicate(keySystemInfo.keyId)) { + return; + } + // Check for duplicate initData if (_isInitDataDuplicate(initDataForKS)) { return; } try { - const sessionType = _getSessionType(selectedKeySystem) - protectionModel.createKeySession(initDataForKS, protData, sessionType, cdmData); + keySystemInfo.initData = initDataForKS; + protectionModel.createKeySession(keySystemInfo); } 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) }); } - } else if (initData) { - const sessionType = _getSessionType(selectedKeySystem) - protectionModel.createKeySession(initData, protData, sessionType, cdmData); + } else if (keySystemInfo && keySystemInfo.initData) { + protectionModel.createKeySession(keySystemInfo); } else { eventBus.trigger(events.KEY_SESSION_CREATED, { data: null, @@ -364,18 +358,6 @@ function ProtectionController(config) { return null; } - /** - * Returns the session type either from the protData or as defined via setSessionType() - * @param keySystem - * @return {*} - * @private - */ - function _getSessionType(keySystem) { - const protData = _getProtDataForKeySystem(keySystem); - - return (protData && protData.sessionType) ? protData.sessionType : sessionType; - } - /** * Removes all entries from the mediaInfoArr */ @@ -400,7 +382,32 @@ function ProtectionController(config) { */ function getSupportedKeySystemsFromContentProtection(cps) { checkConfig(); - return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps); + return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps, protDataSet, sessionType); + } + + /** + * Checks if a session has already created for the provided key id + * @param {string} keyId + * @return {boolean} + * @private + */ + function _isKeyIdDuplicate(keyId) { + + if (!keyId) { + return false; + } + + try { + const sessions = protectionModel.getSessions(); + for (let i = 0; i < sessions.length; i++) { + if (sessions[i].getKeyId() === keyId) { + return true; + } + } + return false; + } catch (e) { + return false; + } } /** @@ -595,13 +602,13 @@ function ProtectionController(config) { * @return {KeySystemConfiguration} * @private */ - function _getKeySystemConfiguration(keySystem) { - const protData = _getProtDataForKeySystem(keySystem); + function _getKeySystemConfiguration(keySystemData) { + const protData = keySystemData.protData; const audioCapabilities = []; const videoCapabilities = []; const audioRobustness = (protData && protData.audioRobustness && protData.audioRobustness.length > 0) ? protData.audioRobustness : robustnessLevel; const videoRobustness = (protData && protData.videoRobustness && protData.videoRobustness.length > 0) ? protData.videoRobustness : robustnessLevel; - const ksSessionType = _getSessionType(keySystem); + const ksSessionType = keySystemData.sessionType; const distinctiveIdentifier = (protData && protData.distinctiveIdentifier) ? protData.distinctiveIdentifier : 'optional'; const persistentState = (protData && protData.persistentState) ? protData.persistentState : (ksSessionType === 'temporary') ? 'optional' : 'required'; @@ -658,7 +665,7 @@ function ProtectionController(config) { // Message not destined for license server if (!licenseServerModelInstance) { - logger.debug('DRM: License server request not required for this message (type = ' + e.data.messageType + '). Session ID = ' + sessionToken.getSessionID()); + logger.debug('DRM: License server request not required for this message (type = ' + e.data.messageType + '). Session ID = ' + sessionToken.getSessionId()); _sendLicenseRequestCompleteEvent(eventData); return; } @@ -770,7 +777,7 @@ function ProtectionController(config) { const reqMethod = licenseServerData.getHTTPMethod(messageType); const responseType = licenseServerData.getResponseType(keySystemString, messageType); const timeout = protData && !isNaN(protData.httpTimeout) ? protData.httpTimeout : LICENSE_SERVER_REQUEST_DEFAULT_TIMEOUT; - const sessionId = sessionToken.getSessionID() || null; + const sessionId = sessionToken.getSessionId() || null; let licenseRequest = new LicenseRequest(url, reqMethod, responseType, reqHeaders, withCredentials, messageType, sessionId, reqPayload); const retryAttempts = !isNaN(settings.get().streaming.retryAttempts[HTTPRequest.LICENSE]) ? settings.get().streaming.retryAttempts[HTTPRequest.LICENSE] : LICENSE_SERVER_REQUEST_RETRIES; @@ -1021,7 +1028,7 @@ function ProtectionController(config) { logger.debug('DRM: initData:', String.fromCharCode.apply(null, new Uint8Array(abInitData))); - const supportedKS = protectionKeyController.getSupportedKeySystems(abInitData, protDataSet); + const supportedKS = protectionKeyController.getSupportedKeySystems(abInitData, protDataSet, sessionType); if (supportedKS.length === 0) { logger.debug('DRM: Received needkey event with initData, but we don\'t support any of the key systems!'); return; diff --git a/src/streaming/protection/controllers/ProtectionKeyController.js b/src/streaming/protection/controllers/ProtectionKeyController.js index bade83ee0a..c85706b559 100644 --- a/src/streaming/protection/controllers/ProtectionKeyController.js +++ b/src/streaming/protection/controllers/ProtectionKeyController.js @@ -191,13 +191,16 @@ function ProtectionKeyController() { * * @param {Array.} cps - array of content protection elements parsed * from the manifest + * @param {ProtectionData} protDataSet user specified protection data - license server url etc + * supported by the content + * @param {string} default session type * @returns {Array.} 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 getSupportedKeySystemsFromContentProtection(cps) { + function getSupportedKeySystemsFromContentProtection(cps, protDataSet, sessionType) { let cp, ks, ksIdx, cpIdx; let supportedKS = []; @@ -205,6 +208,10 @@ function ProtectionKeyController() { const cencContentProtection = CommonEncryption.findCencContentProtection(cps); for (ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) { ks = keySystems[ksIdx]; + + // Get protection data that applies for current key system + const protData = _getProtDataForKeySystem(ks.systemString, protDataSet); + for (cpIdx = 0; cpIdx < cps.length; ++cpIdx) { cp = cps[cpIdx]; if (cp.schemeIdUri.toLowerCase() === ks.schemeIdURI) { @@ -213,9 +220,12 @@ function ProtectionKeyController() { supportedKS.push({ ks: keySystems[ksIdx], + keyId: cp.keyId, initData: initData, - cdmData: ks.getCDMData(), - sessionId: ks.getSessionId(cp) + protData: protData, + cdmData: ks.getCDMData(protData ? protData.cdmData : null), + sessionId: _getSessionId(protData, cp), + sessionType: _getSessionType(protData, sessionType) }); } } @@ -234,13 +244,14 @@ function ProtectionKeyController() { * 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.} 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 getSupportedKeySystems(initData, protDataSet) { + function getSupportedKeySystems(initData, protDataSet, sessionType) { let supportedKS = []; let pssh = CommonEncryption.parsePSSHList(initData); let ks, keySystemString, shouldNotFilterOutKeySystem; @@ -248,14 +259,20 @@ function ProtectionKeyController() { for (let ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) { ks = keySystems[ksIdx]; keySystemString = ks.systemString; + shouldNotFilterOutKeySystem = (protDataSet) ? keySystemString in protDataSet : true; + // Get protection data that applies for current key system + const protData = _getProtDataForKeySystem(keySystemString, protDataSet); + if (ks.uuid in pssh && shouldNotFilterOutKeySystem) { supportedKS.push({ ks: ks, initData: pssh[ks.uuid], - cdmData: ks.getCDMData(), - sessionId: ks.getSessionId() + protData: protData, + cdmData: ks.getCDMData(protData ? protData.cdmData : null), + sessionId: _getSessionId(protData), + sessionType: _getSessionType(protData, sessionType) }); } } @@ -339,6 +356,25 @@ function ProtectionKeyController() { } } + 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 = { initialize, setProtectionData, diff --git a/src/streaming/protection/drm/KeySystemClearKey.js b/src/streaming/protection/drm/KeySystemClearKey.js index 5c1ad914da..e877cb2b24 100644 --- a/src/streaming/protection/drm/KeySystemClearKey.js +++ b/src/streaming/protection/drm/KeySystemClearKey.js @@ -118,11 +118,7 @@ function KeySystemClearKey(config) { return null; } - function getCDMData() { - return null; - } - - function getSessionId(/*cp*/) { + function getCDMData(/*cdmData*/) { return null; } @@ -135,7 +131,6 @@ function KeySystemClearKey(config) { getLicenseRequestFromMessage, getLicenseServerURLFromInitData, getCDMData, - getSessionId, getClearKeysFromProtectionData }; diff --git a/src/streaming/protection/drm/KeySystemPlayReady.js b/src/streaming/protection/drm/KeySystemPlayReady.js index 9d4c983960..e3ded4bee1 100644 --- a/src/streaming/protection/drm/KeySystemPlayReady.js +++ b/src/streaming/protection/drm/KeySystemPlayReady.js @@ -42,7 +42,6 @@ const uuid = '9a04f079-9840-4286-ab92-e65be0885f95'; const systemString = ProtectionConstants.PLAYREADY_KEYSTEM_STRING; const schemeIdURI = 'urn:uuid:' + uuid; const PRCDMData = '%CUSTOMDATA%'; -let protData; function KeySystemPlayReady(config) { @@ -235,64 +234,41 @@ function KeySystemPlayReady(config) { messageFormat = format; } - /** - * Initialize the Key system with protection data - * @param {Object} protectionData the protection data - */ - function init(protectionData) { - if (protectionData) { - protData = protectionData; - } - } - - /** * Get Playready Custom data */ - function getCDMData() { + function getCDMData(_cdmData) { let customData, cdmData, cdmDataBytes, i; checkConfig(); - if (protData && protData.cdmData) { - // Convert custom data into multibyte string - customData = []; - for (i = 0; i < protData.cdmData.length; ++i) { - customData.push(protData.cdmData.charCodeAt(i)); - customData.push(0); - } - customData = String.fromCharCode.apply(null, customData); + if (!_cdmData) return null; - // Encode in Base 64 the custom data string - customData = BASE64.encode(customData); + // Convert custom data into multibyte string + customData = []; + for (i = 0; i < _cdmData.length; ++i) { + customData.push(_cdmData.charCodeAt(i)); + customData.push(0); + } + customData = String.fromCharCode.apply(null, customData); - // Initialize CDM data with Base 64 encoded custom data - // (see https://msdn.microsoft.com/en-us/library/dn457361.aspx) - cdmData = PRCDMData.replace('%CUSTOMDATA%', customData); + // Encode in Base 64 the custom data string + customData = BASE64.encode(customData); - // Convert CDM data into multibyte characters - cdmDataBytes = []; - for (i = 0; i < cdmData.length; ++i) { - cdmDataBytes.push(cdmData.charCodeAt(i)); - cdmDataBytes.push(0); - } + // Initialize CDM data with Base 64 encoded custom data + // (see https://msdn.microsoft.com/en-us/library/dn457361.aspx) + cdmData = PRCDMData.replace('%CUSTOMDATA%', customData); - return new Uint8Array(cdmDataBytes).buffer; + // Convert CDM data into multibyte characters + cdmDataBytes = []; + for (i = 0; i < cdmData.length; ++i) { + cdmDataBytes.push(cdmData.charCodeAt(i)); + cdmDataBytes.push(0); } - return null; - } - - function getSessionId(cp) { - // Get sessionId from protectionData or from manifest - if (protData && protData.sessionId) { - return protData.sessionId; - } else if (cp && cp.sessionId) { - return cp.sessionId; - } - return null; + return new Uint8Array(cdmDataBytes).buffer; } instance = { @@ -304,9 +280,7 @@ function KeySystemPlayReady(config) { getLicenseRequestFromMessage, getLicenseServerURLFromInitData, getCDMData, - getSessionId, - setPlayReadyMessageFormat, - init + setPlayReadyMessageFormat }; return instance; diff --git a/src/streaming/protection/drm/KeySystemW3CClearKey.js b/src/streaming/protection/drm/KeySystemW3CClearKey.js index e9ce847bc2..6416066f8e 100644 --- a/src/streaming/protection/drm/KeySystemW3CClearKey.js +++ b/src/streaming/protection/drm/KeySystemW3CClearKey.js @@ -91,11 +91,7 @@ function KeySystemW3CClearKey(config) { return null; } - function getCDMData() { - return null; - } - - function getSessionId(/*cp*/) { + function getCDMData(/*cdmData*/) { return null; } @@ -108,7 +104,6 @@ function KeySystemW3CClearKey(config) { getLicenseRequestFromMessage: getLicenseRequestFromMessage, getLicenseServerURLFromInitData: getLicenseServerURLFromInitData, getCDMData: getCDMData, - getSessionId: getSessionId, getClearKeysFromProtectionData: getClearKeysFromProtectionData }; diff --git a/src/streaming/protection/drm/KeySystemWidevine.js b/src/streaming/protection/drm/KeySystemWidevine.js index 7273d73eff..1d9c1d98cd 100644 --- a/src/streaming/protection/drm/KeySystemWidevine.js +++ b/src/streaming/protection/drm/KeySystemWidevine.js @@ -47,15 +47,8 @@ function KeySystemWidevine(config) { config = config || {}; let instance; - let protData = null; const BASE64 = config.BASE64; - function init(protectionData) { - if (protectionData) { - protData = protectionData; - } - } - function getInitData(cp) { return CommonEncryption.parseInitDataFromContentProtection(cp, BASE64); } @@ -72,17 +65,7 @@ function KeySystemWidevine(config) { return null; } - function getCDMData() { - return null; - } - - function getSessionId(cp) { - // Get sessionId from protectionData or from manifest - if (protData && protData.sessionId) { - return protData.sessionId; - } else if (cp && cp.sessionId) { - return cp.sessionId; - } + function getCDMData(/*cdmData*/) { return null; } @@ -90,13 +73,11 @@ function KeySystemWidevine(config) { uuid, schemeIdURI, systemString, - init, getInitData, getRequestHeadersFromMessage, getLicenseRequestFromMessage, getLicenseServerURLFromInitData, - getCDMData, - getSessionId + getCDMData }; return instance; diff --git a/src/streaming/protection/models/ProtectionModel.js b/src/streaming/protection/models/ProtectionModel.js index ef9f81270b..5a84d0961b 100644 --- a/src/streaming/protection/models/ProtectionModel.js +++ b/src/streaming/protection/models/ProtectionModel.js @@ -135,7 +135,7 @@ const ProtectionModel = function () { }; */ /** - * Loads the persisted key session data associated with the given sessionID + * Loads the persisted key session data associated with the given sessionId * into a new session. Sends KEY_SESSION_CREATED event with * {@MediaPlayer.vo.protection.SessionToken} as data. * diff --git a/src/streaming/protection/models/ProtectionModel_01b.js b/src/streaming/protection/models/ProtectionModel_01b.js index 9ba214b109..e02f8a321b 100644 --- a/src/streaming/protection/models/ProtectionModel_01b.js +++ b/src/streaming/protection/models/ProtectionModel_01b.js @@ -61,19 +61,19 @@ function ProtectionModel_01b(config) { keySystem, protectionKeyController, - // With this version of the EME APIs, sessionIDs are not assigned to + // With this version of the EME APIs, sessionIds are not assigned to // sessions until the first key message is received. We are assuming // that in the case of multiple sessions, key messages will be received // in the order that generateKeyRequest() is called. // Holding spot for newly-created sessions until we determine whether or - // not the CDM supports sessionIDs + // not the CDM supports sessionIds pendingSessions, // List of sessions that have been initialized. Only the first position will - // be used in the case that the CDM does not support sessionIDs + // be used in the case that the CDM does not support sessionIds sessions, - // Not all CDMs support the notion of sessionIDs. Without sessionIDs + // Not all CDMs support the notion of sessionIds. Without sessionIds // there is no way for us to differentiate between sessions, therefore // we must only allow a single session. Once we receive the first key // message we can set this flag to determine if more sessions are allowed @@ -115,6 +115,10 @@ function ProtectionModel_01b(config) { return retVal; } + function getSessions() { + return sessions.concat(pendingSessions); + } + function requestKeySystemAccess(ksConfigurations) { return new Promise((resolve, reject) => { let ve = videoElement; @@ -206,7 +210,7 @@ function ProtectionModel_01b(config) { } } - function createKeySession(initData /*, protData, keySystemType */) { + function createKeySession(ksInfo) { if (!keySystem) { throw new Error('Can not create sessions until you have selected a key system'); } @@ -214,10 +218,16 @@ function ProtectionModel_01b(config) { // Determine if creating a new session is allowed if (moreSessionsAllowed || sessions.length === 0) { const newSession = { // Implements SessionToken - sessionID: null, - initData: initData, - getSessionID: function () { - return this.sessionID; + sessionId: null, + keyId: ksInfo.keyId, + initData: ksInfo.initData, + + getKeyId: function () { + return this.keyId; + }, + + getSessionId: function () { + return this.sessionId; }, getExpirationTime: function () { @@ -231,7 +241,7 @@ function ProtectionModel_01b(config) { pendingSessions.push(newSession); // Send our request to the CDM - videoElement[api.generateKeyRequest](keySystem.systemString, new Uint8Array(initData)); + videoElement[api.generateKeyRequest](keySystem.systemString, new Uint8Array(ksInfo.initData)); return newSession; @@ -242,16 +252,16 @@ function ProtectionModel_01b(config) { } function updateKeySession(sessionToken, message) { - const sessionID = sessionToken.sessionID; + const sessionId = sessionToken.sessionId; if (!protectionKeyController.isClearKey(keySystem)) { // Send our request to the CDM videoElement[api.addKey](keySystem.systemString, - new Uint8Array(message), new Uint8Array(sessionToken.initData), sessionID); + new Uint8Array(message), new Uint8Array(sessionToken.initData), sessionId); } else { // For clearkey, message is a ClearKeyKeySet for (let i = 0; i < message.keyPairs.length; i++) { videoElement[api.addKey](keySystem.systemString, - message.keyPairs[i].key, message.keyPairs[i].keyID, sessionID); + message.keyPairs[i].key, message.keyPairs[i].keyID, sessionId); } } eventBus.trigger(events.KEY_SESSION_UPDATED); @@ -260,11 +270,11 @@ function ProtectionModel_01b(config) { function closeKeySession(sessionToken) { // Send our request to the CDM try { - videoElement[api.cancelKeyRequest](keySystem.systemString, sessionToken.sessionID); + videoElement[api.cancelKeyRequest](keySystem.systemString, sessionToken.sessionId); } catch (error) { eventBus.trigger(events.KEY_SESSION_CLOSED, { data: null, - error: 'Error closing session (' + sessionToken.sessionID + ') ' + error.message + error: 'Error closing session (' + sessionToken.sessionId + ') ' + error.message }); } } @@ -272,7 +282,7 @@ function ProtectionModel_01b(config) { function setServerCertificate(/*serverCertificate*/) { /* Not supported */ } - function loadKeySession(/*sessionID*/) { /* Not supported */ + function loadKeySession(/*ksInfo*/) { /* Not supported */ } function removeKeySession(/*sessionToken*/) { /* Not supported */ @@ -352,15 +362,15 @@ function ProtectionModel_01b(config) { // SessionIDs supported if (moreSessionsAllowed) { - // Attempt to find an uninitialized token with this sessionID + // Attempt to find an uninitialized token with this sessionId sessionToken = findSessionByID(sessions, event.sessionId); if (!sessionToken && pendingSessions.length > 0) { // This is the first message for our latest session, so set the - // sessionID and add it to our list + // sessionId and add it to our list sessionToken = pendingSessions.shift(); sessions.push(sessionToken); - sessionToken.sessionID = event.sessionId; + sessionToken.sessionId = event.sessionId; eventBus.trigger(events.KEY_SESSION_CREATED, { data: sessionToken }); } @@ -394,19 +404,19 @@ function ProtectionModel_01b(config) { /** * Helper function to retrieve the stored session token based on a given - * sessionID value + * sessionId value * * @param {Array} sessionArray - the array of sessions to search - * @param {*} sessionID - the sessionID to search for - * @returns {*} the session token with the given sessionID + * @param {*} sessionId - the sessionId to search for + * @returns {*} the session token with the given sessionId */ - function findSessionByID(sessionArray, sessionID) { - if (!sessionID || !sessionArray) { + function findSessionByID(sessionArray, sessionId) { + if (!sessionId || !sessionArray) { return null; } else { const len = sessionArray.length; for (let i = 0; i < len; i++) { - if (sessionArray[i].sessionID == sessionID) { + if (sessionArray[i].sessionId == sessionId) { return sessionArray[i]; } } @@ -423,6 +433,7 @@ function ProtectionModel_01b(config) { instance = { getAllInitData, + getSessions, requestKeySystemAccess, selectKeySystem, setMediaElement, diff --git a/src/streaming/protection/models/ProtectionModel_21Jan2015.js b/src/streaming/protection/models/ProtectionModel_21Jan2015.js index 332d9327c0..e9fdc89a0b 100644 --- a/src/streaming/protection/models/ProtectionModel_21Jan2015.js +++ b/src/streaming/protection/models/ProtectionModel_21Jan2015.js @@ -134,6 +134,10 @@ function ProtectionModel_21Jan2015(config) { return retVal; } + function getSessions() { + return sessions; + } + function requestKeySystemAccess(ksConfigurations) { return new Promise((resolve, reject) => { _requestKeySystemAccessInternal(ksConfigurations, 0, resolve, reject); @@ -250,24 +254,22 @@ function ProtectionModel_21Jan2015(config) { /** * Create a key session, a session token and initialize a request by calling generateRequest - * @param initData - * @param protData - * @param sessionType + * @param ksInfo */ - function createKeySession(initData, protData, sessionType) { + function createKeySession(ksInfo) { if (!keySystem || !mediaKeys) { throw new Error('Can not create sessions until you have selected a key system'); } - const session = mediaKeys.createSession(sessionType); - const sessionToken = createSessionToken(session, initData, sessionType); + const session = mediaKeys.createSession(ksInfo.sessionType); + const sessionToken = createSessionToken(session, ksInfo); // The "keyids" type is used for Clearkey when keys are provided directly in the protection data and a request to a license server is not needed - const dataType = keySystem.systemString === ProtectionConstants.CLEARKEY_KEYSTEM_STRING && (initData || (protData && protData.clearkeys)) ? ProtectionConstants.INITIALIZATION_DATA_TYPE_KEYIDS : ProtectionConstants.INITIALIZATION_DATA_TYPE_CENC; + const dataType = keySystem.systemString === ProtectionConstants.CLEARKEY_KEYSTEM_STRING && (ksInfo.initData || (ksInfo.protData && ksInfo.protData.clearkeys)) ? ProtectionConstants.INITIALIZATION_DATA_TYPE_KEYIDS : ProtectionConstants.INITIALIZATION_DATA_TYPE_CENC; - session.generateRequest(dataType, initData).then(function () { - logger.debug('DRM: Session created. SessionID = ' + sessionToken.getSessionID()); + session.generateRequest(dataType, ksInfo.initData).then(function () { + logger.debug('DRM: Session created. SessionID = ' + sessionToken.getSessionId()); eventBus.trigger(events.KEY_SESSION_CREATED, { data: sessionToken }); }).catch(function (error) { removeSession(sessionToken); @@ -294,39 +296,41 @@ function ProtectionModel_21Jan2015(config) { }); } - function loadKeySession(sessionID, initData, sessionType) { + function loadKeySession(ksInfo) { if (!keySystem || !mediaKeys) { throw new Error('Can not load sessions until you have selected a key system'); } + const sessionId = ksInfo.sessionId; + // Check if session Id is not already loaded or loading for (let i = 0; i < sessions.length; i++) { - if (sessionID === sessions[i].sessionId) { + if (sessionId === sessions[i].sessionId) { logger.warn('DRM: Ignoring session ID because we have already seen it!'); return; } } - const session = mediaKeys.createSession(sessionType); - const sessionToken = createSessionToken(session, initData, sessionType, sessionID); + const session = mediaKeys.createSession(ksInfo.sessionType); + const sessionToken = createSessionToken(session, ksInfo); // Load persisted session data into our newly created session object - session.load(sessionID).then(function (success) { + session.load(sessionId).then(function (success) { if (success) { - logger.debug('DRM: Session loaded. SessionID = ' + sessionToken.getSessionID()); + logger.debug('DRM: Session loaded. SessionID = ' + sessionToken.getSessionId()); eventBus.trigger(events.KEY_SESSION_CREATED, { data: sessionToken }); } else { removeSession(sessionToken); eventBus.trigger(events.KEY_SESSION_CREATED, { data: null, - error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Could not load session! Invalid Session ID (' + sessionID + ')') + error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Could not load session! Invalid Session ID (' + sessionId + ')') }); } }).catch(function (error) { removeSession(sessionToken); eventBus.trigger(events.KEY_SESSION_CREATED, { data: null, - error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Could not load session (' + sessionID + ')! ' + error.name) + error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Could not load session (' + sessionId + ')! ' + error.name) }); }); } @@ -335,12 +339,12 @@ function ProtectionModel_21Jan2015(config) { const session = sessionToken.session; session.remove().then(function () { - logger.debug('DRM: Session removed. SessionID = ' + sessionToken.getSessionID()); - eventBus.trigger(events.KEY_SESSION_REMOVED, { data: sessionToken.getSessionID() }); + logger.debug('DRM: Session removed. SessionID = ' + sessionToken.getSessionId()); + eventBus.trigger(events.KEY_SESSION_REMOVED, { data: sessionToken.getSessionId() }); }, function (error) { eventBus.trigger(events.KEY_SESSION_REMOVED, { data: null, - error: 'Error removing session (' + sessionToken.getSessionID() + '). ' + error.name + error: 'Error removing session (' + sessionToken.getSessionId() + '). ' + error.name }); }); @@ -352,7 +356,7 @@ function ProtectionModel_21Jan2015(config) { removeSession(sessionToken); eventBus.trigger(events.KEY_SESSION_CLOSED, { data: null, - error: 'Error closing session (' + sessionToken.getSessionID() + ') ' + error.name + error: 'Error closing session (' + sessionToken.getSessionId() + ') ' + error.name }); }); } @@ -424,11 +428,13 @@ function ProtectionModel_21Jan2015(config) { // Function to create our session token objects which manage the EME // MediaKeySession and session-specific event handler - function createSessionToken(session, initData, sessionType, sessionID) { + function createSessionToken(session, ksInfo) { const token = { // Implements SessionToken session: session, - initData: initData, - sessionId: sessionID, + keyId: ksInfo.keyId, + initData: ksInfo.initData, + sessionId: ksInfo.sessionId, + sessionType: ksInfo.sessionType, // This is our main event handler for all desired MediaKeySession events // These events are translated into our API-independent versions of the @@ -457,10 +463,18 @@ function ProtectionModel_21Jan2015(config) { } }, - getSessionID: function () { + getKeyId: function () { + return this.keyId; + }, + + getSessionId: function () { return session.sessionId; }, + getSessionType: function () { + return this.sessionType; + }, + getExpirationTime: function () { return session.expiration; }, @@ -478,10 +492,6 @@ function ProtectionModel_21Jan2015(config) { } }); return usable; - }, - - getSessionType: function () { - return sessionType; } }; @@ -492,8 +502,8 @@ function ProtectionModel_21Jan2015(config) { // Register callback for session closed Promise session.closed.then(() => { removeSession(token); - logger.debug('DRM: Session closed. SessionID = ' + token.getSessionID()); - eventBus.trigger(events.KEY_SESSION_CLOSED, { data: token.getSessionID() }); + logger.debug('DRM: Session closed. SessionID = ' + token.getSessionId()); + eventBus.trigger(events.KEY_SESSION_CLOSED, { data: token.getSessionId() }); }); // Add to our session list @@ -504,6 +514,7 @@ function ProtectionModel_21Jan2015(config) { instance = { getAllInitData, + getSessions, requestKeySystemAccess, selectKeySystem, setMediaElement, diff --git a/src/streaming/protection/models/ProtectionModel_3Feb2014.js b/src/streaming/protection/models/ProtectionModel_3Feb2014.js index 87cbafe008..aa790c5a16 100644 --- a/src/streaming/protection/models/ProtectionModel_3Feb2014.js +++ b/src/streaming/protection/models/ProtectionModel_3Feb2014.js @@ -98,6 +98,10 @@ function ProtectionModel_3Feb2014(config) { return retVal; } + function getSessions() { + return sessions; + } + function requestKeySystemAccess(ksConfigurations) { return new Promise((resolve, reject) => { // Try key systems in order, first one with supported key system configuration @@ -197,7 +201,7 @@ function ProtectionModel_3Feb2014(config) { } } - function createKeySession(initData, protData, sessionType, cdmData) { + function createKeySession(ksInfo) { if (!keySystem || !mediaKeys || !keySystemAccess) { throw new Error('Can not create sessions until you have selected a key system'); } @@ -221,8 +225,8 @@ function ProtectionModel_3Feb2014(config) { } const contentType = capabilities.contentType; - const session = mediaKeys.createSession(contentType, new Uint8Array(initData), cdmData ? new Uint8Array(cdmData) : null); - const sessionToken = createSessionToken(session, initData); + const session = mediaKeys.createSession(contentType, new Uint8Array(ksInfo.initData), ksInfo.cdmData ? new Uint8Array(ksInfo.cdmData) : null); + const sessionToken = createSessionToken(session, ksInfo); // Add all event listeners session.addEventListener(api.error, sessionToken); @@ -232,7 +236,7 @@ function ProtectionModel_3Feb2014(config) { // Add to our session list sessions.push(sessionToken); - logger.debug('DRM: Session created. SessionID = ' + sessionToken.getSessionID()); + logger.debug('DRM: Session created. SessionID = ' + sessionToken.getSessionId()); eventBus.trigger(events.KEY_SESSION_CREATED, { data: sessionToken }); } @@ -279,7 +283,7 @@ function ProtectionModel_3Feb2014(config) { function setServerCertificate(/*serverCertificate*/) { /* Not supported */ } - function loadKeySession(/*sessionID*/) { /* Not supported */ + function loadKeySession(/*ksInfo*/) { /* Not supported */ } function removeKeySession(/*sessionToken*/) { /* Not supported */ @@ -324,13 +328,18 @@ function ProtectionModel_3Feb2014(config) { // Function to create our session token objects which manage the EME // MediaKeySession and session-specific event handler - function createSessionToken(keySession, initData) { + function createSessionToken(keySession, ksInfo) { return { // Implements SessionToken session: keySession, - initData: initData, + keyId: ksInfo.keyId, + initData: ksInfo.initData, - getSessionID: function () { + getKeyId: function () { + return this.keyId; + }, + + getSessionId: function () { return this.session.sessionId; }, @@ -341,6 +350,7 @@ function ProtectionModel_3Feb2014(config) { getSessionType: function () { return 'temporary'; }, + // This is our main event handler for all desired MediaKeySession events // These events are translated into our API-independent versions of the // same events @@ -360,8 +370,8 @@ function ProtectionModel_3Feb2014(config) { break; case api.close: - logger.debug('DRM: Session closed. SessionID = ' + this.getSessionID()); - eventBus.trigger(events.KEY_SESSION_CLOSED, { data: this.getSessionID() }); + logger.debug('DRM: Session closed. SessionID = ' + this.getSessionId()); + eventBus.trigger(events.KEY_SESSION_CLOSED, { data: this.getSessionId() }); break; } } @@ -370,6 +380,7 @@ function ProtectionModel_3Feb2014(config) { instance = { getAllInitData, + getSessions, requestKeySystemAccess, selectKeySystem, setMediaElement, diff --git a/src/streaming/protection/vo/SessionToken.js b/src/streaming/protection/vo/SessionToken.js index 0bd33ff70c..f19b109992 100644 --- a/src/streaming/protection/vo/SessionToken.js +++ b/src/streaming/protection/vo/SessionToken.js @@ -55,7 +55,7 @@ class SessionToken {} * Returns the unique session ID designated to this session * * @function - * @name SessionToken#getSessionID + * @name SessionToken#getSessionId * @return {string} the session ID or the empty string if the implementation * does not support session IDs or the sessionID has not yet been established */ diff --git a/test/unit/streaming.protection.drm.KeySystemPlayReady.js b/test/unit/streaming.protection.drm.KeySystemPlayReady.js index 8abd8b83af..a42734db8b 100644 --- a/test/unit/streaming.protection.drm.KeySystemPlayReady.js +++ b/test/unit/streaming.protection.drm.KeySystemPlayReady.js @@ -71,11 +71,6 @@ describe('KeySystemPlayready', function () { expect(keySystem.setPlayReadyMessageFormat.bind(keySystem, 'utf-8')).not.to.throw('Specified message format is not one of "utf-8" or "utf-16"'); }); - it('should return null when getSessionId is called and protData is undefined', function () { - const sessionId = keySystem.getSessionId(); - expect(sessionId).to.be.null; // jshint ignore:line - }); - it('should return null when getCDMData is called and protData is undefined', function () { const cdmData = keySystem.getCDMData(); expect(cdmData).to.be.null; // jshint ignore:line @@ -102,8 +97,7 @@ describe('KeySystemPlayready', function () { }); it('should return the correct cdmData', function () { - keySystem.init(protData); - cdmData = keySystem.getCDMData(); + cdmData = keySystem.getCDMData(protData.cdmData); expect(keySystem).to.be.defined; // jshint ignore:line expect(cdmData).to.be.not.null; // jshint ignore:line expect(cdmData).to.be.instanceOf(ArrayBuffer);