shaka-player 3.2.0 → 3.2.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 (162) hide show
  1. package/.github/workflows/build_and_test.yaml +73 -0
  2. package/.github/workflows/release.yaml +53 -0
  3. package/AUTHORS +3 -0
  4. package/CHANGELOG.md +182 -1
  5. package/CONTRIBUTORS +3 -0
  6. package/README.md +4 -4
  7. package/build/checkversion.py +1 -1
  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/build/test.py +6 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/index.html +1 -1
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.js +1 -1
  19. package/dist/deps.js +8 -8
  20. package/dist/locales.js +37 -37
  21. package/dist/receiver.compiled.debug.js +1 -1
  22. package/dist/receiver.compiled.js +1 -1
  23. package/dist/shaka-player.compiled.d.ts +195 -5
  24. package/dist/shaka-player.compiled.debug.d.ts +195 -5
  25. package/dist/shaka-player.compiled.debug.externs.js +226 -14
  26. package/dist/shaka-player.compiled.debug.js +424 -399
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +226 -14
  29. package/dist/shaka-player.compiled.js +826 -812
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +197 -16
  32. package/dist/shaka-player.ui.debug.d.ts +197 -16
  33. package/dist/shaka-player.ui.debug.externs.js +230 -27
  34. package/dist/shaka-player.ui.debug.js +466 -442
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +230 -27
  37. package/dist/shaka-player.ui.js +946 -932
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/fairplay.md +1 -1
  40. package/docs/tutorials/faq.md +14 -0
  41. package/docs/tutorials/ui.md +5 -5
  42. package/externs/ima.js +4 -0
  43. package/externs/shaka/ads.js +25 -0
  44. package/externs/shaka/player.js +8 -0
  45. package/externs/shaka/text.js +10 -0
  46. package/karma.conf.js +20 -15
  47. package/lib/ads/ad_manager.js +46 -26
  48. package/lib/ads/client_side_ad_manager.js +82 -88
  49. package/lib/ads/server_side_ad_manager.js +45 -13
  50. package/lib/cast/cast_proxy.js +6 -2
  51. package/lib/cast/cast_receiver.js +5 -1
  52. package/lib/cast/cast_sender.js +2 -2
  53. package/lib/cea/sei_processor.js +24 -17
  54. package/lib/dash/dash_parser.js +131 -24
  55. package/lib/dash/segment_list.js +1 -1
  56. package/lib/dash/segment_template.js +1 -1
  57. package/lib/hls/hls_parser.js +74 -17
  58. package/lib/hls/manifest_text_parser.js +1 -0
  59. package/lib/media/closed_caption_parser.js +3 -1
  60. package/lib/media/content_workarounds.js +12 -0
  61. package/lib/media/drm_engine.js +20 -1
  62. package/lib/media/gap_jumping_controller.js +8 -4
  63. package/lib/media/media_source_engine.js +1 -2
  64. package/lib/media/playhead.js +1 -1
  65. package/lib/media/region_observer.js +39 -33
  66. package/lib/media/region_timeline.js +17 -19
  67. package/lib/media/segment_index.js +2 -1
  68. package/lib/media/segment_reference.js +19 -1
  69. package/lib/media/streaming_engine.js +8 -4
  70. package/lib/net/networking_engine.js +6 -1
  71. package/lib/offline/download_manager.js +1 -2
  72. package/lib/offline/storage.js +8 -9
  73. package/lib/player.js +129 -59
  74. package/lib/polyfill/aria.js +12 -2
  75. package/lib/polyfill/encryption_scheme.js +2 -0
  76. package/lib/polyfill/fullscreen.js +2 -0
  77. package/lib/polyfill/mathround.js +2 -0
  78. package/lib/polyfill/media_capabilities.js +73 -21
  79. package/lib/polyfill/mediasource.js +8 -0
  80. package/lib/polyfill/orientation.js +3 -1
  81. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  82. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  83. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  84. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  85. package/lib/polyfill/pip_webkit.js +2 -0
  86. package/lib/polyfill/storage_estimate.js +2 -0
  87. package/lib/polyfill/video_play_promise.js +2 -0
  88. package/lib/polyfill/videoplaybackquality.js +2 -0
  89. package/lib/polyfill/vttcue.js +2 -0
  90. package/lib/text/cue.js +6 -0
  91. package/lib/text/mp4_ttml_parser.js +3 -0
  92. package/lib/text/text_engine.js +14 -2
  93. package/lib/text/ttml_text_parser.js +135 -87
  94. package/lib/text/ui_text_displayer.js +213 -80
  95. package/lib/text/vtt_text_parser.js +4 -2
  96. package/lib/text/web_vtt_generator.js +21 -12
  97. package/lib/util/fake_event.js +53 -7
  98. package/lib/util/fake_event_target.js +21 -1
  99. package/lib/util/mp4_box_parsers.js +1 -1
  100. package/lib/util/periods.js +4 -1
  101. package/lib/util/platform.js +13 -1
  102. package/lib/util/stream_utils.js +105 -13
  103. package/lib/util/xml_utils.js +50 -12
  104. package/package.json +3 -6
  105. package/roadmap.md +24 -22
  106. package/test/cast/cast_proxy_unit.js +12 -6
  107. package/test/cast/cast_receiver_integration.js +1 -1
  108. package/test/cast/cast_utils_unit.js +1 -1
  109. package/test/cea/mp4_cea_parser_unit.js +26 -0
  110. package/test/dash/dash_parser_live_unit.js +43 -0
  111. package/test/dash/dash_parser_manifest_unit.js +222 -3
  112. package/test/hls/hls_parser_unit.js +79 -1
  113. package/test/media/closed_caption_parser_unit.js +20 -0
  114. package/test/media/drm_engine_integration.js +86 -2
  115. package/test/media/drm_engine_unit.js +57 -0
  116. package/test/media/playhead_unit.js +27 -1
  117. package/test/media/region_observer_unit.js +40 -5
  118. package/test/media/region_timeline_unit.js +40 -22
  119. package/test/media/streaming_engine_integration.js +2 -1
  120. package/test/media/streaming_engine_unit.js +145 -0
  121. package/test/player_integration.js +79 -8
  122. package/test/player_src_equals_integration.js +41 -8
  123. package/test/player_unit.js +14 -4
  124. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  125. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  126. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  127. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  128. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  129. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  130. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  131. package/test/test/assets/3675/dash_0.mpd +30 -0
  132. package/test/test/assets/3675/dash_1.mpd +30 -0
  133. package/test/test/assets/3675/dash_2.mpd +30 -0
  134. package/test/test/assets/3675/dash_3.mpd +30 -0
  135. package/test/test/assets/3675/dash_4.mpd +30 -0
  136. package/test/test/assets/3675/dash_5.mpd +26 -0
  137. package/test/test/assets/3675/init-stream0.m4s +0 -0
  138. package/test/test/assets/chapters.vtt +2 -2
  139. package/test/test/assets/chapters2.vtt +10 -0
  140. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  141. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  142. package/test/test/util/fake_ad_manager.js +11 -1
  143. package/test/test/util/ttml_utils.js +81 -0
  144. package/test/test/util/util.js +10 -7
  145. package/test/text/mp4_ttml_parser_unit.js +11 -18
  146. package/test/text/ttml_text_parser_unit.js +542 -220
  147. package/test/text/ui_text_displayer_unit.js +93 -20
  148. package/test/text/vtt_text_parser_unit.js +17 -1
  149. package/test/text/web_vtt_generator_unit.js +58 -3
  150. package/test/ui/localization_unit.js +15 -14
  151. package/test/ui/text_displayer_layout_unit.js +21 -8
  152. package/test/util/mp4_box_parsers_unit.js +28 -0
  153. package/test/util/stream_utils_unit.js +60 -3
  154. package/ui/cast_button.js +2 -3
  155. package/ui/controls.js +11 -6
  156. package/ui/less/buttons.less +0 -4
  157. package/ui/less/containers.less +6 -7
  158. package/ui/locales/ru.json +6 -6
  159. package/ui/localization.js +40 -87
  160. package/ui/pip_button.js +2 -3
  161. package/ui/seek_bar.js +1 -2
  162. package/ui/text_selection.js +1 -0
@@ -15,10 +15,12 @@ goog.require('shaka.polyfill');
15
15
  * @summary A polyfill for systems that do not implement screen.orientation.
16
16
  * For now, this only handles systems that implement the deprecated
17
17
  * window.orientation feature... e.g. iPad.
18
+ * @export
18
19
  */
19
20
  shaka.polyfill.Orientation = class {
20
21
  /**
21
22
  * Install the polyfill if needed.
23
+ * @export
22
24
  */
23
25
  static install() {
24
26
  if (screen.orientation) {
@@ -90,7 +92,7 @@ class extends shaka.util.FakeEventTarget {
90
92
 
91
93
  /** Dispatch a 'change' event. */
92
94
  dispatchChangeEvent() {
93
- const event = new shaka.util.FakeEvent('change', {});
95
+ const event = new shaka.util.FakeEvent('change');
94
96
  this.dispatchEvent(event);
95
97
  }
96
98
 
@@ -22,10 +22,12 @@ goog.require('shaka.util.StringUtils');
22
22
  /**
23
23
  * @summary A polyfill to implement modern, standardized EME on top of Apple's
24
24
  * prefixed EME in Safari.
25
+ * @export
25
26
  */
26
27
  shaka.polyfill.PatchedMediaKeysApple = class {
27
28
  /**
28
29
  * Installs the polyfill if needed.
30
+ * @export
29
31
  */
30
32
  static install() {
31
33
  if (!window.HTMLVideoElement || !window.WebKitMediaKeys) {
@@ -565,10 +567,10 @@ class extends shaka.util.FakeEventTarget {
565
567
 
566
568
  const isNew = this.keyStatuses.getStatus() == undefined;
567
569
 
568
- const event2 = new shaka.util.FakeEvent('message', {
569
- messageType: isNew ? 'license-request' : 'license-renewal',
570
- message: shaka.util.BufferUtils.toArrayBuffer(event.message),
571
- });
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);
572
574
 
573
575
  this.dispatchEvent(event2);
574
576
  }
@@ -24,10 +24,12 @@ goog.require('shaka.util.PublicPromise');
24
24
  * {@link https://bit.ly/EmeMar15 EME draft 12 March 2015}
25
25
  * on top of ms-prefixed
26
26
  * {@link https://www.w3.org/TR/2014/WD-encrypted-media-20140218/ EME v20140218}
27
+ * @export
27
28
  */
28
29
  shaka.polyfill.PatchedMediaKeysMs = class {
29
30
  /**
30
31
  * Installs the polyfill if needed.
32
+ * @export
31
33
  */
32
34
  static install() {
33
35
  if (!window.HTMLVideoElement || !window.MSMediaKeys ||
@@ -510,10 +512,10 @@ class extends shaka.util.FakeEventTarget {
510
512
 
511
513
  const isNew = this.keyStatuses.getStatus() == undefined;
512
514
 
513
- const event2 = new shaka.util.FakeEvent('message', {
514
- messageType: isNew ? 'license-request' : 'license-renewal',
515
- message: shaka.util.BufferUtils.toArrayBuffer(event.message),
516
- });
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);
517
519
 
518
520
  this.dispatchEvent(event2);
519
521
  }
@@ -16,10 +16,12 @@ goog.require('shaka.polyfill');
16
16
  * {@link https://bit.ly/EmeMar15 EME draft 12 March 2015} on browsers without
17
17
  * EME.
18
18
  * All methods will fail.
19
+ * @export
19
20
  */
20
21
  shaka.polyfill.PatchedMediaKeysNop = class {
21
22
  /**
22
23
  * Installs the polyfill if needed.
24
+ * @export
23
25
  */
24
26
  static install() {
25
27
  if (!window.HTMLVideoElement ||
@@ -24,10 +24,12 @@ goog.require('shaka.util.Uint8ArrayUtils');
24
24
  * @summary A polyfill to implement
25
25
  * {@link https://bit.ly/EmeMar15 EME draft 12 March 2015} on top of
26
26
  * webkit-prefixed {@link https://bit.ly/Eme01b EME v0.1b}.
27
+ * @export
27
28
  */
28
29
  shaka.polyfill.PatchedMediaKeysWebkit = class {
29
30
  /**
30
31
  * Installs the polyfill if needed.
32
+ * @export
31
33
  */
32
34
  static install() {
33
35
  // Alias.
@@ -449,10 +451,10 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeys = class {
449
451
 
450
452
  const isNew = session.keyStatuses.getStatus() == undefined;
451
453
 
452
- const event2 = new shaka.util.FakeEvent('message', {
453
- messageType: isNew ? 'licenserequest' : 'licenserenewal',
454
- message: event.message,
455
- });
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);
456
458
 
457
459
  session.generated();
458
460
  session.dispatchEvent(event2);
@@ -12,10 +12,12 @@ goog.require('shaka.polyfill');
12
12
  /**
13
13
  * @summary A polyfill to provide PiP support in Safari.
14
14
  * Note that Safari only supports PiP on video elements, not audio.
15
+ * @export
15
16
  */
16
17
  shaka.polyfill.PiPWebkit = class {
17
18
  /**
18
19
  * Install the polyfill if needed.
20
+ * @export
19
21
  */
20
22
  static install() {
21
23
  if (!window.HTMLVideoElement) {
@@ -13,10 +13,12 @@ goog.require('shaka.polyfill');
13
13
  * @summary A polyfill to provide navigator.storage.estimate in old
14
14
  * webkit browsers.
15
15
  * See: https://developers.google.com/web/updates/2017/08/estimating-available-storage-space#the-present
16
+ * @export
16
17
  */
17
18
  shaka.polyfill.StorageEstimate = class {
18
19
  /**
19
20
  * Install the polyfill if needed.
21
+ * @export
20
22
  */
21
23
  static install() {
22
24
  if (navigator.storage && navigator.storage.estimate) {
@@ -12,10 +12,12 @@ goog.require('shaka.polyfill');
12
12
 
13
13
  /**
14
14
  * @summary A polyfill to silence the play() Promise in HTML5 video.
15
+ * @export
15
16
  */
16
17
  shaka.polyfill.VideoPlayPromise = class {
17
18
  /**
18
19
  * Install the polyfill if needed.
20
+ * @export
19
21
  */
20
22
  static install() {
21
23
  shaka.log.debug('VideoPlayPromise.install');
@@ -13,10 +13,12 @@ goog.require('shaka.polyfill');
13
13
  * @summary A polyfill to provide MSE VideoPlaybackQuality metrics.
14
14
  * Many browsers do not yet provide this API, and Chrome currently provides
15
15
  * similar data through individual prefixed attributes on HTMLVideoElement.
16
+ * @export
16
17
  */
17
18
  shaka.polyfill.VideoPlaybackQuality = class {
18
19
  /**
19
20
  * Install the polyfill if needed.
21
+ * @export
20
22
  */
21
23
  static install() {
22
24
  if (!window.HTMLVideoElement) {
@@ -12,10 +12,12 @@ goog.require('shaka.polyfill');
12
12
 
13
13
  /**
14
14
  * @summary A polyfill to provide VTTCue.
15
+ * @export
15
16
  */
16
17
  shaka.polyfill.VTTCue = class {
17
18
  /**
18
19
  * Install the polyfill if needed.
20
+ * @export
19
21
  */
20
22
  static install() {
21
23
  if (window.VTTCue) {
package/lib/text/cue.js CHANGED
@@ -210,6 +210,12 @@ shaka.text.Cue = class {
210
210
  */
211
211
  this.nestedCues = [];
212
212
 
213
+ /**
214
+ * @override
215
+ * @exportInterface
216
+ */
217
+ this.isContainer = false;
218
+
213
219
  /**
214
220
  * @override
215
221
  * @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,11 +214,59 @@ 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
 
220
235
  const localWhitespaceTrim = spaceStyle == 'default';
221
236
 
237
+ // Parse any nested cues first.
238
+ const isTextNode = (node) => {
239
+ return node.nodeType == Node.TEXT_NODE;
240
+ };
241
+ const isLeafNode = Array.from(cueElement.childNodes).every(isTextNode);
242
+ const nestedCues = [];
243
+ if (!isLeafNode) {
244
+ // Otherwise, recurse into the children. Text nodes will convert into
245
+ // anonymous spans, which will then be leaf nodes.
246
+ for (const childNode of cueElement.childNodes) {
247
+ const nestedCue = shaka.text.TtmlTextParser.parseCue_(
248
+ childNode,
249
+ offset,
250
+ rateInfo,
251
+ metadataElements,
252
+ styles,
253
+ regionElements,
254
+ cueRegions,
255
+ localWhitespaceTrim,
256
+ cellResolution,
257
+ cueElement,
258
+ isContent,
259
+ );
260
+
261
+ // This node may or may not generate a nested cue.
262
+ if (nestedCue) {
263
+ nestedCues.push(nestedCue);
264
+ }
265
+ }
266
+ }
267
+
268
+ const isNested = /** @type {boolean} */ (parentCueElement != null);
269
+
222
270
  // In this regex, "\S" means "non-whitespace character".
223
271
  const hasTextContent = /\S/.test(cueElement.textContent);
224
272
  const hasTimeAttributes =
@@ -226,7 +274,8 @@ shaka.text.TtmlTextParser = class {
226
274
  cueElement.hasAttribute('end') ||
227
275
  cueElement.hasAttribute('dur');
228
276
 
229
- if (!hasTimeAttributes && !hasTextContent && cueElement.tagName != 'br') {
277
+ if (!hasTimeAttributes && !hasTextContent && cueElement.tagName != 'br' &&
278
+ nestedCues.length == 0) {
230
279
  if (!isNested) {
231
280
  // Disregards empty <p> elements without time attributes nor content.
232
281
  // <p begin="..." smpte:backgroundImage="..." /> will go through,
@@ -263,6 +312,20 @@ shaka.text.TtmlTextParser = class {
263
312
  end += offset;
264
313
  }
265
314
 
315
+ if (!hasTimeAttributes && nestedCues.length > 0) {
316
+ // If no time is defined for this cue, base the timing information on
317
+ // the time of the nested cues. In the case of multiple nested cues with
318
+ // different start times, it is the text displayer's responsibility to
319
+ // make sure that only the appropriate nested cue is drawn at any given
320
+ // time.
321
+ start = Infinity;
322
+ end = 0;
323
+ for (const cue of nestedCues) {
324
+ start = Math.min(start, cue.startTime);
325
+ end = Math.max(end, cue.endTime);
326
+ }
327
+ }
328
+
266
329
  if (cueElement.tagName == 'br') {
267
330
  const cue = new shaka.text.Cue(start, end, '');
268
331
  cue.lineBreak = true;
@@ -270,13 +333,7 @@ shaka.text.TtmlTextParser = class {
270
333
  }
271
334
 
272
335
  let payload = '';
273
- const nestedCues = [];
274
-
275
- const isTextNode = (node) => {
276
- return node.nodeType == Node.TEXT_NODE;
277
- };
278
-
279
- if (Array.from(cueElement.childNodes).every(isTextNode)) {
336
+ if (isLeafNode) {
280
337
  // If the childNodes are all text, this is a leaf node. Get the payload.
281
338
  payload = cueElement.textContent;
282
339
  if (localWhitespaceTrim) {
@@ -285,33 +342,18 @@ shaka.text.TtmlTextParser = class {
285
342
  // Collapse multiple spaces into one.
286
343
  payload = payload.replace(/\s+/g, ' ');
287
344
  }
288
- } else {
289
- // Otherwise, recurse into the children. Text nodes will convert into
290
- // anonymous spans, which will then be leaf nodes.
291
- for (const childNode of cueElement.childNodes) {
292
- const nestedCue = shaka.text.TtmlTextParser.parseCue_(
293
- childNode,
294
- offset,
295
- rateInfo,
296
- metadataElements,
297
- styles,
298
- regionElements,
299
- cueRegions,
300
- localWhitespaceTrim,
301
- /* isNested= */ true,
302
- cellResolution,
303
- );
304
-
305
- // This node may or may not generate a nested cue.
306
- if (nestedCue) {
307
- nestedCues.push(nestedCue);
308
- }
309
- }
310
345
  }
311
346
 
312
347
  const cue = new shaka.text.Cue(start, end, payload);
313
348
  cue.nestedCues = nestedCues;
314
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
+
315
357
  if (cellResolution) {
316
358
  cue.cellResolution = cellResolution;
317
359
  }
@@ -319,31 +361,32 @@ shaka.text.TtmlTextParser = class {
319
361
  // Get other properties if available.
320
362
  const regionElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
321
363
  cueElement, 'region', regionElements, /* prefix= */ '')[0];
322
- if (regionElement && regionElement.getAttribute('xml:id')) {
323
- const regionId = regionElement.getAttribute('xml:id');
324
- cue.region = cueRegions.filter((region) => region.id == regionId)[0];
325
- }
326
-
327
- let imageElement = null;
328
- for (const nameSpace of shaka.text.TtmlTextParser.smpteNsList_) {
329
- imageElement = shaka.text.TtmlTextParser.getElementsFromCollection_(
330
- cueElement, 'backgroundImage', metadataElements, '#',
331
- nameSpace)[0];
332
- if (imageElement) {
333
- 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];
334
371
  }
335
372
  }
336
373
 
337
- 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
+ }
338
381
 
339
382
  shaka.text.TtmlTextParser.addStyle_(
340
383
  cue,
341
384
  cueElement,
342
- regionElement,
385
+ regionElementForStyle,
343
386
  imageElement,
344
387
  styles,
345
- isNested,
346
- isLeaf);
388
+ /** isNested= */ parentIsContent, // "nested in a <div>" doesn't count.
389
+ /** isLeaf= */ (nestedCues.length == 0));
347
390
 
348
391
  return cue;
349
392
  }
@@ -483,14 +526,13 @@ shaka.text.TtmlTextParser = class {
483
526
  }
484
527
 
485
528
  const align = TtmlTextParser.getStyleAttribute_(
486
- cueElement, region, styles, 'textAlign', shouldInheritRegionStyles);
529
+ cueElement, region, styles, 'textAlign', true);
487
530
  if (align) {
488
531
  cue.positionAlign = TtmlTextParser.textAlignToPositionAlign_[align];
489
532
  cue.lineAlign = TtmlTextParser.textAlignToLineAlign_[align];
490
533
 
491
534
  goog.asserts.assert(align.toUpperCase() in Cue.textAlign,
492
- align.toUpperCase() +
493
- ' Should be in Cue.textAlign values!');
535
+ align.toUpperCase() + ' Should be in Cue.textAlign values!');
494
536
 
495
537
  cue.textAlign = Cue.textAlign[align.toUpperCase()];
496
538
  } else {
@@ -499,7 +541,7 @@ shaka.text.TtmlTextParser = class {
499
541
  }
500
542
 
501
543
  const displayAlign = TtmlTextParser.getStyleAttribute_(
502
- cueElement, region, styles, 'displayAlign', shouldInheritRegionStyles);
544
+ cueElement, region, styles, 'displayAlign', true);
503
545
  if (displayAlign) {
504
546
  goog.asserts.assert(displayAlign.toUpperCase() in Cue.displayAlign,
505
547
  displayAlign.toUpperCase() +
@@ -731,7 +773,7 @@ shaka.text.TtmlTextParser = class {
731
773
  return null;
732
774
  }
733
775
 
734
- const attr = XmlUtils.getAttributeNS(region, ttsNs, attribute);
776
+ const attr = XmlUtils.getAttributeNSList(region, ttsNs, attribute);
735
777
  if (attr) {
736
778
  return attr;
737
779
  }
@@ -756,7 +798,7 @@ shaka.text.TtmlTextParser = class {
756
798
 
757
799
  // Styling on elements should take precedence
758
800
  // over the main styling attributes
759
- const elementAttribute = XmlUtils.getAttributeNS(
801
+ const elementAttribute = XmlUtils.getAttributeNSList(
760
802
  cueElement,
761
803
  ttsNs,
762
804
  attribute);
@@ -799,7 +841,7 @@ shaka.text.TtmlTextParser = class {
799
841
 
800
842
  if (!styleAttributeValue) {
801
843
  // Fall back to tts namespace.
802
- styleAttributeValue = XmlUtils.getAttributeNS(
844
+ styleAttributeValue = XmlUtils.getAttributeNSList(
803
845
  inheritedStyles[i],
804
846
  ttsNs,
805
847
  attribute);
@@ -1254,20 +1296,26 @@ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
1254
1296
  * document, not just "ttp:", so we use this with getAttributeNS() to ensure
1255
1297
  * that we support arbitrary namespace names.
1256
1298
  *
1257
- * @const {string}
1299
+ * @const {!Array.<string>}
1258
1300
  * @private
1259
1301
  */
1260
- shaka.text.TtmlTextParser.parameterNs_ = 'http://www.w3.org/ns/ttml#parameter';
1302
+ shaka.text.TtmlTextParser.parameterNs_ = [
1303
+ 'http://www.w3.org/ns/ttml#parameter',
1304
+ 'http://www.w3.org/2006/10/ttaf1#parameter',
1305
+ ];
1261
1306
 
1262
1307
  /**
1263
1308
  * The namespace URL for TTML styles. Can be assigned any name in the TTML
1264
1309
  * document, not just "tts:", so we use this with getAttributeNS() to ensure
1265
1310
  * that we support arbitrary namespace names.
1266
1311
  *
1267
- * @const {string}
1312
+ * @const {!Array.<string>}
1268
1313
  * @private
1269
1314
  */
1270
- shaka.text.TtmlTextParser.styleNs_ = 'http://www.w3.org/ns/ttml#styling';
1315
+ shaka.text.TtmlTextParser.styleNs_ = [
1316
+ 'http://www.w3.org/ns/ttml#styling',
1317
+ 'http://www.w3.org/2006/10/ttaf1#styling',
1318
+ ];
1271
1319
 
1272
1320
  /**
1273
1321
  * The namespace URL for EBU TTML styles. Can be assigned any name in the TTML