shaka-player 3.2.2 → 3.3.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 (188) 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/.github/workflows/build_and_test.yaml +73 -0
  5. package/.github/workflows/release.yaml +53 -0
  6. package/AUTHORS +6 -5
  7. package/CHANGELOG.md +314 -1
  8. package/CONTRIBUTORS +4 -2
  9. package/build/checkversion.py +1 -1
  10. package/build/conformance.textproto +183 -169
  11. package/build/test.py +6 -0
  12. package/build/types/core +3 -0
  13. package/build/types/offline +1 -0
  14. package/build/types/polyfill +1 -0
  15. package/build/types/ui +2 -0
  16. package/demo/common/assets.js +18 -0
  17. package/demo/common/message_ids.js +13 -1
  18. package/demo/config.js +31 -1
  19. package/demo/index.html +1 -1
  20. package/demo/locales/en.json +12 -0
  21. package/demo/locales/source.json +48 -0
  22. package/dist/controls.css +26 -2
  23. package/dist/controls.css.map +1 -1
  24. package/dist/demo.compiled.debug.js +19 -16
  25. package/dist/demo.compiled.debug.map +3 -3
  26. package/dist/demo.compiled.js +19 -16
  27. package/dist/demo.compiled.map +3 -3
  28. package/dist/deps.js +21 -14
  29. package/dist/locales.js +31 -29
  30. package/dist/receiver.compiled.debug.js +11 -9
  31. package/dist/receiver.compiled.debug.map +3 -3
  32. package/dist/receiver.compiled.js +11 -9
  33. package/dist/receiver.compiled.map +3 -3
  34. package/dist/shaka-player.compiled.d.ts +232 -18
  35. package/dist/shaka-player.compiled.debug.d.ts +232 -18
  36. package/dist/shaka-player.compiled.debug.externs.js +293 -17
  37. package/dist/shaka-player.compiled.debug.js +481 -422
  38. package/dist/shaka-player.compiled.debug.map +4 -4
  39. package/dist/shaka-player.compiled.externs.js +293 -17
  40. package/dist/shaka-player.compiled.js +846 -817
  41. package/dist/shaka-player.compiled.map +4 -4
  42. package/dist/shaka-player.ui.d.ts +250 -30
  43. package/dist/shaka-player.ui.debug.d.ts +250 -30
  44. package/dist/shaka-player.ui.debug.externs.js +322 -28
  45. package/dist/shaka-player.ui.debug.js +630 -553
  46. package/dist/shaka-player.ui.debug.map +4 -4
  47. package/dist/shaka-player.ui.externs.js +322 -28
  48. package/dist/shaka-player.ui.js +1048 -1006
  49. package/dist/shaka-player.ui.map +4 -4
  50. package/docs/design/bg-fetch-after.gv +29 -0
  51. package/docs/design/bg-fetch-after.gv.png +0 -0
  52. package/docs/design/bg-fetch-before.gv +18 -0
  53. package/docs/design/bg-fetch-before.gv.png +0 -0
  54. package/docs/design/bg-fetch.md +134 -0
  55. package/docs/tutorials/blob-url.md +15 -0
  56. package/docs/tutorials/index.json +1 -0
  57. package/docs/tutorials/ui-customization.md +66 -0
  58. package/docs/tutorials/ui.md +5 -5
  59. package/externs/cmcd.js +186 -0
  60. package/externs/ima.js +3 -0
  61. package/externs/shaka/ads.js +5 -0
  62. package/externs/shaka/manifest.js +12 -1
  63. package/externs/shaka/manifest_parser.js +10 -1
  64. package/externs/shaka/net.js +20 -4
  65. package/externs/shaka/offline.js +31 -2
  66. package/externs/shaka/player.js +138 -5
  67. package/externs/shaka/text.js +24 -0
  68. package/externs/webcrypto.js +18 -0
  69. package/karma.conf.js +19 -15
  70. package/lib/abr/ewma.js +14 -0
  71. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  72. package/lib/abr/simple_abr_manager.js +6 -0
  73. package/lib/ads/ad_manager.js +1 -0
  74. package/lib/ads/client_side_ad.js +8 -0
  75. package/lib/ads/client_side_ad_manager.js +8 -4
  76. package/lib/ads/server_side_ad.js +7 -0
  77. package/lib/cast/cast_proxy.js +3 -0
  78. package/lib/cast/cast_receiver.js +3 -0
  79. package/lib/cast/cast_utils.js +1 -0
  80. package/lib/dash/dash_parser.js +79 -33
  81. package/lib/dash/segment_base.js +25 -1
  82. package/lib/dash/segment_list.js +18 -9
  83. package/lib/dash/segment_template.js +23 -10
  84. package/lib/hls/hls_parser.js +116 -23
  85. package/lib/hls/manifest_text_parser.js +1 -0
  86. package/lib/media/quality_observer.js +274 -0
  87. package/lib/media/region_observer.js +39 -33
  88. package/lib/media/region_timeline.js +17 -19
  89. package/lib/media/segment_index.js +8 -1
  90. package/lib/media/segment_reference.js +37 -4
  91. package/lib/media/streaming_engine.js +47 -6
  92. package/lib/net/http_fetch_plugin.js +31 -11
  93. package/lib/net/http_plugin_utils.js +1 -0
  94. package/lib/net/http_xhr_plugin.js +40 -14
  95. package/lib/net/networking_engine.js +85 -18
  96. package/lib/offline/download_info.js +68 -0
  97. package/lib/offline/download_manager.js +13 -5
  98. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  99. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  100. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  101. package/lib/offline/storage.js +401 -293
  102. package/lib/offline/stored_content_utils.js +2 -0
  103. package/lib/player.js +254 -27
  104. package/lib/polyfill/media_capabilities.js +30 -15
  105. package/lib/polyfill/random_uuid.js +52 -0
  106. package/lib/text/cue.js +18 -0
  107. package/lib/text/ttml_text_parser.js +109 -60
  108. package/lib/text/ui_text_displayer.js +19 -13
  109. package/lib/util/cmcd_manager.js +672 -0
  110. package/lib/util/error.js +6 -0
  111. package/lib/util/fake_event_target.js +21 -1
  112. package/lib/util/language_utils.js +12 -0
  113. package/lib/util/mutex.js +52 -0
  114. package/lib/util/periods.js +98 -64
  115. package/lib/util/player_configuration.js +18 -0
  116. package/lib/util/stream_utils.js +86 -10
  117. package/lib/util/xml_utils.js +19 -0
  118. package/package.json +3 -6
  119. package/shaka-player.uncompiled.js +3 -0
  120. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  121. package/test/dash/dash_parser_live_unit.js +144 -1
  122. package/test/dash/dash_parser_manifest_unit.js +37 -2
  123. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  124. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  125. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  126. package/test/hls/hls_live_unit.js +2 -0
  127. package/test/hls/hls_parser_unit.js +76 -0
  128. package/test/media/drm_engine_unit.js +3 -3
  129. package/test/media/quality_observer_unit.js +142 -0
  130. package/test/media/region_observer_unit.js +40 -5
  131. package/test/media/region_timeline_unit.js +40 -22
  132. package/test/media/segment_reference_unit.js +16 -1
  133. package/test/media/streaming_engine_integration.js +4 -2
  134. package/test/media/streaming_engine_unit.js +147 -0
  135. package/test/net/http_plugin_unit.js +20 -15
  136. package/test/offline/storage_integration.js +108 -4
  137. package/test/player_integration.js +140 -8
  138. package/test/player_src_equals_integration.js +41 -8
  139. package/test/player_unit.js +19 -9
  140. package/test/test/assets/chapters.vtt +2 -2
  141. package/test/test/assets/chapters2.vtt +10 -0
  142. package/test/test/util/dash_parser_util.js +4 -0
  143. package/test/test/util/fake_ad.js +7 -0
  144. package/test/test/util/fake_drm_engine.js +9 -2
  145. package/test/test/util/manifest_generator.js +3 -2
  146. package/test/test/util/test_scheme.js +14 -1
  147. package/test/test/util/ttml_utils.js +81 -0
  148. package/test/test/util/util.js +10 -7
  149. package/test/text/mp4_ttml_parser_unit.js +11 -18
  150. package/test/text/ttml_text_parser_unit.js +544 -228
  151. package/test/ui/text_displayer_layout_unit.js +8 -7
  152. package/test/ui/ui_unit.js +250 -0
  153. package/test/util/cmcd_manager_unit.js +291 -0
  154. package/test/util/stream_utils_unit.js +36 -30
  155. package/third_party/closure-uri/uri.js +21 -0
  156. package/ui/airplay_button.js +1 -0
  157. package/ui/audio_language_selection.js +7 -0
  158. package/ui/cast_button.js +1 -0
  159. package/ui/context_menu.js +109 -0
  160. package/ui/controls.js +44 -7
  161. package/ui/controls.less +1 -0
  162. package/ui/enums.js +4 -0
  163. package/ui/externs/ui.js +28 -1
  164. package/ui/fast_forward_button.js +14 -5
  165. package/ui/fullscreen_button.js +1 -0
  166. package/ui/less/buttons.less +0 -4
  167. package/ui/less/containers.less +74 -0
  168. package/ui/less/overflow_menu.less +0 -10
  169. package/ui/less/tooltip.less +97 -0
  170. package/ui/locales/en.json +1 -0
  171. package/ui/locales/source.json +4 -0
  172. package/ui/localization.js +12 -36
  173. package/ui/loop_button.js +5 -0
  174. package/ui/mute_button.js +2 -1
  175. package/ui/overflow_menu.js +5 -25
  176. package/ui/pip_button.js +5 -0
  177. package/ui/play_button.js +2 -2
  178. package/ui/playback_rate_selection.js +8 -9
  179. package/ui/resolution_selection.js +7 -0
  180. package/ui/rewind_button.js +15 -6
  181. package/ui/seek_bar.js +4 -2
  182. package/ui/settings_menu.js +36 -4
  183. package/ui/small_play_button.js +1 -0
  184. package/ui/statistics_button.js +253 -0
  185. package/ui/text_selection.js +12 -4
  186. package/ui/ui.js +30 -0
  187. package/ui/volume_bar.js +1 -1
  188. package/externs/media_capabilities.js +0 -16
@@ -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,18 +174,19 @@ 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);
201
190
 
202
191
  if (cueNode.nodeType == Node.COMMENT_NODE) {
203
192
  // The comments do not contain information that interests us here.
@@ -205,6 +194,12 @@ shaka.text.TtmlTextParser = class {
205
194
  }
206
195
 
207
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
+ }
208
203
  // This should generate an "anonymous span" according to the TTML spec.
209
204
  // So pretend the element was a <span>. parentElement was set above, so
210
205
  // we should still be able to correctly traverse up for timing
@@ -219,6 +214,21 @@ shaka.text.TtmlTextParser = class {
219
214
  }
220
215
  goog.asserts.assert(cueElement, 'cueElement should be non-null!');
221
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
+
222
232
  const spaceStyle = cueElement.getAttribute('xml:space') ||
223
233
  (whitespaceTrim ? 'default' : 'preserve');
224
234
 
@@ -243,8 +253,9 @@ shaka.text.TtmlTextParser = class {
243
253
  regionElements,
244
254
  cueRegions,
245
255
  localWhitespaceTrim,
246
- /* isNested= */ true,
247
256
  cellResolution,
257
+ cueElement,
258
+ isContent,
248
259
  );
249
260
 
250
261
  // This node may or may not generate a nested cue.
@@ -254,6 +265,8 @@ shaka.text.TtmlTextParser = class {
254
265
  }
255
266
  }
256
267
 
268
+ const isNested = /** @type {boolean} */ (parentCueElement != null);
269
+
257
270
  // In this regex, "\S" means "non-whitespace character".
258
271
  const hasTextContent = /\S/.test(cueElement.textContent);
259
272
  const hasTimeAttributes =
@@ -334,6 +347,13 @@ shaka.text.TtmlTextParser = class {
334
347
  const cue = new shaka.text.Cue(start, end, payload);
335
348
  cue.nestedCues = nestedCues;
336
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
+
337
357
  if (cellResolution) {
338
358
  cue.cellResolution = cellResolution;
339
359
  }
@@ -341,31 +361,32 @@ shaka.text.TtmlTextParser = class {
341
361
  // Get other properties if available.
342
362
  const regionElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
343
363
  cueElement, 'region', regionElements, /* prefix= */ '')[0];
344
- if (regionElement && regionElement.getAttribute('xml:id')) {
345
- const regionId = regionElement.getAttribute('xml:id');
346
- cue.region = cueRegions.filter((region) => region.id == regionId)[0];
347
- }
348
-
349
- let imageElement = null;
350
- for (const nameSpace of shaka.text.TtmlTextParser.smpteNsList_) {
351
- imageElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
352
- cueElement, 'backgroundImage', metadataElements, '#',
353
- nameSpace)[0];
354
- if (imageElement) {
355
- 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];
356
371
  }
357
372
  }
358
373
 
359
- 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
+ }
360
381
 
361
382
  shaka.text.TtmlTextParser.addStyle_(
362
383
  cue,
363
384
  cueElement,
364
- regionElement,
385
+ regionElementForStyle,
365
386
  imageElement,
366
387
  styles,
367
- isNested,
368
- isLeaf);
388
+ /** isNested= */ parentIsContent, // "nested in a <div>" doesn't count.
389
+ /** isLeaf= */ (nestedCues.length == 0));
369
390
 
370
391
  return cue;
371
392
  }
@@ -505,14 +526,13 @@ shaka.text.TtmlTextParser = class {
505
526
  }
506
527
 
507
528
  const align = TtmlTextParser.getStyleAttribute_(
508
- cueElement, region, styles, 'textAlign', shouldInheritRegionStyles);
529
+ cueElement, region, styles, 'textAlign', true);
509
530
  if (align) {
510
531
  cue.positionAlign = TtmlTextParser.textAlignToPositionAlign_[align];
511
532
  cue.lineAlign = TtmlTextParser.textAlignToLineAlign_[align];
512
533
 
513
534
  goog.asserts.assert(align.toUpperCase() in Cue.textAlign,
514
- align.toUpperCase() +
515
- ' Should be in Cue.textAlign values!');
535
+ align.toUpperCase() + ' Should be in Cue.textAlign values!');
516
536
 
517
537
  cue.textAlign = Cue.textAlign[align.toUpperCase()];
518
538
  } else {
@@ -521,7 +541,7 @@ shaka.text.TtmlTextParser = class {
521
541
  }
522
542
 
523
543
  const displayAlign = TtmlTextParser.getStyleAttribute_(
524
- cueElement, region, styles, 'displayAlign', shouldInheritRegionStyles);
544
+ cueElement, region, styles, 'displayAlign', true);
525
545
  if (displayAlign) {
526
546
  goog.asserts.assert(displayAlign.toUpperCase() in Cue.displayAlign,
527
547
  displayAlign.toUpperCase() +
@@ -531,7 +551,6 @@ shaka.text.TtmlTextParser = class {
531
551
 
532
552
  const color = TtmlTextParser.getStyleAttribute_(
533
553
  cueElement, region, styles, 'color', shouldInheritRegionStyles);
534
-
535
554
  if (color) {
536
555
  cue.color = color;
537
556
  }
@@ -584,7 +603,6 @@ shaka.text.TtmlTextParser = class {
584
603
 
585
604
  const fontSize = TtmlTextParser.getStyleAttribute_(
586
605
  cueElement, region, styles, 'fontSize', shouldInheritRegionStyles);
587
-
588
606
  if (fontSize) {
589
607
  const isValidFontSizeUnit =
590
608
  fontSize.match(TtmlTextParser.unitValues_) ||
@@ -622,6 +640,31 @@ shaka.text.TtmlTextParser = class {
622
640
  }
623
641
  }
624
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
+
625
668
  const letterSpacing = TtmlTextParser.getStyleAttribute_(
626
669
  cueElement, region, styles, 'letterSpacing', shouldInheritRegionStyles);
627
670
  if (letterSpacing && letterSpacing.match(TtmlTextParser.unitValues_)) {
@@ -753,7 +796,7 @@ shaka.text.TtmlTextParser = class {
753
796
  return null;
754
797
  }
755
798
 
756
- const attr = XmlUtils.getAttributeNS(region, ttsNs, attribute);
799
+ const attr = XmlUtils.getAttributeNSList(region, ttsNs, attribute);
757
800
  if (attr) {
758
801
  return attr;
759
802
  }
@@ -778,7 +821,7 @@ shaka.text.TtmlTextParser = class {
778
821
 
779
822
  // Styling on elements should take precedence
780
823
  // over the main styling attributes
781
- const elementAttribute = XmlUtils.getAttributeNS(
824
+ const elementAttribute = XmlUtils.getAttributeNSList(
782
825
  cueElement,
783
826
  ttsNs,
784
827
  attribute);
@@ -821,7 +864,7 @@ shaka.text.TtmlTextParser = class {
821
864
 
822
865
  if (!styleAttributeValue) {
823
866
  // Fall back to tts namespace.
824
- styleAttributeValue = XmlUtils.getAttributeNS(
867
+ styleAttributeValue = XmlUtils.getAttributeNSList(
825
868
  inheritedStyles[i],
826
869
  ttsNs,
827
870
  attribute);
@@ -1276,20 +1319,26 @@ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
1276
1319
  * document, not just "ttp:", so we use this with getAttributeNS() to ensure
1277
1320
  * that we support arbitrary namespace names.
1278
1321
  *
1279
- * @const {string}
1322
+ * @const {!Array.<string>}
1280
1323
  * @private
1281
1324
  */
1282
- 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
+ ];
1283
1329
 
1284
1330
  /**
1285
1331
  * The namespace URL for TTML styles. Can be assigned any name in the TTML
1286
1332
  * document, not just "tts:", so we use this with getAttributeNS() to ensure
1287
1333
  * that we support arbitrary namespace names.
1288
1334
  *
1289
- * @const {string}
1335
+ * @const {!Array.<string>}
1290
1336
  * @private
1291
1337
  */
1292
- 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
+ ];
1293
1342
 
1294
1343
  /**
1295
1344
  * The namespace URL for EBU TTML styles. Can be assigned any name in the TTML
@@ -352,19 +352,22 @@ shaka.text.UITextDisplayer = class {
352
352
  type = 'br';
353
353
  }
354
354
 
355
+ const needWrapper = !isNested && cue.nestedCues.length > 0;
356
+
355
357
  // Nested cues are inline elements. Top-level cues are block elements.
356
358
  const cueElement = shaka.util.Dom.createHTMLElement(type);
357
359
  if (type != 'br') {
358
- this.setCaptionStyles_(cueElement, cue, parents);
360
+ this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
359
361
  }
360
362
 
361
363
  let wrapper = cueElement;
362
- if (!isNested && cue.nestedCues.length) {
364
+ if (needWrapper) {
363
365
  // Create a wrapper element which will serve to contain all children into
364
366
  // a single item. This ensures that nested span elements appear
365
367
  // horizontally and br elements occupy no vertical space.
366
368
  wrapper = shaka.util.Dom.createHTMLElement('span');
367
369
  wrapper.classList.add('shaka-text-wrapper');
370
+ wrapper.style.backgroundColor = cue.backgroundColor;
368
371
  cueElement.appendChild(wrapper);
369
372
  }
370
373
 
@@ -375,9 +378,10 @@ shaka.text.UITextDisplayer = class {
375
378
  * @param {!HTMLElement} cueElement
376
379
  * @param {!shaka.extern.Cue} cue
377
380
  * @param {!Array.<!shaka.extern.Cue>} parents
381
+ * @param {boolean} hasWrapper
378
382
  * @private
379
383
  */
380
- setCaptionStyles_(cueElement, cue, parents) {
384
+ setCaptionStyles_(cueElement, cue, parents, hasWrapper) {
381
385
  const Cue = shaka.text.Cue;
382
386
  const inherit =
383
387
  (cb) => shaka.text.UITextDisplayer.inheritProperty_(parents, cb);
@@ -403,8 +407,8 @@ shaka.text.UITextDisplayer = class {
403
407
  return nonBreakingSpace.repeat(match.length);
404
408
  });
405
409
 
406
- style.backgroundColor = cue.backgroundColor;
407
- style.border = cue.border;
410
+ style.webkitTextStrokeColor = cue.textStrokeColor;
411
+ style.webkitTextStrokeWidth = cue.textStrokeWidth;
408
412
  style.color = cue.color;
409
413
  style.direction = cue.direction;
410
414
  style.opacity = cue.opacity;
@@ -434,13 +438,15 @@ shaka.text.UITextDisplayer = class {
434
438
  if (cue.border) {
435
439
  elem.style.border = cue.border;
436
440
  }
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)';
441
+ if (!hasWrapper) {
442
+ const bgColor = inherit((c) => c.backgroundColor);
443
+ if (bgColor) {
444
+ elem.style.backgroundColor = bgColor;
445
+ } else if (text) {
446
+ // If there is no background, default to a semi-transparent black.
447
+ // Only do this for the text itself.
448
+ elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
449
+ }
444
450
  }
445
451
  if (text) {
446
452
  elem.textContent = text;
@@ -450,7 +456,7 @@ shaka.text.UITextDisplayer = class {
450
456
  // The displayAlign attribute specifies the vertical alignment of the
451
457
  // captions inside the text container. Before means at the top of the
452
458
  // text container, and after means at the bottom.
453
- if (isNested) {
459
+ if (isNested && !parents[parents.length - 1].isContainer) {
454
460
  style.display = 'inline';
455
461
  } else {
456
462
  style.display = 'flex';