dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
72 lines (71 loc) • 15.7 kB
JavaScript
;Object.defineProperty(exports,"__esModule",{value:true});var _ProtectionKeyController=require('../controllers/ProtectionKeyController');var _ProtectionKeyController2=_interopRequireDefault(_ProtectionKeyController);var _NeedKey=require('../vo/NeedKey');var _NeedKey2=_interopRequireDefault(_NeedKey);var _ProtectionErrors=require('../errors/ProtectionErrors');var _ProtectionErrors2=_interopRequireDefault(_ProtectionErrors);var _DashJSError=require('../../vo/DashJSError');var _DashJSError2=_interopRequireDefault(_DashJSError);var _KeyMessage=require('../vo/KeyMessage');var _KeyMessage2=_interopRequireDefault(_KeyMessage);var _KeySystemAccess=require('../vo/KeySystemAccess');var _KeySystemAccess2=_interopRequireDefault(_KeySystemAccess);var _ProtectionConstants=require('../../constants/ProtectionConstants');var _ProtectionConstants2=_interopRequireDefault(_ProtectionConstants);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function ProtectionModel_21Jan2015(config){config=config||{};var context=this.context;var eventBus=config.eventBus;//Need to pass in here so we can use same instance since this is optional module
var events=config.events;var debug=config.debug;var instance=void 0,logger=void 0,keySystem=void 0,videoElement=void 0,mediaKeys=void 0,sessions=void 0,eventHandler=void 0,protectionKeyController=void 0;function setup(){logger=debug.getLogger(instance);keySystem=null;videoElement=null;mediaKeys=null;sessions=[];protectionKeyController=(0,_ProtectionKeyController2.default)(context).getInstance();eventHandler=createEventHandler();}function reset(){var numSessions=sessions.length;var session=void 0;if(numSessions!==0){(function(){// Called when we are done closing a session. Success or fail
var done=function done(session){removeSession(session);if(sessions.length===0){if(videoElement){videoElement.removeEventListener('encrypted',eventHandler);videoElement.setMediaKeys(null).then(function(){eventBus.trigger(events.TEARDOWN_COMPLETE);});}else{eventBus.trigger(events.TEARDOWN_COMPLETE);}}};for(var i=0;i<numSessions;i++){session=sessions[i];(function(s){// Override closed promise resolver
session.session.closed.then(function(){done(s);});// Close the session and handle errors, otherwise promise
// resolver above will be called
closeKeySessionInternal(session).catch(function(){done(s);});})(session);}})();}else{eventBus.trigger(events.TEARDOWN_COMPLETE);}}function stop(){// Close and remove not usable sessions
var session=void 0;for(var i=0;i<sessions.length;i++){session=sessions[i];if(!session.getUsable()){closeKeySessionInternal(session).catch(function(){removeSession(session);});}}}function getKeySystem(){return keySystem;}function getAllInitData(){var retVal=[];for(var i=0;i<sessions.length;i++){if(sessions[i].initData){retVal.push(sessions[i].initData);}}return retVal;}function requestKeySystemAccess(ksConfigurations){requestKeySystemAccessInternal(ksConfigurations,0);}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);});}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){if(videoElement===mediaElement)return;// Replacing the previous element
if(videoElement){videoElement.removeEventListener('encrypted',eventHandler);if(videoElement.setMediaKeys){videoElement.setMediaKeys(null);}}videoElement=mediaElement;// Only if we are not detaching from the existing element
if(videoElement){videoElement.addEventListener('encrypted',eventHandler);if(videoElement.setMediaKeys&&mediaKeys){videoElement.setMediaKeys(mediaKeys);}}}function setServerCertificate(serverCertificate){if(!keySystem||!mediaKeys){throw new Error('Can not set server certificate until you have selected a key system');}mediaKeys.setServerCertificate(serverCertificate).then(function(){logger.info('DRM: License server certificate successfully updated.');eventBus.trigger(events.SERVER_CERTIFICATE_UPDATED);}).catch(function(error){eventBus.trigger(events.SERVER_CERTIFICATE_UPDATED,{error:new _DashJSError2.default(_ProtectionErrors2.default.SERVER_CERTIFICATE_UPDATED_ERROR_CODE,_ProtectionErrors2.default.SERVER_CERTIFICATE_UPDATED_ERROR_MESSAGE+error.name)});});}function createKeySession(initData,protData,sessionType){if(!keySystem||!mediaKeys){throw new Error('Can not create sessions until you have selected a key system');}var session=mediaKeys.createSession(sessionType);var sessionToken=createSessionToken(session,initData,sessionType);var 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
var dataType=ks.systemString===_ProtectionConstants2.default.CLEARKEY_KEYSTEM_STRING&&protData&&protData.clearkeys?'keyids':'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,error:new _DashJSError2.default(_ProtectionErrors2.default.KEY_SESSION_CREATED_ERROR_CODE,_ProtectionErrors2.default.KEY_SESSION_CREATED_ERROR_MESSAGE+'Error generating key request -- '+error.name)});});}function updateKeySession(sessionToken,message){var session=sessionToken.session;// Send our request to the key session
if(protectionKeyController.isClearKey(keySystem)){message=message.toJWK();}session.update(message).catch(function(error){eventBus.trigger(events.KEY_ERROR,{data:new _DashJSError2.default(_ProtectionErrors2.default.MEDIA_KEYERR_CODE,'Error sending update() message! '+error.name,sessionToken)});});}function loadKeySession(sessionID,initData,sessionType){if(!keySystem||!mediaKeys){throw new Error('Can not load sessions until you have selected a key system');}// Check if session Id is not already loaded or loading
for(var i=0;i<sessions.length;i++){if(sessionID===sessions[i].sessionId){logger.warn('DRM: Ignoring session ID because we have already seen it!');return;}}var session=mediaKeys.createSession(sessionType);var sessionToken=createSessionToken(session,initData,sessionType,sessionID);// Load persisted session data into our newly created session object
session.load(sessionID).then(function(success){if(success){logger.debug('DRM: Session loaded. SessionID = '+sessionToken.getSessionID());eventBus.trigger(events.KEY_SESSION_CREATED,{data:sessionToken});}else{removeSession(sessionToken);eventBus.trigger(events.KEY_SESSION_CREATED,{data:null,error:new _DashJSError2.default(_ProtectionErrors2.default.KEY_SESSION_CREATED_ERROR_CODE,_ProtectionErrors2.default.KEY_SESSION_CREATED_ERROR_MESSAGE+'Could not load session! Invalid Session ID ('+sessionID+')')});}}).catch(function(error){removeSession(sessionToken);eventBus.trigger(events.KEY_SESSION_CREATED,{data:null,error:new _DashJSError2.default(_ProtectionErrors2.default.KEY_SESSION_CREATED_ERROR_CODE,_ProtectionErrors2.default.KEY_SESSION_CREATED_ERROR_MESSAGE+'Could not load session ('+sessionID+')! '+error.name)});});}function removeKeySession(sessionToken){var session=sessionToken.session;session.remove().then(function(){logger.debug('DRM: Session removed. SessionID = '+sessionToken.getSessionID());eventBus.trigger(events.KEY_SESSION_REMOVED,{data:sessionToken.getSessionID()});},function(error){eventBus.trigger(events.KEY_SESSION_REMOVED,{data:null,error:'Error removing session ('+sessionToken.getSessionID()+'). '+error.name});});}function closeKeySession(sessionToken){// Send our request to the key session
closeKeySessionInternal(sessionToken).catch(function(error){removeSession(sessionToken);eventBus.trigger(events.KEY_SESSION_CLOSED,{data:null,error:'Error closing session ('+sessionToken.getSessionID()+') '+error.name});});}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){var keySystem=ksConfigurations[i].ks;var configs=ksConfigurations[i].configs;var systemString=keySystem.systemString;// PATCH to support persistent licenses on Edge browser (see issue #2658)
if(systemString===_ProtectionConstants2.default.PLAYREADY_KEYSTEM_STRING&&configs[0].persistentState==='required'){systemString+='.recommendation';}navigator.requestMediaKeySystemAccess(systemString,configs).then(function(mediaKeySystemAccess){// Chrome 40 does not currently implement MediaKeySystemAccess.getConfiguration()
var configuration=typeof mediaKeySystemAccess.getConfiguration==='function'?mediaKeySystemAccess.getConfiguration():null;var keySystemAccess=new _KeySystemAccess2.default(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){var session=sessionToken.session;// Remove event listeners
session.removeEventListener('keystatuseschange',sessionToken);session.removeEventListener('message',sessionToken);// Send our request to the key session
return session.close();}// This is our main event handler for all desired HTMLMediaElement events
// related to EME. These events are translated into our API-independent
// versions of the same events
function createEventHandler(){return{handleEvent:function handleEvent(event){switch(event.type){case'encrypted':if(event.initData){var initData=ArrayBuffer.isView(event.initData)?event.initData.buffer:event.initData;eventBus.trigger(events.NEED_KEY,{key:new _NeedKey2.default(initData,event.initDataType)});}break;}}};}function removeSession(token){// Remove from our session list
for(var i=0;i<sessions.length;i++){if(sessions[i]===token){sessions.splice(i,1);break;}}}function parseKeyStatus(args){// Edge and Chrome implement different version of keystatues, param are not on same order
var status=void 0,keyId=void 0;if(args&&args.length>0){if(args[0]){if(typeof args[0]==='string'){status=args[0];}else{keyId=args[0];}}if(args[1]){if(typeof args[1]==='string'){status=args[1];}else{keyId=args[1];}}}return{status:status,keyId:keyId};}// Function to create our session token objects which manage the EME
// MediaKeySession and session-specific event handler
function createSessionToken(session,initData,sessionType,sessionID){var token={// Implements SessionToken
session:session,initData:initData,sessionId:sessionID,// This is our main event handler for all desired MediaKeySession events
// These events are translated into our API-independent versions of the
// same events
handleEvent:function handleEvent(event){switch(event.type){case'keystatuseschange':eventBus.trigger(events.KEY_STATUSES_CHANGED,{data:this});event.target.keyStatuses.forEach(function(){var keyStatus=parseKeyStatus(arguments);switch(keyStatus.status){case'expired':eventBus.trigger(events.INTERNAL_KEY_STATUS_CHANGED,{error:new _DashJSError2.default(_ProtectionErrors2.default.KEY_STATUS_CHANGED_EXPIRED_ERROR_CODE,_ProtectionErrors2.default.KEY_STATUS_CHANGED_EXPIRED_ERROR_MESSAGE)});break;default:eventBus.trigger(events.INTERNAL_KEY_STATUS_CHANGED,keyStatus);break;}});break;case'message':var message=ArrayBuffer.isView(event.message)?event.message.buffer:event.message;eventBus.trigger(events.INTERNAL_KEY_MESSAGE,{data:new _KeyMessage2.default(this,message,undefined,event.messageType)});break;}},getSessionID:function getSessionID(){return session.sessionId;},getExpirationTime:function getExpirationTime(){return session.expiration;},getKeyStatuses:function getKeyStatuses(){return session.keyStatuses;},getUsable:function getUsable(){var usable=false;session.keyStatuses.forEach(function(){var keyStatus=parseKeyStatus(arguments);if(keyStatus.status==='usable'){usable=true;}});return usable;},getSessionType:function getSessionType(){return sessionType;}};// Add all event listeners
session.addEventListener('keystatuseschange',token);session.addEventListener('message',token);// Register callback for session closed Promise
session.closed.then(function(){removeSession(token);logger.debug('DRM: Session closed. SessionID = '+token.getSessionID());eventBus.trigger(events.KEY_SESSION_CLOSED,{data:token.getSessionID()});});// Add to our session list
sessions.push(token);return token;}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};setup();return instance;}/**
* 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.
*//**
* Most recent EME implementation
*
* Implemented by Google Chrome v36+ (Windows, OSX, Linux)
*
* @implements ProtectionModel
* @class
*/ProtectionModel_21Jan2015.__dashjs_factory_name='ProtectionModel_21Jan2015';exports.default=dashjs.FactoryMaker.getClassFactory(ProtectionModel_21Jan2015);/* jshint ignore:line */
//# sourceMappingURL=ProtectionModel_21Jan2015.js.map