shaka-player 3.1.5 → 3.2.3

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 (134) hide show
  1. package/CHANGELOG.md +129 -6
  2. package/README.md +4 -4
  3. package/build/types/polyfill +1 -0
  4. package/demo/common/asset.js +9 -7
  5. package/demo/common/assets.js +10 -1
  6. package/demo/common/message_ids.js +2 -1
  7. package/demo/config.js +5 -2
  8. package/demo/index.html +1 -1
  9. package/demo/locales/en.json +2 -1
  10. package/demo/locales/source.json +8 -4
  11. package/demo/main.js +2 -1
  12. package/dist/demo.compiled.debug.js +14 -14
  13. package/dist/demo.compiled.debug.map +2 -2
  14. package/dist/demo.compiled.js +14 -14
  15. package/dist/demo.compiled.map +2 -2
  16. package/dist/deps.js +21 -20
  17. package/dist/locales.js +30 -30
  18. package/dist/receiver.compiled.debug.js +8 -8
  19. package/dist/receiver.compiled.debug.map +2 -2
  20. package/dist/receiver.compiled.js +8 -8
  21. package/dist/receiver.compiled.map +2 -2
  22. package/dist/shaka-player.compiled.d.ts +63 -10
  23. package/dist/shaka-player.compiled.debug.d.ts +63 -10
  24. package/dist/shaka-player.compiled.debug.externs.js +786 -716
  25. package/dist/shaka-player.compiled.debug.js +563 -534
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.externs.js +786 -716
  28. package/dist/shaka-player.compiled.js +820 -809
  29. package/dist/shaka-player.compiled.map +4 -4
  30. package/dist/shaka-player.ui.d.ts +63 -10
  31. package/dist/shaka-player.ui.debug.d.ts +63 -10
  32. package/dist/shaka-player.ui.debug.externs.js +542 -472
  33. package/dist/shaka-player.ui.debug.js +594 -566
  34. package/dist/shaka-player.ui.debug.map +4 -4
  35. package/dist/shaka-player.ui.externs.js +542 -472
  36. package/dist/shaka-player.ui.js +938 -927
  37. package/dist/shaka-player.ui.map +4 -4
  38. package/docs/tutorials/ui.md +5 -5
  39. package/externs/aria.js +6 -0
  40. package/externs/shaka/ads.js +24 -1
  41. package/externs/shaka/manifest.js +6 -1
  42. package/externs/shaka/offline.js +5 -0
  43. package/externs/shaka/player.js +61 -9
  44. package/externs/shaka/text.js +10 -10
  45. package/externs/texttrack.js +3 -0
  46. package/lib/ads/ad_manager.js +16 -15
  47. package/lib/ads/client_side_ad_manager.js +6 -2
  48. package/lib/ads/server_side_ad_manager.js +19 -2
  49. package/lib/cast/cast_proxy.js +8 -0
  50. package/lib/cast/cast_utils.js +6 -1
  51. package/lib/cea/cea608_data_channel.js +56 -56
  52. package/lib/cea/cea608_memory.js +33 -33
  53. package/lib/cea/cea708_service.js +43 -43
  54. package/lib/cea/cea708_window.js +38 -38
  55. package/lib/cea/cea_decoder.js +13 -14
  56. package/lib/cea/cea_utils.js +23 -23
  57. package/lib/cea/dtvcc_packet_builder.js +7 -7
  58. package/lib/cea/i_caption_decoder.js +2 -3
  59. package/lib/cea/i_cea_parser.js +2 -2
  60. package/lib/cea/mp4_cea_parser.js +6 -6
  61. package/lib/dash/dash_parser.js +5 -0
  62. package/lib/hls/hls_parser.js +159 -12
  63. package/lib/media/closed_caption_parser.js +1 -2
  64. package/lib/media/drm_engine.js +162 -192
  65. package/lib/media/segment_index.js +4 -1
  66. package/lib/media/segment_reference.js +37 -2
  67. package/lib/media/streaming_engine.js +6 -6
  68. package/lib/net/networking_engine.js +1 -0
  69. package/lib/offline/download_manager.js +1 -2
  70. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  71. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  72. package/lib/offline/manifest_converter.js +3 -1
  73. package/lib/offline/storage.js +12 -14
  74. package/lib/player.js +426 -149
  75. package/lib/polyfill/aria.js +82 -0
  76. package/lib/routing/walker.js +2 -3
  77. package/lib/text/srt_text_parser.js +4 -4
  78. package/lib/text/text_engine.js +2 -2
  79. package/lib/text/vtt_text_parser.js +2 -2
  80. package/lib/text/web_vtt_generator.js +55 -12
  81. package/lib/util/error.js +6 -0
  82. package/lib/util/manifest_parser_utils.js +1 -0
  83. package/lib/util/mp4_box_parsers.js +20 -20
  84. package/lib/util/multi_map.js +8 -0
  85. package/lib/util/player_configuration.js +13 -1
  86. package/lib/util/stream_utils.js +168 -88
  87. package/lib/util/xml_utils.js +31 -12
  88. package/package.json +1 -1
  89. package/roadmap.md +24 -22
  90. package/shaka-player.uncompiled.js +1 -0
  91. package/test/cast/cast_utils_unit.js +1 -0
  92. package/test/cea/cea608_memory_unit.js +1 -1
  93. package/test/cea/cea708_service_unit.js +8 -8
  94. package/test/cea/cea708_window_unit.js +6 -6
  95. package/test/cea/cea_decoder_unit.js +1 -1
  96. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  97. package/test/dash/dash_parser_manifest_unit.js +43 -1
  98. package/test/hls/hls_parser_unit.js +250 -0
  99. package/test/media/drm_engine_integration.js +86 -12
  100. package/test/media/drm_engine_unit.js +310 -450
  101. package/test/offline/manifest_convert_unit.js +2 -0
  102. package/test/offline/storage_integration.js +9 -19
  103. package/test/player_integration.js +85 -0
  104. package/test/player_src_equals_integration.js +85 -0
  105. package/test/player_unit.js +25 -37
  106. package/test/test/assets/chapters.srt +11 -0
  107. package/test/test/assets/chapters.vtt +10 -0
  108. package/test/test/assets/chapters2.vtt +10 -0
  109. package/test/test/util/cea_utils.js +12 -12
  110. package/test/test/util/fake_ad_manager.js +7 -0
  111. package/test/test/util/manifest_generator.js +2 -0
  112. package/test/test/util/manifest_parser_util.js +3 -1
  113. package/test/text/web_vtt_generator_unit.js +81 -7
  114. package/test/util/stream_utils_unit.js +118 -10
  115. package/ui/airplay_button.js +1 -1
  116. package/ui/audio_language_selection.js +2 -5
  117. package/ui/cast_button.js +4 -6
  118. package/ui/constants.js +0 -3
  119. package/ui/fast_forward_button.js +2 -3
  120. package/ui/fullscreen_button.js +1 -3
  121. package/ui/language_utils.js +1 -1
  122. package/ui/loop_button.js +1 -3
  123. package/ui/mute_button.js +1 -3
  124. package/ui/overflow_menu.js +2 -3
  125. package/ui/pip_button.js +5 -7
  126. package/ui/playback_rate_selection.js +3 -6
  127. package/ui/presentation_time.js +1 -3
  128. package/ui/resolution_selection.js +4 -7
  129. package/ui/rewind_button.js +2 -3
  130. package/ui/seek_bar.js +2 -4
  131. package/ui/spacer.js +1 -1
  132. package/ui/text_selection.js +8 -11
  133. package/ui/ui_utils.js +1 -1
  134. package/ui/volume_bar.js +1 -3
package/lib/player.js CHANGED
@@ -217,6 +217,8 @@ goog.requireType('shaka.routing.Payload');
217
217
  * <code>selectAudioLanguage()</code>, or <code>selectTextLanguage()</code>.
218
218
  * @property {string} type
219
219
  * 'adaptation'
220
+ * @property {shaka.extern.Track} oldTrack
221
+ * @property {shaka.extern.Track} newTrack
220
222
  * @exportDoc
221
223
  */
222
224
 
@@ -1142,25 +1144,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1142
1144
  if (!mimeType) {
1143
1145
  // Try using the uri extension.
1144
1146
  const extension = shaka.media.ManifestParser.getExtension(uri);
1145
- mimeType = {
1146
- 'mp4': 'video/mp4',
1147
- 'm4v': 'video/mp4',
1148
- 'm4a': 'audio/mp4',
1149
- 'webm': 'video/webm',
1150
- 'weba': 'audio/webm',
1151
- 'mkv': 'video/webm', // Chromium browsers supports it.
1152
- 'ts': 'video/mp2t',
1153
- 'ogv': 'video/ogg',
1154
- 'ogg': 'audio/ogg',
1155
- 'mpg': 'video/mpeg',
1156
- 'mpeg': 'video/mpeg',
1157
- 'm3u8': 'application/x-mpegurl',
1158
- 'mpd': 'application/dash+xml',
1159
- 'mp3': 'audio/mpeg',
1160
- 'aac': 'audio/aac',
1161
- 'flac': 'audio/flac',
1162
- 'wav': 'audio/wav',
1163
- }[extension];
1147
+ mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
1164
1148
  }
1165
1149
 
1166
1150
  // TODO: The load graph system has a design limitation that requires routing
@@ -1634,11 +1618,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1634
1618
  }
1635
1619
  });
1636
1620
 
1637
- // TODO (#1391): Once filterManifest_ is async, remove this eslint disable.
1638
- /* eslint-disable require-await */
1639
1621
  const playerInterface = {
1640
1622
  networkingEngine: networkingEngine,
1641
- filter: async (manifest) => this.filterManifest_(manifest),
1623
+ filter: (manifest) => this.filterManifest_(manifest),
1642
1624
  makeTextStreamsForClosedCaptions: (manifest) => {
1643
1625
  return this.makeTextStreamsForClosedCaptions_(manifest);
1644
1626
  },
@@ -1656,7 +1638,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1656
1638
  this.configure('streaming.lowLatencyMode', true);
1657
1639
  },
1658
1640
  };
1659
- /* eslint-enable require-await */
1660
1641
 
1661
1642
  const startTime = Date.now() / 1000;
1662
1643
 
@@ -1748,17 +1729,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1748
1729
  },
1749
1730
  });
1750
1731
 
1751
- // TODO: remove once MediaCap implementation is complete.
1752
- if (!this.config_.useMediaCapabilities) {
1753
- shaka.util.StreamUtils.filterManifestByMediaSource(this.manifest_);
1754
- }
1755
-
1756
1732
  this.drmEngine_.configure(this.config_.drm);
1757
1733
 
1758
1734
  await this.drmEngine_.initForPlayback(
1759
1735
  this.manifest_.variants,
1760
- this.manifest_.offlineSessionIds,
1761
- this.config_.useMediaCapabilities);
1736
+ this.manifest_.offlineSessionIds);
1762
1737
 
1763
1738
  await this.drmEngine_.attach(has.mediaElement);
1764
1739
 
@@ -1873,7 +1848,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1873
1848
  // If the content is multi-codec and the browser can play more than one of
1874
1849
  // them, choose codecs now before we initialize streaming.
1875
1850
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
1876
- this.manifest_, this.config_.preferredAudioChannelCount);
1851
+ this.manifest_,
1852
+ this.config_.preferredVideoCodecs,
1853
+ this.config_.preferredAudioCodecs,
1854
+ this.config_.preferredAudioChannelCount,
1855
+ this.config_.preferredDecodingAttributes);
1877
1856
 
1878
1857
  this.streamingEngine_ = this.createStreamingEngine();
1879
1858
  this.streamingEngine_.configure(this.config_.streaming);
@@ -1912,7 +1891,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1912
1891
  }
1913
1892
 
1914
1893
  // Since the first streams just became active, send an adaptation event.
1915
- this.onAdaptation_();
1894
+ this.onAdaptation_(null,
1895
+ shaka.util.StreamUtils.variantToTrack(initialVariant));
1916
1896
  }
1917
1897
 
1918
1898
  // Decide if text should be shown automatically.
@@ -2025,6 +2005,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2025
2005
 
2026
2006
  this.drmEngine_.configure(this.config_.drm);
2027
2007
 
2008
+ const uri = wants.uri || '';
2009
+ const extension = shaka.media.ManifestParser.getExtension(uri);
2010
+ let mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
2011
+ if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
2012
+ mimeType = 'application/vnd.apple.mpegurl';
2013
+ }
2014
+ if (!mimeType) {
2015
+ mimeType = 'video/mp4';
2016
+ }
2017
+
2028
2018
  // TODO: Instead of feeding DrmEngine with Variants, we should refactor
2029
2019
  // DrmEngine so that it takes a minimal config derived from Variants. In
2030
2020
  // cases like this one or in removal of stored content, the details are
@@ -2045,7 +2035,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2045
2035
  createSegmentIndex: () => Promise.resolve(),
2046
2036
  segmentIndex: null,
2047
2037
  mimeType: wants.mimeType ?
2048
- shaka.util.MimeUtils.getBasicType(wants.mimeType) : 'video/mp4',
2038
+ shaka.util.MimeUtils.getBasicType(wants.mimeType) : mimeType,
2049
2039
  codecs: wants.mimeType ?
2050
2040
  shaka.util.MimeUtils.getCodecs(wants.mimeType) : '',
2051
2041
  encrypted: true,
@@ -2164,8 +2154,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2164
2154
  }
2165
2155
  if (this.video_.textTracks) {
2166
2156
  this.eventManager_.listen(this.video_.textTracks, 'addtrack', (e) => {
2167
- this.onTracksChanged_();
2168
- this.processTimedMetadataSrcEqls_(/** @type {!TrackEvent} */(e));
2157
+ const trackEvent = /** @type {!TrackEvent} */(e);
2158
+ if (trackEvent.track) {
2159
+ const track = trackEvent.track;
2160
+ goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2161
+ switch (track.kind) {
2162
+ case 'metadata':
2163
+ this.processTimedMetadataSrcEqls_(track);
2164
+ break;
2165
+ case 'chapters':
2166
+ this.processChaptersTrack_(track);
2167
+ break;
2168
+ default:
2169
+ this.onTracksChanged_();
2170
+ break;
2171
+ }
2172
+ }
2169
2173
  });
2170
2174
  this.eventManager_.listen(
2171
2175
  this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
@@ -2338,13 +2342,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2338
2342
  * We're looking for metadata tracks to process id3 tags. One of the uses is
2339
2343
  * for ad info on LIVE streams
2340
2344
  *
2341
- * @param {!TrackEvent} event
2345
+ * @param {!TextTrack} track
2342
2346
  * @private
2343
2347
  */
2344
- processTimedMetadataSrcEqls_(event) {
2345
- const track = event.track;
2346
- goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2347
-
2348
+ processTimedMetadataSrcEqls_(track) {
2348
2349
  if (track.kind != 'metadata') {
2349
2350
  return;
2350
2351
  }
@@ -2424,6 +2425,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2424
2425
  this.dispatchEvent(this.makeEvent_(eventName, data));
2425
2426
  }
2426
2427
 
2428
+ /**
2429
+ * We're looking for chapters tracks to process the chapters.
2430
+ *
2431
+ * @param {?TextTrack} track
2432
+ * @private
2433
+ */
2434
+ processChaptersTrack_(track) {
2435
+ if (!track || track.kind != 'chapters') {
2436
+ return;
2437
+ }
2438
+
2439
+ // Hidden mode is required for the cuechange event to launch correctly and
2440
+ // get the cues and the activeCues
2441
+ track.mode = 'hidden';
2442
+
2443
+ // In Safari the initial assignment does not always work, so we schedule
2444
+ // this process to be repeated several times to ensure that it has been put
2445
+ // in the correct mode.
2446
+ new shaka.util.Timer(() => {
2447
+ const chaptersTracks = this.getChaptersTracks_();
2448
+ for (const chaptersTrack of chaptersTracks) {
2449
+ chaptersTrack.mode = 'hidden';
2450
+ }
2451
+ }).tickNow().tickAfter(/* seconds= */ 0.5);
2452
+ }
2453
+
2427
2454
  /**
2428
2455
  * Take a series of variants and ensure that they only contain one type of
2429
2456
  * variant. The different options are:
@@ -2482,6 +2509,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2482
2509
  this.abrManager_.segmentDownloaded(deltaTimeMs, bytesDownloaded);
2483
2510
  }
2484
2511
  };
2512
+
2485
2513
  return new shaka.net.NetworkingEngine(onProgressUpdated_);
2486
2514
  }
2487
2515
 
@@ -2868,6 +2896,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2868
2896
  return this.config_;
2869
2897
  }
2870
2898
 
2899
+ /**
2900
+ * Returns the ratio of video length buffered compared to buffering Goal
2901
+ * @return {number}
2902
+ * @export
2903
+ */
2904
+ getBufferFullness() {
2905
+ if (this.video_) {
2906
+ const bufferedLength = this.video_.buffered.length;
2907
+ const bufferedEnd =
2908
+ bufferedLength ? this.video_.buffered.end(bufferedLength - 1) : 0;
2909
+ const bufferingGoal = this.getConfiguration().streaming.bufferingGoal;
2910
+ const lengthToBeBuffered = Math.min(this.video_.currentTime +
2911
+ bufferingGoal, this.seekRange().end);
2912
+
2913
+ if (bufferedEnd >= lengthToBeBuffered) {
2914
+ return 1;
2915
+ } else if (bufferedEnd <= this.video_.currentTime) {
2916
+ return 0;
2917
+ } else if (bufferedEnd < lengthToBeBuffered) {
2918
+ return ((bufferedEnd - this.video_.currentTime) /
2919
+ (lengthToBeBuffered - this.video_.currentTime));
2920
+ }
2921
+ }
2922
+ return 0;
2923
+ }
2924
+
2871
2925
  /**
2872
2926
  * Reset configuration to default.
2873
2927
  * @export
@@ -3078,6 +3132,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3078
3132
  return {'start': 0, 'end': 0};
3079
3133
  }
3080
3134
 
3135
+ /**
3136
+ * Go to live in a live stream.
3137
+ *
3138
+ * @export
3139
+ */
3140
+ goToLive() {
3141
+ if (this.isLive()) {
3142
+ this.video_.currentTime = this.seekRange().end;
3143
+ } else {
3144
+ shaka.log.warning('goToLive is for live streams!');
3145
+ }
3146
+ }
3147
+
3081
3148
  /**
3082
3149
  * Get the key system currently used by EME. If EME is not being used, this
3083
3150
  * will return an empty string. If the player has not loaded content, this
@@ -3102,6 +3169,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3102
3169
  return this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
3103
3170
  }
3104
3171
 
3172
+
3173
+ /**
3174
+ * Get the drm engine.
3175
+ * This method should only be used for testing. Applications SHOULD NOT
3176
+ * use this in production.
3177
+ *
3178
+ * @return {?shaka.media.DrmEngine}
3179
+ */
3180
+ getDrmEngine() {
3181
+ return this.drmEngine_;
3182
+ }
3183
+
3184
+
3105
3185
  /**
3106
3186
  * Get the next known expiration time for any EME session. If the session
3107
3187
  * never expires, this will return <code>Infinity</code>. If there are no EME
@@ -3356,9 +3436,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3356
3436
  return null;
3357
3437
  }
3358
3438
  const reference = imageStream.segmentIndex.get(referencePosition);
3439
+ const tilesLayout =
3440
+ reference.getTilesLayout() || imageStream.tilesLayout;
3359
3441
  // This expression is used to detect one or more numbers (0-9) followed
3360
3442
  // by an x and after one or more numbers (0-9)
3361
- const match = /(\d+)x(\d+)/.exec(imageStream.tilesLayout);
3443
+ const match = /(\d+)x(\d+)/.exec(tilesLayout);
3362
3444
  if (!match) {
3363
3445
  shaka.log.warning('Tiles layout does not contain a valid format ' +
3364
3446
  ' (columns x rows)');
@@ -3372,7 +3454,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3372
3454
  const height = fullImageHeight / rows;
3373
3455
  const totalImages = columns * rows;
3374
3456
  const segmentDuration = reference.trueEndTime - reference.startTime;
3375
- const thumbnailDuration = segmentDuration / totalImages;
3457
+ const thumbnailDuration =
3458
+ reference.getTileDuration() || (segmentDuration / totalImages);
3376
3459
  let thumbnailTime = reference.startTime;
3377
3460
  let positionX = 0;
3378
3461
  let positionY = 0;
@@ -3395,6 +3478,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3395
3478
  positionY = Math.floor(thumbnailPosition / columns) * height;
3396
3479
  }
3397
3480
  return {
3481
+ imageHeight: fullImageHeight,
3482
+ imageWidth: fullImageWidth,
3398
3483
  height: height,
3399
3484
  positionX: positionX,
3400
3485
  positionY: positionY,
@@ -3598,36 +3683,34 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3598
3683
  new shaka.media.PreferenceBasedCriteria(language, role || '',
3599
3684
  /* channelCount= */ 0, /* label= */ '');
3600
3685
 
3601
- if (!this.config_.abr.enabled) {
3602
- const diff = (a, b) => {
3603
- if (!a.video && !b.video) {
3604
- return 0;
3605
- } else if (!a.video || !b.video) {
3606
- return Infinity;
3607
- } else {
3608
- return Math.abs((a.video.height || 0) - (b.video.height || 0)) +
3686
+ const diff = (a, b) => {
3687
+ if (!a.video && !b.video) {
3688
+ return 0;
3689
+ } else if (!a.video || !b.video) {
3690
+ return Infinity;
3691
+ } else {
3692
+ return Math.abs((a.video.height || 0) - (b.video.height || 0)) +
3609
3693
  Math.abs((a.video.width || 0) - (b.video.width || 0));
3610
- }
3611
- };
3694
+ }
3695
+ };
3612
3696
  // Find the variant whose size is closest to the active variant. This
3613
3697
  // ensures we stay at about the same resolution when just changing the
3614
3698
  // language/role.
3615
- const active = this.streamingEngine_.getCurrentVariant();
3616
- const set =
3699
+ const active = this.streamingEngine_.getCurrentVariant();
3700
+ const set =
3617
3701
  this.currentAdaptationSetCriteria_.create(this.manifest_.variants);
3618
- let bestVariant = null;
3619
- for (const curVariant of set.values()) {
3620
- if (!bestVariant ||
3702
+ let bestVariant = null;
3703
+ for (const curVariant of set.values()) {
3704
+ if (!bestVariant ||
3621
3705
  diff(bestVariant, active) > diff(curVariant, active)) {
3622
- bestVariant = curVariant;
3623
- }
3624
- }
3625
- if (bestVariant) {
3626
- const track = shaka.util.StreamUtils.variantToTrack(bestVariant);
3627
- this.selectVariantTrack(track, /* clearBuffer= */ true);
3628
- return;
3706
+ bestVariant = curVariant;
3629
3707
  }
3630
3708
  }
3709
+ if (bestVariant) {
3710
+ const track = shaka.util.StreamUtils.variantToTrack(bestVariant);
3711
+ this.selectVariantTrack(track, /* clearBuffer= */ true);
3712
+ return;
3713
+ }
3631
3714
 
3632
3715
  // If we haven't switched yet, just use ABR to find a new track.
3633
3716
  this.chooseVariantAndSwitch_();
@@ -3771,6 +3854,55 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3771
3854
  return expected;
3772
3855
  }
3773
3856
 
3857
+ /**
3858
+ * Return a list of chapters tracks.
3859
+ *
3860
+ * @return {!Array.<shaka.extern.Track>}
3861
+ * @export
3862
+ */
3863
+ getChaptersTracks() {
3864
+ if (this.video_ && this.video_.src && this.video_.textTracks) {
3865
+ const textTracks = this.getChaptersTracks_();
3866
+ const StreamUtils = shaka.util.StreamUtils;
3867
+ return textTracks.map((text) => StreamUtils.html5TextTrackToTrack(text));
3868
+ } else {
3869
+ return [];
3870
+ }
3871
+ }
3872
+
3873
+ /**
3874
+ * This returns the list of chapters.
3875
+ *
3876
+ * @param {string} language
3877
+ * @return {!Array.<shaka.extern.Chapter>}
3878
+ * @export
3879
+ */
3880
+ getChapters(language) {
3881
+ const LanguageUtils = shaka.util.LanguageUtils;
3882
+ const inputlanguage = LanguageUtils.normalize(language);
3883
+ const chaptersTracks = this.getChaptersTracks_();
3884
+ const chaptersTracksWithLanguage = chaptersTracks
3885
+ .filter((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3886
+ if (!chaptersTracksWithLanguage || !chaptersTracksWithLanguage.length) {
3887
+ return [];
3888
+ }
3889
+ const chapters = [];
3890
+ for (const chaptersTrack of chaptersTracksWithLanguage) {
3891
+ if (chaptersTrack && chaptersTrack.cues) {
3892
+ for (const cue of chaptersTrack.cues) {
3893
+ /** @type {shaka.extern.Chapter} */
3894
+ const chapter = {
3895
+ title: cue.text,
3896
+ startTime: cue.startTime,
3897
+ endTime: cue.endTime,
3898
+ };
3899
+ chapters.push(chapter);
3900
+ }
3901
+ }
3902
+ }
3903
+ return chapters;
3904
+ }
3905
+
3774
3906
  /**
3775
3907
  * Ignore the TextTracks with the 'metadata' or 'chapters' kind, or the one
3776
3908
  * generated by the SimpleTextDisplayer.
@@ -3799,6 +3931,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3799
3931
  .filter((t) => t.kind == 'metadata');
3800
3932
  }
3801
3933
 
3934
+ /**
3935
+ * Get the TextTracks with the 'chapters' kind.
3936
+ *
3937
+ * @return {!Array.<TextTrack>}
3938
+ * @private
3939
+ */
3940
+ getChaptersTracks_() {
3941
+ goog.asserts.assert(this.video_.textTracks,
3942
+ 'TextTracks should be valid.');
3943
+ return Array.from(this.video_.textTracks)
3944
+ .filter((t) => t.kind == 'chapters');
3945
+ }
3946
+
3802
3947
  /**
3803
3948
  * Enable or disable the text displayer. If the player is in an unloaded
3804
3949
  * state, the request will be applied next time content is loaded.
@@ -4100,16 +4245,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4100
4245
  if (!mimeType) {
4101
4246
  // Try using the uri extension.
4102
4247
  const extension = shaka.media.ManifestParser.getExtension(uri);
4103
- mimeType = {
4104
- 'sbv': 'text/x-subviewer',
4105
- 'srt': 'text/srt',
4106
- 'vtt': 'text/vtt',
4107
- 'webvtt': 'text/vtt',
4108
- 'ttml': 'application/ttml+xml',
4109
- 'lrc': 'application/x-subtitle-lrc',
4110
- 'ssa': 'text/x-ssa',
4111
- 'ass': 'text/x-ssa',
4112
- }[extension];
4248
+ mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
4113
4249
 
4114
4250
  if (!mimeType) {
4115
4251
  shaka.log.error(
@@ -4257,74 +4393,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4257
4393
  }
4258
4394
 
4259
4395
  if (!mimeType) {
4260
- // Try using the uri extension.
4261
- const extension = shaka.media.ManifestParser.getExtension(uri);
4262
- mimeType = {
4263
- 'sbv': 'text/x-subviewer',
4264
- 'srt': 'text/srt',
4265
- 'vtt': 'text/vtt',
4266
- 'webvtt': 'text/vtt',
4267
- 'ttml': 'application/ttml+xml',
4268
- 'lrc': 'application/x-subtitle-lrc',
4269
- 'ssa': 'text/x-ssa',
4270
- 'ass': 'text/x-ssa',
4271
- }[extension];
4272
-
4273
- if (!mimeType) {
4274
- try {
4275
- goog.asserts.assert(
4276
- this.networkingEngine_, 'Need networking engine.');
4277
- // eslint-disable-next-line require-atomic-updates
4278
- mimeType = await shaka.media.ManifestParser.getMimeType(uri,
4279
- this.networkingEngine_,
4280
- this.config_.streaming.retryParameters);
4281
- } catch (error) {}
4282
- }
4396
+ mimeType = await this.getTextMimetype_(uri);
4397
+ }
4283
4398
 
4284
- if (!mimeType) {
4285
- shaka.log.error(
4286
- 'The mimeType has not been provided and it could not be deduced ' +
4287
- 'from its extension.');
4288
- throw new shaka.util.Error(
4289
- shaka.util.Error.Severity.RECOVERABLE,
4290
- shaka.util.Error.Category.TEXT,
4291
- shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
4292
- extension);
4293
- }
4399
+ let adCuePoints = [];
4400
+ if (this.adManager_) {
4401
+ try {
4402
+ adCuePoints = this.adManager_.getServerSideCuePoints();
4403
+ } catch (error) {}
4294
4404
  }
4295
4405
 
4296
4406
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4297
- if (mimeType != 'text/vtt') {
4298
- goog.asserts.assert(
4299
- this.networkingEngine_, 'Need networking engine.');
4300
- const data = await this.getTextData_(uri,
4301
- this.networkingEngine_,
4302
- this.config_.streaming.retryParameters);
4303
- const vvtText = this.convertToWebVTT_(data, mimeType);
4304
- const blob = new Blob([vvtText], {type: 'text/vtt'});
4305
- uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4306
- mimeType = 'text/vtt';
4307
- }
4308
4407
  if (forced) {
4309
4408
  // See: https://github.com/whatwg/html/issues/4472
4310
4409
  kind = 'forced';
4311
4410
  }
4312
- const trackElement =
4313
- /** @type {!HTMLTrackElement} */(document.createElement('track'));
4314
- trackElement.src = uri;
4315
- trackElement.label = label || '';
4316
- trackElement.kind = kind;
4317
- trackElement.srclang = language;
4318
- // Because we're pulling in the text track file via Javascript, the
4319
- // same-origin policy applies. If you'd like to have a player served
4320
- // from one domain, but the text track served from another, you'll
4321
- // need to enable CORS in order to do so. In addition to enabling CORS
4322
- // on the server serving the text tracks, you will need to add the
4323
- // crossorigin attribute to the video element itself.
4324
- if (!this.video_.getAttribute('crossorigin')) {
4325
- this.video_.setAttribute('crossorigin', 'anonymous');
4326
- }
4327
- this.video_.appendChild(trackElement);
4411
+ await this.addSrcTrackElement_(uri, language, kind, mimeType, label || '',
4412
+ adCuePoints);
4328
4413
  const textTracks = this.getTextTracks();
4329
4414
  const srcTrack = textTracks.find((t) => {
4330
4415
  return t.language == language &&
@@ -4354,6 +4439,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4354
4439
  shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM);
4355
4440
  }
4356
4441
 
4442
+ if (adCuePoints.length) {
4443
+ goog.asserts.assert(
4444
+ this.networkingEngine_, 'Need networking engine.');
4445
+ const data = await this.getTextData_(uri,
4446
+ this.networkingEngine_,
4447
+ this.config_.streaming.retryParameters);
4448
+ const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints);
4449
+ const blob = new Blob([vvtText], {type: 'text/vtt'});
4450
+ uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4451
+ mimeType = 'text/vtt';
4452
+ }
4453
+
4357
4454
  /** @type {shaka.extern.Stream} */
4358
4455
  const stream = {
4359
4456
  id: this.nextExternalStreamId_++,
@@ -4399,6 +4496,138 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4399
4496
  return shaka.util.StreamUtils.textStreamToTrack(stream);
4400
4497
  }
4401
4498
 
4499
+ /**
4500
+ * Adds the given chapters track to the loaded manifest. <code>load()</code>
4501
+ * must resolve before calling. The presentation must have a duration.
4502
+ *
4503
+ * This returns the created track.
4504
+ *
4505
+ * @param {string} uri
4506
+ * @param {string} language
4507
+ * @param {string=} mimeType
4508
+ * @return {!Promise.<shaka.extern.Track>}
4509
+ * @export
4510
+ */
4511
+ async addChaptersTrack(uri, language, mimeType) {
4512
+ if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE &&
4513
+ this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) {
4514
+ shaka.log.error(
4515
+ 'Must call load() and wait for it to resolve before adding ' +
4516
+ 'chapters tracks.');
4517
+ throw new shaka.util.Error(
4518
+ shaka.util.Error.Severity.RECOVERABLE,
4519
+ shaka.util.Error.Category.PLAYER,
4520
+ shaka.util.Error.Code.CONTENT_NOT_LOADED);
4521
+ }
4522
+
4523
+ if (!mimeType) {
4524
+ mimeType = await this.getTextMimetype_(uri);
4525
+ }
4526
+ let adCuePoints = [];
4527
+ if (this.adManager_) {
4528
+ try {
4529
+ adCuePoints = this.adManager_.getServerSideCuePoints();
4530
+ } catch (error) {}
4531
+ }
4532
+ await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4533
+ mimeType, /* label= */ '', adCuePoints);
4534
+ const chaptersTracks = this.getChaptersTracks();
4535
+ const chaptersTrack = chaptersTracks.find((t) => {
4536
+ return t.language == language;
4537
+ });
4538
+ if (chaptersTrack) {
4539
+ const html5ChaptersTracks = this.getChaptersTracks_();
4540
+ for (const html5ChaptersTrack of html5ChaptersTracks) {
4541
+ this.processChaptersTrack_(html5ChaptersTrack);
4542
+ }
4543
+ return chaptersTrack;
4544
+ }
4545
+ // This should not happen, but there are browser implementations that may
4546
+ // not support the Track element.
4547
+ shaka.log.error('Cannot add this text when loaded with src=');
4548
+ throw new shaka.util.Error(
4549
+ shaka.util.Error.Severity.RECOVERABLE,
4550
+ shaka.util.Error.Category.TEXT,
4551
+ shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_SRC_EQUALS);
4552
+ }
4553
+
4554
+ /**
4555
+ * @param {string} uri
4556
+ * @return {!Promise.<string>}
4557
+ * @private
4558
+ */
4559
+ async getTextMimetype_(uri) {
4560
+ // Try using the uri extension.
4561
+ const extension = shaka.media.ManifestParser.getExtension(uri);
4562
+ let mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
4563
+
4564
+ if (mimeType) {
4565
+ return mimeType;
4566
+ }
4567
+
4568
+ try {
4569
+ goog.asserts.assert(
4570
+ this.networkingEngine_, 'Need networking engine.');
4571
+ // eslint-disable-next-line require-atomic-updates
4572
+ mimeType = await shaka.media.ManifestParser.getMimeType(uri,
4573
+ this.networkingEngine_,
4574
+ this.config_.streaming.retryParameters);
4575
+ } catch (error) {}
4576
+
4577
+ if (mimeType) {
4578
+ return mimeType;
4579
+ }
4580
+
4581
+ shaka.log.error(
4582
+ 'The mimeType has not been provided and it could not be deduced ' +
4583
+ 'from its extension.');
4584
+ throw new shaka.util.Error(
4585
+ shaka.util.Error.Severity.RECOVERABLE,
4586
+ shaka.util.Error.Category.TEXT,
4587
+ shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
4588
+ extension);
4589
+ }
4590
+
4591
+ /**
4592
+ * @param {string} uri
4593
+ * @param {string} language
4594
+ * @param {string} kind
4595
+ * @param {string} mimeType
4596
+ * @param {string} label
4597
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4598
+ * @private
4599
+ */
4600
+ async addSrcTrackElement_(uri, language, kind, mimeType, label,
4601
+ adCuePoints) {
4602
+ if (mimeType != 'text/vtt' || adCuePoints.length) {
4603
+ goog.asserts.assert(
4604
+ this.networkingEngine_, 'Need networking engine.');
4605
+ const data = await this.getTextData_(uri,
4606
+ this.networkingEngine_,
4607
+ this.config_.streaming.retryParameters);
4608
+ const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints);
4609
+ const blob = new Blob([vvtText], {type: 'text/vtt'});
4610
+ uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4611
+ mimeType = 'text/vtt';
4612
+ }
4613
+ const trackElement =
4614
+ /** @type {!HTMLTrackElement} */(document.createElement('track'));
4615
+ trackElement.src = uri;
4616
+ trackElement.label = label;
4617
+ trackElement.kind = kind;
4618
+ trackElement.srclang = language;
4619
+ // Because we're pulling in the text track file via Javascript, the
4620
+ // same-origin policy applies. If you'd like to have a player served
4621
+ // from one domain, but the text track served from another, you'll
4622
+ // need to enable CORS in order to do so. In addition to enabling CORS
4623
+ // on the server serving the text tracks, you will need to add the
4624
+ // crossorigin attribute to the video element itself.
4625
+ if (!this.video_.getAttribute('crossorigin')) {
4626
+ this.video_.setAttribute('crossorigin', 'anonymous');
4627
+ }
4628
+ this.video_.appendChild(trackElement);
4629
+ }
4630
+
4402
4631
  /**
4403
4632
  * @param {string} uri
4404
4633
  * @param {!shaka.net.NetworkingEngine} netEngine
@@ -4423,10 +4652,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4423
4652
  *
4424
4653
  * @param {BufferSource} buffer
4425
4654
  * @param {string} mimeType
4655
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4426
4656
  * @return {string}
4427
4657
  * @private
4428
4658
  */
4429
- convertToWebVTT_(buffer, mimeType) {
4659
+ convertToWebVTT_(buffer, mimeType, adCuePoints) {
4430
4660
  const factory = shaka.text.TextEngine.findParser(mimeType);
4431
4661
  if (factory) {
4432
4662
  const obj = factory();
@@ -4437,7 +4667,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4437
4667
  };
4438
4668
  const data = shaka.util.BufferUtils.toUint8(buffer);
4439
4669
  const cues = obj.parseMedia(data, time);
4440
- return shaka.text.WebVttGenerator.convert(cues);
4670
+ return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
4441
4671
  }
4442
4672
  throw new shaka.util.Error(
4443
4673
  shaka.util.Error.Severity.CRITICAL,
@@ -4679,8 +4909,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4679
4909
  this.streamingEngine_.getCurrentVariant() : null;
4680
4910
 
4681
4911
  await shaka.util.StreamUtils.filterManifest(
4682
- this.drmEngine_, currentVariant, manifest,
4683
- this.config_.useMediaCapabilities);
4912
+ this.drmEngine_, currentVariant, manifest);
4684
4913
  this.checkPlayableVariants_(manifest);
4685
4914
  }
4686
4915
 
@@ -4984,9 +5213,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4984
5213
  if (chosenVariant) {
4985
5214
  this.switchVariant_(chosenVariant, /* fromAdaptation= */ true,
4986
5215
  /* clearBuffers= */ true, /* safeMargin= */ 0);
4987
- // Send an adaptation event so that the UI can show the new
4988
- // language/tracks.
4989
- this.onAdaptation_();
4990
5216
  }
4991
5217
  }
4992
5218
 
@@ -5017,8 +5243,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5017
5243
  oldTrack = shaka.util.StreamUtils.variantToTrack(currentVariant);
5018
5244
  }
5019
5245
  const newTrack = shaka.util.StreamUtils.variantToTrack(variant);
5020
- // Dispatch a 'variantchanged' event
5021
- this.onVariantChanged_(oldTrack, newTrack);
5246
+
5247
+ if (fromAdaptation) {
5248
+ // Dispatch an 'adaptation' event
5249
+ this.onAdaptation_(oldTrack, newTrack);
5250
+ } else {
5251
+ // Dispatch a 'variantchanged' event
5252
+ this.onVariantChanged_(oldTrack, newTrack);
5253
+ }
5022
5254
  }
5023
5255
 
5024
5256
  /**
@@ -5162,19 +5394,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5162
5394
  return;
5163
5395
  }
5164
5396
 
5165
- this.addVariantToSwitchHistory_(variant, /* fromAdaptation= */ true);
5166
- this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
5167
- this.onAdaptation_();
5397
+ this.switchVariant_(variant, /* fromAdaptation= */ true,
5398
+ clearBuffer, safeMargin);
5168
5399
  }
5169
5400
 
5170
5401
  /**
5171
5402
  * Dispatches an 'adaptation' event.
5403
+ * @param {?shaka.extern.Track} from
5404
+ * @param {shaka.extern.Track} to
5172
5405
  * @private
5173
5406
  */
5174
- onAdaptation_() {
5407
+ onAdaptation_(from, to) {
5175
5408
  // Delay the 'adaptation' event so that StreamingEngine has time to absorb
5176
5409
  // the changes before the user tries to query it.
5177
- const event = this.makeEvent_(shaka.Player.EventName.Adaptation);
5410
+ const data = new Map()
5411
+ .set('oldTrack', from)
5412
+ .set('newTrack', to);
5413
+ const event = this.makeEvent_(shaka.Player.EventName.Adaptation, data);
5178
5414
  this.delayDispatchEvent_(event);
5179
5415
  }
5180
5416
 
@@ -6102,7 +6338,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6102
6338
  * @define {string} A version number taken from git at compile time.
6103
6339
  * @export
6104
6340
  */
6105
- shaka.Player.version = 'v3.1.5-uncompiled';
6341
+ shaka.Player.version = 'v3.2.3-uncompiled';
6106
6342
 
6107
6343
  // Initialize the deprecation system using the version string we just set
6108
6344
  // on the player.
@@ -6129,6 +6365,47 @@ shaka.Player.supportPlugins_ = {};
6129
6365
  shaka.Player.adManagerFactory_ = null;
6130
6366
 
6131
6367
 
6368
+ /**
6369
+ * @const {!Object.<string, string>}
6370
+ * @private
6371
+ */
6372
+ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
6373
+ 'mp4': 'video/mp4',
6374
+ 'm4v': 'video/mp4',
6375
+ 'm4a': 'audio/mp4',
6376
+ 'webm': 'video/webm',
6377
+ 'weba': 'audio/webm',
6378
+ 'mkv': 'video/webm', // Chromium browsers supports it.
6379
+ 'ts': 'video/mp2t',
6380
+ 'ogv': 'video/ogg',
6381
+ 'ogg': 'audio/ogg',
6382
+ 'mpg': 'video/mpeg',
6383
+ 'mpeg': 'video/mpeg',
6384
+ 'm3u8': 'application/x-mpegurl',
6385
+ 'mpd': 'application/dash+xml',
6386
+ 'mp3': 'audio/mpeg',
6387
+ 'aac': 'audio/aac',
6388
+ 'flac': 'audio/flac',
6389
+ 'wav': 'audio/wav',
6390
+ };
6391
+
6392
+
6393
+ /**
6394
+ * @const {!Object.<string, string>}
6395
+ * @private
6396
+ */
6397
+ shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
6398
+ 'sbv': 'text/x-subviewer',
6399
+ 'srt': 'text/srt',
6400
+ 'vtt': 'text/vtt',
6401
+ 'webvtt': 'text/vtt',
6402
+ 'ttml': 'application/ttml+xml',
6403
+ 'lrc': 'application/x-subtitle-lrc',
6404
+ 'ssa': 'text/x-ssa',
6405
+ 'ass': 'text/x-ssa',
6406
+ };
6407
+
6408
+
6132
6409
  /**
6133
6410
  * @const {string}
6134
6411
  */