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