shaka-player 4.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/.github/workflows/build-and-test.yaml +67 -15
  2. package/.github/workflows/compute-incremental-coverage.py +251 -0
  3. package/.github/workflows/report-incremental-coverage.yaml +39 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +44 -1
  5. package/AUTHORS +1 -0
  6. package/CHANGELOG.md +36 -8
  7. package/CONTRIBUTORS +3 -0
  8. package/README.md +20 -6
  9. package/demo/common/assets.js +25 -0
  10. package/demo/common/message_ids.js +2 -0
  11. package/demo/config.js +5 -1
  12. package/demo/index.html +1 -3
  13. package/demo/locales/en.json +2 -0
  14. package/demo/locales/source.json +8 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/demo.compiled.debug.js +25 -23
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +25 -23
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +6 -5
  21. package/dist/locales.js +30 -30
  22. package/dist/receiver.compiled.debug.js +24 -22
  23. package/dist/receiver.compiled.debug.map +3 -3
  24. package/dist/receiver.compiled.js +24 -22
  25. package/dist/receiver.compiled.map +3 -3
  26. package/dist/shaka-player.compiled.d.ts +69 -5
  27. package/dist/shaka-player.compiled.debug.d.ts +69 -5
  28. package/dist/shaka-player.compiled.debug.externs.js +111 -42
  29. package/dist/shaka-player.compiled.debug.js +352 -301
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +111 -42
  32. package/dist/shaka-player.compiled.js +823 -802
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +69 -5
  35. package/dist/shaka-player.ui.debug.d.ts +69 -5
  36. package/dist/shaka-player.ui.debug.externs.js +111 -42
  37. package/dist/shaka-player.ui.debug.js +360 -310
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +111 -42
  40. package/dist/shaka-player.ui.js +980 -957
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/fairplay.md +149 -0
  43. package/docs/tutorials/ui.md +6 -6
  44. package/docs/tutorials/upgrade.md +1 -0
  45. package/docs/tutorials/welcome.md +6 -3
  46. package/externs/shaka/abr_manager.js +8 -0
  47. package/externs/shaka/manifest.js +43 -0
  48. package/externs/shaka/player.js +13 -2
  49. package/karma.conf.js +3 -11
  50. package/lib/abr/simple_abr_manager.js +72 -4
  51. package/lib/ads/client_side_ad_manager.js +1 -1
  52. package/lib/dash/dash_parser.js +5 -0
  53. package/lib/hls/hls_parser.js +123 -27
  54. package/lib/media/drm_engine.js +1 -3
  55. package/lib/media/media_source_engine.js +57 -24
  56. package/lib/media/presentation_timeline.js +24 -1
  57. package/lib/media/segment_index.js +8 -0
  58. package/lib/media/segment_reference.js +7 -1
  59. package/lib/media/streaming_engine.js +42 -6
  60. package/lib/media/time_ranges_utils.js +46 -0
  61. package/lib/net/networking_engine.js +1 -0
  62. package/lib/player.js +8 -6
  63. package/lib/polyfill/media_capabilities.js +9 -0
  64. package/lib/polyfill/patchedmediakeys_apple.js +729 -0
  65. package/lib/polyfill/videoplaybackquality.js +3 -1
  66. package/lib/text/cue.js +0 -5
  67. package/lib/text/simple_text_displayer.js +21 -9
  68. package/lib/text/ui_text_displayer.js +83 -19
  69. package/lib/text/vtt_text_parser.js +61 -4
  70. package/lib/util/buffer_utils.js +3 -0
  71. package/lib/util/error.js +21 -1
  72. package/lib/util/fairplay_utils.js +100 -2
  73. package/lib/util/object_utils.js +1 -0
  74. package/lib/util/platform.js +30 -1
  75. package/lib/util/player_configuration.js +3 -10
  76. package/lib/util/stream_utils.js +23 -3
  77. package/lib/util/string_utils.js +102 -13
  78. package/maintained-branches.md +15 -0
  79. package/package.json +3 -5
  80. package/shaka-player.uncompiled.js +1 -0
  81. package/test/hls/hls_parser_unit.js +49 -33
  82. package/test/media/drm_engine_integration.js +24 -8
  83. package/test/media/media_source_engine_integration.js +24 -10
  84. package/test/media/media_source_engine_unit.js +53 -38
  85. package/test/media/streaming_engine_unit.js +107 -32
  86. package/test/test/util/fake_media_source_engine.js +10 -9
  87. package/test/test/util/simple_fakes.js +4 -0
  88. package/test/test/util/streaming_engine_util.js +10 -2
  89. package/test/text/simple_text_displayer_unit.js +27 -0
  90. package/test/text/ui_text_displayer_unit.js +65 -0
  91. package/test/text/vtt_text_parser_unit.js +26 -0
  92. package/test/util/string_utils_unit.js +46 -8
  93. package/ui/text_selection.js +2 -2
  94. package/app-engine/README.md +0 -9
  95. package/app-engine/demo-version-index/README.md +0 -14
  96. package/app-engine/demo-version-index/app.yaml +0 -28
  97. package/app-engine/demo-version-index/generate.py +0 -164
  98. package/app-engine/demo-version-index/main.py +0 -11
  99. package/app-engine/demo-version-index/requirements.txt +0 -5
  100. package/app-engine/demo-version-index/templates/index.html +0 -114
  101. package/app-engine/shaka-player-demo/README.md +0 -18
  102. package/app-engine/shaka-player-demo/app.yaml +0 -61
  103. package/app-engine/shaka-player-demo/main.py +0 -95
  104. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  105. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  106. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  107. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  108. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  109. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  110. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  111. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  112. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  113. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  114. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  115. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  116. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  117. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  118. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  119. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  120. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  121. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  122. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  123. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  124. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  125. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  126. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  127. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  128. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  129. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  130. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  131. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  132. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  133. package/app-engine/shaka-player-demo/posters.py +0 -110
  134. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  135. package/app-engine/shaka-player-demo/time.txt +0 -1
  136. package/build/README.md +0 -127
  137. package/build/__init__.py +0 -0
  138. package/build/all.py +0 -150
  139. package/build/apps.py +0 -186
  140. package/build/build.py +0 -392
  141. package/build/check.py +0 -354
  142. package/build/checkversion.py +0 -84
  143. package/build/compiler.py +0 -513
  144. package/build/conformance.textproto +0 -335
  145. package/build/docker/Dockerfile +0 -20
  146. package/build/docker/docker-compose.yml +0 -10
  147. package/build/docs.py +0 -54
  148. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  149. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  150. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  151. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  152. package/build/gendeps.py +0 -71
  153. package/build/generateExterns.js +0 -867
  154. package/build/generateLocalizations.py +0 -262
  155. package/build/generateTsDefs.py +0 -137
  156. package/build/install-linux-prereqs.sh +0 -136
  157. package/build/license-header +0 -5
  158. package/build/misspellings.txt +0 -39
  159. package/build/pixelsChanged.js +0 -35
  160. package/build/shaka-lab.yaml +0 -161
  161. package/build/shakaBuildHelpers.py +0 -356
  162. package/build/stats.py +0 -816
  163. package/build/subprocessWindowsPatch.py +0 -103
  164. package/build/test.py +0 -541
  165. package/build/types/ads +0 -8
  166. package/build/types/cast +0 -6
  167. package/build/types/cea +0 -13
  168. package/build/types/complete +0 -9
  169. package/build/types/core +0 -108
  170. package/build/types/dash +0 -8
  171. package/build/types/hls +0 -9
  172. package/build/types/manifests +0 -5
  173. package/build/types/networking +0 -6
  174. package/build/types/offline +0 -22
  175. package/build/types/polyfill +0 -18
  176. package/build/types/text +0 -11
  177. package/build/types/ui +0 -39
  178. package/build/updateScreenshots.py +0 -85
  179. package/build/wrapper.template.js +0 -52
@@ -0,0 +1,729 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.polyfill.PatchedMediaKeysApple');
8
+
9
+ goog.require('goog.asserts');
10
+ goog.require('shaka.log');
11
+ goog.require('shaka.media.DrmEngine');
12
+ goog.require('shaka.polyfill');
13
+ goog.require('shaka.util.BufferUtils');
14
+ goog.require('shaka.util.EventManager');
15
+ goog.require('shaka.util.FakeEvent');
16
+ goog.require('shaka.util.FakeEventTarget');
17
+ goog.require('shaka.util.MediaReadyState');
18
+ goog.require('shaka.util.PublicPromise');
19
+ goog.require('shaka.util.StringUtils');
20
+
21
+
22
+ /**
23
+ * @summary A polyfill to implement modern, standardized EME on top of Apple's
24
+ * prefixed EME in Safari.
25
+ * @export
26
+ */
27
+ shaka.polyfill.PatchedMediaKeysApple = class {
28
+ /**
29
+ * Installs the polyfill if needed.
30
+ * @export
31
+ */
32
+ static install() {
33
+ if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
34
+ // No HTML5 video or no prefixed EME.
35
+ return;
36
+ }
37
+
38
+ shaka.log.info('Using Apple-prefixed EME');
39
+
40
+ // Alias
41
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
42
+
43
+ // Delete mediaKeys to work around strict mode compatibility issues.
44
+ // eslint-disable-next-line no-restricted-syntax
45
+ delete HTMLMediaElement.prototype['mediaKeys'];
46
+ // Work around read-only declaration for mediaKeys by using a string.
47
+ // eslint-disable-next-line no-restricted-syntax
48
+ HTMLMediaElement.prototype['mediaKeys'] = null;
49
+ // eslint-disable-next-line no-restricted-syntax
50
+ HTMLMediaElement.prototype.setMediaKeys =
51
+ PatchedMediaKeysApple.setMediaKeys;
52
+
53
+ // Install patches
54
+ window.MediaKeys = PatchedMediaKeysApple.MediaKeys;
55
+ window.MediaKeySystemAccess = PatchedMediaKeysApple.MediaKeySystemAccess;
56
+ navigator.requestMediaKeySystemAccess =
57
+ PatchedMediaKeysApple.requestMediaKeySystemAccess;
58
+
59
+ window.shakaMediaKeysPolyfill = true;
60
+ }
61
+
62
+ /**
63
+ * An implementation of navigator.requestMediaKeySystemAccess.
64
+ * Retrieves a MediaKeySystemAccess object.
65
+ *
66
+ * @this {!Navigator}
67
+ * @param {string} keySystem
68
+ * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
69
+ * @return {!Promise.<!MediaKeySystemAccess>}
70
+ */
71
+ static requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
72
+ shaka.log.debug('PatchedMediaKeysApple.requestMediaKeySystemAccess');
73
+ goog.asserts.assert(this == navigator,
74
+ 'bad "this" for requestMediaKeySystemAccess');
75
+
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
+ /**
88
+ * An implementation of HTMLMediaElement.prototype.setMediaKeys.
89
+ * Attaches a MediaKeys object to the media element.
90
+ *
91
+ * @this {!HTMLMediaElement}
92
+ * @param {MediaKeys} mediaKeys
93
+ * @return {!Promise}
94
+ */
95
+ static setMediaKeys(mediaKeys) {
96
+ shaka.log.debug('PatchedMediaKeysApple.setMediaKeys');
97
+ goog.asserts.assert(this instanceof HTMLMediaElement,
98
+ 'bad "this" for setMediaKeys');
99
+
100
+ // Alias
101
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
102
+
103
+ const newMediaKeys =
104
+ /** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */ (
105
+ mediaKeys);
106
+ const oldMediaKeys =
107
+ /** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */ (
108
+ this.mediaKeys);
109
+
110
+ if (oldMediaKeys && oldMediaKeys != newMediaKeys) {
111
+ goog.asserts.assert(
112
+ oldMediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
113
+ 'non-polyfill instance of oldMediaKeys');
114
+ // Have the old MediaKeys stop listening to events on the video tag.
115
+ oldMediaKeys.setMedia(null);
116
+ }
117
+
118
+ delete this['mediaKeys']; // in case there is an existing getter
119
+ this['mediaKeys'] = mediaKeys; // work around read-only declaration
120
+
121
+ if (newMediaKeys) {
122
+ goog.asserts.assert(
123
+ newMediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
124
+ 'non-polyfill instance of newMediaKeys');
125
+ return newMediaKeys.setMedia(this);
126
+ }
127
+
128
+ return Promise.resolve();
129
+ }
130
+
131
+ /**
132
+ * Handler for the native media elements webkitneedkey event.
133
+ *
134
+ * @this {!HTMLMediaElement}
135
+ * @param {!MediaKeyEvent} event
136
+ * @suppress {constantProperty} We reassign what would be const on a real
137
+ * MediaEncryptedEvent, but in our look-alike event.
138
+ * @private
139
+ */
140
+ static onWebkitNeedKey_(event) {
141
+ shaka.log.debug('PatchedMediaKeysApple.onWebkitNeedKey_', event);
142
+
143
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
144
+ const mediaKeys =
145
+ /** @type {shaka.polyfill.PatchedMediaKeysApple.MediaKeys} */(
146
+ this.mediaKeys);
147
+ goog.asserts.assert(mediaKeys instanceof PatchedMediaKeysApple.MediaKeys,
148
+ 'non-polyfill instance of newMediaKeys');
149
+
150
+ goog.asserts.assert(event.initData != null, 'missing init data!');
151
+
152
+ // Convert the prefixed init data to match the native 'encrypted' event.
153
+ const uint8 = shaka.util.BufferUtils.toUint8(event.initData);
154
+ const dataview = shaka.util.BufferUtils.toDataView(uint8);
155
+ // The first part is a 4 byte little-endian int, which is the length of
156
+ // the second part.
157
+ const length = dataview.getUint32(
158
+ /* position= */ 0, /* littleEndian= */ true);
159
+ if (length + 4 != uint8.byteLength) {
160
+ throw new RangeError('Malformed FairPlay init data');
161
+ }
162
+ // The remainder is a UTF-16 skd URL. Convert this to UTF-8 and pass on.
163
+ const str = shaka.util.StringUtils.fromUTF16(
164
+ uint8.subarray(4), /* littleEndian= */ true);
165
+ const initData = shaka.util.StringUtils.toUTF8(str);
166
+
167
+ // NOTE: Because "this" is a real EventTarget, the event we dispatch here
168
+ // must also be a real Event.
169
+ const event2 = new Event('encrypted');
170
+
171
+ const encryptedEvent =
172
+ /** @type {!MediaEncryptedEvent} */(/** @type {?} */(event2));
173
+ encryptedEvent.initDataType = 'skd';
174
+ encryptedEvent.initData = shaka.util.BufferUtils.toArrayBuffer(initData);
175
+
176
+ this.dispatchEvent(event2);
177
+ }
178
+ };
179
+
180
+
181
+ /**
182
+ * An implementation of MediaKeySystemAccess.
183
+ *
184
+ * @implements {MediaKeySystemAccess}
185
+ */
186
+ shaka.polyfill.PatchedMediaKeysApple.MediaKeySystemAccess = class {
187
+ /**
188
+ * @param {string} keySystem
189
+ * @param {!Array.<!MediaKeySystemConfiguration>} supportedConfigurations
190
+ */
191
+ constructor(keySystem, supportedConfigurations) {
192
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeySystemAccess');
193
+
194
+ /** @type {string} */
195
+ this.keySystem = keySystem;
196
+
197
+ /** @private {!MediaKeySystemConfiguration} */
198
+ this.configuration_;
199
+
200
+ // Optimization: WebKitMediaKeys.isTypeSupported delays responses by a
201
+ // significant amount of time, possibly to discourage fingerprinting.
202
+ // Since we know only FairPlay is supported here, let's skip queries for
203
+ // anything else to speed up the process.
204
+ if (keySystem.startsWith('com.apple.fps')) {
205
+ for (const cfg of supportedConfigurations) {
206
+ const newCfg = this.checkConfig_(cfg);
207
+ if (newCfg) {
208
+ this.configuration_ = newCfg;
209
+ return;
210
+ }
211
+ }
212
+ }
213
+
214
+ // According to the spec, this should be a DOMException, but there is not a
215
+ // public constructor for that. So we make this look-alike instead.
216
+ const unsupportedKeySystemError = new Error('Unsupported keySystem');
217
+ unsupportedKeySystemError.name = 'NotSupportedError';
218
+ unsupportedKeySystemError['code'] = DOMException.NOT_SUPPORTED_ERR;
219
+ throw unsupportedKeySystemError;
220
+ }
221
+
222
+ /**
223
+ * Check a single config for MediaKeySystemAccess.
224
+ *
225
+ * @param {MediaKeySystemConfiguration} cfg The requested config.
226
+ * @return {?MediaKeySystemConfiguration} A matching config we can support, or
227
+ * null if the input is not supportable.
228
+ * @private
229
+ */
230
+ checkConfig_(cfg) {
231
+ if (cfg.persistentState == 'required') {
232
+ // Not supported by the prefixed API.
233
+ return null;
234
+ }
235
+
236
+ // Create a new config object and start adding in the pieces which we find
237
+ // support for. We will return this from getConfiguration() later if
238
+ // asked.
239
+
240
+ /** @type {!MediaKeySystemConfiguration} */
241
+ const newCfg = {
242
+ 'audioCapabilities': [],
243
+ 'videoCapabilities': [],
244
+ // It is technically against spec to return these as optional, but we
245
+ // don't truly know their values from the prefixed API:
246
+ 'persistentState': 'optional',
247
+ 'distinctiveIdentifier': 'optional',
248
+ // Pretend the requested init data types are supported, since we don't
249
+ // really know that either:
250
+ 'initDataTypes': cfg.initDataTypes,
251
+ 'sessionTypes': ['temporary'],
252
+ 'label': cfg.label,
253
+ };
254
+
255
+ // PatchedMediaKeysApple tests for key system availability through
256
+ // WebKitMediaKeys.isTypeSupported.
257
+ let ranAnyTests = false;
258
+ let success = false;
259
+
260
+ if (cfg.audioCapabilities) {
261
+ for (const cap of cfg.audioCapabilities) {
262
+ if (cap.contentType) {
263
+ ranAnyTests = true;
264
+
265
+ const contentType = cap.contentType.split(';')[0];
266
+ if (WebKitMediaKeys.isTypeSupported(this.keySystem, contentType)) {
267
+ newCfg.audioCapabilities.push(cap);
268
+ success = true;
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ if (cfg.videoCapabilities) {
275
+ for (const cap of cfg.videoCapabilities) {
276
+ if (cap.contentType) {
277
+ ranAnyTests = true;
278
+
279
+ const contentType = cap.contentType.split(';')[0];
280
+ if (WebKitMediaKeys.isTypeSupported(this.keySystem, contentType)) {
281
+ newCfg.videoCapabilities.push(cap);
282
+ success = true;
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ if (!ranAnyTests) {
289
+ // If no specific types were requested, we check all common types to
290
+ // find out if the key system is present at all.
291
+ success = WebKitMediaKeys.isTypeSupported(this.keySystem, 'video/mp4');
292
+ }
293
+
294
+ if (success) {
295
+ return newCfg;
296
+ }
297
+ return null;
298
+ }
299
+
300
+ /** @override */
301
+ createMediaKeys() {
302
+ shaka.log.debug(
303
+ 'PatchedMediaKeysApple.MediaKeySystemAccess.createMediaKeys');
304
+
305
+ // Alias
306
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
307
+
308
+ const mediaKeys = new PatchedMediaKeysApple.MediaKeys(this.keySystem);
309
+ return Promise.resolve(/** @type {!MediaKeys} */ (mediaKeys));
310
+ }
311
+
312
+ /** @override */
313
+ getConfiguration() {
314
+ shaka.log.debug(
315
+ 'PatchedMediaKeysApple.MediaKeySystemAccess.getConfiguration');
316
+ return this.configuration_;
317
+ }
318
+ };
319
+
320
+
321
+ /**
322
+ * An implementation of MediaKeys.
323
+ *
324
+ * @implements {MediaKeys}
325
+ */
326
+ shaka.polyfill.PatchedMediaKeysApple.MediaKeys = class {
327
+ /** @param {string} keySystem */
328
+ constructor(keySystem) {
329
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeys');
330
+
331
+ /** @private {!WebKitMediaKeys} */
332
+ this.nativeMediaKeys_ = new WebKitMediaKeys(keySystem);
333
+
334
+ /** @private {!shaka.util.EventManager} */
335
+ this.eventManager_ = new shaka.util.EventManager();
336
+ }
337
+
338
+ /** @override */
339
+ createSession(sessionType) {
340
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeys.createSession');
341
+
342
+ sessionType = sessionType || 'temporary';
343
+ // For now, only the 'temporary' type is supported.
344
+ if (sessionType != 'temporary') {
345
+ throw new TypeError('Session type ' + sessionType +
346
+ ' is unsupported on this platform.');
347
+ }
348
+
349
+ // Alias
350
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
351
+
352
+ return new PatchedMediaKeysApple.MediaKeySession(
353
+ this.nativeMediaKeys_, sessionType);
354
+ }
355
+
356
+ /** @override */
357
+ setServerCertificate(serverCertificate) {
358
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeys.setServerCertificate');
359
+ return Promise.resolve(false);
360
+ }
361
+
362
+ /**
363
+ * @param {HTMLMediaElement} media
364
+ * @protected
365
+ * @return {!Promise}
366
+ */
367
+ setMedia(media) {
368
+ // Alias
369
+ const PatchedMediaKeysApple = shaka.polyfill.PatchedMediaKeysApple;
370
+
371
+ // Remove any old listeners.
372
+ this.eventManager_.removeAll();
373
+
374
+ // It is valid for media to be null; null is used to flag that event
375
+ // handlers need to be cleaned up.
376
+ if (!media) {
377
+ return Promise.resolve();
378
+ }
379
+
380
+ // Intercept and translate these prefixed EME events.
381
+ this.eventManager_.listen(media, 'webkitneedkey',
382
+ /** @type {shaka.util.EventManager.ListenerType} */
383
+ (PatchedMediaKeysApple.onWebkitNeedKey_));
384
+
385
+ // Wrap native HTMLMediaElement.webkitSetMediaKeys with a Promise.
386
+ try {
387
+ // Some browsers require that readyState >=1 before mediaKeys can be
388
+ // set, so check this and wait for loadedmetadata if we are not in the
389
+ // correct state
390
+ shaka.util.MediaReadyState.waitForReadyState(media,
391
+ HTMLMediaElement.HAVE_METADATA,
392
+ this.eventManager_, () => {
393
+ media.webkitSetMediaKeys(this.nativeMediaKeys_);
394
+ });
395
+
396
+ return Promise.resolve();
397
+ } catch (exception) {
398
+ return Promise.reject(exception);
399
+ }
400
+ }
401
+ };
402
+
403
+
404
+ /**
405
+ * An implementation of MediaKeySession.
406
+ *
407
+ * @implements {MediaKeySession}
408
+ */
409
+ shaka.polyfill.PatchedMediaKeysApple.MediaKeySession =
410
+ class extends shaka.util.FakeEventTarget {
411
+ /**
412
+ * @param {WebKitMediaKeys} nativeMediaKeys
413
+ * @param {string} sessionType
414
+ */
415
+ constructor(nativeMediaKeys, sessionType) {
416
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeySession');
417
+ super();
418
+
419
+ /**
420
+ * The native MediaKeySession, which will be created in generateRequest.
421
+ * @private {WebKitMediaKeySession}
422
+ */
423
+ this.nativeMediaKeySession_ = null;
424
+
425
+ /** @private {WebKitMediaKeys} */
426
+ this.nativeMediaKeys_ = nativeMediaKeys;
427
+
428
+ // Promises that are resolved later
429
+ /** @private {shaka.util.PublicPromise} */
430
+ this.generateRequestPromise_ = null;
431
+
432
+ /** @private {shaka.util.PublicPromise} */
433
+ this.updatePromise_ = null;
434
+
435
+ /** @private {!shaka.util.EventManager} */
436
+ this.eventManager_ = new shaka.util.EventManager();
437
+
438
+ /** @type {string} */
439
+ this.sessionId = '';
440
+
441
+ /** @type {number} */
442
+ this.expiration = NaN;
443
+
444
+ /** @type {!shaka.util.PublicPromise} */
445
+ this.closed = new shaka.util.PublicPromise();
446
+
447
+ /** @type {!shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap} */
448
+ this.keyStatuses =
449
+ new shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap();
450
+ }
451
+
452
+ /** @override */
453
+ generateRequest(initDataType, initData) {
454
+ shaka.log.debug(
455
+ 'PatchedMediaKeysApple.MediaKeySession.generateRequest');
456
+
457
+ this.generateRequestPromise_ = new shaka.util.PublicPromise();
458
+
459
+ try {
460
+ // This EME spec version requires a MIME content type as the 1st param to
461
+ // createSession, but doesn't seem to matter what the value is.
462
+ // It also only accepts Uint8Array, not ArrayBuffer, so explicitly make
463
+ // initData into a Uint8Array.
464
+ const session = this.nativeMediaKeys_.createSession(
465
+ 'video/mp4', shaka.util.BufferUtils.toUint8(initData));
466
+ this.nativeMediaKeySession_ = session;
467
+ this.sessionId = session.sessionId || '';
468
+
469
+ // Attach session event handlers here.
470
+ this.eventManager_.listen(
471
+ this.nativeMediaKeySession_, 'webkitkeymessage',
472
+ /** @type {shaka.util.EventManager.ListenerType} */
473
+ ((event) => this.onWebkitKeyMessage_(event)));
474
+ this.eventManager_.listen(session, 'webkitkeyadded',
475
+ /** @type {shaka.util.EventManager.ListenerType} */
476
+ ((event) => this.onWebkitKeyAdded_(event)));
477
+ this.eventManager_.listen(session, 'webkitkeyerror',
478
+ /** @type {shaka.util.EventManager.ListenerType} */
479
+ ((event) => this.onWebkitKeyError_(event)));
480
+
481
+ this.updateKeyStatus_('status-pending');
482
+ } catch (exception) {
483
+ this.generateRequestPromise_.reject(exception);
484
+ }
485
+
486
+ return this.generateRequestPromise_;
487
+ }
488
+
489
+ /** @override */
490
+ load() {
491
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.load');
492
+
493
+ return Promise.reject(new Error('MediaKeySession.load not yet supported'));
494
+ }
495
+
496
+ /** @override */
497
+ update(response) {
498
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.update');
499
+
500
+ this.updatePromise_ = new shaka.util.PublicPromise();
501
+
502
+ try {
503
+ // Pass through to the native session.
504
+ this.nativeMediaKeySession_.update(
505
+ shaka.util.BufferUtils.toUint8(response));
506
+ } catch (exception) {
507
+ this.updatePromise_.reject(exception);
508
+ }
509
+
510
+ return this.updatePromise_;
511
+ }
512
+
513
+ /** @override */
514
+ close() {
515
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.close');
516
+
517
+ try {
518
+ // Pass through to the native session.
519
+ this.nativeMediaKeySession_.close();
520
+
521
+ this.closed.resolve();
522
+ this.eventManager_.removeAll();
523
+ } catch (exception) {
524
+ this.closed.reject(exception);
525
+ }
526
+
527
+ return this.closed;
528
+ }
529
+
530
+ /** @override */
531
+ remove() {
532
+ shaka.log.debug('PatchedMediaKeysApple.MediaKeySession.remove');
533
+
534
+ return Promise.reject(new Error(
535
+ 'MediaKeySession.remove is only applicable for persistent licenses, ' +
536
+ 'which are not supported on this platform'));
537
+ }
538
+
539
+ /**
540
+ * Handler for the native keymessage event on WebKitMediaKeySession.
541
+ *
542
+ * @param {!MediaKeyEvent} event
543
+ * @private
544
+ */
545
+ onWebkitKeyMessage_(event) {
546
+ shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyMessage_', event);
547
+
548
+ // We can now resolve this.generateRequestPromise, which should be non-null.
549
+ goog.asserts.assert(this.generateRequestPromise_,
550
+ 'generateRequestPromise_ should be set before now!');
551
+ if (this.generateRequestPromise_) {
552
+ this.generateRequestPromise_.resolve();
553
+ this.generateRequestPromise_ = null;
554
+ }
555
+
556
+ const isNew = this.keyStatuses.getStatus() == undefined;
557
+
558
+ const data = new Map()
559
+ .set('messageType', isNew ? 'license-request' : 'license-renewal')
560
+ .set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
561
+ const event2 = new shaka.util.FakeEvent('message', data);
562
+
563
+ this.dispatchEvent(event2);
564
+ }
565
+
566
+ /**
567
+ * Handler for the native keyadded event on WebKitMediaKeySession.
568
+ *
569
+ * @param {!MediaKeyEvent} event
570
+ * @private
571
+ */
572
+ onWebkitKeyAdded_(event) {
573
+ shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyAdded_', event);
574
+
575
+ // This shouldn't fire while we're in the middle of generateRequest,
576
+ // but if it does, we will need to change the logic to account for it.
577
+ goog.asserts.assert(!this.generateRequestPromise_,
578
+ 'Key added during generate!');
579
+
580
+ // We can now resolve this.updatePromise, which should be non-null.
581
+ goog.asserts.assert(this.updatePromise_,
582
+ 'updatePromise_ should be set before now!');
583
+ if (this.updatePromise_) {
584
+ this.updateKeyStatus_('usable');
585
+ this.updatePromise_.resolve();
586
+ this.updatePromise_ = null;
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Handler for the native keyerror event on WebKitMediaKeySession.
592
+ *
593
+ * @param {!MediaKeyEvent} event
594
+ * @private
595
+ */
596
+ onWebkitKeyError_(event) {
597
+ shaka.log.debug('PatchedMediaKeysApple.onWebkitKeyError_', event);
598
+
599
+ const error = new Error('EME PatchedMediaKeysApple key error');
600
+ error['errorCode'] = this.nativeMediaKeySession_.error;
601
+
602
+ if (this.generateRequestPromise_ != null) {
603
+ this.generateRequestPromise_.reject(error);
604
+ this.generateRequestPromise_ = null;
605
+ } else if (this.updatePromise_ != null) {
606
+ this.updatePromise_.reject(error);
607
+ this.updatePromise_ = null;
608
+ } else {
609
+ // Unexpected error - map native codes to standardised key statuses.
610
+ // Possible values of this.nativeMediaKeySession_.error.code:
611
+ // MEDIA_KEYERR_UNKNOWN = 1
612
+ // MEDIA_KEYERR_CLIENT = 2
613
+ // MEDIA_KEYERR_SERVICE = 3
614
+ // MEDIA_KEYERR_OUTPUT = 4
615
+ // MEDIA_KEYERR_HARDWARECHANGE = 5
616
+ // MEDIA_KEYERR_DOMAIN = 6
617
+
618
+ switch (this.nativeMediaKeySession_.error.code) {
619
+ case WebKitMediaKeyError.MEDIA_KEYERR_OUTPUT:
620
+ case WebKitMediaKeyError.MEDIA_KEYERR_HARDWARECHANGE:
621
+ this.updateKeyStatus_('output-not-allowed');
622
+ break;
623
+ default:
624
+ this.updateKeyStatus_('internal-error');
625
+ break;
626
+ }
627
+ }
628
+ }
629
+
630
+ /**
631
+ * Updates key status and dispatch a 'keystatuseschange' event.
632
+ *
633
+ * @param {string} status
634
+ * @private
635
+ */
636
+ updateKeyStatus_(status) {
637
+ this.keyStatuses.setStatus(status);
638
+ const event = new shaka.util.FakeEvent('keystatuseschange');
639
+ this.dispatchEvent(event);
640
+ }
641
+ };
642
+
643
+
644
+ /**
645
+ * @summary An implementation of MediaKeyStatusMap.
646
+ * This fakes a map with a single key ID.
647
+ *
648
+ * @todo Consolidate the MediaKeyStatusMap types in these polyfills.
649
+ * @implements {MediaKeyStatusMap}
650
+ */
651
+ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
652
+ /** */
653
+ constructor() {
654
+ /**
655
+ * @type {number}
656
+ */
657
+ this.size = 0;
658
+
659
+ /**
660
+ * @private {string|undefined}
661
+ */
662
+ this.status_ = undefined;
663
+ }
664
+
665
+ /**
666
+ * An internal method used by the session to set key status.
667
+ * @param {string|undefined} status
668
+ */
669
+ setStatus(status) {
670
+ this.size = status == undefined ? 0 : 1;
671
+ this.status_ = status;
672
+ }
673
+
674
+ /**
675
+ * An internal method used by the session to get key status.
676
+ * @return {string|undefined}
677
+ */
678
+ getStatus() {
679
+ return this.status_;
680
+ }
681
+
682
+ /** @override */
683
+ forEach(fn) {
684
+ if (this.status_) {
685
+ fn(this.status_, shaka.media.DrmEngine.DUMMY_KEY_ID.value());
686
+ }
687
+ }
688
+
689
+ /** @override */
690
+ get(keyId) {
691
+ if (this.has(keyId)) {
692
+ return this.status_;
693
+ }
694
+ return undefined;
695
+ }
696
+
697
+ /** @override */
698
+ has(keyId) {
699
+ const fakeKeyId = shaka.media.DrmEngine.DUMMY_KEY_ID.value();
700
+ if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
701
+ return true;
702
+ }
703
+ return false;
704
+ }
705
+
706
+ /**
707
+ * @suppress {missingReturn}
708
+ * @override
709
+ */
710
+ entries() {
711
+ goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
712
+ }
713
+
714
+ /**
715
+ * @suppress {missingReturn}
716
+ * @override
717
+ */
718
+ keys() {
719
+ goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
720
+ }
721
+
722
+ /**
723
+ * @suppress {missingReturn}
724
+ * @override
725
+ */
726
+ values() {
727
+ goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
728
+ }
729
+ };