shaka-player 3.2.1 → 3.3.1

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 (213) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -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);
@@ -0,0 +1,52 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ goog.provide('shaka.polyfill.RandomUUID');
7
+
8
+ goog.require('shaka.log');
9
+ goog.require('shaka.polyfill');
10
+
11
+ /**
12
+ * @summary A polyfill to provide window.crypto.randomUUID in all browsers.
13
+ * @export
14
+ */
15
+ shaka.polyfill.RandomUUID = class {
16
+ /**
17
+ * Install the polyfill if needed.
18
+ * @export
19
+ */
20
+ static install() {
21
+ shaka.log.debug('randomUUID.install');
22
+
23
+ if (!window.crypto) {
24
+ // See: https://caniuse.com/cryptography
25
+ shaka.log.debug(
26
+ 'window.crypto must be available to install randomUUID polyfill.');
27
+ return;
28
+ }
29
+
30
+ if ('randomUUID' in window.crypto) {
31
+ shaka.log.debug(
32
+ 'RandomUUID: Native window.crypto.randomUUID() support found.');
33
+ return;
34
+ }
35
+
36
+ window.crypto.randomUUID = shaka.polyfill.RandomUUID.randomUUID_;
37
+ }
38
+
39
+ /**
40
+ * @return {string}
41
+ * @private
42
+ */
43
+ static randomUUID_() {
44
+ const url = URL.createObjectURL(new Blob());
45
+ const uuid = url.toString();
46
+ URL.revokeObjectURL(url);
47
+ return uuid.substr(uuid.lastIndexOf('/') + 1);
48
+ }
49
+ };
50
+
51
+
52
+ shaka.polyfill.register(shaka.polyfill.RandomUUID.install);
package/lib/text/cue.js CHANGED
@@ -144,6 +144,18 @@ shaka.text.Cue = class {
144
144
  */
145
145
  this.border = '';
146
146
 
147
+ /**
148
+ * @override
149
+ * @exportInterface
150
+ */
151
+ this.textStrokeColor = '';
152
+
153
+ /**
154
+ * @override
155
+ * @exportInterface
156
+ */
157
+ this.textStrokeWidth = '';
158
+
147
159
  /**
148
160
  * @override
149
161
  * @exportInterface
@@ -210,6 +222,12 @@ shaka.text.Cue = class {
210
222
  */
211
223
  this.nestedCues = [];
212
224
 
225
+ /**
226
+ * @override
227
+ * @exportInterface
228
+ */
229
+ this.isContainer = false;
230
+
213
231
  /**
214
232
  * @override
215
233
  * @exportInterface
@@ -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());
@@ -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_ &&
@@ -86,16 +86,17 @@ shaka.text.TtmlTextParser = class {
86
86
  }
87
87
 
88
88
  // Get the framerate, subFrameRate and frameRateMultiplier if applicable.
89
- const frameRate = XmlUtils.getAttributeNS(tt, ttpNs, 'frameRate');
90
- const subFrameRate = XmlUtils.getAttributeNS(tt, ttpNs, 'subFrameRate');
89
+ const frameRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRate');
90
+ const subFrameRate = XmlUtils.getAttributeNSList(
91
+ tt, ttpNs, 'subFrameRate');
91
92
  const frameRateMultiplier =
92
- XmlUtils.getAttributeNS(tt, ttpNs, 'frameRateMultiplier');
93
- const tickRate = XmlUtils.getAttributeNS(tt, ttpNs, 'tickRate');
93
+ XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRateMultiplier');
94
+ const tickRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'tickRate');
94
95
 
95
- const cellResolution = XmlUtils.getAttributeNS(
96
+ const cellResolution = XmlUtils.getAttributeNSList(
96
97
  tt, ttpNs, 'cellResolution');
97
98
  const spaceStyle = tt.getAttribute('xml:space') || 'default';
98
- const extent = XmlUtils.getAttributeNS(tt, ttsNs, 'extent');
99
+ const extent = XmlUtils.getAttributeNSList(tt, ttsNs, 'extent');
99
100
 
100
101
  if (spaceStyle != 'default' && spaceStyle != 'preserve') {
101
102
  throw new shaka.util.Error(
@@ -147,28 +148,15 @@ shaka.text.TtmlTextParser = class {
147
148
  shaka.util.Error.Code.INVALID_TEXT_CUE,
148
149
  '<span> can only be inside <p> in TTML');
149
150
  }
151
+ }
150
152
 
151
- const pChildren = XmlUtils.findChildren(div, 'p');
152
- if (pChildren && pChildren.length) {
153
- for (const p of pChildren) {
154
- const cue = TtmlTextParser.parseCue_(
155
- p, time.periodStart, rateInfo, metadataElements, styles,
156
- regionElements, cueRegions, whitespaceTrim,
157
- /* isNested= */ false, cellResolutionInfo);
158
- if (cue) {
159
- cues.push(cue);
160
- }
161
- }
162
- } else {
163
- // Only used for parsing the background image
164
- const cue = TtmlTextParser.parseCue_(
165
- div, time.periodStart, rateInfo, metadataElements, styles,
166
- regionElements, cueRegions, whitespaceTrim,
167
- /* isNested= */ false, cellResolutionInfo);
168
- if (cue) {
169
- cues.push(cue);
170
- }
171
- }
153
+ const cue = TtmlTextParser.parseCue_(
154
+ body, time.periodStart, rateInfo, metadataElements, styles,
155
+ regionElements, cueRegions, whitespaceTrim,
156
+ cellResolutionInfo, /* parentCueElement= */ null,
157
+ /* isContent= */ false);
158
+ if (cue) {
159
+ cues.push(cue);
172
160
  }
173
161
  }
174
162
 
@@ -186,20 +174,32 @@ shaka.text.TtmlTextParser = class {
186
174
  * @param {!Array.<!Element>} regionElements
187
175
  * @param {!Array.<!shaka.text.CueRegion>} cueRegions
188
176
  * @param {boolean} whitespaceTrim
189
- * @param {boolean} isNested
190
177
  * @param {?{columns: number, rows: number}} cellResolution
178
+ * @param {?Element} parentCueElement
179
+ * @param {boolean} isContent
191
180
  * @return {shaka.text.Cue}
192
181
  * @private
193
182
  */
194
183
  static parseCue_(
195
184
  cueNode, offset, rateInfo, metadataElements, styles, regionElements,
196
- cueRegions, whitespaceTrim, isNested, cellResolution) {
185
+ cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent) {
197
186
  /** @type {Element} */
198
187
  let cueElement;
199
188
  /** @type {Element} */
200
- let parentElement = /** @type {Element} */(cueNode.parentNode);
189
+ let parentElement = /** @type {Element} */ (cueNode.parentNode);
190
+
191
+ if (cueNode.nodeType == Node.COMMENT_NODE) {
192
+ // The comments do not contain information that interests us here.
193
+ return null;
194
+ }
201
195
 
202
196
  if (cueNode.nodeType == Node.TEXT_NODE) {
197
+ if (!isContent) {
198
+ // Ignore text elements outside the content. For example, whitespace
199
+ // on the same lexical level as the <p> elements, in a document with
200
+ // xml:space="preserve", should not be renderer.
201
+ return null;
202
+ }
203
203
  // This should generate an "anonymous span" according to the TTML spec.
204
204
  // So pretend the element was a <span>. parentElement was set above, so
205
205
  // we should still be able to correctly traverse up for timing
@@ -214,6 +214,21 @@ shaka.text.TtmlTextParser = class {
214
214
  }
215
215
  goog.asserts.assert(cueElement, 'cueElement should be non-null!');
216
216
 
217
+ let imageElement = null;
218
+ for (const nameSpace of shaka.text.TtmlTextParser.smpteNsList_) {
219
+ imageElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
220
+ cueElement, 'backgroundImage', metadataElements, '#',
221
+ nameSpace)[0];
222
+ if (imageElement) {
223
+ break;
224
+ }
225
+ }
226
+
227
+ const parentIsContent = isContent;
228
+ if (cueNode.nodeName == 'p' || imageElement) {
229
+ isContent = true;
230
+ }
231
+
217
232
  const spaceStyle = cueElement.getAttribute('xml:space') ||
218
233
  (whitespaceTrim ? 'default' : 'preserve');
219
234
 
@@ -238,8 +253,9 @@ shaka.text.TtmlTextParser = class {
238
253
  regionElements,
239
254
  cueRegions,
240
255
  localWhitespaceTrim,
241
- /* isNested= */ true,
242
256
  cellResolution,
257
+ cueElement,
258
+ isContent,
243
259
  );
244
260
 
245
261
  // This node may or may not generate a nested cue.
@@ -249,6 +265,8 @@ shaka.text.TtmlTextParser = class {
249
265
  }
250
266
  }
251
267
 
268
+ const isNested = /** @type {boolean} */ (parentCueElement != null);
269
+
252
270
  // In this regex, "\S" means "non-whitespace character".
253
271
  const hasTextContent = /\S/.test(cueElement.textContent);
254
272
  const hasTimeAttributes =
@@ -329,6 +347,13 @@ shaka.text.TtmlTextParser = class {
329
347
  const cue = new shaka.text.Cue(start, end, payload);
330
348
  cue.nestedCues = nestedCues;
331
349
 
350
+ if (!isContent) {
351
+ // If this is not a <p> element or a <div> with images, and it has no
352
+ // parent that was a <p> element, then it's part of the outer containers
353
+ // (e.g. the <body> or a normal <div> element within it).
354
+ cue.isContainer = true;
355
+ }
356
+
332
357
  if (cellResolution) {
333
358
  cue.cellResolution = cellResolution;
334
359
  }
@@ -336,31 +361,32 @@ shaka.text.TtmlTextParser = class {
336
361
  // Get other properties if available.
337
362
  const regionElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
338
363
  cueElement, 'region', regionElements, /* prefix= */ '')[0];
339
- if (regionElement && regionElement.getAttribute('xml:id')) {
340
- const regionId = regionElement.getAttribute('xml:id');
341
- cue.region = cueRegions.filter((region) => region.id == regionId)[0];
342
- }
343
-
344
- let imageElement = null;
345
- for (const nameSpace of shaka.text.TtmlTextParser.smpteNsList_) {
346
- imageElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
347
- cueElement, 'backgroundImage', metadataElements, '#',
348
- nameSpace)[0];
349
- if (imageElement) {
350
- break;
364
+ // Do not actually apply that region unless it is non-inherited, though.
365
+ // This makes it so that, if a parent element has a region, the children
366
+ // don't also all independently apply the positioning of that region.
367
+ if (cueElement.hasAttribute('region')) {
368
+ if (regionElement && regionElement.getAttribute('xml:id')) {
369
+ const regionId = regionElement.getAttribute('xml:id');
370
+ cue.region = cueRegions.filter((region) => region.id == regionId)[0];
351
371
  }
352
372
  }
353
373
 
354
- const isLeaf = nestedCues.length == 0;
374
+ let regionElementForStyle = regionElement;
375
+ if (parentCueElement && isNested && !cueElement.getAttribute('region') &&
376
+ !cueElement.getAttribute('style')) {
377
+ regionElementForStyle =
378
+ shaka.text.TtmlTextParser.getElementsFromCollection_(
379
+ parentCueElement, 'region', regionElements, /* prefix= */ '')[0];
380
+ }
355
381
 
356
382
  shaka.text.TtmlTextParser.addStyle_(
357
383
  cue,
358
384
  cueElement,
359
- regionElement,
385
+ regionElementForStyle,
360
386
  imageElement,
361
387
  styles,
362
- isNested,
363
- isLeaf);
388
+ /** isNested= */ parentIsContent, // "nested in a <div>" doesn't count.
389
+ /** isLeaf= */ (nestedCues.length == 0));
364
390
 
365
391
  return cue;
366
392
  }
@@ -500,14 +526,13 @@ shaka.text.TtmlTextParser = class {
500
526
  }
501
527
 
502
528
  const align = TtmlTextParser.getStyleAttribute_(
503
- cueElement, region, styles, 'textAlign', shouldInheritRegionStyles);
529
+ cueElement, region, styles, 'textAlign', true);
504
530
  if (align) {
505
531
  cue.positionAlign = TtmlTextParser.textAlignToPositionAlign_[align];
506
532
  cue.lineAlign = TtmlTextParser.textAlignToLineAlign_[align];
507
533
 
508
534
  goog.asserts.assert(align.toUpperCase() in Cue.textAlign,
509
- align.toUpperCase() +
510
- ' Should be in Cue.textAlign values!');
535
+ align.toUpperCase() + ' Should be in Cue.textAlign values!');
511
536
 
512
537
  cue.textAlign = Cue.textAlign[align.toUpperCase()];
513
538
  } else {
@@ -516,7 +541,7 @@ shaka.text.TtmlTextParser = class {
516
541
  }
517
542
 
518
543
  const displayAlign = TtmlTextParser.getStyleAttribute_(
519
- cueElement, region, styles, 'displayAlign', shouldInheritRegionStyles);
544
+ cueElement, region, styles, 'displayAlign', true);
520
545
  if (displayAlign) {
521
546
  goog.asserts.assert(displayAlign.toUpperCase() in Cue.displayAlign,
522
547
  displayAlign.toUpperCase() +
@@ -526,7 +551,6 @@ shaka.text.TtmlTextParser = class {
526
551
 
527
552
  const color = TtmlTextParser.getStyleAttribute_(
528
553
  cueElement, region, styles, 'color', shouldInheritRegionStyles);
529
-
530
554
  if (color) {
531
555
  cue.color = color;
532
556
  }
@@ -579,7 +603,6 @@ shaka.text.TtmlTextParser = class {
579
603
 
580
604
  const fontSize = TtmlTextParser.getStyleAttribute_(
581
605
  cueElement, region, styles, 'fontSize', shouldInheritRegionStyles);
582
-
583
606
  if (fontSize) {
584
607
  const isValidFontSizeUnit =
585
608
  fontSize.match(TtmlTextParser.unitValues_) ||
@@ -617,6 +640,31 @@ shaka.text.TtmlTextParser = class {
617
640
  }
618
641
  }
619
642
 
643
+ const textOutline = TtmlTextParser.getStyleAttribute_(
644
+ cueElement, region, styles, 'textOutline', shouldInheritRegionStyles);
645
+ if (textOutline) {
646
+ // tts:textOutline isn't natively supported by browsers, but it can be
647
+ // mostly replicated using the non-standard -webkit-text-stroke-width and
648
+ // -webkit-text-stroke-color properties.
649
+ const split = textOutline.split(' ');
650
+ if (split[0].match(TtmlTextParser.unitValues_)) {
651
+ // There is no defined color, so default to the text color.
652
+ cue.textStrokeColor = cue.color;
653
+ } else {
654
+ cue.textStrokeColor = split[0];
655
+ split.shift();
656
+ }
657
+ if (split[0] && split[0].match(TtmlTextParser.unitValues_)) {
658
+ cue.textStrokeWidth = split[0];
659
+ } else {
660
+ // If there is no width, or the width is not a number, don't draw a
661
+ // border.
662
+ cue.textStrokeColor = '';
663
+ }
664
+ // There is an optional blur radius also, but we have no way of
665
+ // replicating that, so ignore it.
666
+ }
667
+
620
668
  const letterSpacing = TtmlTextParser.getStyleAttribute_(
621
669
  cueElement, region, styles, 'letterSpacing', shouldInheritRegionStyles);
622
670
  if (letterSpacing && letterSpacing.match(TtmlTextParser.unitValues_)) {
@@ -748,7 +796,7 @@ shaka.text.TtmlTextParser = class {
748
796
  return null;
749
797
  }
750
798
 
751
- const attr = XmlUtils.getAttributeNS(region, ttsNs, attribute);
799
+ const attr = XmlUtils.getAttributeNSList(region, ttsNs, attribute);
752
800
  if (attr) {
753
801
  return attr;
754
802
  }
@@ -773,7 +821,7 @@ shaka.text.TtmlTextParser = class {
773
821
 
774
822
  // Styling on elements should take precedence
775
823
  // over the main styling attributes
776
- const elementAttribute = XmlUtils.getAttributeNS(
824
+ const elementAttribute = XmlUtils.getAttributeNSList(
777
825
  cueElement,
778
826
  ttsNs,
779
827
  attribute);
@@ -816,7 +864,7 @@ shaka.text.TtmlTextParser = class {
816
864
 
817
865
  if (!styleAttributeValue) {
818
866
  // Fall back to tts namespace.
819
- styleAttributeValue = XmlUtils.getAttributeNS(
867
+ styleAttributeValue = XmlUtils.getAttributeNSList(
820
868
  inheritedStyles[i],
821
869
  ttsNs,
822
870
  attribute);
@@ -1271,20 +1319,26 @@ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
1271
1319
  * document, not just "ttp:", so we use this with getAttributeNS() to ensure
1272
1320
  * that we support arbitrary namespace names.
1273
1321
  *
1274
- * @const {string}
1322
+ * @const {!Array.<string>}
1275
1323
  * @private
1276
1324
  */
1277
- shaka.text.TtmlTextParser.parameterNs_ = 'http://www.w3.org/ns/ttml#parameter';
1325
+ shaka.text.TtmlTextParser.parameterNs_ = [
1326
+ 'http://www.w3.org/ns/ttml#parameter',
1327
+ 'http://www.w3.org/2006/10/ttaf1#parameter',
1328
+ ];
1278
1329
 
1279
1330
  /**
1280
1331
  * The namespace URL for TTML styles. Can be assigned any name in the TTML
1281
1332
  * document, not just "tts:", so we use this with getAttributeNS() to ensure
1282
1333
  * that we support arbitrary namespace names.
1283
1334
  *
1284
- * @const {string}
1335
+ * @const {!Array.<string>}
1285
1336
  * @private
1286
1337
  */
1287
- shaka.text.TtmlTextParser.styleNs_ = 'http://www.w3.org/ns/ttml#styling';
1338
+ shaka.text.TtmlTextParser.styleNs_ = [
1339
+ 'http://www.w3.org/ns/ttml#styling',
1340
+ 'http://www.w3.org/2006/10/ttaf1#styling',
1341
+ ];
1288
1342
 
1289
1343
  /**
1290
1344
  * The namespace URL for EBU TTML styles. Can be assigned any name in the TTML