Source: lib/polyfill/patchedmediakeys_ms.js

/**
 * @license
 * Copyright 2016 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.
 */

goog.provide('shaka.polyfill.PatchedMediaKeysMs');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.polyfill.register');
goog.require('shaka.util.ArrayUtils');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.FakeEventTarget');
goog.require('shaka.util.Pssh');
goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.Uint8ArrayUtils');


/**
 * @namespace shaka.polyfill.PatchedMediaKeysMs
 *
 * @summary A polyfill to implement
 * {@link http://goo.gl/blgtZZ EME draft 12 March 2015}
 * on top of ms-prefixed
 * {@link http://www.w3.org/TR/2014/WD-encrypted-media-20140218/ EME v20140218}.
 */


/**
 * Installs the polyfill if needed.
 */
shaka.polyfill.PatchedMediaKeysMs.install = function() {
  if (!window.HTMLVideoElement || !window.MSMediaKeys ||
      (navigator.requestMediaKeySystemAccess &&
       MediaKeySystemAccess.prototype.getConfiguration)) {
    return;
  }
  shaka.log.info('Using ms-prefixed EME v20140218');

  // Alias
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;

  // Construct a fake key ID.  This is not done at load-time to avoid exceptions
  // on unsupported browsers.  This particular fake key ID was suggested in
  // w3c/encrypted-media#32.
  PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_ = (new Uint8Array([0])).buffer;

  // Delete mediaKeys to work around strict mode compatibility issues.
  delete HTMLMediaElement.prototype['mediaKeys'];
  // Work around read-only declaration for mediaKeys by using a string.
  HTMLMediaElement.prototype['mediaKeys'] = null;
  HTMLMediaElement.prototype.setMediaKeys = PatchedMediaKeysMs.setMediaKeys;

  // Install patches
  window.MediaKeys = PatchedMediaKeysMs.MediaKeys;
  window.MediaKeySystemAccess = PatchedMediaKeysMs.MediaKeySystemAccess;
  navigator.requestMediaKeySystemAccess =
      PatchedMediaKeysMs.requestMediaKeySystemAccess;
};


/**
 * An implementation of navigator.requestMediaKeySystemAccess.
 * Retrieves a MediaKeySystemAccess object.
 *
 * @this {!Navigator}
 * @param {string} keySystem
 * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
 * @return {!Promise.<!MediaKeySystemAccess>}
 */
shaka.polyfill.PatchedMediaKeysMs.requestMediaKeySystemAccess =
    function(keySystem, supportedConfigurations) {
  shaka.log.debug('PatchedMediaKeysMs.requestMediaKeySystemAccess');
  goog.asserts.assert(this == navigator,
                      'bad "this" for requestMediaKeySystemAccess');

  // Alias.
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  try {
    let access = new PatchedMediaKeysMs.MediaKeySystemAccess(
        keySystem, supportedConfigurations);
    return Promise.resolve(/** @type {!MediaKeySystemAccess} */ (access));
  } catch (exception) {
    return Promise.reject(exception);
  }
};



/**
 * An implementation of MediaKeySystemAccess.
 *
 * @constructor
 * @struct
 * @param {string} keySystem
 * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
 * @implements {MediaKeySystemAccess}
 * @throws {Error} if the key system is not supported.
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySystemAccess =
    function(keySystem, supportedConfigurations) {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySystemAccess');

  /** @type {string} */
  this.keySystem = keySystem;

  /** @private {!MediaKeySystemConfiguration} */
  this.configuration_;

  let allowPersistentState = false;

  let success = false;
  for (let i = 0; i < supportedConfigurations.length; ++i) {
    let cfg = supportedConfigurations[i];

    // Create a new config object and start adding in the pieces which we
    // find support for.  We will return this from getConfiguration() if asked.
    /** @type {!MediaKeySystemConfiguration} */
    let 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
    };

    // PatchedMediaKeysMs tests for key system availability through
    // MSMediaKeys.isTypeSupported
    let ranAnyTests = false;
    if (cfg.audioCapabilities) {
      for (let j = 0; j < cfg.audioCapabilities.length; ++j) {
        let cap = cfg.audioCapabilities[j];
        if (cap.contentType) {
          ranAnyTests = true;
          let contentType = cap.contentType.split(';')[0];
          if (MSMediaKeys.isTypeSupported(this.keySystem, contentType)) {
            newCfg.audioCapabilities.push(cap);
            success = true;
          }
        }
      }
    }
    if (cfg.videoCapabilities) {
      for (let j = 0; j < cfg.videoCapabilities.length; ++j) {
        let cap = cfg.videoCapabilities[j];
        if (cap.contentType) {
          ranAnyTests = true;
          let contentType = cap.contentType.split(';')[0];
          if (MSMediaKeys.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 = MSMediaKeys.isTypeSupported(this.keySystem, 'video/mp4');
    }
    if (cfg.persistentState == 'required') {
      if (allowPersistentState) {
        newCfg.persistentState = 'required';
        newCfg.sessionTypes = ['persistent-license'];
      } else {
        success = false;
      }
    }

    if (success) {
      this.configuration_ = newCfg;
      return;
    }
  }  // for each cfg in supportedConfigurations

  // As per the spec, this should be a DOMException, but there is not a public
  // constructor for this.
  let unsupportedKeySystemError = new Error('Unsupported keySystem');
  unsupportedKeySystemError.name = 'NotSupportedError';
  unsupportedKeySystemError.code = DOMException.NOT_SUPPORTED_ERR;
  throw unsupportedKeySystemError;
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySystemAccess.prototype.
    createMediaKeys = function() {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySystemAccess.createMediaKeys');

  // Alias
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;

  let mediaKeys = new PatchedMediaKeysMs.MediaKeys(this.keySystem);
  return Promise.resolve(/** @type {!MediaKeys} */ (mediaKeys));
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySystemAccess.prototype.
    getConfiguration = function() {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySystemAccess.getConfiguration');
  return this.configuration_;
};


/**
 * An implementation of HTMLMediaElement.prototype.setMediaKeys.
 * Attaches a MediaKeys object to the media element.
 *
 * @this {!HTMLMediaElement}
 * @param {MediaKeys} mediaKeys
 * @return {!Promise}
 */
shaka.polyfill.PatchedMediaKeysMs.setMediaKeys = function(mediaKeys) {
  shaka.log.debug('PatchedMediaKeysMs.setMediaKeys');
  goog.asserts.assert(this instanceof HTMLMediaElement,
                      'bad "this" for setMediaKeys');

  // Alias
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;

  let newMediaKeys =
      /** @type {shaka.polyfill.PatchedMediaKeysMs.MediaKeys} */ (
      mediaKeys);
  let oldMediaKeys =
      /** @type {shaka.polyfill.PatchedMediaKeysMs.MediaKeys} */ (
      this.mediaKeys);

  if (oldMediaKeys && oldMediaKeys != newMediaKeys) {
    goog.asserts.assert(oldMediaKeys instanceof PatchedMediaKeysMs.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 PatchedMediaKeysMs.MediaKeys,
                        'non-polyfill instance of newMediaKeys');
    return newMediaKeys.setMedia(this);
  }

  return Promise.resolve();
};



/**
 * An implementation of MediaKeys.
 *
 * @constructor
 * @struct
 * @param {string} keySystem
 * @implements {MediaKeys}
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeys = function(keySystem) {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeys');

  /** @private {!MSMediaKeys} */
  this.nativeMediaKeys_ = new MSMediaKeys(keySystem);

  /** @private {!shaka.util.EventManager} */
  this.eventManager_ = new shaka.util.EventManager();
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeys.prototype.
    createSession = function(opt_sessionType) {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeys.createSession');

  let sessionType = opt_sessionType || 'temporary';
  // For now, only the 'temporary' type is supported.
  if (sessionType != 'temporary') {
    throw new TypeError('Session type ' + opt_sessionType +
        ' is unsupported on this platform.');
  }

  // Alias
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;

  return new PatchedMediaKeysMs.MediaKeySession(
      this.nativeMediaKeys_, sessionType);
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeys.prototype.
    setServerCertificate = function(serverCertificate) {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeys.setServerCertificate');

  // There is no equivalent in PatchedMediaKeysMs, so return failure.
  return Promise.resolve(false);
};


/**
 * @param {HTMLMediaElement} media
 * @protected
 * @return {!Promise}
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeys.prototype.
    setMedia = function(media) {

  // Alias
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;

  // 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, 'msneedkey',
      /** @type {shaka.util.EventManager.ListenerType} */
      (PatchedMediaKeysMs.onMsNeedKey_));

  let self = this;
  function setMediaKeysDeferred() {
    media.msSetMediaKeys(self.nativeMediaKeys_);
    media.removeEventListener('loadedmetadata', setMediaKeysDeferred);
  }

  // Wrap native HTMLMediaElement.msSetMediaKeys with a Promise.
  try {
    // IE11/Edge requires that readyState >=1 before mediaKeys can be set, so
    // check this and wait for loadedmetadata if we are not in the correct state
    if (media.readyState >= 1) {
      media.msSetMediaKeys(this.nativeMediaKeys_);
    } else {
      media.addEventListener('loadedmetadata', setMediaKeysDeferred);
    }

    return Promise.resolve();

  } catch (exception) {
    return Promise.reject(exception);
  }
};



/**
 * An implementation of MediaKeySession.
 *
 * @constructor
 * @struct
 * @param {MSMediaKeys} nativeMediaKeys
 * @param {string} sessionType
 * @implements {MediaKeySession}
 * @extends {shaka.util.FakeEventTarget}
 */
shaka.polyfill.PatchedMediaKeysMs.
    MediaKeySession = function(nativeMediaKeys, sessionType) {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySession');
  shaka.util.FakeEventTarget.call(this);

  // The native MediaKeySession, which will be created in generateRequest.
  /** @private {MSMediaKeySession} */
  this.nativeMediaKeySession_ = null;

  /** @private {MSMediaKeys} */
  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.PatchedMediaKeysMs.MediaKeyStatusMap} */
  this.keyStatuses =
      new shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap();
};
goog.inherits(shaka.polyfill.PatchedMediaKeysMs.MediaKeySession,
    shaka.util.FakeEventTarget);


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    generateRequest = function(initDataType, initData) {
  shaka.log.debug('PatchedMediaKeysMs.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.

    // NOTE: IE11 takes either Uint8Array or ArrayBuffer, but Edge 12 only
    // accepts Uint8Array.
    this.nativeMediaKeySession_ = this.nativeMediaKeys_
        .createSession('video/mp4', new Uint8Array(initData), null);

    // Attach session event handlers here.
    this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeymessage',
        /** @type {shaka.util.EventManager.ListenerType} */
        (this.onMsKeyMessage_.bind(this)));
    this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeyadded',
        /** @type {shaka.util.EventManager.ListenerType} */
        (this.onMsKeyAdded_.bind(this)));
    this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeyerror',
        /** @type {shaka.util.EventManager.ListenerType} */
        (this.onMsKeyError_.bind(this)));

    this.updateKeyStatus_('status-pending');
  } catch (exception) {
    this.generateRequestPromise_.reject(exception);
  }

  return this.generateRequestPromise_;
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    load = function() {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.load');

  return Promise.reject(new Error('MediaKeySession.load not yet supported'));
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    update = function(response) {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.update');

  this.updatePromise_ = new shaka.util.PublicPromise();

  try {
    // Pass through to the native session.
    // NOTE: IE11 takes either Uint8Array or ArrayBuffer, but Edge 12 only
    // accepts Uint8Array.
    this.nativeMediaKeySession_.update(new Uint8Array(response));
  } catch (exception) {
    this.updatePromise_.reject(exception);
  }

  return this.updatePromise_;
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    close = function() {
  shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.close');

  try {
    // Pass through to the native session.
    // NOTE: IE seems to have a spec discrepancy here - v2010218 should have
    // MediaKeySession.release, but actually uses "close". The next version
    // of the spec is the initial Promise based one, so it's not the target spec
    // either.
    this.nativeMediaKeySession_.close();

    this.closed.resolve();
    this.eventManager_.removeAll();
  } catch (exception) {
    this.closed.reject(exception);
  }

  return this.closed;
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    remove = function() {
  shaka.log.debug('PatchedMediaKeysMs.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 media elements msNeedKey event.
 *
 * @this {!HTMLMediaElement}
 * @param {!MediaKeyEvent} event
 * @private
 */
shaka.polyfill.PatchedMediaKeysMs.onMsNeedKey_ = function(event) {
  shaka.log.debug('PatchedMediaKeysMs.onMsNeedKey_', event);

  // Alias
  const PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;

  // NOTE: Because "this" is a real EventTarget, on IE, the event we dispatch
  // here must also be a real Event.
  let event2 = /** @type {!CustomEvent} */(document.createEvent('CustomEvent'));
  event2.initCustomEvent('encrypted', false, false, null);
  event2.initDataType = 'cenc';
  event2.initData = PatchedMediaKeysMs.NormaliseInitData_(event.initData);

  this.dispatchEvent(event2);
};


/**
 * Normalise the initData array. This is to apply browser specific work-arounds,
 * e.g. removing duplicates which appears to occur intermittently when the
 * native msneedkey event fires (i.e. event.initData contains dupes).
 *
 * @param {?Uint8Array} initData
 * @private
 * @return {?Uint8Array}
 */
shaka.polyfill.PatchedMediaKeysMs.
    NormaliseInitData_ = function(initData) {
  if (!initData) {
    return initData;
  }

  let pssh = new shaka.util.Pssh(initData);

  // If there is only a single pssh, return the original array.
  if (pssh.dataBoundaries.length <= 1) {
    return initData;
  }

  let unfilteredInitDatas = [];
  for (let i = 0; i < pssh.dataBoundaries.length; i++) {
    let currPssh = initData.subarray(
        pssh.dataBoundaries[i].start,
        pssh.dataBoundaries[i].end + 1); // End is exclusive, hence the +1.

    unfilteredInitDatas.push(currPssh);
  }

  // Dedupe psshData.
  let dedupedInitDatas = shaka.util.ArrayUtils.removeDuplicates(
      unfilteredInitDatas,
      shaka.polyfill.PatchedMediaKeysMs.compareInitDatas_);

  let targetLength = 0;
  for (let i = 0; i < dedupedInitDatas.length; i++) {
    targetLength += dedupedInitDatas[i].length;
  }

  // Flatten the array of Uint8Arrays back into a single Uint8Array.
  let normalisedInitData = new Uint8Array(targetLength);
  let offset = 0;
  for (let i = 0; i < dedupedInitDatas.length; i++) {
    normalisedInitData.set(dedupedInitDatas[i], offset);
    offset += dedupedInitDatas[i].length;
  }

  return normalisedInitData;
};


/**
 * @param {!Uint8Array} initDataA
 * @param {!Uint8Array} initDataB
 * @return {boolean}
 * @private
 */
shaka.polyfill.PatchedMediaKeysMs.compareInitDatas_ =
    function(initDataA, initDataB) {
  return shaka.util.Uint8ArrayUtils.equal(initDataA, initDataB);
};


/**
 * Handler for the native keymessage event on MSMediaKeySession.
 *
 * @param {!MediaKeyEvent} event
 * @private
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    onMsKeyMessage_ = function(event) {
  shaka.log.debug('PatchedMediaKeysMs.onMsKeyMessage_', event);

  // We can now resolve this.generateRequestPromise, which should be non-null.
  goog.asserts.assert(this.generateRequestPromise_,
                      'generateRequestPromise_ not set in onMsKeyMessage_');
  if (this.generateRequestPromise_) {
    this.generateRequestPromise_.resolve();
    this.generateRequestPromise_ = null;
  }

  let isNew = this.keyStatuses.getStatus() == undefined;

  let event2 = new shaka.util.FakeEvent('message', {
    messageType: isNew ? 'licenserequest' : 'licenserenewal',
    message: event.message.buffer
  });

  this.dispatchEvent(event2);
};


/**
 * Handler for the native keyadded event on MSMediaKeySession.
 *
 * @param {!MediaKeyEvent} event
 * @private
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    onMsKeyAdded_ = function(event) {
  shaka.log.debug('PatchedMediaKeysMs.onMsKeyAdded_', event);

  // PlayReady's concept of persistent licenses makes emulation difficult here.
  // A license policy can say that the license persists, which causes the CDM to
  // store it for use in a later session.  The result is that in IE11, the CDM
  // fires 'mskeyadded' without ever firing 'mskeymessage'.
  if (this.generateRequestPromise_) {
    shaka.log.debug('Simulating completion for a PR persistent license.');
    goog.asserts.assert(!this.updatePromise_,
        'updatePromise_ and generateRequestPromise_ set in onMsKeyAdded_');
    this.updateKeyStatus_('usable');
    this.generateRequestPromise_.resolve();
    this.generateRequestPromise_ = null;
    return;
  }

  // We can now resolve this.updatePromise, which should be non-null.
  goog.asserts.assert(this.updatePromise_,
                      'updatePromise_ not set in onMsKeyAdded_');
  if (this.updatePromise_) {
    this.updateKeyStatus_('usable');
    this.updatePromise_.resolve();
    this.updatePromise_ = null;
  }
};


/**
 * Handler for the native keyerror event on MSMediaKeySession.
 *
 * @param {!MediaKeyEvent} event
 * @private
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    onMsKeyError_ = function(event) {
  shaka.log.debug('PatchedMediaKeysMs.onMsKeyError_', event);

  let error = new Error('EME PatchedMediaKeysMs 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:
    // MS_MEDIA_KEYERR_UNKNOWN        = 1
    // MS_MEDIA_KEYERR_CLIENT         = 2
    // MS_MEDIA_KEYERR_SERVICE        = 3
    // MS_MEDIA_KEYERR_OUTPUT         = 4
    // MS_MEDIA_KEYERR_HARDWARECHANGE = 5
    // MS_MEDIA_KEYERR_DOMAIN         = 6

    switch (this.nativeMediaKeySession_.error.code) {
      case MSMediaKeyError.MS_MEDIA_KEYERR_OUTPUT:
      case MSMediaKeyError.MS_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
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
    updateKeyStatus_ = function(status) {
  this.keyStatuses.setStatus(status);
  let event = new shaka.util.FakeEvent('keystatuseschange');
  this.dispatchEvent(event);
};



/**
 * An implementation of MediaKeyStatusMap.
 * This fakes a map with a single key ID.
 *
 * @constructor
 * @struct
 * @implements {MediaKeyStatusMap}
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap = function() {
  /**
   * @type {number}
   */
  this.size = 0;

  /**
   * @private {string|undefined}
   */
  this.status_ = undefined;
};


/**
 * @const {!ArrayBuffer}
 * @private
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_;


/**
 * An internal method used by the session to set key status.
 * @param {string|undefined} status
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    setStatus = function(status) {
  this.size = status == undefined ? 0 : 1;
  this.status_ = status;
};


/**
 * An internal method used by the session to get key status.
 * @return {string|undefined}
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    getStatus = function() {
  return this.status_;
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    forEach = function(fn) {
  if (this.status_) {
    let fakeKeyId =
        shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_;
    fn(this.status_, fakeKeyId);
  }
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    get = function(keyId) {
  if (this.has(keyId)) {
    return this.status_;
  }
  return undefined;
};


/** @override */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    has = function(keyId) {
  let fakeKeyId =
      shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_;
  if (this.status_ &&
      shaka.util.Uint8ArrayUtils.equal(
          new Uint8Array(keyId), new Uint8Array(fakeKeyId))) {
    return true;
  }
  return false;
};


/**
 * @suppress {missingReturn}
 * @override
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    entries = function() {
  goog.asserts.assert(false, 'Not used!  Provided only for the compiler.');
};


/**
 * @suppress {missingReturn}
 * @override
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    keys = function() {
  goog.asserts.assert(false, 'Not used!  Provided only for the compiler.');
};


/**
 * @suppress {missingReturn}
 * @override
 */
shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
    values = function() {
  goog.asserts.assert(false, 'Not used!  Provided only for the compiler.');
};


shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysMs.install);