shaka-player 3.2.1 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/AUTHORS +3 -0
  2. package/CHANGELOG.md +57 -0
  3. package/CONTRIBUTORS +3 -0
  4. package/build/generateExterns.js +1 -1
  5. package/demo/common/assets.js +1 -1
  6. package/demo/index.html +1 -1
  7. package/dist/controls.css +2 -2
  8. package/dist/controls.css.map +1 -1
  9. package/dist/demo.compiled.debug.js +1 -1
  10. package/dist/demo.compiled.js +1 -1
  11. package/dist/deps.js +1 -1
  12. package/dist/locales.js +37 -37
  13. package/dist/receiver.compiled.debug.js +1 -1
  14. package/dist/receiver.compiled.js +1 -1
  15. package/dist/shaka-player.compiled.d.ts +1 -1
  16. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  17. package/dist/shaka-player.compiled.debug.externs.js +1 -1
  18. package/dist/shaka-player.compiled.debug.js +188 -181
  19. package/dist/shaka-player.compiled.debug.map +3 -3
  20. package/dist/shaka-player.compiled.externs.js +1 -1
  21. package/dist/shaka-player.compiled.js +795 -790
  22. package/dist/shaka-player.compiled.map +3 -3
  23. package/dist/shaka-player.ui.d.ts +1 -1
  24. package/dist/shaka-player.ui.debug.d.ts +1 -1
  25. package/dist/shaka-player.ui.debug.externs.js +3 -3
  26. package/dist/shaka-player.ui.debug.js +217 -210
  27. package/dist/shaka-player.ui.debug.map +4 -4
  28. package/dist/shaka-player.ui.externs.js +3 -3
  29. package/dist/shaka-player.ui.js +945 -940
  30. package/dist/shaka-player.ui.map +4 -4
  31. package/docs/tutorials/fairplay.md +1 -1
  32. package/docs/tutorials/ui.md +5 -5
  33. package/karma.conf.js +1 -0
  34. package/lib/ads/client_side_ad_manager.js +46 -86
  35. package/lib/ads/server_side_ad_manager.js +7 -10
  36. package/lib/cast/cast_proxy.js +3 -2
  37. package/lib/cast/cast_receiver.js +2 -1
  38. package/lib/cast/cast_sender.js +2 -2
  39. package/lib/dash/dash_parser.js +93 -2
  40. package/lib/dash/segment_list.js +1 -1
  41. package/lib/dash/segment_template.js +1 -1
  42. package/lib/hls/hls_parser.js +30 -17
  43. package/lib/media/drm_engine.js +7 -0
  44. package/lib/media/gap_jumping_controller.js +4 -2
  45. package/lib/media/media_source_engine.js +1 -2
  46. package/lib/media/streaming_engine.js +2 -1
  47. package/lib/net/networking_engine.js +2 -1
  48. package/lib/player.js +26 -25
  49. package/lib/polyfill/media_capabilities.js +7 -1
  50. package/lib/polyfill/mediasource.js +6 -0
  51. package/lib/polyfill/orientation.js +1 -1
  52. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  53. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  54. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  55. package/lib/text/mp4_ttml_parser.js +3 -0
  56. package/lib/text/text_engine.js +14 -2
  57. package/lib/text/ttml_text_parser.js +5 -0
  58. package/lib/text/ui_text_displayer.js +85 -37
  59. package/lib/util/fake_event.js +53 -7
  60. package/lib/util/periods.js +4 -1
  61. package/lib/util/platform.js +13 -1
  62. package/lib/util/stream_utils.js +13 -0
  63. package/package.json +1 -1
  64. package/test/cast/cast_proxy_unit.js +12 -6
  65. package/test/cast/cast_utils_unit.js +1 -1
  66. package/test/dash/dash_parser_manifest_unit.js +187 -1
  67. package/test/hls/hls_parser_unit.js +5 -1
  68. package/test/media/closed_caption_parser_unit.js +6 -1
  69. package/test/media/drm_engine_unit.js +44 -0
  70. package/test/player_integration.js +38 -0
  71. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  72. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  73. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  74. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  75. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  76. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  77. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  78. package/test/test/assets/3675/dash_0.mpd +30 -0
  79. package/test/test/assets/3675/dash_1.mpd +30 -0
  80. package/test/test/assets/3675/dash_2.mpd +30 -0
  81. package/test/test/assets/3675/dash_3.mpd +30 -0
  82. package/test/test/assets/3675/dash_4.mpd +30 -0
  83. package/test/test/assets/3675/dash_5.mpd +26 -0
  84. package/test/test/assets/3675/init-stream0.m4s +0 -0
  85. package/test/test/util/fake_ad_manager.js +1 -1
  86. package/test/text/ttml_text_parser_unit.js +9 -0
  87. package/test/text/ui_text_displayer_unit.js +29 -17
  88. package/test/ui/localization_unit.js +15 -14
  89. package/test/ui/text_displayer_layout_unit.js +13 -1
  90. package/test/util/stream_utils_unit.js +35 -0
  91. package/ui/cast_button.js +2 -3
  92. package/ui/controls.js +4 -6
  93. package/ui/less/containers.less +3 -7
  94. package/ui/locales/ru.json +6 -6
  95. package/ui/localization.js +28 -51
  96. package/ui/pip_button.js +2 -3
@@ -418,18 +418,16 @@ shaka.hls.HlsParser = class {
418
418
  const uri = tag.getAttributeValue('URI');
419
419
  const language = tag.getAttributeValue('LANGUAGE');
420
420
  const value = tag.getAttributeValue('VALUE');
421
- const data = {
422
- 'id': id,
423
- };
421
+ const data = (new Map()).set('id', id);
424
422
  if (uri) {
425
- data.uri =
426
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri);
423
+ data.set('uri',
424
+ shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
427
425
  }
428
426
  if (language) {
429
- data.language = language;
427
+ data.set('language', language);
430
428
  }
431
429
  if (value) {
432
- data.value = value;
430
+ data.set('value', value);
433
431
  }
434
432
  const event = new shaka.util.FakeEvent('sessiondata', data);
435
433
  if (this.playerInterface_) {
@@ -534,6 +532,30 @@ shaka.hls.HlsParser = class {
534
532
  }
535
533
  }
536
534
 
535
+ // Now that the content has been fit, notify segments.
536
+ this.segmentsToNotifyByStream_ = [];
537
+ const streamsToNotify = [];
538
+ for (const variant of variants) {
539
+ for (const stream of [variant.video, variant.audio]) {
540
+ if (stream) {
541
+ streamsToNotify.push(stream);
542
+ }
543
+ }
544
+ }
545
+ await Promise.all(streamsToNotify.map(async (stream) => {
546
+ await stream.createSegmentIndex();
547
+ }));
548
+ for (const stream of streamsToNotify) {
549
+ this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
550
+ }
551
+ this.notifySegments_();
552
+
553
+ // This asserts that the live edge is being calculated from segment times.
554
+ // For VOD and event streams, this check should still pass.
555
+ goog.asserts.assert(
556
+ !this.presentationTimeline_.usingPresentationStartTime(),
557
+ 'We should not be using the presentation start time in HLS!');
558
+
537
559
  this.manifest_ = {
538
560
  presentationTimeline: this.presentationTimeline_,
539
561
  variants,
@@ -1184,7 +1206,7 @@ shaka.hls.HlsParser = class {
1184
1206
  // "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
1185
1207
  // only be selected explicitly by the user, and we don't have a way to
1186
1208
  // represent that in our model.
1187
- const defaultAttrValue = tag.getAttribute('DEFAULT');
1209
+ const defaultAttrValue = tag.getAttributeValue('DEFAULT');
1188
1210
  const primary = defaultAttrValue == 'YES';
1189
1211
 
1190
1212
  const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
@@ -1617,12 +1639,6 @@ shaka.hls.HlsParser = class {
1617
1639
  }
1618
1640
 
1619
1641
  this.notifySegments_();
1620
-
1621
- // This asserts that the live edge is being calculated from segment times.
1622
- // For VOD and event streams, this check should still pass.
1623
- goog.asserts.assert(
1624
- !this.presentationTimeline_.usingPresentationStartTime(),
1625
- 'We should not be using the presentation start time in HLS!');
1626
1642
  }
1627
1643
 
1628
1644
  /**
@@ -2009,9 +2025,6 @@ shaka.hls.HlsParser = class {
2009
2025
  }
2010
2026
  }
2011
2027
 
2012
- this.segmentsToNotifyByStream_.push(references);
2013
- this.notifySegments_();
2014
-
2015
2028
  return references;
2016
2029
  }
2017
2030
 
@@ -1825,9 +1825,16 @@ shaka.media.DrmEngine = class {
1825
1825
  // audio adaptations, so we shouldn't have to worry about checking
1826
1826
  // robustness.
1827
1827
  if (drm1.keySystem == drm2.keySystem) {
1828
+ /** @type {Array<shaka.extern.InitDataOverride>} */
1828
1829
  let initData = [];
1829
1830
  initData = initData.concat(drm1.initData || []);
1830
1831
  initData = initData.concat(drm2.initData || []);
1832
+ initData = initData.filter((d, i) => {
1833
+ return d.keyId === undefined || i === initData.findIndex((d2) => {
1834
+ return d2.keyId === d.keyId;
1835
+ });
1836
+ });
1837
+
1831
1838
  const keyIds = drm1.keyIds && drm2.keyIds ?
1832
1839
  new Set([...drm1.keyIds, ...drm2.keyIds]) :
1833
1840
  drm1.keyIds || drm2.keyIds;
@@ -217,8 +217,10 @@ shaka.media.GapJumpingController = class {
217
217
  this.didFireLargeGap_ = true;
218
218
 
219
219
  // Event firing is synchronous.
220
- const event = new shaka.util.FakeEvent(
221
- 'largegap', {'currentTime': currentTime, 'gapSize': jumpSize});
220
+ const data = new Map()
221
+ .set('currentTime', currentTime)
222
+ .set('gapSize', jumpSize);
223
+ const event = new shaka.util.FakeEvent('largegap', data);
222
224
  event.cancelable = true;
223
225
  this.onEvent_(event);
224
226
 
@@ -48,8 +48,7 @@ shaka.media.MediaSourceEngine = class {
48
48
  * @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)=}
49
49
  * onMetadata
50
50
  */
51
- constructor(video, closedCaptionParser, textDisplayer,
52
- onMetadata) {
51
+ constructor(video, closedCaptionParser, textDisplayer, onMetadata) {
53
52
  /** @private {HTMLMediaElement} */
54
53
  this.video_ = video;
55
54
 
@@ -1662,7 +1662,8 @@ shaka.media.StreamingEngine = class {
1662
1662
 
1663
1663
  // Dispatch an event to notify the application about the emsg box.
1664
1664
  const eventName = shaka.Player.EventName.Emsg;
1665
- const event = new shaka.util.FakeEvent(eventName, {'detail': emsg});
1665
+ const data = (new Map()).set('detail', emsg);
1666
+ const event = new shaka.util.FakeEvent(eventName, data);
1666
1667
  this.playerInterface_.onEvent(event);
1667
1668
  }
1668
1669
  }
@@ -544,7 +544,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
544
544
  }
545
545
 
546
546
  if (error.severity == shaka.util.Error.Severity.RECOVERABLE) {
547
- const event = new shaka.util.FakeEvent('retry', {'error': error});
547
+ const data = (new Map()).set('error', error);
548
+ const event = new shaka.util.FakeEvent('retry', data);
548
549
  this.dispatchEvent(event);
549
550
 
550
551
  // Move to the next URI.
package/lib/player.js CHANGED
@@ -598,7 +598,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
598
598
  enterNode: (node, has, wants) => {
599
599
  this.dispatchEvent(this.makeEvent_(
600
600
  /* name= */ shaka.Player.EventName.OnStateChange,
601
- /* data= */ {'state': node.name}));
601
+ /* data= */ (new Map()).set('state', node.name)));
602
602
 
603
603
  const action = actions.get(node);
604
604
  return action(has, wants);
@@ -629,7 +629,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
629
629
  onIdle: (node) => {
630
630
  this.dispatchEvent(this.makeEvent_(
631
631
  /* name= */ shaka.Player.EventName.OnStateIdle,
632
- /* data= */ {'state': node.name}));
632
+ /* data= */ (new Map()).set('state', node.name)));
633
633
  },
634
634
  };
635
635
 
@@ -649,7 +649,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
649
649
 
650
650
  /**
651
651
  * @param {!shaka.Player.EventName} name
652
- * @param {Object=} data
652
+ * @param {Map.<string, Object>=} data
653
653
  * @return {!shaka.util.FakeEvent}
654
654
  * @private
655
655
  */
@@ -2417,12 +2417,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2417
2417
  goog.asserts.assert(!endTime || startTime <= endTime,
2418
2418
  'Metadata start time should be less or equal to the end time!');
2419
2419
  const eventName = shaka.Player.EventName.Metadata;
2420
- const data = {
2421
- startTime: startTime,
2422
- endTime: endTime,
2423
- metadataType: metadataType,
2424
- payload: payload,
2425
- };
2420
+ const data = new Map()
2421
+ .set('startTime', startTime)
2422
+ .set('endTime', endTime)
2423
+ .set('metadataType', metadataType)
2424
+ .set('payload', payload);
2426
2425
  this.dispatchEvent(this.makeEvent_(eventName, data));
2427
2426
  }
2428
2427
 
@@ -5038,7 +5037,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5038
5037
  // Surface the buffering event so that the app knows if/when we are
5039
5038
  // buffering.
5040
5039
  const eventName = shaka.Player.EventName.Buffering;
5041
- this.dispatchEvent(this.makeEvent_(eventName, {'buffering': isBuffering}));
5040
+ const data = (new Map()).set('buffering', isBuffering);
5041
+ this.dispatchEvent(this.makeEvent_(eventName, data));
5042
5042
  }
5043
5043
 
5044
5044
  /**
@@ -5400,10 +5400,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5400
5400
  onAdaptation_(from, to) {
5401
5401
  // Delay the 'adaptation' event so that StreamingEngine has time to absorb
5402
5402
  // the changes before the user tries to query it.
5403
- const event = this.makeEvent_(shaka.Player.EventName.Adaptation, {
5404
- oldTrack: from,
5405
- newTrack: to,
5406
- });
5403
+ const data = new Map()
5404
+ .set('oldTrack', from)
5405
+ .set('newTrack', to);
5406
+ const event = this.makeEvent_(shaka.Player.EventName.Adaptation, data);
5407
5407
  this.delayDispatchEvent_(event);
5408
5408
  }
5409
5409
 
@@ -5427,10 +5427,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5427
5427
  onVariantChanged_(from, to) {
5428
5428
  // Delay the 'variantchanged' event so StreamingEngine has time to absorb
5429
5429
  // the changes before the user tries to query it.
5430
- const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, {
5431
- oldTrack: from,
5432
- newTrack: to,
5433
- });
5430
+ const data = new Map()
5431
+ .set('oldTrack', from)
5432
+ .set('newTrack', to);
5433
+ const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, data);
5434
5434
  this.delayDispatchEvent_(event);
5435
5435
  }
5436
5436
 
@@ -5453,10 +5453,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5453
5453
 
5454
5454
  /** @private */
5455
5455
  onAbrStatusChanged_() {
5456
- const event = this.makeEvent_(shaka.Player.EventName.AbrStatusChanged, {
5457
- newStatus: this.config_.abr.enabled,
5458
- });
5459
- this.delayDispatchEvent_(event);
5456
+ const data = (new Map()).set('newStatus', this.config_.abr.enabled);
5457
+ this.delayDispatchEvent_(this.makeEvent_(
5458
+ shaka.Player.EventName.AbrStatusChanged, data));
5460
5459
  }
5461
5460
 
5462
5461
  /**
@@ -5473,7 +5472,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5473
5472
  }
5474
5473
 
5475
5474
  const eventName = shaka.Player.EventName.Error;
5476
- const event = this.makeEvent_(eventName, {'detail': error});
5475
+ const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
5477
5476
  this.dispatchEvent(event);
5478
5477
  if (event.defaultPrevented) {
5479
5478
  error.handled = true;
@@ -5502,7 +5501,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5502
5501
  eventElement: region.eventElement,
5503
5502
  };
5504
5503
 
5505
- this.dispatchEvent(this.makeEvent_(eventName, {detail: clone}));
5504
+ const data = (new Map()).set('detail', clone);
5505
+ this.dispatchEvent(this.makeEvent_(eventName, data));
5506
5506
  }
5507
5507
 
5508
5508
  /**
@@ -6331,7 +6331,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6331
6331
  * @define {string} A version number taken from git at compile time.
6332
6332
  * @export
6333
6333
  */
6334
- shaka.Player.version = 'v3.2.1-uncompiled';
6334
+ shaka.Player.version = 'v3.2.2-uncompiled';
6335
6335
 
6336
6336
  // Initialize the deprecation system using the version string we just set
6337
6337
  // on the player.
@@ -6375,6 +6375,7 @@ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
6375
6375
  'mpg': 'video/mpeg',
6376
6376
  'mpeg': 'video/mpeg',
6377
6377
  'm3u8': 'application/x-mpegurl',
6378
+ 'mpd': 'application/dash+xml',
6378
6379
  'mp3': 'audio/mpeg',
6379
6380
  'aac': 'audio/aac',
6380
6381
  'flac': 'audio/flac',
@@ -34,8 +34,14 @@ shaka.polyfill.MediaCapabilities = class {
34
34
  // See: https://github.com/google/shaka-player/issues/3530
35
35
  // TODO: re-evaluate MediaCapabilities in the future versions of Apple
36
36
  // Browsers.
37
+ // Since MediaCapabilities implementation is buggy in PS5 browsers, we
38
+ // should always install polyfill for PS5 browsers.
39
+ // See: https://github.com/google/shaka-player/issues/3582
40
+ // TODO: re-evaluate MediaCapabilities in the future versions of PS5
41
+ // Browsers.
37
42
  if (!shaka.util.Platform.isChromecast() &&
38
43
  !shaka.util.Platform.isApple() &&
44
+ !shaka.util.Platform.isPS5() &&
39
45
  navigator.mediaCapabilities) {
40
46
  shaka.log.debug(
41
47
  'MediaCapabilities: Native mediaCapabilities support found.');
@@ -149,7 +155,7 @@ shaka.polyfill.MediaCapabilities = class {
149
155
  sessionTypes: mediaCapkeySystemConfig.sessionTypes,
150
156
  };
151
157
 
152
- // Only add the audio video capablities if they have valid data.
158
+ // Only add the audio video capabilities if they have valid data.
153
159
  // Otherwise the query will fail.
154
160
  if (audioCapabilities.length) {
155
161
  mediaKeySystemConfig.audioCapabilities = audioCapabilities;
@@ -208,6 +208,12 @@ shaka.polyfill.MediaSource = class {
208
208
  static patchVp09_() {
209
209
  const originalIsTypeSupported = MediaSource.isTypeSupported;
210
210
 
211
+ if (shaka.util.Platform.isWebOS()) {
212
+ // Don't do this on LG webOS as otherwise it is unable
213
+ // to play vp09 at all.
214
+ return;
215
+ }
216
+
211
217
  MediaSource.isTypeSupported = (mimeType) => {
212
218
  // Split the MIME type into its various parameters.
213
219
  const pieces = mimeType.split(/ *; */);
@@ -92,7 +92,7 @@ class extends shaka.util.FakeEventTarget {
92
92
 
93
93
  /** Dispatch a 'change' event. */
94
94
  dispatchChangeEvent() {
95
- const event = new shaka.util.FakeEvent('change', {});
95
+ const event = new shaka.util.FakeEvent('change');
96
96
  this.dispatchEvent(event);
97
97
  }
98
98
 
@@ -567,10 +567,10 @@ class extends shaka.util.FakeEventTarget {
567
567
 
568
568
  const isNew = this.keyStatuses.getStatus() == undefined;
569
569
 
570
- const event2 = new shaka.util.FakeEvent('message', {
571
- messageType: isNew ? 'license-request' : 'license-renewal',
572
- message: shaka.util.BufferUtils.toArrayBuffer(event.message),
573
- });
570
+ const data = new Map()
571
+ .set('messageType', isNew ? 'license-request' : 'license-renewal')
572
+ .set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
573
+ const event2 = new shaka.util.FakeEvent('message', data);
574
574
 
575
575
  this.dispatchEvent(event2);
576
576
  }
@@ -512,10 +512,10 @@ class extends shaka.util.FakeEventTarget {
512
512
 
513
513
  const isNew = this.keyStatuses.getStatus() == undefined;
514
514
 
515
- const event2 = new shaka.util.FakeEvent('message', {
516
- messageType: isNew ? 'license-request' : 'license-renewal',
517
- message: shaka.util.BufferUtils.toArrayBuffer(event.message),
518
- });
515
+ const data = new Map()
516
+ .set('messageType', isNew ? 'license-request' : 'license-renewal')
517
+ .set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
518
+ const event2 = new shaka.util.FakeEvent('message', data);
519
519
 
520
520
  this.dispatchEvent(event2);
521
521
  }
@@ -451,10 +451,10 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeys = class {
451
451
 
452
452
  const isNew = session.keyStatuses.getStatus() == undefined;
453
453
 
454
- const event2 = new shaka.util.FakeEvent('message', {
455
- messageType: isNew ? 'licenserequest' : 'licenserenewal',
456
- message: event.message,
457
- });
454
+ const data = new Map()
455
+ .set('messageType', isNew ? 'licenserequest' : 'licenserenewal')
456
+ .set('message', event.message);
457
+ const event2 = new shaka.util.FakeEvent('message', data);
458
458
 
459
459
  session.generated();
460
460
  session.dispatchEvent(event2);
@@ -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_ &&
@@ -199,6 +199,11 @@ shaka.text.TtmlTextParser = class {
199
199
  /** @type {Element} */
200
200
  let parentElement = /** @type {Element} */(cueNode.parentNode);
201
201
 
202
+ if (cueNode.nodeType == Node.COMMENT_NODE) {
203
+ // The comments do not contain information that interests us here.
204
+ return null;
205
+ }
206
+
202
207
  if (cueNode.nodeType == Node.TEXT_NODE) {
203
208
  // This should generate an "anonymous span" according to the TTML spec.
204
209
  // So pretend the element was a <span>. parentElement was set above, so