shaka-player 3.1.0 → 3.1.4

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 (181) hide show
  1. package/AUTHORS +8 -2
  2. package/CHANGELOG.md +209 -0
  3. package/CONTRIBUTORS +10 -3
  4. package/README.md +3 -0
  5. package/build/apps.py +2 -2
  6. package/build/check.py +2 -2
  7. package/build/conformance.textproto +0 -9
  8. package/build/generateExterns.js +1 -1
  9. package/build/misspellings.txt +1 -0
  10. package/build/shakaBuildHelpers.py +6 -5
  11. package/build/subprocessWindowsPatch.py +103 -0
  12. package/demo/common/asset.js +11 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/common/message_ids.js +5 -0
  15. package/demo/config.js +2 -1
  16. package/demo/custom.js +84 -3
  17. package/demo/demo.less +13 -1
  18. package/demo/demo_utils.js +1 -0
  19. package/demo/input.js +31 -12
  20. package/demo/input_container.js +24 -1
  21. package/demo/locales/en.json +6 -1
  22. package/demo/locales/source.json +20 -0
  23. package/demo/main.js +5 -1
  24. package/dist/controls.css +2 -2
  25. package/dist/controls.css.map +1 -1
  26. package/dist/demo.compiled.debug.js +99 -91
  27. package/dist/demo.compiled.debug.map +3 -3
  28. package/dist/demo.compiled.js +99 -91
  29. package/dist/demo.compiled.map +3 -3
  30. package/dist/demo.css +1 -1
  31. package/dist/demo.css.map +1 -1
  32. package/dist/deps.js +9 -9
  33. package/dist/locales.js +36 -34
  34. package/dist/receiver.compiled.debug.js +74 -70
  35. package/dist/receiver.compiled.debug.map +3 -3
  36. package/dist/receiver.compiled.js +74 -70
  37. package/dist/receiver.compiled.map +3 -3
  38. package/dist/shaka-player.compiled.d.ts +165 -9
  39. package/dist/shaka-player.compiled.debug.d.ts +165 -9
  40. package/dist/shaka-player.compiled.debug.externs.js +179 -11
  41. package/dist/shaka-player.compiled.debug.js +367 -339
  42. package/dist/shaka-player.compiled.debug.map +4 -4
  43. package/dist/shaka-player.compiled.externs.js +179 -11
  44. package/dist/shaka-player.compiled.js +818 -807
  45. package/dist/shaka-player.compiled.map +4 -4
  46. package/dist/shaka-player.ui.d.ts +165 -9
  47. package/dist/shaka-player.ui.debug.d.ts +165 -9
  48. package/dist/shaka-player.ui.debug.externs.js +181 -13
  49. package/dist/shaka-player.ui.debug.js +459 -429
  50. package/dist/shaka-player.ui.debug.map +4 -4
  51. package/dist/shaka-player.ui.externs.js +181 -13
  52. package/dist/shaka-player.ui.js +973 -961
  53. package/dist/shaka-player.ui.map +4 -4
  54. package/docs/tutorials/fairplay.md +1 -1
  55. package/docs/tutorials/faq.md +25 -0
  56. package/docs/tutorials/index.json +2 -2
  57. package/docs/tutorials/upgrade-v3.md +38 -0
  58. package/docs/tutorials/upgrade.md +3 -3
  59. package/externs/ima.js +4 -0
  60. package/externs/media_capabilities.js +16 -0
  61. package/externs/shaka/ads.js +2 -0
  62. package/externs/shaka/net.js +2 -2
  63. package/externs/shaka/player.js +6 -0
  64. package/karma.conf.js +1 -0
  65. package/lib/abr/simple_abr_manager.js +1 -1
  66. package/lib/ads/ad_manager.js +29 -11
  67. package/lib/ads/client_side_ad_manager.js +76 -86
  68. package/lib/ads/server_side_ad_manager.js +26 -11
  69. package/lib/cast/cast_proxy.js +3 -2
  70. package/lib/cast/cast_receiver.js +2 -1
  71. package/lib/cast/cast_sender.js +2 -2
  72. package/lib/cea/sei_processor.js +24 -17
  73. package/lib/dash/dash_parser.js +107 -4
  74. package/lib/dash/segment_list.js +1 -1
  75. package/lib/dash/segment_template.js +45 -15
  76. package/lib/dependencies/all.js +15 -15
  77. package/lib/hls/hls_parser.js +46 -18
  78. package/lib/media/closed_caption_parser.js +3 -1
  79. package/lib/media/content_workarounds.js +18 -0
  80. package/lib/media/drm_engine.js +20 -1
  81. package/lib/media/gap_jumping_controller.js +8 -4
  82. package/lib/media/media_source_engine.js +1 -2
  83. package/lib/media/playhead.js +1 -1
  84. package/lib/media/presentation_timeline.js +3 -1
  85. package/lib/media/segment_index.js +38 -12
  86. package/lib/media/segment_reference.js +8 -0
  87. package/lib/media/streaming_engine.js +30 -21
  88. package/lib/net/http_fetch_plugin.js +2 -2
  89. package/lib/net/networking_engine.js +3 -2
  90. package/lib/offline/storage.js +4 -0
  91. package/lib/player.js +113 -56
  92. package/lib/polyfill/encryption_scheme.js +2 -0
  93. package/lib/polyfill/fullscreen.js +2 -0
  94. package/lib/polyfill/mathround.js +2 -0
  95. package/lib/polyfill/media_capabilities.js +62 -23
  96. package/lib/polyfill/mediasource.js +8 -0
  97. package/lib/polyfill/orientation.js +3 -1
  98. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  99. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  100. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  101. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  102. package/lib/polyfill/pip_webkit.js +2 -0
  103. package/lib/polyfill/storage_estimate.js +2 -0
  104. package/lib/polyfill/video_play_promise.js +2 -0
  105. package/lib/polyfill/videoplaybackquality.js +2 -0
  106. package/lib/polyfill/vttcue.js +2 -0
  107. package/lib/text/cue.js +11 -0
  108. package/lib/text/mp4_ttml_parser.js +3 -0
  109. package/lib/text/text_engine.js +14 -2
  110. package/lib/text/ttml_text_parser.js +52 -30
  111. package/lib/text/ui_text_displayer.js +255 -82
  112. package/lib/text/vtt_text_parser.js +4 -2
  113. package/lib/util/error.js +1 -4
  114. package/lib/util/fake_event.js +53 -7
  115. package/lib/util/mime_utils.js +33 -0
  116. package/lib/util/mp4_box_parsers.js +1 -1
  117. package/lib/util/mp4_parser.js +14 -0
  118. package/lib/util/networking.js +1 -1
  119. package/lib/util/periods.js +132 -37
  120. package/lib/util/platform.js +13 -1
  121. package/lib/util/stream_utils.js +46 -7
  122. package/package.json +3 -3
  123. package/test/cast/cast_proxy_unit.js +12 -6
  124. package/test/cast/cast_receiver_integration.js +1 -1
  125. package/test/cast/cast_utils_unit.js +1 -1
  126. package/test/cea/mp4_cea_parser_unit.js +26 -0
  127. package/test/dash/dash_parser_live_unit.js +1 -0
  128. package/test/dash/dash_parser_manifest_unit.js +187 -1
  129. package/test/dash/dash_parser_segment_template_unit.js +3 -3
  130. package/test/demo/demo_unit.js +0 -1
  131. package/test/hls/hls_parser_unit.js +45 -1
  132. package/test/media/closed_caption_parser_unit.js +20 -0
  133. package/test/media/drm_engine_unit.js +59 -3
  134. package/test/media/playhead_unit.js +27 -1
  135. package/test/media/presentation_timeline_unit.js +19 -0
  136. package/test/media/segment_index_unit.js +76 -3
  137. package/test/media/streaming_engine_unit.js +3 -3
  138. package/test/player_integration.js +38 -0
  139. package/test/player_unit.js +125 -6
  140. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  141. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  142. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  143. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  144. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  145. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  146. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  147. package/test/test/assets/3675/dash_0.mpd +30 -0
  148. package/test/test/assets/3675/dash_1.mpd +30 -0
  149. package/test/test/assets/3675/dash_2.mpd +30 -0
  150. package/test/test/assets/3675/dash_3.mpd +30 -0
  151. package/test/test/assets/3675/dash_4.mpd +30 -0
  152. package/test/test/assets/3675/dash_5.mpd +26 -0
  153. package/test/test/assets/3675/init-stream0.m4s +0 -0
  154. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  155. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  156. package/test/test/util/fake_ad_manager.js +4 -1
  157. package/test/test/util/simple_fakes.js +5 -3
  158. package/test/test/util/waiter.js +36 -2
  159. package/test/text/ttml_text_parser_unit.js +55 -0
  160. package/test/text/ui_text_displayer_unit.js +95 -18
  161. package/test/text/vtt_text_parser_unit.js +17 -1
  162. package/test/ui/localization_unit.js +15 -14
  163. package/test/ui/text_displayer_layout_unit.js +52 -1
  164. package/test/ui/ui_unit.js +4 -0
  165. package/test/util/mp4_box_parsers_unit.js +28 -0
  166. package/test/util/periods_unit.js +190 -2
  167. package/test/util/stream_utils_unit.js +56 -5
  168. package/ui/airplay_button.js +5 -0
  169. package/ui/big_play_button.js +10 -0
  170. package/ui/cast_button.js +2 -3
  171. package/ui/controls.js +9 -7
  172. package/ui/less/containers.less +10 -5
  173. package/ui/locales/en.json +1 -0
  174. package/ui/locales/ru.json +6 -6
  175. package/ui/locales/source.json +4 -0
  176. package/ui/localization.js +28 -51
  177. package/ui/pip_button.js +2 -3
  178. package/ui/play_button.js +10 -10
  179. package/ui/small_play_button.js +12 -0
  180. package/ui/ui.js +1 -1
  181. package/docs/tutorials/upgrade-v2-4.md +0 -481
@@ -12,6 +12,7 @@ goog.require('shaka.Deprecate');
12
12
  goog.require('shaka.text.Cue');
13
13
  goog.require('shaka.text.CueRegion');
14
14
  goog.require('shaka.util.Dom');
15
+ goog.require('shaka.util.EventManager');
15
16
  goog.require('shaka.util.Timer');
16
17
 
17
18
 
@@ -72,8 +73,31 @@ shaka.text.UITextDisplayer = class {
72
73
  this.updateCaptions_();
73
74
  }).tickEvery(updatePeriod);
74
75
 
75
- /** private {Map.<!shaka.extern.Cue, !HTMLElement>} */
76
+ /**
77
+ * Maps cues to cue elements. Specifically points out the wrapper element of
78
+ * the cue (e.g. the HTML element to put nested cues inside).
79
+ * @private {Map.<!shaka.extern.Cue, !{
80
+ * cueElement: !HTMLElement,
81
+ * wrapper: !HTMLElement
82
+ * }>}
83
+ */
76
84
  this.currentCuesMap_ = new Map();
85
+
86
+ /** @private {shaka.util.EventManager} */
87
+ this.eventManager_ = new shaka.util.EventManager();
88
+
89
+ this.eventManager_.listen(document, 'fullscreenchange', () => {
90
+ this.updateCaptions_(/* forceUpdate= */ true);
91
+ });
92
+
93
+ /** @private {ResizeObserver} */
94
+ this.resizeObserver_ = null;
95
+ if ('ResizeObserver' in window) {
96
+ this.resizeObserver_ = new ResizeObserver(() => {
97
+ this.updateCaptions_(/* forceUpdate= */ true);
98
+ });
99
+ this.resizeObserver_.observe(this.textContainer_);
100
+ }
77
101
  }
78
102
 
79
103
 
@@ -118,6 +142,17 @@ shaka.text.UITextDisplayer = class {
118
142
  }
119
143
 
120
144
  this.currentCuesMap_.clear();
145
+
146
+ // Tear-down the event manager to ensure messages stop moving around.
147
+ if (this.eventManager_) {
148
+ this.eventManager_.release();
149
+ this.eventManager_ = null;
150
+ }
151
+
152
+ if (this.resizeObserver_) {
153
+ this.resizeObserver_.disconnect();
154
+ this.resizeObserver_ = null;
155
+ }
121
156
  }
122
157
 
123
158
 
@@ -132,9 +167,14 @@ shaka.text.UITextDisplayer = class {
132
167
  }
133
168
 
134
169
  // Remove the cues out of the time range.
170
+ const oldNumCues = this.cues_.length;
135
171
  this.cues_ = this.cues_.filter(
136
172
  (cue) => cue.startTime < start || cue.endTime >= end);
137
- this.updateCaptions_();
173
+ // If anything was actually removed in this process, force the captions to
174
+ // update. This makes sure that the currently-displayed cues will stop
175
+ // displaying if removed (say, due to the user changing languages).
176
+ const forceUpdate = oldNumCues > this.cues_.length;
177
+ this.updateCaptions_(forceUpdate);
138
178
 
139
179
  return true;
140
180
  }
@@ -157,67 +197,151 @@ shaka.text.UITextDisplayer = class {
157
197
  }
158
198
 
159
199
  /**
160
- * Display the current captions.
200
+ * @param {!Array.<!shaka.extern.Cue>} cues
201
+ * @param {!HTMLElement} container
202
+ * @param {number} currentTime
203
+ * @param {!Array.<!shaka.extern.Cue>} parents
161
204
  * @private
162
205
  */
163
- updateCaptions_() {
164
- const currentTime = this.video_.currentTime;
206
+ updateCuesRecursive_(cues, container, currentTime, parents) {
207
+ // Set to true if the cues have changed in some way, which will require
208
+ // DOM changes. E.g. if a cue was added or removed.
209
+ let updateDOM = false;
210
+ /**
211
+ * The elements to remove from the DOM.
212
+ * Some of these elements may be added back again, if their corresponding
213
+ * cue is in toPlant.
214
+ * These elements are only removed if updateDOM is true.
215
+ * @type {!Array.<!HTMLElement>}
216
+ */
217
+ const toUproot = [];
218
+ /**
219
+ * The cues whose corresponding elements should be in the DOM.
220
+ * Some of these might be new, some might have been displayed beforehand.
221
+ * These will only be added if updateDOM is true.
222
+ * @type {!Array.<!shaka.extern.Cue>}
223
+ */
224
+ const toPlant = [];
225
+ for (const cue of cues) {
226
+ parents.push(cue);
227
+
228
+ let cueRegistry = this.currentCuesMap_.get(cue);
229
+ const shouldBeDisplayed =
230
+ cue.startTime <= currentTime && cue.endTime > currentTime;
231
+ let wrapper = cueRegistry ? cueRegistry.wrapper : null;
232
+
233
+ if (cueRegistry) {
234
+ // If the cues are replanted, all existing cues should be uprooted,
235
+ // even ones which are going to be planted again.
236
+ toUproot.push(cueRegistry.cueElement);
237
+
238
+ // If the cue should not be displayed, remove it entirely.
239
+ if (!shouldBeDisplayed) {
240
+ // Since something has to be removed, we will need to update the DOM.
241
+ updateDOM = true;
242
+ this.currentCuesMap_.delete(cue);
243
+ cueRegistry = null;
244
+ }
245
+ }
165
246
 
166
- // Return true if the cue should be displayed at the current time point.
167
- const shouldCueBeDisplayed = (cue) => {
168
- return this.cues_.includes(cue) && this.isTextVisible_ &&
169
- cue.startTime <= currentTime && cue.endTime > currentTime;
170
- };
247
+ if (shouldBeDisplayed) {
248
+ toPlant.push(cue);
249
+ if (!cueRegistry) {
250
+ // The cue has to be made!
251
+ this.createCue_(cue, parents);
252
+ cueRegistry = this.currentCuesMap_.get(cue);
253
+ wrapper = cueRegistry.wrapper;
254
+ updateDOM = true;
255
+ }
256
+ }
171
257
 
172
- // For each cue in the current cues map, if the cue's end time has passed,
173
- // remove the entry from the map, and remove the captions from the page.
174
- for (const cue of this.currentCuesMap_.keys()) {
175
- if (!shouldCueBeDisplayed(cue)) {
176
- const captions = this.currentCuesMap_.get(cue);
177
- this.textContainer_.removeChild(captions);
178
- this.currentCuesMap_.delete(cue);
258
+ // Recursively check the nested cues, to see if they need to be added or
259
+ // removed.
260
+ // If wrapper is null, that means that the cue is not only not being
261
+ // displayed currently, it also was not removed this tick. So it's
262
+ // guaranteed that the children will neither need to be added nor removed.
263
+ if (cue.nestedCues.length > 0 && wrapper) {
264
+ this.updateCuesRecursive_(
265
+ cue.nestedCues, wrapper, currentTime, parents);
179
266
  }
180
- }
181
267
 
182
- // Sometimes we don't remove a cue element correctly. So check all the
183
- // child nodes and remove any that don't have an associated cue.
184
- const expectedChildren = new Set(this.currentCuesMap_.values());
185
- for (const child of Array.from(this.textContainer_.childNodes)) {
186
- if (!expectedChildren.has(child)) {
187
- this.textContainer_.removeChild(child);
268
+ const topCue = parents.pop();
269
+ goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
270
+ }
271
+ if (updateDOM) {
272
+ for (const cueElement of toUproot) {
273
+ container.removeChild(cueElement);
274
+ }
275
+ toPlant.sort((a, b) => {
276
+ if (a.startTime != b.startTime) {
277
+ return a.startTime - b.startTime;
278
+ } else {
279
+ return a.endTime - b.endTime;
280
+ }
281
+ });
282
+ for (const cue of toPlant) {
283
+ const cueRegistry = this.currentCuesMap_.get(cue);
284
+ goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
285
+ container.appendChild(cueRegistry.cueElement);
188
286
  }
189
287
  }
288
+ }
190
289
 
191
- // Get the current cues that should be added to display. If the cue is not
192
- // being displayed already, add it to the map, and add the captions onto the
193
- // page.
194
- const currentCues = this.cues_.filter((cue) => {
195
- return shouldCueBeDisplayed(cue) && !this.currentCuesMap_.has(cue);
196
- }).sort((a, b) => {
197
- if (a.startTime != b.startTime) {
198
- return a.startTime - b.startTime;
199
- } else {
200
- return a.endTime - b.endTime;
290
+ /**
291
+ * Display the current captions.
292
+ * @param {boolean=} forceUpdate
293
+ * @private
294
+ */
295
+ updateCaptions_(forceUpdate = false) {
296
+ if (!this.textContainer_) {
297
+ return;
298
+ }
299
+
300
+ const currentTime = this.video_.currentTime;
301
+ if (!this.isTextVisible_ || forceUpdate) {
302
+ if (this.currentCuesMap_.size > 0) {
303
+ // Clear away any existing cues.
304
+ shaka.util.Dom.removeAllChildren(this.textContainer_);
305
+ this.currentCuesMap_.clear();
306
+ }
307
+ }
308
+ if (this.isTextVisible_) {
309
+ // Log currently attached cue elements for verification, later.
310
+ const previousCuesMap = new Map();
311
+ for (const cue of this.currentCuesMap_.keys()) {
312
+ previousCuesMap.set(cue, this.currentCuesMap_.get(cue));
201
313
  }
202
- });
203
314
 
204
- for (const cue of currentCues) {
205
- const cueElement = this.displayCue_(
206
- this.textContainer_, cue, /* isNested= */ false);
207
- this.currentCuesMap_.set(cue, cueElement);
315
+ // Update the cues.
316
+ this.updateCuesRecursive_(
317
+ this.cues_, this.textContainer_, currentTime, /* parents= */ []);
318
+
319
+ if (goog.DEBUG) {
320
+ // Previously, we had an issue (#2076) where cues sometimes were not
321
+ // properly removed from the DOM. It is not clear if this issue still
322
+ // happens, so the previous fix for it has been changed to an assert.
323
+ for (const cue of previousCuesMap.keys()) {
324
+ if (!this.currentCuesMap_.has(cue)) {
325
+ // TODO: If the problem does not appear again, then we should remove
326
+ // this assert (and the previousCuesMap code) in Shaka v4.
327
+ const cueElement = previousCuesMap.get(cue).cueElement;
328
+ goog.asserts.assert(
329
+ !cueElement.parentNode, 'Cue was not properly removed!');
330
+ }
331
+ }
332
+ }
208
333
  }
209
334
  }
210
335
 
211
336
  /**
212
- * Displays a cue
337
+ * Creates the object for a cue.
213
338
  *
214
- * @param {Element} container
215
339
  * @param {!shaka.extern.Cue} cue
216
- * @param {boolean} isNested
217
- * @return {!Element} the created captions element
340
+ * @param {!Array.<!shaka.extern.Cue>} parents
218
341
  * @private
219
342
  */
220
- displayCue_(container, cue, isNested) {
343
+ createCue_(cue, parents) {
344
+ const isNested = parents.length > 1;
221
345
  let type = isNested ? 'span' : 'div';
222
346
  if (cue.lineBreak || cue.spacer) {
223
347
  if (cue.spacer) {
@@ -230,29 +354,36 @@ shaka.text.UITextDisplayer = class {
230
354
 
231
355
  // Nested cues are inline elements. Top-level cues are block elements.
232
356
  const cueElement = shaka.util.Dom.createHTMLElement(type);
233
-
234
357
  if (type != 'br') {
235
- this.setCaptionStyles_(cueElement, cue, isNested);
358
+ this.setCaptionStyles_(cueElement, cue, parents);
359
+ }
236
360
 
237
- for (const nestedCue of cue.nestedCues) {
238
- this.displayCue_(cueElement, nestedCue, /* isNested= */ true);
239
- }
361
+ let wrapper = cueElement;
362
+ if (!isNested && cue.nestedCues.length) {
363
+ // Create a wrapper element which will serve to contain all children into
364
+ // a single item. This ensures that nested span elements appear
365
+ // horizontally and br elements occupy no vertical space.
366
+ wrapper = shaka.util.Dom.createHTMLElement('span');
367
+ wrapper.classList.add('shaka-text-wrapper');
368
+ cueElement.appendChild(wrapper);
240
369
  }
241
370
 
242
- container.appendChild(cueElement);
243
- return cueElement;
371
+ this.currentCuesMap_.set(cue, {cueElement, wrapper});
244
372
  }
245
373
 
246
374
  /**
247
375
  * @param {!HTMLElement} cueElement
248
376
  * @param {!shaka.extern.Cue} cue
249
- * @param {boolean} isNested
377
+ * @param {!Array.<!shaka.extern.Cue>} parents
250
378
  * @private
251
379
  */
252
- setCaptionStyles_(cueElement, cue, isNested) {
380
+ setCaptionStyles_(cueElement, cue, parents) {
253
381
  const Cue = shaka.text.Cue;
254
- let style = cueElement.style;
382
+ const inherit =
383
+ (cb) => shaka.text.UITextDisplayer.inheritProperty_(parents, cb);
384
+ const style = cueElement.style;
255
385
  const isLeaf = cue.nestedCues.length == 0;
386
+ const isNested = parents.length > 1;
256
387
 
257
388
  // TODO: wrapLine is not yet supported. Lines always wrap.
258
389
 
@@ -271,16 +402,6 @@ shaka.text.UITextDisplayer = class {
271
402
  const nonBreakingSpace = '\xa0';
272
403
  return nonBreakingSpace.repeat(match.length);
273
404
  });
274
- if (isNested) {
275
- cueElement.textContent = text;
276
- } else if (text.length) {
277
- // If a top-level cue has text, move to a <span> so the background is
278
- // styled correctly.
279
- const span = shaka.util.Dom.createHTMLElement('span');
280
- span.textContent = text;
281
- cueElement.appendChild(span);
282
- style = span.style;
283
- }
284
405
 
285
406
  style.backgroundColor = cue.backgroundColor;
286
407
  style.border = cue.border;
@@ -289,32 +410,60 @@ shaka.text.UITextDisplayer = class {
289
410
  style.opacity = cue.opacity;
290
411
  style.paddingLeft = shaka.text.UITextDisplayer.convertLengthValue_(
291
412
  cue.linePadding, cue, this.videoContainer_);
292
- style.paddingRight = shaka.text.UITextDisplayer.convertLengthValue_(
293
- cue.linePadding, cue, this.videoContainer_);
413
+ style.paddingRight =
414
+ shaka.text.UITextDisplayer.convertLengthValue_(
415
+ cue.linePadding, cue, this.videoContainer_);
294
416
 
295
417
  if (cue.backgroundImage) {
296
418
  style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
297
419
  style.backgroundRepeat = 'no-repeat';
298
420
  style.backgroundSize = 'contain';
299
421
  style.backgroundPosition = 'center';
422
+ } else {
423
+ // If we have both text and nested cues, then style everything; otherwise
424
+ // place the text in its own <span> so the background doesn't fill the
425
+ // whole region.
426
+ let elem;
427
+ if (cue.nestedCues.length) {
428
+ elem = cueElement;
429
+ } else {
430
+ elem = shaka.util.Dom.createHTMLElement('span');
431
+ cueElement.appendChild(elem);
432
+ }
300
433
 
301
- if (cue.backgroundColor == '') {
302
- // In text-based cues, background color can default in CSS.
303
- // In bitmap-based cues, we default to a transparent background color,
304
- // so that the bitmap can be the only background.
305
- style.backgroundColor = 'transparent';
434
+ if (cue.border) {
435
+ elem.style.border = cue.border;
436
+ }
437
+ const bgColor = inherit((c) => c.backgroundColor);
438
+ if (bgColor) {
439
+ elem.style.backgroundColor = bgColor;
440
+ } else if (text) {
441
+ // If there is no background, default to a semi-transparent black.
442
+ // Only do this for the text itself.
443
+ elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
444
+ }
445
+ if (text) {
446
+ elem.textContent = text;
306
447
  }
307
448
  }
308
449
 
309
450
  // The displayAlign attribute specifies the vertical alignment of the
310
451
  // captions inside the text container. Before means at the top of the
311
452
  // text container, and after means at the bottom.
312
- if (cue.displayAlign == Cue.displayAlign.BEFORE) {
313
- style.verticalAlign = 'top';
314
- } else if (cue.displayAlign == Cue.displayAlign.CENTER) {
315
- style.verticalAlign = 'middle';
453
+ if (isNested) {
454
+ style.display = 'inline';
316
455
  } else {
317
- style.verticalAlign = 'bottom';
456
+ style.display = 'flex';
457
+ style.flexDirection = 'column';
458
+ style.alignItems = 'center';
459
+
460
+ if (cue.displayAlign == Cue.displayAlign.BEFORE) {
461
+ style.justifyContent = 'flex-start';
462
+ } else if (cue.displayAlign == Cue.displayAlign.CENTER) {
463
+ style.justifyContent = 'center';
464
+ } else {
465
+ style.justifyContent = 'flex-end';
466
+ }
318
467
  }
319
468
 
320
469
  if (!isLeaf) {
@@ -347,20 +496,28 @@ shaka.text.UITextDisplayer = class {
347
496
  // TODO: Implement lineAlignment of 'CENTER'.
348
497
  if (cue.line) {
349
498
  if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
499
+ // When setting absolute positioning, you need to set x/y/width/height
500
+ // so the element is positioned correctly. Set these as default and
501
+ // other settings will override them.
350
502
  style.position = 'absolute';
503
+ style.left = '0';
504
+ style.top = '0';
351
505
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
506
+ style.width = '100%';
352
507
  if (cue.lineAlign == Cue.lineAlign.START) {
353
508
  style.top = cue.line + '%';
354
509
  } else if (cue.lineAlign == Cue.lineAlign.END) {
355
510
  style.bottom = cue.line + '%';
356
511
  }
357
512
  } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
513
+ style.height = '100%';
358
514
  if (cue.lineAlign == Cue.lineAlign.START) {
359
515
  style.left = cue.line + '%';
360
516
  } else if (cue.lineAlign == Cue.lineAlign.END) {
361
517
  style.right = cue.line + '%';
362
518
  }
363
519
  } else {
520
+ style.height = '100%';
364
521
  if (cue.lineAlign == Cue.lineAlign.START) {
365
522
  style.right = cue.line + '%';
366
523
  } else if (cue.lineAlign == Cue.lineAlign.END) {
@@ -368,12 +525,7 @@ shaka.text.UITextDisplayer = class {
368
525
  }
369
526
  }
370
527
  }
371
- } else if (cue.region && cue.region.id &&
372
- ((!isNested && !isLeaf) || (cue.backgroundImage))) {
373
- // In text-base cues, regions are only applied to block container
374
- // (!isNested && !isLeaf).
375
- // In bitmap-based cues, region settings are used to specify the size and
376
- // position of the backgroundImage.
528
+ } else if (cue.region && cue.region.id) {
377
529
  const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
378
530
  const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
379
531
  const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
@@ -506,4 +658,25 @@ shaka.text.UITextDisplayer = class {
506
658
 
507
659
  return (containerHeight * value / cue.cellResolution.rows) + 'px';
508
660
  }
661
+
662
+ /**
663
+ * Inherits a property from the parent Cue elements. If the value is falsy,
664
+ * it is assumed to be inherited from the parent. This returns null if the
665
+ * value isn't found.
666
+ *
667
+ * @param {!Array.<!shaka.extern.Cue>} parents
668
+ * @param {function(!shaka.extern.Cue):?T} cb
669
+ * @return {?T}
670
+ * @template T
671
+ * @private
672
+ */
673
+ static inheritProperty_(parents, cb) {
674
+ for (let i = parents.length - 1; i >= 0; i--) {
675
+ const val = cb(parents[i]);
676
+ if (val || val === 0) {
677
+ return val;
678
+ }
679
+ }
680
+ return null;
681
+ }
509
682
  };
@@ -122,7 +122,7 @@ shaka.text.VttTextParser = class {
122
122
  }
123
123
 
124
124
  /**
125
- * Add deafult color
125
+ * Add default color
126
126
  *
127
127
  * @param {!Map.<string, shaka.text.Cue>} styles
128
128
  * @private
@@ -436,7 +436,9 @@ shaka.text.VttTextParser = class {
436
436
  }
437
437
  const tagEnd = payload.substring(i + 1, end);
438
438
  const tagStart = names.pop();
439
- if (tagStart === tagEnd) {
439
+ if (!tagEnd || !tagStart) {
440
+ return payload;
441
+ } else if (tagStart === tagEnd) {
440
442
  newPayload += '/' + tagEnd + '>';
441
443
  i += tagEnd.length + 1;
442
444
  } else {
package/lib/util/error.js CHANGED
@@ -22,10 +22,7 @@ goog.provide('shaka.util.Error');
22
22
  * <ul>
23
23
  * <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaError">MediaError</a>
24
24
  * <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP Codes</a>
25
- * <li>
26
- * <a href="https://docs.microsoft.com/en-us/windows/win32/wmdm/error-codes">PlayReady errors</a>
27
- * or
28
- * <a href="https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/winrt/Windows.Media.Protection.PlayReadyErrors.h">more PlayReady errors</a>
25
+ * <li><a href="https://hresult.info">Edge/PlayReady errors</a>
29
26
  * </ul>
30
27
  *
31
28
  * @export
@@ -6,6 +6,8 @@
6
6
 
7
7
  goog.provide('shaka.util.FakeEvent');
8
8
 
9
+ goog.require('goog.asserts');
10
+
9
11
 
10
12
  /**
11
13
  * @summary Create an Event work-alike object based on the provided dictionary.
@@ -16,18 +18,62 @@ goog.provide('shaka.util.FakeEvent');
16
18
  */
17
19
  shaka.util.FakeEvent = class {
18
20
  /**
19
- * @param {string} type
20
- * @param {Object=} dict
21
+ * @param {!Event} event
22
+ * @return {!shaka.util.FakeEvent}
21
23
  */
22
- constructor(type, dict = {}) {
23
- // Take properties from dict if present.
24
- for (const key in dict) {
25
- Object.defineProperty(this, key, {
26
- value: dict[key],
24
+ static fromRealEvent(event) {
25
+ const fakeEvent = new shaka.util.FakeEvent(event.type);
26
+ for (const key in event) {
27
+ Object.defineProperty(fakeEvent, key, {
28
+ value: event[key],
27
29
  writable: true,
28
30
  enumerable: true,
29
31
  });
30
32
  }
33
+ return fakeEvent;
34
+ }
35
+
36
+ /**
37
+ * Allows us to tell the compiler that the dictionary "map" is actually a
38
+ * generic object, for backwards compatibility.
39
+ * @param {!Map.<string, Object>} dict
40
+ * @return {!Object}
41
+ * @suppress {invalidCasts}
42
+ * @private
43
+ */
44
+ static recastDictAsObject_(dict) {
45
+ goog.asserts.assert(!(dict instanceof Map), 'dict should not be a map');
46
+ return /** @type {!Object} */ (dict);
47
+ }
48
+
49
+ /**
50
+ * @param {string} type
51
+ * @param {Map.<string, Object>=} dict
52
+ */
53
+ constructor(type, dict) {
54
+ if (dict) {
55
+ if (dict instanceof Map) {
56
+ // Take properties from dict if present.
57
+ for (const key of dict.keys()) {
58
+ Object.defineProperty(this, key, {
59
+ value: dict.get(key),
60
+ writable: true,
61
+ enumerable: true,
62
+ });
63
+ }
64
+ } else {
65
+ // For backwards compatibility with external apps that may make use of
66
+ // this public constructor, this should still accept generic objects.
67
+ const obj = shaka.util.FakeEvent.recastDictAsObject_(dict);
68
+ for (const key in obj) {
69
+ Object.defineProperty(this, key, {
70
+ value: obj[key],
71
+ writable: true,
72
+ enumerable: true,
73
+ });
74
+ }
75
+ }
76
+ }
31
77
 
32
78
  // The properties below cannot be set by the dict. They are all provided
33
79
  // for compatibility with native events.
@@ -105,6 +105,39 @@ shaka.util.MimeUtils = class {
105
105
  return parts[0];
106
106
  }
107
107
 
108
+ /**
109
+ * Takes a full MIME type (with codecs) or basic MIME type (without codecs)
110
+ * and returns a basic MIME type (without codecs or other parameters).
111
+ *
112
+ * @param {string} mimeType
113
+ * @return {string}
114
+ */
115
+ static getBasicType(mimeType) {
116
+ return mimeType.split(';')[0];
117
+ }
118
+
119
+ /**
120
+ * Takes a MIME type and returns the codecs parameter, or an empty string if
121
+ * there is no codecs parameter.
122
+ *
123
+ * @param {string} mimeType
124
+ * @return {string}
125
+ */
126
+ static getCodecs(mimeType) {
127
+ // Parse the basic MIME type from its parameters.
128
+ const pieces = mimeType.split(/ *; */);
129
+ pieces.shift(); // Remove basic MIME type from pieces.
130
+
131
+ const codecs = pieces.find((piece) => piece.startsWith('codecs='));
132
+ if (!codecs) {
133
+ return '';
134
+ }
135
+
136
+ // The value may be quoted, so remove quotes at the beginning or end.
137
+ const value = codecs.split('=')[1].replace(/^"|"$/g, '');
138
+ return value;
139
+ }
140
+
108
141
  /**
109
142
  * Get the base and profile of a codec string. Where [0] will be the codec
110
143
  * base and [1] will be the profile.
@@ -176,7 +176,7 @@ shaka.util.Mp4BoxParsers = class {
176
176
  if (version == 1) {
177
177
  reader.skip(8); // Skip "creation_time"
178
178
  reader.skip(8); // Skip "modification_time"
179
- trackId = reader.readUint64();
179
+ trackId = reader.readUint32();
180
180
  } else {
181
181
  reader.skip(4); // Skip "creation_time"
182
182
  reader.skip(4); // Skip "modification_time"
@@ -111,6 +111,12 @@ shaka.util.Mp4Parser = class {
111
111
  parseNext(absStart, reader, partialOkay, stopOnPartial) {
112
112
  const start = reader.getPosition();
113
113
 
114
+ // size(4 bytes) + type(4 bytes) = 8 bytes
115
+ if (stopOnPartial && start + 8 > reader.getLength()) {
116
+ this.done_ = true;
117
+ return;
118
+ }
119
+
114
120
  let size = reader.readUint32();
115
121
  const type = reader.readUint32();
116
122
  const name = shaka.util.Mp4Parser.typeToString(type);
@@ -122,6 +128,10 @@ shaka.util.Mp4Parser = class {
122
128
  size = reader.getLength() - start;
123
129
  break;
124
130
  case 1:
131
+ if (stopOnPartial && reader.getPosition() + 8 > reader.getLength()) {
132
+ this.done_ = true;
133
+ return;
134
+ }
125
135
  size = reader.readUint64();
126
136
  has64BitSize = true;
127
137
  break;
@@ -134,6 +144,10 @@ shaka.util.Mp4Parser = class {
134
144
  let flags = null;
135
145
 
136
146
  if (this.headers_[type] == shaka.util.Mp4Parser.BoxType_.FULL_BOX) {
147
+ if (stopOnPartial && reader.getPosition() + 4 > reader.getLength()) {
148
+ this.done_ = true;
149
+ return;
150
+ }
137
151
  const versionAndFlags = reader.readUint32();
138
152
  version = versionAndFlags >>> 24;
139
153
  flags = versionAndFlags & 0xFFFFFF;
@@ -26,7 +26,7 @@ shaka.util.Networking = class {
26
26
  * @param {?number} start
27
27
  * @param {?number} end
28
28
  * @param {shaka.extern.RetryParameters} retryParameters
29
- * @param {?function(BufferSource)=} streamDataCallback
29
+ * @param {?function(BufferSource):!Promise=} streamDataCallback
30
30
  * @return {shaka.extern.Request}
31
31
  */
32
32
  static createSegmentRequest(uris, start, end, retryParameters,