Source: lib/polyfill/patchedmediakeys_apple.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.polyfill.PatchedMediaKeysApple');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.media.DrmEngine');
  10. goog.require('shaka.polyfill');
  11. goog.require('shaka.util.BufferUtils');
  12. goog.require('shaka.util.EventManager');
  13. goog.require('shaka.util.FakeEvent');
  14. goog.require('shaka.util.FakeEventTarget');
  15. goog.require('shaka.util.MediaReadyState');
  16. goog.require('shaka.util.PublicPromise');
  17. goog.require('shaka.util.StringUtils');
  18. /**
  19. * @summary A polyfill to implement modern, standardized EME on top of Apple's
  20. * prefixed EME in Safari.
  21. * @export
  22. */
  23. shaka.polyfill.PatchedMediaKeysApple = class {
  24. /**
  25. * Installs the polyfill if needed.
  26. * @export
  27. */
  28. static install() {
  29. if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
  30. // No HTML5 video or no prefixed EME.
  31. return;
  32. }
  33. /* Unprefixed EME disabled. See:
  34. https://github.com/google/shaka-player/pull/3021#issuecomment-766999811
  35. // Only tested in Safari 14.
  36. const safariVersion = shaka.util.Platform.safariVersion();
  37. if (navigator.requestMediaKeySystemAccess &&
  38. // eslint-disable-next-line no-restricted-syntax
  39. MediaKeySystemAccess.prototype.getConfiguration &&
  40. safariVersion && safariVersion >= 14) {
  41. // Unprefixed EME is preferable.
  42. return;
  43. }
  44. */
  45. shaka.log.info('Using Apple-prefixed EME');
  46. // Alias
  47. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  48. // Delete mediaKeys to work around strict mode compatibility issues.
  49. // eslint-disable-next-line no-restricted-syntax
  50. delete HTMLMediaElement.prototype['mediaKeys'];
  51. // Work around read-only declaration for mediaKeys by using a string.
  52. // eslint-disable-next-line no-restricted-syntax
  53. HTMLMediaElement.prototype['mediaKeys'] = null;
  54. // eslint-disable-next-line no-restricted-syntax
  55. HTMLMediaElement.prototype.setMediaKeys =
  56. PatchedMediaKeysApple.setMediaKeys;
  57. // Install patches
  58. window.MediaKeys = PatchedMediaKeysApple.MediaKeys;
  59. window.MediaKeySystemAccess = PatchedMediaKeysApple.MediaKeySystemAccess;
  60. navigator.requestMediaKeySystemAccess =
  61. PatchedMediaKeysApple.requestMediaKeySystemAccess;
  62. }
  63. /**
  64. * An implementation of navigator.requestMediaKeySystemAccess.
  65. * Retrieves a MediaKeySystemAccess object.
  66. *
  67. * @this {!Navigator}
  68. * @param {string} keySystem
  69. * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
  70. * @return {!Promise.<!MediaKeySystemAccess>}
  71. */
  72. static requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
  73. shaka.log.debug('PatchedMediaKeysApple.requestMediaKeySystemAccess');
  74. goog.asserts.assert(this == navigator,
  75. 'bad "this" for requestMediaKeySystemAccess');
  76. // Alias.
  77. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  78. try {
  79. const access = new PatchedMediaKeysApple.MediaKeySystemAccess(
  80. keySystem, supportedConfigurations);
  81. return Promise.resolve(/** @type {!MediaKeySystemAccess} */ (access));
  82. } catch (exception) {
  83. return Promise.reject(exception);
  84. }
  85. }
  86. /**
  87. * An implementation of HTMLMediaElement.prototype.setMediaKeys.
  88. * Attaches a MediaKeys object to the media element.
  89. *
  90. * @this {!HTMLMediaElement}
  91. * @param {MediaKeys} mediaKeys
  92. * @return {!Promise}
  93. */
  94. static setMediaKeys(mediaKeys) {
  95. shaka.log.debug('PatchedMediaKeysApple.setMediaKeys');
  96. goog.asserts.assert(this instanceof HTMLMediaElement,
  97. 'bad "this" for setMediaKeys');
  98. // Alias
  99. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  100. const newMediaKeys =
  101. /** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */ (
  102. mediaKeys);
  103. const oldMediaKeys =
  104. /** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */ (
  105. this.mediaKeys);
  106. if (oldMediaKeys && oldMediaKeys != newMediaKeys) {
  107. goog.asserts.assert(
  108. oldMediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
  109. 'non-polyfill instance of oldMediaKeys');
  110. // Have the old MediaKeys stop listening to events on the video tag.
  111. oldMediaKeys.setMedia(null);
  112. }
  113. delete this['mediaKeys']; // in case there is an existing getter
  114. this['mediaKeys'] = mediaKeys; // work around read-only declaration
  115. if (newMediaKeys) {
  116. goog.asserts.assert(
  117. newMediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
  118. 'non-polyfill instance of newMediaKeys');
  119. return newMediaKeys.setMedia(this);
  120. }
  121. return Promise.resolve();
  122. }
  123. /**
  124. * Handler for the native media elements webkitneedkey event.
  125. *
  126. * @this {!HTMLMediaElement}
  127. * @param {!MediaKeyEvent} event
  128. * @suppress {constantProperty} We reassign what would be const on a real
  129. * MediaEncryptedEvent, but in our look-alike event.
  130. * @private
  131. */
  132. static onWebkitNeedKey_(event) {
  133. shaka.log.debug('PatchedMediaKeysApple.onWebkitNeedKey_', event);
  134. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  135. const mediaKeys =
  136. /** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */(
  137. this.mediaKeys);
  138. goog.asserts.assert(mediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
  139. 'non-polyfill instance of newMediaKeys');
  140. goog.asserts.assert(event.initData != null, 'missing init data!');
  141. // Convert the prefixed init data to match the native 'encrypted' event.
  142. const uint8 = shaka.util.BufferUtils.toUint8(event.initData);
  143. const dataview = shaka.util.BufferUtils.toDataView(uint8);
  144. // The first part is a 4 byte little-endian int, which is the length of
  145. // the second part.
  146. const length = dataview.getUint32(
  147. /* position= */ 0, /* littleEndian= */ true);
  148. if (length + 4 != uint8.byteLength) {
  149. throw new RangeError('Malformed FairPlay init data');
  150. }
  151. // The remainder is a UTF-16 skd URL. Convert this to UTF-8 and pass on.
  152. const str = shaka.util.StringUtils.fromUTF16(
  153. uint8.subarray(4), /* littleEndian= */ true);
  154. const initData = shaka.util.StringUtils.toUTF8(str);
  155. // NOTE: Because "this" is a real EventTarget, the event we dispatch here
  156. // must also be a real Event.
  157. const event2 = new Event('encrypted');
  158. const encryptedEvent =
  159. /** @type {!MediaEncryptedEvent} */(/** @type {?} */(event2));
  160. encryptedEvent.initDataType = 'skd';
  161. encryptedEvent.initData = shaka.util.BufferUtils.toArrayBuffer(initData);
  162. this.dispatchEvent(event2);
  163. }
  164. };
  165. /**
  166. * An implementation of MediaKeySystemAccess.
  167. *
  168. * @implements {MediaKeySystemAccess}
  169. */
  170. shaka.polyfill.PatchedMediaKeysApple.MediaKeySystemAccess = class {
  171. /**
  172. * @param {string} keySystem
  173. * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
  174. */
  175. constructor(keySystem, supportedConfigurations) {
  176. shaka.log.debug('PatchedMediaKeysApple.MediaKeySystemAccess');
  177. /** @type {string} */
  178. this.keySystem = keySystem;
  179. /** @private {!MediaKeySystemConfiguration} */
  180. this.configuration_;
  181. // Optimization: WebKitMediaKeys.isTypeSupported delays responses by a
  182. // significant amount of time, possibly to discourage fingerprinting.
  183. // Since we know only FairPlay is supported here, let's skip queries for
  184. // anything else to speed up the process.
  185. if (keySystem.startsWith('com.apple.fps')) {
  186. for (const cfg of supportedConfigurations) {
  187. const newCfg = this.checkConfig_(cfg);
  188. if (newCfg) {
  189. this.configuration_ = newCfg;
  190. return;
  191. }
  192. }
  193. }
  194. // According to the spec, this should be a DOMException, but there is not a
  195. // public constructor for that. So we make this look-alike instead.
  196. const unsupportedKeySystemError = new Error('Unsupported keySystem');
  197. unsupportedKeySystemError.name = 'NotSupportedError';
  198. unsupportedKeySystemError['code'] = DOMException.NOT_SUPPORTED_ERR;
  199. throw unsupportedKeySystemError;
  200. }
  201. /**
  202. * Check a single config for MediaKeySystemAccess.
  203. *
  204. * @param {MediaKeySystemConfiguration} cfg The requested config.
  205. * @return {?MediaKeySystemConfiguration} A matching config we can support, or
  206. * null if the input is not supportable.
  207. * @private
  208. */
  209. checkConfig_(cfg) {
  210. if (cfg.persistentState == 'required') {
  211. // Not supported by the prefixed API.
  212. return null;
  213. }
  214. // Create a new config object and start adding in the pieces which we find
  215. // support for. We will return this from getConfiguration() later if
  216. // asked.
  217. /** @type {!MediaKeySystemConfiguration} */
  218. const newCfg = {
  219. 'audioCapabilities': [],
  220. 'videoCapabilities': [],
  221. // It is technically against spec to return these as optional, but we
  222. // don't truly know their values from the prefixed API:
  223. 'persistentState': 'optional',
  224. 'distinctiveIdentifier': 'optional',
  225. // Pretend the requested init data types are supported, since we don't
  226. // really know that either:
  227. 'initDataTypes': cfg.initDataTypes,
  228. 'sessionTypes': ['temporary'],
  229. 'label': cfg.label,
  230. };
  231. // PatchedMediaKeysApple tests for key system availability through
  232. // WebKitMediaKeys.isTypeSupported.
  233. let ranAnyTests = false;
  234. let success = false;
  235. if (cfg.audioCapabilities) {
  236. for (const cap of cfg.audioCapabilities) {
  237. if (cap.contentType) {
  238. ranAnyTests = true;
  239. const contentType = cap.contentType.split(';')[0];
  240. if (WebKitMediaKeys.isTypeSupported(this.keySystem, contentType)) {
  241. newCfg.audioCapabilities.push(cap);
  242. success = true;
  243. }
  244. }
  245. }
  246. }
  247. if (cfg.videoCapabilities) {
  248. for (const cap of cfg.videoCapabilities) {
  249. if (cap.contentType) {
  250. ranAnyTests = true;
  251. const contentType = cap.contentType.split(';')[0];
  252. if (WebKitMediaKeys.isTypeSupported(this.keySystem, contentType)) {
  253. newCfg.videoCapabilities.push(cap);
  254. success = true;
  255. }
  256. }
  257. }
  258. }
  259. if (!ranAnyTests) {
  260. // If no specific types were requested, we check all common types to
  261. // find out if the key system is present at all.
  262. success = WebKitMediaKeys.isTypeSupported(this.keySystem, 'video/mp4');
  263. }
  264. if (success) {
  265. return newCfg;
  266. }
  267. return null;
  268. }
  269. /** @override */
  270. createMediaKeys() {
  271. shaka.log.debug(
  272. 'PatchedMediaKeysApple.MediaKeySystemAccess.createMediaKeys');
  273. // Alias
  274. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  275. const mediaKeys = new PatchedMediaKeysApple.MediaKeys(this.keySystem);
  276. return Promise.resolve(/** @type {!MediaKeys} */ (mediaKeys));
  277. }
  278. /** @override */
  279. getConfiguration() {
  280. shaka.log.debug(
  281. 'PatchedMediaKeysApple.MediaKeySystemAccess.getConfiguration');
  282. return this.configuration_;
  283. }
  284. };
  285. /**
  286. * An implementation of MediaKeys.
  287. *
  288. * @implements {MediaKeys}
  289. */
  290. shaka.polyfill.PatchedMediaKeysApple.MediaKeys = class {
  291. /** @param {string} keySystem */
  292. constructor(keySystem) {
  293. shaka.log.debug('PatchedMediaKeysApple.MediaKeys');
  294. /** @private {!WebKitMediaKeys} */
  295. this.nativeMediaKeys_ = new WebKitMediaKeys(keySystem);
  296. /** @private {!shaka.util.EventManager} */
  297. this.eventManager_ = new shaka.util.EventManager();
  298. }
  299. /** @override */
  300. createSession(sessionType) {
  301. shaka.log.debug('PatchedMediaKeysApple.MediaKeys.createSession');
  302. sessionType = sessionType || 'temporary';
  303. // For now, only the 'temporary' type is supported.
  304. if (sessionType != 'temporary') {
  305. throw new TypeError('Session type ' + sessionType +
  306. ' is unsupported on this platform.');
  307. }
  308. // Alias
  309. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  310. return new PatchedMediaKeysApple.MediaKeySession(
  311. this.nativeMediaKeys_, sessionType);
  312. }
  313. /** @override */
  314. setServerCertificate(serverCertificate) {
  315. shaka.log.debug('PatchedMediaKeysApple.MediaKeys.setServerCertificate');
  316. return Promise.resolve(false);
  317. }
  318. /**
  319. * @param {HTMLMediaElement} media
  320. * @protected
  321. * @return {!Promise}
  322. */
  323. setMedia(media) {
  324. // Alias
  325. const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
  326. // Remove any old listeners.
  327. this.eventManager_.removeAll();
  328. // It is valid for media to be null; null is used to flag that event
  329. // handlers need to be cleaned up.
  330. if (!media) {
  331. return Promise.resolve();
  332. }
  333. // Intercept and translate these prefixed EME events.
  334. this.eventManager_.listen(media, 'webkitneedkey',
  335. /** @type {shaka.util.EventManager.ListenerType} */
  336. (PatchedMediaKeysApple.onWebkitNeedKey_));
  337. // Wrap native HTMLMediaElement.webkitSetMediaKeys with a Promise.
  338. try {
  339. // Some browsers require that readyState >=1 before mediaKeys can be
  340. // set, so check this and wait for loadedmetadata if we are not in the
  341. // correct state
  342. shaka.util.MediaReadyState.waitForReadyState(media,
  343. HTMLMediaElement.HAVE_METADATA,
  344. this.eventManager_, () => {
  345. media.webkitSetMediaKeys(this.nativeMediaKeys_);
  346. });
  347. return Promise.resolve();
  348. } catch (exception) {
  349. return Promise.reject(exception);
  350. }
  351. }
  352. };
  353. /**
  354. * An implementation of MediaKeySession.
  355. *
  356. * @implements {MediaKeySession}
  357. */
  358. shaka.polyfill.PatchedMediaKeysApple.MediaKeySession =
  359. class extends shaka.util.FakeEventTarget {
  360. /**
  361. * @param {WebKitMediaKeys} nativeMediaKeys
  362. * @param {string} sessionType
  363. */
  364. constructor(nativeMediaKeys, sessionType) {
  365. shaka.log.debug('PatchedMediaKeysApple.MediaKeySession');
  366. super();
  367. /**
  368. * The native MediaKeySession, which will be created in generateRequest.
  369. * @private {WebKitMediaKeySession}
  370. */
  371. this.nativeMediaKeySession_ = null;
  372. /** @private {WebKitMediaKeys} */
  373. this.nativeMediaKeys_ = nativeMediaKeys;
  374. // Promises that are resolved later
  375. /** @private {shaka.util.PublicPromise} */
  376. this.generateRequestPromise_ = null;
  377. /** @private {shaka.util.PublicPromise} */
  378. this.updatePromise_ = null;
  379. /** @private {!shaka.util.EventManager} */
  380. this.eventManager_ = new shaka.util.EventManager();
  381. /** @type {string} */
  382. this.sessionId = '';
  383. /** @type {number} */
  384. this.expiration = NaN;
  385. /** @type {!shaka.util.PublicPromise} */
  386. this.closed = new shaka.util.PublicPromise();
  387. /** @type {!shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap} */
  388. this.keyStatuses =
  389. new shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap();
  390. }
  391. /** @override */
  392. generateRequest(initDataType, initData) {
  393. shaka.log.debug(
  394. 'PatchedMediaKeysApple.MediaKeySession.generateRequest');
  395. this.generateRequestPromise_ = new shaka.util.PublicPromise();
  396. try {
  397. // This EME spec version requires a MIME content type as the 1st param to
  398. // createSession, but doesn't seem to matter what the value is.
  399. // It also only accepts Uint8Array, not ArrayBuffer, so explicitly make
  400. // initData into a Uint8Array.
  401. const session = this.nativeMediaKeys_.createSession(
  402. 'video/mp4', shaka.util.BufferUtils.toUint8(initData));
  403. this.nativeMediaKeySession_ = session;
  404. this.sessionId = session.sessionId || '';
  405. // Attach session event handlers here.
  406. this.eventManager_.listen(
  407. this.nativeMediaKeySession_, 'webkitkeymessage',
  408. /** @type {shaka.util.EventManager.ListenerType} */
  409. ((event) => this.onWebkitKeyMessage_(event)));
  410. this.eventManager_.listen(session, 'webkitkeyadded',
  411. /** @type {shaka.util.EventManager.ListenerType} */
  412. ((event) => this.onWebkitKeyAdded_(event)));
  413. this.eventManager_.listen(session, 'webkitkeyerror',
  414. /** @type {shaka.util.EventManager.ListenerType} */
  415. ((event) => this.onWebkitKeyError_(event)));
  416. this.updateKeyStatus_('status-pending');
  417. } catch (exception) {
  418. this.generateRequestPromise_.reject(exception);
  419. }
  420. return this.generateRequestPromise_;
  421. }
  422. /** @override */
  423. load() {
  424. shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.load');
  425. return Promise.reject(new Error('MediaKeySession.load not yet supported'));
  426. }
  427. /** @override */
  428. update(response) {
  429. shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.update');
  430. this.updatePromise_ = new shaka.util.PublicPromise();
  431. try {
  432. // Pass through to the native session.
  433. this.nativeMediaKeySession_.update(
  434. shaka.util.BufferUtils.toUint8(response));
  435. } catch (exception) {
  436. this.updatePromise_.reject(exception);
  437. }
  438. return this.updatePromise_;
  439. }
  440. /** @override */
  441. close() {
  442. shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.close');
  443. try {
  444. // Pass through to the native session.
  445. this.nativeMediaKeySession_.close();
  446. this.closed.resolve();
  447. this.eventManager_.removeAll();
  448. } catch (exception) {
  449. this.closed.reject(exception);
  450. }
  451. return this.closed;
  452. }
  453. /** @override */
  454. remove() {
  455. shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.remove');
  456. return Promise.reject(new Error(
  457. 'MediaKeySession.remove is only applicable for persistent licenses, ' +
  458. 'which are not supported on this platform'));
  459. }
  460. /**
  461. * Handler for the native keymessage event on WebKitMediaKeySession.
  462. *
  463. * @param {!MediaKeyEvent} event
  464. * @private
  465. */
  466. onWebkitKeyMessage_(event) {
  467. shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyMessage_', event);
  468. // We can now resolve this.generateRequestPromise, which should be non-null.
  469. goog.asserts.assert(this.generateRequestPromise_,
  470. 'generateRequestPromise_ should be set before now!');
  471. if (this.generateRequestPromise_) {
  472. this.generateRequestPromise_.resolve();
  473. this.generateRequestPromise_ = null;
  474. }
  475. const isNew = this.keyStatuses.getStatus() == undefined;
  476. const data = new Map()
  477. .set('messageType', isNew ? 'license-request' : 'license-renewal')
  478. .set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
  479. const event2 = new shaka.util.FakeEvent('message', data);
  480. this.dispatchEvent(event2);
  481. }
  482. /**
  483. * Handler for the native keyadded event on WebKitMediaKeySession.
  484. *
  485. * @param {!MediaKeyEvent} event
  486. * @private
  487. */
  488. onWebkitKeyAdded_(event) {
  489. shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyAdded_', event);
  490. // This shouldn't fire while we're in the middle of generateRequest,
  491. // but if it does, we will need to change the logic to account for it.
  492. goog.asserts.assert(!this.generateRequestPromise_,
  493. 'Key added during generate!');
  494. // We can now resolve this.updatePromise, which should be non-null.
  495. goog.asserts.assert(this.updatePromise_,
  496. 'updatePromise_ should be set before now!');
  497. if (this.updatePromise_) {
  498. this.updateKeyStatus_('usable');
  499. this.updatePromise_.resolve();
  500. this.updatePromise_ = null;
  501. }
  502. }
  503. /**
  504. * Handler for the native keyerror event on WebKitMediaKeySession.
  505. *
  506. * @param {!MediaKeyEvent} event
  507. * @private
  508. */
  509. onWebkitKeyError_(event) {
  510. shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyError_', event);
  511. const error = new Error('EME PatchedMediaKeysApple key error');
  512. error['errorCode'] = this.nativeMediaKeySession_.error;
  513. if (this.generateRequestPromise_ != null) {
  514. this.generateRequestPromise_.reject(error);
  515. this.generateRequestPromise_ = null;
  516. } else if (this.updatePromise_ != null) {
  517. this.updatePromise_.reject(error);
  518. this.updatePromise_ = null;
  519. } else {
  520. // Unexpected error - map native codes to standardised key statuses.
  521. // Possible values of this.nativeMediaKeySession_.error.code:
  522. // MEDIA_KEYERR_UNKNOWN = 1
  523. // MEDIA_KEYERR_CLIENT = 2
  524. // MEDIA_KEYERR_SERVICE = 3
  525. // MEDIA_KEYERR_OUTPUT = 4
  526. // MEDIA_KEYERR_HARDWARECHANGE = 5
  527. // MEDIA_KEYERR_DOMAIN = 6
  528. switch (this.nativeMediaKeySession_.error.code) {
  529. case WebKitMediaKeyError.MEDIA_KEYERR_OUTPUT:
  530. case WebKitMediaKeyError.MEDIA_KEYERR_HARDWARECHANGE:
  531. this.updateKeyStatus_('output-not-allowed');
  532. break;
  533. default:
  534. this.updateKeyStatus_('internal-error');
  535. break;
  536. }
  537. }
  538. }
  539. /**
  540. * Updates key status and dispatch a 'keystatuseschange' event.
  541. *
  542. * @param {string} status
  543. * @private
  544. */
  545. updateKeyStatus_(status) {
  546. this.keyStatuses.setStatus(status);
  547. const event = new shaka.util.FakeEvent('keystatuseschange');
  548. this.dispatchEvent(event);
  549. }
  550. };
  551. /**
  552. * @summary An implementation of MediaKeyStatusMap.
  553. * This fakes a map with a single key ID.
  554. *
  555. * @todo Consolidate the MediaKeyStatusMap types in these polyfills.
  556. * @implements {MediaKeyStatusMap}
  557. */
  558. shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
  559. /** */
  560. constructor() {
  561. /**
  562. * @type {number}
  563. */
  564. this.size = 0;
  565. /**
  566. * @private {string|undefined}
  567. */
  568. this.status_ = undefined;
  569. }
  570. /**
  571. * An internal method used by the session to set key status.
  572. * @param {string|undefined} status
  573. */
  574. setStatus(status) {
  575. this.size = status == undefined ? 0 : 1;
  576. this.status_ = status;
  577. }
  578. /**
  579. * An internal method used by the session to get key status.
  580. * @return {string|undefined}
  581. */
  582. getStatus() {
  583. return this.status_;
  584. }
  585. /** @override */
  586. forEach(fn) {
  587. if (this.status_) {
  588. fn(this.status_, shaka.media.DrmEngine.DUMMY_KEY_ID.value());
  589. }
  590. }
  591. /** @override */
  592. get(keyId) {
  593. if (this.has(keyId)) {
  594. return this.status_;
  595. }
  596. return undefined;
  597. }
  598. /** @override */
  599. has(keyId) {
  600. const fakeKeyId = shaka.media.DrmEngine.DUMMY_KEY_ID.value();
  601. if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
  602. return true;
  603. }
  604. return false;
  605. }
  606. /**
  607. * @suppress {missingReturn}
  608. * @override
  609. */
  610. entries() {
  611. goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
  612. }
  613. /**
  614. * @suppress {missingReturn}
  615. * @override
  616. */
  617. keys() {
  618. goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
  619. }
  620. /**
  621. * @suppress {missingReturn}
  622. * @override
  623. */
  624. values() {
  625. goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
  626. }
  627. };
  628. shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysApple.install);