mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-17 16:26:39 +03:00
f539147d48
This fixes all the license headers in the main library, which corrects the appearance of the main license in the compiled output. It seems that the `!` in the header forces the compiler to keep it in the output. I believe older compiler releases did this purely based on `@license`. Issue #2638 Change-Id: I7f0e918caad10c9af689c9d07672b7fe9be7b2f3
737 lines
22 KiB
JavaScript
737 lines
22 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.polyfill.PatchedMediaKeysApple');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('shaka.log');
|
|
goog.require('shaka.media.DrmEngine');
|
|
goog.require('shaka.polyfill');
|
|
goog.require('shaka.util.BufferUtils');
|
|
goog.require('shaka.util.EventManager');
|
|
goog.require('shaka.util.FakeEvent');
|
|
goog.require('shaka.util.FakeEventTarget');
|
|
goog.require('shaka.util.MediaReadyState');
|
|
goog.require('shaka.util.PublicPromise');
|
|
|
|
|
|
/**
|
|
* @summary A polyfill to implement modern, standardized EME on top of Apple's
|
|
* prefixed EME in Safari.
|
|
*/
|
|
shaka.polyfill.PatchedMediaKeysApple = class {
|
|
/**
|
|
* Installs the polyfill if needed.
|
|
*/
|
|
static install() {
|
|
if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
|
|
// No HTML5 video or no prefixed EME.
|
|
return;
|
|
}
|
|
|
|
// TODO: Prefer unprefixed EME once we know how to use it.
|
|
// See: https://bugs.webkit.org/show_bug.cgi?id=197433
|
|
/*
|
|
if (navigator.requestMediaKeySystemAccess &&
|
|
MediaKeySystemAccess.prototype.getConfiguration) {
|
|
// Prefixed EME is preferable.
|
|
return;
|
|
}
|
|
*/
|
|
|
|
shaka.log.info('Using Apple-prefixed EME');
|
|
|
|
// Alias
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
|
|
// Delete mediaKeys to work around strict mode compatibility issues.
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
delete HTMLMediaElement.prototype['mediaKeys'];
|
|
// Work around read-only declaration for mediaKeys by using a string.
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
HTMLMediaElement.prototype['mediaKeys'] = null;
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
HTMLMediaElement.prototype.setMediaKeys =
|
|
PatchedMediaKeysApple.setMediaKeys;
|
|
|
|
// Install patches
|
|
window.MediaKeys = PatchedMediaKeysApple.MediaKeys;
|
|
window.MediaKeySystemAccess = PatchedMediaKeysApple.MediaKeySystemAccess;
|
|
navigator.requestMediaKeySystemAccess =
|
|
PatchedMediaKeysApple.requestMediaKeySystemAccess;
|
|
}
|
|
|
|
/**
|
|
* An implementation of navigator.requestMediaKeySystemAccess.
|
|
* Retrieves a MediaKeySystemAccess object.
|
|
*
|
|
* @this {!Navigator}
|
|
* @param {string} keySystem
|
|
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
|
|
* @return {!Promise.<!MediaKeySystemAccess>}
|
|
*/
|
|
static requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
|
|
shaka.log.debug('PatchedMediaKeysApple.requestMediaKeySystemAccess');
|
|
goog.asserts.assert(this == navigator,
|
|
'bad "this" for requestMediaKeySystemAccess');
|
|
|
|
// Alias.
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
try {
|
|
const access = new PatchedMediaKeysApple.MediaKeySystemAccess(
|
|
keySystem, supportedConfigurations);
|
|
return Promise.resolve(/** @type {!MediaKeySystemAccess} */ (access));
|
|
} catch (exception) {
|
|
return Promise.reject(exception);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An implementation of HTMLMediaElement.prototype.setMediaKeys.
|
|
* Attaches a MediaKeys object to the media element.
|
|
*
|
|
* @this {!HTMLMediaElement}
|
|
* @param {MediaKeys} mediaKeys
|
|
* @return {!Promise}
|
|
*/
|
|
static setMediaKeys(mediaKeys) {
|
|
shaka.log.debug('PatchedMediaKeysApple.setMediaKeys');
|
|
goog.asserts.assert(this instanceof HTMLMediaElement,
|
|
'bad "this" for setMediaKeys');
|
|
|
|
// Alias
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
|
|
const newMediaKeys =
|
|
/** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */ (
|
|
mediaKeys);
|
|
const oldMediaKeys =
|
|
/** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */ (
|
|
this.mediaKeys);
|
|
|
|
if (oldMediaKeys && oldMediaKeys != newMediaKeys) {
|
|
goog.asserts.assert(
|
|
oldMediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
|
|
'non-polyfill instance of oldMediaKeys');
|
|
// Have the old MediaKeys stop listening to events on the video tag.
|
|
oldMediaKeys.setMedia(null);
|
|
}
|
|
|
|
delete this['mediaKeys']; // in case there is an existing getter
|
|
this['mediaKeys'] = mediaKeys; // work around read-only declaration
|
|
|
|
if (newMediaKeys) {
|
|
goog.asserts.assert(
|
|
newMediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
|
|
'non-polyfill instance of newMediaKeys');
|
|
return newMediaKeys.setMedia(this);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
/**
|
|
* Handler for the native media elements webkitneedkey event.
|
|
*
|
|
* @this {!HTMLMediaElement}
|
|
* @param {!MediaKeyEvent} event
|
|
* @suppress {constantProperty} We reassign what would be const on a real
|
|
* MediaEncryptedEvent, but in our look-alike event.
|
|
* @private
|
|
*/
|
|
static onWebkitNeedKey_(event) {
|
|
shaka.log.debug('PatchedMediaKeysApple.onWebkitNeedKey_', event);
|
|
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
const mediaKeys =
|
|
/** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */(
|
|
this.mediaKeys);
|
|
goog.asserts.assert(mediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
|
|
'non-polyfill instance of newMediaKeys');
|
|
|
|
goog.asserts.assert(event.initData != null, 'missing init data!');
|
|
|
|
// Convert the prefixed init data to match the native 'encrypted' event.
|
|
const uint8 = shaka.util.BufferUtils.toUint8(event.initData);
|
|
const dataview = shaka.util.BufferUtils.toDataView(uint8);
|
|
// The first part is a 4 byte little-endian int, which is the length of
|
|
// the second part.
|
|
const length = dataview.getUint32(
|
|
/* position= */ 0, /* littleEndian= */ true);
|
|
if (length + 4 != uint8.byteLength) {
|
|
throw new RangeError('Malformed FairPlay init data');
|
|
}
|
|
// The remainder is a UTF-16 skd URL. Convert this to UTF-8 and pass on.
|
|
const str = shaka.util.StringUtils.fromUTF16(
|
|
uint8.subarray(4), /* littleEndian= */ true);
|
|
const initData = shaka.util.StringUtils.toUTF8(str);
|
|
|
|
// NOTE: Because "this" is a real EventTarget, the event we dispatch here
|
|
// must also be a real Event.
|
|
const event2 = new Event('encrypted');
|
|
|
|
const encryptedEvent =
|
|
/** @type {!MediaEncryptedEvent} */(/** @type {?} */(event2));
|
|
encryptedEvent.initDataType = 'skd';
|
|
encryptedEvent.initData = shaka.util.BufferUtils.toArrayBuffer(initData);
|
|
|
|
this.dispatchEvent(event2);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* An implementation of MediaKeySystemAccess.
|
|
*
|
|
* @implements {MediaKeySystemAccess}
|
|
*/
|
|
shaka.polyfill.PatchedMediaKeysApple.MediaKeySystemAccess = class {
|
|
/**
|
|
* @param {string} keySystem
|
|
* @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
|
|
*/
|
|
constructor(keySystem, supportedConfigurations) {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySystemAccess');
|
|
|
|
/** @type {string} */
|
|
this.keySystem = keySystem;
|
|
|
|
/** @private {!MediaKeySystemConfiguration} */
|
|
this.configuration_;
|
|
|
|
// Optimization: WebKitMediaKeys.isTypeSupported delays responses by a
|
|
// significant amount of time, possibly to discourage fingerprinting.
|
|
// Since we know only FairPlay is supported here, let's skip queries for
|
|
// anything else to speed up the process.
|
|
if (keySystem.startsWith('com.apple.fps')) {
|
|
for (const cfg of supportedConfigurations) {
|
|
const newCfg = this.checkConfig_(cfg);
|
|
if (newCfg) {
|
|
this.configuration_ = newCfg;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// According to the spec, this should be a DOMException, but there is not a
|
|
// public constructor for that. So we make this look-alike instead.
|
|
const unsupportedKeySystemError = new Error('Unsupported keySystem');
|
|
unsupportedKeySystemError.name = 'NotSupportedError';
|
|
unsupportedKeySystemError['code'] = DOMException.NOT_SUPPORTED_ERR;
|
|
throw unsupportedKeySystemError;
|
|
}
|
|
|
|
/**
|
|
* Check a single config for MediaKeySystemAccess.
|
|
*
|
|
* @param {MediaKeySystemConfiguration} cfg The requested config.
|
|
* @return {?MediaKeySystemConfiguration} A matching config we can support, or
|
|
* null if the input is not supportable.
|
|
* @private
|
|
*/
|
|
checkConfig_(cfg) {
|
|
if (cfg.persistentState == 'required') {
|
|
// Not supported by the prefixed API.
|
|
return null;
|
|
}
|
|
|
|
// Create a new config object and start adding in the pieces which we find
|
|
// support for. We will return this from getConfiguration() later if
|
|
// asked.
|
|
|
|
/** @type {!MediaKeySystemConfiguration} */
|
|
const newCfg = {
|
|
'audioCapabilities': [],
|
|
'videoCapabilities': [],
|
|
// It is technically against spec to return these as optional, but we
|
|
// don't truly know their values from the prefixed API:
|
|
'persistentState': 'optional',
|
|
'distinctiveIdentifier': 'optional',
|
|
// Pretend the requested init data types are supported, since we don't
|
|
// really know that either:
|
|
'initDataTypes': cfg.initDataTypes,
|
|
'sessionTypes': ['temporary'],
|
|
'label': cfg.label,
|
|
};
|
|
|
|
// PatchedMediaKeysApple tests for key system availability through
|
|
// WebKitMediaKeys.isTypeSupported.
|
|
let ranAnyTests = false;
|
|
let success = false;
|
|
|
|
if (cfg.audioCapabilities) {
|
|
for (const cap of cfg.audioCapabilities) {
|
|
if (cap.contentType) {
|
|
ranAnyTests = true;
|
|
|
|
const contentType = cap.contentType.split(';')[0];
|
|
if (WebKitMediaKeys.isTypeSupported(this.keySystem, contentType)) {
|
|
newCfg.audioCapabilities.push(cap);
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cfg.videoCapabilities) {
|
|
for (const cap of cfg.videoCapabilities) {
|
|
if (cap.contentType) {
|
|
ranAnyTests = true;
|
|
|
|
const contentType = cap.contentType.split(';')[0];
|
|
if (WebKitMediaKeys.isTypeSupported(this.keySystem, contentType)) {
|
|
newCfg.videoCapabilities.push(cap);
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ranAnyTests) {
|
|
// If no specific types were requested, we check all common types to
|
|
// find out if the key system is present at all.
|
|
success = WebKitMediaKeys.isTypeSupported(this.keySystem, 'video/mp4');
|
|
}
|
|
|
|
if (success) {
|
|
return newCfg;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** @override */
|
|
createMediaKeys() {
|
|
shaka.log.debug(
|
|
'PatchedMediaKeysApple.MediaKeySystemAccess.createMediaKeys');
|
|
|
|
// Alias
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
|
|
const mediaKeys = new PatchedMediaKeysApple.MediaKeys(this.keySystem);
|
|
return Promise.resolve(/** @type {!MediaKeys} */ (mediaKeys));
|
|
}
|
|
|
|
/** @override */
|
|
getConfiguration() {
|
|
shaka.log.debug(
|
|
'PatchedMediaKeysApple.MediaKeySystemAccess.getConfiguration');
|
|
return this.configuration_;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* An implementation of MediaKeys.
|
|
*
|
|
* @implements {MediaKeys}
|
|
*/
|
|
shaka.polyfill.PatchedMediaKeysApple.MediaKeys = class {
|
|
/** @param {string} keySystem */
|
|
constructor(keySystem) {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeys');
|
|
|
|
/** @private {!WebKitMediaKeys} */
|
|
this.nativeMediaKeys_ = new WebKitMediaKeys(keySystem);
|
|
|
|
/** @private {!shaka.util.EventManager} */
|
|
this.eventManager_ = new shaka.util.EventManager();
|
|
}
|
|
|
|
/** @override */
|
|
createSession(sessionType) {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeys.createSession');
|
|
|
|
sessionType = sessionType || 'temporary';
|
|
// For now, only the 'temporary' type is supported.
|
|
if (sessionType != 'temporary') {
|
|
throw new TypeError('Session type ' + sessionType +
|
|
' is unsupported on this platform.');
|
|
}
|
|
|
|
// Alias
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
|
|
return new PatchedMediaKeysApple.MediaKeySession(
|
|
this.nativeMediaKeys_, sessionType);
|
|
}
|
|
|
|
/** @override */
|
|
setServerCertificate(serverCertificate) {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeys.setServerCertificate');
|
|
return Promise.resolve(false);
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLMediaElement} media
|
|
* @protected
|
|
* @return {!Promise}
|
|
*/
|
|
setMedia(media) {
|
|
// Alias
|
|
const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
|
|
|
|
// Remove any old listeners.
|
|
this.eventManager_.removeAll();
|
|
|
|
// It is valid for media to be null; null is used to flag that event
|
|
// handlers need to be cleaned up.
|
|
if (!media) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// Intercept and translate these prefixed EME events.
|
|
this.eventManager_.listen(media, 'webkitneedkey',
|
|
/** @type {shaka.util.EventManager.ListenerType} */
|
|
(PatchedMediaKeysApple.onWebkitNeedKey_));
|
|
|
|
// Wrap native HTMLMediaElement.webkitSetMediaKeys with a Promise.
|
|
try {
|
|
// Some browsers require that readyState >=1 before mediaKeys can be
|
|
// set, so check this and wait for loadedmetadata if we are not in the
|
|
// correct state
|
|
shaka.util.MediaReadyState.waitForReadyState(media,
|
|
HTMLMediaElement.HAVE_METADATA,
|
|
this.eventManager_, () => {
|
|
media.webkitSetMediaKeys(this.nativeMediaKeys_);
|
|
});
|
|
|
|
return Promise.resolve();
|
|
} catch (exception) {
|
|
return Promise.reject(exception);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* An implementation of MediaKeySession.
|
|
*
|
|
* @implements {MediaKeySession}
|
|
*/
|
|
shaka.polyfill.PatchedMediaKeysApple.MediaKeySession =
|
|
class extends shaka.util.FakeEventTarget {
|
|
/**
|
|
* @param {WebKitMediaKeys} nativeMediaKeys
|
|
* @param {string} sessionType
|
|
*/
|
|
constructor(nativeMediaKeys, sessionType) {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySession');
|
|
super();
|
|
|
|
/**
|
|
* The native MediaKeySession, which will be created in generateRequest.
|
|
* @private {WebKitMediaKeySession}
|
|
*/
|
|
this.nativeMediaKeySession_ = null;
|
|
|
|
/** @private {WebKitMediaKeys} */
|
|
this.nativeMediaKeys_ = nativeMediaKeys;
|
|
|
|
// Promises that are resolved later
|
|
/** @private {shaka.util.PublicPromise} */
|
|
this.generateRequestPromise_ = null;
|
|
|
|
/** @private {shaka.util.PublicPromise} */
|
|
this.updatePromise_ = null;
|
|
|
|
/** @private {!shaka.util.EventManager} */
|
|
this.eventManager_ = new shaka.util.EventManager();
|
|
|
|
/** @type {string} */
|
|
this.sessionId = '';
|
|
|
|
/** @type {number} */
|
|
this.expiration = NaN;
|
|
|
|
/** @type {!shaka.util.PublicPromise} */
|
|
this.closed = new shaka.util.PublicPromise();
|
|
|
|
/** @type {!shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap} */
|
|
this.keyStatuses =
|
|
new shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap();
|
|
}
|
|
|
|
/** @override */
|
|
generateRequest(initDataType, initData) {
|
|
shaka.log.debug(
|
|
'PatchedMediaKeysApple.MediaKeySession.generateRequest');
|
|
|
|
this.generateRequestPromise_ = new shaka.util.PublicPromise();
|
|
|
|
try {
|
|
// This EME spec version requires a MIME content type as the 1st param to
|
|
// createSession, but doesn't seem to matter what the value is.
|
|
// It also only accepts Uint8Array, not ArrayBuffer, so explicitly make
|
|
// initData into a Uint8Array.
|
|
const session = this.nativeMediaKeys_.createSession(
|
|
'video/mp4', shaka.util.BufferUtils.toUint8(initData));
|
|
this.nativeMediaKeySession_ = session;
|
|
this.sessionId = session.sessionId || '';
|
|
|
|
// Attach session event handlers here.
|
|
this.eventManager_.listen(
|
|
this.nativeMediaKeySession_, 'webkitkeymessage',
|
|
/** @type {shaka.util.EventManager.ListenerType} */
|
|
((event) => this.onWebkitKeyMessage_(event)));
|
|
this.eventManager_.listen(session, 'webkitkeyadded',
|
|
/** @type {shaka.util.EventManager.ListenerType} */
|
|
((event) => this.onWebkitKeyAdded_(event)));
|
|
this.eventManager_.listen(session, 'webkitkeyerror',
|
|
/** @type {shaka.util.EventManager.ListenerType} */
|
|
((event) => this.onWebkitKeyError_(event)));
|
|
|
|
this.updateKeyStatus_('status-pending');
|
|
} catch (exception) {
|
|
this.generateRequestPromise_.reject(exception);
|
|
}
|
|
|
|
return this.generateRequestPromise_;
|
|
}
|
|
|
|
/** @override */
|
|
load() {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.load');
|
|
|
|
return Promise.reject(new Error('MediaKeySession.load not yet supported'));
|
|
}
|
|
|
|
/** @override */
|
|
update(response) {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.update');
|
|
|
|
this.updatePromise_ = new shaka.util.PublicPromise();
|
|
|
|
try {
|
|
// Pass through to the native session.
|
|
this.nativeMediaKeySession_.update(
|
|
shaka.util.BufferUtils.toUint8(response));
|
|
} catch (exception) {
|
|
this.updatePromise_.reject(exception);
|
|
}
|
|
|
|
return this.updatePromise_;
|
|
}
|
|
|
|
/** @override */
|
|
close() {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.close');
|
|
|
|
try {
|
|
// Pass through to the native session.
|
|
this.nativeMediaKeySession_.close();
|
|
|
|
this.closed.resolve();
|
|
this.eventManager_.removeAll();
|
|
} catch (exception) {
|
|
this.closed.reject(exception);
|
|
}
|
|
|
|
return this.closed;
|
|
}
|
|
|
|
/** @override */
|
|
remove() {
|
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.remove');
|
|
|
|
return Promise.reject(new Error(
|
|
'MediaKeySession.remove is only applicable for persistent licenses, ' +
|
|
'which are not supported on this platform'));
|
|
}
|
|
|
|
/**
|
|
* Handler for the native keymessage event on WebKitMediaKeySession.
|
|
*
|
|
* @param {!MediaKeyEvent} event
|
|
* @private
|
|
*/
|
|
onWebkitKeyMessage_(event) {
|
|
shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyMessage_', event);
|
|
|
|
// We can now resolve this.generateRequestPromise, which should be non-null.
|
|
goog.asserts.assert(this.generateRequestPromise_,
|
|
'generateRequestPromise_ should be set before now!');
|
|
if (this.generateRequestPromise_) {
|
|
this.generateRequestPromise_.resolve();
|
|
this.generateRequestPromise_ = null;
|
|
}
|
|
|
|
const isNew = this.keyStatuses.getStatus() == undefined;
|
|
|
|
const event2 = new shaka.util.FakeEvent('message', {
|
|
messageType: isNew ? 'license-request' : 'license-renewal',
|
|
message: shaka.util.BufferUtils.toArrayBuffer(event.message),
|
|
});
|
|
|
|
this.dispatchEvent(event2);
|
|
}
|
|
|
|
/**
|
|
* Handler for the native keyadded event on WebKitMediaKeySession.
|
|
*
|
|
* @param {!MediaKeyEvent} event
|
|
* @private
|
|
*/
|
|
onWebkitKeyAdded_(event) {
|
|
shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyAdded_', event);
|
|
|
|
// This shouldn't fire while we're in the middle of generateRequest,
|
|
// but if it does, we will need to change the logic to account for it.
|
|
goog.asserts.assert(!this.generateRequestPromise_,
|
|
'Key added during generate!');
|
|
|
|
// We can now resolve this.updatePromise, which should be non-null.
|
|
goog.asserts.assert(this.updatePromise_,
|
|
'updatePromise_ should be set before now!');
|
|
if (this.updatePromise_) {
|
|
this.updateKeyStatus_('usable');
|
|
this.updatePromise_.resolve();
|
|
this.updatePromise_ = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for the native keyerror event on WebKitMediaKeySession.
|
|
*
|
|
* @param {!MediaKeyEvent} event
|
|
* @private
|
|
*/
|
|
onWebkitKeyError_(event) {
|
|
shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyError_', event);
|
|
|
|
const error = new Error('EME PatchedMediaKeysApple key error');
|
|
error['errorCode'] = this.nativeMediaKeySession_.error;
|
|
|
|
if (this.generateRequestPromise_ != null) {
|
|
this.generateRequestPromise_.reject(error);
|
|
this.generateRequestPromise_ = null;
|
|
} else if (this.updatePromise_ != null) {
|
|
this.updatePromise_.reject(error);
|
|
this.updatePromise_ = null;
|
|
} else {
|
|
// Unexpected error - map native codes to standardised key statuses.
|
|
// Possible values of this.nativeMediaKeySession_.error.code:
|
|
// MEDIA_KEYERR_UNKNOWN = 1
|
|
// MEDIA_KEYERR_CLIENT = 2
|
|
// MEDIA_KEYERR_SERVICE = 3
|
|
// MEDIA_KEYERR_OUTPUT = 4
|
|
// MEDIA_KEYERR_HARDWARECHANGE = 5
|
|
// MEDIA_KEYERR_DOMAIN = 6
|
|
|
|
switch (this.nativeMediaKeySession_.error.code) {
|
|
case WebKitMediaKeyError.MEDIA_KEYERR_OUTPUT:
|
|
case WebKitMediaKeyError.MEDIA_KEYERR_HARDWARECHANGE:
|
|
this.updateKeyStatus_('output-not-allowed');
|
|
break;
|
|
default:
|
|
this.updateKeyStatus_('internal-error');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates key status and dispatch a 'keystatuseschange' event.
|
|
*
|
|
* @param {string} status
|
|
* @private
|
|
*/
|
|
updateKeyStatus_(status) {
|
|
this.keyStatuses.setStatus(status);
|
|
const event = new shaka.util.FakeEvent('keystatuseschange');
|
|
this.dispatchEvent(event);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @summary An implementation of MediaKeyStatusMap.
|
|
* This fakes a map with a single key ID.
|
|
*
|
|
* @todo Consolidate the MediaKeyStatusMap types in these polyfills.
|
|
* @implements {MediaKeyStatusMap}
|
|
*/
|
|
shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
|
|
constructor() {
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
this.size = 0;
|
|
|
|
/**
|
|
* @private {string|undefined}
|
|
*/
|
|
this.status_ = undefined;
|
|
}
|
|
|
|
/**
|
|
* An internal method used by the session to set key status.
|
|
* @param {string|undefined} status
|
|
*/
|
|
setStatus(status) {
|
|
this.size = status == undefined ? 0 : 1;
|
|
this.status_ = status;
|
|
}
|
|
|
|
/**
|
|
* An internal method used by the session to get key status.
|
|
* @return {string|undefined}
|
|
*/
|
|
getStatus() {
|
|
return this.status_;
|
|
}
|
|
|
|
/** @override */
|
|
forEach(fn) {
|
|
if (this.status_) {
|
|
fn(this.status_, shaka.media.DrmEngine.DUMMY_KEY_ID.value());
|
|
}
|
|
}
|
|
|
|
/** @override */
|
|
get(keyId) {
|
|
if (this.has(keyId)) {
|
|
return this.status_;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/** @override */
|
|
has(keyId) {
|
|
const fakeKeyId = shaka.media.DrmEngine.DUMMY_KEY_ID.value();
|
|
if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @suppress {missingReturn}
|
|
* @override
|
|
*/
|
|
entries() {
|
|
goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
|
|
}
|
|
|
|
/**
|
|
* @suppress {missingReturn}
|
|
* @override
|
|
*/
|
|
keys() {
|
|
goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
|
|
}
|
|
|
|
/**
|
|
* @suppress {missingReturn}
|
|
* @override
|
|
*/
|
|
values() {
|
|
goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
|
|
}
|
|
};
|
|
|
|
|
|
shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysApple.install);
|