shaka-player 3.2.5 → 3.2.8

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 (72) hide show
  1. package/.github/workflows/build-and-test.yaml +0 -1
  2. package/.github/workflows/release-please.yaml +6 -3
  3. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +4 -4
  6. package/build/README.md +2 -2
  7. package/build/test.py +7 -1
  8. package/demo/demo.less +0 -1
  9. package/dist/controls.css +2 -2
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.css +1 -1
  12. package/dist/demo.css.map +1 -1
  13. package/dist/deps.js +1 -1
  14. package/dist/locales.js +29 -29
  15. package/dist/shaka-player.compiled.d.ts +1 -0
  16. package/dist/shaka-player.compiled.debug.d.ts +1 -0
  17. package/dist/shaka-player.compiled.debug.externs.js +1 -0
  18. package/dist/shaka-player.compiled.debug.js +217 -213
  19. package/dist/shaka-player.compiled.debug.map +3 -3
  20. package/dist/shaka-player.compiled.externs.js +1 -0
  21. package/dist/shaka-player.compiled.js +767 -763
  22. package/dist/shaka-player.compiled.map +3 -3
  23. package/dist/shaka-player.ui.d.ts +1 -0
  24. package/dist/shaka-player.ui.debug.d.ts +1 -0
  25. package/dist/shaka-player.ui.debug.externs.js +1 -0
  26. package/dist/shaka-player.ui.debug.js +139 -135
  27. package/dist/shaka-player.ui.debug.map +3 -3
  28. package/dist/shaka-player.ui.externs.js +1 -0
  29. package/dist/shaka-player.ui.js +893 -888
  30. package/dist/shaka-player.ui.map +3 -3
  31. package/karma.conf.js +4 -0
  32. package/lib/abr/simple_abr_manager.js +2 -1
  33. package/lib/dash/dash_parser.js +2 -3
  34. package/lib/hls/hls_parser.js +37 -14
  35. package/lib/media/drm_engine.js +3 -0
  36. package/lib/media/media_source_engine.js +7 -1
  37. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  38. package/lib/player.js +165 -107
  39. package/lib/util/config_utils.js +25 -0
  40. package/lib/util/error.js +7 -0
  41. package/lib/util/manifest_parser_utils.js +1 -1
  42. package/lib/util/mime_utils.js +62 -6
  43. package/lib/util/periods.js +5 -4
  44. package/lib/util/player_configuration.js +9 -14
  45. package/lib/util/stream_utils.js +4 -2
  46. package/package.json +2 -2
  47. package/test/cast/cast_utils_unit.js +1 -0
  48. package/test/dash/dash_parser_manifest_unit.js +0 -24
  49. package/test/hls/hls_live_unit.js +86 -7
  50. package/test/hls/manifest_text_parser_unit.js +19 -0
  51. package/test/media/media_source_engine_integration.js +1 -0
  52. package/test/media/media_source_engine_unit.js +4 -4
  53. package/test/media/streaming_engine_integration.js +2 -0
  54. package/test/offline/indexeddb_storage_unit.js +1 -1
  55. package/test/offline/storage_compatibility_unit.js +1 -1
  56. package/test/player_external.js +7 -1
  57. package/test/player_integration.js +6 -10
  58. package/test/player_src_equals_integration.js +81 -83
  59. package/test/player_unit.js +98 -1
  60. package/test/test/boot.js +9 -0
  61. package/test/test/externs/filters.js +4 -0
  62. package/test/test/util/simple_fakes.js +31 -2
  63. package/test/util/mime_utils_unit.js +54 -0
  64. package/test/util/periods_unit.js +79 -0
  65. package/ui/less/ad_controls.less +2 -2
  66. package/ui/less/buttons.less +1 -1
  67. package/ui/less/containers.less +5 -5
  68. package/ui/less/general.less +1 -1
  69. package/ui/less/range_elements.less +3 -2
  70. package/ui/seek_bar.js +2 -1
  71. package/.github/pull_request_template.md +0 -38
  72. package/docs/tutorials/gsoc-ideas-list.md +0 -3
package/lib/player.js CHANGED
@@ -397,8 +397,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
397
397
  */
398
398
  this.isTextVisible_ = false;
399
399
 
400
- /** @private {shaka.util.EventManager} */
401
- this.eventManager_ = new shaka.util.EventManager();
400
+ /**
401
+ * For listeners scoped to the lifetime of the Player instance.
402
+ * @private {shaka.util.EventManager}
403
+ */
404
+ this.globalEventManager_ = new shaka.util.EventManager();
405
+
406
+ /**
407
+ * For listeners scoped to the lifetime of the media element attachment.
408
+ * @private {shaka.util.EventManager}
409
+ */
410
+ this.attachEventManager_ = new shaka.util.EventManager();
411
+
412
+ /**
413
+ * For listeners scoped to the lifetime of the loaded content.
414
+ * @private {shaka.util.EventManager}
415
+ */
416
+ this.loadEventManager_ = new shaka.util.EventManager();
402
417
 
403
418
  /** @private {shaka.net.NetworkingEngine} */
404
419
  this.networkingEngine_ = null;
@@ -524,7 +539,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
524
539
 
525
540
  // If the browser comes back online after being offline, then try to play
526
541
  // again.
527
- this.eventManager_.listen(window, 'online', () => {
542
+ this.globalEventManager_.listen(window, 'online', () => {
528
543
  this.retryStreaming();
529
544
  });
530
545
 
@@ -712,10 +727,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
712
727
  });
713
728
  await this.walker_.destroy();
714
729
 
715
- // Tear-down the event manager to ensure messages stop moving around.
716
- if (this.eventManager_) {
717
- this.eventManager_.release();
718
- this.eventManager_ = null;
730
+ // Tear-down the event managers to ensure handlers stop firing.
731
+ if (this.globalEventManager_) {
732
+ this.globalEventManager_.release();
733
+ this.globalEventManager_ = null;
734
+ }
735
+ if (this.attachEventManager_) {
736
+ this.attachEventManager_.release();
737
+ this.attachEventManager_ = null;
738
+ }
739
+ if (this.loadEventManager_) {
740
+ this.loadEventManager_.release();
741
+ this.loadEventManager_ = null;
719
742
  }
720
743
 
721
744
  this.abrManagerFactory_ = null;
@@ -1239,7 +1262,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1239
1262
  has.mediaElement = wants.mediaElement;
1240
1263
 
1241
1264
  const onError = (error) => this.onVideoError_(error);
1242
- this.eventManager_.listen(has.mediaElement, 'error', onError);
1265
+ this.attachEventManager_.listen(has.mediaElement, 'error', onError);
1243
1266
  }
1244
1267
 
1245
1268
  this.video_ = has.mediaElement;
@@ -1266,10 +1289,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1266
1289
  * @private
1267
1290
  */
1268
1291
  onDetach_(has, wants) {
1269
- // If we are going from "detached" to "detached" we wouldn't have
1292
+ // If we were going from "detached" to "detached" we wouldn't have
1270
1293
  // a media element to detach from.
1271
1294
  if (has.mediaElement) {
1272
- this.eventManager_.unlisten(has.mediaElement, 'error');
1295
+ this.attachEventManager_.removeAll();
1273
1296
  has.mediaElement = null;
1274
1297
  }
1275
1298
 
@@ -1327,11 +1350,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1327
1350
  // In most cases we should have a media element. The one exception would
1328
1351
  // be if there was an error and we, by chance, did not have a media element.
1329
1352
  if (has.mediaElement) {
1330
- this.eventManager_.unlisten(has.mediaElement, 'loadedmetadata');
1331
- this.eventManager_.unlisten(has.mediaElement, 'playing');
1332
- this.eventManager_.unlisten(has.mediaElement, 'pause');
1333
- this.eventManager_.unlisten(has.mediaElement, 'ended');
1334
- this.eventManager_.unlisten(has.mediaElement, 'ratechange');
1353
+ this.loadEventManager_.removeAll();
1335
1354
  }
1336
1355
 
1337
1356
  // Some observers use some playback components, shutting down the observers
@@ -1795,18 +1814,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1795
1814
  this.assetUri_ = assetUri;
1796
1815
 
1797
1816
  this.playRateController_ = new shaka.media.PlayRateController({
1798
- getRate: () => has.mediaElement.playbackRate,
1799
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
1800
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
1801
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
1817
+ getRate: () => mediaElement.playbackRate,
1818
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
1819
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
1820
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
1802
1821
  });
1803
1822
 
1804
1823
  const updateStateHistory = () => this.updateStateHistory_();
1805
1824
  const onRateChange = () => this.onRateChange_();
1806
- this.eventManager_.listen(mediaElement, 'playing', updateStateHistory);
1807
- this.eventManager_.listen(mediaElement, 'pause', updateStateHistory);
1808
- this.eventManager_.listen(mediaElement, 'ended', updateStateHistory);
1809
- this.eventManager_.listen(mediaElement, 'ratechange', onRateChange);
1825
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
1826
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
1827
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
1828
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
1810
1829
 
1811
1830
  const abrFactory = this.config_.abrFactory;
1812
1831
  if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
@@ -1869,6 +1888,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1869
1888
  // "streaming" so that they can access internal information.
1870
1889
  this.loadMode_ = shaka.Player.LoadMode.MEDIA_SOURCE;
1871
1890
 
1891
+ if (mediaElement.textTracks) {
1892
+ this.loadEventManager_.listen(
1893
+ mediaElement.textTracks, 'addtrack', (e) => {
1894
+ const trackEvent = /** @type {!TrackEvent} */(e);
1895
+ if (trackEvent.track) {
1896
+ const track = trackEvent.track;
1897
+ goog.asserts.assert(
1898
+ track instanceof TextTrack, 'Wrong track type!');
1899
+
1900
+ switch (track.kind) {
1901
+ case 'chapters':
1902
+ this.activateChaptersTrack_(track);
1903
+ break;
1904
+ }
1905
+ }
1906
+ });
1907
+ }
1908
+
1872
1909
  // The event must be fired after we filter by restrictions but before the
1873
1910
  // active stream is picked to allow those listening for the "streaming"
1874
1911
  // event to make changes before streaming starts.
@@ -1955,7 +1992,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1955
1992
  }
1956
1993
 
1957
1994
  // Wait for the 'loadedmetadata' event to measure load() latency.
1958
- this.eventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
1995
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
1959
1996
  const now = Date.now() / 1000;
1960
1997
  const delta = now - wants.startTimeOfLoad;
1961
1998
  this.stats_.setLoadLatency(delta);
@@ -2108,11 +2145,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2108
2145
  // Save the uri so that it can be used outside of the load-graph.
2109
2146
  this.assetUri_ = has.uri;
2110
2147
 
2111
- this.playhead_ = new shaka.media.SrcEqualsPlayhead(has.mediaElement);
2148
+ const mediaElement = has.mediaElement;
2149
+
2150
+ this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
2112
2151
 
2113
- // This flag is used below in the language preference setup and
2114
- // track-management to check if this load was canceled before the necessary
2115
- // events fired.
2152
+ // This flag is used below in the language preference setup to check if
2153
+ // this load was canceled before the necessary awaits completed.
2116
2154
  let unloaded = false;
2117
2155
  this.cleanupOnUnload_.push(() => {
2118
2156
  unloaded = true;
@@ -2123,10 +2161,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2123
2161
  }
2124
2162
 
2125
2163
  this.playRateController_ = new shaka.media.PlayRateController({
2126
- getRate: () => has.mediaElement.playbackRate,
2127
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
2128
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
2129
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
2164
+ getRate: () => mediaElement.playbackRate,
2165
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
2166
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
2167
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
2130
2168
  });
2131
2169
 
2132
2170
  // We need to start the buffer management code near the end because it will
@@ -2138,17 +2176,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2138
2176
  // Add all media element listeners.
2139
2177
  const updateStateHistory = () => this.updateStateHistory_();
2140
2178
  const onRateChange = () => this.onRateChange_();
2141
- this.eventManager_.listen(has.mediaElement, 'playing', updateStateHistory);
2142
- this.eventManager_.listen(has.mediaElement, 'pause', updateStateHistory);
2143
- this.eventManager_.listen(has.mediaElement, 'ended', updateStateHistory);
2144
- this.eventManager_.listen(has.mediaElement, 'ratechange', onRateChange);
2179
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
2180
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
2181
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
2182
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
2145
2183
 
2146
2184
  // Wait for the 'loadedmetadata' event to measure load() latency, but only
2147
2185
  // if preload is set in a way that would result in this event firing
2148
2186
  // automatically.
2149
2187
  // See https://github.com/shaka-project/shaka-player/issues/2483
2150
- if (this.video_.preload != 'none') {
2151
- this.eventManager_.listenOnce(this.video_, 'loadedmetadata', () => {
2188
+ if (mediaElement.preload != 'none') {
2189
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2152
2190
  const now = Date.now() / 1000;
2153
2191
  const delta = now - wants.startTimeOfLoad;
2154
2192
  this.stats_.setLoadLatency(delta);
@@ -2158,56 +2196,59 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2158
2196
  // The audio tracks are only available on Safari at the moment, but this
2159
2197
  // drives the tracks API for Safari's native HLS. So when they change,
2160
2198
  // fire the corresponding Shaka Player event.
2161
- if (this.video_.audioTracks) {
2162
- this.eventManager_.listen(
2163
- this.video_.audioTracks, 'addtrack', () => this.onTracksChanged_());
2164
- this.eventManager_.listen(
2165
- this.video_.audioTracks, 'removetrack',
2199
+ if (mediaElement.audioTracks) {
2200
+ this.loadEventManager_.listen(
2201
+ mediaElement.audioTracks, 'addtrack', () => this.onTracksChanged_());
2202
+ this.loadEventManager_.listen(
2203
+ mediaElement.audioTracks, 'removetrack',
2166
2204
  () => this.onTracksChanged_());
2167
- this.eventManager_.listen(
2168
- this.video_.audioTracks, 'change', () => this.onTracksChanged_());
2169
- }
2170
- if (this.video_.textTracks) {
2171
- this.eventManager_.listen(this.video_.textTracks, 'addtrack', (e) => {
2172
- // If we have moved on to another piece of content while waiting for
2173
- // the above event, we should not process tracks here.
2174
- if (unloaded) {
2175
- return;
2176
- }
2205
+ this.loadEventManager_.listen(
2206
+ mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
2207
+ }
2208
+
2209
+ if (mediaElement.textTracks) {
2210
+ this.loadEventManager_.listen(
2211
+ mediaElement.textTracks, 'addtrack', (e) => {
2212
+ const trackEvent = /** @type {!TrackEvent} */(e);
2213
+ if (trackEvent.track) {
2214
+ const track = trackEvent.track;
2215
+ goog.asserts.assert(
2216
+ track instanceof TextTrack, 'Wrong track type!');
2217
+
2218
+ switch (track.kind) {
2219
+ case 'metadata':
2220
+ this.processTimedMetadataSrcEqls_(track);
2221
+ break;
2222
+
2223
+ case 'chapters':
2224
+ this.activateChaptersTrack_(track);
2225
+ break;
2226
+
2227
+ default:
2228
+ this.onTracksChanged_();
2229
+ break;
2230
+ }
2231
+ }
2232
+ });
2177
2233
 
2178
- const trackEvent = /** @type {!TrackEvent} */(e);
2179
- if (trackEvent.track) {
2180
- const track = trackEvent.track;
2181
- goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2182
- switch (track.kind) {
2183
- case 'metadata':
2184
- this.processTimedMetadataSrcEqls_(track);
2185
- break;
2186
- case 'chapters':
2187
- this.processChaptersTrack_(track);
2188
- break;
2189
- default:
2190
- this.onTracksChanged_();
2191
- break;
2192
- }
2193
- }
2194
- });
2195
- this.eventManager_.listen(
2196
- this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
2197
- this.eventManager_.listen(
2198
- this.video_.textTracks, 'change', () => this.onTracksChanged_());
2234
+ this.loadEventManager_.listen(
2235
+ mediaElement.textTracks, 'removetrack',
2236
+ () => this.onTracksChanged_());
2237
+ this.loadEventManager_.listen(
2238
+ mediaElement.textTracks, 'change',
2239
+ () => this.onTracksChanged_());
2199
2240
  }
2200
2241
 
2201
2242
  // By setting |src| we are done "loading" with src=. We don't need to set
2202
2243
  // the current time because |playhead| will do that for us.
2203
- has.mediaElement.src = has.uri;
2244
+ mediaElement.src = has.uri;
2204
2245
 
2205
2246
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2206
2247
  // no matter the value of the preload attribute. This is harmful on some
2207
2248
  // other platforms by triggering unbounded loading of media data, but is
2208
2249
  // necessary here.
2209
2250
  if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
2210
- has.mediaElement.load();
2251
+ mediaElement.load();
2211
2252
  }
2212
2253
 
2213
2254
  // Set the load mode last so that we know that all our components are
@@ -2223,24 +2264,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2223
2264
  // wait for the full data, that won't happen on Safari until the play button
2224
2265
  // is hit.
2225
2266
  const fullyLoaded = new shaka.util.PublicPromise();
2226
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2267
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2227
2268
  HTMLMediaElement.HAVE_METADATA,
2228
- this.eventManager_,
2269
+ this.loadEventManager_,
2229
2270
  () => {
2230
2271
  fullyLoaded.resolve();
2231
2272
  });
2232
2273
 
2233
2274
  // We can't switch to preferred languages, though, until the data is loaded.
2234
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2275
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2235
2276
  HTMLMediaElement.HAVE_CURRENT_DATA,
2236
- this.eventManager_,
2277
+ this.loadEventManager_,
2237
2278
  async () => {
2238
- // If we have moved on to another piece of content while waiting for
2239
- // the above event, we should not change tracks here.
2240
- if (unloaded) {
2241
- return;
2242
- }
2243
-
2244
2279
  this.setupPreferredAudioOnSrc_();
2245
2280
 
2246
2281
  // Applying the text preference too soon can result in it being
@@ -2248,8 +2283,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2248
2283
  const textTracks = this.getFilteredTextTracks_();
2249
2284
  if (!textTracks.find((t) => t.mode != 'disabled')) {
2250
2285
  await new Promise((resolve) => {
2251
- this.eventManager_.listenOnce(
2252
- this.video_.textTracks, 'change', resolve);
2286
+ this.loadEventManager_.listenOnce(
2287
+ mediaElement.textTracks, 'change', resolve);
2288
+
2253
2289
  // We expect the event to fire because it does on Safari.
2254
2290
  // But in case it doesn't on some other platform or future
2255
2291
  // version, move on in 1 second no matter what. This keeps the
@@ -2268,10 +2304,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2268
2304
  this.setupPreferredTextOnSrc_();
2269
2305
  });
2270
2306
 
2271
- if (this.video_.error) {
2307
+ if (mediaElement.error) {
2272
2308
  // Already failed!
2273
2309
  fullyLoaded.reject(this.videoErrorToShakaError_());
2274
- } else if (this.video_.preload == 'none') {
2310
+ } else if (mediaElement.preload == 'none') {
2275
2311
  shaka.log.alwaysWarn(
2276
2312
  'With <video preload="none">, the browser will not load anything ' +
2277
2313
  'until play() is called. We are unable to measure load latency in ' +
@@ -2282,7 +2318,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2282
2318
  fullyLoaded.resolve();
2283
2319
  }
2284
2320
 
2285
- this.eventManager_.listenOnce(this.video_, 'error', () => {
2321
+ this.loadEventManager_.listenOnce(mediaElement, 'error', () => {
2286
2322
  fullyLoaded.reject(this.videoErrorToShakaError_());
2287
2323
  });
2288
2324
 
@@ -2366,7 +2402,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2366
2402
 
2367
2403
  // Hidden mode is required for the cuechange event to launch correctly
2368
2404
  track.mode = 'hidden';
2369
- this.eventManager_.listen(track, 'cuechange', () => {
2405
+ this.loadEventManager_.listen(track, 'cuechange', () => {
2370
2406
  if (!track.activeCues) {
2371
2407
  return;
2372
2408
  }
@@ -2444,12 +2480,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2444
2480
  }
2445
2481
 
2446
2482
  /**
2447
- * We're looking for chapters tracks to process the chapters.
2483
+ * Set the mode on a chapters track so that it loads.
2448
2484
  *
2449
2485
  * @param {?TextTrack} track
2450
2486
  * @private
2451
2487
  */
2452
- processChaptersTrack_(track) {
2488
+ activateChaptersTrack_(track) {
2453
2489
  if (!track || track.kind != 'chapters') {
2454
2490
  return;
2455
2491
  }
@@ -2462,10 +2498,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2462
2498
  // this process to be repeated several times to ensure that it has been put
2463
2499
  // in the correct mode.
2464
2500
  const timer = new shaka.util.Timer(() => {
2465
- const chaptersTracks = this.getChaptersTracks_();
2466
- for (const chaptersTrack of chaptersTracks) {
2467
- chaptersTrack.mode = 'hidden';
2468
- }
2501
+ track.mode = 'hidden';
2469
2502
  }).tickNow().tickAfter(0.5);
2470
2503
 
2471
2504
  this.cleanupOnUnload_.push(() => {
@@ -4557,25 +4590,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4557
4590
  if (!mimeType) {
4558
4591
  mimeType = await this.getTextMimetype_(uri);
4559
4592
  }
4593
+
4560
4594
  let adCuePoints = [];
4561
4595
  if (this.adManager_) {
4562
4596
  try {
4563
4597
  adCuePoints = this.adManager_.getServerSideCuePoints();
4564
4598
  } catch (error) {}
4565
4599
  }
4566
- await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4567
- mimeType, /* label= */ '', adCuePoints);
4600
+
4601
+ /** @type {!HTMLTrackElement} */
4602
+ const trackElement = await this.addSrcTrackElement_(
4603
+ uri, language, /* kind= */ 'chapters', mimeType, /* label= */ '',
4604
+ adCuePoints);
4605
+
4568
4606
  const chaptersTracks = this.getChaptersTracks();
4569
4607
  const chaptersTrack = chaptersTracks.find((t) => {
4570
4608
  return t.language == language;
4571
4609
  });
4610
+
4572
4611
  if (chaptersTrack) {
4573
- const html5ChaptersTracks = this.getChaptersTracks_();
4574
- for (const html5ChaptersTrack of html5ChaptersTracks) {
4575
- this.processChaptersTrack_(html5ChaptersTrack);
4576
- }
4612
+ await new Promise((resolve, reject) => {
4613
+ // The chapter data isn't available until the 'load' event fires, and
4614
+ // that won't happen until the chapters track is activated by the
4615
+ // activateChaptersTrack_ method.
4616
+ this.loadEventManager_.listenOnce(trackElement, 'load', resolve);
4617
+ this.loadEventManager_.listenOnce(trackElement, 'error', (event) => {
4618
+ reject(new shaka.util.Error(
4619
+ shaka.util.Error.Severity.RECOVERABLE,
4620
+ shaka.util.Error.Category.TEXT,
4621
+ shaka.util.Error.Code.CHAPTERS_TRACK_FAILED));
4622
+ });
4623
+ });
4624
+
4577
4625
  return chaptersTrack;
4578
4626
  }
4627
+
4579
4628
  // This should not happen, but there are browser implementations that may
4580
4629
  // not support the Track element.
4581
4630
  shaka.log.error('Cannot add this text when loaded with src=');
@@ -4629,6 +4678,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4629
4678
  * @param {string} mimeType
4630
4679
  * @param {string} label
4631
4680
  * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4681
+ * @return {!Promise.<!HTMLTrackElement>}
4632
4682
  * @private
4633
4683
  */
4634
4684
  async addSrcTrackElement_(uri, language, kind, mimeType, label,
@@ -4644,12 +4694,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4644
4694
  uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4645
4695
  mimeType = 'text/vtt';
4646
4696
  }
4697
+
4647
4698
  const trackElement =
4648
4699
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4649
4700
  trackElement.src = uri;
4650
4701
  trackElement.label = label;
4651
4702
  trackElement.kind = kind;
4652
4703
  trackElement.srclang = language;
4704
+
4653
4705
  // Because we're pulling in the text track file via Javascript, the
4654
4706
  // same-origin policy applies. If you'd like to have a player served
4655
4707
  // from one domain, but the text track served from another, you'll
@@ -4659,7 +4711,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4659
4711
  if (!this.video_.getAttribute('crossorigin')) {
4660
4712
  this.video_.setAttribute('crossorigin', 'anonymous');
4661
4713
  }
4714
+
4662
4715
  this.video_.appendChild(trackElement);
4716
+ return trackElement;
4663
4717
  }
4664
4718
 
4665
4719
  /**
@@ -5027,7 +5081,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5027
5081
  }
5028
5082
 
5029
5083
  await stream.createSegmentIndex();
5030
- const ref = stream.segmentIndex[Symbol.iterator]().seek(time);
5084
+ const iter = stream.segmentIndex.getIteratorForTime(time);
5085
+ const ref = iter ? iter.next().value : null;
5031
5086
  if (!ref) {
5032
5087
  return null;
5033
5088
  }
@@ -5661,7 +5716,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5661
5716
  } // if (keyIds.size)
5662
5717
 
5663
5718
  if (tracksChanged) {
5664
- this.updateAbrManagerVariants_();
5719
+ const variantsUpdated = this.updateAbrManagerVariants_();
5720
+ if (!variantsUpdated) {
5721
+ return;
5722
+ }
5665
5723
  }
5666
5724
 
5667
5725
  const currentVariant = this.streamingEngine_.getCurrentVariant();
@@ -6372,7 +6430,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6372
6430
  * @define {string} A version number taken from git at compile time.
6373
6431
  * @export
6374
6432
  */
6375
- shaka.Player.version = 'v3.2.5-uncompiled';
6433
+ shaka.Player.version = 'v3.2.8-uncompiled';
6376
6434
 
6377
6435
  // Initialize the deprecation system using the version string we just set
6378
6436
  // on the player.
@@ -127,4 +127,29 @@ shaka.util.ConfigUtils = class {
127
127
  last[fieldName.substring(nameStart).replace(/\\\./g, '.')] = value;
128
128
  return configObject;
129
129
  }
130
+
131
+ /**
132
+ * Reference the input parameters so the compiler doesn't remove them from
133
+ * the calling function. Return whatever value is specified.
134
+ *
135
+ * This allows an empty or default implementation of a config callback that
136
+ * still bears the complete function signature even in compiled mode.
137
+ *
138
+ * The caller should look something like this:
139
+ *
140
+ * const callback = (a, b, c, d) => {
141
+ * return referenceParametersAndReturn(
142
+ [a, b, c, d],
143
+ a); // Can be anything, doesn't need to be one of the parameters
144
+ * };
145
+ *
146
+ * @param {!Array.<?>} parameters
147
+ * @param {T} returnValue
148
+ * @return {T}
149
+ * @template T
150
+ * @noinline
151
+ */
152
+ static referenceParametersAndReturn(parameters, returnValue) {
153
+ return parameters && returnValue;
154
+ }
130
155
  };
package/lib/util/error.js CHANGED
@@ -345,6 +345,13 @@ shaka.util.Error.Code = {
345
345
  */
346
346
  'MISSING_TEXT_PLUGIN': 2014,
347
347
 
348
+ /**
349
+ * The chapters track failed to load. The browser does not provide any
350
+ * information in this case to identify why it failed, but there may be
351
+ * details in the JavaScript console.
352
+ */
353
+ 'CHAPTERS_TRACK_FAILED': 2015,
354
+
348
355
  /**
349
356
  * Some component tried to read past the end of a buffer. The segment index,
350
357
  * init segment, or PSSH may be malformed.
@@ -167,7 +167,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
167
167
  /^hev/,
168
168
  /^hvc/,
169
169
  /^vp0?[89]/,
170
- /^av1$/,
170
+ /^av01/,
171
171
  ];
172
172
 
173
173
 
@@ -8,6 +8,7 @@ goog.provide('shaka.util.MimeUtils');
8
8
 
9
9
  goog.require('shaka.dependencies');
10
10
  goog.require('shaka.media.Transmuxer');
11
+ goog.require('shaka.util.ManifestParserUtils');
11
12
 
12
13
  /**
13
14
  * @summary A set of utility functions for dealing with MIME types.
@@ -32,6 +33,8 @@ shaka.util.MimeUtils = class {
32
33
  /**
33
34
  * Takes a MIME type and a codecs string and produces the full MIME
34
35
  * type. If it's a transport stream, convert its codecs to MP4 codecs.
36
+ * Otherwise for multiplexed content, convert the video MIME types to
37
+ * their audio equivalents if the content type is audio.
35
38
  *
36
39
  * @param {string} mimeType
37
40
  * @param {string} codecs
@@ -40,13 +43,19 @@ shaka.util.MimeUtils = class {
40
43
  */
41
44
  static getFullOrConvertedType(mimeType, codecs, contentType) {
42
45
  const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
46
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
43
47
 
44
- if (!shaka.dependencies.muxjs() ||
45
- !shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
46
- return fullMimeType;
48
+ if (shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
49
+ if (shaka.dependencies.muxjs()) {
50
+ return shaka.media.Transmuxer.convertTsCodecs(
51
+ contentType, fullMimeType);
52
+ }
53
+ } else if (contentType == ContentType.AUDIO) {
54
+ // video/mp2t is the correct mime type for TS audio, so only replace the
55
+ // word "video" with "audio" for non-TS audio content.
56
+ return fullMimeType.replace('video', 'audio');
47
57
  }
48
-
49
- return shaka.media.Transmuxer.convertTsCodecs(contentType, fullMimeType);
58
+ return fullMimeType;
50
59
  }
51
60
 
52
61
 
@@ -94,6 +103,53 @@ shaka.util.MimeUtils = class {
94
103
  return codecs.split(',');
95
104
  }
96
105
 
106
+ /**
107
+ * Get the normalized codec from a codec string,
108
+ * independently of their container.
109
+ *
110
+ * @param {string} codecString
111
+ * @return {string}
112
+ */
113
+ static getNormalizedCodec(codecString) {
114
+ const parts =
115
+ shaka.util.MimeUtils.getCodecParts_(codecString);
116
+ const base = parts[0];
117
+ const profile = parts[1].toLowerCase();
118
+ switch (true) {
119
+ case base === 'mp4a' && profile === '69':
120
+ case base === 'mp4a' && profile === '6b':
121
+ return 'mp3';
122
+ case base === 'mp4a' && profile === '66':
123
+ case base === 'mp4a' && profile === '67':
124
+ case base === 'mp4a' && profile === '68':
125
+ case base === 'mp4a' && profile === '40.2':
126
+ case base === 'mp4a' && profile === '40.02':
127
+ case base === 'mp4a' && profile === '40.5':
128
+ case base === 'mp4a' && profile === '40.05':
129
+ case base === 'mp4a' && profile === '40.29':
130
+ case base === 'mp4a' && profile === '40.42': // Extended HE-AAC
131
+ return 'aac';
132
+ case base === 'mp4a' && profile === 'a5':
133
+ return 'ac-3'; // Dolby Digital
134
+ case base === 'mp4a' && profile === 'a6':
135
+ return 'ec-3'; // Dolby Digital Plus
136
+ case base === 'mp4a' && profile === 'b2':
137
+ return 'dtsx'; // DTS:X
138
+ case base === 'mp4a' && profile === 'a9':
139
+ return 'dtsc'; // DTS Digital Surround
140
+ case base === 'avc1':
141
+ case base === 'avc3':
142
+ return 'avc'; // H264
143
+ case base === 'hvc1':
144
+ case base === 'hev1':
145
+ return 'hevc'; // H265
146
+ case base === 'dvh1':
147
+ case base === 'dvhe':
148
+ return 'dovi'; // Dolby Vision
149
+ }
150
+ return base;
151
+ }
152
+
97
153
  /**
98
154
  * Get the base codec from a codec string.
99
155
  *
@@ -150,7 +206,7 @@ shaka.util.MimeUtils = class {
150
206
 
151
207
  const base = parts[0];
152
208
 
153
- parts.pop();
209
+ parts.shift();
154
210
  const profile = parts.join('.');
155
211
 
156
212
  // Make sure that we always return a "base" and "profile".