shaka-player 4.9.13 → 4.10.1

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 (91) hide show
  1. package/README.md +4 -0
  2. package/dist/controls.css +2 -2
  3. package/dist/controls.css.map +1 -1
  4. package/dist/deps.js +24 -19
  5. package/dist/locales.js +46 -35
  6. package/dist/shaka-player.compiled.d.ts +291 -12
  7. package/dist/shaka-player.compiled.debug.d.ts +291 -12
  8. package/dist/shaka-player.compiled.debug.externs.js +443 -6
  9. package/dist/shaka-player.compiled.debug.js +651 -556
  10. package/dist/shaka-player.compiled.debug.map +4 -4
  11. package/dist/shaka-player.compiled.externs.js +443 -6
  12. package/dist/shaka-player.compiled.js +1325 -1270
  13. package/dist/shaka-player.compiled.map +4 -4
  14. package/dist/shaka-player.ui.d.ts +306 -13
  15. package/dist/shaka-player.ui.debug.d.ts +306 -13
  16. package/dist/shaka-player.ui.debug.externs.js +477 -18
  17. package/dist/shaka-player.ui.debug.js +799 -681
  18. package/dist/shaka-player.ui.debug.map +4 -4
  19. package/dist/shaka-player.ui.externs.js +477 -18
  20. package/dist/shaka-player.ui.js +1600 -1533
  21. package/dist/shaka-player.ui.map +4 -4
  22. package/externs/shaka/ads.js +21 -1
  23. package/externs/shaka/manifest.js +20 -1
  24. package/externs/shaka/manifest_parser.js +18 -1
  25. package/externs/shaka/player.js +164 -50
  26. package/lib/ads/ad_manager.js +225 -10
  27. package/lib/ads/ad_utils.js +323 -0
  28. package/lib/ads/ads_stats.js +24 -0
  29. package/lib/ads/client_side_ad_manager.js +42 -34
  30. package/lib/ads/interstitial_ad.js +292 -0
  31. package/lib/ads/interstitial_ad_manager.js +772 -0
  32. package/lib/ads/media_tailor_ad_manager.js +19 -22
  33. package/lib/ads/server_side_ad_manager.js +12 -15
  34. package/lib/cast/cast_utils.js +1 -0
  35. package/lib/cea/cea_utils.js +12 -0
  36. package/lib/cea/mp4_cea_parser.js +17 -0
  37. package/lib/dash/dash_parser.js +81 -28
  38. package/lib/dash/segment_base.js +3 -0
  39. package/lib/hls/hls_parser.js +429 -121
  40. package/lib/hls/manifest_text_parser.js +1 -0
  41. package/lib/media/adaptation_set_criteria.js +31 -1
  42. package/lib/media/drm_engine.js +16 -2
  43. package/lib/media/manifest_filterer.js +7 -5
  44. package/lib/media/preload_manager.js +8 -10
  45. package/lib/media/presentation_timeline.js +37 -5
  46. package/lib/media/quality_observer.js +68 -2
  47. package/lib/media/segment_reference.js +1 -1
  48. package/lib/media/segment_utils.js +33 -1
  49. package/lib/media/streaming_engine.js +120 -43
  50. package/lib/mss/mss_parser.js +13 -2
  51. package/lib/net/networking_engine.js +1 -0
  52. package/lib/offline/manifest_converter.js +3 -0
  53. package/lib/offline/offline_manifest_parser.js +5 -0
  54. package/lib/offline/storage.js +2 -0
  55. package/lib/player.js +498 -165
  56. package/lib/util/fake_event.js +1 -0
  57. package/lib/util/manifest_parser_utils.js +9 -2
  58. package/lib/util/mp4_box_parsers.js +32 -0
  59. package/lib/util/player_configuration.js +21 -9
  60. package/lib/util/stats.js +43 -0
  61. package/package.json +1 -1
  62. package/shaka-player.uncompiled.js +2 -0
  63. package/ui/ad_counter.js +3 -3
  64. package/ui/ad_position.js +3 -3
  65. package/ui/ad_statistics_button.js +237 -0
  66. package/ui/audio_language_selection.js +8 -4
  67. package/ui/controls.js +6 -3
  68. package/ui/element.js +3 -3
  69. package/ui/enums.js +1 -0
  70. package/ui/externs/ui.js +3 -0
  71. package/ui/language_utils.js +48 -3
  72. package/ui/less/containers.less +32 -0
  73. package/ui/less/thumbnails.less +13 -0
  74. package/ui/locales/en.json +2 -0
  75. package/ui/locales/es-419.json +2 -0
  76. package/ui/locales/es.json +3 -0
  77. package/ui/locales/fr.json +1 -0
  78. package/ui/locales/pl.json +1 -0
  79. package/ui/locales/pt-BR.json +1 -0
  80. package/ui/locales/pt-PT.json +1 -0
  81. package/ui/locales/source.json +8 -0
  82. package/ui/mute_button.js +4 -4
  83. package/ui/overflow_menu.js +3 -3
  84. package/ui/play_button.js +5 -7
  85. package/ui/presentation_time.js +3 -3
  86. package/ui/save_video_frame_button.js +196 -0
  87. package/ui/seek_bar.js +75 -20
  88. package/ui/skip_ad_button.js +4 -4
  89. package/ui/statistics_button.js +14 -2
  90. package/ui/ui.js +15 -0
  91. package/ui/volume_bar.js +4 -4
package/lib/player.js CHANGED
@@ -175,7 +175,6 @@ goog.requireType('shaka.media.PresentationTimeline');
175
175
  * @description Fired when the media quality changes at the playhead.
176
176
  * That may be caused by an adaptation change or a DASH period transition.
177
177
  * Separate events are emitted for audio and video contentTypes.
178
- * This is supported for only DASH streams at this time.
179
178
  * @property {string} type
180
179
  * 'mediaqualitychanged'
181
180
  * @property {shaka.extern.MediaQualityInfo} mediaQuality
@@ -185,6 +184,19 @@ goog.requireType('shaka.media.PresentationTimeline');
185
184
  * @exportDoc
186
185
  */
187
186
 
187
+ /**
188
+ * @event shaka.Player.AudioTrackChangedEvent
189
+ * @description Fired when the audio track changes at the playhead.
190
+ * That may be caused by a user requesting to chang audio tracks.
191
+ * @property {string} type
192
+ * 'audiotrackchanged'
193
+ * @property {shaka.extern.MediaQualityInfo} mediaQuality
194
+ * Information about media quality at the playhead position.
195
+ * @property {number} position
196
+ * The playhead position.
197
+ * @exportDoc
198
+ */
199
+
188
200
 
189
201
  /**
190
202
  * @event shaka.Player.BufferingEvent
@@ -332,7 +344,7 @@ goog.requireType('shaka.media.PresentationTimeline');
332
344
  * The time that describes the end of the range of the metadata to which
333
345
  * the cue applies.
334
346
  * @property {string} metadataType
335
- * Type of metadata. Eg: org.id3 or org.mp4ra
347
+ * Type of metadata. Eg: 'org.id3' or 'com.apple.quicktime.HLS'
336
348
  * @property {shaka.extern.MetadataFrame} payload
337
349
  * The metadata itself
338
350
  * @exportDoc
@@ -710,6 +722,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
710
722
  /** @private {?shaka.extern.PlayerConfiguration} */
711
723
  this.config_ = this.defaultConfig_();
712
724
 
725
+ /** @private {?number} */
726
+ this.currentTargetLatency_ = null;
727
+
728
+ /** @private {number} */
729
+ this.rebufferingCount_ = -1;
730
+
731
+ /** @private {?number} */
732
+ this.targetLatencyReached_ = null;
733
+
713
734
  /**
714
735
  * The TextDisplayerFactory that was last used to make a text displayer.
715
736
  * Stored so that we can tell if a new type of text displayer is desired.
@@ -779,6 +800,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
779
800
  /** @private {HTMLMediaElement} */
780
801
  this.preloadDueAdManagerVideo_ = null;
781
802
 
803
+ /** @private {boolean} */
804
+ this.preloadDueAdManagerVideoEnded_ = false;
805
+
782
806
  /** @private {shaka.util.Timer} */
783
807
  this.preloadDueAdManagerTimer_ = new shaka.util.Timer(async () => {
784
808
  if (this.preloadDueAdManager_) {
@@ -786,8 +810,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
786
810
  await this.attach(
787
811
  this.preloadDueAdManagerVideo_, /* initializeMediaSource= */ true);
788
812
  await this.load(this.preloadDueAdManager_);
789
- this.preloadDueAdManagerVideo_.play();
813
+ if (!this.preloadDueAdManagerVideoEnded_) {
814
+ this.preloadDueAdManagerVideo_.play();
815
+ } else {
816
+ this.preloadDueAdManagerVideo_.pause();
817
+ }
790
818
  this.preloadDueAdManager_ = null;
819
+ this.preloadDueAdManagerVideoEnded_ = false;
791
820
  }
792
821
  });
793
822
 
@@ -795,50 +824,43 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
795
824
  this.adManager_ = shaka.Player.adManagerFactory_();
796
825
  this.adManager_.configure(this.config_.ads);
797
826
 
798
- // Note: we don't use shaka.ads.AdManager.AD_STARTED to avoid add a
799
- // optional module in the player.
827
+ // Note: we don't use shaka.ads.Utils.AD_CONTENT_PAUSE_REQUESTED to
828
+ // avoid add a optional module in the player.
800
829
  this.adManagerEventManager_.listen(
801
- this.adManager_, 'ad-started', async (e) => {
802
- if (this.config_.ads.supportsMultipleMediaElements) {
803
- return;
804
- }
805
- const event = /** @type {?google.ima.AdEvent} */
806
- (e['originalEvent']);
807
- if (!event) {
808
- return;
809
- }
810
- const contentPauseRequested =
811
- google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED;
812
- if (event.type != contentPauseRequested) {
813
- return;
814
- }
830
+ this.adManager_, 'ad-content-pause-requested', async (e) => {
815
831
  this.preloadDueAdManagerTimer_.stop();
816
832
  if (!this.preloadDueAdManager_) {
817
833
  this.preloadDueAdManagerVideo_ = this.video_;
818
- this.preloadDueAdManager_ =
819
- await this.detachAndSavePreload(true);
834
+ this.preloadDueAdManagerVideoEnded_ = this.video_.ended;
835
+ const saveLivePosition = /** @type {boolean} */(
836
+ e['saveLivePosition']) || false;
837
+ this.preloadDueAdManager_ = await this.detachAndSavePreload(
838
+ /* keepAdManager= */ true, saveLivePosition);
820
839
  }
821
840
  });
822
841
 
823
- // Note: we don't use shaka.ads.AdManager.AD_STOPPED to avoid add a
824
- // optional module in the player.
842
+ // Note: we don't use shaka.ads.Utils.AD_CONTENT_RESUME_REQUESTED to
843
+ // avoid add a optional module in the player.
825
844
  this.adManagerEventManager_.listen(
826
- this.adManager_, 'ad-stopped', (e) => {
827
- if (this.config_.ads.supportsMultipleMediaElements) {
828
- return;
829
- }
830
- const event = /** @type {?google.ima.AdEvent} */
831
- (e['originalEvent']);
832
- if (!event) {
833
- return;
834
- }
835
- const contentResumeRequested =
836
- google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED;
837
- if (event.type != contentResumeRequested) {
838
- return;
845
+ this.adManager_, 'ad-content-resume-requested', (e) => {
846
+ const offset = /** @type {number} */(e['offset']) || 0;
847
+ if (this.preloadDueAdManager_) {
848
+ this.preloadDueAdManager_.setOffsetToStartTime(offset);
839
849
  }
840
850
  this.preloadDueAdManagerTimer_.tickAfter(0.1);
841
851
  });
852
+
853
+ // Note: we don't use shaka.ads.Utils.AD_CONTENT_ATTACH_REQUESTED to
854
+ // avoid add a optional module in the player.
855
+ this.adManagerEventManager_.listen(
856
+ this.adManager_, 'ad-content-attach-requested', async (e) => {
857
+ if (!this.video_ && this.preloadDueAdManagerVideo_) {
858
+ goog.asserts.assert(this.preloadDueAdManagerVideo_,
859
+ 'Must have video');
860
+ await this.attach(this.preloadDueAdManagerVideo_,
861
+ /* initializeMediaSource= */ true);
862
+ }
863
+ });
842
864
  }
843
865
 
844
866
  // If the browser comes back online after being offline, then try to play
@@ -948,14 +970,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
948
970
  // A PreloadManager can only be used with the Player instance that created
949
971
  // it, so all PreloadManagers this Player has created are now useless.
950
972
  // Destroy any remaining managers now, to help prevent memory leaks.
951
- const preloadManagerDestroys = [];
952
- for (const preloadManager of this.createdPreloadManagers_) {
953
- if (!preloadManager.isDestroyed()) {
954
- preloadManagerDestroys.push(preloadManager.destroy());
955
- }
956
- }
957
- this.createdPreloadManagers_ = [];
958
- await Promise.all(preloadManagerDestroys);
973
+ await this.destroyAllPreloads();
959
974
 
960
975
  // Tear-down the event managers to ensure handlers stop firing.
961
976
  if (this.globalEventManager_) {
@@ -1462,6 +1477,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1462
1477
  this.stats_ = new shaka.util.Stats(); // Replace with a clean object.
1463
1478
  this.lastTextFactory_ = null;
1464
1479
 
1480
+ this.targetLatencyReached_ = null;
1481
+ this.currentTargetLatency_ = null;
1482
+ this.rebufferingCount_ = -1;
1483
+
1465
1484
  this.externalSrcEqualsThumbnailsStreams_ = [];
1466
1485
 
1467
1486
  this.completionPercent_ = NaN;
@@ -1473,7 +1492,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1473
1492
  }
1474
1493
 
1475
1494
  if (initializeMediaSource && shaka.util.Platform.supportsMediaSource() &&
1476
- !this.mediaSourceEngine_) {
1495
+ !this.mediaSourceEngine_ && this.video_) {
1477
1496
  await this.initializeMediaSourceEngineInner_();
1478
1497
  }
1479
1498
  }
@@ -1625,8 +1644,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1625
1644
  // asset.
1626
1645
  const shouldUseSrcEquals = !preloadManager;
1627
1646
 
1628
- const startTimeOfLoad = preloadManager ?
1629
- preloadManager.getStartTimeOfLoad() : (Date.now() / 1000);
1647
+ const startTimeOfLoad = Date.now() / 1000;
1630
1648
 
1631
1649
  // Stats are for a single playback/load session. Stats must be initialized
1632
1650
  // before we allow calls to |updateStateHistory|.
@@ -1662,6 +1680,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1662
1680
  this.manifestFilterer_ = preloadManager.getManifestFilterer();
1663
1681
  this.parserFactory_ = preloadManager.getParserFactory();
1664
1682
  this.parser_ = preloadManager.receiveParser();
1683
+ if (this.parser_ && this.parser_.setMediaElement && this.video_) {
1684
+ this.parser_.setMediaElement(this.video_);
1685
+ }
1665
1686
  this.regionTimeline_ = preloadManager.receiveRegionTimeline();
1666
1687
  this.qualityObserver_ = preloadManager.getQualityObserver();
1667
1688
  this.manifest_ = preloadManager.getManifest();
@@ -1790,37 +1811,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1790
1811
  * attached to something else.
1791
1812
  *
1792
1813
  * @param {boolean=} keepAdManager
1814
+ * @param {boolean=} saveLivePosition
1793
1815
  * @return {!Promise.<?shaka.media.PreloadManager>}
1794
1816
  * @export
1795
1817
  */
1796
- async detachAndSavePreload(keepAdManager = false) {
1797
- const preloadManager = await this.savePreload_();
1818
+ async detachAndSavePreload(keepAdManager = false, saveLivePosition = false) {
1819
+ const preloadManager = await this.savePreload_(saveLivePosition);
1798
1820
  await this.detach(keepAdManager);
1799
1821
  return preloadManager;
1800
1822
  }
1801
1823
 
1802
1824
  /**
1825
+ * @param {boolean=} saveLivePosition
1803
1826
  * @return {!Promise.<?shaka.media.PreloadManager>}
1804
1827
  * @private
1805
1828
  */
1806
- async savePreload_() {
1829
+ async savePreload_(saveLivePosition = false) {
1807
1830
  let preloadManager = null;
1808
1831
  if (this.manifest_ && this.parser_ && this.parserFactory_ &&
1809
1832
  this.assetUri_) {
1810
1833
  let startTime = this.video_.currentTime;
1811
- if (this.isLive()) {
1834
+ if (this.isLive() && !saveLivePosition) {
1812
1835
  startTime = null;
1813
1836
  }
1814
1837
  // We have enough information to make a PreloadManager!
1815
- const startTimeOfLoad = Date.now() / 1000;
1816
1838
  preloadManager = await this.makePreloadManager_(
1817
1839
  this.assetUri_,
1818
1840
  startTime,
1819
1841
  this.mimeType_,
1820
- startTimeOfLoad,
1821
1842
  /* allowPrefetch= */ true,
1822
1843
  /* disableVideo= */ false,
1823
1844
  /* allowMakeAbrManager= */ false);
1845
+ this.createdPreloadManagers_.push(preloadManager);
1846
+ if (this.parser_ && this.parser_.setMediaElement) {
1847
+ this.parser_.setMediaElement(/* mediaElement= */ null);
1848
+ }
1824
1849
  preloadManager.attachManifest(
1825
1850
  this.manifest_, this.parser_, this.parserFactory_);
1826
1851
  preloadManager.attachAbrManager(
@@ -1878,6 +1903,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1878
1903
  return preloadManager;
1879
1904
  }
1880
1905
 
1906
+ /**
1907
+ * Calls |destroy| on each PreloadManager object this player has created.
1908
+ * @export
1909
+ */
1910
+ async destroyAllPreloads() {
1911
+ const preloadManagerDestroys = [];
1912
+ for (const preloadManager of this.createdPreloadManagers_) {
1913
+ if (!preloadManager.isDestroyed()) {
1914
+ preloadManagerDestroys.push(preloadManager.destroy());
1915
+ }
1916
+ }
1917
+ this.createdPreloadManagers_ = [];
1918
+ await Promise.all(preloadManagerDestroys);
1919
+ }
1920
+
1881
1921
  /**
1882
1922
  * @param {string} assetUri
1883
1923
  * @param {?number} startTime
@@ -1889,7 +1929,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1889
1929
  async preloadInner_(assetUri, startTime, mimeType, standardLoad = false) {
1890
1930
  goog.asserts.assert(this.networkingEngine_, 'Should have a net engine!');
1891
1931
  goog.asserts.assert(this.config_, 'Config must not be null!');
1892
- const startTimeOfLoad = Date.now() / 1000;
1893
1932
  if (!mimeType) {
1894
1933
  mimeType = await this.guessMimeType_(assetUri);
1895
1934
  }
@@ -1915,23 +1954,34 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1915
1954
  disableVideo = true;
1916
1955
  }
1917
1956
  }
1918
- return this.makePreloadManager_(
1919
- assetUri, startTime, mimeType || null, startTimeOfLoad,
1957
+ let preloadManagerPromise = this.makePreloadManager_(
1958
+ assetUri, startTime, mimeType || null,
1920
1959
  /* allowPrefetch= */ !standardLoad, disableVideo, allowMakeAbrManager);
1960
+ if (!standardLoad) {
1961
+ // We only need to track the PreloadManager if it is not part of a
1962
+ // standard load. If it is, the load() method will handle destroying it.
1963
+ // Adding a standard load PreloadManager to the createdPreloadManagers_
1964
+ // array runs the risk that the user will call destroyAllPreloads and
1965
+ // destroy that PreloadManager mid-load.
1966
+ preloadManagerPromise = preloadManagerPromise.then((preloadManager) => {
1967
+ this.createdPreloadManagers_.push(preloadManager);
1968
+ return preloadManager;
1969
+ });
1970
+ }
1971
+ return preloadManagerPromise;
1921
1972
  }
1922
1973
 
1923
1974
  /**
1924
1975
  * @param {string} assetUri
1925
1976
  * @param {?number} startTime
1926
1977
  * @param {?string} mimeType
1927
- * @param {number} startTimeOfLoad
1928
1978
  * @param {boolean=} allowPrefetch
1929
1979
  * @param {boolean=} disableVideo
1930
1980
  * @param {boolean=} allowMakeAbrManager
1931
1981
  * @return {!Promise.<!shaka.media.PreloadManager>}
1932
1982
  * @private
1933
1983
  */
1934
- async makePreloadManager_(assetUri, startTime, mimeType, startTimeOfLoad,
1984
+ async makePreloadManager_(assetUri, startTime, mimeType,
1935
1985
  allowPrefetch = true, disableVideo = false, allowMakeAbrManager = true) {
1936
1986
  goog.asserts.assert(this.networkingEngine_, 'Must have net engine');
1937
1987
  /** @type {?shaka.media.PreloadManager} */
@@ -2034,6 +2084,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2034
2084
  });
2035
2085
  },
2036
2086
  getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
2087
+ onMetadata: (type, startTime, endTime, values) => {
2088
+ let metadataType = type;
2089
+ if (type == 'com.apple.hls.interstitial') {
2090
+ metadataType = 'com.apple.quicktime.HLS';
2091
+ /** @type {shaka.extern.Interstitial} */
2092
+ const interstitial = {
2093
+ startTime,
2094
+ endTime,
2095
+ values,
2096
+ };
2097
+ if (this.adManager_) {
2098
+ goog.asserts.assert(this.video_, 'Must have video');
2099
+ this.adManager_.onInterstitialMetadata(
2100
+ this, this.video_, interstitial);
2101
+ }
2102
+ }
2103
+ for (const payload of values) {
2104
+ preloadManager.addQueuedOperation(false, () => {
2105
+ this.dispatchMetadataEvent_(
2106
+ startTime, endTime, metadataType, payload);
2107
+ });
2108
+ }
2109
+ },
2110
+ disableStream: (stream) => this.disableStream(
2111
+ stream, this.config_.streaming.maxDisabledTime),
2037
2112
  };
2038
2113
  const regionTimeline =
2039
2114
  new shaka.media.RegionTimeline(() => this.seekRange());
@@ -2062,6 +2137,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2062
2137
  const position = event['position'];
2063
2138
  this.onMediaQualityChange_(mediaQualityInfo, position);
2064
2139
  });
2140
+
2141
+ qualityObserver.addEventListener('audiotrackchange', (event) => {
2142
+ /** @type {shaka.extern.MediaQualityInfo} */
2143
+ const mediaQualityInfo = event['quality'];
2144
+ /** @type {number} */
2145
+ const position = event['position'];
2146
+ this.onMediaQualityChange_(mediaQualityInfo, position,
2147
+ /* audioTrackChanged= */ true);
2148
+ });
2065
2149
  }
2066
2150
  let firstEvent = true;
2067
2151
  const drmPlayerInterface = {
@@ -2126,8 +2210,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2126
2210
  allowMakeAbrManager,
2127
2211
  };
2128
2212
  preloadManager = new shaka.media.PreloadManager(
2129
- assetUri, mimeType, startTimeOfLoad, startTime, playerInterface);
2130
- this.createdPreloadManagers_.push(preloadManager);
2213
+ assetUri, mimeType, startTime, playerInterface);
2131
2214
  return preloadManager;
2132
2215
  }
2133
2216
 
@@ -2200,11 +2283,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2200
2283
  // example, Android has native HLS, but we'd prefer our own MediaSource
2201
2284
  // version there.
2202
2285
  if (MimeUtils.isHlsType(mimeType)) {
2203
- // Native HLS can be preferred on any platform via this flag:
2204
- if (this.config_.streaming.preferNativeHls) {
2205
- return true;
2206
- }
2207
-
2208
2286
  // Native FairPlay HLS can be preferred on Apple platfforms.
2209
2287
  if (Platform.isApple() &&
2210
2288
  (this.config_.drm.servers['com.apple.fps'] ||
@@ -2212,11 +2290,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2212
2290
  return this.config_.streaming.useNativeHlsForFairPlay;
2213
2291
  }
2214
2292
 
2215
- // For Safari, we have an older flag which only applies to this one
2216
- // browser:
2217
- if (Platform.isApple()) {
2218
- return this.config_.streaming.useNativeHlsOnSafari;
2219
- }
2293
+ // Native HLS can be preferred on any platform via this flag:
2294
+ return this.config_.streaming.preferNativeHls;
2220
2295
  }
2221
2296
 
2222
2297
  // In all other cases, we prefer MediaSource.
@@ -2378,28 +2453,40 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2378
2453
  // Unless the user has already picked a variant, anyway, by calling
2379
2454
  // selectVariantTrack before this loading stage.
2380
2455
  let initialVariant = prefetchedVariant;
2381
- const activeVariant = this.streamingEngine_.getCurrentVariant();
2382
- if (!activeVariant && !initialVariant) {
2383
- initialVariant = this.chooseVariant_(/* initialSelection= */ true);
2384
- goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
2385
- }
2386
-
2387
- // Lazy-load the stream, so we will have enough info to make the playhead.
2388
- const createSegmentIndexPromises = [];
2389
- const toLazyLoad = activeVariant || initialVariant;
2390
- for (const stream of [toLazyLoad.video, toLazyLoad.audio]) {
2391
- if (stream && !stream.segmentIndex) {
2392
- createSegmentIndexPromises.push(stream.createSegmentIndex());
2456
+ let toLazyLoad;
2457
+ let activeVariant;
2458
+ do {
2459
+ activeVariant = this.streamingEngine_.getCurrentVariant();
2460
+ if (!activeVariant && !initialVariant) {
2461
+ initialVariant = this.chooseVariant_(/* initialSelection= */ true);
2462
+ goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
2463
+ }
2464
+
2465
+ // Lazy-load the stream, so we will have enough info to make the playhead.
2466
+ const createSegmentIndexPromises = [];
2467
+ toLazyLoad = activeVariant || initialVariant;
2468
+ for (const stream of [toLazyLoad.video, toLazyLoad.audio]) {
2469
+ if (stream && !stream.segmentIndex) {
2470
+ createSegmentIndexPromises.push(stream.createSegmentIndex());
2471
+ }
2393
2472
  }
2394
- }
2395
- if (createSegmentIndexPromises.length > 0) {
2396
- await Promise.all(createSegmentIndexPromises);
2397
- }
2473
+ if (createSegmentIndexPromises.length > 0) {
2474
+ // eslint-disable-next-line no-await-in-loop
2475
+ await Promise.all(createSegmentIndexPromises);
2476
+ }
2477
+ } while (!toLazyLoad || toLazyLoad.disabledUntilTime != 0);
2398
2478
 
2399
2479
  if (this.parser_ && this.parser_.onInitialVariantChosen) {
2400
2480
  this.parser_.onInitialVariantChosen(toLazyLoad);
2401
2481
  }
2402
2482
 
2483
+ if (this.manifest_.isLowLatency && !this.config_.streaming.lowLatencyMode) {
2484
+ shaka.log.alwaysWarn('Low-latency live stream detected, but ' +
2485
+ 'low-latency streaming mode is not enabled in Shaka Player. ' +
2486
+ 'Set streaming.lowLatencyMode configuration to true, and see ' +
2487
+ 'https://bit.ly/3clctcj for details.');
2488
+ }
2489
+
2403
2490
  shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
2404
2491
  this.config_.playRangeStart,
2405
2492
  this.config_.playRangeEnd);
@@ -2508,9 +2595,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2508
2595
 
2509
2596
  const isLive = this.isLive();
2510
2597
 
2511
- if ((isLive && (this.config_.streaming.liveSync ||
2598
+ if ((isLive && ((this.config_.streaming.liveSync &&
2599
+ this.config_.streaming.liveSync.enabled) ||
2512
2600
  this.manifest_.serviceDescription ||
2513
- this.config_.streaming.liveSyncPanicMode)) ||
2601
+ this.config_.streaming.liveSync.panicMode)) ||
2514
2602
  this.config_.streaming.vodDynamicPlaybackRate) {
2515
2603
  const onTimeUpdate = () => this.onTimeUpdate_();
2516
2604
  this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
@@ -2895,8 +2983,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2895
2983
 
2896
2984
  const isLive = this.isLive();
2897
2985
 
2898
- if ((isLive && (this.config_.streaming.liveSync ||
2899
- this.config_.streaming.liveSyncPanicMode)) ||
2986
+ if ((isLive && ((this.config_.streaming.liveSync &&
2987
+ this.config_.streaming.liveSync.enabled) ||
2988
+ this.config_.streaming.liveSync.panicMode)) ||
2900
2989
  this.config_.streaming.vodDynamicPlaybackRate) {
2901
2990
  const onTimeUpdate = () => this.onTimeUpdate_();
2902
2991
  this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
@@ -2985,6 +3074,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2985
3074
  return;
2986
3075
  }
2987
3076
 
3077
+ /** @type {!Array.<shaka.extern.Interstitial>} */
3078
+ const interstitials = [];
3079
+
2988
3080
  for (const cue of track.activeCues) {
2989
3081
  this.dispatchMetadataEvent_(cue.startTime, cue.endTime,
2990
3082
  cue.type, cue.value);
@@ -2992,6 +3084,42 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2992
3084
  if (this.adManager_) {
2993
3085
  this.adManager_.onCueMetadataChange(cue.value);
2994
3086
  }
3087
+ if (cue.type == 'com.apple.quicktime.HLS' && cue.startTime != null) {
3088
+ let interstitial = interstitials.find((i) => {
3089
+ return i.startTime == cue.startTime && i.endTime == cue.endTime;
3090
+ });
3091
+ if (!interstitial) {
3092
+ interstitial = /** @type {shaka.extern.Interstitial} */ ({
3093
+ startTime: cue.startTime,
3094
+ endTime: cue.endTime,
3095
+ values: [],
3096
+ });
3097
+ interstitials.push(interstitial);
3098
+ }
3099
+ interstitial.values.push(cue.value);
3100
+ }
3101
+ }
3102
+ for (const interstitial of interstitials) {
3103
+ const isValidInterstitial = interstitial.values.some((value) => {
3104
+ return value.key == 'X-ASSET-URI' || value.key == 'X-ASSET-LIST';
3105
+ });
3106
+ if (!isValidInterstitial) {
3107
+ continue;
3108
+ }
3109
+ if (this.adManager_) {
3110
+ // It seems that CUE is natively omitted, by default we use CUE=ONCE
3111
+ // to avoid repeating them.
3112
+ interstitial.values.push({
3113
+ key: 'CUE',
3114
+ description: '',
3115
+ data: 'ONCE',
3116
+ mimeType: null,
3117
+ pictureType: null,
3118
+ });
3119
+ goog.asserts.assert(this.video_, 'Must have video');
3120
+ this.adManager_.onInterstitialMetadata(
3121
+ this, this.video_, interstitial);
3122
+ }
2995
3123
  }
2996
3124
  });
2997
3125
 
@@ -3215,6 +3343,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3215
3343
  const stats = getStats();
3216
3344
  if (stats) {
3217
3345
  stats.addBytesDownloaded(bytesDownloaded);
3346
+ if (type === shaka.net.NetworkingEngine.RequestType.MANIFEST) {
3347
+ stats.setManifestSize(bytesDownloaded);
3348
+ }
3218
3349
  }
3219
3350
  }
3220
3351
  };
@@ -3526,6 +3657,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3526
3657
  * @export
3527
3658
  */
3528
3659
  configure(config, value) {
3660
+ const Platform = shaka.util.Platform;
3661
+
3529
3662
  goog.asserts.assert(this.config_, 'Config must not be null!');
3530
3663
  goog.asserts.assert(typeof(config) == 'object' || arguments.length == 2,
3531
3664
  'String configs should have values!');
@@ -3563,6 +3696,121 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3563
3696
  'streaming.useNativeHlsOnSafari configuration',
3564
3697
  'Please Use streaming.useNativeHlsForFairPlay or ' +
3565
3698
  'streaming.preferNativeHls instead.');
3699
+ config['streaming']['preferNativeHls'] =
3700
+ config['streaming']['useNativeHlsOnSafari'] && Platform.isApple();
3701
+ delete config['streaming']['useNativeHlsOnSafari'];
3702
+ }
3703
+
3704
+ // Deprecate 'streaming.liveSync' boolean configuration.
3705
+ if (config['streaming'] &&
3706
+ typeof config['streaming']['liveSync'] == 'boolean') {
3707
+ shaka.Deprecate.deprecateFeature(5,
3708
+ 'streaming.liveSync',
3709
+ 'Please Use streaming.liveSync.enabled instead.');
3710
+ const liveSyncValue = config['streaming']['liveSync'];
3711
+ config['streaming']['liveSync'] = {};
3712
+ config['streaming']['liveSync']['enabled'] = liveSyncValue;
3713
+ }
3714
+
3715
+ // map liveSyncMinLatency and liveSyncMaxLatency to liveSync.targetLatency
3716
+ // if liveSync.targetLatency isn't set.
3717
+ if (config['streaming'] && (!config['streaming']['liveSync'] ||
3718
+ !('targetLatency' in config['streaming']['liveSync'])) &&
3719
+ ('liveSyncMinLatency' in config['streaming'] ||
3720
+ 'liveSyncMaxLatency' in config['streaming'])) {
3721
+ const min = config['streaming']['liveSyncMinLatency'] || 0;
3722
+ const max = config['streaming']['liveSyncMaxLatency'] || 1;
3723
+ const mid = Math.abs(max - min) / 2;
3724
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3725
+ config['streaming']['liveSync']['targetLatency'] = min + mid;
3726
+ config['streaming']['liveSync']['targetLatencyTolerance'] = mid;
3727
+ }
3728
+ // Deprecate 'streaming.liveSyncMaxLatency' configuration.
3729
+ if (config['streaming'] && 'liveSyncMaxLatency' in config['streaming']) {
3730
+ shaka.Deprecate.deprecateFeature(5,
3731
+ 'streaming.liveSyncMaxLatency',
3732
+ 'Please Use streaming.liveSync.targetLatency and ' +
3733
+ 'streaming.liveSync.targetLatencyTolerance instead. ' +
3734
+ 'Or, set the values in your DASH manifest');
3735
+ delete config['streaming']['liveSyncMaxLatency'];
3736
+ }
3737
+ // Deprecate 'streaming.liveSyncMinLatency' configuration.
3738
+ if (config['streaming'] && 'liveSyncMinLatency' in config['streaming']) {
3739
+ shaka.Deprecate.deprecateFeature(5,
3740
+ 'streaming.liveSyncMinLatency',
3741
+ 'Please Use streaming.liveSync.targetLatency and ' +
3742
+ 'streaming.liveSync.targetLatencyTolerance instead. ' +
3743
+ 'Or, set the values in your DASH manifest');
3744
+ delete config['streaming']['liveSyncMinLatency'];
3745
+ }
3746
+
3747
+ // Deprecate 'streaming.liveSyncTargetLatency' configuration.
3748
+ if (config['streaming'] && 'liveSyncTargetLatency' in config['streaming']) {
3749
+ shaka.Deprecate.deprecateFeature(5,
3750
+ 'streaming.liveSyncTargetLatency',
3751
+ 'Please Use streaming.liveSync.targetLatency instead.');
3752
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3753
+ config['streaming']['liveSync']['targetLatency'] =
3754
+ config['streaming']['liveSyncTargetLatency'];
3755
+ delete config['streaming']['liveSyncTargetLatency'];
3756
+ }
3757
+
3758
+ // Deprecate 'streaming.liveSyncTargetLatencyTolerance' configuration.
3759
+ if (config['streaming'] &&
3760
+ 'liveSyncTargetLatencyTolerance' in config['streaming']) {
3761
+ shaka.Deprecate.deprecateFeature(5,
3762
+ 'streaming.liveSyncTargetLatencyTolerance',
3763
+ 'Please Use streaming.liveSync.targetLatencyTolerance instead.');
3764
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3765
+ config['streaming']['liveSync']['targetLatencyTolerance'] =
3766
+ config['streaming']['liveSyncTargetLatencyTolerance'];
3767
+ delete config['streaming']['liveSyncTargetLatencyTolerance'];
3768
+ }
3769
+
3770
+ // Deprecate 'streaming.liveSyncPlaybackRate' configuration.
3771
+ if (config['streaming'] && 'liveSyncPlaybackRate' in config['streaming']) {
3772
+ shaka.Deprecate.deprecateFeature(5,
3773
+ 'streaming.liveSyncPlaybackRate',
3774
+ 'Please Use streaming.liveSync.maxPlaybackRate instead.');
3775
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3776
+ config['streaming']['liveSync']['maxPlaybackRate'] =
3777
+ config['streaming']['liveSyncPlaybackRate'];
3778
+ delete config['streaming']['liveSyncPlaybackRate'];
3779
+ }
3780
+
3781
+ // Deprecate 'streaming.liveSyncMinPlaybackRate' configuration.
3782
+ if (config['streaming'] &&
3783
+ 'liveSyncMinPlaybackRate' in config['streaming']) {
3784
+ shaka.Deprecate.deprecateFeature(5,
3785
+ 'streaming.liveSyncMinPlaybackRate',
3786
+ 'Please Use streaming.liveSync.minPlaybackRate instead.');
3787
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3788
+ config['streaming']['liveSync']['minPlaybackRate'] =
3789
+ config['streaming']['liveSyncMinPlaybackRate'];
3790
+ delete config['streaming']['liveSyncMinPlaybackRate'];
3791
+ }
3792
+
3793
+ // Deprecate 'streaming.liveSyncPanicMode' configuration.
3794
+ if (config['streaming'] && 'liveSyncPanicMode' in config['streaming']) {
3795
+ shaka.Deprecate.deprecateFeature(5,
3796
+ 'streaming.liveSyncPanicMode',
3797
+ 'Please Use streaming.liveSync.panicMode instead.');
3798
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3799
+ config['streaming']['liveSync']['panicMode'] =
3800
+ config['streaming']['liveSyncPanicMode'];
3801
+ delete config['streaming']['liveSyncPanicMode'];
3802
+ }
3803
+
3804
+ // Deprecate 'streaming.liveSyncPanicThreshold' configuration.
3805
+ if (config['streaming'] &&
3806
+ 'liveSyncPanicThreshold' in config['streaming']) {
3807
+ shaka.Deprecate.deprecateFeature(5,
3808
+ 'streaming.liveSyncPanicThreshold',
3809
+ 'Please Use streaming.liveSync.panicThreshold instead.');
3810
+ config['streaming']['liveSync'] = config['streaming']['liveSync'] || {};
3811
+ config['streaming']['liveSync']['panicThreshold'] =
3812
+ config['streaming']['liveSyncPanicThreshold'];
3813
+ delete config['streaming']['liveSyncPanicThreshold'];
3566
3814
  }
3567
3815
 
3568
3816
  // Deprecate 'mediaSource.sourceBufferExtraFeatures' configuration.
@@ -4064,6 +4312,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4064
4312
  }
4065
4313
  }
4066
4314
 
4315
+ /**
4316
+ * Indicates if the player has fully loaded the stream.
4317
+ *
4318
+ * @return {boolean}
4319
+ * @export
4320
+ */
4321
+ isFullyLoaded() {
4322
+ return this.fullyLoaded_;
4323
+ }
4324
+
4067
4325
  /**
4068
4326
  * Get the key system currently used by EME. If EME is not being used, this
4069
4327
  * will return an empty string. If the player has not loaded content, this
@@ -4708,9 +4966,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4708
4966
  * @param {string=} role
4709
4967
  * @param {number=} channelsCount
4710
4968
  * @param {number=} safeMargin
4969
+ * @param {string=} codec
4711
4970
  * @export
4712
4971
  */
4713
- selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0) {
4972
+ selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0,
4973
+ codec = '') {
4714
4974
  if (this.manifest_ && this.playhead_) {
4715
4975
  this.currentAdaptationSetCriteria_ =
4716
4976
  new shaka.media.PreferenceBasedCriteria(
@@ -4723,7 +4983,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4723
4983
  /* audioLabel= */ '',
4724
4984
  /* videoLabel= */ '',
4725
4985
  this.config_.mediaSource.codecSwitchingStrategy,
4726
- this.config_.manifest.dash.enableAudioGroups);
4986
+ this.config_.manifest.dash.enableAudioGroups,
4987
+ codec);
4727
4988
 
4728
4989
  const diff = (a, b) => {
4729
4990
  if (!a.video && !b.video) {
@@ -4743,6 +5004,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4743
5004
  this.currentAdaptationSetCriteria_.create(this.manifest_.variants);
4744
5005
  let bestVariant = null;
4745
5006
  for (const curVariant of set.values()) {
5007
+ if (!shaka.util.StreamUtils.isPlayable(curVariant)) {
5008
+ continue;
5009
+ }
4746
5010
  if (!bestVariant ||
4747
5011
  diff(bestVariant, active) > diff(curVariant, active)) {
4748
5012
  bestVariant = curVariant;
@@ -5269,10 +5533,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5269
5533
  this.stats_.setLiveLatency(latency);
5270
5534
  }
5271
5535
 
5272
- if (this.manifest_ && this.manifest_.presentationTimeline) {
5273
- const maxSegmentDuration =
5274
- this.manifest_.presentationTimeline.getMaxSegmentDuration();
5275
- this.stats_.setMaxSegmentDuration(maxSegmentDuration);
5536
+ if (this.manifest_) {
5537
+ this.stats_.setManifestPeriodCount(this.manifest_.periodCount);
5538
+ this.stats_.setManifestGapCount(this.manifest_.gapCount);
5539
+ if (this.manifest_.presentationTimeline) {
5540
+ const maxSegmentDuration =
5541
+ this.manifest_.presentationTimeline.getMaxSegmentDuration();
5542
+ this.stats_.setMaxSegmentDuration(maxSegmentDuration);
5543
+ }
5276
5544
  }
5277
5545
 
5278
5546
  const estimate = this.abrManager_.getBandwidthEstimate();
@@ -6096,6 +6364,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6096
6364
  this.cmcdManager_.setBuffering(isBuffering);
6097
6365
  }
6098
6366
  this.updateStateHistory_();
6367
+
6368
+ const dynamicTargetLatency =
6369
+ this.config_.streaming.liveSync.dynamicTargetLatency.enabled;
6370
+ const maxAttempts =
6371
+ this.config_.streaming.liveSync.dynamicTargetLatency.maxAttempts;
6372
+
6373
+
6374
+ if (dynamicTargetLatency && isBuffering &&
6375
+ this.rebufferingCount_ < maxAttempts) {
6376
+ const maxLatency =
6377
+ this.config_.streaming.liveSync.dynamicTargetLatency.maxLatency;
6378
+
6379
+ const targetLatencyTolerance =
6380
+ this.config_.streaming.liveSync.targetLatencyTolerance;
6381
+ const rebufferIncrement =
6382
+ this.config_.streaming.liveSync.dynamicTargetLatency
6383
+ .rebufferIncrement;
6384
+ if (this.currentTargetLatency_) {
6385
+ this.currentTargetLatency_ = Math.min(
6386
+ this.currentTargetLatency_ +
6387
+ ++this.rebufferingCount_ * rebufferIncrement,
6388
+ maxLatency - targetLatencyTolerance);
6389
+ }
6390
+ }
6099
6391
  }
6100
6392
 
6101
6393
  // Surface the buffering event so that the app knows if/when we are
@@ -6222,52 +6514,78 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6222
6514
  return;
6223
6515
  }
6224
6516
 
6225
- let liveSyncMaxLatency;
6226
- let liveSyncPlaybackRate;
6227
- if (this.config_.streaming.liveSync) {
6228
- liveSyncMaxLatency = this.config_.streaming.liveSyncMaxLatency;
6229
- liveSyncPlaybackRate = this.config_.streaming.liveSyncPlaybackRate;
6517
+ let targetLatency;
6518
+ let maxLatency;
6519
+ let maxPlaybackRate;
6520
+ let minLatency;
6521
+ let minPlaybackRate;
6522
+ const targetLatencyTolerance =
6523
+ this.config_.streaming.liveSync.targetLatencyTolerance;
6524
+ const dynamicTargetLatency =
6525
+ this.config_.streaming.liveSync.dynamicTargetLatency.enabled;
6526
+ const stabilityThreshold =
6527
+ this.config_.streaming.liveSync.dynamicTargetLatency.stabilityThreshold;
6528
+
6529
+ if (this.config_.streaming.liveSync &&
6530
+ this.config_.streaming.liveSync.enabled) {
6531
+ targetLatency = this.config_.streaming.liveSync.targetLatency;
6532
+ maxLatency = targetLatency + targetLatencyTolerance;
6533
+ minLatency = Math.max(0, targetLatency - targetLatencyTolerance);
6534
+ maxPlaybackRate = this.config_.streaming.liveSync.maxPlaybackRate;
6535
+ minPlaybackRate = this.config_.streaming.liveSync.minPlaybackRate;
6230
6536
  } else {
6231
6537
  // serviceDescription must override if it is defined in the MPD and
6232
6538
  // liveSync configuration is not set.
6233
6539
  if (this.manifest_ && this.manifest_.serviceDescription) {
6234
- liveSyncMaxLatency = this.config_.streaming.liveSyncMaxLatency;
6540
+ targetLatency = this.manifest_.serviceDescription.targetLatency;
6235
6541
  if (this.manifest_.serviceDescription.targetLatency != null) {
6236
- liveSyncMaxLatency =
6237
- this.manifest_.serviceDescription.targetLatency +
6238
- this.config_.streaming.liveSyncTargetLatencyTolerance;
6542
+ maxLatency = this.manifest_.serviceDescription.targetLatency +
6543
+ targetLatencyTolerance;
6239
6544
  } else if (this.manifest_.serviceDescription.maxLatency != null) {
6240
- liveSyncMaxLatency = this.manifest_.serviceDescription.maxLatency;
6545
+ maxLatency = this.manifest_.serviceDescription.maxLatency;
6241
6546
  }
6242
- liveSyncPlaybackRate =
6243
- this.manifest_.serviceDescription.maxPlaybackRate ||
6244
- this.config_.streaming.liveSyncPlaybackRate;
6245
- }
6246
- }
6247
-
6248
- let liveSyncMinLatency;
6249
- let liveSyncMinPlaybackRate;
6250
- if (this.config_.streaming.liveSync) {
6251
- liveSyncMinLatency = this.config_.streaming.liveSyncMinLatency;
6252
- liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
6253
- } else {
6254
- // serviceDescription must override if it is defined in the MPD and
6255
- // liveSync configuration is not set.
6256
- if (this.manifest_ && this.manifest_.serviceDescription) {
6257
- liveSyncMinLatency = this.config_.streaming.liveSyncMinLatency;
6258
6547
  if (this.manifest_.serviceDescription.targetLatency != null) {
6259
- liveSyncMinLatency =
6548
+ minLatency = Math.max(0,
6260
6549
  this.manifest_.serviceDescription.targetLatency -
6261
- this.config_.streaming.liveSyncTargetLatencyTolerance;
6550
+ targetLatencyTolerance);
6262
6551
  } else if (this.manifest_.serviceDescription.minLatency != null) {
6263
- liveSyncMinLatency = this.manifest_.serviceDescription.minLatency;
6552
+ minLatency = this.manifest_.serviceDescription.minLatency;
6264
6553
  }
6265
- liveSyncMinPlaybackRate =
6554
+ maxPlaybackRate =
6555
+ this.manifest_.serviceDescription.maxPlaybackRate ||
6556
+ this.config_.streaming.liveSync.maxPlaybackRate;
6557
+ minPlaybackRate =
6266
6558
  this.manifest_.serviceDescription.minPlaybackRate ||
6267
- this.config_.streaming.liveSyncMinPlaybackRate;
6559
+ this.config_.streaming.liveSync.minPlaybackRate;
6268
6560
  }
6269
6561
  }
6270
6562
 
6563
+ if (!this.currentTargetLatency_ && typeof targetLatency === 'number') {
6564
+ this.currentTargetLatency_ = targetLatency;
6565
+ }
6566
+
6567
+ const maxAttempts =
6568
+ this.config_.streaming.liveSync.dynamicTargetLatency.maxAttempts;
6569
+ if (dynamicTargetLatency && this.targetLatencyReached_ &&
6570
+ this.currentTargetLatency_ !== null &&
6571
+ typeof targetLatency === 'number' &&
6572
+ this.rebufferingCount_ < maxAttempts &&
6573
+ (Date.now() - this.targetLatencyReached_) > stabilityThreshold * 1000) {
6574
+ const dynamicMinLatency =
6575
+ this.config_.streaming.liveSync.dynamicTargetLatency.minLatency;
6576
+ const latencyIncrement = (targetLatency - dynamicMinLatency) / 2;
6577
+ this.currentTargetLatency_ = Math.max(
6578
+ this.currentTargetLatency_ - latencyIncrement,
6579
+ // current target latency should be within the tolerance of the min
6580
+ // latency to not overshoot it
6581
+ dynamicMinLatency + targetLatencyTolerance);
6582
+ this.targetLatencyReached_ = Date.now();
6583
+ }
6584
+ if (dynamicTargetLatency && this.currentTargetLatency_ !== null) {
6585
+ maxLatency = this.currentTargetLatency_ + targetLatencyTolerance;
6586
+ minLatency = this.currentTargetLatency_ - targetLatencyTolerance;
6587
+ }
6588
+
6271
6589
  const latency = seekRange.end - this.video_.currentTime;
6272
6590
  let offset = 0;
6273
6591
  // In src= mode, the seek range isn't updated frequently enough, so we need
@@ -6278,45 +6596,49 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6278
6596
  const buffered = this.video_.buffered;
6279
6597
  if (buffered.length > 0) {
6280
6598
  const bufferedEnd = buffered.end(buffered.length - 1);
6281
- offset = Math.max(liveSyncPlaybackRate, bufferedEnd - seekRange.end);
6599
+ offset = Math.max(maxPlaybackRate, bufferedEnd - seekRange.end);
6282
6600
  }
6283
6601
  }
6284
6602
 
6285
- const panicMode = this.config_.streaming.liveSyncPanicMode;
6286
- const panicThreshold = this.config_.streaming.liveSyncPanicThreshold * 1000;
6603
+ const panicMode = this.config_.streaming.liveSync.panicMode;
6604
+ const panicThreshold =
6605
+ this.config_.streaming.liveSync.panicThreshold * 1000;
6287
6606
  const timeSinceLastRebuffer =
6288
6607
  Date.now() - this.bufferObserver_.getLastRebufferTime();
6289
- if (panicMode && !liveSyncMinPlaybackRate) {
6290
- liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
6608
+ if (panicMode && !minPlaybackRate) {
6609
+ minPlaybackRate = this.config_.streaming.liveSync.minPlaybackRate;
6291
6610
  }
6292
6611
 
6293
- if (panicMode && liveSyncMinPlaybackRate &&
6612
+ if (panicMode && minPlaybackRate &&
6294
6613
  timeSinceLastRebuffer <= panicThreshold) {
6295
- if (playbackRate != liveSyncMinPlaybackRate) {
6614
+ if (playbackRate != minPlaybackRate) {
6296
6615
  shaka.log.debug('Time since last rebuffer (' +
6297
6616
  timeSinceLastRebuffer + 's) ' +
6298
- 'is less than the liveSyncPanicThreshold (' + panicThreshold +
6299
- 's). Updating playbackRate to ' + liveSyncMinPlaybackRate);
6300
- this.trickPlay(liveSyncMinPlaybackRate);
6301
- }
6302
- } else if (liveSyncMaxLatency && liveSyncPlaybackRate &&
6303
- (latency - offset) > liveSyncMaxLatency) {
6304
- if (playbackRate != liveSyncPlaybackRate) {
6305
- shaka.log.debug('Latency (' + latency + 's) ' +
6306
- 'is greater than liveSyncMaxLatency (' + liveSyncMaxLatency + 's). ' +
6307
- 'Updating playbackRate to ' + liveSyncPlaybackRate);
6308
- this.trickPlay(liveSyncPlaybackRate);
6309
- }
6310
- } else if (liveSyncMinLatency && liveSyncMinPlaybackRate &&
6311
- (latency - offset) < liveSyncMinLatency) {
6312
- if (playbackRate != liveSyncMinPlaybackRate) {
6313
- shaka.log.debug('Latency (' + latency + 's) ' +
6314
- 'is smaller than liveSyncMinLatency (' + liveSyncMinLatency + 's). ' +
6315
- 'Updating playbackRate to ' + liveSyncMinPlaybackRate);
6316
- this.trickPlay(liveSyncMinPlaybackRate);
6317
- }
6617
+ 'is less than the live sync panicThreshold (' + panicThreshold +
6618
+ 's). Updating playbackRate to ' + minPlaybackRate);
6619
+ this.trickPlay(minPlaybackRate);
6620
+ }
6621
+ } else if (maxLatency && maxPlaybackRate &&
6622
+ (latency - offset) > maxLatency) {
6623
+ if (playbackRate != maxPlaybackRate) {
6624
+ shaka.log.debug('Latency (' + latency + 's) is greater than ' +
6625
+ 'live sync maxLatency (' + maxLatency + 's). ' +
6626
+ 'Updating playbackRate to ' + maxPlaybackRate);
6627
+ this.trickPlay(maxPlaybackRate);
6628
+ }
6629
+ this.targetLatencyReached_ = null;
6630
+ } else if (minLatency && minPlaybackRate &&
6631
+ (latency - offset) < minLatency) {
6632
+ if (playbackRate != minPlaybackRate) {
6633
+ shaka.log.debug('Latency (' + latency + 's) is smaller than ' +
6634
+ 'live sync minLatency (' + minLatency + 's). ' +
6635
+ 'Updating playbackRate to ' + minPlaybackRate);
6636
+ this.trickPlay(minPlaybackRate);
6637
+ }
6638
+ this.targetLatencyReached_ = null;
6318
6639
  } else if (playbackRate !== this.playRateController_.getDefaultRate()) {
6319
6640
  this.cancelTrickPlay();
6641
+ this.targetLatencyReached_ = Date.now();
6320
6642
  }
6321
6643
  }
6322
6644
 
@@ -6458,6 +6780,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6458
6780
  return str;
6459
6781
  };
6460
6782
 
6783
+ let shouldStopTimer = true;
6461
6784
  for (const variant of this.manifest_.variants) {
6462
6785
  if (variant.disabledUntilTime > 0 && variant.disabledUntilTime <= now) {
6463
6786
  variant.disabledUntilTime = 0;
@@ -6465,15 +6788,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6465
6788
 
6466
6789
  shaka.log.v2('Re-enabled variant with ' + streamsAsString(variant));
6467
6790
  }
6791
+ if (variant.disabledUntilTime > 0) {
6792
+ shouldStopTimer = false;
6793
+ }
6468
6794
  }
6469
6795
 
6470
- const shouldStopTimer = this.manifest_.variants.every((variant) => {
6471
- goog.asserts.assert(
6472
- variant.disabledUntilTime >= 0,
6473
- '|variant.disableTimeUntilTime| must always be >= 0');
6474
- return variant.disabledUntilTime === 0;
6475
- });
6476
-
6477
6796
  if (shouldStopTimer) {
6478
6797
  this.checkVariantsTimer_.stop();
6479
6798
  }
@@ -6906,7 +7225,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6906
7225
  const hasAltStream = this.manifest_.variants.some((variant) => {
6907
7226
  const altStream = variant[stream.type];
6908
7227
 
6909
- if (altStream && altStream.id !== stream.id) {
7228
+ if (altStream && altStream.id !== stream.id &&
7229
+ !variant.disabledUntilTime) {
6910
7230
  if (shaka.util.StreamUtils.isAudio(stream)) {
6911
7231
  return stream.language === altStream.language;
6912
7232
  }
@@ -6967,6 +7287,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6967
7287
  return;
6968
7288
  }
6969
7289
 
7290
+ if (error.severity === shaka.util.Error.Severity.RECOVERABLE) {
7291
+ this.stats_.addNonFatalError();
7292
+ }
7293
+
6970
7294
  let fireError = true;
6971
7295
  if (this.fullyLoaded_ && this.manifest_ && this.streamingEngine_ &&
6972
7296
  (error.code == shaka.util.Error.Code.VIDEO_ERROR ||
@@ -7032,10 +7356,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7032
7356
  *
7033
7357
  * @param {shaka.extern.MediaQualityInfo} mediaQuality
7034
7358
  * @param {number} position
7359
+ * @param {boolean} audioTrackChanged This is to specify whether this should
7360
+ * trigger a MediaQualityChangedEvent or an AudioTrackChangedEvent. Defaults
7361
+ * to false to trigger MediaQualityChangedEvent.
7035
7362
  *
7036
7363
  * @private
7037
7364
  */
7038
- onMediaQualityChange_(mediaQuality, position) {
7365
+ onMediaQualityChange_(mediaQuality, position, audioTrackChanged = false) {
7039
7366
  // Always make a copy to avoid exposing our internal data to the app.
7040
7367
  const clone = {
7041
7368
  bandwidth: mediaQuality.bandwidth,
@@ -7048,6 +7375,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7048
7375
  channelsCount: mediaQuality.channelsCount,
7049
7376
  pixelAspectRatio: mediaQuality.pixelAspectRatio,
7050
7377
  width: mediaQuality.width,
7378
+ label: mediaQuality.label,
7379
+ roles: mediaQuality.roles,
7380
+ language: mediaQuality.language,
7051
7381
  };
7052
7382
 
7053
7383
  const data = new Map()
@@ -7055,7 +7385,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7055
7385
  .set('position', position);
7056
7386
 
7057
7387
  this.dispatchEvent(shaka.Player.makeEvent_(
7058
- shaka.util.FakeEvent.EventName.MediaQualityChanged, data));
7388
+ audioTrackChanged ?
7389
+ shaka.util.FakeEvent.EventName.AudioTrackChanged :
7390
+ shaka.util.FakeEvent.EventName.MediaQualityChanged,
7391
+ data));
7059
7392
  }
7060
7393
 
7061
7394
  /**
@@ -7473,7 +7806,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7473
7806
  * @export
7474
7807
  */
7475
7808
  // eslint-disable-next-line no-useless-concat
7476
- shaka.Player.version = 'v4.9.13' + '-uncompiled'; // x-release-please-version
7809
+ shaka.Player.version = 'v4.10.1' + '-uncompiled'; // x-release-please-version
7477
7810
 
7478
7811
  // Initialize the deprecation system using the version string we just set
7479
7812
  // on the player.