shaka-player 4.2.3 → 4.3.0

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 (147) hide show
  1. package/.github/workflows/sync-labels.yaml +1 -1
  2. package/.github/workflows/update-issues.yaml +1 -1
  3. package/AUTHORS +5 -0
  4. package/CHANGELOG.md +85 -39
  5. package/CONTRIBUTORS +5 -0
  6. package/README.md +14 -0
  7. package/demo/common/assets.js +58 -0
  8. package/demo/common/message_ids.js +22 -1
  9. package/demo/config.js +89 -5
  10. package/demo/demo.less +7 -1
  11. package/demo/index.html +8 -1
  12. package/demo/locales/en.json +21 -1
  13. package/demo/locales/source.json +82 -2
  14. package/demo/main.js +57 -1
  15. package/demo/search.js +2 -0
  16. package/demo/visualizer.js +337 -0
  17. package/demo/visualizer_button.js +103 -0
  18. package/dist/controls.css +1 -1
  19. package/dist/controls.css.map +1 -1
  20. package/dist/demo.compiled.debug.js +83 -58
  21. package/dist/demo.compiled.debug.map +4 -4
  22. package/dist/demo.compiled.js +83 -58
  23. package/dist/demo.compiled.map +4 -4
  24. package/dist/demo.css +1 -1
  25. package/dist/demo.css.map +1 -1
  26. package/dist/deps.js +18 -11
  27. package/dist/locales.js +32 -32
  28. package/dist/receiver.compiled.debug.js +23 -19
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +23 -19
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +178 -27
  33. package/dist/shaka-player.compiled.debug.d.ts +178 -27
  34. package/dist/shaka-player.compiled.debug.externs.js +163 -32
  35. package/dist/shaka-player.compiled.debug.js +488 -422
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +163 -32
  38. package/dist/shaka-player.compiled.js +895 -857
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +178 -27
  41. package/dist/shaka-player.ui.debug.d.ts +178 -27
  42. package/dist/shaka-player.ui.debug.externs.js +163 -32
  43. package/dist/shaka-player.ui.debug.js +519 -450
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +163 -32
  46. package/dist/shaka-player.ui.js +1053 -1015
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/lcevc-architecture.png +0 -0
  49. package/docs/design/lcevc-demo.png +0 -0
  50. package/docs/design/lcevc-integration.md +72 -0
  51. package/docs/tutorials/fairplay.md +7 -0
  52. package/docs/tutorials/faq.md +1 -1
  53. package/docs/tutorials/ui.md +7 -7
  54. package/docs/tutorials/upgrade.md +6 -0
  55. package/externs/ima.js +13 -1
  56. package/externs/lcevc.js +81 -0
  57. package/externs/mux.js +4 -42
  58. package/externs/shaka/ads.js +11 -1
  59. package/externs/shaka/manifest_parser.js +5 -1
  60. package/externs/shaka/player.js +178 -15
  61. package/lib/abr/simple_abr_manager.js +7 -0
  62. package/lib/ads/ad_manager.js +0 -8
  63. package/lib/ads/client_side_ad.js +15 -0
  64. package/lib/ads/client_side_ad_manager.js +21 -8
  65. package/lib/ads/server_side_ad.js +15 -0
  66. package/lib/ads/server_side_ad_manager.js +3 -3
  67. package/lib/cast/cast_proxy.js +2 -1
  68. package/lib/cast/cast_receiver.js +19 -5
  69. package/lib/cast/cast_sender.js +2 -1
  70. package/lib/cast/cast_utils.js +18 -6
  71. package/lib/cea/dummy_cea_parser.js +28 -0
  72. package/lib/cea/i_cea_parser.js +14 -2
  73. package/lib/cea/mp4_cea_parser.js +87 -5
  74. package/lib/config/auto_show_text.js +33 -0
  75. package/lib/dash/dash_parser.js +1 -1
  76. package/lib/hls/hls_parser.js +799 -376
  77. package/lib/lcevc/lcevc_dil.js +152 -0
  78. package/lib/media/closed_caption_parser.js +9 -6
  79. package/lib/media/drm_engine.js +14 -6
  80. package/lib/media/media_source_engine.js +112 -26
  81. package/lib/media/playhead.js +23 -1
  82. package/lib/media/presentation_timeline.js +26 -11
  83. package/lib/media/segment_reference.js +49 -1
  84. package/lib/media/streaming_engine.js +145 -24
  85. package/lib/media/transmuxer.js +79 -18
  86. package/lib/offline/storage.js +1 -0
  87. package/lib/player.js +477 -85
  88. package/lib/polyfill/patchedmediakeys_apple.js +55 -1
  89. package/lib/text/ui_text_displayer.js +5 -5
  90. package/lib/text/vtt_text_parser.js +35 -1
  91. package/lib/util/dom_utils.js +0 -1
  92. package/lib/util/error.js +17 -0
  93. package/lib/util/fake_event.js +2 -0
  94. package/lib/util/id3_utils.js +300 -0
  95. package/lib/util/manifest_parser_utils.js +4 -0
  96. package/lib/util/mime_utils.js +3 -6
  97. package/lib/util/mp4_box_parsers.js +15 -5
  98. package/lib/util/platform.js +9 -1
  99. package/lib/util/player_configuration.js +23 -1
  100. package/lib/util/ts_parser.js +499 -0
  101. package/maintained-branches.md +2 -2
  102. package/package.json +4 -1
  103. package/roadmap.md +27 -7
  104. package/test/ads/ad_manager_unit.js +180 -29
  105. package/test/cast/cast_receiver_integration.js +97 -16
  106. package/test/cast/cast_receiver_unit.js +17 -12
  107. package/test/cast/cast_utils_unit.js +4 -1
  108. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  109. package/test/dash/dash_parser_live_unit.js +1 -0
  110. package/test/dash/dash_parser_manifest_unit.js +93 -1
  111. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  112. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  113. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  114. package/test/demo/demo_unit.js +2 -1
  115. package/test/hls/hls_live_unit.js +253 -19
  116. package/test/hls/hls_parser_unit.js +500 -12
  117. package/test/media/closed_caption_parser_unit.js +2 -1
  118. package/test/media/drm_engine_integration.js +3 -1
  119. package/test/media/media_source_engine_integration.js +102 -6
  120. package/test/media/media_source_engine_unit.js +10 -5
  121. package/test/media/playhead_unit.js +41 -0
  122. package/test/media/streaming_engine_integration.js +17 -1
  123. package/test/media/streaming_engine_unit.js +180 -24
  124. package/test/media/transmuxer_integration.js +21 -10
  125. package/test/player_integration.js +224 -82
  126. package/test/player_unit.js +92 -2
  127. package/test/polyfill/patchedmediakeys_apple_unit.js +124 -0
  128. package/test/test/assets/id3-metadata.aac +0 -0
  129. package/test/test/assets/id3-metadata.ts +0 -0
  130. package/test/test/assets/thumbnails-sprites.vtt +10 -0
  131. package/test/test/assets/thumbnails.vtt +10 -0
  132. package/test/test/util/dash_parser_util.js +2 -0
  133. package/test/test/util/fake_ad.js +21 -0
  134. package/test/test/util/fake_media_source_engine.js +7 -0
  135. package/test/test/util/id3_generator.js +100 -0
  136. package/test/test/util/manifest_generator.js +3 -6
  137. package/test/test/util/simple_fakes.js +7 -0
  138. package/test/test/util/stream_generator.js +38 -0
  139. package/test/test/util/test_scheme.js +43 -14
  140. package/test/text/vtt_text_parser_unit.js +11 -0
  141. package/test/ui/text_displayer_layout_unit.js +14 -0
  142. package/test/util/id3_utils_unit.js +116 -0
  143. package/test/util/ts_parser_unit.js +77 -0
  144. package/ui/controls.js +4 -1
  145. package/ui/less/containers.less +7 -0
  146. package/ui/settings_menu.js +11 -2
  147. package/ui/ui.js +43 -4
package/lib/player.js CHANGED
@@ -8,6 +8,7 @@ goog.provide('shaka.Player');
8
8
 
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.Deprecate');
11
+ goog.require('shaka.config.AutoShowText');
11
12
  goog.require('shaka.log');
12
13
  goog.require('shaka.media.AdaptationSetCriteria');
13
14
  goog.require('shaka.media.BufferingObserver');
@@ -17,7 +18,6 @@ goog.require('shaka.media.ManifestParser');
17
18
  goog.require('shaka.media.MediaSourceEngine');
18
19
  goog.require('shaka.media.MediaSourcePlayhead');
19
20
  goog.require('shaka.media.MetaSegmentIndex');
20
- goog.require('shaka.media.ClosedCaptionParser');
21
21
  goog.require('shaka.media.PlayRateController');
22
22
  goog.require('shaka.media.Playhead');
23
23
  goog.require('shaka.media.PlayheadObserverManager');
@@ -26,6 +26,7 @@ goog.require('shaka.media.QualityObserver');
26
26
  goog.require('shaka.media.RegionObserver');
27
27
  goog.require('shaka.media.RegionTimeline');
28
28
  goog.require('shaka.media.SegmentIndex');
29
+ goog.require('shaka.media.SegmentReference');
29
30
  goog.require('shaka.media.SrcEqualsPlayhead');
30
31
  goog.require('shaka.media.StreamingEngine');
31
32
  goog.require('shaka.media.TimeRangesUtils');
@@ -55,7 +56,7 @@ goog.require('shaka.util.PublicPromise');
55
56
  goog.require('shaka.util.Stats');
56
57
  goog.require('shaka.util.StreamUtils');
57
58
  goog.require('shaka.util.Timer');
58
- goog.requireType('shaka.media.IClosedCaptionParser');
59
+ goog.require('shaka.lcevc.Dil');
59
60
  goog.requireType('shaka.media.PresentationTimeline');
60
61
  goog.requireType('shaka.routing.Node');
61
62
  goog.requireType('shaka.routing.Payload');
@@ -325,7 +326,7 @@ goog.requireType('shaka.routing.Payload');
325
326
  * the cue applies.
326
327
  * @property {string} metadataType
327
328
  * Type of metadata. Eg: org.id3 or org.mp4ra
328
- * @property {shaka.extern.ID3Metadata} payload
329
+ * @property {shaka.extern.MetadataFrame} payload
329
330
  * The metadata itself
330
331
  * @exportDoc
331
332
  */
@@ -367,6 +368,21 @@ goog.requireType('shaka.routing.Payload');
367
368
  */
368
369
 
369
370
 
371
+ /**
372
+ * @event shaka.Player.SegmentAppended
373
+ * @description Fired when a segment is appended to the media element.
374
+ * @property {string} type
375
+ * 'segmentappended'
376
+ * @property {number} start
377
+ * The start time of the segment.
378
+ * @property {number} end
379
+ * The end time of the segment.
380
+ * @property {string} contentType
381
+ * The content type of the segment. E.g. 'video', 'audio', or 'text'.
382
+ * @exportDoc
383
+ */
384
+
385
+
370
386
  /**
371
387
  * @event shaka.Player.SessionDataEvent
372
388
  * @description Fired when the manifest parser find info about session data.
@@ -505,6 +521,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
505
521
  /** @private {shaka.util.CmcdManager} */
506
522
  this.cmcdManager_ = null;
507
523
 
524
+ // This is the canvas element that will be used for rendering LCEVC
525
+ // enhanced frames.
526
+ /** @private {?HTMLCanvasElement} */
527
+ this.lcevcCanvas_ = null;
528
+
529
+ // This is the LCEVC Dil object to decode LCEVC.
530
+ /** @private {?shaka.lcevc.Dil} */
531
+ this.lcevcDil_ = null;
532
+
508
533
  /** @private {shaka.media.QualityObserver} */
509
534
  this.qualityObserver_ = null;
510
535
 
@@ -523,6 +548,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
523
548
  /** @private {?string} */
524
549
  this.assetUri_ = null;
525
550
 
551
+ /** @private {boolean} */
552
+ this.fullyLoaded_ = false;
553
+
526
554
  /** @private {shaka.extern.AbrManager} */
527
555
  this.abrManager_ = null;
528
556
 
@@ -726,6 +754,66 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
726
754
  }
727
755
  }
728
756
 
757
+ /**
758
+ * Create a shaka.lcevc.Dil object
759
+ * @param {shaka.extern.LcevcConfiguration} config
760
+ * @private
761
+ */
762
+ createLcevcDil_(config) {
763
+ if (this.lcevcDil_ == null) {
764
+ this.lcevcDil_ = new shaka.lcevc.Dil(
765
+ /** @type {HTMLVideoElement} */ (this.video_),
766
+ this.lcevcCanvas_,
767
+ config,
768
+ );
769
+ if (this.mediaSourceEngine_) {
770
+ this.mediaSourceEngine_.updateLcevcDil(this.lcevcDil_);
771
+ }
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Close a shaka.lcevc.Dil object if present and hide the canvas.
777
+ * @private
778
+ */
779
+ closeLcevcDil_() {
780
+ if (this.lcevcDil_ != null) {
781
+ this.lcevcDil_.release();
782
+ this.lcevcDil_ = null;
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Setup shaka.lcevc.Dil object
788
+ * @param {?shaka.extern.PlayerConfiguration} config
789
+ * @private
790
+ */
791
+ setupLcevc_(config) {
792
+ if (config.lcevc.enabled) {
793
+ const tracks = this.getVariantTracks();
794
+ if (tracks && tracks[0] &&
795
+ tracks[0].videoMimeType ==
796
+ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_['ts']) {
797
+ const edge = shaka.util.Platform.isEdge() ||
798
+ shaka.util.Platform.isLegacyEdge();
799
+ if (edge) {
800
+ if (!config.streaming.forceTransmux) {
801
+ // If forceTransmux is disabled for Microsoft Edge, LCEVC data
802
+ // is stripped out in case of a MPEG-2 TS container.
803
+ // Hence the warning for Microsoft Edge when playing content with
804
+ // MPEG-2 TS container.
805
+ shaka.log.alwaysWarn('LCEVC Warning: For MPEG-2 TS decoding '+
806
+ 'the config.streaming.forceTransmux must be enabled.');
807
+ }
808
+ }
809
+ }
810
+ this.closeLcevcDil_();
811
+ this.createLcevcDil_(config.lcevc);
812
+ } else {
813
+ this.closeLcevcDil_();
814
+ }
815
+ }
816
+
729
817
  /**
730
818
  * @param {!shaka.util.FakeEvent.EventName} name
731
819
  * @param {Map.<string, Object>=} data
@@ -748,6 +836,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
748
836
  return;
749
837
  }
750
838
 
839
+ // If LCEVC Dil exists close it.
840
+ this.closeLcevcDil_();
841
+
751
842
  // Mark as "dead". This should stop external-facing calls from changing our
752
843
  // internal state any more. This will stop calls to |attach|, |detach|, etc.
753
844
  // from interrupting our final move to the detached state.
@@ -985,6 +1076,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
985
1076
  return this.wrapWalkerListenersWithPromise_(events);
986
1077
  }
987
1078
 
1079
+
1080
+ /**
1081
+ * Calling <code>attachCanvas</code> will tell the player to set canvas
1082
+ * element for LCEVC decoding.
1083
+ *
1084
+ * @param {HTMLCanvasElement} canvas
1085
+ * @export
1086
+ */
1087
+ attachCanvas(canvas) {
1088
+ this.lcevcCanvas_ = canvas;
1089
+ }
1090
+
988
1091
  /**
989
1092
  * Tell the player to stop using its current media element. If the player is:
990
1093
  * <ul>
@@ -1050,12 +1153,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1050
1153
  return Promise.reject(this.createAbortLoadError_());
1051
1154
  }
1052
1155
 
1156
+ this.fullyLoaded_ = false;
1157
+
1053
1158
  // If the platform does not support media source, we will never want to
1054
1159
  // initialize media source.
1055
1160
  if (!shaka.util.Platform.supportsMediaSource()) {
1056
1161
  initializeMediaSource = false;
1057
1162
  }
1058
1163
 
1164
+ // If LCEVC Dil exists close it.
1165
+ this.closeLcevcDil_();
1166
+
1059
1167
  // Since we are going either to attached or detached (through unloaded), we
1060
1168
  // can't allow it to be interrupted or else we could lose track of what
1061
1169
  // media element we are suppose to use.
@@ -1141,6 +1249,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1141
1249
  load(assetUri, startTime, mimeType) {
1142
1250
  this.updatedStartTime_ = null;
1143
1251
 
1252
+ this.fullyLoaded_ = false;
1253
+
1144
1254
  // Do not allow the player to be used after |destroy| is called.
1145
1255
  if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
1146
1256
  return Promise.reject(this.createAbortLoadError_());
@@ -1578,8 +1688,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1578
1688
  this.mediaSourceEngine_ == null,
1579
1689
  'We should not have a media source engine yet.');
1580
1690
 
1581
- const closedCaptionsParser = new shaka.media.ClosedCaptionParser();
1582
-
1583
1691
  // When changing text visibility we need to update both the text displayer
1584
1692
  // and streaming engine because we don't always stream text. To ensure that
1585
1693
  // text displayer and streaming engine are always in sync, wait until they
@@ -1590,11 +1698,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1590
1698
 
1591
1699
  const mediaSourceEngine = this.createMediaSourceEngine(
1592
1700
  has.mediaElement,
1593
- closedCaptionsParser,
1594
1701
  textDisplayer,
1595
1702
  (metadata, offset, endTime) => {
1596
1703
  this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
1597
- });
1704
+ },
1705
+ this.lcevcDil_);
1706
+ mediaSourceEngine.configure(this.config_.mediaSource);
1598
1707
  const {segmentRelativeVttTiming} = this.config_.manifest;
1599
1708
  mediaSourceEngine.setSegmentRelativeVttTiming(segmentRelativeVttTiming);
1600
1709
 
@@ -1775,6 +1884,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1775
1884
  this.streamingEngine_.updateDuration();
1776
1885
  }
1777
1886
  },
1887
+ newDrmInfo: (stream) => {
1888
+ // We may need to create new sessions for any new init data.
1889
+ const currentDrmInfo =
1890
+ this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
1891
+ // DrmEngine.newInitData() requires mediaKeys to be available.
1892
+ if (currentDrmInfo && this.drmEngine_.getMediaKeys()) {
1893
+ this.processDrmInfos_(currentDrmInfo.keySystem, stream);
1894
+ }
1895
+ },
1778
1896
  };
1779
1897
 
1780
1898
  const startTime = Date.now() / 1000;
@@ -1865,6 +1983,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1865
1983
  const now = Date.now() / 1000;
1866
1984
  const delta = now - startTime;
1867
1985
  this.stats_.setDrmTime(delta);
1986
+ // LCEVC data by itself is not encrypted in DRM protected streams and
1987
+ // can therefore be accessed and decoded as normal. However, the LCEVC
1988
+ // decoder needs access to the VideoElement output in order to apply
1989
+ // the enhancement. In DRM contexts where the browser CDM restricts
1990
+ // access from our decoder, the enhancement cannot be applied and
1991
+ // therefore the LCEVC output canvas is hidden accordingly.
1992
+ if (this.lcevcDil_) {
1993
+ this.lcevcDil_.hideCanvas();
1994
+ }
1868
1995
  }
1869
1996
  },
1870
1997
  });
@@ -1949,6 +2076,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1949
2076
  this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
1950
2077
  this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
1951
2078
 
2079
+ // Check status of the LCEVC DIL Object and reset or
2080
+ // create or close based on config
2081
+ this.setupLcevc_(this.config_);
2082
+
1952
2083
  const abrFactory = this.config_.abrFactory;
1953
2084
  if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
1954
2085
  this.abrManagerFactory_ = abrFactory;
@@ -1983,16 +2114,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1983
2114
  });
1984
2115
  this.abrManager_.setMediaElement(mediaElement);
1985
2116
 
1986
- this.playhead_ = this.createPlayhead(has.startTime);
1987
- this.playheadObservers_ = this.createPlayheadObserversForMSE_();
1988
-
1989
- // We need to start the buffer management code near the end because it will
1990
- // set the initial buffering state and that depends on other components
1991
- // being initialized.
1992
- const rebufferThreshold = Math.max(
1993
- this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
1994
- this.startBufferManagement_(rebufferThreshold);
1995
-
1996
2117
  // If the content is multi-codec and the browser can play more than one of
1997
2118
  // them, choose codecs now before we initialize streaming.
1998
2119
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
@@ -2036,14 +2157,42 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2036
2157
  this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
2037
2158
 
2038
2159
  // Pick the initial streams to play.
2039
- // however, we would skip switch to initial variant
2040
- // if user already pick variant track (via selectVariantTrack api)
2160
+ // Unless the user has already picked a variant, anyway, by calling
2161
+ // selectVariantTrack before this loading stage.
2041
2162
  let initialVariant = null;
2042
2163
  const activeVariantTrack = this.getVariantTracks().find((t) => t.active);
2043
-
2044
2164
  if (!activeVariantTrack) {
2045
2165
  initialVariant = this.chooseVariant_();
2046
2166
  goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
2167
+ }
2168
+
2169
+ // Lazy-load the stream, so we will have enough info to make the playhead.
2170
+ const createSegmentIndexPromises = [];
2171
+ const toLazyLoad = activeVariantTrack || initialVariant;
2172
+ for (const stream of [toLazyLoad.video, toLazyLoad.audio]) {
2173
+ if (stream && !stream.segmentIndex) {
2174
+ createSegmentIndexPromises.push(stream.createSegmentIndex());
2175
+ }
2176
+ }
2177
+ if (createSegmentIndexPromises.length > 0) {
2178
+ await Promise.all(createSegmentIndexPromises);
2179
+ }
2180
+
2181
+ this.playhead_ = this.createPlayhead(has.startTime);
2182
+ this.playheadObservers_ = this.createPlayheadObserversForMSE_();
2183
+
2184
+ // We need to start the buffer management code near the end because it will
2185
+ // set the initial buffering state and that depends on other components
2186
+ // being initialized.
2187
+ const rebufferThreshold = Math.max(
2188
+ this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
2189
+ this.startBufferManagement_(rebufferThreshold);
2190
+
2191
+ // Now we can switch to the initial variant.
2192
+ if (!activeVariantTrack) {
2193
+ goog.asserts.assert(initialVariant,
2194
+ 'Must have choosen an initial variant!');
2195
+
2047
2196
  this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
2048
2197
  /* clearBuffer= */ false, /* safeMargin= */ 0);
2049
2198
 
@@ -2062,6 +2211,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2062
2211
  shaka.util.StreamUtils.variantToTrack(initialVariant));
2063
2212
  }
2064
2213
 
2214
+ this.playhead_.ready();
2215
+
2065
2216
  // Decide if text should be shown automatically.
2066
2217
  // similar to video/audio track, we would skip switch initial text track
2067
2218
  // if user already pick text track (via selectTextTrack api)
@@ -2115,6 +2266,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2115
2266
  'arbitrary language initially');
2116
2267
  }
2117
2268
 
2269
+ this.fullyLoaded_ = true;
2270
+
2118
2271
  // Wait for the 'loadedmetadata' event to measure load() latency.
2119
2272
  this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2120
2273
  const now = Date.now() / 1000;
@@ -2399,6 +2552,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2399
2552
  HTMLMediaElement.HAVE_METADATA,
2400
2553
  this.loadEventManager_,
2401
2554
  () => {
2555
+ this.playhead_.ready();
2402
2556
  fullyLoaded.resolve();
2403
2557
  });
2404
2558
 
@@ -2460,6 +2614,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2460
2614
  shaka.util.Error.Code.OPERATION_ABORTED);
2461
2615
  fullyLoaded.reject(abortedError);
2462
2616
  return Promise.resolve(); // Abort complete.
2617
+ }).chain(() => {
2618
+ this.fullyLoaded_ = true;
2463
2619
  });
2464
2620
  }
2465
2621
 
@@ -2572,11 +2728,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2572
2728
  */
2573
2729
  processTimedMetadataMediaSrc_(metadata, offset, segmentEndTime) {
2574
2730
  for (const sample of metadata) {
2575
- if (sample['data'] && sample['cueTime'] && sample['frames']) {
2576
- const start = sample['cueTime'] + offset;
2577
- const end = segmentEndTime;
2578
- const metadataType = 'ID3';
2579
- for (const frame of sample['frames']) {
2731
+ if (sample.data && sample.cueTime && sample.frames) {
2732
+ const start = sample.cueTime + offset;
2733
+ let end = segmentEndTime;
2734
+ // This can happen when the ID3 info arrives in a previous segment.
2735
+ if (end && start > end) {
2736
+ end = start;
2737
+ }
2738
+ const metadataType = 'org.id3';
2739
+ for (const frame of sample.frames) {
2580
2740
  const payload = frame;
2581
2741
  this.dispatchMetadataEvent_(start, end, metadataType, payload);
2582
2742
  }
@@ -2595,7 +2755,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2595
2755
  * @param {number} startTime
2596
2756
  * @param {?number} endTime
2597
2757
  * @param {string} metadataType
2598
- * @param {shaka.extern.ID3Metadata} payload
2758
+ * @param {shaka.extern.MetadataFrame} payload
2599
2759
  * @private
2600
2760
  */
2601
2761
  dispatchMetadataEvent_(startTime, endTime, metadataType, payload) {
@@ -2893,17 +3053,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2893
3053
  * way to inject fake media source engine instances.
2894
3054
  *
2895
3055
  * @param {!HTMLMediaElement} mediaElement
2896
- * @param {!shaka.media.IClosedCaptionParser} closedCaptionsParser
2897
3056
  * @param {!shaka.extern.TextDisplayer} textDisplayer
2898
3057
  * @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)}
2899
3058
  * onMetadata
3059
+ * @param {shaka.lcevc.Dil} lcevcDil
2900
3060
  *
2901
3061
  * @return {!shaka.media.MediaSourceEngine}
2902
3062
  */
2903
- createMediaSourceEngine(mediaElement, closedCaptionsParser, textDisplayer,
2904
- onMetadata) {
3063
+ createMediaSourceEngine(mediaElement, textDisplayer, onMetadata, lcevcDil) {
2905
3064
  return new shaka.media.MediaSourceEngine(
2906
- mediaElement, closedCaptionsParser, textDisplayer, onMetadata);
3065
+ mediaElement,
3066
+ textDisplayer,
3067
+ onMetadata,
3068
+ lcevcDil);
2907
3069
  }
2908
3070
 
2909
3071
  /**
@@ -2934,13 +3096,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2934
3096
  */
2935
3097
  createStreamingEngine() {
2936
3098
  goog.asserts.assert(
2937
- this.playhead_ && this.abrManager_ && this.mediaSourceEngine_ &&
3099
+ this.abrManager_ && this.mediaSourceEngine_ &&
2938
3100
  this.cmcdManager_ && this.manifest_,
2939
3101
  'Must not be destroyed');
2940
3102
 
2941
3103
  /** @type {shaka.media.StreamingEngine.PlayerInterface} */
2942
3104
  const playerInterface = {
2943
- getPresentationTime: () => this.playhead_.getTime(),
3105
+ getPresentationTime: () => this.playhead_ ? this.playhead_.getTime() : 0,
2944
3106
  getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
2945
3107
  modifySegmentRequest: (request, segmentInfo) => {
2946
3108
  this.cmcdManager_.applySegmentData(request, segmentInfo);
@@ -2950,7 +3112,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2950
3112
  onError: (error) => this.onError_(error),
2951
3113
  onEvent: (event) => this.dispatchEvent(event),
2952
3114
  onManifestUpdate: () => this.onManifestUpdate_(),
2953
- onSegmentAppended: () => this.onSegmentAppended_(),
3115
+ onSegmentAppended: (start, end, contentType) => {
3116
+ this.onSegmentAppended_(start, end, contentType);
3117
+ },
2954
3118
  onInitSegmentAppended: (position, initSegment) => {
2955
3119
  const mediaQuality = initSegment.getMediaQuality();
2956
3120
  if (mediaQuality && this.qualityObserver_) {
@@ -2960,6 +3124,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2960
3124
  beforeAppendSegment: (contentType, segment) => {
2961
3125
  return this.drmEngine_.parseInbandPssh(contentType, segment);
2962
3126
  },
3127
+ onMetadata: (metadata, offset, endTime) => {
3128
+ this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
3129
+ },
2963
3130
  };
2964
3131
 
2965
3132
  return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
@@ -3003,6 +3170,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3003
3170
 
3004
3171
  goog.asserts.assert(typeof(config) == 'object', 'Should be an object!');
3005
3172
 
3173
+ // Deprecate 'streaming.forceTransmuxTS' configuration.
3174
+ if (config['streaming'] && 'forceTransmuxTS' in config['streaming']) {
3175
+ shaka.Deprecate.deprecateFeature(5,
3176
+ 'streaming.forceTransmuxTS configuration',
3177
+ 'Please Use streaming.forceTransmux instead.');
3178
+ config['streaming']['forceTransmux'] =
3179
+ config['streaming']['forceTransmuxTS'];
3180
+ delete config['streaming']['forceTransmuxTS'];
3181
+ }
3182
+
3006
3183
  // If lowLatencyMode is enabled, and inaccurateManifestTolerance and
3007
3184
  // rebufferingGoal are not specified, set inaccurateManifestTolerance to 0
3008
3185
  // and rebufferingGoal to 0.01 by default for low latency streaming.
@@ -3069,6 +3246,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3069
3246
  }
3070
3247
 
3071
3248
  if (this.mediaSourceEngine_) {
3249
+ this.mediaSourceEngine_.configure(this.config_.mediaSource);
3072
3250
  const {segmentRelativeVttTiming} = this.config_.manifest;
3073
3251
  this.mediaSourceEngine_.setSegmentRelativeVttTiming(
3074
3252
  segmentRelativeVttTiming);
@@ -3356,6 +3534,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3356
3534
  * @export
3357
3535
  */
3358
3536
  seekRange() {
3537
+ if (!this.fullyLoaded_) {
3538
+ return {'start': 0, 'end': 0};
3539
+ }
3540
+
3359
3541
  if (this.manifest_) {
3360
3542
  const timeline = this.manifest_.presentationTimeline;
3361
3543
 
@@ -3698,8 +3880,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3698
3880
  const fullImageHeight = imageStream.height || 0;
3699
3881
  const columns = parseInt(match[1], 10);
3700
3882
  const rows = parseInt(match[2], 10);
3701
- const width = fullImageWidth / columns;
3702
- const height = fullImageHeight / rows;
3883
+ let width = fullImageWidth / columns;
3884
+ let height = fullImageHeight / rows;
3703
3885
  const totalImages = columns * rows;
3704
3886
  const segmentDuration = reference.trueEndTime - reference.startTime;
3705
3887
  const thumbnailDuration =
@@ -3725,6 +3907,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3725
3907
  positionX = (thumbnailPosition % columns) * width;
3726
3908
  positionY = Math.floor(thumbnailPosition / columns) * height;
3727
3909
  }
3910
+ let sprite = false;
3911
+ const thumbnailSprite = reference.getThumbnailSprite();
3912
+ if (thumbnailSprite) {
3913
+ sprite = true;
3914
+ height = thumbnailSprite.height;
3915
+ positionX = thumbnailSprite.positionX;
3916
+ positionY = thumbnailSprite.positionY;
3917
+ width = thumbnailSprite.width;
3918
+ }
3728
3919
  return {
3729
3920
  imageHeight: fullImageHeight,
3730
3921
  imageWidth: fullImageWidth,
@@ -3735,6 +3926,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3735
3926
  duration: thumbnailDuration,
3736
3927
  uris: reference.getUris(),
3737
3928
  width: width,
3929
+ sprite: sprite,
3738
3930
  };
3739
3931
  }
3740
3932
  return null;
@@ -3918,20 +4110,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3918
4110
 
3919
4111
  /**
3920
4112
  * Sets the current audio language and current variant role to the selected
3921
- * language and role, and chooses a new variant if need be. If the player has
3922
- * not loaded any content, this will be a no-op.
4113
+ * language, role and channel count, and chooses a new variant if need be.
4114
+ * If the player has not loaded any content, this will be a no-op.
3923
4115
  *
3924
4116
  * @param {string} language
3925
4117
  * @param {string=} role
4118
+ * @param {number=} channelsCount
3926
4119
  * @export
3927
4120
  */
3928
- selectAudioLanguage(language, role) {
4121
+ selectAudioLanguage(language, role, channelsCount = 0) {
3929
4122
  const LanguageUtils = shaka.util.LanguageUtils;
3930
4123
 
3931
4124
  if (this.manifest_ && this.playhead_) {
3932
4125
  this.currentAdaptationSetCriteria_ =
3933
4126
  new shaka.media.PreferenceBasedCriteria(language, role || '',
3934
- /* channelCount= */ 0, /* label= */ '');
4127
+ channelsCount, /* label= */ '');
3935
4128
 
3936
4129
  const diff = (a, b) => {
3937
4130
  if (!a.video && !b.video) {
@@ -4610,6 +4803,146 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4610
4803
  return shaka.util.StreamUtils.textStreamToTrack(stream);
4611
4804
  }
4612
4805
 
4806
+ /**
4807
+ * Adds the given thumbnails track to the loaded manifest.
4808
+ * <code>load()</code> must resolve before calling. The presentation must
4809
+ * have a duration.
4810
+ *
4811
+ * This returns the created track, which can immediately be used by the
4812
+ * application.
4813
+ *
4814
+ * @param {string} uri
4815
+ * @param {string=} mimeType
4816
+ * @return {!Promise.<shaka.extern.Track>}
4817
+ * @export
4818
+ */
4819
+ async addThumbnailsTrack(uri, mimeType) {
4820
+ if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE &&
4821
+ this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) {
4822
+ shaka.log.error(
4823
+ 'Must call load() and wait for it to resolve before adding image ' +
4824
+ 'tracks.');
4825
+ throw new shaka.util.Error(
4826
+ shaka.util.Error.Severity.RECOVERABLE,
4827
+ shaka.util.Error.Category.PLAYER,
4828
+ shaka.util.Error.Code.CONTENT_NOT_LOADED);
4829
+ }
4830
+
4831
+ if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4832
+ shaka.log.error('Cannot add this thumbnail track when loaded with src=');
4833
+ throw new shaka.util.Error(
4834
+ shaka.util.Error.Severity.RECOVERABLE,
4835
+ shaka.util.Error.Category.TEXT,
4836
+ shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_SRC_EQUALS);
4837
+ }
4838
+
4839
+ if (!mimeType) {
4840
+ mimeType = await this.getTextMimetype_(uri);
4841
+ }
4842
+
4843
+ if (mimeType != 'text/vtt') {
4844
+ throw new shaka.util.Error(
4845
+ shaka.util.Error.Severity.RECOVERABLE,
4846
+ shaka.util.Error.Category.TEXT,
4847
+ shaka.util.Error.Code.UNSUPPORTED_EXTERNAL_THUMBNAILS_URI,
4848
+ uri);
4849
+ }
4850
+
4851
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
4852
+
4853
+ const duration = this.manifest_.presentationTimeline.getDuration();
4854
+ if (duration == Infinity) {
4855
+ throw new shaka.util.Error(
4856
+ shaka.util.Error.Severity.RECOVERABLE,
4857
+ shaka.util.Error.Category.MANIFEST,
4858
+ shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_LIVE_STREAM);
4859
+ }
4860
+
4861
+ goog.asserts.assert(
4862
+ this.networkingEngine_, 'Need networking engine.');
4863
+ const buffer = await this.getTextData_(uri,
4864
+ this.networkingEngine_,
4865
+ this.config_.streaming.retryParameters);
4866
+
4867
+ const factory = shaka.text.TextEngine.findParser(mimeType);
4868
+ if (!factory) {
4869
+ throw new shaka.util.Error(
4870
+ shaka.util.Error.Severity.CRITICAL,
4871
+ shaka.util.Error.Category.TEXT,
4872
+ shaka.util.Error.Code.MISSING_TEXT_PLUGIN,
4873
+ mimeType);
4874
+ }
4875
+ const TextParser = factory();
4876
+ const time = {
4877
+ periodStart: 0,
4878
+ segmentStart: 0,
4879
+ segmentEnd: duration,
4880
+ vttOffset: 0,
4881
+ };
4882
+ const data = shaka.util.BufferUtils.toUint8(buffer);
4883
+ const cues = TextParser.parseMedia(data, time);
4884
+
4885
+ const references = [];
4886
+ for (const cue of cues) {
4887
+ const imageUri = shaka.util.ManifestParserUtils.resolveUris(
4888
+ [uri], [cue.payload])[0];
4889
+ const reference = new shaka.media.SegmentReference(
4890
+ cue.startTime,
4891
+ cue.endTime,
4892
+ () => [imageUri],
4893
+ /* startByte= */ 0,
4894
+ /* endByte= */ null,
4895
+ /* initSegmentReference= */ null,
4896
+ /* timestampOffset= */ 0,
4897
+ /* appendWindowStart= */ 0,
4898
+ /* appendWindowEnd= */ Infinity,
4899
+ );
4900
+ if (imageUri.includes('#xywh')) {
4901
+ const spriteInfo = imageUri.split('#xywh=')[1].split(',');
4902
+ if (spriteInfo.length === 4) {
4903
+ reference.setThumbnailSprite({
4904
+ height: parseInt(spriteInfo[3], 10),
4905
+ positionX: parseInt(spriteInfo[0], 10),
4906
+ positionY: parseInt(spriteInfo[1], 10),
4907
+ width: parseInt(spriteInfo[2], 10),
4908
+ });
4909
+ }
4910
+ }
4911
+ references.push(reference);
4912
+ }
4913
+
4914
+ /** @type {shaka.extern.Stream} */
4915
+ const stream = {
4916
+ id: this.nextExternalStreamId_++,
4917
+ originalId: null,
4918
+ createSegmentIndex: () => Promise.resolve(),
4919
+ segmentIndex: new shaka.media.SegmentIndex(references),
4920
+ mimeType: mimeType || '',
4921
+ codecs: '',
4922
+ kind: '',
4923
+ encrypted: false,
4924
+ drmInfos: [],
4925
+ keyIds: new Set(),
4926
+ language: 'und',
4927
+ label: null,
4928
+ type: ContentType.IMAGE,
4929
+ primary: false,
4930
+ trickModeVideo: null,
4931
+ emsgSchemeIdUris: null,
4932
+ roles: [],
4933
+ forced: false,
4934
+ channelsCount: null,
4935
+ audioSamplingRate: null,
4936
+ spatialAudio: false,
4937
+ closedCaptions: null,
4938
+ tilesLayout: '1x1',
4939
+ };
4940
+
4941
+ this.manifest_.imageStreams.push(stream);
4942
+ this.onTracksChanged_();
4943
+ return shaka.util.StreamUtils.imageStreamToTrack(stream);
4944
+ }
4945
+
4613
4946
  /**
4614
4947
  * Adds the given chapters track to the loaded manifest. <code>load()</code>
4615
4948
  * must resolve before calling. The presentation must have a duration.
@@ -4841,12 +5174,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4841
5174
  * If the player has loaded content, and streaming seen an error, but the
4842
5175
  * could not resume streaming, this will return <code>false</code>.
4843
5176
  *
5177
+ * @param {number=} retryDelaySeconds
4844
5178
  * @return {boolean}
4845
5179
  * @export
4846
5180
  */
4847
- retryStreaming() {
5181
+ retryStreaming(retryDelaySeconds = 0.1) {
4848
5182
  return this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE ?
4849
- this.streamingEngine_.retry() :
5183
+ this.streamingEngine_.retry(retryDelaySeconds) :
4850
5184
  false;
4851
5185
  }
4852
5186
 
@@ -4942,17 +5276,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4942
5276
  * @private
4943
5277
  */
4944
5278
  defaultStreamingFailureCallback_(error) {
4945
- const retryErrorCodes = [
4946
- shaka.util.Error.Code.BAD_HTTP_STATUS,
4947
- shaka.util.Error.Code.HTTP_ERROR,
4948
- shaka.util.Error.Code.TIMEOUT,
4949
- ];
5279
+ // For live streams, we retry streaming automatically for certain errors.
5280
+ // For VOD streams, all streaming failures are fatal.
5281
+ if (!this.isLive()) {
5282
+ return;
5283
+ }
4950
5284
 
4951
- if (this.isLive() && retryErrorCodes.includes(error.code)) {
4952
- error.severity = shaka.util.Error.Severity.RECOVERABLE;
5285
+ let retryDelaySeconds = null;
5286
+ if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS ||
5287
+ error.code == shaka.util.Error.Code.HTTP_ERROR) {
5288
+ // These errors can be near-instant, so delay a bit before retrying.
5289
+ retryDelaySeconds = 1;
5290
+ } else if (error.code == shaka.util.Error.Code.TIMEOUT) {
5291
+ // We already waited for a timeout, so retry quickly.
5292
+ retryDelaySeconds = 0.1;
5293
+ }
4953
5294
 
5295
+ if (retryDelaySeconds != null) {
5296
+ error.severity = shaka.util.Error.Severity.RECOVERABLE;
4954
5297
  shaka.log.warning('Live streaming error. Retrying automatically...');
4955
- this.retryStreaming();
5298
+ this.retryStreaming(retryDelaySeconds);
4956
5299
  }
4957
5300
  }
4958
5301
 
@@ -5071,28 +5414,39 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5071
5414
  }
5072
5415
 
5073
5416
  // We may need to create new sessions for any new init data.
5074
- const curDrmInfo = this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
5417
+ const currentDrmInfo =
5418
+ this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
5075
5419
  // DrmEngine.newInitData() requires mediaKeys to be available.
5076
- if (curDrmInfo && this.drmEngine_.getMediaKeys()) {
5420
+ if (currentDrmInfo && this.drmEngine_.getMediaKeys()) {
5077
5421
  for (const variant of manifest.variants) {
5078
- const videoDrmInfos = variant.video ? variant.video.drmInfos : [];
5079
- const audioDrmInfos = variant.audio ? variant.audio.drmInfos : [];
5080
- const drmInfos = videoDrmInfos.concat(audioDrmInfos);
5081
- for (const drmInfo of drmInfos) {
5082
- // Ignore any data for different key systems.
5083
- if (drmInfo.keySystem == curDrmInfo.keySystem) {
5084
- for (const initData of (drmInfo.initData || [])) {
5085
- this.drmEngine_.newInitData(
5086
- initData.initDataType, initData.initData);
5087
- }
5088
- }
5089
- }
5422
+ this.processDrmInfos_(currentDrmInfo.keySystem, variant.video);
5423
+ this.processDrmInfos_(currentDrmInfo.keySystem, variant.audio);
5090
5424
  }
5091
5425
  }
5092
-
5093
5426
  this.checkRestrictedVariants_(manifest);
5094
5427
  }
5095
5428
 
5429
+ /**
5430
+ * @param {string} keySystem
5431
+ * @param {?shaka.extern.Stream} stream
5432
+ * @private
5433
+ */
5434
+ processDrmInfos_(keySystem, stream) {
5435
+ if (!stream) {
5436
+ return;
5437
+ }
5438
+
5439
+ for (const drmInfo of stream.drmInfos) {
5440
+ // Ignore any data for different key systems.
5441
+ if (drmInfo.keySystem == keySystem) {
5442
+ for (const initData of (drmInfo.initData || [])) {
5443
+ this.drmEngine_.newInitData(
5444
+ initData.initDataType, initData.initData);
5445
+ }
5446
+ }
5447
+ }
5448
+ }
5449
+
5096
5450
  /**
5097
5451
  * @private
5098
5452
  */
@@ -5468,38 +5822,62 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5468
5822
  /**
5469
5823
  * Check if we should show text on screen automatically.
5470
5824
  *
5471
- * The text should automatically be shown if the text is language-compatible
5472
- * with the user's text language preference, but not compatible with the
5473
- * audio.
5474
- *
5475
- * For example:
5476
- * preferred | chosen | chosen |
5477
- * text | text | audio | show
5478
- * -----------------------------------
5479
- * en-CA | en | jp | true
5480
- * en | en-US | fr | true
5481
- * fr-CA | en-US | jp | false
5482
- * en-CA | en-US | en-US | false
5483
- *
5484
5825
  * @param {shaka.extern.Stream} audioStream
5485
5826
  * @param {shaka.extern.Stream} textStream
5486
5827
  * @return {boolean}
5487
5828
  * @private
5488
5829
  */
5489
5830
  shouldInitiallyShowText_(audioStream, textStream) {
5831
+ const AutoShowText = shaka.config.AutoShowText;
5832
+
5833
+ if (this.config_.autoShowText == AutoShowText.NEVER) {
5834
+ return false;
5835
+ }
5836
+ if (this.config_.autoShowText == AutoShowText.ALWAYS) {
5837
+ return true;
5838
+ }
5839
+
5490
5840
  const LanguageUtils = shaka.util.LanguageUtils;
5491
5841
 
5492
5842
  /** @type {string} */
5493
5843
  const preferredTextLocale =
5494
5844
  LanguageUtils.normalize(this.config_.preferredTextLanguage);
5495
5845
  /** @type {string} */
5496
- const audioLocale = LanguageUtils.normalize(audioStream.language);
5497
- /** @type {string} */
5498
5846
  const textLocale = LanguageUtils.normalize(textStream.language);
5499
5847
 
5500
- return (
5501
- LanguageUtils.areLanguageCompatible(textLocale, preferredTextLocale) &&
5502
- !LanguageUtils.areLanguageCompatible(audioLocale, textLocale));
5848
+ if (this.config_.autoShowText == AutoShowText.IF_PREFERRED_TEXT_LANGUAGE) {
5849
+ // Only the text language match matters.
5850
+ return LanguageUtils.areLanguageCompatible(
5851
+ textLocale,
5852
+ preferredTextLocale);
5853
+ }
5854
+
5855
+ if (this.config_.autoShowText == AutoShowText.IF_SUBTITLES_MAY_BE_NEEDED) {
5856
+ /* The text should automatically be shown if the text is
5857
+ * language-compatible with the user's text language preference, but not
5858
+ * compatible with the audio. These are cases where we deduce that
5859
+ * subtitles may be needed.
5860
+ *
5861
+ * For example:
5862
+ * preferred | chosen | chosen |
5863
+ * text | text | audio | show
5864
+ * -----------------------------------
5865
+ * en-CA | en | jp | true
5866
+ * en | en-US | fr | true
5867
+ * fr-CA | en-US | jp | false
5868
+ * en-CA | en-US | en-US | false
5869
+ *
5870
+ */
5871
+ /** @type {string} */
5872
+ const audioLocale = LanguageUtils.normalize(audioStream.language);
5873
+
5874
+ return (
5875
+ LanguageUtils.areLanguageCompatible(textLocale, preferredTextLocale) &&
5876
+ !LanguageUtils.areLanguageCompatible(audioLocale, textLocale));
5877
+ }
5878
+
5879
+ shaka.log.alwaysWarn('Invalid autoShowText setting!');
5880
+ return false;
5503
5881
  }
5504
5882
 
5505
5883
  /**
@@ -5518,7 +5896,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5518
5896
  *
5519
5897
  * @private
5520
5898
  */
5521
- onSegmentAppended_() {
5899
+ onSegmentAppended_(start, end, contentType) {
5522
5900
  // When we append a segment to media source (via streaming engine) we are
5523
5901
  // changing what data we have buffered, so notify the playhead of the
5524
5902
  // change.
@@ -5526,6 +5904,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5526
5904
  this.playhead_.notifyOfBufferingChange();
5527
5905
  }
5528
5906
  this.pollBufferState_();
5907
+
5908
+ // Dispatch an event for users to consume, too.
5909
+ const data = new Map()
5910
+ .set('start', start)
5911
+ .set('end', end)
5912
+ .set('contentType', contentType);
5913
+ this.dispatchEvent(this.makeEvent_(
5914
+ shaka.util.FakeEvent.EventName.SegmentAppended, data));
5529
5915
  }
5530
5916
 
5531
5917
  /**
@@ -5571,6 +5957,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5571
5957
  const data = new Map()
5572
5958
  .set('oldTrack', from)
5573
5959
  .set('newTrack', to);
5960
+ if (this.lcevcDil_) {
5961
+ this.lcevcDil_.updateVariant(to);
5962
+ }
5574
5963
  const event =
5575
5964
  this.makeEvent_(shaka.util.FakeEvent.EventName.Adaptation, data);
5576
5965
  this.delayDispatchEvent_(event);
@@ -5599,6 +5988,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5599
5988
  const data = new Map()
5600
5989
  .set('oldTrack', from)
5601
5990
  .set('newTrack', to);
5991
+ if (this.lcevcDil_) {
5992
+ this.lcevcDil_.updateVariant(to);
5993
+ }
5994
+
5602
5995
  const event =
5603
5996
  this.makeEvent_(shaka.util.FakeEvent.EventName.VariantChanged, data);
5604
5997
  this.delayDispatchEvent_(event);
@@ -6543,7 +6936,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6543
6936
  }
6544
6937
  };
6545
6938
 
6546
-
6547
6939
  /**
6548
6940
  * In order to know what method of loading the player used for some content, we
6549
6941
  * have this enum. It lets us know if content has not been loaded, loaded with
@@ -6582,7 +6974,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6582
6974
  * @define {string} A version number taken from git at compile time.
6583
6975
  * @export
6584
6976
  */
6585
- shaka.Player.version = 'v4.2.3-uncompiled';
6977
+ shaka.Player.version = 'v4.3.0-uncompiled';
6586
6978
 
6587
6979
  // Initialize the deprecation system using the version string we just set
6588
6980
  // on the player.