@l5i/dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
78 lines (77 loc) • 13 kB
JavaScript
/**
* 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.
*/ /**
* Implementation of the EME APIs as of the 3 Feb 2014 state of the specification.
*
* Implemented by Internet Explorer 11 (Windows 8.1)
*
* @implements ProtectionModel
* @class
*/'use strict';Object.defineProperty(exports,'__esModule',{value:true});function _interopRequireDefault(obj){return obj && obj.__esModule?obj:{'default':obj};}var _controllersProtectionKeyController=require('../controllers/ProtectionKeyController');var _controllersProtectionKeyController2=_interopRequireDefault(_controllersProtectionKeyController);var _voNeedKey=require('../vo/NeedKey');var _voNeedKey2=_interopRequireDefault(_voNeedKey);var _voDashJSError=require('../../vo/DashJSError');var _voDashJSError2=_interopRequireDefault(_voDashJSError);var _errorsProtectionErrors=require('../errors/ProtectionErrors');var _errorsProtectionErrors2=_interopRequireDefault(_errorsProtectionErrors);var _voKeyMessage=require('../vo/KeyMessage');var _voKeyMessage2=_interopRequireDefault(_voKeyMessage);var _voKeySystemConfiguration=require('../vo/KeySystemConfiguration');var _voKeySystemConfiguration2=_interopRequireDefault(_voKeySystemConfiguration);var _voKeySystemAccess=require('../vo/KeySystemAccess');var _voKeySystemAccess2=_interopRequireDefault(_voKeySystemAccess);function ProtectionModel_3Feb2014(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 api=config.api;var instance=undefined,logger=undefined,videoElement=undefined,keySystem=undefined,mediaKeys=undefined,keySystemAccess=undefined,sessions=undefined,eventHandler=undefined,protectionKeyController=undefined;function setup(){logger = debug.getLogger(instance);videoElement = null;keySystem = null;mediaKeys = null;keySystemAccess = null;sessions = [];protectionKeyController = (0,_controllersProtectionKeyController2['default'])(context).getInstance();eventHandler = createEventHandler();}function reset(){try{for(var i=0;i < sessions.length;i++) {closeKeySession(sessions[i]);}if(videoElement){videoElement.removeEventListener(api.needkey,eventHandler);}eventBus.trigger(events.TEARDOWN_COMPLETE);}catch(error) {eventBus.trigger(events.TEARDOWN_COMPLETE,{error:'Error tearing down key sessions and MediaKeys! -- ' + error.message});}}function getKeySystem(){return keySystem;}function getAllInitData(){var retVal=[];for(var i=0;i < sessions.length;i++) {retVal.push(sessions[i].initData);}return retVal;}function requestKeySystemAccess(ksConfigurations){ // Try key systems in order, first one with supported key system configuration
// is used
var found=false;for(var ksIdx=0;ksIdx < ksConfigurations.length;ksIdx++) {var systemString=ksConfigurations[ksIdx].ks.systemString;var configs=ksConfigurations[ksIdx].configs;var supportedAudio=null;var supportedVideo=null; // Try key system configs in order, first one with supported audio/video
// is used
for(var configIdx=0;configIdx < configs.length;configIdx++) {var audios=configs[configIdx].audioCapabilities;var 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(var 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(var 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;} // This configuration is supported
found = true;var ksConfig=new _voKeySystemConfiguration2['default'](supportedAudio,supportedVideo);var ks=protectionKeyController.getKeySystemBySystemString(systemString);eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE,{data:new _voKeySystemAccess2['default'](ks,ksConfig)});break;}}if(!found){eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE,{error:'Key system access denied! -- No valid audio/video content configurations detected!'});}}function selectKeySystem(ksAccess){try{mediaKeys = ksAccess.mediaKeys = new window[api.MediaKeys](ksAccess.keySystem.systemString);keySystem = ksAccess.keySystem;keySystemAccess = ksAccess;if(videoElement){setMediaKeys();}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){if(videoElement === mediaElement)return; // Replacing the previous element
if(videoElement){videoElement.removeEventListener(api.needkey,eventHandler);}videoElement = mediaElement; // Only if we are not detaching from the existing element
if(videoElement){videoElement.addEventListener(api.needkey,eventHandler);if(mediaKeys){setMediaKeys();}}}function createKeySession(initData,protData,sessionType,cdmData){if(!keySystem || !mediaKeys || !keySystemAccess){throw new Error('Can not create sessions until you have selected a key system');} // Use the first video capability for the contentType.
// TODO: Not sure if there is a way to concatenate all capability data into a RFC6386-compatible format
// If player is trying to playback Audio only stream - don't error out.
var capabilities=null;if(keySystemAccess.ksConfiguration.videoCapabilities && keySystemAccess.ksConfiguration.videoCapabilities.length > 0){capabilities = keySystemAccess.ksConfiguration.videoCapabilities[0];}if(capabilities === null && keySystemAccess.ksConfiguration.audioCapabilities && keySystemAccess.ksConfiguration.audioCapabilities.length > 0){capabilities = keySystemAccess.ksConfiguration.audioCapabilities[0];}if(capabilities === null){throw new Error('Can not create sessions for unknown content types.');}var contentType=capabilities.contentType;var session=mediaKeys.createSession(contentType,new Uint8Array(initData),cdmData?new Uint8Array(cdmData):null);var sessionToken=createSessionToken(session,initData); // Add all event listeners
session.addEventListener(api.error,sessionToken);session.addEventListener(api.message,sessionToken);session.addEventListener(api.ready,sessionToken);session.addEventListener(api.close,sessionToken); // Add to our session list
sessions.push(sessionToken);logger.debug('DRM: Session created. SessionID = ' + sessionToken.getSessionID());eventBus.trigger(events.KEY_SESSION_CREATED,{data:sessionToken});}function updateKeySession(sessionToken,message){var session=sessionToken.session;if(!protectionKeyController.isClearKey(keySystem)){ // Send our request to the key session
session.update(new Uint8Array(message));}else { // For clearkey, message is a ClearKeyKeySet
session.update(new Uint8Array(message.toJWK()));}} /**
* Close the given session and release all associated keys. Following
* this call, the sessionToken becomes invalid
*
* @param {Object} sessionToken - the session token
*/function closeKeySession(sessionToken){var session=sessionToken.session; // Remove event listeners
session.removeEventListener(api.error,sessionToken);session.removeEventListener(api.message,sessionToken);session.removeEventListener(api.ready,sessionToken);session.removeEventListener(api.close,sessionToken); // Remove from our session list
for(var i=0;i < sessions.length;i++) {if(sessions[i] === sessionToken){sessions.splice(i,1);break;}} // Send our request to the key session
session[api.release]();}function setServerCertificate() /*serverCertificate*/{ /* Not supported */}function loadKeySession() /*sessionID*/{ /* Not supported */}function removeKeySession() /*sessionToken*/{ /* Not supported */}function createEventHandler(){return {handleEvent:function handleEvent(event){switch(event.type){case api.needkey:if(event.initData){var initData=ArrayBuffer.isView(event.initData)?event.initData.buffer:event.initData;eventBus.trigger(events.NEED_KEY,{key:new _voNeedKey2['default'](initData,'cenc')});}break;}}};} // IE11 does not let you set MediaKeys until it has entered a certain
// readyState, so we need this logic to ensure we don't set the keys
// too early
function setMediaKeys(){var boundDoSetKeys=null;var doSetKeys=function doSetKeys(){videoElement.removeEventListener('loadedmetadata',boundDoSetKeys);videoElement[api.setMediaKeys](mediaKeys);eventBus.trigger(events.VIDEO_ELEMENT_SELECTED);};if(videoElement.readyState >= 1){doSetKeys();}else {boundDoSetKeys = doSetKeys.bind(this);videoElement.addEventListener('loadedmetadata',boundDoSetKeys);}} // Function to create our session token objects which manage the EME
// MediaKeySession and session-specific event handler
function createSessionToken(keySession,initData){return { // Implements SessionToken
session:keySession,initData:initData,getSessionID:function getSessionID(){return this.session.sessionId;},getExpirationTime:function getExpirationTime(){return NaN;},getSessionType:function getSessionType(){return 'temporary';}, // This is our main event handler for all desired MediaKeySession events
// These events are translated into our API-independent versions of the
// same events
handleEvent:function handleEvent(event){switch(event.type){case api.error:var errorStr='KeyError'; // TODO: Make better string from event
eventBus.trigger(events.KEY_ERROR,{data:new _voDashJSError2['default'](_errorsProtectionErrors2['default'].MEDIA_KEYERR_CODE,errorStr,this)});break;case api.message:var message=ArrayBuffer.isView(event.message)?event.message.buffer:event.message;eventBus.trigger(events.INTERNAL_KEY_MESSAGE,{data:new _voKeyMessage2['default'](this,message,event.destinationURL)});break;case api.ready:logger.debug('DRM: Key added.');eventBus.trigger(events.KEY_ADDED);break;case api.close:logger.debug('DRM: Session closed. SessionID = ' + this.getSessionID());eventBus.trigger(events.KEY_SESSION_CLOSED,{data:this.getSessionID()});break;}}};}instance = {getAllInitData:getAllInitData,requestKeySystemAccess:requestKeySystemAccess,getKeySystem:getKeySystem,selectKeySystem:selectKeySystem,setMediaElement:setMediaElement,createKeySession:createKeySession,updateKeySession:updateKeySession,closeKeySession:closeKeySession,setServerCertificate:setServerCertificate,loadKeySession:loadKeySession,removeKeySession:removeKeySession,stop:reset,reset:reset};setup();return instance;}ProtectionModel_3Feb2014.__dashjs_factory_name = 'ProtectionModel_3Feb2014';exports['default'] = dashjs.FactoryMaker.getClassFactory(ProtectionModel_3Feb2014); /* jshint ignore:line */module.exports = exports['default'];
//# sourceMappingURL=ProtectionModel_3Feb2014.js.map