shaka-player 4.13.0 → 4.13.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 (67) hide show
  1. package/CONTRIBUTORS +1 -0
  2. package/dist/deps.js +4 -4
  3. package/dist/locales.js +36 -36
  4. package/dist/shaka-player.compiled.d.ts +1 -1
  5. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  6. package/dist/shaka-player.compiled.debug.externs.js +2 -9
  7. package/dist/shaka-player.compiled.debug.js +491 -478
  8. package/dist/shaka-player.compiled.debug.map +4 -4
  9. package/dist/shaka-player.compiled.externs.js +2 -9
  10. package/dist/shaka-player.compiled.js +1455 -1443
  11. package/dist/shaka-player.compiled.map +4 -4
  12. package/dist/shaka-player.ui.d.ts +1 -1
  13. package/dist/shaka-player.ui.debug.d.ts +1 -1
  14. package/dist/shaka-player.ui.debug.externs.js +2 -9
  15. package/dist/shaka-player.ui.debug.js +544 -530
  16. package/dist/shaka-player.ui.debug.map +4 -4
  17. package/dist/shaka-player.ui.externs.js +2 -9
  18. package/dist/shaka-player.ui.js +1627 -1613
  19. package/dist/shaka-player.ui.map +4 -4
  20. package/eslint.config.mjs +14 -1
  21. package/lib/cast/cast_proxy.js +50 -12
  22. package/lib/cast/cast_receiver.js +6 -9
  23. package/lib/cast/cast_sender.js +14 -11
  24. package/lib/cast/cast_utils.js +43 -46
  25. package/lib/cea/mp4_cea_parser.js +23 -23
  26. package/lib/dash/content_protection.js +1 -1
  27. package/lib/dash/dash_parser.js +39 -29
  28. package/lib/dash/mpd_utils.js +8 -9
  29. package/lib/dash/segment_list.js +2 -2
  30. package/lib/dash/segment_template.js +9 -9
  31. package/lib/debug/log.js +16 -17
  32. package/lib/hls/hls_parser.js +122 -99
  33. package/lib/media/closed_caption_parser.js +5 -5
  34. package/lib/media/content_workarounds.js +78 -0
  35. package/lib/media/example_based_criteria.js +4 -3
  36. package/lib/media/manifest_parser.js +8 -12
  37. package/lib/media/media_source_engine.js +136 -119
  38. package/lib/media/preload_manager.js +10 -5
  39. package/lib/media/region_observer.js +9 -8
  40. package/lib/media/region_timeline.js +10 -8
  41. package/lib/media/streaming_engine.js +25 -9
  42. package/lib/mss/mss_parser.js +6 -7
  43. package/lib/net/http_plugin_utils.js +1 -1
  44. package/lib/net/networking_engine.js +7 -7
  45. package/lib/net/networking_utils.js +38 -39
  46. package/lib/offline/stream_bandwidth_estimator.js +8 -8
  47. package/lib/player.js +98 -47
  48. package/lib/polyfill/media_capabilities.js +8 -9
  49. package/lib/text/cue.js +6 -2
  50. package/lib/text/text_engine.js +7 -7
  51. package/lib/text/ttml_text_parser.js +14 -16
  52. package/lib/text/ui_text_displayer.js +25 -4
  53. package/lib/transmuxer/ac3_transmuxer.js +6 -1
  54. package/lib/transmuxer/transmuxer_engine.js +6 -6
  55. package/lib/util/manifest_parser_utils.js +7 -8
  56. package/lib/util/mp4_generator.js +23 -8
  57. package/lib/util/mp4_parser.js +10 -10
  58. package/lib/util/multi_map.js +22 -20
  59. package/lib/util/platform.js +40 -9
  60. package/lib/util/player_configuration.js +1 -1
  61. package/lib/util/stream_utils.js +35 -17
  62. package/package.json +2 -2
  63. package/project-words.txt +1 -0
  64. package/third_party/closure-uri/uri.js +13 -13
  65. package/ui/ad_statistics_button.js +17 -16
  66. package/ui/audio_language_selection.js +6 -1
  67. package/ui/statistics_button.js +36 -35
package/lib/player.js CHANGED
@@ -54,6 +54,7 @@ goog.require('shaka.util.Functional');
54
54
  goog.require('shaka.util.IDestroyable');
55
55
  goog.require('shaka.util.LanguageUtils');
56
56
  goog.require('shaka.util.ManifestParserUtils');
57
+ goog.require('shaka.util.MapUtils');
57
58
  goog.require('shaka.util.MediaReadyState');
58
59
  goog.require('shaka.util.MimeUtils');
59
60
  goog.require('shaka.util.Mutex');
@@ -698,7 +699,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
698
699
  /** @private {shaka.media.BufferingObserver} */
699
700
  this.bufferObserver_ = null;
700
701
 
701
- /** @private {shaka.media.RegionTimeline} */
702
+ /**
703
+ * @private {shaka.media.RegionTimeline<
704
+ * shaka.extern.TimelineRegionInfo>}
705
+ */
702
706
  this.regionTimeline_ = null;
703
707
 
704
708
  /** @private {shaka.util.CmcdManager} */
@@ -1083,7 +1087,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1083
1087
  * @export
1084
1088
  */
1085
1089
  static registerSupportPlugin(name, callback) {
1086
- shaka.Player.supportPlugins_[name] = callback;
1090
+ shaka.Player.supportPlugins_.set(name, callback);
1087
1091
  }
1088
1092
 
1089
1093
  /**
@@ -1172,9 +1176,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1172
1176
  };
1173
1177
 
1174
1178
  const plugins = shaka.Player.supportPlugins_;
1175
- for (const name in plugins) {
1176
- ret[name] = plugins[name]();
1177
- }
1179
+ plugins.forEach((value, key) => {
1180
+ ret[key] = value();
1181
+ });
1178
1182
 
1179
1183
  return ret;
1180
1184
  }
@@ -3591,6 +3595,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3591
3595
 
3592
3596
  // Create the region observer. This will allow us to notify the app when we
3593
3597
  // move in and out of timeline regions.
3598
+ /** @type {!shaka.media.RegionObserver<shaka.extern.TimelineRegionInfo>} */
3594
3599
  const regionObserver = new shaka.media.RegionObserver(
3595
3600
  this.regionTimeline_, startsPastZero);
3596
3601
 
@@ -3808,10 +3813,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3808
3813
  netEngine: this.networkingEngine_,
3809
3814
  onError: (error) => this.onError_(error),
3810
3815
  onEvent: (event) => this.dispatchEvent(event),
3811
- onSegmentAppended: (reference, stream) => {
3816
+ onSegmentAppended: (reference, stream, isMuxed) => {
3812
3817
  this.onSegmentAppended_(
3813
- reference.startTime, reference.endTime, stream.type,
3814
- stream.codecs.includes(','));
3818
+ reference.startTime, reference.endTime, stream.type, isMuxed);
3815
3819
  },
3816
3820
  onInitSegmentAppended: (position, initSegment) => {
3817
3821
  const mediaQuality = initSegment.getMediaQuality();
@@ -4723,7 +4727,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4723
4727
  shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
4724
4728
  return;
4725
4729
  }
4726
- this.trickPlayEventManager_.removeAll();
4727
4730
 
4728
4731
  this.playRateController_.set(rate);
4729
4732
 
@@ -4732,26 +4735,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4732
4735
  this.streamingEngine_.setTrickPlay(
4733
4736
  useTrickPlayTrack && Math.abs(rate) > 1);
4734
4737
  }
4735
- if (this.isLive()) {
4736
- this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
4737
- const currentTime = this.video_.currentTime;
4738
- const seekRange = this.seekRange();
4739
- const safeSeekOffset = this.config_.streaming.safeSeekOffset;
4740
-
4741
- // Cancel trick play if we hit the beginning or end of the seekable
4742
- // (Sub-second accuracy not required here)
4743
- if (rate > 0) {
4744
- if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
4745
- this.cancelTrickPlay();
4746
- }
4747
- } else {
4748
- if (Math.floor(currentTime) <=
4749
- Math.floor(seekRange.start + safeSeekOffset)) {
4750
- this.cancelTrickPlay();
4751
- }
4752
- }
4753
- });
4754
- }
4738
+ this.setupTrickPlayEventListeners_(rate);
4755
4739
  }
4756
4740
 
4757
4741
  /**
@@ -5187,7 +5171,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5187
5171
  if (this.config_.abr.enabled && (active.video != variant.video ||
5188
5172
  (active.audio && variant.audio &&
5189
5173
  active.audio.language == variant.audio.language &&
5190
- active.audio.channelsCount == variant.audio.channelsCount))) {
5174
+ active.audio.channelsCount == variant.audio.channelsCount &&
5175
+ active.audio.label == variant.audio.label))) {
5191
5176
  shaka.log.alwaysWarn('Changing tracks while abr manager is enabled ' +
5192
5177
  'will likely result in the selected track ' +
5193
5178
  'being overridden. Consider disabling abr ' +
@@ -5296,10 +5281,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5296
5281
  * @param {number=} safeMargin
5297
5282
  * @param {string=} codec
5298
5283
  * @param {boolean=} spatialAudio
5284
+ * @param {string=} label
5299
5285
  * @export
5300
5286
  */
5301
5287
  selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0,
5302
- codec = '', spatialAudio = false) {
5288
+ codec = '', spatialAudio = false, label = '') {
5303
5289
  const selectMediaSourceMode = () => {
5304
5290
  this.currentAdaptationSetCriteria_ =
5305
5291
  this.config_.adaptationSetCriteriaFactory();
@@ -5310,7 +5296,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5310
5296
  hdrLevel: '',
5311
5297
  spatialAudio: spatialAudio || false,
5312
5298
  videoLayout: '',
5313
- audioLabel: '',
5299
+ audioLabel: label || '',
5314
5300
  videoLabel: '',
5315
5301
  codecSwitchingStrategy:
5316
5302
  this.config_.mediaSource.codecSwitchingStrategy,
@@ -5823,12 +5809,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5823
5809
  * is playing.
5824
5810
  */
5825
5811
  getLiveLatency() {
5826
- if (!this.video_) {
5812
+ if (!this.video_ || !this.video_.currentTime) {
5827
5813
  return null;
5828
5814
  }
5829
5815
  const now = this.getPresentationStartTimeAsDate().getTime() +
5830
5816
  this.video_.currentTime * 1000;
5831
- return Date.now() - now;
5817
+
5818
+ return Math.floor(Date.now() - now);
5832
5819
  }
5833
5820
 
5834
5821
  /**
@@ -6865,6 +6852,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6865
6852
  // The playback rate has changed. This could be us or someone else.
6866
6853
  // If this was us, setting the rate again will be a no-op.
6867
6854
  this.playRateController_.set(newRate);
6855
+
6856
+ if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
6857
+ this.abrManager_.playbackRateChanged(newRate);
6858
+ }
6859
+ this.setupTrickPlayEventListeners_(newRate);
6868
6860
  }
6869
6861
 
6870
6862
  const event = shaka.Player.makeEvent_(
@@ -6872,6 +6864,36 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6872
6864
  this.dispatchEvent(event);
6873
6865
  }
6874
6866
 
6867
+ /**
6868
+ * Configures all the necessary listeners when trick play is being performed.
6869
+ *
6870
+ * @param {number} rate
6871
+ * @private
6872
+ */
6873
+ setupTrickPlayEventListeners_(rate) {
6874
+ this.trickPlayEventManager_.removeAll();
6875
+ if (this.isLive()) {
6876
+ this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
6877
+ const currentTime = this.video_.currentTime;
6878
+ const seekRange = this.seekRange();
6879
+ const safeSeekOffset = this.config_.streaming.safeSeekOffset;
6880
+
6881
+ // Cancel trick play if we hit the beginning or end of the seekable
6882
+ // (Sub-second accuracy not required here)
6883
+ if (rate > 0) {
6884
+ if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
6885
+ this.cancelTrickPlay();
6886
+ }
6887
+ } else {
6888
+ if (Math.floor(currentTime) <=
6889
+ Math.floor(seekRange.start + safeSeekOffset)) {
6890
+ this.cancelTrickPlay();
6891
+ }
6892
+ }
6893
+ });
6894
+ }
6895
+ }
6896
+
6875
6897
  /**
6876
6898
  * Try updating the state history. If the player has not finished
6877
6899
  * initializing, this will be a no-op.
@@ -8040,6 +8062,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
8040
8062
 
8041
8063
  // Only filter tracks for keys if we have some key statuses to look at.
8042
8064
  if (keyIds.length) {
8065
+ const currentKeySystem = this.keySystem();
8066
+ const clearKeys = shaka.util.MapUtils.asMap(this.config_.drm.clearKeys);
8067
+
8043
8068
  for (const variant of this.manifest_.variants) {
8044
8069
  const streams = shaka.util.StreamUtils.getVariantStreams(variant);
8045
8070
 
@@ -8049,16 +8074,42 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
8049
8074
  // Only update if we have key IDs for the stream. If the keys aren't
8050
8075
  // all present, then the track should be restricted.
8051
8076
  if (stream.keyIds.size) {
8052
- variant.allowedByKeySystem = true;
8053
-
8054
- for (const keyId of stream.keyIds) {
8055
- const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
8056
- if (keyStatus || this.drmEngine_.hasManifestInitData()) {
8057
- variant.allowedByKeySystem = variant.allowedByKeySystem &&
8058
- !!keyStatus && !restrictedStatuses.includes(keyStatus);
8059
- }
8060
- }
8061
- }
8077
+ // If we are not using clearkeys, and the stream has drmInfos we
8078
+ // only want to check the keyIds of the keySystem we are using.
8079
+ // Other keySystems might have other keyIds that might not be
8080
+ // valid in this case. This can happen in HLS if the manifest
8081
+ // has Widevine with keyIds and PlayReady without keyIds and we are
8082
+ // using PlayReady.
8083
+ if (stream.drmInfos.length && !clearKeys.size) {
8084
+ for (const drmInfo of stream.drmInfos) {
8085
+ if (drmInfo.keyIds.size &&
8086
+ drmInfo.keySystem == currentKeySystem) {
8087
+ variant.allowedByKeySystem = true;
8088
+
8089
+ for (const keyId of drmInfo.keyIds) {
8090
+ const keyStatus =
8091
+ keyStatusMap[isGlobalStatus ? '00' : keyId];
8092
+ if (keyStatus || this.drmEngine_.hasManifestInitData()) {
8093
+ variant.allowedByKeySystem =
8094
+ variant.allowedByKeySystem &&
8095
+ !!keyStatus &&
8096
+ !restrictedStatuses.includes(keyStatus);
8097
+ } // if (keyStatus || this.drmEngine_.hasManifestInitData())
8098
+ } // for (const keyId of drmInfo.keyIds)
8099
+ } // if (drmInfo.keyIds.size && ...
8100
+ } // for (const drmInfo of stream.drmInfos
8101
+ } else {
8102
+ variant.allowedByKeySystem = true;
8103
+
8104
+ for (const keyId of stream.keyIds) {
8105
+ const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
8106
+ if (keyStatus || this.drmEngine_.hasManifestInitData()) {
8107
+ variant.allowedByKeySystem = variant.allowedByKeySystem &&
8108
+ !!keyStatus && !restrictedStatuses.includes(keyStatus);
8109
+ }
8110
+ } // for (const keyId of stream.keyIds)
8111
+ } // if (stream.drmInfos.length && !clearKeys.size)
8112
+ } // if (stream.keyIds.size)
8062
8113
 
8063
8114
  if (originalAllowed != variant.allowedByKeySystem) {
8064
8115
  tracksChanged = true;
@@ -8384,15 +8435,15 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
8384
8435
  * @export
8385
8436
  */
8386
8437
  // eslint-disable-next-line no-useless-concat
8387
- shaka.Player.version = 'v4.13.0' + '-uncompiled'; // x-release-please-version
8438
+ shaka.Player.version = 'v4.13.2' + '-uncompiled'; // x-release-please-version
8388
8439
 
8389
8440
  // Initialize the deprecation system using the version string we just set
8390
8441
  // on the player.
8391
8442
  shaka.Deprecate.init(shaka.Player.version);
8392
8443
 
8393
8444
 
8394
- /** @private {!Object<string, function(): *>} */
8395
- shaka.Player.supportPlugins_ = {};
8445
+ /** @private {!Map<string, function(): *>} */
8446
+ shaka.Player.supportPlugins_ = new Map();
8396
8447
 
8397
8448
 
8398
8449
  /** @private {?shaka.extern.IAdManager.Factory} */
@@ -378,14 +378,13 @@ shaka.polyfill.MediaCapabilities = class {
378
378
  }
379
379
 
380
380
  let result = false;
381
- if (displayType in shaka.polyfill.MediaCapabilities
382
- .memoizedCanDisplayTypeRequests_) {
383
- result = shaka.polyfill.MediaCapabilities
384
- .memoizedCanDisplayTypeRequests_[displayType];
381
+ const memoizedCanDisplayTypeRequests =
382
+ shaka.polyfill.MediaCapabilities.memoizedCanDisplayTypeRequests_;
383
+ if (memoizedCanDisplayTypeRequests.has(displayType)) {
384
+ result = memoizedCanDisplayTypeRequests.get(displayType);
385
385
  } else {
386
386
  result = await cast.__platform__.canDisplayType(displayType);
387
- shaka.polyfill.MediaCapabilities
388
- .memoizedCanDisplayTypeRequests_[displayType] = result;
387
+ memoizedCanDisplayTypeRequests.set(displayType, result);
389
388
  }
390
389
  return result;
391
390
  }
@@ -405,10 +404,10 @@ shaka.polyfill.MediaCapabilities.originalMcap = null;
405
404
  * A cache that stores the canDisplayType result of calling
406
405
  * `cast.__platform__.canDisplayType`.
407
406
  *
408
- * @type {(Object<(!string), (!boolean)>)}
409
- * @export
407
+ * @type {Map<string, boolean>}
408
+ * @private
410
409
  */
411
- shaka.polyfill.MediaCapabilities.memoizedCanDisplayTypeRequests_ = {};
410
+ shaka.polyfill.MediaCapabilities.memoizedCanDisplayTypeRequests_ = new Map();
412
411
 
413
412
  // Install at a lower priority than MediaSource polyfill, so that we have
414
413
  // MediaSource available first.
package/lib/text/cue.js CHANGED
@@ -371,8 +371,12 @@ shaka.text.Cue = class {
371
371
  // performance optimization. We can avoid the more expensive recursive
372
372
  // checks if the top-level properties don't match.
373
373
  // See: https://github.com/shaka-project/shaka-player/issues/3018
374
- if (cue1.startTime != cue2.startTime || cue1.endTime != cue2.endTime ||
375
- cue1.payload != cue2.payload) {
374
+ if (cue1.payload != cue2.payload) {
375
+ return false;
376
+ }
377
+ const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.001;
378
+ if (!isDiffNegligible(cue1.startTime, cue2.startTime) ||
379
+ !isDiffNegligible(cue1.endTime, cue2.endTime)) {
376
380
  return false;
377
381
  }
378
382
  for (const k in cue1) {
@@ -74,7 +74,7 @@ shaka.text.TextEngine = class {
74
74
  * @export
75
75
  */
76
76
  static registerParser(mimeType, plugin) {
77
- shaka.text.TextEngine.parserMap_[mimeType] = plugin;
77
+ shaka.text.TextEngine.parserMap_.set(mimeType, plugin);
78
78
  }
79
79
 
80
80
  /**
@@ -82,7 +82,7 @@ shaka.text.TextEngine = class {
82
82
  * @export
83
83
  */
84
84
  static unregisterParser(mimeType) {
85
- delete shaka.text.TextEngine.parserMap_[mimeType];
85
+ shaka.text.TextEngine.parserMap_.delete(mimeType);
86
86
  }
87
87
 
88
88
  /**
@@ -90,7 +90,7 @@ shaka.text.TextEngine = class {
90
90
  * @export
91
91
  */
92
92
  static findParser(mimeType) {
93
- return shaka.text.TextEngine.parserMap_[mimeType];
93
+ return shaka.text.TextEngine.parserMap_.get(mimeType);
94
94
  }
95
95
 
96
96
  /**
@@ -98,7 +98,7 @@ shaka.text.TextEngine = class {
98
98
  * @return {boolean}
99
99
  */
100
100
  static isTypeSupported(mimeType) {
101
- if (shaka.text.TextEngine.parserMap_[mimeType]) {
101
+ if (shaka.text.TextEngine.parserMap_.has(mimeType)) {
102
102
  // An actual parser is available.
103
103
  return true;
104
104
  }
@@ -147,7 +147,7 @@ shaka.text.TextEngine = class {
147
147
  return;
148
148
  }
149
149
 
150
- const factory = shaka.text.TextEngine.parserMap_[mimeType];
150
+ const factory = shaka.text.TextEngine.parserMap_.get(mimeType);
151
151
  goog.asserts.assert(
152
152
  factory, 'Text type negotiation should have happened already');
153
153
  this.parser_ = factory();
@@ -469,5 +469,5 @@ shaka.text.TextEngine = class {
469
469
  }
470
470
  };
471
471
 
472
- /** @private {!Object<string, !shaka.extern.TextParserPlugin>} */
473
- shaka.text.TextEngine.parserMap_ = {};
472
+ /** @private {!Map<string, !shaka.extern.TextParserPlugin>} */
473
+ shaka.text.TextEngine.parserMap_ = new Map();
@@ -589,8 +589,8 @@ shaka.text.TtmlTextParser = class {
589
589
  const align = TtmlTextParser.getStyleAttribute_(
590
590
  cueElement, region, styles, 'textAlign', true);
591
591
  if (align) {
592
- cue.positionAlign = TtmlTextParser.textAlignToPositionAlign_[align];
593
- cue.lineAlign = TtmlTextParser.textAlignToLineAlign_[align];
592
+ cue.positionAlign = TtmlTextParser.textAlignToPositionAlign_.get(align);
593
+ cue.lineAlign = TtmlTextParser.textAlignToLineAlign_.get(align);
594
594
 
595
595
  goog.asserts.assert(align.toUpperCase() in Cue.textAlign,
596
596
  align.toUpperCase() + ' Should be in Cue.textAlign values!');
@@ -1402,25 +1402,23 @@ shaka.text.TtmlTextParser.timeHMSFormat_ =
1402
1402
 
1403
1403
  /**
1404
1404
  * @const
1405
- * @private {!Object<string, shaka.text.Cue.lineAlign>}
1405
+ * @private {!Map<string, shaka.text.Cue.lineAlign>}
1406
1406
  */
1407
- shaka.text.TtmlTextParser.textAlignToLineAlign_ = {
1408
- 'left': shaka.text.Cue.lineAlign.START,
1409
- 'center': shaka.text.Cue.lineAlign.CENTER,
1410
- 'right': shaka.text.Cue.lineAlign.END,
1411
- 'start': shaka.text.Cue.lineAlign.START,
1412
- 'end': shaka.text.Cue.lineAlign.END,
1413
- };
1407
+ shaka.text.TtmlTextParser.textAlignToLineAlign_ = new Map()
1408
+ .set('left', shaka.text.Cue.lineAlign.START)
1409
+ .set('center', shaka.text.Cue.lineAlign.CENTER)
1410
+ .set('right', shaka.text.Cue.lineAlign.END)
1411
+ .set('start', shaka.text.Cue.lineAlign.START)
1412
+ .set('end', shaka.text.Cue.lineAlign.END);
1414
1413
 
1415
1414
  /**
1416
1415
  * @const
1417
- * @private {!Object<string, shaka.text.Cue.positionAlign>}
1416
+ * @private {!Map<string, shaka.text.Cue.positionAlign>}
1418
1417
  */
1419
- shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
1420
- 'left': shaka.text.Cue.positionAlign.LEFT,
1421
- 'center': shaka.text.Cue.positionAlign.CENTER,
1422
- 'right': shaka.text.Cue.positionAlign.RIGHT,
1423
- };
1418
+ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = new Map()
1419
+ .set('left', shaka.text.Cue.positionAlign.LEFT)
1420
+ .set('center', shaka.text.Cue.positionAlign.CENTER)
1421
+ .set('right', shaka.text.Cue.positionAlign.RIGHT);
1424
1422
 
1425
1423
  /**
1426
1424
  * The namespace URL for TTML parameters. Can be assigned any name in the TTML
@@ -55,6 +55,9 @@ shaka.text.UITextDisplayer = class {
55
55
  /** @private {?number} */
56
56
  this.aspectRatio_ = null;
57
57
 
58
+ /** @private {?shaka.extern.TextDisplayerConfiguration} */
59
+ this.config_ = null;
60
+
58
61
  /** @type {HTMLElement} */
59
62
  this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
60
63
  this.textContainer_.classList.add('shaka-text-container');
@@ -77,7 +80,8 @@ shaka.text.UITextDisplayer = class {
77
80
  if (!this.video_.paused) {
78
81
  this.updateCaptions_();
79
82
  }
80
- }).tickEvery(/* seconds= */ 0.25);
83
+ });
84
+ this.configureCaptionsTimer_();
81
85
 
82
86
  /**
83
87
  * Maps cues to cue elements. Specifically points out the wrapper element of
@@ -101,6 +105,10 @@ shaka.text.UITextDisplayer = class {
101
105
  this.updateCaptions_(/* forceUpdate= */ true);
102
106
  });
103
107
 
108
+ this.eventManager_.listen(this.video_, 'ratechange', () => {
109
+ this.configureCaptionsTimer_();
110
+ });
111
+
104
112
  // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
105
113
  // Whenever the natural width or natural height of the video changes
106
114
  // (including, for example, because the selected video track was changed),
@@ -137,9 +145,8 @@ shaka.text.UITextDisplayer = class {
137
145
  * @export
138
146
  */
139
147
  configure(config) {
140
- if (this.captionsTimer_) {
141
- this.captionsTimer_.tickEvery(config.captionsUpdatePeriod);
142
- }
148
+ this.config_ = config;
149
+ this.configureCaptionsTimer_();
143
150
  }
144
151
 
145
152
 
@@ -186,6 +193,7 @@ shaka.text.UITextDisplayer = class {
186
193
  this.cues_ = [];
187
194
  if (this.captionsTimer_) {
188
195
  this.captionsTimer_.stop();
196
+ this.captionsTimer_ = null;
189
197
  }
190
198
 
191
199
  this.currentCuesMap_.clear();
@@ -265,6 +273,19 @@ shaka.text.UITextDisplayer = class {
265
273
  enableTextDisplayer() {
266
274
  }
267
275
 
276
+ /**
277
+ * @private
278
+ */
279
+ configureCaptionsTimer_() {
280
+ if (this.captionsTimer_) {
281
+ const captionsUpdatePeriod = this.config_ ?
282
+ this.config_.captionsUpdatePeriod : 0.25;
283
+ const updateTime = captionsUpdatePeriod /
284
+ Math.max(1, Math.abs(this.video_.playbackRate));
285
+ this.captionsTimer_.tickEvery(updateTime);
286
+ }
287
+ }
288
+
268
289
  /**
269
290
  * @private
270
291
  */
@@ -14,6 +14,7 @@ goog.require('shaka.util.Error');
14
14
  goog.require('shaka.util.Id3Utils');
15
15
  goog.require('shaka.util.ManifestParserUtils');
16
16
  goog.require('shaka.util.Mp4Generator');
17
+ goog.require('shaka.util.Platform');
17
18
  goog.require('shaka.util.Uint8ArrayUtils');
18
19
 
19
20
 
@@ -86,7 +87,11 @@ shaka.transmuxer.Ac3Transmuxer = class {
86
87
  */
87
88
  convertCodecs(contentType, mimeType) {
88
89
  if (this.isAc3Container_(mimeType)) {
89
- return 'audio/mp4; codecs="ac-3"';
90
+ if (shaka.util.Platform.requiresEC3InitSegments()) {
91
+ return 'audio/mp4; codecs="ec-3"';
92
+ } else {
93
+ return 'audio/mp4; codecs="ac-3"';
94
+ }
90
95
  }
91
96
  return mimeType;
92
97
  }
@@ -33,10 +33,10 @@ shaka.transmuxer.TransmuxerEngine = class {
33
33
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
34
34
  const normalizedMimetype = TransmuxerEngine.normalizeMimeType_(mimeType);
35
35
  const key = normalizedMimetype + '-' + priority;
36
- TransmuxerEngine.transmuxerMap_[key] = {
36
+ TransmuxerEngine.transmuxerMap_.set(key, {
37
37
  priority: priority,
38
38
  plugin: plugin,
39
- };
39
+ });
40
40
  }
41
41
 
42
42
  /**
@@ -48,7 +48,7 @@ shaka.transmuxer.TransmuxerEngine = class {
48
48
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
49
49
  const normalizedMimetype = TransmuxerEngine.normalizeMimeType_(mimeType);
50
50
  const key = normalizedMimetype + '-' + priority;
51
- delete TransmuxerEngine.transmuxerMap_[key];
51
+ TransmuxerEngine.transmuxerMap_.delete(key);
52
52
  }
53
53
 
54
54
  /**
@@ -68,7 +68,7 @@ shaka.transmuxer.TransmuxerEngine = class {
68
68
  ];
69
69
  for (const priority of priorities) {
70
70
  const key = normalizedMimetype + '-' + priority;
71
- const object = TransmuxerEngine.transmuxerMap_[key];
71
+ const object = TransmuxerEngine.transmuxerMap_.get(key);
72
72
  if (object) {
73
73
  const transmuxer = object.plugin();
74
74
  const isSupported = transmuxer.isSupported(mimeType, contentType);
@@ -139,9 +139,9 @@ shaka.transmuxer.TransmuxerEngine.PluginObject;
139
139
 
140
140
 
141
141
  /**
142
- * @private {!Object<string, !shaka.transmuxer.TransmuxerEngine.PluginObject>}
142
+ * @private {!Map<string, !shaka.transmuxer.TransmuxerEngine.PluginObject>}
143
143
  */
144
- shaka.transmuxer.TransmuxerEngine.transmuxerMap_ = {};
144
+ shaka.transmuxer.TransmuxerEngine.transmuxerMap_ = new Map();
145
145
 
146
146
 
147
147
  /**
@@ -202,7 +202,7 @@ shaka.util.ManifestParserUtils = class {
202
202
  */
203
203
  static guessCodecsSafe(contentType, codecs) {
204
204
  const formats = shaka.util.ManifestParserUtils
205
- .CODEC_REGEXPS_BY_CONTENT_TYPE_[contentType];
205
+ .CODEC_REGEXPS_BY_CONTENT_TYPE_.get(contentType);
206
206
  for (const format of formats) {
207
207
  for (const codec of codecs) {
208
208
  if (format.test(codec.trim())) {
@@ -231,7 +231,7 @@ shaka.util.ManifestParserUtils = class {
231
231
  static guessAllCodecsSafe(contentType, codecs) {
232
232
  const allCodecs = [];
233
233
  const formats = shaka.util.ManifestParserUtils
234
- .CODEC_REGEXPS_BY_CONTENT_TYPE_[contentType];
234
+ .CODEC_REGEXPS_BY_CONTENT_TYPE_.get(contentType);
235
235
  for (const format of formats) {
236
236
  for (const codec of codecs) {
237
237
  if (format.test(codec.trim())) {
@@ -332,10 +332,9 @@ shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_ = [
332
332
 
333
333
 
334
334
  /**
335
- * @const {!Object<string, !Array<!RegExp>>}
335
+ * @const {!Map<string, !Array<!RegExp>>}
336
336
  */
337
- shaka.util.ManifestParserUtils.CODEC_REGEXPS_BY_CONTENT_TYPE_ = {
338
- 'audio': shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_,
339
- 'video': shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_,
340
- 'text': shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_,
341
- };
337
+ shaka.util.ManifestParserUtils.CODEC_REGEXPS_BY_CONTENT_TYPE_ = new Map()
338
+ .set('audio', shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_)
339
+ .set('video', shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_)
340
+ .set('text', shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_);
@@ -8,6 +8,7 @@ goog.provide('shaka.util.Mp4Generator');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.util.ManifestParserUtils');
11
+ goog.require('shaka.util.Platform');
11
12
  goog.require('shaka.util.Uint8ArrayUtils');
12
13
 
13
14
 
@@ -298,6 +299,20 @@ shaka.util.Mp4Generator = class {
298
299
  stsd_(streamInfo) {
299
300
  const Mp4Generator = shaka.util.Mp4Generator;
300
301
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
302
+ let audioCodec = 'aac';
303
+ if (streamInfo.codecs.includes('mp3')) {
304
+ audioCodec = 'mp3';
305
+ } else if (streamInfo.codecs.includes('ac-3')) {
306
+ if (shaka.util.Platform.requiresEC3InitSegments()) {
307
+ audioCodec = 'ec-3';
308
+ } else {
309
+ audioCodec = 'ac-3';
310
+ }
311
+ } else if (streamInfo.codecs.includes('ec-3')) {
312
+ audioCodec = 'ec-3';
313
+ } else if (streamInfo.codecs.includes('opus')) {
314
+ audioCodec = 'opus';
315
+ }
301
316
  let bytes = new Uint8Array([]);
302
317
  switch (streamInfo.type) {
303
318
  case ContentType.VIDEO:
@@ -308,13 +323,13 @@ shaka.util.Mp4Generator = class {
308
323
  }
309
324
  break;
310
325
  case ContentType.AUDIO:
311
- if (streamInfo.codecs.includes('mp3')) {
326
+ if (audioCodec == 'mp3') {
312
327
  bytes = this.mp3_(streamInfo);
313
- } else if (streamInfo.codecs.includes('ac-3')) {
328
+ } else if (audioCodec == 'ac-3') {
314
329
  bytes = this.ac3_(streamInfo);
315
- } else if (streamInfo.codecs.includes('ec-3')) {
330
+ } else if (audioCodec == 'ec-3') {
316
331
  bytes = this.ec3_(streamInfo);
317
- } else if (streamInfo.codecs.includes('opus')) {
332
+ } else if (audioCodec == 'opus') {
318
333
  bytes = this.opus_(streamInfo);
319
334
  } else {
320
335
  bytes = this.mp4a_(streamInfo);
@@ -1167,7 +1182,7 @@ shaka.util.Mp4Generator = class {
1167
1182
  * @return {!Uint8Array}
1168
1183
  */
1169
1184
  static box(boxName, ...payload) {
1170
- let type = shaka.util.Mp4Generator.BOX_TYPES_[boxName];
1185
+ let type = shaka.util.Mp4Generator.BOX_TYPES_.get(boxName);
1171
1186
  if (!type) {
1172
1187
  type = [
1173
1188
  boxName.charCodeAt(0),
@@ -1175,7 +1190,7 @@ shaka.util.Mp4Generator = class {
1175
1190
  boxName.charCodeAt(2),
1176
1191
  boxName.charCodeAt(3),
1177
1192
  ];
1178
- shaka.util.Mp4Generator.BOX_TYPES_[boxName] = type;
1193
+ shaka.util.Mp4Generator.BOX_TYPES_.set(boxName, type);
1179
1194
  }
1180
1195
  // make the header for the box
1181
1196
  let size = 8;
@@ -1212,9 +1227,9 @@ shaka.util.Mp4Generator.initialized_ = false;
1212
1227
  shaka.util.Mp4Generator.UINT32_MAX_ = Math.pow(2, 32) - 1;
1213
1228
 
1214
1229
  /**
1215
- * @private {!Object<string, !Array<number>>}
1230
+ * @private {!Map<string, !Array<number>>}
1216
1231
  */
1217
- shaka.util.Mp4Generator.BOX_TYPES_ = {};
1232
+ shaka.util.Mp4Generator.BOX_TYPES_ = new Map();
1218
1233
 
1219
1234
  /**
1220
1235
  * @private {{video: !Uint8Array, audio: !Uint8Array}}