Source: lib/polyfill/patchedmediakeys_ms.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.polyfill.PatchedMediaKeysMs');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.log');
  20. goog.require('shaka.util.ArrayUtils');
  21. goog.require('shaka.util.EventManager');
  22. goog.require('shaka.util.FakeEvent');
  23. goog.require('shaka.util.FakeEventTarget');
  24. goog.require('shaka.util.Pssh');
  25. goog.require('shaka.util.PublicPromise');
  26. goog.require('shaka.util.Uint8ArrayUtils');
  27. /**
  28. * Install a polyfill to implement {@link http://goo.gl/blgtZZ EME draft
  29. * 12 March 2015} on top of ms-prefixed
  30. * {@link http://www.w3.org/TR/2014/WD-encrypted-media-20140218/ EME v20140218}.
  31. */
  32. shaka.polyfill.PatchedMediaKeysMs.install = function() {
  33. shaka.log.debug('PatchedMediaKeysMs.install');
  34. // Alias
  35. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  36. // Construct fake key ID. This is not done at load-time to avoid exceptions
  37. // on unsupported browsers. This particular fake key ID was suggested in
  38. // w3c/encrypted-media#32.
  39. PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_ = (new Uint8Array([0])).buffer;
  40. // Delete mediaKeys to work around strict mode compatibility issues.
  41. delete HTMLMediaElement.prototype['mediaKeys'];
  42. // Work around read-only declaration for mediaKeys by using a string.
  43. HTMLMediaElement.prototype['mediaKeys'] = null;
  44. HTMLMediaElement.prototype.setMediaKeys = PatchedMediaKeysMs.setMediaKeys;
  45. // Install patches
  46. window.MediaKeys = PatchedMediaKeysMs.MediaKeys;
  47. window.MediaKeySystemAccess = PatchedMediaKeysMs.MediaKeySystemAccess;
  48. navigator.requestMediaKeySystemAccess =
  49. PatchedMediaKeysMs.requestMediaKeySystemAccess;
  50. };
  51. /**
  52. * An implementation of navigator.requestMediaKeySystemAccess.
  53. * Retrieve a MediaKeySystemAccess object.
  54. *
  55. * @this {!Navigator}
  56. * @param {string} keySystem
  57. * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
  58. * @return {!Promise.<!MediaKeySystemAccess>}
  59. * @suppress {unnecessaryCasts}
  60. */
  61. shaka.polyfill.PatchedMediaKeysMs.requestMediaKeySystemAccess =
  62. function(keySystem, supportedConfigurations) {
  63. shaka.log.debug('PatchedMediaKeysMs.requestMediaKeySystemAccess');
  64. goog.asserts.assert(this == navigator,
  65. 'bad "this" for requestMediaKeySystemAccess');
  66. // Alias.
  67. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  68. try {
  69. var access = new PatchedMediaKeysMs.MediaKeySystemAccess(
  70. keySystem, supportedConfigurations);
  71. return Promise.resolve(/** @type {!MediaKeySystemAccess} */ (access));
  72. } catch (exception) {
  73. return Promise.reject(exception);
  74. }
  75. };
  76. /**
  77. * An implementation of MediaKeySystemAccess.
  78. *
  79. * @constructor
  80. * @struct
  81. * @param {string} keySystem
  82. * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
  83. * @implements {MediaKeySystemAccess}
  84. * @throws {Error} if the key system is not supported.
  85. */
  86. shaka.polyfill.PatchedMediaKeysMs.MediaKeySystemAccess =
  87. function(keySystem, supportedConfigurations) {
  88. shaka.log.debug('PatchedMediaKeysMs.MediaKeySystemAccess');
  89. /** @type {string} */
  90. this.keySystem = keySystem;
  91. /** @private {!MediaKeySystemConfiguration} */
  92. this.configuration_;
  93. var allowPersistentState = true;
  94. var success = false;
  95. for (var i = 0; i < supportedConfigurations.length; ++i) {
  96. var cfg = supportedConfigurations[i];
  97. // Create a new config object and start adding in the pieces which we
  98. // find support for. We will return this from getConfiguration() if
  99. // asked.
  100. /** @type {!MediaKeySystemConfiguration} */
  101. var newCfg = {
  102. 'audioCapabilities': [],
  103. 'videoCapabilities': [],
  104. // It is technically against spec to return these as optional, but we
  105. // don't truly know their values from the prefixed API:
  106. 'persistentState': 'optional',
  107. 'distinctiveIdentifier': 'optional',
  108. // Pretend the requested init data types are supported, since we don't
  109. // really know that either:
  110. 'initDataTypes': cfg.initDataTypes,
  111. 'sessionTypes': ['temporary'],
  112. 'label': cfg.label
  113. };
  114. // PatchedMediaKeysMs tests for key system availability through
  115. // MSMediaKeys.isTypeSupported
  116. var ranAnyTests = false;
  117. if (cfg.audioCapabilities) {
  118. for (var j = 0; j < cfg.audioCapabilities.length; ++j) {
  119. var cap = cfg.audioCapabilities[j];
  120. if (cap.contentType) {
  121. ranAnyTests = true;
  122. var contentType = cap.contentType.split(';')[0];
  123. if (MSMediaKeys.isTypeSupported(this.keySystem, contentType)) {
  124. newCfg.audioCapabilities.push(cap);
  125. success = true;
  126. }
  127. }
  128. }
  129. }
  130. if (cfg.videoCapabilities) {
  131. for (var j = 0; j < cfg.videoCapabilities.length; ++j) {
  132. var cap = cfg.videoCapabilities[j];
  133. if (cap.contentType) {
  134. ranAnyTests = true;
  135. var contentType = cap.contentType.split(';')[0];
  136. if (MSMediaKeys.isTypeSupported(this.keySystem, contentType)) {
  137. newCfg.videoCapabilities.push(cap);
  138. success = true;
  139. }
  140. }
  141. }
  142. }
  143. if (!ranAnyTests) {
  144. // If no specific types were requested, we check all common types to find
  145. // out if the key system is present at all.
  146. success = MSMediaKeys.isTypeSupported(this.keySystem, 'video/mp4');
  147. }
  148. if (cfg.persistentState == 'required') {
  149. if (allowPersistentState) {
  150. newCfg.persistentState = 'required';
  151. newCfg.sessionTypes = ['persistent-license'];
  152. } else {
  153. success = false;
  154. }
  155. }
  156. if (success) {
  157. this.configuration_ = newCfg;
  158. return;
  159. }
  160. } // for each cfg in supportedConfigurations
  161. // As per the spec, this should be a DOMException, but
  162. // there is not a public constructor for this
  163. var unsupportedKeySystemError = new Error('Unsupported keySystem');
  164. unsupportedKeySystemError.name = 'NotSupportedError';
  165. unsupportedKeySystemError.code = DOMException.NOT_SUPPORTED_ERR;
  166. throw unsupportedKeySystemError;
  167. };
  168. /**
  169. * @override
  170. * @suppress {unnecessaryCasts}
  171. */
  172. shaka.polyfill.PatchedMediaKeysMs.MediaKeySystemAccess.prototype.
  173. createMediaKeys = function() {
  174. shaka.log.debug('PatchedMediaKeysMs.MediaKeySystemAccess.createMediaKeys');
  175. // Alias
  176. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  177. var mediaKeys = new PatchedMediaKeysMs.MediaKeys(this.keySystem);
  178. return Promise.resolve(/** @type {!MediaKeys} */ (mediaKeys));
  179. };
  180. /** @override */
  181. shaka.polyfill.PatchedMediaKeysMs.MediaKeySystemAccess.prototype.
  182. getConfiguration = function() {
  183. shaka.log.debug('PatchedMediaKeysMs.MediaKeySystemAccess.getConfiguration');
  184. return this.configuration_;
  185. };
  186. /**
  187. * An implementation of HTMLMediaElement.prototype.setMediaKeys.
  188. * Attach a MediaKeys object to the media element.
  189. *
  190. * @this {!HTMLMediaElement}
  191. * @param {MediaKeys} mediaKeys
  192. * @return {!Promise}
  193. */
  194. shaka.polyfill.PatchedMediaKeysMs.setMediaKeys = function(mediaKeys) {
  195. shaka.log.debug('PatchedMediaKeysMs.setMediaKeys');
  196. goog.asserts.assert(this instanceof HTMLMediaElement,
  197. 'bad "this" for setMediaKeys');
  198. // Alias
  199. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  200. var newMediaKeys =
  201. /** @type {shaka.polyfill.PatchedMediaKeysMs.MediaKeys} */ (
  202. mediaKeys);
  203. var oldMediaKeys =
  204. /** @type {shaka.polyfill.PatchedMediaKeysMs.MediaKeys} */ (
  205. this.mediaKeys);
  206. if (oldMediaKeys && oldMediaKeys != newMediaKeys) {
  207. goog.asserts.assert(oldMediaKeys instanceof PatchedMediaKeysMs.MediaKeys,
  208. 'non-polyfill instance of oldMediaKeys');
  209. // Have the old MediaKeys stop listening to events on the video tag.
  210. oldMediaKeys.setMedia(null);
  211. }
  212. delete this['mediaKeys']; // in case there is an existing getter
  213. this['mediaKeys'] = mediaKeys; // work around read-only declaration
  214. if (newMediaKeys) {
  215. goog.asserts.assert(newMediaKeys instanceof PatchedMediaKeysMs.MediaKeys,
  216. 'non-polyfill instance of newMediaKeys');
  217. return newMediaKeys.setMedia(this);
  218. }
  219. return Promise.resolve();
  220. };
  221. /**
  222. * An implementation of MediaKeys.
  223. *
  224. * @constructor
  225. * @struct
  226. * @param {string} keySystem
  227. * @implements {MediaKeys}
  228. */
  229. shaka.polyfill.PatchedMediaKeysMs.MediaKeys = function(keySystem) {
  230. shaka.log.debug('PatchedMediaKeysMs.MediaKeys');
  231. /** @private {!MSMediaKeys} */
  232. this.nativeMediaKeys_ = new MSMediaKeys(keySystem);
  233. /** @private {!shaka.util.EventManager} */
  234. this.eventManager_ = new shaka.util.EventManager();
  235. };
  236. /** @override */
  237. shaka.polyfill.PatchedMediaKeysMs.MediaKeys.prototype.
  238. createSession = function(opt_sessionType) {
  239. shaka.log.debug('PatchedMediaKeysMs.MediaKeys.createSession');
  240. var sessionType = opt_sessionType || 'temporary';
  241. // For now, only 'temporary' type is supported
  242. if (sessionType != 'temporary') {
  243. throw new TypeError('Session type ' + opt_sessionType +
  244. ' is unsupported on this platform.');
  245. }
  246. // Alias
  247. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  248. return new PatchedMediaKeysMs.MediaKeySession(
  249. this.nativeMediaKeys_, sessionType);
  250. };
  251. /** @override */
  252. shaka.polyfill.PatchedMediaKeysMs.MediaKeys.prototype.
  253. setServerCertificate = function(serverCertificate) {
  254. shaka.log.debug('PatchedMediaKeysMs.MediaKeys.setServerCertificate');
  255. // There is no equivalent in PatchedMediaKeysMs, so return failure.
  256. return Promise.reject(new Error('setServerCertificate not supported on ' +
  257. 'this platform.'));
  258. };
  259. /**
  260. * @param {HTMLMediaElement} media
  261. * @protected
  262. * @return {!Promise}
  263. */
  264. shaka.polyfill.PatchedMediaKeysMs.MediaKeys.prototype.
  265. setMedia = function(media) {
  266. // Alias
  267. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  268. // Remove any old listeners.
  269. this.eventManager_.removeAll();
  270. // It is valid for media to be null, it's used to flag that event handlers
  271. // need to be cleaned up
  272. if (!media) {
  273. return Promise.resolve();
  274. }
  275. // Intercept and translate these prefixed EME events.
  276. this.eventManager_.listen(media, 'msneedkey',
  277. /** @type {shaka.util.EventManager.ListenerType} */
  278. (PatchedMediaKeysMs.onMsNeedKey_));
  279. var self = this;
  280. function setMediaKeysDeferred() {
  281. media.msSetMediaKeys(self.nativeMediaKeys_);
  282. media.removeEventListener('loadedmetadata', setMediaKeysDeferred);
  283. }
  284. // Wrap native HTMLMediaElement.msSetMediaKeys with Promise
  285. try {
  286. // IE11/Edge requires that readyState >=1 before mediaKeys can be set, so
  287. // check this and wait for loadedmetadata if we are not in the correct state
  288. if (media.readyState >= 1) {
  289. media.msSetMediaKeys(this.nativeMediaKeys_);
  290. } else {
  291. media.addEventListener('loadedmetadata', setMediaKeysDeferred);
  292. }
  293. return Promise.resolve();
  294. } catch (exception) {
  295. return Promise.reject(exception);
  296. }
  297. };
  298. /**
  299. * An implementation of MediaKeySession.
  300. *
  301. * @constructor
  302. * @struct
  303. * @param {MSMediaKeys} nativeMediaKeys
  304. * @param {string} sessionType
  305. * @implements {MediaKeySession}
  306. * @extends {shaka.util.FakeEventTarget}
  307. */
  308. shaka.polyfill.PatchedMediaKeysMs.
  309. MediaKeySession = function(nativeMediaKeys, sessionType) {
  310. shaka.log.debug('PatchedMediaKeysMs.MediaKeySession');
  311. shaka.util.FakeEventTarget.call(this);
  312. // Native MediaKeySession, which will be created in generateRequest
  313. /** @private {MSMediaKeySession} */
  314. this.nativeMediaKeySession_ = null;
  315. /** @private {MSMediaKeys} */
  316. this.nativeMediaKeys_ = nativeMediaKeys;
  317. // Promises that are resolved later
  318. /** @private {Promise} */
  319. this.generateRequestPromise_ = null;
  320. /** @private {Promise} */
  321. this.updatePromise_ = null;
  322. /** @private {!shaka.util.EventManager} */
  323. this.eventManager_ = new shaka.util.EventManager();
  324. /** @type {string} */
  325. this.sessionId = '';
  326. /** @type {number} */
  327. this.expiration = NaN;
  328. /** @type {!shaka.util.PublicPromise} */
  329. this.closed = new shaka.util.PublicPromise();
  330. /** @type {!MediaKeyStatusMap} */
  331. this.keyStatuses =
  332. new shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap();
  333. };
  334. goog.inherits(shaka.polyfill.PatchedMediaKeysMs.MediaKeySession,
  335. shaka.util.FakeEventTarget);
  336. /** @override */
  337. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  338. generateRequest = function(initDataType, initData) {
  339. shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.generateRequest');
  340. this.generateRequestPromise_ = new shaka.util.PublicPromise();
  341. try {
  342. // This EME spec version requires a MIME content type as the 1st param
  343. // to createSession, but doesn't seem to matter what the value is.
  344. // NOTE: IE11 takes either Uint8Array or ArrayBuffer, but Edge 12 only
  345. // accepts Uint8Array.
  346. this.nativeMediaKeySession_ = this.nativeMediaKeys_
  347. .createSession('video/mp4', new Uint8Array(initData), null);
  348. // Attach session event handlers here
  349. this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeymessage',
  350. /** @type {shaka.util.EventManager.ListenerType} */
  351. (this.onMsKeyMessage_.bind(this)));
  352. this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeyadded',
  353. /** @type {shaka.util.EventManager.ListenerType} */
  354. (this.onMsKeyAdded_.bind(this)));
  355. this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeyerror',
  356. /** @type {shaka.util.EventManager.ListenerType} */
  357. (this.onMsKeyError_.bind(this)));
  358. this.updateKeyStatus_('status-pending');
  359. } catch (exception) {
  360. this.generateRequestPromise_.reject(exception);
  361. }
  362. return this.generateRequestPromise_;
  363. };
  364. /** @override */
  365. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  366. load = function() {
  367. shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.load');
  368. return Promise.reject(new Error('MediaKeySession.load not yet supported'));
  369. };
  370. /** @override */
  371. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  372. update = function(response) {
  373. shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.update');
  374. this.updatePromise_ = new shaka.util.PublicPromise();
  375. try {
  376. // Pass through to the native session.
  377. // NOTE: IE11 takes either Uint8Array or ArrayBuffer, but Edge 12 only
  378. // accepts Uint8Array.
  379. this.nativeMediaKeySession_.update(new Uint8Array(response));
  380. } catch (exception) {
  381. this.updatePromise_.reject(exception);
  382. }
  383. return this.updatePromise_;
  384. };
  385. /** @override */
  386. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  387. close = function() {
  388. shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.close');
  389. try {
  390. // Pass through to the native session
  391. // NOTE: IE seems to have spec discrepancy here - v2010218 should have
  392. // MediaKeySession.release, but actually uses "close". The next version
  393. // of the spec is the initial Promise based one, so it's not the target spec
  394. // either.
  395. this.nativeMediaKeySession_.close();
  396. this.closed.resolve();
  397. this.eventManager_.removeAll();
  398. } catch (exception) {
  399. this.closed.reject(exception);
  400. }
  401. return this.closed;
  402. };
  403. /** @override */
  404. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  405. remove = function() {
  406. shaka.log.debug('PatchedMediaKeysMs.MediaKeySession.remove');
  407. return Promise.reject(new Error('MediaKeySession.remove is only ' +
  408. 'applicable for persistent licenses, which are not supported on ' +
  409. 'this platform'));
  410. };
  411. /**
  412. * Handler for the native media elements msNeedKey event.
  413. *
  414. * @this {!HTMLMediaElement}
  415. * @param {!MediaKeyEvent} event
  416. * @private
  417. */
  418. shaka.polyfill.PatchedMediaKeysMs.onMsNeedKey_ = function(event) {
  419. shaka.log.debug('PatchedMediaKeysMs.onMsNeedKey_', event);
  420. // Alias
  421. var PatchedMediaKeysMs = shaka.polyfill.PatchedMediaKeysMs;
  422. // NOTE: Because "this" is a real EventTarget, on IE, the event we dispatch
  423. // here must also be a real Event.
  424. var event2 = /** @type {!CustomEvent} */(document.createEvent('CustomEvent'));
  425. event2.initCustomEvent('encrypted', false, false, null);
  426. event2.initDataType = 'cenc';
  427. event2.initData = PatchedMediaKeysMs.NormaliseInitData_(event.initData);
  428. this.dispatchEvent(event2);
  429. };
  430. /**
  431. * Normalise the initData array. This is to apply browser specific work-arounds,
  432. * e.g. removing duplicates which appears to occur intermittently when the
  433. * native msneedkey event fires (i.e. event.initData contains dupes).
  434. *
  435. * @param {?Uint8Array} initData
  436. * @private
  437. * @return {?Uint8Array}
  438. */
  439. shaka.polyfill.PatchedMediaKeysMs.
  440. NormaliseInitData_ = function(initData) {
  441. if (!initData) {
  442. return initData;
  443. }
  444. var pssh = new shaka.util.Pssh(initData);
  445. // If there is only a single pssh, return the original array
  446. if (pssh.dataBoundaries.length <= 1) {
  447. return initData;
  448. }
  449. var unfilteredInitDatas = [];
  450. for (var i = 0; i < pssh.dataBoundaries.length; i++) {
  451. var currPssh = initData.subarray(
  452. pssh.dataBoundaries[i].start,
  453. pssh.dataBoundaries[i].end + 1); // end is exclusive, hence the +1
  454. unfilteredInitDatas.push(currPssh);
  455. }
  456. // Dedupe psshData
  457. var dedupedInitDatas = shaka.util.ArrayUtils.removeDuplicates(
  458. unfilteredInitDatas,
  459. shaka.polyfill.PatchedMediaKeysMs.compareInitDatas_);
  460. var targetLength = 0;
  461. for (var i = 0; i < dedupedInitDatas.length; i++) {
  462. targetLength += dedupedInitDatas[i].length;
  463. }
  464. // Concat array of Uint8Arrays back into a single Uint8Array
  465. var normalisedInitData = new Uint8Array(targetLength);
  466. var offset = 0;
  467. for (var i = 0; i < dedupedInitDatas.length; i++) {
  468. normalisedInitData.set(dedupedInitDatas[i], offset);
  469. offset += dedupedInitDatas[i].length;
  470. }
  471. return normalisedInitData;
  472. };
  473. /**
  474. * @param {!Uint8Array} initDataA
  475. * @param {!Uint8Array} initDataB
  476. * @return {boolean}
  477. * @private
  478. */
  479. shaka.polyfill.PatchedMediaKeysMs.compareInitDatas_ =
  480. function(initDataA, initDataB) {
  481. return shaka.util.Uint8ArrayUtils.equal(initDataA, initDataB);
  482. };
  483. /**
  484. * Handler for the native keymessage event on MSMediaKeySession.
  485. *
  486. * @param {!MediaKeyEvent} event
  487. * @private
  488. */
  489. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  490. onMsKeyMessage_ = function(event) {
  491. shaka.log.debug('PatchedMediaKeysMs.onMsKeyMessage_', event);
  492. // We can now resolve this.generateRequestPromise (it should be non-null)
  493. goog.asserts.assert(this.generateRequestPromise_,
  494. 'generateRequestPromise_ not set in onMsKeyMessage_');
  495. if (this.generateRequestPromise_) {
  496. this.generateRequestPromise_.resolve();
  497. this.generateRequestPromise_ = null;
  498. }
  499. var isNew = this.keyStatuses.getStatus() == undefined;
  500. var event2 = new shaka.util.FakeEvent('message', {
  501. messageType: isNew ? 'licenserequest' : 'licenserenewal',
  502. message: event.message.buffer
  503. });
  504. this.dispatchEvent(event2);
  505. };
  506. /**
  507. * Handler for the native keyadded event on MSMediaKeySession.
  508. *
  509. * @param {!MediaKeyEvent} event
  510. * @private
  511. */
  512. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  513. onMsKeyAdded_ = function(event) {
  514. shaka.log.debug('PatchedMediaKeysMs.onMsKeyAdded_', event);
  515. // PlayReady's concept of persistent licenses makes emulation difficult here.
  516. // A license policy can say that the license persists, which causes the CDM to
  517. // store it for use in a later session. The result is that in IE11, the CDM
  518. // fires 'mskeyadded' without ever firing 'mskeymessage'.
  519. if (this.generateRequestPromise_) {
  520. shaka.log.debug('Simulating completion for a PR persistent license.');
  521. goog.asserts.assert(!this.updatePromise_,
  522. 'updatePromise_ and generateRequestPromise_ set in onMsKeyAdded_');
  523. this.updateKeyStatus_('usable');
  524. this.generateRequestPromise_.resolve();
  525. this.generateRequestPromise_ = null;
  526. return;
  527. }
  528. // We can now resolve this.updatePromise (it should be non-null)
  529. goog.asserts.assert(this.updatePromise_,
  530. 'updatePromise_ not set in onMsKeyAdded_');
  531. if (this.updatePromise_) {
  532. this.updateKeyStatus_('usable');
  533. this.updatePromise_.resolve();
  534. this.updatePromise_ = null;
  535. }
  536. };
  537. /**
  538. * Handler for the native keyerror event on MSMediaKeySession.
  539. *
  540. * @param {!MediaKeyEvent} event
  541. * @private
  542. */
  543. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  544. onMsKeyError_ = function(event) {
  545. shaka.log.debug('PatchedMediaKeysMs.onMsKeyError_', event);
  546. var error = new Error('EME PatchedMediaKeysMs key error');
  547. error.errorCode = this.nativeMediaKeySession_.error;
  548. if (this.generateRequestPromise_ != null) {
  549. this.generateRequestPromise_.reject(error);
  550. this.generateRequestPromise_ = null;
  551. } else if (this.updatePromise_ != null) {
  552. this.updatePromise_.reject(error);
  553. this.updatePromise_ = null;
  554. } else {
  555. /*
  556. Unexpected error - map native codes to standardised key statuses.
  557. Possible values of this.nativeMediaKeySession_.error.code
  558. MS_MEDIA_KEYERR_UNKNOWN = 1
  559. MS_MEDIA_KEYERR_CLIENT = 2
  560. MS_MEDIA_KEYERR_SERVICE = 3
  561. MS_MEDIA_KEYERR_OUTPUT = 4
  562. MS_MEDIA_KEYERR_HARDWARECHANGE = 5
  563. MS_MEDIA_KEYERR_DOMAIN = 6
  564. */
  565. switch (this.nativeMediaKeySession_.error.code) {
  566. case MSMediaKeyError.MS_MEDIA_KEYERR_OUTPUT:
  567. case MSMediaKeyError.MS_MEDIA_KEYERR_HARDWARECHANGE:
  568. this.updateKeyStatus_('output-not-allowed');
  569. default:
  570. this.updateKeyStatus_('internal-error');
  571. }
  572. }
  573. };
  574. /**
  575. * Update key status and dispatch a 'keystatuseschange' event.
  576. *
  577. * @param {string} status
  578. * @private
  579. */
  580. shaka.polyfill.PatchedMediaKeysMs.MediaKeySession.prototype.
  581. updateKeyStatus_ = function(status) {
  582. this.keyStatuses.setStatus(status);
  583. var event = new shaka.util.FakeEvent('keystatuseschange');
  584. this.dispatchEvent(event);
  585. };
  586. /**
  587. * An implementation of MediaKeyStatusMap.
  588. * This fakes a map with a single key ID.
  589. *
  590. * @constructor
  591. * @struct
  592. * @implements {MediaKeyStatusMap}
  593. */
  594. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap = function() {
  595. /**
  596. * @type {number}
  597. */
  598. this.size = 0;
  599. /**
  600. * @private {string|undefined}
  601. */
  602. this.status_ = undefined;
  603. };
  604. /**
  605. * @const {!ArrayBuffer}
  606. * @private
  607. */
  608. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_;
  609. /**
  610. * An internal method used by the session to set key status.
  611. * @param {string|undefined} status
  612. */
  613. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
  614. setStatus = function(status) {
  615. this.size = status == undefined ? 0 : 1;
  616. this.status_ = status;
  617. };
  618. /**
  619. * An internal method used by the session to get key status.
  620. * @return {string|undefined}
  621. */
  622. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
  623. getStatus = function() {
  624. return this.status_;
  625. };
  626. /** @override */
  627. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
  628. forEach = function(fn) {
  629. if (this.status_) {
  630. var fakeKeyId =
  631. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_;
  632. fn(this.status_, fakeKeyId);
  633. }
  634. };
  635. /** @override */
  636. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
  637. get = function(keyId) {
  638. if (this.has(keyId)) {
  639. return this.status_;
  640. }
  641. return undefined;
  642. };
  643. /** @override */
  644. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.prototype.
  645. has = function(keyId) {
  646. var fakeKeyId =
  647. shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_;
  648. if (this.status_ &&
  649. shaka.util.Uint8ArrayUtils.equal(
  650. new Uint8Array(keyId), new Uint8Array(fakeKeyId))) {
  651. return true;
  652. }
  653. return false;
  654. };