shaka-player 3.2.2 → 3.2.3

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 (51) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/controls.css +2 -2
  3. package/dist/controls.css.map +1 -1
  4. package/dist/locales.js +30 -30
  5. package/dist/shaka-player.compiled.d.ts +21 -2
  6. package/dist/shaka-player.compiled.debug.d.ts +21 -2
  7. package/dist/shaka-player.compiled.debug.externs.js +22 -1
  8. package/dist/shaka-player.compiled.debug.js +205 -202
  9. package/dist/shaka-player.compiled.debug.map +3 -3
  10. package/dist/shaka-player.compiled.externs.js +22 -1
  11. package/dist/shaka-player.compiled.js +727 -723
  12. package/dist/shaka-player.compiled.map +4 -4
  13. package/dist/shaka-player.ui.d.ts +21 -2
  14. package/dist/shaka-player.ui.debug.d.ts +21 -2
  15. package/dist/shaka-player.ui.debug.externs.js +22 -1
  16. package/dist/shaka-player.ui.debug.js +221 -218
  17. package/dist/shaka-player.ui.debug.map +3 -3
  18. package/dist/shaka-player.ui.externs.js +22 -1
  19. package/dist/shaka-player.ui.js +829 -825
  20. package/dist/shaka-player.ui.map +4 -4
  21. package/externs/shaka/player.js +8 -0
  22. package/externs/shaka/text.js +10 -0
  23. package/lib/dash/dash_parser.js +4 -2
  24. package/lib/hls/hls_parser.js +30 -0
  25. package/lib/media/segment_index.js +2 -1
  26. package/lib/media/segment_reference.js +19 -1
  27. package/lib/media/streaming_engine.js +5 -2
  28. package/lib/player.js +20 -13
  29. package/lib/polyfill/media_capabilities.js +30 -15
  30. package/lib/text/cue.js +6 -0
  31. package/lib/text/ttml_text_parser.js +84 -58
  32. package/lib/text/ui_text_displayer.js +17 -11
  33. package/lib/util/stream_utils.js +86 -10
  34. package/lib/util/xml_utils.js +19 -0
  35. package/package.json +2 -2
  36. package/test/dash/dash_parser_manifest_unit.js +33 -1
  37. package/test/media/drm_engine_unit.js +3 -3
  38. package/test/media/streaming_engine_integration.js +2 -1
  39. package/test/media/streaming_engine_unit.js +145 -0
  40. package/test/player_integration.js +41 -8
  41. package/test/player_src_equals_integration.js +41 -8
  42. package/test/player_unit.js +19 -9
  43. package/test/test/assets/chapters.vtt +2 -2
  44. package/test/test/assets/chapters2.vtt +10 -0
  45. package/test/test/util/ttml_utils.js +81 -0
  46. package/test/text/mp4_ttml_parser_unit.js +11 -18
  47. package/test/text/ttml_text_parser_unit.js +495 -228
  48. package/test/util/stream_utils_unit.js +36 -30
  49. package/ui/less/buttons.less +0 -4
  50. package/ui/text_selection.js +1 -0
  51. package/externs/media_capabilities.js +0 -16
@@ -1082,6 +1082,8 @@ shaka.extern.LanguageRole;
1082
1082
 
1083
1083
  /**
1084
1084
  * @typedef {{
1085
+ * imageHeight: number,
1086
+ * imageWidth: number,
1085
1087
  * height: number,
1086
1088
  * positionX: number,
1087
1089
  * positionY: number,
@@ -1091,6 +1093,12 @@ shaka.extern.LanguageRole;
1091
1093
  * width: number
1092
1094
  * }}
1093
1095
  *
1096
+ * @property {number} imageHeight
1097
+ * The image height in px. The image height could be different to height if
1098
+ * the layout is different to 1x1.
1099
+ * @property {number} imageWidth
1100
+ * The image width in px. The image width could be different to width if
1101
+ * the layout is different to 1x1.
1094
1102
  * @property {number} height
1095
1103
  * The thumbnail height in px.
1096
1104
  * @property {number} positionX
@@ -349,6 +349,16 @@ shaka.extern.Cue = class {
349
349
  */
350
350
  this.nestedCues;
351
351
 
352
+ /**
353
+ * If true, this represents a container element that is "above" the main
354
+ * cues. For example, the <body> and <div> tags that contain the <p> tags
355
+ * in a TTML file. This controls the flow of the final cues; any nested cues
356
+ * within an "isContainer" cue will be laid out as separate lines.
357
+ * @type {boolean}
358
+ * @exportDoc
359
+ */
360
+ this.isContainer;
361
+
352
362
  /**
353
363
  * Whether or not the cue only acts as a line break between two nested cues.
354
364
  * Should only appear in nested cues.
@@ -1220,8 +1220,10 @@ shaka.dash.DashParser = class {
1220
1220
 
1221
1221
  let forced = false;
1222
1222
  if (isText) {
1223
- // See: https://github.com/google/shaka-player/issues/2122
1224
- forced = roles.includes('forced_subtitle');
1223
+ // See: https://github.com/google/shaka-player/issues/2122 and
1224
+ // https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/165
1225
+ forced = roles.includes('forced_subtitle') ||
1226
+ roles.includes('forced-subtitle');
1225
1227
  }
1226
1228
 
1227
1229
  let tilesLayout;
@@ -1280,6 +1280,30 @@ shaka.hls.HlsParser = class {
1280
1280
  if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
1281
1281
  return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1282
1282
  }
1283
+
1284
+ // Parse misc attributes.
1285
+ const resolution = tag.getAttributeValue('RESOLUTION');
1286
+ if (resolution) {
1287
+ // The RESOLUTION tag represents the resolution of a single thumbnail, not
1288
+ // of the entire sheet at once (like we expect in the output).
1289
+ // So multiply by the layout size.
1290
+
1291
+ const reference = streamInfo.stream.segmentIndex.get(0);
1292
+ const layout = reference.getTilesLayout();
1293
+ if (layout) {
1294
+ streamInfo.stream.width =
1295
+ Number(resolution.split('x')[0]) * Number(layout.split('x')[0]);
1296
+ streamInfo.stream.height =
1297
+ Number(resolution.split('x')[1]) * Number(layout.split('x')[1]);
1298
+ // TODO: What happens if there are multiple grids, with different
1299
+ // layout sizes, inside this image stream?
1300
+ }
1301
+ }
1302
+ const bandwidth = tag.getAttributeValue('BANDWIDTH');
1303
+ if (bandwidth) {
1304
+ streamInfo.stream.bandwidth = Number(bandwidth);
1305
+ }
1306
+
1283
1307
  this.uriToStreamInfosMap_.set(verbatimImagePlaylistUri, streamInfo);
1284
1308
  return streamInfo;
1285
1309
  }
@@ -1811,6 +1835,7 @@ shaka.hls.HlsParser = class {
1811
1835
  }
1812
1836
 
1813
1837
  let tilesLayout = '';
1838
+ let tileDuration = null;
1814
1839
  if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
1815
1840
  // By default in HLS the tilesLayout is 1x1
1816
1841
  tilesLayout = '1x1';
@@ -1818,6 +1843,10 @@ shaka.hls.HlsParser = class {
1818
1843
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-TILES');
1819
1844
  if (tilesTag) {
1820
1845
  tilesLayout = tilesTag.getRequiredAttrValue('LAYOUT');
1846
+ const duration = tilesTag.getAttributeValue('DURATION');
1847
+ if (duration) {
1848
+ tileDuration = Number(duration);
1849
+ }
1821
1850
  }
1822
1851
  }
1823
1852
 
@@ -1833,6 +1862,7 @@ shaka.hls.HlsParser = class {
1833
1862
  /* appendWindowEnd= */ Infinity,
1834
1863
  partialSegmentRefs,
1835
1864
  tilesLayout,
1865
+ tileDuration,
1836
1866
  );
1837
1867
  }
1838
1868
 
@@ -347,7 +347,8 @@ shaka.media.SegmentIndex = class {
347
347
  lastReference.appendWindowStart,
348
348
  lastReference.appendWindowEnd,
349
349
  lastReference.partialReferences,
350
- lastReference.tilesLayout);
350
+ lastReference.tilesLayout,
351
+ lastReference.tileDuration);
351
352
  }
352
353
 
353
354
 
@@ -140,11 +140,15 @@ shaka.media.SegmentReference = class {
140
140
  * The value is a grid-item-dimension consisting of two positive decimal
141
141
  * integers in the format: column-x-row ('4x3'). It describes the
142
142
  * arrangement of Images in a Grid. The minimum valid LAYOUT is '1x1'.
143
+ * @param {?number=} tileDuration
144
+ * The explicit duration of an individual tile within the tiles grid.
145
+ * If not provided, the duration should be automatically calculated based on
146
+ * the duration of the reference.
143
147
  */
144
148
  constructor(
145
149
  startTime, endTime, uris, startByte, endByte, initSegmentReference,
146
150
  timestampOffset, appendWindowStart, appendWindowEnd,
147
- partialReferences = [], tilesLayout = '') {
151
+ partialReferences = [], tilesLayout = '', tileDuration = null) {
148
152
  // A preload hinted Partial Segment has the same startTime and endTime.
149
153
  goog.asserts.assert(startTime <= endTime,
150
154
  'startTime must be less than or equal to endTime');
@@ -191,6 +195,9 @@ shaka.media.SegmentReference = class {
191
195
 
192
196
  /** @type {?string} */
193
197
  this.tilesLayout = tilesLayout;
198
+
199
+ /** @type {?number} */
200
+ this.tileDuration = tileDuration;
194
201
  }
195
202
 
196
203
  /**
@@ -275,6 +282,17 @@ shaka.media.SegmentReference = class {
275
282
  getTilesLayout() {
276
283
  return this.tilesLayout;
277
284
  }
285
+
286
+ /**
287
+ * Returns the segment's explicit tile duration.
288
+ * Only defined in image segments.
289
+ *
290
+ * @return {?number}
291
+ * @export
292
+ */
293
+ getTileDuration() {
294
+ return this.tileDuration;
295
+ }
278
296
  };
279
297
 
280
298
 
@@ -623,7 +623,7 @@ shaka.media.StreamingEngine = class {
623
623
 
624
624
  // Don't clear the buffer unless something is buffered. This extra
625
625
  // check prevents extra, useless calls to clear the buffer.
626
- if (somethingBuffered) {
626
+ if (somethingBuffered || mediaState.performingUpdate) {
627
627
  this.forceClearBuffer_(mediaState);
628
628
  streamCleared = true;
629
629
  }
@@ -977,7 +977,10 @@ shaka.media.StreamingEngine = class {
977
977
  unscaledBufferingGoal * this.bufferingGoalScale_;
978
978
 
979
979
  // Check if we've buffered to the end of the presentation.
980
- if (timeNeeded >= this.manifest_.presentationTimeline.getDuration()) {
980
+ const timeUntilEnd =
981
+ this.manifest_.presentationTimeline.getDuration() - timeNeeded;
982
+ const oneMicrosecond = 1e-6;
983
+ if (timeUntilEnd < oneMicrosecond) {
981
984
  // We shouldn't rebuffer if the playhead is close to the end of the
982
985
  // presentation.
983
986
  shaka.log.debug(logPrefix, 'buffered to end of presentation');
package/lib/player.js CHANGED
@@ -3454,7 +3454,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3454
3454
  const height = fullImageHeight / rows;
3455
3455
  const totalImages = columns * rows;
3456
3456
  const segmentDuration = reference.trueEndTime - reference.startTime;
3457
- const thumbnailDuration = segmentDuration / totalImages;
3457
+ const thumbnailDuration =
3458
+ reference.getTileDuration() || (segmentDuration / totalImages);
3458
3459
  let thumbnailTime = reference.startTime;
3459
3460
  let positionX = 0;
3460
3461
  let positionY = 0;
@@ -3477,6 +3478,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3477
3478
  positionY = Math.floor(thumbnailPosition / columns) * height;
3478
3479
  }
3479
3480
  return {
3481
+ imageHeight: fullImageHeight,
3482
+ imageWidth: fullImageWidth,
3480
3483
  height: height,
3481
3484
  positionX: positionX,
3482
3485
  positionY: positionY,
@@ -3878,20 +3881,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3878
3881
  const LanguageUtils = shaka.util.LanguageUtils;
3879
3882
  const inputlanguage = LanguageUtils.normalize(language);
3880
3883
  const chaptersTracks = this.getChaptersTracks_();
3881
- const chaptersTrack = chaptersTracks
3882
- .find((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3883
- if (!chaptersTrack || !chaptersTrack.cues) {
3884
+ const chaptersTracksWithLanguage = chaptersTracks
3885
+ .filter((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3886
+ if (!chaptersTracksWithLanguage || !chaptersTracksWithLanguage.length) {
3884
3887
  return [];
3885
3888
  }
3886
3889
  const chapters = [];
3887
- for (const cue of chaptersTrack.cues) {
3888
- /** @type {shaka.extern.Chapter} */
3889
- const chapter = {
3890
- title: cue.text,
3891
- startTime: cue.startTime,
3892
- endTime: cue.endTime,
3893
- };
3894
- chapters.push(chapter);
3890
+ for (const chaptersTrack of chaptersTracksWithLanguage) {
3891
+ if (chaptersTrack && chaptersTrack.cues) {
3892
+ for (const cue of chaptersTrack.cues) {
3893
+ /** @type {shaka.extern.Chapter} */
3894
+ const chapter = {
3895
+ title: cue.text,
3896
+ startTime: cue.startTime,
3897
+ endTime: cue.endTime,
3898
+ };
3899
+ chapters.push(chapter);
3900
+ }
3901
+ }
3895
3902
  }
3896
3903
  return chapters;
3897
3904
  }
@@ -6331,7 +6338,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6331
6338
  * @define {string} A version number taken from git at compile time.
6332
6339
  * @export
6333
6340
  */
6334
- shaka.Player.version = 'v3.2.2-uncompiled';
6341
+ shaka.Player.version = 'v3.2.3-uncompiled';
6335
6342
 
6336
6343
  // Initialize the deprecation system using the version string we just set
6337
6344
  // on the player.
@@ -24,8 +24,6 @@ shaka.polyfill.MediaCapabilities = class {
24
24
  * @export
25
25
  */
26
26
  static install() {
27
- shaka.log.debug('MediaCapabilities: install');
28
-
29
27
  // Since MediaCapabilities is not fully supported on Chromecast yet, we
30
28
  // should always install polyfill for Chromecast.
31
29
  // TODO: re-evaluate MediaCapabilities in the future versions of Chromecast.
@@ -40,21 +38,28 @@ shaka.polyfill.MediaCapabilities = class {
40
38
  // TODO: re-evaluate MediaCapabilities in the future versions of PS5
41
39
  // Browsers.
42
40
  if (!shaka.util.Platform.isChromecast() &&
43
- !shaka.util.Platform.isApple() &&
44
- !shaka.util.Platform.isPS5() &&
45
- navigator.mediaCapabilities) {
46
- shaka.log.debug(
41
+ !shaka.util.Platform.isApple() &&
42
+ !shaka.util.Platform.isPS5() &&
43
+ navigator.mediaCapabilities) {
44
+ shaka.log.info(
47
45
  'MediaCapabilities: Native mediaCapabilities support found.');
48
- } else {
49
- if (!navigator.mediaCapabilities) {
50
- navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
51
- }
52
- navigator.mediaCapabilities.decodingInfo =
53
- shaka.polyfill.MediaCapabilities.decodingInfo_;
46
+ return;
47
+ }
48
+
49
+ shaka.log.info('MediaCapabilities: install');
50
+
51
+ if (!navigator.mediaCapabilities) {
52
+ navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
54
53
  }
55
- // Workaround because in Apple browsers there is no way to overwrite
56
- // MediaCapabilities.
57
- window.shakaMediaCapabilities = navigator.mediaCapabilities;
54
+
55
+ // Keep the patched MediaCapabilities object from being garbage-collected in
56
+ // Safari.
57
+ // See https://github.com/google/shaka-player/issues/3696#issuecomment-1009472718
58
+ shaka.polyfill.MediaCapabilities.originalMcap =
59
+ navigator.mediaCapabilities;
60
+
61
+ navigator.mediaCapabilities.decodingInfo =
62
+ shaka.polyfill.MediaCapabilities.decodingInfo_;
58
63
  }
59
64
 
60
65
  /**
@@ -182,6 +187,16 @@ shaka.polyfill.MediaCapabilities = class {
182
187
  }
183
188
  };
184
189
 
190
+ /**
191
+ * A copy of the MediaCapabilities instance, to prevent Safari from
192
+ * garbage-collecting the polyfilled method on it. We make it public and export
193
+ * it to ensure that it is not stripped out by the compiler.
194
+ *
195
+ * @type {MediaCapabilities}
196
+ * @export
197
+ */
198
+ shaka.polyfill.MediaCapabilities.originalMcap = null;
199
+
185
200
  // Install at a lower priority than MediaSource polyfill, so that we have
186
201
  // MediaSource available first.
187
202
  shaka.polyfill.register(shaka.polyfill.MediaCapabilities.install, -1);
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
@@ -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() +
@@ -753,7 +773,7 @@ shaka.text.TtmlTextParser = class {
753
773
  return null;
754
774
  }
755
775
 
756
- const attr = XmlUtils.getAttributeNS(region, ttsNs, attribute);
776
+ const attr = XmlUtils.getAttributeNSList(region, ttsNs, attribute);
757
777
  if (attr) {
758
778
  return attr;
759
779
  }
@@ -778,7 +798,7 @@ shaka.text.TtmlTextParser = class {
778
798
 
779
799
  // Styling on elements should take precedence
780
800
  // over the main styling attributes
781
- const elementAttribute = XmlUtils.getAttributeNS(
801
+ const elementAttribute = XmlUtils.getAttributeNSList(
782
802
  cueElement,
783
803
  ttsNs,
784
804
  attribute);
@@ -821,7 +841,7 @@ shaka.text.TtmlTextParser = class {
821
841
 
822
842
  if (!styleAttributeValue) {
823
843
  // Fall back to tts namespace.
824
- styleAttributeValue = XmlUtils.getAttributeNS(
844
+ styleAttributeValue = XmlUtils.getAttributeNSList(
825
845
  inheritedStyles[i],
826
846
  ttsNs,
827
847
  attribute);
@@ -1276,20 +1296,26 @@ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
1276
1296
  * document, not just "ttp:", so we use this with getAttributeNS() to ensure
1277
1297
  * that we support arbitrary namespace names.
1278
1298
  *
1279
- * @const {string}
1299
+ * @const {!Array.<string>}
1280
1300
  * @private
1281
1301
  */
1282
- 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
+ ];
1283
1306
 
1284
1307
  /**
1285
1308
  * The namespace URL for TTML styles. Can be assigned any name in the TTML
1286
1309
  * document, not just "tts:", so we use this with getAttributeNS() to ensure
1287
1310
  * that we support arbitrary namespace names.
1288
1311
  *
1289
- * @const {string}
1312
+ * @const {!Array.<string>}
1290
1313
  * @private
1291
1314
  */
1292
- 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
+ ];
1293
1319
 
1294
1320
  /**
1295
1321
  * The namespace URL for EBU TTML styles. Can be assigned any name in the TTML