diff --git a/index.d.ts b/index.d.ts
index 1198e8a35e..6a66fe6948 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -43,7 +43,7 @@ declare namespace dashjs {
interface ProtectionController {
initializeForMedia(mediaInfo: ProtectionMediaInfo): void;
- clearMediaInfoArrayByStreamId(streamId: string): void;
+ clearMediaInfoArray(): void;
createKeySession(initData: ArrayBuffer, cdmData: Uint8Array): void;
@@ -175,6 +175,7 @@ declare namespace dashjs {
},
protection?: {
keepProtectionMediaKeys?: boolean,
+ ignoreEmeEncryptedEvent?: boolean
},
buffer?: {
enableSeekDecorrelationFix?: boolean,
diff --git a/samples/drm/dashif-laurl.html b/samples/drm/dashif-laurl.html
new file mode 100644
index 0000000000..946172f947
--- /dev/null
+++ b/samples/drm/dashif-laurl.html
@@ -0,0 +1,95 @@
+
+
+
+
+ License server via MPD example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Widevine DRM instantiation example
+
This example shows how to specify the license server url as part of the MPD using
+ 'dashif:laurl'.
+
For a detailed explanation on this checkout the
+ Wiki .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/samples.json b/samples/samples.json
index 898d32f835..e781c28f14 100644
--- a/samples/samples.json
+++ b/samples/samples.json
@@ -389,7 +389,22 @@
"Video",
"Audio"
]
+ },
+ {
+ "title": "License server via MPD",
+ "description": "This example shows how to specify the license server url as part of the MPD using 'dashif:laurl'",
+ "href": "drm/dashif-laurl.html",
+ "image": "lib/img/tos-3.jpg",
+ "labels": [
+ "VoD",
+ "DRM",
+ "Widevine",
+ "Playready",
+ "Video",
+ "Audio"
+ ]
}
+
]
},
{
diff --git a/src/core/Settings.js b/src/core/Settings.js
index 3151e73fe5..835c8785d2 100644
--- a/src/core/Settings.js
+++ b/src/core/Settings.js
@@ -521,6 +521,8 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* Set the value for the ProtectionController and MediaKeys life cycle.
*
* If true, the ProtectionController and then created MediaKeys and MediaKeySessions will be preserved during the MediaPlayer lifetime.
+ * @property {boolean} ignoreEmeEncryptedEvent
+ * If set to true the player will ignore "encrypted" and "needkey" events thrown by the EME.
*/
/**
@@ -773,7 +775,8 @@ function Settings() {
applyServiceDescription: true
},
protection: {
- keepProtectionMediaKeys: false
+ keepProtectionMediaKeys: false,
+ ignoreEmeEncryptedEvent: false
},
buffer: {
enableSeekDecorrelationFix: false,
diff --git a/src/streaming/Stream.js b/src/streaming/Stream.js
index 7ccdaa7d20..70ca834892 100644
--- a/src/streaming/Stream.js
+++ b/src/streaming/Stream.js
@@ -715,7 +715,7 @@ function Stream(config) {
if (protectionController) {
// Need to check if streamProcessors exists because streamProcessors
// could be cleared in case an error is detected while initializing DRM keysystem
- protectionController.clearMediaInfoArrayByStreamId(getId());
+ protectionController.clearMediaInfoArray();
for (let i = 0; i < ln && streamProcessors[i]; i++) {
const type = streamProcessors[i].getType();
const mediaInfo = streamProcessors[i].getMediaInfo();
diff --git a/src/streaming/constants/ProtectionConstants.js b/src/streaming/constants/ProtectionConstants.js
index b3dccfaad5..a47c1c0c6d 100644
--- a/src/streaming/constants/ProtectionConstants.js
+++ b/src/streaming/constants/ProtectionConstants.js
@@ -40,6 +40,9 @@ class ProtectionConstants {
this.CLEARKEY_KEYSTEM_STRING = 'org.w3.clearkey';
this.WIDEVINE_KEYSTEM_STRING = 'com.widevine.alpha';
this.PLAYREADY_KEYSTEM_STRING = 'com.microsoft.playready';
+ this.INITIALIZATION_DATA_TYPE_CENC = 'cenc';
+ this.INITIALIZATION_DATA_TYPE_KEYIDS = 'keyids'
+ this.INITIALIZATION_DATA_TYPE_WEBM = 'webm'
}
constructor () {
diff --git a/src/streaming/controllers/StreamController.js b/src/streaming/controllers/StreamController.js
index 9e2e91bb76..3b08bf3669 100644
--- a/src/streaming/controllers/StreamController.js
+++ b/src/streaming/controllers/StreamController.js
@@ -114,7 +114,7 @@ function StreamController() {
}
function initialize(autoPl, protData) {
- checkConfig();
+ _checkConfig();
autoPlay = autoPl;
protectionData = protData;
@@ -496,7 +496,7 @@ function StreamController() {
_handleOuterPeriodSeek(e, seekToStream);
}
- createPlaylistMetrics(PlayList.SEEK_START_REASON);
+ _createPlaylistMetrics(PlayList.SEEK_START_REASON);
}
/**
@@ -698,7 +698,7 @@ function StreamController() {
if (isNaN(initialBufferLevel) || initialBufferLevel <= playbackController.getBufferLevel() || (adapter.getIsDynamic() && initialBufferLevel > playbackController.getLiveDelay())) {
initialPlayback = false;
- createPlaylistMetrics(PlayList.INITIAL_PLAYOUT_START_REASON);
+ _createPlaylistMetrics(PlayList.INITIAL_PLAYOUT_START_REASON);
playbackController.play();
}
}
@@ -752,9 +752,9 @@ function StreamController() {
* @private
*/
function _onPlaybackStarted( /*e*/) {
- logger.debug('[onPlaybackStarted]');
- if (!initialPlayback && isPaused) {
- createPlaylistMetrics(PlayList.RESUME_FROM_PAUSE_START_REASON);
+ logger.debug('[onPlaybackStarted]');
+ if (!initialPlayback && isPaused) {
+ _createPlaylistMetrics(PlayList.RESUME_FROM_PAUSE_START_REASON);
}
if (initialPlayback) {
initialPlayback = false;
@@ -1233,7 +1233,7 @@ function StreamController() {
dashMetrics.addPlayList();
}
- function createPlaylistMetrics(startReason) {
+ function _createPlaylistMetrics(startReason) {
dashMetrics.createPlaylistMetrics(playbackController.getTime() * 1000, startReason);
}
@@ -1324,7 +1324,7 @@ function StreamController() {
return null;
}
- function checkConfig() {
+ function _checkConfig() {
if (!manifestLoader || !manifestLoader.hasOwnProperty('load') || !timelineConverter || !timelineConverter.hasOwnProperty('initialize') ||
!timelineConverter.hasOwnProperty('reset') || !timelineConverter.hasOwnProperty('getClientTimeOffset') || !manifestModel || !errHandler ||
!dashMetrics || !playbackController) {
@@ -1332,19 +1332,19 @@ function StreamController() {
}
}
- function checkInitialize() {
+ function _checkInitialize() {
if (!manifestUpdater || !manifestUpdater.hasOwnProperty('setManifest')) {
throw new Error('initialize function has to be called previously');
}
}
function load(url) {
- checkConfig();
+ _checkConfig();
manifestLoader.load(url);
}
function loadWithManifest(manifest) {
- checkInitialize();
+ _checkInitialize();
manifestUpdater.setManifest(manifest);
}
@@ -1446,7 +1446,7 @@ function StreamController() {
}
function reset() {
- checkConfig();
+ _checkConfig();
timeSyncController.reset();
diff --git a/src/streaming/protection/CommonEncryption.js b/src/streaming/protection/CommonEncryption.js
index fd3ff98f75..bc92cdd5ef 100644
--- a/src/streaming/protection/CommonEncryption.js
+++ b/src/streaming/protection/CommonEncryption.js
@@ -29,6 +29,11 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
+const LICENSE_SERVER_MANIFEST_CONFIGURATIONS = {
+ attributes: ['Laurl','laurl'],
+ prefixes: ['clearkey', 'dashif']
+};
+
/**
* @class
* @ignore
@@ -211,6 +216,54 @@ class CommonEncryption {
return pssh;
}
+
+ static getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri) {
+ try {
+
+ if (!mediaInfo || mediaInfo.length === 0) {
+ return null;
+ }
+
+ let i = 0;
+ let licenseServer = null;
+
+ while (i < mediaInfo.length && !licenseServer) {
+ const info = mediaInfo[i];
+
+ if (info && info.contentProtection && info.contentProtection.length > 0) {
+ const targetProtectionData = info.contentProtection.filter((cp) => {
+ return cp.schemeIdUri && cp.schemeIdUri === schemeIdUri;
+ });
+
+ if (targetProtectionData && targetProtectionData.length > 0) {
+ let j = 0;
+ while (j < targetProtectionData.length && !licenseServer) {
+ const ckData = targetProtectionData[j];
+ let k = 0;
+ while (k < LICENSE_SERVER_MANIFEST_CONFIGURATIONS.attributes.length && !licenseServer) {
+ let l = 0;
+ const attribute = LICENSE_SERVER_MANIFEST_CONFIGURATIONS.attributes[k];
+ while (l < LICENSE_SERVER_MANIFEST_CONFIGURATIONS.prefixes.length && !licenseServer) {
+ const prefix = LICENSE_SERVER_MANIFEST_CONFIGURATIONS.prefixes[l];
+ if (ckData[attribute] && ckData[attribute].__prefix && ckData[attribute].__prefix === prefix && ckData[attribute].__text) {
+ licenseServer = ckData[attribute].__text;
+ }
+ l += 1;
+ }
+ k += 1;
+ }
+ j += 1;
+ }
+ }
+ }
+ i += 1;
+ }
+ return licenseServer;
+ } catch
+ (e) {
+ return null;
+ }
+ }
}
export default CommonEncryption;
diff --git a/src/streaming/protection/Protection.js b/src/streaming/protection/Protection.js
index 81585fa65f..f7d0b9c7a0 100644
--- a/src/streaming/protection/Protection.js
+++ b/src/streaming/protection/Protection.js
@@ -119,7 +119,7 @@ function Protection() {
protectionKeyController.setConfig({ debug: config.debug, BASE64: config.BASE64 });
protectionKeyController.initialize();
- let protectionModel = getProtectionModel(config);
+ let protectionModel = _getProtectionModel(config);
if (!controller && protectionModel) {//TODO add ability to set external controller if still needed at all?
controller = ProtectionController(context).create({
@@ -138,7 +138,7 @@ function Protection() {
return controller;
}
- function getProtectionModel(config) {
+ function _getProtectionModel(config) {
const debug = config.debug;
const logger = debug.getLogger(instance);
const eventBus = config.eventBus;
@@ -149,19 +149,19 @@ function Protection() {
(!videoElement || videoElement.mediaKeys !== undefined)) {
logger.info('EME detected on this user agent! (ProtectionModel_21Jan2015)');
return ProtectionModel_21Jan2015(context).create({ debug: debug, eventBus: eventBus, events: config.events });
- } else if (getAPI(videoElement, APIS_ProtectionModel_3Feb2014)) {
+ } else if (_getAPI(videoElement, APIS_ProtectionModel_3Feb2014)) {
logger.info('EME detected on this user agent! (ProtectionModel_3Feb2014)');
- return ProtectionModel_3Feb2014(context).create({ debug: debug, eventBus: eventBus, events: config.events, api: getAPI(videoElement, APIS_ProtectionModel_3Feb2014) });
- } else if (getAPI(videoElement, APIS_ProtectionModel_01b)) {
+ return ProtectionModel_3Feb2014(context).create({ debug: debug, eventBus: eventBus, events: config.events, api: _getAPI(videoElement, APIS_ProtectionModel_3Feb2014) });
+ } else if (_getAPI(videoElement, APIS_ProtectionModel_01b)) {
logger.info('EME detected on this user agent! (ProtectionModel_01b)');
- return ProtectionModel_01b(context).create({ debug: debug, eventBus: eventBus, errHandler: errHandler, events: config.events, api: getAPI(videoElement, APIS_ProtectionModel_01b) });
+ return ProtectionModel_01b(context).create({ debug: debug, eventBus: eventBus, errHandler: errHandler, events: config.events, api: _getAPI(videoElement, APIS_ProtectionModel_01b) });
} else {
logger.warn('No supported version of EME detected on this user agent! - Attempts to play encrypted content will fail!');
return null;
}
}
- function getAPI(videoElement, apis) {
+ function _getAPI(videoElement, apis) {
for (let i = 0; i < apis.length; i++) {
const api = apis[i];
// detect if api is supported by browser
@@ -177,7 +177,7 @@ function Protection() {
}
instance = {
- createProtectionSystem: createProtectionSystem
+ createProtectionSystem
};
return instance;
diff --git a/src/streaming/protection/ProtectionEvents.js b/src/streaming/protection/ProtectionEvents.js
index 60aad835e3..de5cf53f0d 100644
--- a/src/streaming/protection/ProtectionEvents.js
+++ b/src/streaming/protection/ProtectionEvents.js
@@ -49,13 +49,6 @@ class ProtectionEvents extends EventsBase {
*/
this.INTERNAL_KEY_MESSAGE = 'internalKeyMessage';
- /**
- * Event ID for events delivered when a key system selection procedure
- * completes
- * @ignore
- */
- this.INTERNAL_KEY_SYSTEM_SELECTED = 'internalKeySystemSelected';
-
/**
* Event ID for events delivered when the status of one decryption keys has changed
* @ignore
diff --git a/src/streaming/protection/controllers/ProtectionController.js b/src/streaming/protection/controllers/ProtectionController.js
index 9477a8dcad..1afe93d93f 100644
--- a/src/streaming/protection/controllers/ProtectionController.js
+++ b/src/streaming/protection/controllers/ProtectionController.js
@@ -39,6 +39,7 @@ import LicenseResponse from '../vo/LicenseResponse';
import {HTTPRequest} from '../../vo/metrics/HTTPRequest';
import Utils from '../../../core/Utils';
import Constants from '../../constants/Constants';
+import FactoryMaker from '../../../core/FactoryMaker';
const NEEDKEY_BEFORE_INITIALIZE_RETRIES = 5;
const NEEDKEY_BEFORE_INITIALIZE_TIMEOUT = 500;
@@ -78,23 +79,26 @@ function ProtectionController(config) {
let instance,
logger,
- pendingNeedKeyData,
+ pendingKeySystemData,
mediaInfoArr,
protDataSet,
sessionType,
robustnessLevel,
- keySystem,
+ selectedKeySystem,
+ keySystemSelectionInProgress,
licenseRequestFilters,
licenseResponseFilters;
function setup() {
logger = debug.getLogger(instance);
- pendingNeedKeyData = [];
+ pendingKeySystemData = [];
mediaInfoArr = [];
sessionType = 'temporary';
robustnessLevel = '';
licenseRequestFilters = [];
licenseResponseFilters = [];
+ eventBus.on(events.INTERNAL_KEY_MESSAGE, _onKeyMessage, instance);
+ eventBus.on(events.INTERNAL_KEY_STATUS_CHANGED, _onKeyStatusChanged, instance);
}
function checkConfig() {
@@ -104,16 +108,11 @@ function ProtectionController(config) {
}
/**
- * Initialize this protection system with a given audio
- * or video stream information.
+ * Initialize this protection system for a given media type.
*
* @param {StreamInfo} [mediaInfo] Media information
* @memberof module:ProtectionController
* @instance
- * @todo This API will change when we have better support for allowing applications
- * to select different adaptation sets for playback. Right now it is clunky for
- * applications to create {@link StreamInfo} with the right information,
- * @ignore
*/
function initializeForMedia(mediaInfo) {
// Not checking here if a session for similar KS/KID combination is already created
@@ -126,66 +125,201 @@ function ProtectionController(config) {
checkConfig();
- eventBus.on(events.INTERNAL_KEY_MESSAGE, onKeyMessage, this);
- eventBus.on(events.INTERNAL_KEY_STATUS_CHANGED, onKeyStatusChanged, this);
mediaInfoArr.push(mediaInfo);
// 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
- const supportedKS = protectionKeyController.getSupportedKeySystemsFromContentProtection(mediaInfo.contentProtection);
+ // and video will be the same. Just use one valid MediaInfo object
+ let supportedKS = protectionKeyController.getSupportedKeySystemsFromContentProtection(mediaInfo.contentProtection);
+
+ // Reorder key systems according to priority order provided in protectionData
+ supportedKS = supportedKS.sort((ksA, ksB) => {
+ let indexA = (protDataSet && protDataSet[ksA.ks.systemString] && protDataSet[ksA.ks.systemString].priority >= 0) ? protDataSet[ksA.ks.systemString].priority : supportedKS.length;
+ let indexB = (protDataSet && protDataSet[ksB.ks.systemString] && protDataSet[ksB.ks.systemString].priority >= 0) ? protDataSet[ksB.ks.systemString].priority : supportedKS.length;
+ return indexA - indexB;
+ });
+
if (supportedKS && supportedKS.length > 0) {
- selectKeySystem(supportedKS, true);
+ _selectKeySystem(supportedKS, true);
+ }
+ }
+
+ /**
+ * Selects a key system if we dont have any one yet. Otherwise we use the existing key system and trigger a new license request if the initdata has changed
+ * @param {array} supportedKS
+ * @param {boolean} fromManifest
+ * @private
+ */
+ function _selectKeySystem(supportedKS, fromManifest) {
+
+ // We are in the process of selecting a key system, so just save the data which might be coming from additional AdaptationSets.
+ if (keySystemSelectionInProgress) {
+ pendingKeySystemData.push(supportedKS);
+ }
+
+ // First time, so we need to select a key system
+ else if (!selectedKeySystem) {
+ _selectInitialKeySystem(supportedKS, fromManifest);
+ }
+
+ // We already selected a key system. We only need to trigger a new license exchange if the init data has changed
+ else if (selectedKeySystem) {
+ _initiateWithExistingKeySystem(supportedKS);
}
}
/**
- * Removes all entries from the mediaInfoArr array for a specific stream id
- * @param {String} streamId
+ * We do not have a key system yet. Select one
+ * @param {array} supportedKS
+ * @param {boolean} fromManifest
+ * @private
*/
- function clearMediaInfoArrayByStreamId(streamId) {
- mediaInfoArr = mediaInfoArr.filter((mediaInfo) => {
- return mediaInfo.streamInfo.id !== streamId;
+ function _selectInitialKeySystem(supportedKS, fromManifest) {
+ keySystemSelectionInProgress = true;
+ const requestedKeySystems = [];
+
+ pendingKeySystemData.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++) {
+ const keySystemConfiguration = _getKeySystemConfiguration(supportedKS[i].ks);
+ requestedKeySystems.push({
+ ks: supportedKS[i].ks,
+ configs: [keySystemConfiguration]
+ });
+ }
+
+ let keySystemAccess;
+
+ protectionModel.requestKeySystemAccess(requestedKeySystems)
+ .then((event) => {
+ keySystemAccess = event.data;
+ logger.info('DRM: KeySystem Access Granted (' + keySystemAccess.keySystem.systemString + ')! Selecting key system...');
+ return protectionModel.selectKeySystem(keySystemAccess);
+ })
+ .then((keySystem) => {
+ selectedKeySystem = keySystem;
+ keySystemSelectionInProgress = false;
+
+ if (!protectionModel) {
+ return;
+ }
+
+ eventBus.trigger(events.KEY_SYSTEM_SELECTED, { data: keySystemAccess });
+
+ // Set server certificate from protData
+ const protData = _getProtDataForKeySystem(selectedKeySystem);
+ if (protData && protData.serverCertificate && protData.serverCertificate.length > 0) {
+ protectionModel.setServerCertificate(BASE64.decodeArray(protData.serverCertificate).buffer);
+ }
+
+ // Create key sessions for the different AdaptationSets
+ let ksIdx;
+ for (let i = 0; i < pendingKeySystemData.length; i++) {
+ for (ksIdx = 0; ksIdx < pendingKeySystemData[i].length; ksIdx++) {
+ if (selectedKeySystem === pendingKeySystemData[i][ksIdx].ks) {
+ const current = pendingKeySystemData[i][ksIdx]
+ _loadOrCreateKeySession(protData, current)
+ break;
+ }
+ }
+ }
+ })
+ .catch((event) => {
+ selectedKeySystem = null;
+ keySystemSelectionInProgress = false;
+ 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)
+ });
+ }
+ })
+ }
+
+ /**
+ * If we have already selected a keysytem we only need to create a new key session and issue a new license request if the init data has changed.
+ * @param {array} supportedKS
+ * @private
+ */
+ function _initiateWithExistingKeySystem(supportedKS,) {
+ const ksIdx = supportedKS.findIndex((entry) => {
+ return entry.ks === selectedKeySystem;
});
+
+ const current = supportedKS[ksIdx];
+ if (ksIdx === -1 || !current.initData) {
+ 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);
}
/**
- * 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).
+ * 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) {
+ // 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) };
+ keySystemInfo.initData = new TextEncoder().encode(JSON.stringify(initData));
+ }
+ }
+
+ // Reuse existing KeySession
+ if (keySystemInfo.sessionId) {
+ // Load MediaKeySession with sessionId
+ loadKeySession(keySystemInfo.sessionId, keySystemInfo.initData);
+ }
+
+ // Create a new KeySession
+ else if (keySystemInfo.initData !== null) {
+ // Create new MediaKeySession with initData
+ createKeySession(keySystemInfo.initData, keySystemInfo.cdmData);
+ }
+ }
+
+ /**
+ * Loads a key session with the given session ID from persistent storage. This essentially creates a new key session
*
- * @param {Array.} cps - array of content protection elements parsed
- * from the manifest
- * @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
+ * @param {string} sessionID
+ * @param {string} initData
+ * @memberof module:ProtectionController
* @instance
+ * @fires ProtectionController#KeySessionCreated
* @ignore
*/
- function getSupportedKeySystemsFromContentProtection(cps) {
+ function loadKeySession(sessionID, initData) {
checkConfig();
- return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps);
+ protectionModel.loadKeySession(sessionID, initData, _getSessionType(selectedKeySystem));
}
/**
- * Create a new key session associated with the given initialization data from
- * the MPD or from the PSSH box in the media
- *
+ * Create a new key session associated with the given initialization data from the MPD or from the PSSH box in the media
+ * For the latest version of the EME a request is generated. Once this request is ready we get notified via the INTERNAL_KEY_MESSAGE event
* @param {ArrayBuffer} initData the initialization data
* @param {Uint8Array} cdmData the custom data to provide to licenser
* @memberof module:ProtectionController
* @instance
* @fires ProtectionController#KeySessionCreated
- * @todo In older versions of the EME spec, there was a one-to-one relationship between
- * initialization data and key sessions. That is no longer true in the latest APIs. This
- * API will need to modified (and a new "generateRequest(keySession, initData)" API created)
- * to come up to speed with the latest EME standard
* @ignore
*/
function createKeySession(initData, cdmData) {
- const initDataForKS = CommonEncryption.getPSSHForKeySystem(keySystem, initData);
- const protData = getProtData(keySystem);
+ const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, initData);
+ const protData = _getProtDataForKeySystem(selectedKeySystem);
+
if (initDataForKS) {
// Check for duplicate initData
@@ -194,7 +328,8 @@ function ProtectionController(config) {
}
try {
- protectionModel.createKeySession(initDataForKS, protData, getSessionType(keySystem), cdmData);
+ const sessionType = _getSessionType(selectedKeySystem)
+ protectionModel.createKeySession(initDataForKS, protData, sessionType, cdmData);
} catch (error) {
eventBus.trigger(events.KEY_SESSION_CREATED, {
data: null,
@@ -202,15 +337,72 @@ function ProtectionController(config) {
});
}
} else if (initData) {
- protectionModel.createKeySession(initData, protData, getSessionType(keySystem), cdmData);
+ const sessionType = _getSessionType(selectedKeySystem)
+ protectionModel.createKeySession(initData, protData, sessionType, 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!')
+ error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + 'Selected key system is ' + (selectedKeySystem ? selectedKeySystem.systemString : null) + '. needkey/encrypted event contains no initData corresponding to that key system!')
});
}
}
+ /**
+ * Returns the protectionData for a specific keysystem as specified by the application.
+ * @param {object} keySystem
+ * @return {object | null}
+ * @private
+ */
+ function _getProtDataForKeySystem(keySystem) {
+ if (keySystem) {
+ const keySystemString = keySystem.systemString;
+
+ if (protDataSet) {
+ return (keySystemString in protDataSet) ? protDataSet[keySystemString] : null;
+ }
+ }
+ 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
+ */
+ function clearMediaInfoArray() {
+ mediaInfoArr = [];
+ }
+
+ /**
+ * 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.} cps - array of content protection elements parsed
+ * from the manifest
+ * @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
+ * @ignore
+ */
+ function getSupportedKeySystemsFromContentProtection(cps) {
+ checkConfig();
+ return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps);
+ }
+
/**
* Checks if the provided init data is equal to one of the existing init data values
* @param {any} initDataForKS
@@ -238,22 +430,6 @@ function ProtectionController(config) {
}
}
- /**
- * 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
- * @memberof module:ProtectionController
- * @instance
- * @fires ProtectionController#KeySessionCreated
- * @ignore
- */
- function loadKeySession(sessionID, initData) {
- checkConfig();
- protectionModel.loadKeySession(sessionID, initData, getSessionType(keySystem));
- }
-
/**
* Removes the given key session from persistent storage and closes the session
* as if {@link ProtectionController#closeKeySession}
@@ -318,10 +494,10 @@ function ProtectionController(config) {
checkConfig();
if (element) {
protectionModel.setMediaElement(element);
- eventBus.on(events.NEED_KEY, onNeedKey, this);
+ eventBus.on(events.NEED_KEY, _onNeedKey, instance);
} else if (element === null) {
protectionModel.setMediaElement(element);
- eventBus.off(events.NEED_KEY, onNeedKey, this);
+ eventBus.off(events.NEED_KEY, _onNeedKey, instance);
}
}
@@ -388,17 +564,18 @@ function ProtectionController(config) {
* @ignore
*/
function reset() {
+ eventBus.off(events.INTERNAL_KEY_MESSAGE, _onKeyMessage, instance);
+ eventBus.off(events.INTERNAL_KEY_STATUS_CHANGED, _onKeyStatusChanged, instance);
+
checkConfig();
licenseRequestFilters = [];
licenseResponseFilters = [];
- eventBus.off(events.INTERNAL_KEY_MESSAGE, onKeyMessage, this);
- eventBus.off(events.INTERNAL_KEY_STATUS_CHANGED, onKeyStatusChanged, this);
-
setMediaElement(null);
- keySystem = undefined;
+ selectedKeySystem = null;
+ keySystemSelectionInProgress = false;
if (protectionModel) {
protectionModel.reset();
@@ -409,31 +586,22 @@ function ProtectionController(config) {
needkeyRetries = [];
mediaInfoArr = [];
+ pendingKeySystemData = [];
}
- ///////////////
- // Private
- ///////////////
-
- function getProtData(keySystem) {
- let protData = null;
- if (keySystem) {
- const keySystemString = keySystem.systemString;
-
- if (protDataSet) {
- protData = (keySystemString in protDataSet) ? protDataSet[keySystemString] : null;
- }
- }
- return protData;
- }
-
- function getKeySystemConfiguration(keySystem) {
- const protData = getProtData(keySystem);
+ /**
+ * Returns an object corresponding to the EME MediaKeySystemConfiguration dictionary
+ * @param {object} keySystem
+ * @return {KeySystemConfiguration}
+ * @private
+ */
+ function _getKeySystemConfiguration(keySystem) {
+ const protData = _getProtDataForKeySystem(keySystem);
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 = _getSessionType(keySystem);
const distinctiveIdentifier = (protData && protData.distinctiveIdentifier) ? protData.distinctiveIdentifier : 'optional';
const persistentState = (protData && protData.persistentState) ? protData.persistentState : (ksSessionType === 'temporary') ? 'optional' : 'required';
@@ -451,189 +619,12 @@ function ProtectionController(config) {
[ksSessionType]);
}
- function getSessionType(keySystem) {
- const protData = getProtData(keySystem);
- const ksSessionType = (protData && protData.sessionType) ? protData.sessionType : sessionType;
- return ksSessionType;
- }
-
- function selectKeySystem(supportedKS, fromManifest) {
-
- // Reorder key systems according to priority order provided in protectionData
- supportedKS = supportedKS.sort((ksA, ksB) => {
- let indexA = (protDataSet && protDataSet[ksA.ks.systemString] && protDataSet[ksA.ks.systemString].priority >= 0) ? protDataSet[ksA.ks.systemString].priority : supportedKS.length;
- let indexB = (protDataSet && protDataSet[ksB.ks.systemString] && protDataSet[ksB.ks.systemString].priority >= 0) ? protDataSet[ksB.ks.systemString].priority : supportedKS.length;
- return indexA - indexB;
- });
-
-
- // First time, so we need to select a key system
- if (keySystem === undefined) {
- _selectInitialKeySystem(supportedKS, fromManifest);
- }
-
- // We already selected a key system. we only need to trigger a new license exchange if the init data has changed
- else if (keySystem) {
- _selectWithExistingKeySystem(supportedKS, fromManifest);
- }
-
- // We are in the process of selecting a key system, so just save the data which might be coming from additional AdaptationSets.
- else {
- pendingNeedKeyData.push(supportedKS);
- }
- }
-
- function _selectWithExistingKeySystem(supportedKS, fromManifest) {
- const self = this;
- const requestedKeySystems = [];
-
- const ksIdx = supportedKS.findIndex((entry) => {
- return entry.ks === keySystem;
- });
-
- if (ksIdx === -1 || !supportedKS[ksIdx].initData) {
- return;
- }
-
- // we only need to call this if the init data has changed
- const initDataForKs = CommonEncryption.getPSSHForKeySystem(keySystem, supportedKS[ksIdx].initData);
- if (_isInitDataDuplicate(initDataForKs)) {
- return;
- }
-
- 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
- const onKeySystemAccessComplete = function (event) {
- eventBus.off(events.KEY_SYSTEM_ACCESS_COMPLETE, onKeySystemAccessComplete, self);
- if (event.error) {
- if (!fromManifest) {
- eventBus.trigger(events.KEY_SYSTEM_SELECTED, { error: new DashJSError(ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE, ProtectionErrors.KEY_SYSTEM_ACCESS_DENIED_ERROR_MESSAGE + event.error) });
- }
- } else {
- logger.info('DRM: KeySystem Access Granted');
- eventBus.trigger(events.KEY_SYSTEM_SELECTED, { data: event.data });
- const protData = getProtData(keySystem);
- 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) };
- supportedKS[ksIdx].initData = new TextEncoder().encode(JSON.stringify(initData));
- }
- }
- if (supportedKS[ksIdx].sessionId) {
- // Load MediaKeySession with sessionId
- loadKeySession(supportedKS[ksIdx].sessionId, supportedKS[ksIdx].initData);
- } else if (supportedKS[ksIdx].initData) {
- // Create new MediaKeySession with initData
- createKeySession(supportedKS[ksIdx].initData, supportedKS[ksIdx].cdmData);
- }
- }
- };
-
- eventBus.on(events.KEY_SYSTEM_ACCESS_COMPLETE, onKeySystemAccessComplete, self);
- protectionModel.requestKeySystemAccess(requestedKeySystems);
- }
-
- function _selectInitialKeySystem(supportedKS, fromManifest) {
- const self = this;
- const requestedKeySystems = [];
- let ksIdx;
-
- // 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)]
- });
- }
-
- let keySystemAccess;
- const onKeySystemAccessComplete = function (event) {
- eventBus.off(events.KEY_SYSTEM_ACCESS_COMPLETE, onKeySystemAccessComplete, self);
- if (event.error) {
- 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)
- });
- }
- } else {
- keySystemAccess = event.data;
- logger.info('DRM: KeySystem Access Granted (' + keySystemAccess.keySystem.systemString + ')! Selecting key system...');
- protectionModel.selectKeySystem(keySystemAccess);
- }
- };
- var onKeySystemSelected = function (event) {
- eventBus.off(events.INTERNAL_KEY_SYSTEM_SELECTED, onKeySystemSelected, self);
- eventBus.off(events.KEY_SYSTEM_ACCESS_COMPLETE, onKeySystemAccessComplete, self);
- if (!event.error) {
- if (!protectionModel) {
- return;
- }
- keySystem = protectionModel.getKeySystem();
- eventBus.trigger(events.KEY_SYSTEM_SELECTED, { data: keySystemAccess });
- // Set server certificate from protData
- const protData = getProtData(keySystem);
- if (protData && protData.serverCertificate && protData.serverCertificate.length > 0) {
- protectionModel.setServerCertificate(BASE64.decodeArray(protData.serverCertificate).buffer);
- }
-
- // Create key session for the remaining AdaptationSets which have been added to pendingNeedKeyData
- for (let i = 0; i < pendingNeedKeyData.length; i++) {
- for (ksIdx = 0; ksIdx < pendingNeedKeyData[i].length; ksIdx++) {
- if (keySystem === pendingNeedKeyData[i][ksIdx].ks) {
- 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
- loadKeySession(pendingNeedKeyData[i][ksIdx].sessionId, pendingNeedKeyData[i][ksIdx].initData);
- } else if (pendingNeedKeyData[i][ksIdx].initData !== null) {
- // Create new MediaKeySession with initData
- createKeySession(pendingNeedKeyData[i][ksIdx].initData, pendingNeedKeyData[i][ksIdx].cdmData);
- }
- break;
- }
- }
- }
- } 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.on(events.INTERNAL_KEY_SYSTEM_SELECTED, onKeySystemSelected, self);
- eventBus.on(events.KEY_SYSTEM_ACCESS_COMPLETE, onKeySystemAccessComplete, self);
- protectionModel.requestKeySystemAccess(requestedKeySystems);
- }
-
- function sendLicenseRequestCompleteEvent(data, error) {
- eventBus.trigger(events.LICENSE_REQUEST_COMPLETE, { data: data, error: error });
- }
-
- function onKeyStatusChanged(e) {
+ /**
+ * Event handler for when the status of the key has changed
+ * @param {object} e
+ * @private
+ */
+ function _onKeyStatusChanged(e) {
if (e.error) {
eventBus.trigger(events.KEY_STATUSES_CHANGED, { data: null, error: e.error });
} else {
@@ -641,7 +632,12 @@ function ProtectionController(config) {
}
}
- function onKeyMessage(e) {
+ /**
+ * Event handler for the key message event. Once we have a key message we can issue a license request
+ * @param {object} e
+ * @private
+ */
+ function _onKeyMessage(e) {
logger.debug('DRM: onKeyMessage');
// Dispatch event to applications indicating we received a key message
@@ -650,133 +646,127 @@ function ProtectionController(config) {
const messageType = (keyMessage.messageType) ? keyMessage.messageType : 'license-request';
const message = keyMessage.message;
const sessionToken = keyMessage.sessionToken;
- const protData = getProtData(keySystem);
- const keySystemString = keySystem ? keySystem.systemString : null;
- const licenseServerData = protectionKeyController.getLicenseServer(keySystem, protData, messageType);
+ const protData = _getProtDataForKeySystem(selectedKeySystem);
+ const licenseServerModelInstance = protectionKeyController.getLicenseServerModelInstance(selectedKeySystem, protData, messageType);
const eventData = { sessionToken: sessionToken, messageType: messageType };
// Ensure message from CDM is not empty
if (!message || message.byteLength === 0) {
- sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_CODE, ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_MESSAGE));
+ _sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_CODE, ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_MESSAGE));
return;
}
// Message not destined for license server
- if (!licenseServerData) {
+ if (!licenseServerModelInstance) {
logger.debug('DRM: License server request not required for this message (type = ' + e.data.messageType + '). Session ID = ' + sessionToken.getSessionID());
- sendLicenseRequestCompleteEvent(eventData);
+ _sendLicenseRequestCompleteEvent(eventData);
return;
}
// Perform any special handling for ClearKey
- if (protectionKeyController.isClearKey(keySystem)) {
- const clearkeys = protectionKeyController.processClearKeyLicenseRequest(keySystem, protData, message);
+ if (protectionKeyController.isClearKey(selectedKeySystem)) {
+ const clearkeys = protectionKeyController.processClearKeyLicenseRequest(selectedKeySystem, protData, message);
if (clearkeys) {
logger.debug('DRM: ClearKey license request handled by application!');
- sendLicenseRequestCompleteEvent(eventData);
+ _sendLicenseRequestCompleteEvent(eventData);
protectionModel.updateKeySession(sessionToken, clearkeys);
return;
}
}
- // All remaining key system scenarios require a request to a remote license server
+ // In all other cases we have to make a license request
+ _issueLicenseRequest(keyMessage, licenseServerModelInstance, protData);
+ }
+
+ /**
+ * Notify other classes that the license request was completed
+ * @param {object} data
+ * @param {object} error
+ * @private
+ */
+ function _sendLicenseRequestCompleteEvent(data, error) {
+ eventBus.trigger(events.LICENSE_REQUEST_COMPLETE, { data: data, error: error });
+ }
+
+ /**
+ * Start issuing a license request
+ * @param {object} keyMessage
+ * @param {object} licenseServerData
+ * @param {object} protData
+ * @private
+ */
+ function _issueLicenseRequest(keyMessage, licenseServerData, protData) {
+ const sessionToken = keyMessage.sessionToken;
+ const messageType = (keyMessage.messageType) ? keyMessage.messageType : 'license-request';
+ const eventData = { sessionToken: sessionToken, messageType: messageType };
+ const keySystemString = selectedKeySystem ? selectedKeySystem.systemString : null;
+
// Determine license server URL
- let url = null;
- if (protData && protData.serverURL) {
- const serverURL = protData.serverURL;
- if (typeof serverURL === 'string' && serverURL !== '') {
- url = serverURL;
- } else if (typeof serverURL === 'object' && serverURL.hasOwnProperty(messageType)) {
- url = serverURL[messageType];
- }
- } else if (protData && protData.laURL && protData.laURL !== '') {
- // TODO: Deprecated!
- url = protData.laURL;
- } else {
- // 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
- url = licenseServerData.getServerURLFromMessage(url, message, messageType);
+ let url = _getLicenseServerUrl(protData, messageType, sessionToken, keyMessage, licenseServerData);
// Ensure valid license server URL
if (!url) {
- sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_LICENSE_SERVER_URL_ERROR_CODE, ProtectionErrors.MEDIA_KEY_MESSAGE_NO_LICENSE_SERVER_URL_ERROR_MESSAGE));
+ _sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_LICENSE_SERVER_URL_ERROR_CODE, ProtectionErrors.MEDIA_KEY_MESSAGE_NO_LICENSE_SERVER_URL_ERROR_MESSAGE));
return;
}
// Set optional XMLHttpRequest headers from protection data and message
const reqHeaders = {};
let withCredentials = false;
- const updateHeaders = function (headers) {
- if (headers) {
- for (const key in headers) {
- if ('authorization' === key.toLowerCase()) {
- withCredentials = true;
- }
- reqHeaders[key] = headers[key];
- }
- }
- };
if (protData) {
- updateHeaders(protData.httpRequestHeaders);
+ _updateHeaders(reqHeaders, protData.httpRequestHeaders);
}
- updateHeaders(keySystem.getRequestHeadersFromMessage(message));
+ const message = keyMessage.message;
+ const headersFromMessage = selectedKeySystem.getRequestHeadersFromMessage(message);
+ _updateHeaders(reqHeaders, headersFromMessage);
+
+ Object.keys(reqHeaders).forEach((key) => {
+ if ('authorization' === key.toLowerCase()) {
+ withCredentials = true;
+ }
+ });
// Overwrite withCredentials property from protData if present
if (protData && typeof protData.withCredentials == 'boolean') {
withCredentials = protData.withCredentials;
}
- const reportError = function (xhr, eventData, keySystemString, messageType) {
- const errorMsg = ((xhr.response) ? licenseServerData.getErrorResponse(xhr.response, keySystemString, messageType) : 'NONE');
- sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE,
- ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_MESSAGE + keySystemString + ' update, XHR complete. status is "' +
- xhr.statusText + '" (' + xhr.status + '), readyState is ' + xhr.readyState + '. Response is ' + errorMsg));
- };
-
const onLoad = function (xhr) {
if (!protectionModel) {
return;
}
if (xhr.status >= 200 && xhr.status <= 299) {
- let licenseResponse = new LicenseResponse(xhr.responseURL, Utils.parseHttpHeaders(xhr.getAllResponseHeaders ? xhr.getAllResponseHeaders() : null), xhr.response);
- applyFilters(licenseResponseFilters, licenseResponse).then(() => {
- const licenseMessage = licenseServerData.getLicenseMessage(licenseResponse.data, keySystemString, messageType);
- if (licenseMessage !== null) {
- sendLicenseRequestCompleteEvent(eventData);
- protectionModel.updateKeySession(sessionToken, licenseMessage);
- } else {
- reportError(xhr, eventData, keySystemString, messageType);
- }
- });
+ const responseHeaders = Utils.parseHttpHeaders(xhr.getAllResponseHeaders ? xhr.getAllResponseHeaders() : null);
+ let licenseResponse = new LicenseResponse(xhr.responseURL, responseHeaders, xhr.response);
+ _applyFilters(licenseResponseFilters, licenseResponse)
+ .then(() => {
+ const licenseMessage = licenseServerData.getLicenseMessage(licenseResponse.data, keySystemString, messageType);
+ if (licenseMessage !== null) {
+ _sendLicenseRequestCompleteEvent(eventData);
+ protectionModel.updateKeySession(sessionToken, licenseMessage);
+ } else {
+ _reportError(xhr, eventData, keySystemString, messageType, licenseServerData);
+ }
+ });
} else {
- reportError(xhr, eventData, keySystemString, messageType);
+ _reportError(xhr, eventData, keySystemString, messageType, licenseServerData);
}
};
const onAbort = function (xhr) {
- sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE,
+ _sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE,
ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_MESSAGE + keySystemString + ' update, XHR aborted. status is "' +
xhr.statusText + '" (' + xhr.status + '), readyState is ' + xhr.readyState));
};
const onError = function (xhr) {
- sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE,
+ _sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE,
ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_MESSAGE + keySystemString + ' update, XHR error. status is "' +
xhr.statusText + '" (' + xhr.status + '), readyState is ' + xhr.readyState));
};
- const reqPayload = keySystem.getLicenseRequestFromMessage(message);
+ const reqPayload = selectedKeySystem.getLicenseRequestFromMessage(message);
const reqMethod = licenseServerData.getHTTPMethod(messageType);
const responseType = licenseServerData.getResponseType(keySystemString, messageType);
const timeout = protData && !isNaN(protData.httpTimeout) ? protData.httpTimeout : LICENSE_SERVER_REQUEST_DEFAULT_TIMEOUT;
@@ -784,13 +774,23 @@ function ProtectionController(config) {
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;
- applyFilters(licenseRequestFilters, licenseRequest).then(() => {
- doLicenseRequest(licenseRequest, retryAttempts, timeout, onLoad, onAbort, onError);
+ _applyFilters(licenseRequestFilters, licenseRequest)
+ .then(() => {
+ _doLicenseRequest(licenseRequest, retryAttempts, timeout, onLoad, onAbort, onError);
});
}
- // Implement license requests with a retry mechanism to avoid temporary network issues to affect playback experience
- function doLicenseRequest(request, retriesCount, timeout, onLoad, onAbort, onError) {
+ /**
+ * Implement license requests with a retry mechanism to avoid temporary network issues to affect playback experience
+ * @param {object} request
+ * @param {number} retriesCount
+ * @param {number} timeout
+ * @param {function} onLoad
+ * @param {function} onAbort
+ * @param {function} onError
+ * @private
+ */
+ function _doLicenseRequest(request, retriesCount, timeout, onLoad, onAbort, onError) {
const xhr = new XMLHttpRequest();
if (settings.get().streaming.cmcd && settings.get().streaming.cmcd.enabled) {
@@ -836,12 +836,12 @@ function ProtectionController(config) {
}
}
- const retryRequest = function () {
+ const _retryRequest = function () {
// fail silently and retry
retriesCount--;
const retryInterval = !isNaN(settings.get().streaming.retryIntervals[HTTPRequest.LICENSE]) ? settings.get().streaming.retryIntervals[HTTPRequest.LICENSE] : LICENSE_SERVER_REQUEST_RETRY_INTERVAL;
setTimeout(function () {
- doLicenseRequest(request, retriesCount, timeout, onLoad, onAbort, onError);
+ _doLicenseRequest(request, retriesCount, timeout, onLoad, onAbort, onError);
}, retryInterval);
};
@@ -850,7 +850,7 @@ function ProtectionController(config) {
onLoad(this);
} else {
logger.warn('License request failed (' + this.status + '). Retrying it... Pending retries: ' + retriesCount);
- retryRequest();
+ _retryRequest();
}
};
@@ -859,7 +859,7 @@ function ProtectionController(config) {
onError(this);
} else {
logger.warn('License request network request failed . Retrying it... Pending retries: ' + retriesCount);
- retryRequest();
+ _retryRequest();
}
};
@@ -878,86 +878,196 @@ function ProtectionController(config) {
xhr.send(request.data);
}
- function onNeedKey(event, retry) {
- logger.debug('DRM: onNeedKey');
- // Ignore non-cenc initData
- if (event.key.initDataType !== 'cenc') {
- logger.warn('DRM: Only \'cenc\' initData is supported! Ignoring initData of type: ' + event.key.initDataType);
- return;
- }
+ /**
+ * Returns the url of the license server
+ * @param {object} protData
+ * @param {string} messageType
+ * @param {object} sessionToken
+ * @param {object} keyMessage
+ * @param {object} licenseServerData
+ * @return {*}
+ * @private
+ */
+ function _getLicenseServerUrl(protData, messageType, sessionToken, keyMessage, licenseServerData) {
+ let url = null;
+ const message = keyMessage.message;
- if (mediaInfoArr.length === 0) {
- logger.warn('DRM: onNeedKey called before initializeForMedia, wait until initialized');
- retry = typeof retry === 'undefined' ? 1 : retry + 1;
- if (retry < NEEDKEY_BEFORE_INITIALIZE_RETRIES) {
- needkeyRetries.push(setTimeout(() => {
- onNeedKey(event, retry);
- }, NEEDKEY_BEFORE_INITIALIZE_TIMEOUT));
- return;
+ // Check if the url is defined by the application
+ if (protData && protData.serverURL) {
+ const serverURL = protData.serverURL;
+ if (typeof serverURL === 'string' && serverURL !== '') {
+ url = serverURL;
+ } else if (typeof serverURL === 'object' && serverURL.hasOwnProperty(messageType)) {
+ url = serverURL[messageType];
}
-
}
- // Some browsers return initData as Uint8Array (IE), some as ArrayBuffer (Chrome).
- // Convert to ArrayBuffer
- let abInitData = event.key.initData;
- if (ArrayBuffer.isView(abInitData)) {
- abInitData = abInitData.buffer;
+ // This is the old way of providing the url
+ else if (protData && protData.laURL && protData.laURL !== '') {
+ url = protData.laURL;
}
- // If key system has already been selected and initData already seen, then do nothing
- if (keySystem) {
- const initDataForKS = CommonEncryption.getPSSHForKeySystem(keySystem, abInitData);
- if (initDataForKS) {
+ // No url provided by the app. Check the manifest and the pssh
+ else {
+ // Check for url defined in the manifest
+ url = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfoArr, selectedKeySystem.schemeIdURI);
- // Check for duplicate initData
- if (_isInitDataDuplicate(initDataForKS)) {
- return;
+ // In case we are not using Clearky we can still get a url from the pssh.
+ if (!url && !protectionKeyController.isClearKey(selectedKeySystem)) {
+ const psshData = CommonEncryption.getPSSHData(sessionToken.initData);
+ url = selectedKeySystem.getLicenseServerURLFromInitData(psshData);
+
+ // Still no url, check the keymessage
+ if (!url) {
+ url = keyMessage.laURL;
}
}
}
+ // Possibly update or override the URL based on the message
+ url = licenseServerData.getServerURLFromMessage(url, message, messageType);
- logger.debug('DRM: initData:', String.fromCharCode.apply(null, new Uint8Array(abInitData)));
+ return url;
+ }
- const supportedKS = protectionKeyController.getSupportedKeySystems(abInitData, protDataSet);
- if (supportedKS.length === 0) {
- logger.debug('DRM: Received needkey event with initData, but we don\'t support any of the key systems!');
- return;
+ /**
+ * Add new headers to the existing ones
+ * @param {array} reqHeaders
+ * @param {object} headers
+ * @private
+ */
+ function _updateHeaders(reqHeaders, headers) {
+ if (headers) {
+ for (const key in headers) {
+ reqHeaders[key] = headers[key];
+ }
}
+ }
+
+ /**
+ * Reports an error that might have occured during the license request
+ * @param {object} xhr
+ * @param {object} eventData
+ * @param {string} keySystemString
+ * @param {string} messageType
+ * @param {object} licenseServerData
+ * @private
+ */
+ function _reportError(xhr, eventData, keySystemString, messageType, licenseServerData) {
+ const errorMsg = ((xhr.response) ? licenseServerData.getErrorResponse(xhr.response, keySystemString, messageType) : 'NONE');
+ _sendLicenseRequestCompleteEvent(eventData, new DashJSError(ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE,
+ ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_MESSAGE + keySystemString + ' update, XHR complete. status is "' +
+ xhr.statusText + '" (' + xhr.status + '), readyState is ' + xhr.readyState + '. Response is ' + errorMsg));
+ }
- selectKeySystem(supportedKS, false);
+ /**
+ * Applies custom filters defined by the application
+ * @param {array} filters
+ * @param {object} param
+ * @return {Promise|*}
+ * @private
+ */
+ function _applyFilters(filters, param) {
+ if (!filters) return Promise.resolve();
+ return filters.reduce((prev, next) => {
+ return prev.then(() => {
+ return next(param);
+ });
+ }, Promise.resolve());
}
+ /**
+ * Event handler for "needkey" and "encrypted" events
+ * @param {object} event
+ * @param {number} retry
+ */
+ function _onNeedKey(event, retry) {
+ if (!settings.get().streaming.protection.ignoreEmeEncryptedEvent) {
+ logger.debug('DRM: onNeedKey');
+
+ // Ignore non-cenc initData
+ if (event.key.initDataType !== 'cenc') {
+ logger.warn('DRM: Only \'cenc\' initData is supported! Ignoring initData of type: ' + event.key.initDataType);
+ return;
+ }
+
+ if (mediaInfoArr.length === 0) {
+ logger.warn('DRM: onNeedKey called before initializeForMedia, wait until initialized');
+ retry = typeof retry === 'undefined' ? 1 : retry + 1;
+ if (retry < NEEDKEY_BEFORE_INITIALIZE_RETRIES) {
+ needkeyRetries.push(setTimeout(() => {
+ _onNeedKey(event, retry);
+ }, NEEDKEY_BEFORE_INITIALIZE_TIMEOUT));
+ return;
+ }
+ }
+
+ // Some browsers return initData as Uint8Array (IE), some as ArrayBuffer (Chrome).
+ // Convert to ArrayBuffer
+ let abInitData = event.key.initData;
+ if (ArrayBuffer.isView(abInitData)) {
+ abInitData = abInitData.buffer;
+ }
+
+ // If key system has already been selected and initData already seen, then do nothing
+ if (selectedKeySystem) {
+ const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, abInitData);
+ if (initDataForKS) {
+ // Check for duplicate initData
+ if (_isInitDataDuplicate(initDataForKS)) {
+ return;
+ }
+ }
+ }
+
+ logger.debug('DRM: initData:', String.fromCharCode.apply(null, new Uint8Array(abInitData)));
+
+ const supportedKS = protectionKeyController.getSupportedKeySystems(abInitData, protDataSet);
+ if (supportedKS.length === 0) {
+ logger.debug('DRM: Received needkey event with initData, but we don\'t support any of the key systems!');
+ return;
+ }
+
+ _selectKeySystem(supportedKS, false);
+ }
+ }
+
+ /**
+ * Returns all available key systems
+ * @return {*|*[]}
+ */
function getKeySystems() {
return protectionKeyController ? protectionKeyController.getKeySystems() : [];
}
+ /**
+ * Sets all available key systems
+ * @param {array} keySystems
+ */
function setKeySystems(keySystems) {
if (protectionKeyController) {
protectionKeyController.setKeySystems(keySystems);
}
}
+ /**
+ * Sets the request filters to be applied before the license request is made
+ * @param {array} filters
+ */
function setLicenseRequestFilters(filters) {
licenseRequestFilters = filters;
}
+ /**
+ * Sets the response filters to be applied after the license response has been received.
+ * @param {array} filters
+ */
function setLicenseResponseFilters(filters) {
licenseResponseFilters = filters;
}
- function applyFilters(filters, param) {
- if (!filters) return Promise.resolve();
- return filters.reduce((prev, next) => {
- return prev.then(() => {
- return next(param);
- });
- }, Promise.resolve());
- }
-
instance = {
initializeForMedia,
- clearMediaInfoArrayByStreamId,
+ clearMediaInfoArray,
createKeySession,
loadKeySession,
removeKeySession,
@@ -981,4 +1091,4 @@ function ProtectionController(config) {
}
ProtectionController.__dashjs_factory_name = 'ProtectionController';
-export default dashjs.FactoryMaker.getClassFactory(ProtectionController); /* jshint ignore:line */
+export default FactoryMaker.getClassFactory(ProtectionController); /* jshint ignore:line */
diff --git a/src/streaming/protection/controllers/ProtectionKeyController.js b/src/streaming/protection/controllers/ProtectionKeyController.js
index e41151a852..bade83ee0a 100644
--- a/src/streaming/protection/controllers/ProtectionKeyController.js
+++ b/src/streaming/protection/controllers/ProtectionKeyController.js
@@ -279,7 +279,7 @@ function ProtectionKeyController() {
* @instance
*
*/
- function getLicenseServer(keySystem, protData, messageType) {
+ 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
@@ -340,18 +340,18 @@ function ProtectionKeyController() {
}
instance = {
- initialize: initialize,
- setProtectionData: setProtectionData,
- isClearKey: isClearKey,
- initDataEquals: initDataEquals,
- getKeySystems: getKeySystems,
- setKeySystems: setKeySystems,
- getKeySystemBySystemString: getKeySystemBySystemString,
- getSupportedKeySystemsFromContentProtection: getSupportedKeySystemsFromContentProtection,
- getSupportedKeySystems: getSupportedKeySystems,
- getLicenseServer: getLicenseServer,
- processClearKeyLicenseRequest: processClearKeyLicenseRequest,
- setConfig: setConfig
+ initialize,
+ setProtectionData,
+ isClearKey,
+ initDataEquals,
+ getKeySystems,
+ setKeySystems,
+ getKeySystemBySystemString,
+ getSupportedKeySystemsFromContentProtection,
+ getSupportedKeySystems,
+ getLicenseServerModelInstance,
+ processClearKeyLicenseRequest,
+ setConfig
};
return instance;
diff --git a/src/streaming/protection/drm/KeySystemClearKey.js b/src/streaming/protection/drm/KeySystemClearKey.js
index 3111dfba27..5c1ad914da 100644
--- a/src/streaming/protection/drm/KeySystemClearKey.js
+++ b/src/streaming/protection/drm/KeySystemClearKey.js
@@ -43,10 +43,6 @@ function KeySystemClearKey(config) {
config = config || {};
let instance;
const BASE64 = config.BASE64;
- const LICENSE_SERVER_MANIFEST_CONFIGURATIONS = {
- attributes: ['Laurl', 'laurl'],
- prefixes: ['clearkey', 'dashif']
- };
/**
* Returns desired clearkeys (as specified in the CDM message) from protection data
@@ -122,49 +118,6 @@ function KeySystemClearKey(config) {
return null;
}
- function getLicenseServerUrlFromMediaInfo(mediaInfo) {
- try {
- if (!mediaInfo || mediaInfo.length === 0) {
- return null;
- }
- let i = 0;
- let licenseServer = null;
- while (i < mediaInfo.length && !licenseServer) {
- const info = mediaInfo[i];
- if (info && info.contentProtection && info.contentProtection.length > 0) {
- const clearkeyProtData = info.contentProtection.filter((cp) => {
- return cp.schemeIdUri && cp.schemeIdUri === schemeIdURI;
- });
- if (clearkeyProtData && clearkeyProtData.length > 0) {
- let j = 0;
- while (j < clearkeyProtData.length && !licenseServer) {
- const ckData = clearkeyProtData[j];
- let k = 0;
- while (k < LICENSE_SERVER_MANIFEST_CONFIGURATIONS.attributes.length && !licenseServer) {
- let l = 0;
- const attribute = LICENSE_SERVER_MANIFEST_CONFIGURATIONS.attributes[k];
- while (l < LICENSE_SERVER_MANIFEST_CONFIGURATIONS.prefixes.length && !licenseServer) {
- const prefix = LICENSE_SERVER_MANIFEST_CONFIGURATIONS.prefixes[l];
- if (ckData[attribute] && ckData[attribute].__prefix && ckData[attribute].__prefix === prefix && ckData[attribute].__text) {
- licenseServer = ckData[attribute].__text;
- }
- l += 1;
- }
- k += 1;
- }
- j += 1;
- }
- }
- }
- i += 1;
- }
- return licenseServer;
- } catch
- (e) {
- return null;
- }
- }
-
function getCDMData() {
return null;
}
@@ -174,17 +127,16 @@ function KeySystemClearKey(config) {
}
instance = {
- uuid: uuid,
- schemeIdURI: schemeIdURI,
- systemString: systemString,
- getInitData: getInitData,
- getRequestHeadersFromMessage: getRequestHeadersFromMessage,
- getLicenseRequestFromMessage: getLicenseRequestFromMessage,
- getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
- getCDMData: getCDMData,
- getSessionId: getSessionId,
- getLicenseServerUrlFromMediaInfo,
- getClearKeysFromProtectionData: getClearKeysFromProtectionData
+ uuid,
+ schemeIdURI,
+ systemString,
+ getInitData,
+ getRequestHeadersFromMessage,
+ getLicenseRequestFromMessage,
+ getLicenseServerURLFromInitData,
+ getCDMData,
+ getSessionId,
+ getClearKeysFromProtectionData
};
return instance;
diff --git a/src/streaming/protection/drm/KeySystemPlayReady.js b/src/streaming/protection/drm/KeySystemPlayReady.js
index 4566ed4d3a..9d4c983960 100644
--- a/src/streaming/protection/drm/KeySystemPlayReady.js
+++ b/src/streaming/protection/drm/KeySystemPlayReady.js
@@ -296,17 +296,17 @@ function KeySystemPlayReady(config) {
}
instance = {
- uuid: uuid,
- schemeIdURI: schemeIdURI,
- systemString: systemString,
- getInitData: getInitData,
- getRequestHeadersFromMessage: getRequestHeadersFromMessage,
- getLicenseRequestFromMessage: getLicenseRequestFromMessage,
- getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
- getCDMData: getCDMData,
- getSessionId: getSessionId,
- setPlayReadyMessageFormat: setPlayReadyMessageFormat,
- init: init
+ uuid,
+ schemeIdURI,
+ systemString,
+ getInitData,
+ getRequestHeadersFromMessage,
+ getLicenseRequestFromMessage,
+ getLicenseServerURLFromInitData,
+ getCDMData,
+ getSessionId,
+ setPlayReadyMessageFormat,
+ init
};
return instance;
diff --git a/src/streaming/protection/drm/KeySystemWidevine.js b/src/streaming/protection/drm/KeySystemWidevine.js
index c4a421e680..7273d73eff 100644
--- a/src/streaming/protection/drm/KeySystemWidevine.js
+++ b/src/streaming/protection/drm/KeySystemWidevine.js
@@ -87,16 +87,16 @@ function KeySystemWidevine(config) {
}
instance = {
- uuid: uuid,
- schemeIdURI: schemeIdURI,
- systemString: systemString,
- init: init,
- getInitData: getInitData,
- getRequestHeadersFromMessage: getRequestHeadersFromMessage,
- getLicenseRequestFromMessage: getLicenseRequestFromMessage,
- getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
- getCDMData: getCDMData,
- getSessionId: getSessionId
+ uuid,
+ schemeIdURI,
+ systemString,
+ init,
+ getInitData,
+ getRequestHeadersFromMessage,
+ getLicenseRequestFromMessage,
+ getLicenseServerURLFromInitData,
+ getCDMData,
+ getSessionId
};
return instance;
diff --git a/src/streaming/protection/models/ProtectionModel_01b.js b/src/streaming/protection/models/ProtectionModel_01b.js
index a814d7a706..9ba214b109 100644
--- a/src/streaming/protection/models/ProtectionModel_01b.js
+++ b/src/streaming/protection/models/ProtectionModel_01b.js
@@ -104,10 +104,6 @@ function ProtectionModel_01b(config) {
eventBus.trigger(events.TEARDOWN_COMPLETE);
}
- function getKeySystem() {
- return keySystem;
- }
-
function getAllInitData() {
const retVal = [];
for (let i = 0; i < pendingSessions.length; i++) {
@@ -120,59 +116,66 @@ function ProtectionModel_01b(config) {
}
function requestKeySystemAccess(ksConfigurations) {
- let ve = videoElement;
- if (!ve) { // Must have a video element to do this capability tests
- ve = document.createElement('video');
- }
-
- // Try key systems in order, first one with supported key system configuration
- // is used
- let found = false;
- for (let ksIdx = 0; ksIdx < ksConfigurations.length; ksIdx++) {
- const systemString = ksConfigurations[ksIdx].ks.systemString;
- const configs = ksConfigurations[ksIdx].configs;
- let supportedAudio = null;
- let supportedVideo = null;
+ return new Promise((resolve, reject) => {
+ let ve = videoElement;
+ if (!ve) { // Must have a video element to do this capability tests
+ ve = document.createElement('video');
+ }
- // Try key system configs in order, first one with supported audio/video
+ // Try key systems in order, first one with supported key system configuration
// is used
- for (let configIdx = 0; configIdx < configs.length; configIdx++) {
- //let audios = configs[configIdx].audioCapabilities;
- const videos = configs[configIdx].videoCapabilities;
- // Look for supported video container/codecs
- if (videos && videos.length !== 0) {
- supportedVideo = []; // Indicates that we have a requested video config
- for (let videoIdx = 0; videoIdx < videos.length; videoIdx++) {
- if (ve.canPlayType(videos[videoIdx].contentType, systemString) !== '') {
- supportedVideo.push(videos[videoIdx]);
+ let found = false;
+ for (let ksIdx = 0; ksIdx < ksConfigurations.length; ksIdx++) {
+ const systemString = ksConfigurations[ksIdx].ks.systemString;
+ const configs = ksConfigurations[ksIdx].configs;
+ let supportedAudio = null;
+ let supportedVideo = null;
+
+ // Try key system configs in order, first one with supported audio/video
+ // is used
+ for (let configIdx = 0; configIdx < configs.length; configIdx++) {
+ //let audios = configs[configIdx].audioCapabilities;
+ const videos = configs[configIdx].videoCapabilities;
+ // Look for supported video container/codecs
+ if (videos && videos.length !== 0) {
+ supportedVideo = []; // Indicates that we have a requested video config
+ for (let videoIdx = 0; videoIdx < videos.length; videoIdx++) {
+ if (ve.canPlayType(videos[videoIdx].contentType, systemString) !== '') {
+ supportedVideo.push(videos[videoIdx]);
+ }
}
}
- }
- // No supported audio or video in this configuration OR we have
- // requested audio or video configuration that is not supported
- if ((!supportedAudio && !supportedVideo) ||
- (supportedAudio && supportedAudio.length === 0) ||
- (supportedVideo && supportedVideo.length === 0)) {
- continue;
- }
+ // No supported audio or video in this configuration OR we have
+ // requested audio or video configuration that is not supported
+ if ((!supportedAudio && !supportedVideo) ||
+ (supportedAudio && supportedAudio.length === 0) ||
+ (supportedVideo && supportedVideo.length === 0)) {
+ continue;
+ }
- // This configuration is supported
- found = true;
- const ksConfig = new KeySystemConfiguration(supportedAudio, supportedVideo);
- const ks = protectionKeyController.getKeySystemBySystemString(systemString);
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: new KeySystemAccess(ks, ksConfig) });
- break;
+ // This configuration is supported
+ found = true;
+ const ksConfig = new KeySystemConfiguration(supportedAudio, supportedVideo);
+ const ks = protectionKeyController.getKeySystemBySystemString(systemString);
+ const keySystemAccess = new KeySystemAccess(ks, ksConfig)
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: keySystemAccess });
+ resolve({ data: keySystemAccess });
+ break;
+ }
}
- }
- if (!found) {
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: 'Key system access denied! -- No valid audio/video content configurations detected!' });
- }
+ if (!found) {
+ const errorMessage = 'Key system access denied! -- No valid audio/video content configurations detected!';
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: errorMessage });
+ reject({ error: errorMessage });
+ }
+ })
+
}
function selectKeySystem(keySystemAccess) {
keySystem = keySystemAccess.keySystem;
- eventBus.trigger(events.INTERNAL_KEY_SYSTEM_SELECTED);
+ return Promise.resolve(keySystem);
}
function setMediaElement(mediaElement) {
@@ -259,13 +262,21 @@ function ProtectionModel_01b(config) {
try {
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 });
+ eventBus.trigger(events.KEY_SESSION_CLOSED, {
+ data: null,
+ error: 'Error closing session (' + sessionToken.sessionID + ') ' + error.message
+ });
}
}
- function setServerCertificate(/*serverCertificate*/) { /* Not supported */ }
- function loadKeySession(/*sessionID*/) { /* Not supported */ }
- function removeKeySession(/*sessionToken*/) { /* Not supported */ }
+ function setServerCertificate(/*serverCertificate*/) { /* Not supported */
+ }
+
+ function loadKeySession(/*sessionID*/) { /* Not supported */
+ }
+
+ function removeKeySession(/*sessionToken*/) { /* Not supported */
+ }
function createEventHandler() {
return {
@@ -411,19 +422,18 @@ function ProtectionModel_01b(config) {
}
instance = {
- getAllInitData: getAllInitData,
- requestKeySystemAccess: requestKeySystemAccess,
- getKeySystem: getKeySystem,
- selectKeySystem: selectKeySystem,
- setMediaElement: setMediaElement,
- createKeySession: createKeySession,
- updateKeySession: updateKeySession,
- closeKeySession: closeKeySession,
- setServerCertificate: setServerCertificate,
- loadKeySession: loadKeySession,
- removeKeySession: removeKeySession,
+ getAllInitData,
+ requestKeySystemAccess,
+ selectKeySystem,
+ setMediaElement,
+ createKeySession,
+ updateKeySession,
+ closeKeySession,
+ setServerCertificate,
+ loadKeySession,
+ removeKeySession,
stop: reset,
- reset: reset
+ reset
};
setup();
diff --git a/src/streaming/protection/models/ProtectionModel_21Jan2015.js b/src/streaming/protection/models/ProtectionModel_21Jan2015.js
index 0eb2cb6e8d..332d9327c0 100644
--- a/src/streaming/protection/models/ProtectionModel_21Jan2015.js
+++ b/src/streaming/protection/models/ProtectionModel_21Jan2015.js
@@ -100,7 +100,7 @@ function ProtectionModel_21Jan2015(config) {
});
// Close the session and handle errors, otherwise promise
// resolver above will be called
- closeKeySessionInternal(session).catch(function () {
+ _closeKeySessionInternal(session).catch(function () {
done(s);
});
@@ -117,17 +117,13 @@ function ProtectionModel_21Jan2015(config) {
for (let i = 0; i < sessions.length; i++) {
session = sessions[i];
if (!session.getUsable()) {
- closeKeySessionInternal(session).catch(function () {
+ _closeKeySessionInternal(session).catch(function () {
removeSession(session);
});
}
}
}
- function getKeySystem() {
- return keySystem;
- }
-
function getAllInitData() {
const retVal = [];
for (let i = 0; i < sessions.length; i++) {
@@ -139,24 +135,82 @@ function ProtectionModel_21Jan2015(config) {
}
function requestKeySystemAccess(ksConfigurations) {
- requestKeySystemAccessInternal(ksConfigurations, 0);
+ return new Promise((resolve, reject) => {
+ _requestKeySystemAccessInternal(ksConfigurations, 0, resolve, reject);
+ })
+ }
+
+ /**
+ * Initializes access to a key system. Once we found a valid configuration we get a mediaKeySystemAccess object
+ * @param ksConfigurations
+ * @param idx
+ * @param resolve
+ * @param reject
+ * @private
+ */
+ function _requestKeySystemAccessInternal(ksConfigurations, idx, resolve, reject) {
+ if (navigator.requestMediaKeySystemAccess === undefined ||
+ typeof navigator.requestMediaKeySystemAccess !== 'function') {
+ const msg = 'Insecure origins are not allowed';
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: msg });
+ reject({ error: msg });
+ return;
+ }
+
+ const keySystem = ksConfigurations[idx].ks;
+ const configs = ksConfigurations[idx].configs;
+ let systemString = keySystem.systemString;
+
+ // Patch to support persistent licenses on Edge browser (see issue #2658)
+ if (systemString === ProtectionConstants.PLAYREADY_KEYSTEM_STRING && configs[0].persistentState === 'required') {
+ systemString += '.recommendation';
+ }
+
+ navigator.requestMediaKeySystemAccess(systemString, configs)
+ .then((mediaKeySystemAccess) => {
+ const configuration = (typeof mediaKeySystemAccess.getConfiguration === 'function') ?
+ mediaKeySystemAccess.getConfiguration() : null;
+ const keySystemAccess = new KeySystemAccess(keySystem, configuration);
+
+ keySystemAccess.mksa = mediaKeySystemAccess;
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: keySystemAccess });
+ resolve({ data: keySystemAccess });
+ })
+ .catch((error) => {
+ if (idx + 1 < ksConfigurations.length) {
+ _requestKeySystemAccessInternal(ksConfigurations, idx + 1, resolve, reject);
+ } else {
+ const errorMessage = 'Key system access denied! ';
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: errorMessage + error.message });
+ reject({ error: errorMessage + error.message });
+ }
+ });
}
+ /**
+ * Selects a key system by creating the mediaKeys and adding them to the video element
+ * @param keySystemAccess
+ * @return {Promise}
+ */
function selectKeySystem(keySystemAccess) {
- keySystemAccess.mksa.createMediaKeys().then(function (mkeys) {
- keySystem = keySystemAccess.keySystem;
- mediaKeys = mkeys;
- if (videoElement) {
- videoElement.setMediaKeys(mediaKeys).then(function () {
- eventBus.trigger(events.INTERNAL_KEY_SYSTEM_SELECTED);
+ return new Promise((resolve, reject) => {
+ keySystemAccess.mksa.createMediaKeys()
+ .then((mkeys) => {
+ keySystem = keySystemAccess.keySystem;
+ mediaKeys = mkeys;
+ if (videoElement) {
+ return videoElement.setMediaKeys(mediaKeys)
+ } else {
+ return Promise.resolve();
+ }
+ })
+ .then(() => {
+ resolve(keySystem);
+ })
+ .catch(function () {
+ reject({ error: 'Error selecting keys system (' + keySystemAccess.keySystem.systemString + ')! Could not create MediaKeys -- TODO' });
});
- } else {
- eventBus.trigger(events.INTERNAL_KEY_SYSTEM_SELECTED);
- }
-
- }).catch(function () {
- eventBus.trigger(events.INTERNAL_KEY_SYSTEM_SELECTED, { error: 'Error selecting keys system (' + keySystemAccess.keySystem.systemString + ')! Could not create MediaKeys -- TODO' });
- });
+ })
}
function setMediaElement(mediaElement) {
@@ -194,6 +248,12 @@ function ProtectionModel_21Jan2015(config) {
});
}
+ /**
+ * Create a key session, a session token and initialize a request by calling generateRequest
+ * @param initData
+ * @param protData
+ * @param sessionType
+ */
function createKeySession(initData, protData, sessionType) {
if (!keySystem || !mediaKeys) {
throw new Error('Can not create sessions until you have selected a key system');
@@ -201,16 +261,15 @@ function ProtectionModel_21Jan2015(config) {
const session = mediaKeys.createSession(sessionType);
const sessionToken = createSessionToken(session, initData, sessionType);
- const ks = this.getKeySystem();
- // Generate initial key request.
- // keyids type is used for clearkey when keys are provided directly in the protection data and then request to a license server is not needed
- const dataType = ks.systemString === ProtectionConstants.CLEARKEY_KEYSTEM_STRING && (initData || (protData && protData.clearkeys)) ? 'keyids' : 'cenc';
+
+ // 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;
+
session.generateRequest(dataType, initData).then(function () {
logger.debug('DRM: Session created. SessionID = ' + sessionToken.getSessionID());
eventBus.trigger(events.KEY_SESSION_CREATED, { data: sessionToken });
}).catch(function (error) {
- // TODO: Better error string
removeSession(sessionToken);
eventBus.trigger(events.KEY_SESSION_CREATED, {
data: null,
@@ -231,7 +290,7 @@ function ProtectionModel_21Jan2015(config) {
eventBus.trigger(events.KEY_SESSION_UPDATED);
})
.catch(function (error) {
- eventBus.trigger(events.KEY_ERROR, {error: new DashJSError(ProtectionErrors.MEDIA_KEYERR_CODE, 'Error sending update() message! ' + error.name, sessionToken)});
+ eventBus.trigger(events.KEY_ERROR, { error: new DashJSError(ProtectionErrors.MEDIA_KEYERR_CODE, 'Error sending update() message! ' + error.name, sessionToken) });
});
}
@@ -289,7 +348,7 @@ function ProtectionModel_21Jan2015(config) {
function closeKeySession(sessionToken) {
// Send our request to the key session
- closeKeySessionInternal(sessionToken).catch(function (error) {
+ _closeKeySessionInternal(sessionToken).catch(function (error) {
removeSession(sessionToken);
eventBus.trigger(events.KEY_SESSION_CLOSED, {
data: null,
@@ -298,43 +357,7 @@ function ProtectionModel_21Jan2015(config) {
});
}
- function requestKeySystemAccessInternal(ksConfigurations, idx) {
-
- if (navigator.requestMediaKeySystemAccess === undefined ||
- typeof navigator.requestMediaKeySystemAccess !== 'function') {
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: 'Insecure origins are not allowed' });
- return;
- }
-
- (function (i) {
- const keySystem = ksConfigurations[i].ks;
- const configs = ksConfigurations[i].configs;
- let systemString = keySystem.systemString;
-
- // PATCH to support persistent licenses on Edge browser (see issue #2658)
- if (systemString === ProtectionConstants.PLAYREADY_KEYSTEM_STRING && configs[0].persistentState === 'required') {
- systemString += '.recommendation';
- }
-
- navigator.requestMediaKeySystemAccess(systemString, configs).then(function (mediaKeySystemAccess) {
- // Chrome 40 does not currently implement MediaKeySystemAccess.getConfiguration()
- const configuration = (typeof mediaKeySystemAccess.getConfiguration === 'function') ?
- mediaKeySystemAccess.getConfiguration() : null;
- const keySystemAccess = new KeySystemAccess(keySystem, configuration);
- keySystemAccess.mksa = mediaKeySystemAccess;
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: keySystemAccess });
-
- }).catch(function (error) {
- if (++i < ksConfigurations.length) {
- requestKeySystemAccessInternal(ksConfigurations, i);
- } else {
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: 'Key system access denied! ' + error.message });
- }
- });
- })(idx);
- }
-
- function closeKeySessionInternal(sessionToken) {
+ function _closeKeySessionInternal(sessionToken) {
const session = sessionToken.session;
// Remove event listeners
@@ -467,7 +490,7 @@ function ProtectionModel_21Jan2015(config) {
session.addEventListener('message', token);
// Register callback for session closed Promise
- session.closed.then(function () {
+ session.closed.then(() => {
removeSession(token);
logger.debug('DRM: Session closed. SessionID = ' + token.getSessionID());
eventBus.trigger(events.KEY_SESSION_CLOSED, { data: token.getSessionID() });
@@ -480,19 +503,18 @@ function ProtectionModel_21Jan2015(config) {
}
instance = {
- getAllInitData: getAllInitData,
- requestKeySystemAccess: requestKeySystemAccess,
- getKeySystem: getKeySystem,
- selectKeySystem: selectKeySystem,
- setMediaElement: setMediaElement,
- setServerCertificate: setServerCertificate,
- createKeySession: createKeySession,
- updateKeySession: updateKeySession,
- loadKeySession: loadKeySession,
- removeKeySession: removeKeySession,
- closeKeySession: closeKeySession,
- stop: stop,
- reset: reset
+ getAllInitData,
+ requestKeySystemAccess,
+ selectKeySystem,
+ setMediaElement,
+ setServerCertificate,
+ createKeySession,
+ updateKeySession,
+ loadKeySession,
+ removeKeySession,
+ closeKeySession,
+ stop,
+ reset
};
setup();
diff --git a/src/streaming/protection/models/ProtectionModel_3Feb2014.js b/src/streaming/protection/models/ProtectionModel_3Feb2014.js
index df69304193..87cbafe008 100644
--- a/src/streaming/protection/models/ProtectionModel_3Feb2014.js
+++ b/src/streaming/protection/models/ProtectionModel_3Feb2014.js
@@ -90,10 +90,6 @@ function ProtectionModel_3Feb2014(config) {
}
}
- function getKeySystem() {
- return keySystem;
- }
-
function getAllInitData() {
const retVal = [];
for (let i = 0; i < sessions.length; i++) {
@@ -103,75 +99,82 @@ function ProtectionModel_3Feb2014(config) {
}
function requestKeySystemAccess(ksConfigurations) {
-
- // Try key systems in order, first one with supported key system configuration
- // is used
- let found = false;
- for (let ksIdx = 0; ksIdx < ksConfigurations.length; ksIdx++) {
- const systemString = ksConfigurations[ksIdx].ks.systemString;
- const configs = ksConfigurations[ksIdx].configs;
- let supportedAudio = null;
- let supportedVideo = null;
-
- // Try key system configs in order, first one with supported audio/video
+ return new Promise((resolve, reject) => {
+ // Try key systems in order, first one with supported key system configuration
// is used
- for (let configIdx = 0; configIdx < configs.length; configIdx++) {
- const audios = configs[configIdx].audioCapabilities;
- const videos = configs[configIdx].videoCapabilities;
-
- // Look for supported audio container/codecs
- if (audios && audios.length !== 0) {
- supportedAudio = []; // Indicates that we have a requested audio config
- for (let audioIdx = 0; audioIdx < audios.length; audioIdx++) {
- if (window[api.MediaKeys].isTypeSupported(systemString, audios[audioIdx].contentType)) {
- supportedAudio.push(audios[audioIdx]);
+ let found = false;
+ for (let ksIdx = 0; ksIdx < ksConfigurations.length; ksIdx++) {
+ const systemString = ksConfigurations[ksIdx].ks.systemString;
+ const configs = ksConfigurations[ksIdx].configs;
+ let supportedAudio = null;
+ let supportedVideo = null;
+
+ // Try key system configs in order, first one with supported audio/video
+ // is used
+ for (let configIdx = 0; configIdx < configs.length; configIdx++) {
+ const audios = configs[configIdx].audioCapabilities;
+ const videos = configs[configIdx].videoCapabilities;
+
+ // Look for supported audio container/codecs
+ if (audios && audios.length !== 0) {
+ supportedAudio = []; // Indicates that we have a requested audio config
+ for (let audioIdx = 0; audioIdx < audios.length; audioIdx++) {
+ if (window[api.MediaKeys].isTypeSupported(systemString, audios[audioIdx].contentType)) {
+ supportedAudio.push(audios[audioIdx]);
+ }
}
}
- }
- // Look for supported video container/codecs
- if (videos && videos.length !== 0) {
- supportedVideo = []; // Indicates that we have a requested video config
- for (let videoIdx = 0; videoIdx < videos.length; videoIdx++) {
- if (window[api.MediaKeys].isTypeSupported(systemString, videos[videoIdx].contentType)) {
- supportedVideo.push(videos[videoIdx]);
+ // Look for supported video container/codecs
+ if (videos && videos.length !== 0) {
+ supportedVideo = []; // Indicates that we have a requested video config
+ for (let videoIdx = 0; videoIdx < videos.length; videoIdx++) {
+ if (window[api.MediaKeys].isTypeSupported(systemString, videos[videoIdx].contentType)) {
+ supportedVideo.push(videos[videoIdx]);
+ }
}
}
- }
- // No supported audio or video in this configuration OR we have
- // requested audio or video configuration that is not supported
- if ((!supportedAudio && !supportedVideo) ||
- (supportedAudio && supportedAudio.length === 0) ||
- (supportedVideo && supportedVideo.length === 0)) {
- continue;
- }
+ // No supported audio or video in this configuration OR we have
+ // requested audio or video configuration that is not supported
+ if ((!supportedAudio && !supportedVideo) ||
+ (supportedAudio && supportedAudio.length === 0) ||
+ (supportedVideo && supportedVideo.length === 0)) {
+ continue;
+ }
- // This configuration is supported
- found = true;
- const ksConfig = new KeySystemConfiguration(supportedAudio, supportedVideo);
- const ks = protectionKeyController.getKeySystemBySystemString(systemString);
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: new KeySystemAccess(ks, ksConfig) });
- break;
+ // This configuration is supported
+ found = true;
+ const ksConfig = new KeySystemConfiguration(supportedAudio, supportedVideo);
+ const ks = protectionKeyController.getKeySystemBySystemString(systemString);
+ const keySystemAccess = new KeySystemAccess(ks, ksConfig);
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: keySystemAccess });
+ resolve({ data: keySystemAccess });
+ break;
+ }
}
- }
- if (!found) {
- eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: 'Key system access denied! -- No valid audio/video content configurations detected!' });
- }
+ if (!found) {
+ const errorMessage = 'Key system access denied! -- No valid audio/video content configurations detected!';
+ eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: errorMessage });
+ reject({ error: errorMessage });
+ }
+ })
}
function selectKeySystem(ksAccess) {
- try {
- mediaKeys = ksAccess.mediaKeys = new window[api.MediaKeys](ksAccess.keySystem.systemString);
- keySystem = ksAccess.keySystem;
- keySystemAccess = ksAccess;
- if (videoElement) {
- setMediaKeys();
+ return new Promise((resolve, reject) => {
+ try {
+ mediaKeys = ksAccess.mediaKeys = new window[api.MediaKeys](ksAccess.keySystem.systemString);
+ keySystem = ksAccess.keySystem;
+ keySystemAccess = ksAccess;
+ if (videoElement) {
+ setMediaKeys();
+ }
+ resolve(keySystem);
+ } catch (error) {
+ reject({ error: 'Error selecting keys system (' + keySystem.systemString + ')! Could not create MediaKeys -- TODO' });
}
- eventBus.trigger(events.INTERNAL_KEY_SYSTEM_SELECTED);
- } catch (error) {
- eventBus.trigger(events.INTERNAL_KEY_SYSTEM_SELECTED, { error: 'Error selecting keys system (' + keySystem.systemString + ')! Could not create MediaKeys -- TODO' });
- }
+ })
}
function setMediaElement(mediaElement) {
@@ -273,9 +276,14 @@ function ProtectionModel_3Feb2014(config) {
session[api.release]();
}
- function setServerCertificate(/*serverCertificate*/) { /* Not supported */ }
- function loadKeySession(/*sessionID*/) { /* Not supported */ }
- function removeKeySession(/*sessionToken*/) { /* Not supported */ }
+ function setServerCertificate(/*serverCertificate*/) { /* Not supported */
+ }
+
+ function loadKeySession(/*sessionID*/) { /* Not supported */
+ }
+
+ function removeKeySession(/*sessionToken*/) { /* Not supported */
+ }
function createEventHandler() {
@@ -361,19 +369,18 @@ function ProtectionModel_3Feb2014(config) {
}
instance = {
- getAllInitData: getAllInitData,
- requestKeySystemAccess: requestKeySystemAccess,
- getKeySystem: getKeySystem,
- selectKeySystem: selectKeySystem,
- setMediaElement: setMediaElement,
- createKeySession: createKeySession,
- updateKeySession: updateKeySession,
- closeKeySession: closeKeySession,
- setServerCertificate: setServerCertificate,
- loadKeySession: loadKeySession,
- removeKeySession: removeKeySession,
+ getAllInitData,
+ requestKeySystemAccess,
+ selectKeySystem,
+ setMediaElement,
+ createKeySession,
+ updateKeySession,
+ closeKeySession,
+ setServerCertificate,
+ loadKeySession,
+ removeKeySession,
stop: reset,
- reset: reset
+ reset
};
setup();
diff --git a/src/streaming/protection/servers/ClearKey.js b/src/streaming/protection/servers/ClearKey.js
index 1fc1811fff..3087722098 100644
--- a/src/streaming/protection/servers/ClearKey.js
+++ b/src/streaming/protection/servers/ClearKey.js
@@ -77,11 +77,11 @@ function ClearKey() {
}
instance = {
- getServerURLFromMessage: getServerURLFromMessage,
- getHTTPMethod: getHTTPMethod,
- getResponseType: getResponseType,
- getLicenseMessage: getLicenseMessage,
- getErrorResponse: getErrorResponse
+ getServerURLFromMessage,
+ getHTTPMethod,
+ getResponseType,
+ getLicenseMessage,
+ getErrorResponse
};
return instance;
diff --git a/src/streaming/protection/servers/DRMToday.js b/src/streaming/protection/servers/DRMToday.js
index ed81d3f8b1..089f31f527 100644
--- a/src/streaming/protection/servers/DRMToday.js
+++ b/src/streaming/protection/servers/DRMToday.js
@@ -93,15 +93,15 @@ function DRMToday(config) {
}
instance = {
- getServerURLFromMessage: getServerURLFromMessage,
- getHTTPMethod: getHTTPMethod,
- getResponseType: getResponseType,
- getLicenseMessage: getLicenseMessage,
- getErrorResponse: getErrorResponse
+ getServerURLFromMessage,
+ getHTTPMethod,
+ getResponseType,
+ getLicenseMessage,
+ getErrorResponse
};
return instance;
}
DRMToday.__dashjs_factory_name = 'DRMToday';
-export default dashjs.FactoryMaker.getSingletonFactory(DRMToday); /* jshint ignore:line */
\ No newline at end of file
+export default dashjs.FactoryMaker.getSingletonFactory(DRMToday); /* jshint ignore:line */
diff --git a/src/streaming/protection/servers/PlayReady.js b/src/streaming/protection/servers/PlayReady.js
index 1ad76a4150..bea4b0e8ed 100644
--- a/src/streaming/protection/servers/PlayReady.js
+++ b/src/streaming/protection/servers/PlayReady.js
@@ -134,15 +134,15 @@ function PlayReady() {
}
instance = {
- getServerURLFromMessage: getServerURLFromMessage,
- getHTTPMethod: getHTTPMethod,
- getResponseType: getResponseType,
- getLicenseMessage: getLicenseMessage,
- getErrorResponse: getErrorResponse
+ getServerURLFromMessage,
+ getHTTPMethod,
+ getResponseType,
+ getLicenseMessage,
+ getErrorResponse
};
return instance;
}
PlayReady.__dashjs_factory_name = 'PlayReady';
-export default dashjs.FactoryMaker.getSingletonFactory(PlayReady); /* jshint ignore:line */
\ No newline at end of file
+export default dashjs.FactoryMaker.getSingletonFactory(PlayReady); /* jshint ignore:line */
diff --git a/src/streaming/protection/servers/Widevine.js b/src/streaming/protection/servers/Widevine.js
index 233d3e2c18..2609d5acb6 100644
--- a/src/streaming/protection/servers/Widevine.js
+++ b/src/streaming/protection/servers/Widevine.js
@@ -57,15 +57,15 @@ function Widevine() {
}
instance = {
- getServerURLFromMessage: getServerURLFromMessage,
- getHTTPMethod: getHTTPMethod,
- getResponseType: getResponseType,
- getLicenseMessage: getLicenseMessage,
- getErrorResponse: getErrorResponse
+ getServerURLFromMessage,
+ getHTTPMethod,
+ getResponseType,
+ getLicenseMessage,
+ getErrorResponse
};
return instance;
}
Widevine.__dashjs_factory_name = 'Widevine';
-export default dashjs.FactoryMaker.getSingletonFactory(Widevine); /* jshint ignore:line */
\ No newline at end of file
+export default dashjs.FactoryMaker.getSingletonFactory(Widevine); /* jshint ignore:line */
diff --git a/test/unit/mocks/ProtectionKeyControllerMock.js b/test/unit/mocks/ProtectionKeyControllerMock.js
index ab5bed397c..c719922b4c 100644
--- a/test/unit/mocks/ProtectionKeyControllerMock.js
+++ b/test/unit/mocks/ProtectionKeyControllerMock.js
@@ -11,6 +11,10 @@ function ProtectionKeyControllerMock () {
this.getLicenseServer = function () {
return null;
};
+
+ this.getLicenseServerModelInstance = function () {
+ return {};
+ }
}
-export default ProtectionKeyControllerMock;
\ No newline at end of file
+export default ProtectionKeyControllerMock;
diff --git a/test/unit/mocks/ProtectionModelMock.js b/test/unit/mocks/ProtectionModelMock.js
index 36f0a71179..ac5e3c8570 100644
--- a/test/unit/mocks/ProtectionModelMock.js
+++ b/test/unit/mocks/ProtectionModelMock.js
@@ -14,7 +14,8 @@ function ProtectionModelMock (config) {
};
this.requestKeySystemAccess = function () {
+ return Promise.resolve();
};
}
-export default ProtectionModelMock;
\ No newline at end of file
+export default ProtectionModelMock;
diff --git a/test/unit/streaming.protection.CommonEncryption.js b/test/unit/streaming.protection.CommonEncryption.js
index cf5c5871ce..d16abb4e30 100644
--- a/test/unit/streaming.protection.CommonEncryption.js
+++ b/test/unit/streaming.protection.CommonEncryption.js
@@ -4,7 +4,7 @@ import Base64 from '../../externals/base64';
const expect = require('chai').expect;
let cpData;
-describe('CommonEncryption', () => {
+describe('CommonEncryption', () => {
beforeEach(() => {
cpData = {
@@ -21,13 +21,13 @@ describe('CommonEncryption', () => {
it('should return null if no init data is available in the ContentProtection element', () => {
cpData = {};
- const result = CommonEncryption.parseInitDataFromContentProtection(cpData,Base64);
+ const result = CommonEncryption.parseInitDataFromContentProtection(cpData, Base64);
expect(result).to.be.null; // jshint ignore:line
});
it('should return base64 decoded string if init data is available in the ContentProtection element', () => {
- const result = CommonEncryption.parseInitDataFromContentProtection(cpData,Base64);
+ const result = CommonEncryption.parseInitDataFromContentProtection(cpData, Base64);
const expectedByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
expect(result.byteLength).to.equal(expectedByteLength);
@@ -37,7 +37,7 @@ describe('CommonEncryption', () => {
const expectedByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
cpData.pssh.__text = '\nAAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARABGgZlbHV2aW8iBmVsdXZpbw==\n';
const originalByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
- const result = CommonEncryption.parseInitDataFromContentProtection(cpData,Base64);
+ const result = CommonEncryption.parseInitDataFromContentProtection(cpData, Base64);
expect(originalByteLength).to.not.equal(result.byteLength);
expect(result.byteLength).to.equal(expectedByteLength);
@@ -47,7 +47,7 @@ describe('CommonEncryption', () => {
const expectedByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
cpData.pssh.__text = 'AAAANHBzc2gAAAAA7e+LqXnWSs6jy Cfc1R0h7QAAABQIARABGgZlbHV2aW8iBmVsdXZpbw==';
const originalByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
- const result = CommonEncryption.parseInitDataFromContentProtection(cpData,Base64);
+ const result = CommonEncryption.parseInitDataFromContentProtection(cpData, Base64);
expect(originalByteLength).to.not.equal(result.byteLength);
expect(result.byteLength).to.equal(expectedByteLength);
@@ -57,7 +57,7 @@ describe('CommonEncryption', () => {
const expectedByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
cpData.pssh.__text = '\n\n\nAAAANHBzc2gAAAAA7e+LqXnWSs6jy Cfc1R0h7QAAABQIARABGgZlbHV2aW8iBmVsdXZpbw==\n\n';
const originalByteLength = Base64.decodeArray(cpData.pssh.__text).buffer.byteLength;
- const result = CommonEncryption.parseInitDataFromContentProtection(cpData,Base64);
+ const result = CommonEncryption.parseInitDataFromContentProtection(cpData, Base64);
expect(originalByteLength).to.not.equal(result.byteLength);
expect(result.byteLength).to.equal(expectedByteLength);
@@ -65,4 +65,63 @@ describe('CommonEncryption', () => {
});
-});
+ describe('getLicenseServerUrlFromMediaInfo', () => {
+ let mediaInfo;
+ let schemeIdUri = 'abcd-efgh';
+
+ beforeEach(() => {
+ mediaInfo = [{
+ contentProtection: [
+ {
+ schemeIdUri: schemeIdUri,
+ laurl: {
+ __prefix: 'dashif',
+ __text: 'license-server-url'
+ }
+ }
+ ]
+ }]
+ });
+
+ it('should return null in case the schemeIdUri does not match', () => {
+ const result = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfo, 'nomatch');
+
+ expect(result).to.be.null;
+ });
+
+ it('should return null if license server url is empty', () => {
+ mediaInfo[0].contentProtection[0].laurl.__text = '';
+ const result = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri);
+
+ expect(result).to.be.null;
+ })
+
+ it('should return null if wrong prefix', () => {
+ mediaInfo[0].contentProtection[0].laurl.__prefix = 'wrongprefix';
+ const result = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri);
+
+ expect(result).to.be.null;
+ })
+
+ it('should return null if wrong attribute', () => {
+ delete mediaInfo[0].contentProtection[0].laurl;
+ const result = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri);
+
+ expect(result).to.be.null;
+ })
+
+ it('should return valid license server for dashif:laurl', () => {
+ const result = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri);
+
+ expect(result).to.be.equal('license-server-url');
+ })
+
+ it('should return valid license server for dashif:Laurl', () => {
+ delete mediaInfo[0].contentProtection[0].laurl;
+ mediaInfo[0].contentProtection[0].Laurl = { __prefix: 'dashif', __text: 'license-server-url' };
+ const result = CommonEncryption.getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri);
+
+ expect(result).to.be.equal('license-server-url');
+ })
+ });
+})
diff --git a/test/unit/streaming.protection.controllers.ProtectionController.js b/test/unit/streaming.protection.controllers.ProtectionController.js
index 176f540b01..6291df8736 100644
--- a/test/unit/streaming.protection.controllers.ProtectionController.js
+++ b/test/unit/streaming.protection.controllers.ProtectionController.js
@@ -13,12 +13,15 @@ const expect = require('chai').expect;
const context = {};
const eventBus = EventBus(context).getInstance();
let protectionController;
-const protectionKeyControllerMock = new ProtectionKeyControllerMock();
describe('ProtectionController', function () {
describe('Not well initialized', function () {
beforeEach(function () {
- protectionController = ProtectionController(context).create({debug: new DebugMock()});
+ protectionController = ProtectionController(context).create({
+ debug: new DebugMock(),
+ events: ProtectionEvents,
+ eventBus
+ });
});
afterEach(function () {
@@ -66,33 +69,21 @@ describe('ProtectionController', function () {
describe('Well initialized', function () {
beforeEach(function () {
- protectionController = ProtectionController(context).create({protectionKeyController: protectionKeyControllerMock,
+ const protectionKeyControllerMock = new ProtectionKeyControllerMock();
+ protectionController = ProtectionController(context).create({
+ protectionKeyController: protectionKeyControllerMock,
events: ProtectionEvents,
debug: new DebugMock(),
- protectionModel: new ProtectionModelMock({events: ProtectionEvents, eventBus: eventBus}),
+ protectionModel: new ProtectionModelMock({ events: ProtectionEvents, eventBus: eventBus }),
eventBus: eventBus,
- constants: Constants});
+ constants: Constants
+ });
});
afterEach(function () {
protectionController.reset();
});
- it('onKeyMessage behavior', function (done) {
- let onDRMError = function (data) {
- eventBus.off(ProtectionEvents.LICENSE_REQUEST_COMPLETE, onDRMError);
- expect(data.error.code).to.be.equal(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_CODE); // jshint ignore:line
- expect(data.error.message).to.be.equal(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_MESSAGE); // jshint ignore:line
- done();
- };
-
- eventBus.on(ProtectionEvents.LICENSE_REQUEST_COMPLETE, onDRMError, this);
-
- protectionController.initializeForMedia({type: 'VIDEO'});
-
- eventBus.trigger(ProtectionEvents.INTERNAL_KEY_MESSAGE, {data: {}});
- });
-
it('setServerCertificate behavior', function (done) {
let onDRMError = function (data) {
@@ -107,6 +98,21 @@ describe('ProtectionController', function () {
protectionController.setServerCertificate();
});
+ it('onKeyMessage behavior', function (done) {
+ let onDRMError = function (data) {
+ eventBus.off(ProtectionEvents.LICENSE_REQUEST_COMPLETE, onDRMError);
+ expect(data.error.code).to.be.equal(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_CODE); // jshint ignore:line
+ expect(data.error.message).to.be.equal(ProtectionErrors.MEDIA_KEY_MESSAGE_NO_CHALLENGE_ERROR_MESSAGE); // jshint ignore:line
+ done();
+ };
+
+ eventBus.on(ProtectionEvents.LICENSE_REQUEST_COMPLETE, onDRMError, this);
+
+ protectionController.initializeForMedia({ type: 'VIDEO' });
+
+ eventBus.trigger(ProtectionEvents.INTERNAL_KEY_MESSAGE, { data: {} });
+ });
+
it('should trigger KEY_SESSION_CREATED event with an error when createKeySession is called without parameter', function (done) {
let onSessionCreated = function (data) {
eventBus.off(ProtectionEvents.KEY_SESSION_CREATED, onSessionCreated);
@@ -125,12 +131,5 @@ describe('ProtectionController', function () {
expect(keySystems).not.to.be.empty; // jshint ignore:line
});
- it('should ????? when setMediaElement is called', function () {
- protectionController.initializeForMedia({type: 'VIDEO'});
-
- protectionController.setMediaElement({});
-
- expect(eventBus.trigger.bind(eventBus, ProtectionEvents.NEED_KEY, {key: {initDataType: 'cenc'}})).not.to.throw();
- });
});
-});
\ No newline at end of file
+});