OLD | NEW |
(Empty) | |
| 1 (function(){ |
| 2 |
| 3 // Save platform functions that will be modified |
| 4 var _requestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess.bin
d( navigator ), |
| 5 _setMediaKeys = HTMLMediaElement.prototype.setMediaKeys; |
| 6 |
| 7 // Allow us to modify the target of Events |
| 8 Object.defineProperties( Event.prototype, { |
| 9 target: { get: function() { return this._target || this.currentTarget;
}, |
| 10 set: function( newtarget ) { this._target = newtarget; } } |
| 11 } ); |
| 12 |
| 13 var EventTarget = function(){ |
| 14 this.listeners = {}; |
| 15 }; |
| 16 |
| 17 EventTarget.prototype.listeners = null; |
| 18 |
| 19 EventTarget.prototype.addEventListener = function(type, callback){ |
| 20 if(!(type in this.listeners)) { |
| 21 this.listeners[type] = []; |
| 22 } |
| 23 this.listeners[type].push(callback); |
| 24 }; |
| 25 |
| 26 EventTarget.prototype.removeEventListener = function(type, callback){ |
| 27 if(!(type in this.listeners)) { |
| 28 return; |
| 29 } |
| 30 var stack = this.listeners[type]; |
| 31 for(var i = 0, l = stack.length; i < l; i++){ |
| 32 if(stack[i] === callback){ |
| 33 stack.splice(i, 1); |
| 34 return this.removeEventListener(type, callback); |
| 35 } |
| 36 } |
| 37 }; |
| 38 |
| 39 EventTarget.prototype.dispatchEvent = function(event){ |
| 40 if(!(event.type in this.listeners)) { |
| 41 return; |
| 42 } |
| 43 var stack = this.listeners[event.type]; |
| 44 event.target = this; |
| 45 for(var i = 0, l = stack.length; i < l; i++) { |
| 46 stack[i].call(this, event); |
| 47 } |
| 48 }; |
| 49 |
| 50 function MediaKeySystemAccessProxy( keysystem, access, configuration ) |
| 51 { |
| 52 this._keysystem = keysystem; |
| 53 this._access = access; |
| 54 this._configuration = configuration; |
| 55 } |
| 56 |
| 57 Object.defineProperties( MediaKeySystemAccessProxy.prototype, { |
| 58 keysystem: { get: function() { return this._keysystem; } } |
| 59 }); |
| 60 |
| 61 MediaKeySystemAccessProxy.prototype.getConfiguration = function getConfigura
tion() |
| 62 { |
| 63 return this._configuration; |
| 64 }; |
| 65 |
| 66 MediaKeySystemAccessProxy.prototype.createMediaKeys = function createMediaKe
ys() |
| 67 { |
| 68 return new Promise( function( resolve, reject ) { |
| 69 |
| 70 this._access.createMediaKeys() |
| 71 .then( function( mediaKeys ) { resolve( new MediaKeysProxy( mediaKey
s ) ); }) |
| 72 .catch( function( error ) { reject( error ); } ); |
| 73 |
| 74 }.bind( this ) ); |
| 75 }; |
| 76 |
| 77 function MediaKeysProxy( mediaKeys ) |
| 78 { |
| 79 this._mediaKeys = mediaKeys; |
| 80 this._sessions = [ ]; |
| 81 this._videoelement = undefined; |
| 82 this._onTimeUpdateListener = MediaKeysProxy.prototype._onTimeUpdate.bind
( this ); |
| 83 } |
| 84 |
| 85 MediaKeysProxy.prototype._setVideoElement = function _setVideoElement( video
Element ) |
| 86 { |
| 87 if ( videoElement !== this._videoelement ) |
| 88 { |
| 89 if ( this._videoelement ) |
| 90 { |
| 91 this._videoelement.removeEventListener( 'timeupdate', this._onTi
meUpdateListener ); |
| 92 } |
| 93 |
| 94 this._videoelement = videoElement; |
| 95 |
| 96 if ( this._videoelement ) |
| 97 { |
| 98 this._videoelement.addEventListener( 'timeupdate', this._onTimeU
pdateListener ); |
| 99 } |
| 100 } |
| 101 }; |
| 102 |
| 103 MediaKeysProxy.prototype._onTimeUpdate = function( event ) |
| 104 { |
| 105 this._sessions.forEach( function( session ) { |
| 106 |
| 107 if ( session._sessionType === 'persistent-usage-record' ) |
| 108 { |
| 109 session._onTimeUpdate( event ); |
| 110 } |
| 111 |
| 112 } ); |
| 113 }; |
| 114 |
| 115 MediaKeysProxy.prototype._removeSession = function _removeSession( session ) |
| 116 { |
| 117 var index = this._sessions.indexOf( session ); |
| 118 if ( index !== -1 ) this._sessions.splice( index, 1 ); |
| 119 }; |
| 120 |
| 121 MediaKeysProxy.prototype.createSession = function createSession( sessionType
) |
| 122 { |
| 123 if ( !sessionType || sessionType === 'temporary' ) return this._mediaKey
s.createSession(); |
| 124 |
| 125 var session = new MediaKeySessionProxy( this, sessionType ); |
| 126 this._sessions.push( session ); |
| 127 |
| 128 return session; |
| 129 }; |
| 130 |
| 131 MediaKeysProxy.prototype.setServerCertificate = function setServerCertificat
e( certificate ) |
| 132 { |
| 133 return this._mediaKeys.setServerCertificate( certificate ); |
| 134 }; |
| 135 |
| 136 function MediaKeySessionProxy( mediaKeysProxy, sessionType ) |
| 137 { |
| 138 EventTarget.call( this ); |
| 139 |
| 140 this._mediaKeysProxy = mediaKeysProxy |
| 141 this._sessionType = sessionType; |
| 142 this._sessionId = ""; |
| 143 |
| 144 // MediaKeySessionProxy states |
| 145 // 'created' - After initial creation |
| 146 // 'loading' - Persistent license session waiting for key message to loa
d stored keys |
| 147 // 'active' - Normal active state - proxy all key messages |
| 148 // 'removing' - Release message generated, waiting for ack |
| 149 // 'closed' - Session closed |
| 150 this._state = 'created'; |
| 151 |
| 152 this._closed = new Promise( function( resolve ) { this._resolveClosed =
resolve; }.bind( this ) ); |
| 153 } |
| 154 |
| 155 MediaKeySessionProxy.prototype = Object.create( EventTarget.prototype ); |
| 156 |
| 157 Object.defineProperties( MediaKeySessionProxy.prototype, { |
| 158 |
| 159 sessionId: { get: function() { return this._sessionId; } }, |
| 160 expiration: { get: function() { return NaN; } }, |
| 161 closed: { get: function() { return this._closed; } }, |
| 162 keyStatuses:{ get: function() { return this._session.keyStatuses; } },
// TODO this will fail if examined too early |
| 163 _kids: { get: function() { return this._keys.map( function( key ) {
return key.kid; } ); } }, |
| 164 }); |
| 165 |
| 166 MediaKeySessionProxy.prototype._createSession = function _createSession() |
| 167 { |
| 168 this._session = this._mediaKeysProxy._mediaKeys.createSession(); |
| 169 |
| 170 this._session.addEventListener( 'message', MediaKeySessionProxy.prototyp
e._onMessage.bind( this ) ); |
| 171 this._session.addEventListener( 'keystatuseschange', MediaKeySessionProx
y.prototype._onKeyStatusesChange.bind( this ) ); |
| 172 }; |
| 173 |
| 174 MediaKeySessionProxy.prototype._onMessage = function _onMessage( event ) |
| 175 { |
| 176 switch( this._state ) |
| 177 { |
| 178 case 'loading': |
| 179 this._session.update( toUtf8( { keys: this._keys } ) ) |
| 180 .then( function() { |
| 181 this._state = 'active'; |
| 182 this._loaded( true ); |
| 183 }.bind(this)).catch( this._loadfailed ); |
| 184 |
| 185 break; |
| 186 |
| 187 case 'active': |
| 188 this.dispatchEvent( event ); |
| 189 break; |
| 190 |
| 191 default: |
| 192 // Swallow the event |
| 193 break; |
| 194 } |
| 195 }; |
| 196 |
| 197 MediaKeySessionProxy.prototype._onKeyStatusesChange = function _onKeyStatuse
sChange( event ) |
| 198 { |
| 199 switch( this._state ) |
| 200 { |
| 201 case 'active' : |
| 202 case 'removing' : |
| 203 this.dispatchEvent( event ); |
| 204 break; |
| 205 |
| 206 default: |
| 207 // Swallow the event |
| 208 break; |
| 209 } |
| 210 }; |
| 211 |
| 212 MediaKeySessionProxy.prototype._onTimeUpdate = function _onTimeUpdate( event
) |
| 213 { |
| 214 if ( !this._firstTime ) this._firstTime = Date.now(); |
| 215 this._latestTime = Date.now(); |
| 216 this._store(); |
| 217 }; |
| 218 |
| 219 MediaKeySessionProxy.prototype._queueMessage = function _queueMessage( messa
geType, message ) |
| 220 { |
| 221 setTimeout( function() { |
| 222 |
| 223 var messageAsArray = toUtf8( message ).buffer; |
| 224 |
| 225 this.dispatchEvent( new MediaKeyMessageEvent( 'message', { messageTy
pe: messageType, message: messageAsArray } ) ); |
| 226 |
| 227 }.bind( this ) ); |
| 228 }; |
| 229 |
| 230 function _storageKey( sessionId ) |
| 231 { |
| 232 return sessionId; |
| 233 } |
| 234 |
| 235 MediaKeySessionProxy.prototype._store = function _store() |
| 236 { |
| 237 var data; |
| 238 |
| 239 if ( this._sessionType === 'persistent-usage-record' ) |
| 240 { |
| 241 data = { kids: this._kids }; |
| 242 if ( this._firstTime ) data.firstTime = this._firstTime; |
| 243 if ( this._latestTime ) data.latestTime = this._latestTime; |
| 244 } |
| 245 else |
| 246 { |
| 247 data = { keys: this._keys }; |
| 248 } |
| 249 |
| 250 window.localStorage.setItem( _storageKey( this._sessionId ), JSON.string
ify( data ) ); |
| 251 }; |
| 252 |
| 253 MediaKeySessionProxy.prototype._load = function _load( sessionId ) |
| 254 { |
| 255 var store = window.localStorage.getItem( _storageKey( sessionId ) ); |
| 256 if ( store === null ) return false; |
| 257 |
| 258 var data; |
| 259 try { data = JSON.parse( store ) } catch( error ) { |
| 260 return false; |
| 261 } |
| 262 |
| 263 if ( data.kids ) |
| 264 { |
| 265 this._sessionType = 'persistent-usage-record'; |
| 266 this._keys = data.kids.map( function( kid ) { return { kid: kid }; }
); |
| 267 if ( data.firstTime ) this._firstTime = data.firstTime; |
| 268 if ( data.latestTime ) this._latestTime = data.latestTime; |
| 269 } |
| 270 else |
| 271 { |
| 272 this._sessionType = 'persistent-license'; |
| 273 this._keys = data.keys; |
| 274 } |
| 275 |
| 276 return true; |
| 277 }; |
| 278 |
| 279 MediaKeySessionProxy.prototype._clear = function _clear() |
| 280 { |
| 281 window.localStorage.removeItem( _storageKey( this._sessionId ) ); |
| 282 }; |
| 283 |
| 284 MediaKeySessionProxy.prototype.generateRequest = function generateRequest( i
nitDataType, initData ) |
| 285 { |
| 286 if ( this._state !== 'created' ) return Promise.reject( new InvalidState
Error() ); |
| 287 |
| 288 this._createSession(); |
| 289 |
| 290 this._state = 'active'; |
| 291 |
| 292 return this._session.generateRequest( initDataType, initData ) |
| 293 .then( function() { |
| 294 this._sessionId = Math.random().toString(36).slice(2); |
| 295 }.bind( this ) ); |
| 296 }; |
| 297 |
| 298 MediaKeySessionProxy.prototype.load = function load( sessionId ) |
| 299 { |
| 300 if ( this._state !== 'created' ) return Promise.reject( new InvalidState
Error() ); |
| 301 |
| 302 return new Promise( function( resolve, reject ) { |
| 303 |
| 304 try |
| 305 { |
| 306 if ( !this._load( sessionId ) ) |
| 307 { |
| 308 resolve( false ); |
| 309 |
| 310 return; |
| 311 } |
| 312 |
| 313 this._sessionId = sessionId; |
| 314 |
| 315 if ( this._sessionType === 'persistent-usage-record' ) |
| 316 { |
| 317 var msg = { kids: this._kids }; |
| 318 if ( this._firstTime ) msg.firstTime = this._firstTime; |
| 319 if ( this._latestTime ) msg.latestTime = this._latestTime; |
| 320 |
| 321 this._queueMessage( 'license-release', msg ); |
| 322 |
| 323 this._state = 'removing'; |
| 324 |
| 325 resolve( true ); |
| 326 } |
| 327 else |
| 328 { |
| 329 this._createSession(); |
| 330 |
| 331 this._state = 'loading'; |
| 332 this._loaded = resolve; |
| 333 this._loadfailed = reject; |
| 334 |
| 335 var initData = { kids: this._kids }; |
| 336 |
| 337 this._session.generateRequest( 'keyids', toUtf8( initData )
); |
| 338 } |
| 339 } |
| 340 catch( error ) |
| 341 { |
| 342 reject( error ); |
| 343 } |
| 344 }.bind( this ) ); |
| 345 }; |
| 346 |
| 347 MediaKeySessionProxy.prototype.update = function update( response ) |
| 348 { |
| 349 return new Promise( function( resolve, reject ) { |
| 350 |
| 351 switch( this._state ) { |
| 352 |
| 353 case 'active' : |
| 354 |
| 355 var message = fromUtf8( response ); |
| 356 |
| 357 // JSON Web Key Set |
| 358 this._keys = message.keys; |
| 359 |
| 360 this._store(); |
| 361 |
| 362 resolve( this._session.update( response ) ); |
| 363 |
| 364 break; |
| 365 |
| 366 case 'removing' : |
| 367 |
| 368 this._state = 'closed'; |
| 369 |
| 370 this._clear(); |
| 371 |
| 372 this._mediaKeysProxy._removeSession( this ); |
| 373 |
| 374 this._resolveClosed(); |
| 375 |
| 376 delete this._session; |
| 377 |
| 378 resolve(); |
| 379 |
| 380 break; |
| 381 |
| 382 default: |
| 383 reject( new InvalidStateError() ); |
| 384 } |
| 385 |
| 386 }.bind( this ) ); |
| 387 }; |
| 388 |
| 389 MediaKeySessionProxy.prototype.close = function close() |
| 390 { |
| 391 if ( this._state === 'closed' ) return Promise.resolve(); |
| 392 |
| 393 this._state = 'closed'; |
| 394 |
| 395 this._mediaKeysProxy._removeSession( this ); |
| 396 |
| 397 this._resolveClosed(); |
| 398 |
| 399 var session = this._session; |
| 400 if ( !session ) return Promise.resolve(); |
| 401 |
| 402 this._session = undefined; |
| 403 |
| 404 return session.close(); |
| 405 }; |
| 406 |
| 407 MediaKeySessionProxy.prototype.remove = function remove() |
| 408 { |
| 409 if ( this._state !== 'active' || !this._session ) return Promise.reject(
new DOMException('InvalidStateError('+this._state+')') ); |
| 410 |
| 411 this._state = 'removing'; |
| 412 |
| 413 this._mediaKeysProxy._removeSession( this ); |
| 414 |
| 415 return this._session.close() |
| 416 .then( function() { |
| 417 |
| 418 var msg = { kids: this._kids }; |
| 419 |
| 420 if ( this._sessionType === 'persistent-usage-record' ) |
| 421 { |
| 422 if ( this._firstTime ) msg.firstTime = this._firstTime; |
| 423 if ( this._latestTime ) msg.latestTime = this._latestTime; |
| 424 } |
| 425 |
| 426 this._queueMessage( 'license-release', msg ); |
| 427 |
| 428 }.bind( this ) ) |
| 429 }; |
| 430 |
| 431 HTMLMediaElement.prototype.setMediaKeys = function setMediaKeys( mediaKeys ) |
| 432 { |
| 433 if ( mediaKeys instanceof MediaKeysProxy ) |
| 434 { |
| 435 mediaKeys._setVideoElement( this ); |
| 436 return _setMediaKeys.call( this, mediaKeys._mediaKeys ); |
| 437 } |
| 438 else |
| 439 { |
| 440 return _setMediaKeys.call( this, mediaKeys ); |
| 441 } |
| 442 }; |
| 443 |
| 444 navigator.requestMediaKeySystemAccess = function( keysystem, configurations
) |
| 445 { |
| 446 // First, see if this is supported by the platform |
| 447 return new Promise( function( resolve, reject ) { |
| 448 |
| 449 _requestMediaKeySystemAccess( keysystem, configurations ) |
| 450 .then( function( access ) { resolve( access ); } ) |
| 451 .catch( function( error ) { |
| 452 |
| 453 if ( error instanceof TypeError ) reject( error ); |
| 454 |
| 455 if ( keysystem !== 'org.w3.clearkey' ) reject( error ); |
| 456 |
| 457 if ( !configurations.some( is_persistent_configuration ) ) rejec
t( error ); |
| 458 |
| 459 // Shallow copy the configurations, swapping out the labels and
omitting the sessiontypes |
| 460 var configurations_copy = configurations.map( function( config,
index ) { |
| 461 |
| 462 var config_copy = copy_configuration( config ); |
| 463 config_copy.label = index.toString(); |
| 464 return config_copy; |
| 465 |
| 466 } ); |
| 467 |
| 468 // And try again with these configurations |
| 469 _requestMediaKeySystemAccess( keysystem, configurations_copy ) |
| 470 .then( function( access ) { |
| 471 |
| 472 // Create the supported configuration based on the original
request |
| 473 var configuration = access.getConfiguration(), |
| 474 original_configuration = configurations[ configuration.l
abel ]; |
| 475 |
| 476 // If the original configuration did not need persistent ses
sion types, then we're done |
| 477 if ( !is_persistent_configuration( original_configuration )
) resolve( access ); |
| 478 |
| 479 // Create the configuration that we will return |
| 480 var returned_configuration = copy_configuration( configurati
on ); |
| 481 |
| 482 if ( original_configuration.label ) |
| 483 returned_configuration.label = original_configuration; |
| 484 else |
| 485 delete returned_configuration.label; |
| 486 |
| 487 returned_configuration.sessionTypes = original_configuration
.sessionTypes; |
| 488 |
| 489 resolve( new MediaKeySystemAccessProxy( keysystem, access, r
eturned_configuration ) ); |
| 490 } ) |
| 491 .catch( function( error ) { reject( error ); } ); |
| 492 } ); |
| 493 } ); |
| 494 }; |
| 495 |
| 496 function is_persistent_configuration( configuration ) |
| 497 { |
| 498 return configuration.sessionTypes && |
| 499 ( configuration.sessionTypes.indexOf( 'persistent-usage-record'
) !== -1 |
| 500 || configuration.sessionTypes.indexOf( 'persistent-license' ) !=
= -1 ); |
| 501 } |
| 502 |
| 503 function copy_configuration( src ) |
| 504 { |
| 505 var dst = {}; |
| 506 [ 'label', 'initDataTypes', 'audioCapabilities', 'videoCapabilities', 'd
istinctiveIdenfifier', 'persistentState' ] |
| 507 .forEach( function( item ) { if ( src[item] ) dst[item] = src[item]; } )
; |
| 508 return dst; |
| 509 } |
| 510 }()); |
OLD | NEW |