Skip to content

Commit

Permalink
Add key status support and video error events.
Browse files Browse the repository at this point in the history
This adds support for EME key statuses, which are translated into
Player error events.  It also listens for error events on the video
tag, which are also translated into Player error events.

This also updates the prefixed EME polyfill to simulate key statuses
for prefixed EME.

Applications can now detect HDCP failures and message appropriately.

Native key status support was not added to Chrome until Chrome 42.
The prefixed EME events to detect HDCP failures work in Chrome 39 and
Chrome 42, but are broken in Chrome 40 & 41.

Closes #14, b/19244591

Change-Id: I112c489bda63360d2bedca83f190c639559641ea
  • Loading branch information
joeyparrish committed Feb 17, 2015
1 parent b244def commit b092231
Show file tree
Hide file tree
Showing 4 changed files with 371 additions and 21 deletions.
34 changes: 34 additions & 0 deletions externs/iterator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @fileoverview Iterator externs missing from closure.
* @externs
*/



/**
* @interface
* @template VALUE
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol
*/
function Iterator() {}


/**
* @return {{value:VALUE, done:boolean}}
*/
Iterator.prototype.next = function() {};

70 changes: 55 additions & 15 deletions externs/mediakeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
var BufferSource;


/** @typedef {!Object.<BufferSource, string>} */
var MediaKeyStatuses;


/**
* @param {string} keySystem
* @param {Array.<Object>=} opt_supportedConfigurations
Expand All @@ -43,14 +39,6 @@ Navigator.prototype.requestMediaKeySystemAccess =
HTMLMediaElement.prototype.mediaKeys;


/**
* NOTE: Not yet implemented as of Chrome 40. Type may be incorrect.
* @type {string}
* @const
*/
HTMLMediaElement.prototype.waitingFor;


/**
* @param {MediaKeys} mediaKeys
* @return {!Promise}
Expand All @@ -68,7 +56,6 @@ MediaKeySystemAccess.prototype.createMediaKeys = function() {};


/**
* NOTE: Not yet implemented as of Chrome 40. Type may be incorrect.
* @return {Object}
*/
MediaKeySystemAccess.prototype.getConfiguration = function() {};
Expand Down Expand Up @@ -99,6 +86,60 @@ MediaKeys.prototype.setServerCertificate = function(serverCertificate) {};



/**
* @interface
*/
function MediaKeyStatusMap() {}


/**
* @type {number}
* @const
*/
MediaKeyStatusMap.prototype.size;


/**
* Array entry 0 is the key, 1 is the value.
* @return {Iterator.<Array.<!BufferSource|string>>}
*/
MediaKeyStatusMap.prototype.entries = function() {};


/**
* The functor is called with each value.
* @param {function(string)} fn
*/
MediaKeyStatusMap.prototype.forEach = function(fn) {};


/**
* @param {!BufferSource} keyId
* @return {string|undefined}
*/
MediaKeyStatusMap.prototype.get = function(keyId) {};


/**
* @param {!BufferSource} keyId
* @return {boolean}
*/
MediaKeyStatusMap.prototype.has = function(keyId) {};


/**
* @return {Iterator.<!BufferSource>}
*/
MediaKeyStatusMap.prototype.keys = function() {};


/**
* @return {Iterator.<string>}
*/
MediaKeyStatusMap.prototype.values = function() {};



/**
* @interface
* @extends {EventTarget}
Expand Down Expand Up @@ -128,8 +169,7 @@ MediaKeySession.prototype.closed;


/**
* NOTE: Not yet implemented as of Chrome 40. Type may be incorrect.
* @type {!MediaKeyStatuses}
* @type {!MediaKeyStatusMap}
* @const
*/
MediaKeySession.prototype.keyStatuses;
Expand Down
97 changes: 95 additions & 2 deletions lib/player/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ goog.require('shaka.util.Uint8ArrayUtils');
* @property {string} type 'error'
* @property {boolean} bubbles true
* @property {!Error} detail An object which contains details on the error.
* The error's 'type' property will help you identify the specific error
* condition and display an appropriate message or error indicator to the
* user. The error's 'message' property contains English text which can
* be useful during debugging.
* @export
*/

Expand Down Expand Up @@ -406,6 +410,7 @@ shaka.player.Player.prototype.generateFakeEncryptedEvents_ =
* @private
*/
shaka.player.Player.prototype.setVideoEventListeners_ = function() {
this.eventManager_.listen(this.video_, 'error', this.onError_.bind(this));
// TODO(story 1891509): Connect these events to the UI.
this.eventManager_.listen(this.video_, 'play', this.onPlay_.bind(this));
this.eventManager_.listen(this.video_, 'playing', this.onPlaying_.bind(this));
Expand Down Expand Up @@ -443,9 +448,12 @@ shaka.player.Player.prototype.onEncrypted_ = function(event) {
var session = this.mediaKeys_.createSession();
this.sessions_.push(session);

this.eventManager_.listen(
session, 'message', /** @type {shaka.util.EventManager.ListenerType} */(
this.eventManager_.listen(session, 'message',
/** @type {shaka.util.EventManager.ListenerType} */(
this.onSessionMessage_.bind(this)));
this.eventManager_.listen(session, 'keystatuseschange',
/** @type {shaka.util.EventManager.ListenerType} */(
this.onKeyStatusChange_.bind(this)));

var p = session.generateRequest(event.initDataType, event.initData);
this.requestGenerated_[initDataKey] = true;
Expand Down Expand Up @@ -476,6 +484,29 @@ shaka.player.Player.prototype.onSessionMessage_ = function(event) {
};


/**
* EME status-change handler.
*
* @param {!Event} event
* @private
*/
shaka.player.Player.prototype.onKeyStatusChange_ = function(event) {
shaka.log.info('onKeyStatusChange_', event);
var session = /** @type {!MediaKeySession} */(event.target);
var map = session.keyStatuses;
var i = map.values();
for (var v = i.next(); !v.done; v = i.next()) {
var message = shaka.player.Player.KEY_STATUS_ERROR_MAP_[v.value];
if (message) {
var error = new Error(message);
error.type = v.value;
var event = shaka.util.FakeEvent.createErrorEvent(error);
this.dispatchEvent(event);
}
}
};


/**
* Requests a license.
*
Expand Down Expand Up @@ -538,6 +569,31 @@ shaka.player.Player.prototype.onFirstTimestamp_ = function(event) {
};


/**
* Video error event handler.
*
* @param {!Event} event
* @private
*/
shaka.player.Player.prototype.onError_ = function(event) {
var code = this.video_.error.code;
if (code == MediaError['MEDIA_ERR_ABORTED']) {
// Ignore this error code, which should only occur when navigating away or
// deliberately stopping playback of HTTP content.
return;
}

shaka.log.debug('onError_', event, code);
var message = shaka.player.Player.MEDIA_ERROR_MAP_[code] ||
'Unknown playback error.';

var error = new Error(message);
error.type = 'playback';
var event = shaka.util.FakeEvent.createErrorEvent(error);
this.dispatchEvent(event);
};


/**
* Video play event handler.
*
Expand Down Expand Up @@ -978,3 +1034,40 @@ shaka.player.Player.prototype.onWatchdogTimer_ = function() {
*/
shaka.player.Player.UNDERFLOW_THRESHOLD_ = 0.050;


/**
* A map of key statuses to errors. Not every key status appears in the map,
* in which case that key status is not treated as an error.
*
* @private {!Object.<string, string>}
* @const
*/
shaka.player.Player.KEY_STATUS_ERROR_MAP_ = {
'output-not-allowed': 'The required output protection is not available.',
'expired': 'A required key has expired and the content cannot be decrypted.',
'internal-error': 'An unknown error has occurred in the CDM.'
};


/**
* A map of MediaError codes to error messages. The JS interpreter won't take
* a symbolic name as a key, so the symbolic names for these error codes appear
* in comments after the number.
*
* @private {!Object.<number, string>}
* @const
*/
shaka.player.Player.MEDIA_ERROR_MAP_ = {
// This should not occur for DASH sources, but may occur for HTTP sources.
2: // MediaError.MEDIA_ERR_NETWORK
'A network failure occured while loading media content.',

3: // MediaError.MEDIA_ERR_DECODE
'The browser failed to decode the media content.',

// This is also unlikely for DASH sources, but HTTP sources do not check
// browser support before beginning playback.
4: // MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
'The browser does not support the media content.'
};

Loading

0 comments on commit b092231

Please sign in to comment.