shaka-player 3.1.3 → 3.2.2

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 (182) hide show
  1. package/AUTHORS +3 -0
  2. package/CHANGELOG.md +175 -3
  3. package/CONTRIBUTORS +3 -0
  4. package/README.md +4 -4
  5. package/build/generateExterns.js +1 -1
  6. package/build/types/polyfill +1 -0
  7. package/demo/common/asset.js +9 -7
  8. package/demo/common/assets.js +11 -2
  9. package/demo/common/message_ids.js +2 -1
  10. package/demo/config.js +5 -2
  11. package/demo/index.html +1 -1
  12. package/demo/locales/en.json +2 -1
  13. package/demo/locales/source.json +8 -4
  14. package/demo/main.js +2 -1
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +15 -15
  18. package/dist/demo.compiled.debug.map +2 -2
  19. package/dist/demo.compiled.js +15 -15
  20. package/dist/demo.compiled.map +2 -2
  21. package/dist/deps.js +22 -21
  22. package/dist/locales.js +36 -36
  23. package/dist/receiver.compiled.debug.js +9 -9
  24. package/dist/receiver.compiled.debug.map +2 -2
  25. package/dist/receiver.compiled.js +9 -9
  26. package/dist/receiver.compiled.map +2 -2
  27. package/dist/shaka-player.compiled.d.ts +58 -10
  28. package/dist/shaka-player.compiled.debug.d.ts +58 -10
  29. package/dist/shaka-player.compiled.debug.externs.js +777 -717
  30. package/dist/shaka-player.compiled.debug.js +639 -607
  31. package/dist/shaka-player.compiled.debug.map +4 -4
  32. package/dist/shaka-player.compiled.externs.js +777 -717
  33. package/dist/shaka-player.compiled.js +826 -819
  34. package/dist/shaka-player.compiled.map +4 -4
  35. package/dist/shaka-player.ui.d.ts +58 -10
  36. package/dist/shaka-player.ui.debug.d.ts +58 -10
  37. package/dist/shaka-player.ui.debug.externs.js +529 -469
  38. package/dist/shaka-player.ui.debug.js +676 -645
  39. package/dist/shaka-player.ui.debug.map +4 -4
  40. package/dist/shaka-player.ui.externs.js +529 -469
  41. package/dist/shaka-player.ui.js +963 -955
  42. package/dist/shaka-player.ui.map +4 -4
  43. package/docs/tutorials/fairplay.md +1 -1
  44. package/docs/tutorials/ui.md +5 -5
  45. package/externs/aria.js +6 -0
  46. package/externs/shaka/ads.js +24 -1
  47. package/externs/shaka/manifest.js +6 -1
  48. package/externs/shaka/offline.js +5 -0
  49. package/externs/shaka/player.js +53 -9
  50. package/externs/shaka/text.js +10 -10
  51. package/externs/texttrack.js +3 -0
  52. package/karma.conf.js +1 -0
  53. package/lib/ads/ad_manager.js +16 -15
  54. package/lib/ads/client_side_ad_manager.js +52 -88
  55. package/lib/ads/server_side_ad_manager.js +26 -12
  56. package/lib/cast/cast_proxy.js +11 -2
  57. package/lib/cast/cast_receiver.js +2 -1
  58. package/lib/cast/cast_sender.js +2 -2
  59. package/lib/cast/cast_utils.js +6 -1
  60. package/lib/cea/cea608_data_channel.js +56 -56
  61. package/lib/cea/cea608_memory.js +33 -33
  62. package/lib/cea/cea708_service.js +43 -43
  63. package/lib/cea/cea708_window.js +38 -38
  64. package/lib/cea/cea_decoder.js +13 -14
  65. package/lib/cea/cea_utils.js +23 -23
  66. package/lib/cea/dtvcc_packet_builder.js +7 -7
  67. package/lib/cea/i_caption_decoder.js +2 -3
  68. package/lib/cea/i_cea_parser.js +2 -2
  69. package/lib/cea/mp4_cea_parser.js +6 -6
  70. package/lib/dash/dash_parser.js +98 -2
  71. package/lib/dash/segment_list.js +1 -1
  72. package/lib/dash/segment_template.js +1 -1
  73. package/lib/hls/hls_parser.js +159 -29
  74. package/lib/media/closed_caption_parser.js +1 -2
  75. package/lib/media/drm_engine.js +169 -192
  76. package/lib/media/gap_jumping_controller.js +4 -2
  77. package/lib/media/media_source_engine.js +1 -2
  78. package/lib/media/segment_index.js +3 -1
  79. package/lib/media/segment_reference.js +19 -2
  80. package/lib/media/streaming_engine.js +8 -7
  81. package/lib/net/networking_engine.js +3 -1
  82. package/lib/offline/download_manager.js +1 -2
  83. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  84. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  85. package/lib/offline/manifest_converter.js +3 -1
  86. package/lib/offline/storage.js +12 -14
  87. package/lib/player.js +437 -167
  88. package/lib/polyfill/aria.js +82 -0
  89. package/lib/polyfill/media_capabilities.js +7 -1
  90. package/lib/polyfill/mediasource.js +6 -0
  91. package/lib/polyfill/orientation.js +1 -1
  92. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  93. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  94. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  95. package/lib/routing/walker.js +2 -3
  96. package/lib/text/mp4_ttml_parser.js +3 -0
  97. package/lib/text/srt_text_parser.js +4 -4
  98. package/lib/text/text_engine.js +16 -4
  99. package/lib/text/ttml_text_parser.js +5 -0
  100. package/lib/text/ui_text_displayer.js +85 -37
  101. package/lib/text/vtt_text_parser.js +2 -2
  102. package/lib/text/web_vtt_generator.js +55 -12
  103. package/lib/util/error.js +6 -0
  104. package/lib/util/fake_event.js +53 -7
  105. package/lib/util/manifest_parser_utils.js +1 -0
  106. package/lib/util/mp4_box_parsers.js +20 -20
  107. package/lib/util/multi_map.js +8 -0
  108. package/lib/util/periods.js +4 -1
  109. package/lib/util/platform.js +13 -1
  110. package/lib/util/player_configuration.js +13 -1
  111. package/lib/util/stream_utils.js +153 -85
  112. package/lib/util/xml_utils.js +31 -12
  113. package/package.json +1 -1
  114. package/roadmap.md +24 -22
  115. package/shaka-player.uncompiled.js +1 -0
  116. package/test/cast/cast_proxy_unit.js +12 -6
  117. package/test/cast/cast_utils_unit.js +2 -1
  118. package/test/cea/cea608_memory_unit.js +1 -1
  119. package/test/cea/cea708_service_unit.js +8 -8
  120. package/test/cea/cea708_window_unit.js +6 -6
  121. package/test/cea/cea_decoder_unit.js +1 -1
  122. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  123. package/test/dash/dash_parser_manifest_unit.js +230 -2
  124. package/test/hls/hls_parser_unit.js +255 -1
  125. package/test/media/closed_caption_parser_unit.js +6 -1
  126. package/test/media/drm_engine_integration.js +86 -12
  127. package/test/media/drm_engine_unit.js +353 -450
  128. package/test/offline/manifest_convert_unit.js +2 -0
  129. package/test/offline/storage_integration.js +9 -19
  130. package/test/player_integration.js +90 -0
  131. package/test/player_src_equals_integration.js +52 -0
  132. package/test/player_unit.js +11 -33
  133. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  134. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  135. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  136. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  137. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  138. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  139. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  140. package/test/test/assets/3675/dash_0.mpd +30 -0
  141. package/test/test/assets/3675/dash_1.mpd +30 -0
  142. package/test/test/assets/3675/dash_2.mpd +30 -0
  143. package/test/test/assets/3675/dash_3.mpd +30 -0
  144. package/test/test/assets/3675/dash_4.mpd +30 -0
  145. package/test/test/assets/3675/dash_5.mpd +26 -0
  146. package/test/test/assets/3675/init-stream0.m4s +0 -0
  147. package/test/test/assets/chapters.srt +11 -0
  148. package/test/test/assets/chapters.vtt +10 -0
  149. package/test/test/util/cea_utils.js +12 -12
  150. package/test/test/util/fake_ad_manager.js +8 -1
  151. package/test/test/util/manifest_generator.js +2 -0
  152. package/test/test/util/manifest_parser_util.js +3 -1
  153. package/test/text/ttml_text_parser_unit.js +9 -0
  154. package/test/text/ui_text_displayer_unit.js +29 -17
  155. package/test/text/web_vtt_generator_unit.js +81 -7
  156. package/test/ui/localization_unit.js +15 -14
  157. package/test/ui/text_displayer_layout_unit.js +13 -1
  158. package/test/util/stream_utils_unit.js +153 -10
  159. package/ui/airplay_button.js +1 -1
  160. package/ui/audio_language_selection.js +2 -5
  161. package/ui/cast_button.js +6 -9
  162. package/ui/constants.js +0 -3
  163. package/ui/controls.js +4 -6
  164. package/ui/fast_forward_button.js +2 -3
  165. package/ui/fullscreen_button.js +1 -3
  166. package/ui/language_utils.js +1 -1
  167. package/ui/less/containers.less +3 -7
  168. package/ui/locales/ru.json +6 -6
  169. package/ui/localization.js +28 -51
  170. package/ui/loop_button.js +1 -3
  171. package/ui/mute_button.js +1 -3
  172. package/ui/overflow_menu.js +2 -3
  173. package/ui/pip_button.js +7 -10
  174. package/ui/playback_rate_selection.js +3 -6
  175. package/ui/presentation_time.js +1 -3
  176. package/ui/resolution_selection.js +4 -7
  177. package/ui/rewind_button.js +2 -3
  178. package/ui/seek_bar.js +2 -4
  179. package/ui/spacer.js +1 -1
  180. package/ui/text_selection.js +8 -11
  181. package/ui/ui_utils.js +1 -1
  182. package/ui/volume_bar.js +1 -3
@@ -0,0 +1,82 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.polyfill.Aria');
8
+
9
+ goog.require('shaka.log');
10
+ goog.require('shaka.polyfill');
11
+
12
+ /**
13
+ * @summary A polyfill to add support for the ARIAMixin interface mixin, for
14
+ * browsers that do not implement it (e.g. Firefox).
15
+ * Note that IE also does not support ARIAMixin, but this polyfill does not work
16
+ * for that platform, as it relies on getters and setters.
17
+ * @see https://w3c.github.io/aria/#ARIAMixin
18
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Element
19
+ * @export
20
+ */
21
+ shaka.polyfill.Aria = class {
22
+ /**
23
+ * Install the polyfill if needed.
24
+ * @export
25
+ */
26
+ static install() {
27
+ // eslint-disable-next-line no-restricted-syntax
28
+ if (Object.getOwnPropertyDescriptor(Element.prototype, 'ariaHidden')) {
29
+ shaka.log.info('Using native ARIAMixin interface.');
30
+ return;
31
+ }
32
+ shaka.log.info('ARIAMixin interface not detected. Installing polyfill.');
33
+
34
+ // Define a list of all of the ARIAMixin properties that we have externs
35
+ // for.
36
+ const attributes = [
37
+ 'ariaHidden',
38
+ 'ariaLabel',
39
+ 'ariaPressed',
40
+ 'ariaSelected',
41
+ ];
42
+
43
+ // Add each attribute, one by one.
44
+ for (const attribute of attributes) {
45
+ shaka.polyfill.Aria.addARIAMixinAttribute_(attribute);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Adds an attribute with the given name.
51
+ * @param {string} name The name of the attribute, in camelCase.
52
+ * @private
53
+ */
54
+ static addARIAMixinAttribute_(name) {
55
+ const baseName = name.toLowerCase().replace(/^aria/, '');
56
+ // NOTE: All the attributes listed in the method above begin with "aria".
57
+ // However, to add extra protection against the possibility of XSS attacks
58
+ // through this method, this enforces "aria-" at the beginning of the
59
+ // snake-case name, even if somehow "aria" were missing from the input.
60
+ const snakeCaseName = `aria-${baseName}`;
61
+
62
+ /* eslint-disable no-restricted-syntax */
63
+ Object.defineProperty(Element.prototype, name, {
64
+ get() {
65
+ const element = /** @type {!Element} */ (this);
66
+ return element.getAttribute(snakeCaseName);
67
+ },
68
+ set(value) {
69
+ const element = /** @type {!Element} */ (this);
70
+ if (value == null || value == undefined) {
71
+ element.removeAttribute(snakeCaseName);
72
+ } else {
73
+ element.setAttribute(snakeCaseName, value);
74
+ }
75
+ },
76
+ });
77
+ /* eslint-enable no-restricted-syntax */
78
+ }
79
+ };
80
+
81
+
82
+ shaka.polyfill.register(shaka.polyfill.Aria.install);
@@ -34,8 +34,14 @@ shaka.polyfill.MediaCapabilities = class {
34
34
  // See: https://github.com/google/shaka-player/issues/3530
35
35
  // TODO: re-evaluate MediaCapabilities in the future versions of Apple
36
36
  // Browsers.
37
+ // Since MediaCapabilities implementation is buggy in PS5 browsers, we
38
+ // should always install polyfill for PS5 browsers.
39
+ // See: https://github.com/google/shaka-player/issues/3582
40
+ // TODO: re-evaluate MediaCapabilities in the future versions of PS5
41
+ // Browsers.
37
42
  if (!shaka.util.Platform.isChromecast() &&
38
43
  !shaka.util.Platform.isApple() &&
44
+ !shaka.util.Platform.isPS5() &&
39
45
  navigator.mediaCapabilities) {
40
46
  shaka.log.debug(
41
47
  'MediaCapabilities: Native mediaCapabilities support found.');
@@ -149,7 +155,7 @@ shaka.polyfill.MediaCapabilities = class {
149
155
  sessionTypes: mediaCapkeySystemConfig.sessionTypes,
150
156
  };
151
157
 
152
- // Only add the audio video capablities if they have valid data.
158
+ // Only add the audio video capabilities if they have valid data.
153
159
  // Otherwise the query will fail.
154
160
  if (audioCapabilities.length) {
155
161
  mediaKeySystemConfig.audioCapabilities = audioCapabilities;
@@ -208,6 +208,12 @@ shaka.polyfill.MediaSource = class {
208
208
  static patchVp09_() {
209
209
  const originalIsTypeSupported = MediaSource.isTypeSupported;
210
210
 
211
+ if (shaka.util.Platform.isWebOS()) {
212
+ // Don't do this on LG webOS as otherwise it is unable
213
+ // to play vp09 at all.
214
+ return;
215
+ }
216
+
211
217
  MediaSource.isTypeSupported = (mimeType) => {
212
218
  // Split the MIME type into its various parameters.
213
219
  const pieces = mimeType.split(/ *; */);
@@ -92,7 +92,7 @@ class extends shaka.util.FakeEventTarget {
92
92
 
93
93
  /** Dispatch a 'change' event. */
94
94
  dispatchChangeEvent() {
95
- const event = new shaka.util.FakeEvent('change', {});
95
+ const event = new shaka.util.FakeEvent('change');
96
96
  this.dispatchEvent(event);
97
97
  }
98
98
 
@@ -567,10 +567,10 @@ class extends shaka.util.FakeEventTarget {
567
567
 
568
568
  const isNew = this.keyStatuses.getStatus() == undefined;
569
569
 
570
- const event2 = new shaka.util.FakeEvent('message', {
571
- messageType: isNew ? 'license-request' : 'license-renewal',
572
- message: shaka.util.BufferUtils.toArrayBuffer(event.message),
573
- });
570
+ const data = new Map()
571
+ .set('messageType', isNew ? 'license-request' : 'license-renewal')
572
+ .set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
573
+ const event2 = new shaka.util.FakeEvent('message', data);
574
574
 
575
575
  this.dispatchEvent(event2);
576
576
  }
@@ -512,10 +512,10 @@ class extends shaka.util.FakeEventTarget {
512
512
 
513
513
  const isNew = this.keyStatuses.getStatus() == undefined;
514
514
 
515
- const event2 = new shaka.util.FakeEvent('message', {
516
- messageType: isNew ? 'license-request' : 'license-renewal',
517
- message: shaka.util.BufferUtils.toArrayBuffer(event.message),
518
- });
515
+ const data = new Map()
516
+ .set('messageType', isNew ? 'license-request' : 'license-renewal')
517
+ .set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
518
+ const event2 = new shaka.util.FakeEvent('message', data);
519
519
 
520
520
  this.dispatchEvent(event2);
521
521
  }
@@ -451,10 +451,10 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeys = class {
451
451
 
452
452
  const isNew = session.keyStatuses.getStatus() == undefined;
453
453
 
454
- const event2 = new shaka.util.FakeEvent('message', {
455
- messageType: isNew ? 'licenserequest' : 'licenserenewal',
456
- message: event.message,
457
- });
454
+ const data = new Map()
455
+ .set('messageType', isNew ? 'licenserequest' : 'licenserenewal')
456
+ .set('message', event.message);
457
+ const event2 = new shaka.util.FakeEvent('message', data);
458
458
 
459
459
  session.generated();
460
460
  session.dispatchEvent(event2);
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  goog.provide('shaka.routing.Walker');
8
- goog.provide('shaka.routing.Walker.Implementation');
9
8
 
10
9
  goog.require('goog.asserts');
11
10
  goog.require('shaka.routing.Node');
@@ -81,7 +80,7 @@ shaka.routing.Walker = class {
81
80
  * new work is added (and this is not null) it can be resolved. The only
82
81
  * time when this should be non-null is when we are waiting for more work.
83
82
  *
84
- * @private {shaka.util.PublicPromise}
83
+ * @private {?shaka.util.PublicPromise}
85
84
  */
86
85
  this.waitForWork_ = null;
87
86
 
@@ -91,7 +90,7 @@ shaka.routing.Walker = class {
91
90
  /** @private {?shaka.routing.Walker.ActiveRoute_} */
92
91
  this.currentRoute_ = null;
93
92
 
94
- /** @private {shaka.util.AbortableOperation} */
93
+ /** @private {?shaka.util.AbortableOperation} */
95
94
  this.currentStep_ = null;
96
95
 
97
96
  /**
@@ -88,6 +88,9 @@ shaka.text.Mp4TtmlParser = class {
88
88
 
89
89
  shaka.text.TextEngine.registerParser(
90
90
  'application/mp4; codecs="stpp"', () => new shaka.text.Mp4TtmlParser());
91
+ shaka.text.TextEngine.registerParser(
92
+ 'application/mp4; codecs="stpp.ttml"',
93
+ () => new shaka.text.Mp4TtmlParser());
91
94
  shaka.text.TextEngine.registerParser(
92
95
  'application/mp4; codecs="stpp.ttml.im1t"',
93
96
  () => new shaka.text.Mp4TtmlParser());
@@ -57,8 +57,8 @@ shaka.text.SrtTextParser = class {
57
57
  /**
58
58
  * Convert a SRT format to WebVTT
59
59
  *
60
- * @param {!string} data
61
- * @return {!string}
60
+ * @param {string} data
61
+ * @return {string}
62
62
  * @export
63
63
  */
64
64
  static srt2webvtt(data) {
@@ -87,8 +87,8 @@ shaka.text.SrtTextParser = class {
87
87
  /**
88
88
  * Convert a SRT cue into WebVTT cue
89
89
  *
90
- * @param {!string} caption
91
- * @return {!string}
90
+ * @param {string} caption
91
+ * @return {string}
92
92
  * @private
93
93
  */
94
94
  static convertSrtCue_(caption) {
@@ -12,7 +12,7 @@ goog.require('shaka.util.BufferUtils');
12
12
  goog.require('shaka.util.Functional');
13
13
  goog.require('shaka.util.IDestroyable');
14
14
  goog.require('shaka.util.MimeUtils');
15
- goog.requireType('shaka.cea.ICaptionDecoder.ClosedCaption');
15
+ goog.requireType('shaka.cea.ICaptionDecoder');
16
16
 
17
17
 
18
18
  // TODO: revisit this when Closure Compiler supports partially-exported classes.
@@ -24,7 +24,7 @@ goog.requireType('shaka.cea.ICaptionDecoder.ClosedCaption');
24
24
  shaka.text.TextEngine = class {
25
25
  /** @param {shaka.extern.TextDisplayer} displayer */
26
26
  constructor(displayer) {
27
- /** @private {shaka.extern.TextParser} */
27
+ /** @private {?shaka.extern.TextParser} */
28
28
  this.parser_ = null;
29
29
 
30
30
  /** @private {shaka.extern.TextDisplayer} */
@@ -345,6 +345,19 @@ shaka.text.TextEngine = class {
345
345
  return cues;
346
346
  }
347
347
 
348
+ /**
349
+ * @param {!shaka.text.Cue} cue the cue to apply the timestamp to recursively
350
+ * @param {number} videoTimestampOffset the timestamp offset of the video
351
+ * @private
352
+ */
353
+ applyVideoTimestampOffsetRecursive_(cue, videoTimestampOffset) {
354
+ cue.startTime += videoTimestampOffset;
355
+ cue.endTime += videoTimestampOffset;
356
+ for (const nested of cue.nestedCues) {
357
+ this.applyVideoTimestampOffsetRecursive_(nested, videoTimestampOffset);
358
+ }
359
+ }
360
+
348
361
  /**
349
362
  * Store the closed captions in the text engine, and append the cues to the
350
363
  * text displayer. This is a side-channel used for embedded text only.
@@ -373,8 +386,7 @@ shaka.text.TextEngine = class {
373
386
 
374
387
  // Adjust CEA captions with respect to the timestamp offset of the video
375
388
  // stream in which they were embedded.
376
- cue.startTime += videoTimestampOffset;
377
- cue.endTime += videoTimestampOffset;
389
+ this.applyVideoTimestampOffsetRecursive_(cue, videoTimestampOffset);
378
390
 
379
391
  const keepThisCue =
380
392
  cue.startTime >= this.appendWindowStart_ &&
@@ -199,6 +199,11 @@ shaka.text.TtmlTextParser = class {
199
199
  /** @type {Element} */
200
200
  let parentElement = /** @type {Element} */(cueNode.parentNode);
201
201
 
202
+ if (cueNode.nodeType == Node.COMMENT_NODE) {
203
+ // The comments do not contain information that interests us here.
204
+ return null;
205
+ }
206
+
202
207
  if (cueNode.nodeType == Node.TEXT_NODE) {
203
208
  // This should generate an "anonymous span" according to the TTML spec.
204
209
  // So pretend the element was a <span>. parentElement was set above, so
@@ -167,9 +167,14 @@ shaka.text.UITextDisplayer = class {
167
167
  }
168
168
 
169
169
  // Remove the cues out of the time range.
170
+ const oldNumCues = this.cues_.length;
170
171
  this.cues_ = this.cues_.filter(
171
172
  (cue) => cue.startTime < start || cue.endTime >= end);
172
- 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);
173
178
 
174
179
  return true;
175
180
  }
@@ -192,13 +197,13 @@ shaka.text.UITextDisplayer = class {
192
197
  }
193
198
 
194
199
  /**
195
- * @param {!Array.<!shaka.text.Cue>} cues
200
+ * @param {!Array.<!shaka.extern.Cue>} cues
196
201
  * @param {!HTMLElement} container
197
202
  * @param {number} currentTime
198
- * @param {boolean} isNested
203
+ * @param {!Array.<!shaka.extern.Cue>} parents
199
204
  * @private
200
205
  */
201
- updateCuesRecursive_(cues, container, currentTime, isNested) {
206
+ updateCuesRecursive_(cues, container, currentTime, parents) {
202
207
  // Set to true if the cues have changed in some way, which will require
203
208
  // DOM changes. E.g. if a cue was added or removed.
204
209
  let updateDOM = false;
@@ -214,10 +219,12 @@ shaka.text.UITextDisplayer = class {
214
219
  * The cues whose corresponding elements should be in the DOM.
215
220
  * Some of these might be new, some might have been displayed beforehand.
216
221
  * These will only be added if updateDOM is true.
217
- * @type {!Array.<!shaka.text.Cue>}
222
+ * @type {!Array.<!shaka.extern.Cue>}
218
223
  */
219
224
  const toPlant = [];
220
225
  for (const cue of cues) {
226
+ parents.push(cue);
227
+
221
228
  let cueRegistry = this.currentCuesMap_.get(cue);
222
229
  const shouldBeDisplayed =
223
230
  cue.startTime <= currentTime && cue.endTime > currentTime;
@@ -241,7 +248,7 @@ shaka.text.UITextDisplayer = class {
241
248
  toPlant.push(cue);
242
249
  if (!cueRegistry) {
243
250
  // The cue has to be made!
244
- this.createCue_(cue, isNested);
251
+ this.createCue_(cue, parents);
245
252
  cueRegistry = this.currentCuesMap_.get(cue);
246
253
  wrapper = cueRegistry.wrapper;
247
254
  updateDOM = true;
@@ -255,8 +262,11 @@ shaka.text.UITextDisplayer = class {
255
262
  // guaranteed that the children will neither need to be added nor removed.
256
263
  if (cue.nestedCues.length > 0 && wrapper) {
257
264
  this.updateCuesRecursive_(
258
- cue.nestedCues, wrapper, currentTime, /* isNested= */ true);
265
+ cue.nestedCues, wrapper, currentTime, parents);
259
266
  }
267
+
268
+ const topCue = parents.pop();
269
+ goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
260
270
  }
261
271
  if (updateDOM) {
262
272
  for (const cueElement of toUproot) {
@@ -304,7 +314,7 @@ shaka.text.UITextDisplayer = class {
304
314
 
305
315
  // Update the cues.
306
316
  this.updateCuesRecursive_(
307
- this.cues_, this.textContainer_, currentTime, false);
317
+ this.cues_, this.textContainer_, currentTime, /* parents= */ []);
308
318
 
309
319
  if (goog.DEBUG) {
310
320
  // Previously, we had an issue (#2076) where cues sometimes were not
@@ -327,10 +337,11 @@ shaka.text.UITextDisplayer = class {
327
337
  * Creates the object for a cue.
328
338
  *
329
339
  * @param {!shaka.extern.Cue} cue
330
- * @param {boolean} isNested
340
+ * @param {!Array.<!shaka.extern.Cue>} parents
331
341
  * @private
332
342
  */
333
- createCue_(cue, isNested) {
343
+ createCue_(cue, parents) {
344
+ const isNested = parents.length > 1;
334
345
  let type = isNested ? 'span' : 'div';
335
346
  if (cue.lineBreak || cue.spacer) {
336
347
  if (cue.spacer) {
@@ -344,7 +355,7 @@ shaka.text.UITextDisplayer = class {
344
355
  // Nested cues are inline elements. Top-level cues are block elements.
345
356
  const cueElement = shaka.util.Dom.createHTMLElement(type);
346
357
  if (type != 'br') {
347
- this.setCaptionStyles_(cueElement, cue, isNested);
358
+ this.setCaptionStyles_(cueElement, cue, parents);
348
359
  }
349
360
 
350
361
  let wrapper = cueElement;
@@ -363,13 +374,16 @@ shaka.text.UITextDisplayer = class {
363
374
  /**
364
375
  * @param {!HTMLElement} cueElement
365
376
  * @param {!shaka.extern.Cue} cue
366
- * @param {boolean} isNested
377
+ * @param {!Array.<!shaka.extern.Cue>} parents
367
378
  * @private
368
379
  */
369
- setCaptionStyles_(cueElement, cue, isNested) {
380
+ setCaptionStyles_(cueElement, cue, parents) {
370
381
  const Cue = shaka.text.Cue;
371
- let style = cueElement.style;
382
+ const inherit =
383
+ (cb) => shaka.text.UITextDisplayer.inheritProperty_(parents, cb);
384
+ const style = cueElement.style;
372
385
  const isLeaf = cue.nestedCues.length == 0;
386
+ const isNested = parents.length > 1;
373
387
 
374
388
  // TODO: wrapLine is not yet supported. Lines always wrap.
375
389
 
@@ -388,16 +402,6 @@ shaka.text.UITextDisplayer = class {
388
402
  const nonBreakingSpace = '\xa0';
389
403
  return nonBreakingSpace.repeat(match.length);
390
404
  });
391
- if (isNested) {
392
- cueElement.textContent = text;
393
- } else if (text.length) {
394
- // If a top-level cue has text, move to a <span> so the background is
395
- // styled correctly.
396
- const span = shaka.util.Dom.createHTMLElement('span');
397
- span.textContent = text;
398
- cueElement.appendChild(span);
399
- style = span.style;
400
- }
401
405
 
402
406
  style.backgroundColor = cue.backgroundColor;
403
407
  style.border = cue.border;
@@ -406,20 +410,40 @@ shaka.text.UITextDisplayer = class {
406
410
  style.opacity = cue.opacity;
407
411
  style.paddingLeft = shaka.text.UITextDisplayer.convertLengthValue_(
408
412
  cue.linePadding, cue, this.videoContainer_);
409
- style.paddingRight = shaka.text.UITextDisplayer.convertLengthValue_(
410
- cue.linePadding, cue, this.videoContainer_);
413
+ style.paddingRight =
414
+ shaka.text.UITextDisplayer.convertLengthValue_(
415
+ cue.linePadding, cue, this.videoContainer_);
411
416
 
412
417
  if (cue.backgroundImage) {
413
418
  style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
414
419
  style.backgroundRepeat = 'no-repeat';
415
420
  style.backgroundSize = 'contain';
416
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
+ }
417
433
 
418
- if (cue.backgroundColor == '') {
419
- // In text-based cues, background color can default in CSS.
420
- // In bitmap-based cues, we default to a transparent background color,
421
- // so that the bitmap can be the only background.
422
- 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;
423
447
  }
424
448
  }
425
449
 
@@ -472,20 +496,28 @@ shaka.text.UITextDisplayer = class {
472
496
  // TODO: Implement lineAlignment of 'CENTER'.
473
497
  if (cue.line) {
474
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.
475
502
  style.position = 'absolute';
503
+ style.left = '0';
504
+ style.top = '0';
476
505
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
506
+ style.width = '100%';
477
507
  if (cue.lineAlign == Cue.lineAlign.START) {
478
508
  style.top = cue.line + '%';
479
509
  } else if (cue.lineAlign == Cue.lineAlign.END) {
480
510
  style.bottom = cue.line + '%';
481
511
  }
482
512
  } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
513
+ style.height = '100%';
483
514
  if (cue.lineAlign == Cue.lineAlign.START) {
484
515
  style.left = cue.line + '%';
485
516
  } else if (cue.lineAlign == Cue.lineAlign.END) {
486
517
  style.right = cue.line + '%';
487
518
  }
488
519
  } else {
520
+ style.height = '100%';
489
521
  if (cue.lineAlign == Cue.lineAlign.START) {
490
522
  style.right = cue.line + '%';
491
523
  } else if (cue.lineAlign == Cue.lineAlign.END) {
@@ -493,12 +525,7 @@ shaka.text.UITextDisplayer = class {
493
525
  }
494
526
  }
495
527
  }
496
- } else if (cue.region && cue.region.id &&
497
- ((!isNested && !isLeaf) || (cue.backgroundImage))) {
498
- // In text-base cues, regions are only applied to block container
499
- // (!isNested && !isLeaf).
500
- // In bitmap-based cues, region settings are used to specify the size and
501
- // position of the backgroundImage.
528
+ } else if (cue.region && cue.region.id) {
502
529
  const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
503
530
  const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
504
531
  const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
@@ -631,4 +658,25 @@ shaka.text.UITextDisplayer = class {
631
658
 
632
659
  return (containerHeight * value / cue.cellResolution.rows) + 'px';
633
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
+ }
634
682
  };
@@ -464,8 +464,8 @@ shaka.text.VttTextParser = class {
464
464
  }
465
465
 
466
466
  /**
467
- * @param {!string} value
468
- * @param {!string} defaultValue
467
+ * @param {string} value
468
+ * @param {string} defaultValue
469
469
  * @private
470
470
  */
471
471
  static getOrDefault_(value, defaultValue) {
@@ -17,9 +17,10 @@ goog.require('shaka.text.Cue');
17
17
  shaka.text.WebVttGenerator = class {
18
18
  /**
19
19
  * @param {!Array.<!shaka.text.Cue>} cues
20
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
20
21
  * @return {string}
21
22
  */
22
- static convert(cues) {
23
+ static convert(cues, adCuePoints) {
23
24
  // Flatten nested cue payloads recursively. If a cue has nested cues,
24
25
  // their contents should be combined and replace the payload of the parent.
25
26
  const flattenPayload = (cue) => {
@@ -64,6 +65,25 @@ shaka.text.WebVttGenerator = class {
64
65
  }
65
66
  };
66
67
 
68
+ const webvttTimeString = (time) => {
69
+ let newTime = time;
70
+ for (const adCuePoint of adCuePoints) {
71
+ if (adCuePoint.end && adCuePoint.start < time) {
72
+ const offset = adCuePoint.end - adCuePoint.start;
73
+ newTime += offset;
74
+ }
75
+ }
76
+ const hours = Math.floor(newTime / 3600);
77
+ const minutes = Math.floor(newTime / 60 % 60);
78
+ const seconds = Math.floor(newTime % 60);
79
+ const milliseconds = Math.floor(newTime * 1000 % 1000);
80
+ return (hours < 10 ? '0' : '') + hours + ':' +
81
+ (minutes < 10 ? '0' : '') + minutes + ':' +
82
+ (seconds < 10 ? '0' : '') + seconds + '.' +
83
+ (milliseconds < 100 ? (milliseconds < 10 ? '00' : '0') : '') +
84
+ milliseconds;
85
+ };
86
+
67
87
  // We don't want to modify the array or objects passed in, since we don't
68
88
  // technically own them. So we build a new array and replace certain items
69
89
  // in it if they need to be flattened.
@@ -80,19 +100,42 @@ shaka.text.WebVttGenerator = class {
80
100
 
81
101
  let webvttString = 'WEBVTT\n\n';
82
102
  for (const cue of flattenedCues) {
83
- const webvttTimeString = (time) => {
84
- const hours = Math.floor(time / 3600);
85
- const minutes = Math.floor(time / 60 % 60);
86
- const seconds = Math.floor(time % 60);
87
- const milliseconds = Math.floor(time * 1000 % 1000);
88
- return (hours < 10 ? '0' : '') + hours + ':' +
89
- (minutes < 10 ? '0' : '') + minutes + ':' +
90
- (seconds < 10 ? '0' : '') + seconds + '.' +
91
- (milliseconds < 100 ? (milliseconds < 10 ? '00' : '0') : '') +
92
- milliseconds;
103
+ const webvttSettings = (cue) => {
104
+ const settings = [];
105
+ const Cue = shaka.text.Cue;
106
+ switch (cue.textAlign) {
107
+ case Cue.textAlign.LEFT:
108
+ settings.push('align:left');
109
+ break;
110
+ case Cue.textAlign.RIGHT:
111
+ settings.push('align:right');
112
+ break;
113
+ case Cue.textAlign.CENTER:
114
+ settings.push('align:middle');
115
+ break;
116
+ case Cue.textAlign.START:
117
+ settings.push('align:start');
118
+ break;
119
+ case Cue.textAlign.END:
120
+ settings.push('align:end');
121
+ break;
122
+ }
123
+ switch (cue.writingMode) {
124
+ case Cue.writingMode.VERTICAL_LEFT_TO_RIGHT:
125
+ settings.push('vertical:lr');
126
+ break;
127
+ case Cue.writingMode.VERTICAL_RIGHT_TO_LEFT:
128
+ settings.push('vertical:rl');
129
+ break;
130
+ }
131
+
132
+ if (settings.length) {
133
+ return ' ' + settings.join(' ');
134
+ }
135
+ return '';
93
136
  };
94
137
  webvttString += webvttTimeString(cue.startTime) + ' --> ' +
95
- webvttTimeString(cue.endTime) + '\n';
138
+ webvttTimeString(cue.endTime) + webvttSettings(cue) + '\n';
96
139
  webvttString += cue.payload + '\n\n';
97
140
  }
98
141
  return webvttString;
package/lib/util/error.js CHANGED
@@ -814,6 +814,12 @@ shaka.util.Error.Code = {
814
814
  */
815
815
  'INIT_DATA_TRANSFORM_ERROR': 6016,
816
816
 
817
+ /**
818
+ * The server certificate request failed.
819
+ * <br> error.data[0] is a shaka.util.Error from the networking engine.
820
+ */
821
+ 'SERVER_CERTIFICATE_REQUEST_FAILED': 6017,
822
+
817
823
 
818
824
  /**
819
825
  * The call to Player.load() was interrupted by a call to Player.unload()