shaka-player 3.3.4 → 4.0.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 (201) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/build-and-test.yaml +0 -1
  3. package/.github/workflows/demo-version-index.yaml +8 -0
  4. package/.github/workflows/release-please.yaml +19 -3
  5. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  6. package/.github/workflows/sync-labels.yaml +33 -0
  7. package/.github/workflows/update-issues.yaml +29 -0
  8. package/.github/workflows/validate-pr-title.yaml +35 -0
  9. package/AUTHORS +1 -0
  10. package/CHANGELOG.md +209 -28
  11. package/CONTRIBUTORS +1 -0
  12. package/README.md +10 -7
  13. package/app-engine/demo-version-index/README.md +3 -3
  14. package/app-engine/demo-version-index/app.yaml +27 -1
  15. package/app-engine/demo-version-index/generate.py +164 -0
  16. package/app-engine/demo-version-index/main.py +4 -155
  17. package/app-engine/demo-version-index/requirements.txt +0 -1
  18. package/build/README.md +2 -2
  19. package/build/compiler.py +1 -1
  20. package/build/generateTsDefs.py +0 -2
  21. package/build/misspellings.txt +1 -0
  22. package/build/shaka-lab.yaml +0 -2
  23. package/build/test.py +7 -1
  24. package/build/types/core +0 -1
  25. package/build/types/polyfill +0 -3
  26. package/demo/common/asset.js +17 -1
  27. package/demo/common/assets.js +29 -0
  28. package/demo/common/message_ids.js +6 -3
  29. package/demo/config.js +10 -11
  30. package/demo/custom.js +39 -0
  31. package/demo/demo_utils.js +0 -1
  32. package/demo/locales/en.json +6 -3
  33. package/demo/locales/source.json +22 -10
  34. package/demo/main.js +9 -6
  35. package/demo/search.js +2 -1
  36. package/dist/controls.css +1 -1
  37. package/dist/demo.compiled.debug.js +71 -64
  38. package/dist/demo.compiled.debug.map +3 -3
  39. package/dist/demo.compiled.js +71 -64
  40. package/dist/demo.compiled.map +3 -3
  41. package/dist/demo.css +1 -1
  42. package/dist/demo.css.map +1 -1
  43. package/dist/deps.js +16 -20
  44. package/dist/locales.js +33 -31
  45. package/dist/receiver.compiled.debug.js +32 -28
  46. package/dist/receiver.compiled.debug.map +3 -3
  47. package/dist/receiver.compiled.js +32 -28
  48. package/dist/receiver.compiled.map +3 -3
  49. package/dist/shaka-player.compiled.d.ts +78 -82
  50. package/dist/shaka-player.compiled.debug.d.ts +78 -82
  51. package/dist/shaka-player.compiled.debug.externs.js +127 -126
  52. package/dist/shaka-player.compiled.debug.js +471 -523
  53. package/dist/shaka-player.compiled.debug.map +4 -4
  54. package/dist/shaka-player.compiled.externs.js +127 -126
  55. package/dist/shaka-player.compiled.js +844 -858
  56. package/dist/shaka-player.compiled.map +4 -4
  57. package/dist/shaka-player.ui.d.ts +80 -82
  58. package/dist/shaka-player.ui.debug.d.ts +80 -82
  59. package/dist/shaka-player.ui.debug.externs.js +135 -126
  60. package/dist/shaka-player.ui.debug.js +531 -581
  61. package/dist/shaka-player.ui.debug.map +4 -4
  62. package/dist/shaka-player.ui.externs.js +135 -126
  63. package/dist/shaka-player.ui.js +991 -1001
  64. package/dist/shaka-player.ui.map +4 -4
  65. package/docs/tutorials/config.md +0 -2
  66. package/docs/tutorials/drm-config.md +5 -0
  67. package/docs/tutorials/fairplay.md +7 -22
  68. package/docs/tutorials/faq.md +0 -11
  69. package/docs/tutorials/index.json +2 -8
  70. package/docs/tutorials/plugins.md +1 -3
  71. package/docs/tutorials/upgrade.md +85 -11
  72. package/externs/polyfill.js +15 -0
  73. package/externs/shaka/manifest.js +5 -1
  74. package/externs/shaka/net.js +12 -0
  75. package/externs/shaka/offline.js +5 -1
  76. package/externs/shaka/player.js +39 -21
  77. package/externs/shaka/text.js +12 -12
  78. package/externs/sourcebuffer.js +15 -0
  79. package/karma.conf.js +4 -0
  80. package/lib/ads/client_side_ad.js +13 -2
  81. package/lib/ads/client_side_ad_manager.js +22 -4
  82. package/lib/cast/cast_utils.js +0 -1
  83. package/lib/dash/content_protection.js +85 -5
  84. package/lib/dash/dash_parser.js +3 -3
  85. package/lib/hls/hls_parser.js +391 -747
  86. package/lib/hls/manifest_text_parser.js +2 -3
  87. package/lib/media/drm_engine.js +102 -37
  88. package/lib/media/gap_jumping_controller.js +10 -45
  89. package/lib/media/media_source_engine.js +157 -31
  90. package/lib/media/playhead.js +2 -5
  91. package/lib/media/presentation_timeline.js +1 -2
  92. package/lib/media/segment_index.js +13 -46
  93. package/lib/media/segment_reference.js +47 -1
  94. package/lib/media/streaming_engine.js +28 -11
  95. package/lib/media/time_ranges_utils.js +4 -9
  96. package/lib/net/networking_engine.js +3 -0
  97. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  98. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  99. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  100. package/lib/offline/manifest_converter.js +1 -0
  101. package/lib/offline/storage.js +100 -132
  102. package/lib/player.js +181 -307
  103. package/lib/polyfill/media_capabilities.js +12 -6
  104. package/lib/polyfill/mediasource.js +0 -28
  105. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  106. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  107. package/lib/text/cue.js +0 -6
  108. package/lib/text/lrc_text_parser.js +8 -0
  109. package/lib/text/mp4_ttml_parser.js +8 -0
  110. package/lib/text/mp4_vtt_parser.js +8 -0
  111. package/lib/text/sbv_text_parser.js +8 -0
  112. package/lib/text/simple_text_displayer.js +1 -7
  113. package/lib/text/srt_text_parser.js +8 -0
  114. package/lib/text/ssa_text_parser.js +8 -0
  115. package/lib/text/text_engine.js +19 -3
  116. package/lib/text/ttml_text_parser.js +8 -0
  117. package/lib/text/ui_text_displayer.js +1 -7
  118. package/lib/text/vtt_text_parser.js +27 -5
  119. package/lib/text/web_vtt_generator.js +1 -7
  120. package/lib/util/config_utils.js +25 -0
  121. package/lib/util/error.js +16 -13
  122. package/lib/util/fairplay_utils.js +109 -0
  123. package/lib/util/functional.js +0 -42
  124. package/lib/util/manifest_parser_utils.js +1 -1
  125. package/lib/util/mime_utils.js +50 -1
  126. package/lib/util/periods.js +5 -4
  127. package/lib/util/platform.js +45 -0
  128. package/lib/util/player_configuration.js +26 -20
  129. package/lib/util/pssh.js +23 -4
  130. package/lib/util/stream_utils.js +96 -24
  131. package/lib/util/uint8array_utils.js +0 -16
  132. package/lib/util/xml_utils.js +1 -1
  133. package/package.json +4 -1
  134. package/roadmap.md +19 -19
  135. package/shaka-player.uncompiled.js +0 -3
  136. package/test/cast/cast_utils_unit.js +1 -0
  137. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  138. package/test/dash/dash_parser_manifest_unit.js +0 -24
  139. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  140. package/test/demo/demo_unit.js +2 -1
  141. package/test/hls/hls_live_unit.js +121 -220
  142. package/test/hls/hls_parser_unit.js +515 -432
  143. package/test/hls/manifest_text_parser_unit.js +19 -0
  144. package/test/media/drm_engine_unit.js +86 -120
  145. package/test/media/media_source_engine_integration.js +26 -3
  146. package/test/media/media_source_engine_unit.js +7 -6
  147. package/test/media/playhead_unit.js +27 -191
  148. package/test/media/segment_index_unit.js +15 -25
  149. package/test/media/streaming_engine_integration.js +4 -39
  150. package/test/media/streaming_engine_unit.js +3 -2
  151. package/test/media/time_ranges_utils_unit.js +0 -5
  152. package/test/offline/indexeddb_storage_unit.js +1 -1
  153. package/test/offline/manifest_convert_unit.js +34 -4
  154. package/test/offline/offline_manifest_parser_unit.js +1 -0
  155. package/test/offline/storage_compatibility_unit.js +1 -1
  156. package/test/offline/storage_integration.js +5 -4
  157. package/test/player_external.js +7 -7
  158. package/test/player_integration.js +22 -10
  159. package/test/player_src_equals_integration.js +81 -83
  160. package/test/player_unit.js +124 -1
  161. package/test/test/boot.js +9 -0
  162. package/test/test/externs/filters.js +4 -0
  163. package/test/test/util/fake_media_source_engine.js +11 -4
  164. package/test/test/util/manifest_generator.js +3 -1
  165. package/test/test/util/manifest_parser_util.js +4 -1
  166. package/test/test/util/offline_utils.js +1 -0
  167. package/test/test/util/simple_fakes.js +31 -2
  168. package/test/test/util/streaming_engine_util.js +1 -0
  169. package/test/test/util/test_scheme.js +24 -1
  170. package/test/test/util/util.js +4 -4
  171. package/test/text/cue_integration.js +2 -2
  172. package/test/text/lrc_text_parser_unit.js +6 -7
  173. package/test/text/mp4_ttml_parser_unit.js +6 -4
  174. package/test/text/mp4_vtt_parser_unit.js +6 -5
  175. package/test/text/sbv_text_parser_unit.js +5 -6
  176. package/test/text/srt_text_parser_unit.js +5 -6
  177. package/test/text/ssa_text_parser_unit.js +17 -29
  178. package/test/text/text_engine_unit.js +45 -5
  179. package/test/text/ttml_text_parser_unit.js +77 -77
  180. package/test/text/vtt_text_parser_unit.js +116 -59
  181. package/test/ui/ui_integration.js +0 -16
  182. package/test/ui/ui_unit.js +27 -0
  183. package/test/util/cmcd_manager_unit.js +3 -0
  184. package/test/util/mime_utils_unit.js +54 -0
  185. package/test/util/periods_unit.js +79 -0
  186. package/test/util/stream_utils_unit.js +23 -7
  187. package/ui/controls.js +65 -40
  188. package/ui/fullscreen_button.js +26 -6
  189. package/ui/less/containers.less +3 -3
  190. package/ui/locales/en.json +1 -0
  191. package/ui/locales/source.json +5 -0
  192. package/ui/resolution_selection.js +31 -33
  193. package/ui/ui.js +0 -17
  194. package/docs/tutorials/gsoc-ideas-list.md +0 -3
  195. package/docs/tutorials/upgrade-v2-5.md +0 -441
  196. package/docs/tutorials/upgrade-v3.md +0 -38
  197. package/lib/polyfill/mathround.js +0 -49
  198. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  199. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  200. package/lib/util/mutex.js +0 -52
  201. package/test/util/functional_unit.js +0 -82
package/lib/player.js CHANGED
@@ -43,7 +43,6 @@ goog.require('shaka.util.Error');
43
43
  goog.require('shaka.util.EventManager');
44
44
  goog.require('shaka.util.FakeEvent');
45
45
  goog.require('shaka.util.FakeEventTarget');
46
- goog.require('shaka.util.Functional');
47
46
  goog.require('shaka.util.IDestroyable');
48
47
  goog.require('shaka.util.LanguageUtils');
49
48
  goog.require('shaka.util.ManifestParserUtils');
@@ -301,22 +300,6 @@ goog.requireType('shaka.routing.Payload');
301
300
  */
302
301
 
303
302
 
304
- /**
305
- * @event shaka.Player.LargeGapEvent
306
- * @description Fired when the playhead enters a large gap. If the
307
- * <code>streaming.jumpLargeGaps</code> configuration is set, the default
308
- * action of this event is to jump the gap; this can be prevented by calling
309
- * <code>preventDefault()</code> on the event object.
310
- * @property {string} type
311
- * 'largegap'
312
- * @property {number} currentTime
313
- * The current time of the playhead.
314
- * @property {number} gapSize
315
- * The size of the gap, in seconds.
316
- * @exportDoc
317
- */
318
-
319
-
320
303
  /**
321
304
  * @event shaka.Player.ManifestParsedEvent
322
305
  * @description Fired after the manifest has been parsed, but before anything
@@ -440,8 +423,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
440
423
  */
441
424
  this.isTextVisible_ = false;
442
425
 
443
- /** @private {shaka.util.EventManager} */
444
- this.eventManager_ = new shaka.util.EventManager();
426
+ /**
427
+ * For listeners scoped to the lifetime of the Player instance.
428
+ * @private {shaka.util.EventManager}
429
+ */
430
+ this.globalEventManager_ = new shaka.util.EventManager();
431
+
432
+ /**
433
+ * For listeners scoped to the lifetime of the media element attachment.
434
+ * @private {shaka.util.EventManager}
435
+ */
436
+ this.attachEventManager_ = new shaka.util.EventManager();
437
+
438
+ /**
439
+ * For listeners scoped to the lifetime of the loaded content.
440
+ * @private {shaka.util.EventManager}
441
+ */
442
+ this.loadEventManager_ = new shaka.util.EventManager();
445
443
 
446
444
  /** @private {shaka.net.NetworkingEngine} */
447
445
  this.networkingEngine_ = null;
@@ -576,13 +574,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
576
574
  this.adManager_ = null;
577
575
 
578
576
  if (shaka.Player.adManagerFactory_) {
579
- this.adManager_ =
580
- shaka.util.Functional.callFactory(shaka.Player.adManagerFactory_);
577
+ this.adManager_ = shaka.Player.adManagerFactory_();
581
578
  }
582
579
 
583
580
  // If the browser comes back online after being offline, then try to play
584
581
  // again.
585
- this.eventManager_.listen(window, 'online', () => {
582
+ this.globalEventManager_.listen(window, 'online', () => {
586
583
  this.retryStreaming();
587
584
  });
588
585
 
@@ -770,10 +767,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
770
767
  });
771
768
  await this.walker_.destroy();
772
769
 
773
- // Tear-down the event manager to ensure messages stop moving around.
774
- if (this.eventManager_) {
775
- this.eventManager_.release();
776
- this.eventManager_ = null;
770
+ // Tear-down the event managers to ensure handlers stop firing.
771
+ if (this.globalEventManager_) {
772
+ this.globalEventManager_.release();
773
+ this.globalEventManager_ = null;
774
+ }
775
+ if (this.attachEventManager_) {
776
+ this.attachEventManager_.release();
777
+ this.attachEventManager_ = null;
778
+ }
779
+ if (this.loadEventManager_) {
780
+ this.loadEventManager_.release();
781
+ this.loadEventManager_ = null;
777
782
  }
778
783
 
779
784
  this.abrManagerFactory_ = null;
@@ -847,10 +852,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
847
852
  return false;
848
853
  }
849
854
 
850
- // We do not support iOS 9, 10, or 11, nor those same versions of desktop
851
- // Safari.
855
+ // We do not support iOS 9, 10, 11 or 12, nor those same versions of
856
+ // desktop Safari.
852
857
  const safariVersion = shaka.util.Platform.safariVersion();
853
- if (safariVersion && safariVersion < 12) {
858
+ if (safariVersion && safariVersion < 13) {
854
859
  return false;
855
860
  }
856
861
 
@@ -1316,7 +1321,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1316
1321
  has.mediaElement = wants.mediaElement;
1317
1322
 
1318
1323
  const onError = (error) => this.onVideoError_(error);
1319
- this.eventManager_.listen(has.mediaElement, 'error', onError);
1324
+ this.attachEventManager_.listen(has.mediaElement, 'error', onError);
1320
1325
  }
1321
1326
 
1322
1327
  this.video_ = has.mediaElement;
@@ -1343,10 +1348,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1343
1348
  * @private
1344
1349
  */
1345
1350
  onDetach_(has, wants) {
1346
- // If we are going from "detached" to "detached" we wouldn't have
1351
+ // If we were going from "detached" to "detached" we wouldn't have
1347
1352
  // a media element to detach from.
1348
1353
  if (has.mediaElement) {
1349
- this.eventManager_.unlisten(has.mediaElement, 'error');
1354
+ this.attachEventManager_.removeAll();
1350
1355
  has.mediaElement = null;
1351
1356
  }
1352
1357
 
@@ -1404,11 +1409,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1404
1409
  // In most cases we should have a media element. The one exception would
1405
1410
  // be if there was an error and we, by chance, did not have a media element.
1406
1411
  if (has.mediaElement) {
1407
- this.eventManager_.unlisten(has.mediaElement, 'loadedmetadata');
1408
- this.eventManager_.unlisten(has.mediaElement, 'playing');
1409
- this.eventManager_.unlisten(has.mediaElement, 'pause');
1410
- this.eventManager_.unlisten(has.mediaElement, 'ended');
1411
- this.eventManager_.unlisten(has.mediaElement, 'ratechange');
1412
+ this.loadEventManager_.removeAll();
1412
1413
  }
1413
1414
 
1414
1415
  // Some observers use some playback components, shutting down the observers
@@ -1560,8 +1561,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1560
1561
  // text displayer and streaming engine are always in sync, wait until they
1561
1562
  // are both initialized before setting the initial value.
1562
1563
  const textDisplayerFactory = this.config_.textDisplayFactory;
1563
- const textDisplayer =
1564
- shaka.util.Functional.callFactory(textDisplayerFactory);
1564
+ const textDisplayer = textDisplayerFactory();
1565
1565
  this.lastTextFactory_ = textDisplayerFactory;
1566
1566
 
1567
1567
  const mediaSourceEngine = this.createMediaSourceEngine(
@@ -1571,6 +1571,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1571
1571
  (metadata, offset, endTime) => {
1572
1572
  this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
1573
1573
  });
1574
+ const {segmentRelativeVttTiming} = this.config_.manifest;
1575
+ mediaSourceEngine.setSegmentRelativeVttTiming(segmentRelativeVttTiming);
1574
1576
 
1575
1577
  // Wait for media source engine to finish opening. This promise should
1576
1578
  // NEVER be rejected as per the media source engine implementation.
@@ -1632,7 +1634,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1632
1634
  this.config_.manifest.retryParameters,
1633
1635
  has.mimeType);
1634
1636
  goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
1635
- this.parser_ = shaka.util.Functional.callFactory(this.parserFactory_);
1637
+ this.parser_ = this.parserFactory_();
1636
1638
 
1637
1639
  const manifestConfig =
1638
1640
  shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
@@ -1902,29 +1904,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1902
1904
  this.assetUri_ = assetUri;
1903
1905
 
1904
1906
  this.playRateController_ = new shaka.media.PlayRateController({
1905
- getRate: () => has.mediaElement.playbackRate,
1906
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
1907
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
1908
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
1907
+ getRate: () => mediaElement.playbackRate,
1908
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
1909
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
1910
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
1909
1911
  });
1910
1912
 
1911
1913
  const updateStateHistory = () => this.updateStateHistory_();
1912
1914
  const onRateChange = () => this.onRateChange_();
1913
- this.eventManager_.listen(mediaElement, 'playing', updateStateHistory);
1914
- this.eventManager_.listen(mediaElement, 'pause', updateStateHistory);
1915
- this.eventManager_.listen(mediaElement, 'ended', updateStateHistory);
1916
- this.eventManager_.listen(mediaElement, 'ratechange', onRateChange);
1915
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
1916
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
1917
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
1918
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
1917
1919
 
1918
1920
  const abrFactory = this.config_.abrFactory;
1919
1921
  if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
1920
1922
  this.abrManagerFactory_ = abrFactory;
1921
- this.abrManager_ = shaka.util.Functional.callFactory(abrFactory);
1922
- if (typeof this.abrManager_.playbackRateChanged != 'function') {
1923
- shaka.Deprecate.deprecateFeature(4,
1924
- 'AbrManager',
1925
- 'Please use an AbrManager with playbackRateChanged function.');
1926
- this.abrManager_.playbackRateChanged = (rate) => {};
1927
- }
1923
+ this.abrManager_ = abrFactory();
1928
1924
  this.abrManager_.configure(this.config_.abr);
1929
1925
  }
1930
1926
 
@@ -1976,6 +1972,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1976
1972
  // "streaming" so that they can access internal information.
1977
1973
  this.loadMode_ = shaka.Player.LoadMode.MEDIA_SOURCE;
1978
1974
 
1975
+ if (mediaElement.textTracks) {
1976
+ this.loadEventManager_.listen(
1977
+ mediaElement.textTracks, 'addtrack', (e) => {
1978
+ const trackEvent = /** @type {!TrackEvent} */(e);
1979
+ if (trackEvent.track) {
1980
+ const track = trackEvent.track;
1981
+ goog.asserts.assert(
1982
+ track instanceof TextTrack, 'Wrong track type!');
1983
+
1984
+ switch (track.kind) {
1985
+ case 'chapters':
1986
+ this.activateChaptersTrack_(track);
1987
+ break;
1988
+ }
1989
+ }
1990
+ });
1991
+ }
1992
+
1979
1993
  // The event must be fired after we filter by restrictions but before the
1980
1994
  // active stream is picked to allow those listening for the "streaming"
1981
1995
  // event to make changes before streaming starts.
@@ -2062,7 +2076,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2062
2076
  }
2063
2077
 
2064
2078
  // Wait for the 'loadedmetadata' event to measure load() latency.
2065
- this.eventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2079
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2066
2080
  const now = Date.now() / 1000;
2067
2081
  const delta = now - wants.startTimeOfLoad;
2068
2082
  this.stats_.setLoadLatency(delta);
@@ -2215,11 +2229,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2215
2229
  // Save the uri so that it can be used outside of the load-graph.
2216
2230
  this.assetUri_ = has.uri;
2217
2231
 
2218
- this.playhead_ = new shaka.media.SrcEqualsPlayhead(has.mediaElement);
2232
+ const mediaElement = has.mediaElement;
2233
+
2234
+ this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
2219
2235
 
2220
- // This flag is used below in the language preference setup and
2221
- // track-management to check if this load was canceled before the necessary
2222
- // events fired.
2236
+ // This flag is used below in the language preference setup to check if
2237
+ // this load was canceled before the necessary awaits completed.
2223
2238
  let unloaded = false;
2224
2239
  this.cleanupOnUnload_.push(() => {
2225
2240
  unloaded = true;
@@ -2230,10 +2245,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2230
2245
  }
2231
2246
 
2232
2247
  this.playRateController_ = new shaka.media.PlayRateController({
2233
- getRate: () => has.mediaElement.playbackRate,
2234
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
2235
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
2236
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
2248
+ getRate: () => mediaElement.playbackRate,
2249
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
2250
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
2251
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
2237
2252
  });
2238
2253
 
2239
2254
  // We need to start the buffer management code near the end because it will
@@ -2245,17 +2260,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2245
2260
  // Add all media element listeners.
2246
2261
  const updateStateHistory = () => this.updateStateHistory_();
2247
2262
  const onRateChange = () => this.onRateChange_();
2248
- this.eventManager_.listen(has.mediaElement, 'playing', updateStateHistory);
2249
- this.eventManager_.listen(has.mediaElement, 'pause', updateStateHistory);
2250
- this.eventManager_.listen(has.mediaElement, 'ended', updateStateHistory);
2251
- this.eventManager_.listen(has.mediaElement, 'ratechange', onRateChange);
2263
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
2264
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
2265
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
2266
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
2252
2267
 
2253
2268
  // Wait for the 'loadedmetadata' event to measure load() latency, but only
2254
2269
  // if preload is set in a way that would result in this event firing
2255
2270
  // automatically.
2256
2271
  // See https://github.com/shaka-project/shaka-player/issues/2483
2257
- if (this.video_.preload != 'none') {
2258
- this.eventManager_.listenOnce(this.video_, 'loadedmetadata', () => {
2272
+ if (mediaElement.preload != 'none') {
2273
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2259
2274
  const now = Date.now() / 1000;
2260
2275
  const delta = now - wants.startTimeOfLoad;
2261
2276
  this.stats_.setLoadLatency(delta);
@@ -2265,44 +2280,47 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2265
2280
  // The audio tracks are only available on Safari at the moment, but this
2266
2281
  // drives the tracks API for Safari's native HLS. So when they change,
2267
2282
  // fire the corresponding Shaka Player event.
2268
- if (this.video_.audioTracks) {
2269
- this.eventManager_.listen(
2270
- this.video_.audioTracks, 'addtrack', () => this.onTracksChanged_());
2271
- this.eventManager_.listen(
2272
- this.video_.audioTracks, 'removetrack',
2283
+ if (mediaElement.audioTracks) {
2284
+ this.loadEventManager_.listen(
2285
+ mediaElement.audioTracks, 'addtrack', () => this.onTracksChanged_());
2286
+ this.loadEventManager_.listen(
2287
+ mediaElement.audioTracks, 'removetrack',
2273
2288
  () => this.onTracksChanged_());
2274
- this.eventManager_.listen(
2275
- this.video_.audioTracks, 'change', () => this.onTracksChanged_());
2276
- }
2277
- if (this.video_.textTracks) {
2278
- this.eventManager_.listen(this.video_.textTracks, 'addtrack', (e) => {
2279
- // If we have moved on to another piece of content while waiting for
2280
- // the above event, we should not process tracks here.
2281
- if (unloaded) {
2282
- return;
2283
- }
2289
+ this.loadEventManager_.listen(
2290
+ mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
2291
+ }
2292
+
2293
+ if (mediaElement.textTracks) {
2294
+ this.loadEventManager_.listen(
2295
+ mediaElement.textTracks, 'addtrack', (e) => {
2296
+ const trackEvent = /** @type {!TrackEvent} */(e);
2297
+ if (trackEvent.track) {
2298
+ const track = trackEvent.track;
2299
+ goog.asserts.assert(
2300
+ track instanceof TextTrack, 'Wrong track type!');
2301
+
2302
+ switch (track.kind) {
2303
+ case 'metadata':
2304
+ this.processTimedMetadataSrcEqls_(track);
2305
+ break;
2306
+
2307
+ case 'chapters':
2308
+ this.activateChaptersTrack_(track);
2309
+ break;
2310
+
2311
+ default:
2312
+ this.onTracksChanged_();
2313
+ break;
2314
+ }
2315
+ }
2316
+ });
2284
2317
 
2285
- const trackEvent = /** @type {!TrackEvent} */(e);
2286
- if (trackEvent.track) {
2287
- const track = trackEvent.track;
2288
- goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2289
- switch (track.kind) {
2290
- case 'metadata':
2291
- this.processTimedMetadataSrcEqls_(track);
2292
- break;
2293
- case 'chapters':
2294
- this.processChaptersTrack_(track);
2295
- break;
2296
- default:
2297
- this.onTracksChanged_();
2298
- break;
2299
- }
2300
- }
2301
- });
2302
- this.eventManager_.listen(
2303
- this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
2304
- this.eventManager_.listen(
2305
- this.video_.textTracks, 'change', () => this.onTracksChanged_());
2318
+ this.loadEventManager_.listen(
2319
+ mediaElement.textTracks, 'removetrack',
2320
+ () => this.onTracksChanged_());
2321
+ this.loadEventManager_.listen(
2322
+ mediaElement.textTracks, 'change',
2323
+ () => this.onTracksChanged_());
2306
2324
  }
2307
2325
 
2308
2326
  const extension = shaka.media.ManifestParser.getExtension(has.uri);
@@ -2311,14 +2329,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2311
2329
 
2312
2330
  // By setting |src| we are done "loading" with src=. We don't need to set
2313
2331
  // the current time because |playhead| will do that for us.
2314
- has.mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2332
+ mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2315
2333
 
2316
2334
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2317
2335
  // no matter the value of the preload attribute. This is harmful on some
2318
2336
  // other platforms by triggering unbounded loading of media data, but is
2319
2337
  // necessary here.
2320
2338
  if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
2321
- has.mediaElement.load();
2339
+ mediaElement.load();
2322
2340
  }
2323
2341
 
2324
2342
  // Set the load mode last so that we know that all our components are
@@ -2334,24 +2352,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2334
2352
  // wait for the full data, that won't happen on Safari until the play button
2335
2353
  // is hit.
2336
2354
  const fullyLoaded = new shaka.util.PublicPromise();
2337
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2355
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2338
2356
  HTMLMediaElement.HAVE_METADATA,
2339
- this.eventManager_,
2357
+ this.loadEventManager_,
2340
2358
  () => {
2341
2359
  fullyLoaded.resolve();
2342
2360
  });
2343
2361
 
2344
2362
  // We can't switch to preferred languages, though, until the data is loaded.
2345
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2363
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2346
2364
  HTMLMediaElement.HAVE_CURRENT_DATA,
2347
- this.eventManager_,
2365
+ this.loadEventManager_,
2348
2366
  async () => {
2349
- // If we have moved on to another piece of content while waiting for
2350
- // the above event, we should not change tracks here.
2351
- if (unloaded) {
2352
- return;
2353
- }
2354
-
2355
2367
  this.setupPreferredAudioOnSrc_();
2356
2368
 
2357
2369
  // Applying the text preference too soon can result in it being
@@ -2359,8 +2371,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2359
2371
  const textTracks = this.getFilteredTextTracks_();
2360
2372
  if (!textTracks.find((t) => t.mode != 'disabled')) {
2361
2373
  await new Promise((resolve) => {
2362
- this.eventManager_.listenOnce(
2363
- this.video_.textTracks, 'change', resolve);
2374
+ this.loadEventManager_.listenOnce(
2375
+ mediaElement.textTracks, 'change', resolve);
2376
+
2364
2377
  // We expect the event to fire because it does on Safari.
2365
2378
  // But in case it doesn't on some other platform or future
2366
2379
  // version, move on in 1 second no matter what. This keeps the
@@ -2379,10 +2392,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2379
2392
  this.setupPreferredTextOnSrc_();
2380
2393
  });
2381
2394
 
2382
- if (this.video_.error) {
2395
+ if (mediaElement.error) {
2383
2396
  // Already failed!
2384
2397
  fullyLoaded.reject(this.videoErrorToShakaError_());
2385
- } else if (this.video_.preload == 'none') {
2398
+ } else if (mediaElement.preload == 'none') {
2386
2399
  shaka.log.alwaysWarn(
2387
2400
  'With <video preload="none">, the browser will not load anything ' +
2388
2401
  'until play() is called. We are unable to measure load latency in ' +
@@ -2393,7 +2406,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2393
2406
  fullyLoaded.resolve();
2394
2407
  }
2395
2408
 
2396
- this.eventManager_.listenOnce(this.video_, 'error', () => {
2409
+ this.loadEventManager_.listenOnce(mediaElement, 'error', () => {
2397
2410
  fullyLoaded.reject(this.videoErrorToShakaError_());
2398
2411
  });
2399
2412
 
@@ -2477,7 +2490,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2477
2490
 
2478
2491
  // Hidden mode is required for the cuechange event to launch correctly
2479
2492
  track.mode = 'hidden';
2480
- this.eventManager_.listen(track, 'cuechange', () => {
2493
+ this.loadEventManager_.listen(track, 'cuechange', () => {
2481
2494
  if (!track.activeCues) {
2482
2495
  return;
2483
2496
  }
@@ -2555,12 +2568,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2555
2568
  }
2556
2569
 
2557
2570
  /**
2558
- * We're looking for chapters tracks to process the chapters.
2571
+ * Set the mode on a chapters track so that it loads.
2559
2572
  *
2560
2573
  * @param {?TextTrack} track
2561
2574
  * @private
2562
2575
  */
2563
- processChaptersTrack_(track) {
2576
+ activateChaptersTrack_(track) {
2564
2577
  if (!track || track.kind != 'chapters') {
2565
2578
  return;
2566
2579
  }
@@ -2573,10 +2586,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2573
2586
  // this process to be repeated several times to ensure that it has been put
2574
2587
  // in the correct mode.
2575
2588
  const timer = new shaka.util.Timer(() => {
2576
- const chaptersTracks = this.getChaptersTracks_();
2577
- for (const chaptersTrack of chaptersTracks) {
2578
- chaptersTrack.mode = 'hidden';
2579
- }
2589
+ track.mode = 'hidden';
2580
2590
  }).tickNow().tickAfter(0.5);
2581
2591
 
2582
2592
  this.cleanupOnUnload_.push(() => {
@@ -2683,8 +2693,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2683
2693
  this.manifest_,
2684
2694
  this.config_.streaming,
2685
2695
  startTime,
2686
- () => this.onSeek_(),
2687
- (event) => this.dispatchEvent(event));
2696
+ () => this.onSeek_());
2688
2697
  }
2689
2698
 
2690
2699
  /**
@@ -2943,17 +2952,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2943
2952
 
2944
2953
  goog.asserts.assert(typeof(config) == 'object', 'Should be an object!');
2945
2954
 
2946
- // Deprecate 'manifest.dash.defaultPresentationDelay' configuration.
2947
- if (config['manifest'] && config['manifest']['dash'] &&
2948
- 'defaultPresentationDelay' in config['manifest']['dash']) {
2949
- shaka.Deprecate.deprecateFeature(4,
2950
- 'manifest.dash.defaultPresentationDelay configuration',
2951
- 'Please Use manifest.defaultPresentationDelay instead.');
2952
- config['manifest']['defaultPresentationDelay'] =
2953
- config['manifest']['dash']['defaultPresentationDelay'];
2954
- delete config['manifest']['dash']['defaultPresentationDelay'];
2955
- }
2956
-
2957
2955
  // If lowLatencyMode is enabled, and inaccurateManifestTolerance and
2958
2956
  // rebufferingGoal are not specified, set inaccurateManifestTolerance to 0
2959
2957
  // and rebufferingGoal to 0.01 by default for low latency streaming.
@@ -3020,10 +3018,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3020
3018
  }
3021
3019
 
3022
3020
  if (this.mediaSourceEngine_) {
3021
+ const {segmentRelativeVttTiming} = this.config_.manifest;
3022
+ this.mediaSourceEngine_.setSegmentRelativeVttTiming(
3023
+ segmentRelativeVttTiming);
3024
+
3023
3025
  const textDisplayerFactory = this.config_.textDisplayFactory;
3024
3026
  if (this.lastTextFactory_ != textDisplayerFactory) {
3025
- const displayer =
3026
- shaka.util.Functional.callFactory(textDisplayerFactory);
3027
+ const displayer = textDisplayerFactory();
3027
3028
  this.mediaSourceEngine_.setTextDisplayer(displayer);
3028
3029
  this.lastTextFactory_ = textDisplayerFactory;
3029
3030
 
@@ -4408,158 +4409,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4408
4409
  return this.stats_.getBlob();
4409
4410
  }
4410
4411
 
4411
- /**
4412
- * Adds the given text track to the loaded manifest. <code>load()</code> must
4413
- * resolve before calling. The presentation must have a duration.
4414
- *
4415
- * This returns the created track, which can immediately be selected by the
4416
- * application. The track will not be automatically selected.
4417
- *
4418
- * @param {string} uri
4419
- * @param {string} language
4420
- * @param {string} kind
4421
- * @param {string=} mimeType
4422
- * @param {string=} codec
4423
- * @param {string=} label
4424
- * @param {boolean=} forced
4425
- * @return {shaka.extern.Track}
4426
- * @export
4427
- */
4428
- addTextTrack(uri, language, kind, mimeType, codec, label, forced = false) {
4429
- shaka.Deprecate.deprecateFeature(4,
4430
- 'addTextTrack',
4431
- 'Please use an addTextTrackAsync.');
4432
- if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE &&
4433
- this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) {
4434
- shaka.log.error(
4435
- 'Must call load() and wait for it to resolve before adding text ' +
4436
- 'tracks.');
4437
- throw new shaka.util.Error(
4438
- shaka.util.Error.Severity.RECOVERABLE,
4439
- shaka.util.Error.Category.PLAYER,
4440
- shaka.util.Error.Code.CONTENT_NOT_LOADED);
4441
- }
4442
-
4443
- if (!mimeType) {
4444
- // Try using the uri extension.
4445
- const extension = shaka.media.ManifestParser.getExtension(uri);
4446
- mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
4447
-
4448
- if (!mimeType) {
4449
- shaka.log.error(
4450
- 'The mimeType has not been provided and it could not be deduced ' +
4451
- 'from its extension.');
4452
- throw new shaka.util.Error(
4453
- shaka.util.Error.Severity.RECOVERABLE,
4454
- shaka.util.Error.Category.TEXT,
4455
- shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
4456
- extension);
4457
- }
4458
- }
4459
-
4460
- if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4461
- if (mimeType != 'text/vtt') {
4462
- shaka.log.error('Only WebVTT is supported when using src=');
4463
- throw new shaka.util.Error(
4464
- shaka.util.Error.Severity.RECOVERABLE,
4465
- shaka.util.Error.Category.TEXT,
4466
- shaka.util.Error.Code.TEXT_ONLY_WEBVTT_SRC_EQUALS,
4467
- mimeType);
4468
- }
4469
- if (forced) {
4470
- // See: https://github.com/whatwg/html/issues/4472
4471
- kind = 'forced';
4472
- }
4473
- const trackElement =
4474
- /** @type {!HTMLTrackElement} */(document.createElement('track'));
4475
- trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4476
- trackElement.label = label || '';
4477
- trackElement.kind = kind;
4478
- trackElement.srclang = language;
4479
- // Because we're pulling in the text track file via Javascript, the
4480
- // same-origin policy applies. If you'd like to have a player served
4481
- // from one domain, but the text track served from another, you'll
4482
- // need to enable CORS in order to do so. In addition to enabling CORS
4483
- // on the server serving the text tracks, you will need to add the
4484
- // crossorigin attribute to the video element itself.
4485
- if (!this.video_.getAttribute('crossorigin')) {
4486
- this.video_.setAttribute('crossorigin', 'anonymous');
4487
- }
4488
- this.video_.appendChild(trackElement);
4489
- const textTracks = this.getTextTracks();
4490
- const srcTrack = textTracks.find((t) => {
4491
- return t.language == language &&
4492
- t.label == (label || '') &&
4493
- t.kind == kind;
4494
- });
4495
- if (srcTrack) {
4496
- this.onTracksChanged_();
4497
- return srcTrack;
4498
- }
4499
- // This should not happen, but there are browser implementations that may
4500
- // not support the Track element.
4501
- shaka.log.error('Cannot add this text when loaded with src=');
4502
- throw new shaka.util.Error(
4503
- shaka.util.Error.Severity.RECOVERABLE,
4504
- shaka.util.Error.Category.TEXT,
4505
- shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_SRC_EQUALS);
4506
- }
4507
-
4508
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
4509
-
4510
- const duration = this.manifest_.presentationTimeline.getDuration();
4511
- if (duration == Infinity) {
4512
- throw new shaka.util.Error(
4513
- shaka.util.Error.Severity.RECOVERABLE,
4514
- shaka.util.Error.Category.MANIFEST,
4515
- shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM);
4516
- }
4517
-
4518
- /** @type {shaka.extern.Stream} */
4519
- const stream = {
4520
- id: this.nextExternalStreamId_++,
4521
- originalId: null,
4522
- createSegmentIndex: () => Promise.resolve(),
4523
- segmentIndex: shaka.media.SegmentIndex.forSingleSegment(
4524
- /* startTime= */ 0,
4525
- /* duration= */ duration,
4526
- /* uris= */ [uri]),
4527
- mimeType: mimeType || '',
4528
- codecs: codec || '',
4529
- kind: kind,
4530
- encrypted: false,
4531
- drmInfos: [],
4532
- keyIds: new Set(),
4533
- language: language,
4534
- label: label || null,
4535
- type: ContentType.TEXT,
4536
- primary: false,
4537
- trickModeVideo: null,
4538
- emsgSchemeIdUris: null,
4539
- roles: [],
4540
- forced: !!forced,
4541
- channelsCount: null,
4542
- audioSamplingRate: null,
4543
- spatialAudio: false,
4544
- closedCaptions: null,
4545
- };
4546
-
4547
- const fullMimeType = shaka.util.MimeUtils.getFullType(
4548
- stream.mimeType, stream.codecs);
4549
- const supported = shaka.text.TextEngine.isTypeSupported(fullMimeType);
4550
- if (!supported) {
4551
- throw new shaka.util.Error(
4552
- shaka.util.Error.Severity.CRITICAL,
4553
- shaka.util.Error.Category.TEXT,
4554
- shaka.util.Error.Code.MISSING_TEXT_PLUGIN,
4555
- mimeType);
4556
- }
4557
-
4558
- this.manifest_.textStreams.push(stream);
4559
- this.onTracksChanged_();
4560
- return shaka.util.StreamUtils.textStreamToTrack(stream);
4561
- }
4562
-
4563
4412
  /**
4564
4413
  * Adds the given text track to the loaded manifest. <code>load()</code> must
4565
4414
  * resolve before calling. The presentation must have a duration.
@@ -4721,25 +4570,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4721
4570
  if (!mimeType) {
4722
4571
  mimeType = await this.getTextMimetype_(uri);
4723
4572
  }
4573
+
4724
4574
  let adCuePoints = [];
4725
4575
  if (this.adManager_) {
4726
4576
  try {
4727
4577
  adCuePoints = this.adManager_.getServerSideCuePoints();
4728
4578
  } catch (error) {}
4729
4579
  }
4730
- await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4731
- mimeType, /* label= */ '', adCuePoints);
4580
+
4581
+ /** @type {!HTMLTrackElement} */
4582
+ const trackElement = await this.addSrcTrackElement_(
4583
+ uri, language, /* kind= */ 'chapters', mimeType, /* label= */ '',
4584
+ adCuePoints);
4585
+
4732
4586
  const chaptersTracks = this.getChaptersTracks();
4733
4587
  const chaptersTrack = chaptersTracks.find((t) => {
4734
4588
  return t.language == language;
4735
4589
  });
4590
+
4736
4591
  if (chaptersTrack) {
4737
- const html5ChaptersTracks = this.getChaptersTracks_();
4738
- for (const html5ChaptersTrack of html5ChaptersTracks) {
4739
- this.processChaptersTrack_(html5ChaptersTrack);
4740
- }
4592
+ await new Promise((resolve, reject) => {
4593
+ // The chapter data isn't available until the 'load' event fires, and
4594
+ // that won't happen until the chapters track is activated by the
4595
+ // activateChaptersTrack_ method.
4596
+ this.loadEventManager_.listenOnce(trackElement, 'load', resolve);
4597
+ this.loadEventManager_.listenOnce(trackElement, 'error', (event) => {
4598
+ reject(new shaka.util.Error(
4599
+ shaka.util.Error.Severity.RECOVERABLE,
4600
+ shaka.util.Error.Category.TEXT,
4601
+ shaka.util.Error.Code.CHAPTERS_TRACK_FAILED));
4602
+ });
4603
+ });
4604
+
4741
4605
  return chaptersTrack;
4742
4606
  }
4607
+
4743
4608
  // This should not happen, but there are browser implementations that may
4744
4609
  // not support the Track element.
4745
4610
  shaka.log.error('Cannot add this text when loaded with src=');
@@ -4793,6 +4658,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4793
4658
  * @param {string} mimeType
4794
4659
  * @param {string} label
4795
4660
  * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4661
+ * @return {!Promise.<!HTMLTrackElement>}
4796
4662
  * @private
4797
4663
  */
4798
4664
  async addSrcTrackElement_(uri, language, kind, mimeType, label,
@@ -4808,12 +4674,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4808
4674
  uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4809
4675
  mimeType = 'text/vtt';
4810
4676
  }
4677
+
4811
4678
  const trackElement =
4812
4679
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4813
4680
  trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4814
4681
  trackElement.label = label;
4815
4682
  trackElement.kind = kind;
4816
4683
  trackElement.srclang = language;
4684
+
4817
4685
  // Because we're pulling in the text track file via Javascript, the
4818
4686
  // same-origin policy applies. If you'd like to have a player served
4819
4687
  // from one domain, but the text track served from another, you'll
@@ -4823,7 +4691,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4823
4691
  if (!this.video_.getAttribute('crossorigin')) {
4824
4692
  this.video_.setAttribute('crossorigin', 'anonymous');
4825
4693
  }
4694
+
4826
4695
  this.video_.appendChild(trackElement);
4696
+ return trackElement;
4827
4697
  }
4828
4698
 
4829
4699
  /**
@@ -4864,6 +4734,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4864
4734
  periodStart: 0,
4865
4735
  segmentStart: 0,
4866
4736
  segmentEnd: this.video_.duration,
4737
+ vttOffset: 0,
4867
4738
  };
4868
4739
  const data = shaka.util.BufferUtils.toUint8(buffer);
4869
4740
  const cues = obj.parseMedia(data, time);
@@ -5193,7 +5064,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5193
5064
  }
5194
5065
 
5195
5066
  await stream.createSegmentIndex();
5196
- const ref = stream.segmentIndex[Symbol.iterator]().seek(time);
5067
+ const iter = stream.segmentIndex.getIteratorForTime(time);
5068
+ const ref = iter ? iter.next().value : null;
5197
5069
  if (!ref) {
5198
5070
  return null;
5199
5071
  }
@@ -5862,7 +5734,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5862
5734
  } // if (keyIds.size)
5863
5735
 
5864
5736
  if (tracksChanged) {
5865
- this.updateAbrManagerVariants_();
5737
+ const variantsUpdated = this.updateAbrManagerVariants_();
5738
+ if (!variantsUpdated) {
5739
+ return;
5740
+ }
5866
5741
  }
5867
5742
 
5868
5743
  const currentVariant = this.streamingEngine_.getCurrentVariant();
@@ -6516,7 +6391,6 @@ shaka.Player.EventName = {
6516
6391
  Emsg: 'emsg',
6517
6392
  Error: 'error',
6518
6393
  ExpirationUpdated: 'expirationupdated',
6519
- LargeGap: 'largegap',
6520
6394
  Loaded: 'loaded',
6521
6395
  Loading: 'loading',
6522
6396
  ManifestParsed: 'manifestparsed',
@@ -6576,7 +6450,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6576
6450
  * @define {string} A version number taken from git at compile time.
6577
6451
  * @export
6578
6452
  */
6579
- shaka.Player.version = 'v3.3.4-uncompiled';
6453
+ shaka.Player.version = 'v4.0.1-uncompiled';
6580
6454
 
6581
6455
  // Initialize the deprecation system using the version string we just set
6582
6456
  // on the player.