UNPKG

dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

992 lines (872 loc) 62.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>dash.js Source: streaming/protection/controllers/ProtectionController.js</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css"> <link type="text/css" rel="stylesheet" href="styles/site.spacelab.css"> </head> <body> <div class="navbar navbar-default navbar-fixed-top navbar-inverse"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="index.html">dash.js</a> <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#topNavigation"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> </div> <div class="navbar-collapse collapse" id="topNavigation"> <ul class="nav navbar-nav"> <li class="dropdown"> <a href="modules.list.html" class="dropdown-toggle" data-toggle="dropdown">Modules<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="module-DashAdapter.html">DashAdapter</a></li><li><a href="module-DashMetrics.html">DashMetrics</a></li><li><a href="module-MediaPlayer.html">MediaPlayer</a></li><li><a href="module-OfflineController.html">OfflineController</a></li><li><a href="module-ProtectionController.html">ProtectionController</a></li><li><a href="module-Settings.html">Settings</a></li> </ul> </li> <li class="dropdown"> <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="Errors.html">Errors</a></li><li><a href="MediaPlayerEvents.html">MediaPlayerEvents</a></li><li><a href="MediaPlayerModel.html">MediaPlayerModel</a></li><li><a href="MetricsReportingEvents.html">MetricsReportingEvents</a></li><li><a href="MssErrors.html">MssErrors</a></li><li><a href="OfflineErrors.html">OfflineErrors</a></li><li><a href="OfflineEvents.html">OfflineEvents</a></li><li><a href="ProtectionErrors.html">ProtectionErrors</a></li><li><a href="ProtectionEvents.html">ProtectionEvents</a></li> </ul> </li> <li class="dropdown"> <a href="events.list.html" class="dropdown-toggle" data-toggle="dropdown">Events<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="MediaPlayerEvents.html#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES">MediaPlayerEvents#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES</a></li><li><a href="MediaPlayerEvents.html#event:AST_IN_FUTURE">MediaPlayerEvents#event:AST_IN_FUTURE</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_EMPTY">MediaPlayerEvents#event:BUFFER_EMPTY</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_STATE_CHANGED">MediaPlayerEvents#event:BUFFER_LEVEL_STATE_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_UPDATED">MediaPlayerEvents#event:BUFFER_LEVEL_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LOADED">MediaPlayerEvents#event:BUFFER_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:CAN_PLAY">MediaPlayerEvents#event:CAN_PLAY</a></li><li><a href="MediaPlayerEvents.html#event:CAN_PLAY_THROUGH">MediaPlayerEvents#event:CAN_PLAY_THROUGH</a></li><li><a href="MediaPlayerEvents.html#event:CAPTION_CONTAINER_RESIZE">MediaPlayerEvents#event:CAPTION_CONTAINER_RESIZE</a></li><li><a href="MediaPlayerEvents.html#event:CAPTION_RENDERED">MediaPlayerEvents#event:CAPTION_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:CONFORMANCE_VIOLATION">MediaPlayerEvents#event:CONFORMANCE_VIOLATION</a></li><li><a href="MediaPlayerEvents.html#event:CONTENT_STEERING_REQUEST_COMPLETED">MediaPlayerEvents#event:CONTENT_STEERING_REQUEST_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:DYNAMIC_TO_STATIC">MediaPlayerEvents#event:DYNAMIC_TO_STATIC</a></li><li><a href="MediaPlayerEvents.html#event:ERROR">MediaPlayerEvents#event:ERROR</a></li><li><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_RECEIVE">MediaPlayerEvents#event:EVENT_MODE_ON_RECEIVE</a></li><li><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_START">MediaPlayerEvents#event:EVENT_MODE_ON_START</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_ABANDONED">MediaPlayerEvents#event:FRAGMENT_LOADING_ABANDONED</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_COMPLETED">MediaPlayerEvents#event:FRAGMENT_LOADING_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_PROGRESS">MediaPlayerEvents#event:FRAGMENT_LOADING_PROGRESS</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_STARTED">MediaPlayerEvents#event:FRAGMENT_LOADING_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:LOG">MediaPlayerEvents#event:LOG</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_LOADED">MediaPlayerEvents#event:MANIFEST_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_VALIDITY_CHANGED">MediaPlayerEvents#event:MANIFEST_VALIDITY_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_ADDED">MediaPlayerEvents#event:METRIC_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_CHANGED">MediaPlayerEvents#event:METRIC_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_UPDATED">MediaPlayerEvents#event:METRIC_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:METRICS_CHANGED">MediaPlayerEvents#event:METRICS_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_COMPLETED">MediaPlayerEvents#event:PERIOD_SWITCH_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_STARTED">MediaPlayerEvents#event:PERIOD_SWITCH_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_ENDED">MediaPlayerEvents#event:PLAYBACK_ENDED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_ERROR">MediaPlayerEvents#event:PLAYBACK_ERROR</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_LOADED_DATA">MediaPlayerEvents#event:PLAYBACK_LOADED_DATA</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_METADATA_LOADED">MediaPlayerEvents#event:PLAYBACK_METADATA_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_NOT_ALLOWED">MediaPlayerEvents#event:PLAYBACK_NOT_ALLOWED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PAUSED">MediaPlayerEvents#event:PLAYBACK_PAUSED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PLAYING">MediaPlayerEvents#event:PLAYBACK_PLAYING</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PROGRESS">MediaPlayerEvents#event:PLAYBACK_PROGRESS</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_RATE_CHANGED">MediaPlayerEvents#event:PLAYBACK_RATE_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKED">MediaPlayerEvents#event:PLAYBACK_SEEKED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKING">MediaPlayerEvents#event:PLAYBACK_SEEKING</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_STALLED">MediaPlayerEvents#event:PLAYBACK_STALLED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_STARTED">MediaPlayerEvents#event:PLAYBACK_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_TIME_UPDATED">MediaPlayerEvents#event:PLAYBACK_TIME_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_VOLUME_CHANGED">MediaPlayerEvents#event:PLAYBACK_VOLUME_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_WAITING">MediaPlayerEvents#event:PLAYBACK_WAITING</a></li><li><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_RENDERED">MediaPlayerEvents#event:QUALITY_CHANGE_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_REQUESTED">MediaPlayerEvents#event:QUALITY_CHANGE_REQUESTED</a></li><li><a href="MediaPlayerEvents.html#event:REPRESENTATION_SWITCH">MediaPlayerEvents#event:REPRESENTATION_SWITCH</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_ACTIVATED">MediaPlayerEvents#event:STREAM_ACTIVATED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_DEACTIVATED">MediaPlayerEvents#event:STREAM_DEACTIVATED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZED">MediaPlayerEvents#event:STREAM_INITIALIZED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZING">MediaPlayerEvents#event:STREAM_INITIALIZING</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_TEARDOWN_COMPLETE">MediaPlayerEvents#event:STREAM_TEARDOWN_COMPLETE</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_UPDATED">MediaPlayerEvents#event:STREAM_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:TEXT_TRACK_ADDED">MediaPlayerEvents#event:TEXT_TRACK_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:TEXT_TRACKS_ADDED">MediaPlayerEvents#event:TEXT_TRACKS_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:TRACK_CHANGE_RENDERED">MediaPlayerEvents#event:TRACK_CHANGE_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:TTML_PARSED">MediaPlayerEvents#event:TTML_PARSED</a></li><li><a href="MediaPlayerEvents.html#event:TTML_TO_PARSE">MediaPlayerEvents#event:TTML_TO_PARSE</a></li><li><a href="MetricsReportingEvents.html#event:CMCD_DATA_GENERATED">MetricsReportingEvents#event:CMCD_DATA_GENERATED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_FINISHED">OfflineEvents#event:OFFLINE_RECORD_FINISHED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_LOADEDMETADATA">OfflineEvents#event:OFFLINE_RECORD_LOADEDMETADATA</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_STARTED">OfflineEvents#event:OFFLINE_RECORD_STARTED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_STOPPED">OfflineEvents#event:OFFLINE_RECORD_STOPPED</a></li><li><a href="ProtectionEvents.html#event:KEY_ADDED">ProtectionEvents#event:KEY_ADDED</a></li><li><a href="ProtectionEvents.html#event:KEY_ERROR">ProtectionEvents#event:KEY_ERROR</a></li><li><a href="ProtectionEvents.html#event:KEY_MESSAGE">ProtectionEvents#event:KEY_MESSAGE</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_CLOSED">ProtectionEvents#event:KEY_SESSION_CLOSED</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_CREATED">ProtectionEvents#event:KEY_SESSION_CREATED</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_REMOVED">ProtectionEvents#event:KEY_SESSION_REMOVED</a></li><li><a href="ProtectionEvents.html#event:KEY_STATUSES_CHANGED">ProtectionEvents#event:KEY_STATUSES_CHANGED</a></li><li><a href="ProtectionEvents.html#event:KEY_SYSTEM_SELECTED">ProtectionEvents#event:KEY_SYSTEM_SELECTED</a></li><li><a href="ProtectionEvents.html#event:LICENSE_REQUEST_COMPLETE">ProtectionEvents#event:LICENSE_REQUEST_COMPLETE</a></li><li><a href="ProtectionEvents.html#event:LICENSE_REQUEST_SENDING">ProtectionEvents#event:LICENSE_REQUEST_SENDING</a></li><li><a href="ProtectionEvents.html#event:PROTECTION_CREATED">ProtectionEvents#event:PROTECTION_CREATED</a></li><li><a href="ProtectionEvents.html#event:PROTECTION_DESTROYED">ProtectionEvents#event:PROTECTION_DESTROYED</a></li> </ul> </li> <li class="dropdown"> <a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="global.html#LICENSE_SERVER_MANIFEST_CONFIGURATIONS">LICENSE_SERVER_MANIFEST_CONFIGURATIONS</a></li> </ul> </li> </ul> <div class="col-sm-3 col-md-3"> <form class="navbar-form" role="search"> <div class="input-group"> <input type="text" class="form-control" placeholder="Search" name="q" id="search-input"> <div class="input-group-btn"> <button class="btn btn-default" id="search-submit"><i class="glyphicon glyphicon-search"></i></button> </div> </div> </form> </div> </div> </div> </div> <div class="container" id="toc-content"> <div class="row"> <div class="col-md-12"> <div id="main"> <h1 class="page-title">Source: streaming/protection/controllers/ProtectionController.js</h1> <section> <article> <pre class="sunlight-highlight-javascript linenums">/** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import CommonEncryption from '../CommonEncryption'; import MediaCapability from '../vo/MediaCapability'; import KeySystemConfiguration from '../vo/KeySystemConfiguration'; import ProtectionErrors from '../errors/ProtectionErrors'; import DashJSError from '../../vo/DashJSError'; import LicenseRequest from '../vo/LicenseRequest'; 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; const LICENSE_SERVER_REQUEST_RETRIES = 3; const LICENSE_SERVER_REQUEST_RETRY_INTERVAL = 1000; const LICENSE_SERVER_REQUEST_DEFAULT_TIMEOUT = 8000; /** * @module ProtectionController * @description Provides access to media protection information and functionality. Each * ProtectionController manages a single {@link MediaPlayer.models.ProtectionModel} * which encapsulates a set of protection information (EME APIs, selected key system, * key sessions). The APIs of ProtectionController mostly align with the latest EME * APIs. Key system selection is mostly automated when combined with app-overrideable * functionality provided in {@link ProtectionKeyController}. * @todo ProtectionController does almost all of its tasks automatically after init() is * called. Applications might want more control over this process and want to go through * each step manually (key system selection, session creation, session maintenance). * This module can be accessed using the MediaPlayer API getProtectionController() * @param {Object} config */ function ProtectionController(config) { config = config || {}; const protectionKeyController = config.protectionKeyController; let protectionModel = config.protectionModel; const eventBus = config.eventBus; const events = config.events; const debug = config.debug; const BASE64 = config.BASE64; const constants = config.constants; let needkeyRetries = []; const cmcdModel = config.cmcdModel; const settings = config.settings; const customParametersModel = config.customParametersModel; let instance, logger, pendingKeySessionsToHandle, mediaInfoArr, protDataSet, sessionType, robustnessLevel, selectedKeySystem, keySystemSelectionInProgress, licenseXhrRequest, licenseRequestRetryTimeout; function setup() { logger = debug.getLogger(instance); pendingKeySessionsToHandle = []; mediaInfoArr = []; sessionType = 'temporary'; robustnessLevel = ''; licenseXhrRequest = null; licenseRequestRetryTimeout = null; eventBus.on(events.INTERNAL_KEY_MESSAGE, _onKeyMessage, instance); eventBus.on(events.INTERNAL_KEY_STATUS_CHANGED, _onKeyStatusChanged, instance); } function checkConfig() { if (!eventBus || !eventBus.hasOwnProperty('on') || !protectionKeyController || !protectionKeyController.hasOwnProperty('getSupportedKeySystemsFromContentProtection')) { throw new Error('Missing config parameter(s)'); } } /** * Initialize this protection system for a given media type. * * @param {StreamInfo} [mediaInfo] Media information * @memberof module:ProtectionController * @instance */ function initializeForMedia(mediaInfo) { // Not checking here if a session for similar KS/KID combination is already created // because still don't know which keysystem will be selected. // Once Keysystem is selected and before creating the session, we will do that check // so we create the strictly necessary DRM sessions if (!mediaInfo) { throw new Error('mediaInfo can not be null or undefined'); } checkConfig(); mediaInfoArr.push(mediaInfo); } /** * Once all mediaInfo objects have been added to our mediaInfoArray we can select a key system or check if the kid has changed and we need to trigger a new license request * @memberof module:ProtectionController * @instance */ function handleKeySystemFromManifest() { if (!mediaInfoArr || mediaInfoArr.length === 0) { return; } let supportedKeySystems = []; mediaInfoArr.forEach((mInfo) => { const currentKs = protectionKeyController.getSupportedKeySystemsFromContentProtection(mInfo.contentProtection, protDataSet, sessionType); // We assume that the same key systems are signaled for each AS. We can use the first entry we found if (currentKs.length > 0) { if (supportedKeySystems.length === 0) { supportedKeySystems = currentKs; } // Save config for creating key session once we selected a key system pendingKeySessionsToHandle.push(currentKs); } }) if (supportedKeySystems &amp;&amp; supportedKeySystems.length > 0) { _selectKeySystemOrUpdateKeySessions(supportedKeySystems, 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 * @private */ function _handleKeySystemFromPssh(supportedKs) { pendingKeySessionsToHandle.push(supportedKs); _selectKeySystemOrUpdateKeySessions(supportedKs, false); } /** * Select the key system or update one of our existing key sessions * @param {array} supportedKs * @param {boolean} fromManifest * @private */ function _selectKeySystemOrUpdateKeySessions(supportedKs, fromManifest) { // First time, so we need to select a key system if (!selectedKeySystem &amp;&amp; !keySystemSelectionInProgress) { _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) { _handleKeySessions(); } } /** * We do not have a key system yet. Select one * @param {array} supportedKs * @param {boolean} fromManifest * @private */ function _selectInitialKeySystem(supportedKs, fromManifest) { if (!keySystemSelectionInProgress) { keySystemSelectionInProgress = true; const requestedKeySystems = []; // Reorder key systems according to priority order provided in protectionData supportedKs = supportedKs.sort((ksA, ksB) => { let indexA = (protDataSet &amp;&amp; protDataSet[ksA.ks.systemString] &amp;&amp; protDataSet[ksA.ks.systemString].priority >= 0) ? protDataSet[ksA.ks.systemString].priority : supportedKs.length; let indexB = (protDataSet &amp;&amp; protDataSet[ksB.ks.systemString] &amp;&amp; protDataSet[ksB.ks.systemString].priority >= 0) ? protDataSet[ksB.ks.systemString].priority : supportedKs.length; return indexA - indexB; }); // Add all key systems to our request list since we have yet to select a key system for (let i = 0; i &lt; supportedKs.length; i++) { const keySystemConfiguration = _getKeySystemConfiguration(supportedKs[i]); requestedKeySystems.push({ ks: supportedKs[i].ks, configs: [keySystemConfiguration], protData: supportedKs[i].protData }); } let keySystemAccess; protectionModel.requestKeySystemAccess(requestedKeySystems) .then((event) => { keySystemAccess = event.data; let selectedSystemString = keySystemAccess.mksa &amp;&amp; keySystemAccess.mksa.selectedSystemString ? keySystemAccess.mksa.selectedSystemString : keySystemAccess.keySystem.systemString; logger.info('DRM: KeySystem Access Granted for system string (' + selectedSystemString + ')! 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 &amp;&amp; protData.serverCertificate &amp;&amp; protData.serverCertificate.length > 0) { protectionModel.setServerCertificate(BASE64.decodeArray(protData.serverCertificate).buffer); } _handleKeySessions(); }) .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 key system we only need to create a new key session and issue a new license request if the init data has changed. * @private */ function _handleKeySessions() { // Create key sessions for the different AdaptationSets let ksIdx; for (let i = 0; i &lt; pendingKeySessionsToHandle.length; i++) { for (ksIdx = 0; ksIdx &lt; pendingKeySessionsToHandle[i].length; ksIdx++) { if (selectedKeySystem === pendingKeySessionsToHandle[i][ksIdx].ks) { const current = pendingKeySessionsToHandle[i][ksIdx] _loadOrCreateKeySession(current) break; } } } pendingKeySessionsToHandle = []; } /** * Loads an existing key session if we already have a session id. Otherwise we create a new key session * @param {object} keySystemInfo * @private */ function _loadOrCreateKeySession(keySystemInfo) { // Clearkey if (protectionKeyController.isClearKey(selectedKeySystem)) { // For Clearkey: if parameters for generating init data was provided by the user, use them for generating // initData and overwrite possible initData indicated in encrypted event (EME) if (keySystemInfo.protData &amp;&amp; keySystemInfo.protData.hasOwnProperty('clearkeys') &amp;&amp; Object.keys(keySystemInfo.protData.clearkeys).length !== 0) { const initData = { kids: Object.keys(keySystemInfo.protData.clearkeys) }; keySystemInfo.initData = new TextEncoder().encode(JSON.stringify(initData)); } } // Reuse existing KeySession if (keySystemInfo.sessionId) { // Load MediaKeySession with sessionId loadKeySession(keySystemInfo); } // Create a new KeySession else if (keySystemInfo.initData !== null) { // Create new MediaKeySession with initData createKeySession(keySystemInfo); } } /** * Loads a key session with the given session ID from persistent storage. This essentially creates a new key session * * @param {object} ksInfo * @memberof module:ProtectionController * @instance * @fires ProtectionController#KeySessionCreated * @ignore */ function loadKeySession(keySystemInfo) { checkConfig(); protectionModel.loadKeySession(keySystemInfo); } /** * 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 * @ignore */ function createKeySession(keySystemInfo) { const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, keySystemInfo ? keySystemInfo.initData : null); if (initDataForKS) { // Check for duplicate key id if (_isKeyIdDuplicate(keySystemInfo.keyId)) { return; } // Check for duplicate initData if (_isInitDataDuplicate(initDataForKS)) { return; } try { keySystemInfo.initData = initDataForKS; protectionModel.createKeySession(keySystemInfo); } catch (error) { eventBus.trigger(events.KEY_SESSION_CREATED, { data: null, error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + error.message) }); } } else if (keySystemInfo &amp;&amp; keySystemInfo.initData) { protectionModel.createKeySession(keySystemInfo); } 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 ' + (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; } /** * 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.&lt;Object>} cps - array of content protection elements parsed * from the manifest * @returns {Array.&lt;Object>} array of objects indicating which supported key * systems were found. Empty array is returned if no * supported key systems were found * @memberof module:ProtectionKeyController * @instance * @ignore */ function getSupportedKeySystemsFromContentProtection(cps) { checkConfig(); return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps, protDataSet, sessionType); } /** * Checks if a session has already created for the provided key id * @param {string} keyId * @return {boolean} * @private */ function _isKeyIdDuplicate(keyId) { if (!keyId) { return false; } try { const sessions = protectionModel.getSessions(); for (let i = 0; i &lt; sessions.length; i++) { if (sessions[i].getKeyId() === keyId) { return true; } } return false; } catch (e) { return false; } } /** * Checks if the provided init data is equal to one of the existing init data values * @param {any} initDataForKS * @return {boolean} * @private */ function _isInitDataDuplicate(initDataForKS) { if (!initDataForKS) { return false; } try { const currentInitData = protectionModel.getAllInitData(); for (let i = 0; i &lt; currentInitData.length; i++) { if (protectionKeyController.initDataEquals(initDataForKS, currentInitData[i])) { logger.debug('DRM: Ignoring initData because we have already seen it!'); return true; } } return false; } catch (e) { return false; } } /** * Removes the given key session from persistent storage and closes the session * as if {@link ProtectionController#closeKeySession} * was called * * @param {SessionToken} sessionToken the session * token * @memberof module:ProtectionController * @instance * @fires ProtectionController#KeySessionRemoved * @fires ProtectionController#KeySessionClosed * @ignore */ function removeKeySession(sessionToken) { checkConfig(); protectionModel.removeKeySession(sessionToken); } /** * Closes the key session and releases all associated decryption keys. These * keys will no longer be available for decrypting media * * @param {SessionToken} sessionToken the session * token * @memberof module:ProtectionController * @instance * @fires ProtectionController#KeySessionClosed * @ignore */ function closeKeySession(sessionToken) { checkConfig(); protectionModel.closeKeySession(sessionToken); } /** * Sets a server certificate for use by the CDM when signing key messages * intended for a particular license server. This will fire * an error event if a key system has not yet been selected. * * @param {ArrayBuffer} serverCertificate a CDM-specific license server * certificate * @memberof module:ProtectionController * @instance * @fires ProtectionController#ServerCertificateUpdated */ function setServerCertificate(serverCertificate) { checkConfig(); protectionModel.setServerCertificate(serverCertificate); } /** * Associate this protection system with the given HTMLMediaElement. This * causes the system to register for needkey/encrypted events from the given * element and provides a destination for setting of MediaKeys * * @param {HTMLMediaElement} element the media element to which the protection * system should be associated * @memberof module:ProtectionController * @instance */ function setMediaElement(element) { checkConfig(); if (element) { protectionModel.setMediaElement(element); eventBus.on(events.NEED_KEY, _onNeedKey, instance); } else if (element === null) { protectionModel.setMediaElement(element); eventBus.off(events.NEED_KEY, _onNeedKey, instance); } } /** * Sets the session type to use when creating key sessions. Either "temporary" or * "persistent-license". Default is "temporary". * * @param {string} value the session type * @memberof module:ProtectionController * @instance */ function setSessionType(value) { sessionType = value; } /** * Sets the robustness level for video and audio capabilities. Optional to remove Chrome warnings. * Possible values are SW_SECURE_CRYPTO, SW_SECURE_DECODE, HW_SECURE_CRYPTO, HW_SECURE_CRYPTO, HW_SECURE_DECODE, HW_SECURE_ALL. * * @param {string} level the robustness level * @memberof module:ProtectionController * @instance */ function setRobustnessLevel(level) { robustnessLevel = level; } /** * Attach KeySystem-specific data to use for license acquisition with EME * * @param {Object} data an object containing property names corresponding to * key system name strings (e.g. "org.w3.clearkey") and associated values * being instances of {@link ProtectionData} * @memberof module:ProtectionController * @instance * @ignore */ function setProtectionData(data) { protDataSet = data; protectionKeyController.setProtectionData(data); } /** * Stop method is called when current playback is stopped/resetted. * * @memberof module:ProtectionController * @instance */ function stop() { _abortLicenseRequest(); if (protectionModel) { protectionModel.stop(); } } /** * Destroys all protection data associated with this protection set. This includes * deleting all key sessions. In the case of persistent key sessions, the sessions * will simply be unloaded and not deleted. Additionally, if this protection set is * associated with a HTMLMediaElement, it will be detached from that element. * * @memberof module:ProtectionController * @instance * @ignore */ function reset() { eventBus.off(events.INTERNAL_KEY_MESSAGE, _onKeyMessage, instance); eventBus.off(events.INTERNAL_KEY_STATUS_CHANGED, _onKeyStatusChanged, instance); checkConfig(); _abortLicenseRequest(); setMediaElement(null); selectedKeySystem = null; keySystemSelectionInProgress = false; if (protectionModel) { protectionModel.reset(); protectionModel = null; } needkeyRetries.forEach(retryTimeout => clearTimeout(retryTimeout)); needkeyRetries = []; mediaInfoArr = []; pendingKeySessionsToHandle = []; } /** * Returns an object corresponding to the EME MediaKeySystemConfiguration dictionary * @param {object} keySystem * @return {KeySystemConfiguration} * @private */ function _getKeySystemConfiguration(keySystemData) { const protData = keySystemData.protData; const audioCapabilities = []; const videoCapabilities = []; const audioRobustness = (protData &amp;&amp; protData.audioRobustness &amp;&amp; protData.audioRobustness.length > 0) ? protData.audioRobustness : robustnessLevel; const videoRobustness = (protData &amp;&amp; protData.videoRobustness &amp;&amp; protData.videoRobustness.length > 0) ? protData.videoRobustness : robustnessLevel; const ksSessionType = keySystemData.sessionType; const distinctiveIdentifier = (protData &amp;&amp; protData.distinctiveIdentifier) ? protData.distinctiveIdentifier : 'optional'; const persistentState = (protData &amp;&amp; protData.persistentState) ? protData.persistentState : (ksSessionType === 'temporary') ? 'optional' : 'required'; mediaInfoArr.forEach((media) => { if (media.type === constants.AUDIO) { audioCapabilities.push(new MediaCapability(media.codec, audioRobustness)); } else if (media.type === constants.VIDEO) { videoCapabilities.push(new MediaCapability(media.codec, videoRobustness)); } }); return new KeySystemConfiguration( audioCapabilities, videoCapabilities, distinctiveIdentifier, persistentState, [ksSessionType]); } /** * 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 { logger.debug('DRM: key status = ' + e.status); } } /** * 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 const keyMessage = e.data; eventBus.trigger(events.KEY_MESSAGE, { data: keyMessage }); const messageType = (keyMessage.messageType) ? keyMessage.messageType : 'license-request'; const message = keyMessage.message; const sessionToken = keyMessage.sessionToken; 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)); return; } // Message not destined for license server if (!licenseServerModelInstance) { logger.debug('DRM: License server request not required for this message (type = ' + e.data.messageType + '). Session ID = ' + sessionToken.getSessionId()); _sendLicenseRequestCompleteEvent(eventData); return; } // Perform any special handling for ClearKey if (protectionKeyController.isClearKey(selectedKeySystem)) { const clearkeys = protectionKeyController.processClearKeyLicenseRequest(selectedKeySystem, protData, message); if (clearkeys &amp;&amp; clearkeys.keyPairs &amp;&amp; clearkeys.keyPairs.length > 0) { logger.debug('DRM: ClearKey license request handled by application!'); _sendLicenseRequestCompleteEvent(eventData); protectionModel.updateKeySession(sessionToken, clearkeys); return; } } // 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 = _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)); return; } // Set optional XMLHttpRequest headers from protection data and message const reqHeaders = {}; let withCredentials = false; if (protData) { _updateHeaders(reqHeaders, protData.httpRequestHeaders); } 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 &amp;&amp; typeof protData.withCredentials == 'boolean') { withCredentials = protData.withCredentials; } const onLoad = function (xhr) { if (!protectionModel) { return; } if (xhr.status >= 200 &amp;&amp; xhr.status &lt;= 299) { const responseHeaders = Utils.parseHttpHeaders(xhr.getAllResponseHeaders ? xhr.getAllResponseHeaders() : null); let licenseResponse = new LicenseResponse(xhr.responseURL, responseHeaders, xhr.response); const licenseResponseFilters = customParametersModel.getLicenseResponseFilters(); _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, licenseServerData); } }; const onAbort = function (xhr) { _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, ProtectionErrors.MEDIA_KEY_MESSAGE_LICENSER_ERROR_MESSAGE + keySystemString + ' update, XHR error. status is "' + xhr.statusText + '" (' + xhr.status + '), readyState is ' + xhr.readyState)); }; const reqPayload = selectedKeySystem.getLicenseRequestFromMessage(message); const reqMethod = licenseServerData.getHTTPMethod(messageType); const responseType = licenseServerData.getResponseType(keySystemString, messageType); const timeout = protData &amp;&amp; !isNaN(protData.httpTimeout) ? protData.httpTimeout : LICENSE_SERVER_REQUEST_DEFAULT_TIMEOUT; const sessionId = sessionToken.getSessionId() || null; let licenseRequest = new LicenseRequest(url, reqMethod, responseType, reqHeaders, withCredentials, messageType, sessionId, reqPayload); const retryAttempts = !isNaN(settings.get().streaming.retryAttempts[HTTPRequest.LICENSE]) ? settings.get().streaming.retryAttempts[HTTPRequest.LICENSE] : LICENSE_SERVER_REQUEST_RETRIES; const licenseRequestFilters = customParametersModel.getLicenseRequestFilters(); _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 * @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 &amp;&amp; settings.get().streaming.cmcd.enabled) { const cmcdMode = settings.get().streaming.cmcd.mode; if (cmcdMode === Constants.CMCD_MODE_QUERY) { const cmcdParams = cmcdModel.getQueryParameter({ url: request.url, type: HTTPRequest.LICENSE }); if (cmcdParams) { request.url = Utils.addAditionalQueryParameterToUrl(request.url, [cmcdParams]); } } } xhr.open(request.method, request.url, true); xhr.responseType = request.responseType; xhr.withCredentials = request.withCredentials; if (timeout > 0) { xhr.timeout = timeout; } for (const key in request.headers) { xhr.setRequestHeader(key, request.headers[key]); } if (settings.get().streaming.cmcd &amp;&amp; settings.get().streaming.cmcd.enabled) { const cmcdMode = settings.get().streaming.cmcd.mode; if (cmcdMode === Constants.CMCD_MODE_HEADER) { const cmcdHeaders = cmcdModel.getHeaderParameters({ url: request.url, type: HTTPRequest.LICENSE }); if (cmcdHeaders) { for (const header in cmcdHeaders) { let value = cmcdHeaders[header]; if (value) { xhr.setRequestHeader(header, value); } } } } } 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; licenseRequestRetryTimeout = setTimeout(function () { _doLicenseRequest(request, retriesCount, timeout, onLoad, onAbort, onError); }, retryInterval); }; xhr.onload = function () { licenseXhrRequest = null; if (this.status >= 200 &amp;&amp; this.status &lt;= 299 || retriesCount &lt;= 0) { onLoad(this); } else { logger.warn('License request failed (' + this.status + '). Retrying it... Pending retries: ' + retriesCount); _retryRequest(); } }; xhr.ontimeout = xhr.onerror = function () { licenseXhrRequest = null; if (retriesCount &lt;= 0) { onError(this); } else { logger.warn('License request network request failed . Retrying it... Pending retries: ' + retriesCount); _retryRequest(); } }; xhr.onabort = function () { onAbort(this); }; // deprecated, to be removed eventBus.trigger(events.LICENSE_REQUEST_SENDING, {