shaka-player 3.3.4 → 3.3.7

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 (80) hide show
  1. package/.github/workflows/build-and-test.yaml +0 -1
  2. package/.github/workflows/release-please.yaml +4 -3
  3. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  4. package/AUTHORS +1 -0
  5. package/CHANGELOG.md +38 -0
  6. package/CONTRIBUTORS +1 -0
  7. package/README.md +4 -4
  8. package/build/README.md +2 -2
  9. package/build/generateTsDefs.py +0 -2
  10. package/build/test.py +8 -2
  11. package/build/types/core +0 -1
  12. package/demo/demo.less +0 -1
  13. package/demo/main.js +3 -3
  14. package/dist/controls.css +3 -3
  15. package/dist/controls.css.map +1 -1
  16. package/dist/demo.compiled.debug.js +2 -2
  17. package/dist/demo.compiled.debug.map +2 -2
  18. package/dist/demo.compiled.js +2 -2
  19. package/dist/demo.compiled.map +2 -2
  20. package/dist/demo.css +1 -1
  21. package/dist/demo.css.map +1 -1
  22. package/dist/deps.js +2 -3
  23. package/dist/locales.js +31 -31
  24. package/dist/shaka-player.compiled.d.ts +1 -2
  25. package/dist/shaka-player.compiled.debug.d.ts +1 -2
  26. package/dist/shaka-player.compiled.debug.externs.js +1 -0
  27. package/dist/shaka-player.compiled.debug.js +153 -151
  28. package/dist/shaka-player.compiled.debug.map +4 -4
  29. package/dist/shaka-player.compiled.externs.js +1 -0
  30. package/dist/shaka-player.compiled.js +845 -843
  31. package/dist/shaka-player.compiled.map +4 -4
  32. package/dist/shaka-player.ui.d.ts +1 -2
  33. package/dist/shaka-player.ui.debug.d.ts +1 -2
  34. package/dist/shaka-player.ui.debug.externs.js +1 -0
  35. package/dist/shaka-player.ui.debug.js +225 -223
  36. package/dist/shaka-player.ui.debug.map +4 -4
  37. package/dist/shaka-player.ui.externs.js +1 -0
  38. package/dist/shaka-player.ui.js +953 -951
  39. package/dist/shaka-player.ui.map +4 -4
  40. package/karma.conf.js +4 -0
  41. package/lib/abr/simple_abr_manager.js +2 -1
  42. package/lib/dash/dash_parser.js +2 -3
  43. package/lib/hls/hls_parser.js +37 -14
  44. package/lib/media/drm_engine.js +3 -0
  45. package/lib/media/media_source_engine.js +7 -1
  46. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  47. package/lib/offline/storage.js +92 -91
  48. package/lib/player.js +165 -107
  49. package/lib/util/config_utils.js +25 -0
  50. package/lib/util/error.js +7 -0
  51. package/lib/util/manifest_parser_utils.js +1 -1
  52. package/lib/util/mime_utils.js +62 -6
  53. package/lib/util/periods.js +5 -4
  54. package/lib/util/player_configuration.js +9 -14
  55. package/lib/util/stream_utils.js +5 -2
  56. package/package.json +2 -2
  57. package/test/cast/cast_utils_unit.js +1 -0
  58. package/test/dash/dash_parser_manifest_unit.js +0 -24
  59. package/test/hls/hls_live_unit.js +86 -7
  60. package/test/hls/manifest_text_parser_unit.js +19 -0
  61. package/test/media/media_source_engine_integration.js +1 -0
  62. package/test/media/media_source_engine_unit.js +4 -4
  63. package/test/media/streaming_engine_integration.js +2 -0
  64. package/test/offline/indexeddb_storage_unit.js +1 -1
  65. package/test/offline/storage_compatibility_unit.js +1 -1
  66. package/test/player_external.js +7 -1
  67. package/test/player_integration.js +6 -10
  68. package/test/player_src_equals_integration.js +81 -83
  69. package/test/player_unit.js +98 -1
  70. package/test/test/boot.js +9 -0
  71. package/test/test/externs/filters.js +4 -0
  72. package/test/test/util/simple_fakes.js +31 -2
  73. package/test/util/mime_utils_unit.js +54 -0
  74. package/test/util/periods_unit.js +79 -0
  75. package/ui/less/containers.less +1 -1
  76. package/ui/less/general.less +1 -1
  77. package/ui/less/range_elements.less +1 -0
  78. package/.github/pull_request_template.md +0 -10
  79. package/docs/tutorials/gsoc-ideas-list.md +0 -3
  80. package/lib/util/mutex.js +0 -52
package/lib/player.js CHANGED
@@ -440,8 +440,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
440
440
  */
441
441
  this.isTextVisible_ = false;
442
442
 
443
- /** @private {shaka.util.EventManager} */
444
- this.eventManager_ = new shaka.util.EventManager();
443
+ /**
444
+ * For listeners scoped to the lifetime of the Player instance.
445
+ * @private {shaka.util.EventManager}
446
+ */
447
+ this.globalEventManager_ = new shaka.util.EventManager();
448
+
449
+ /**
450
+ * For listeners scoped to the lifetime of the media element attachment.
451
+ * @private {shaka.util.EventManager}
452
+ */
453
+ this.attachEventManager_ = new shaka.util.EventManager();
454
+
455
+ /**
456
+ * For listeners scoped to the lifetime of the loaded content.
457
+ * @private {shaka.util.EventManager}
458
+ */
459
+ this.loadEventManager_ = new shaka.util.EventManager();
445
460
 
446
461
  /** @private {shaka.net.NetworkingEngine} */
447
462
  this.networkingEngine_ = null;
@@ -582,7 +597,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
582
597
 
583
598
  // If the browser comes back online after being offline, then try to play
584
599
  // again.
585
- this.eventManager_.listen(window, 'online', () => {
600
+ this.globalEventManager_.listen(window, 'online', () => {
586
601
  this.retryStreaming();
587
602
  });
588
603
 
@@ -770,10 +785,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
770
785
  });
771
786
  await this.walker_.destroy();
772
787
 
773
- // Tear-down the event manager to ensure messages stop moving around.
774
- if (this.eventManager_) {
775
- this.eventManager_.release();
776
- this.eventManager_ = null;
788
+ // Tear-down the event managers to ensure handlers stop firing.
789
+ if (this.globalEventManager_) {
790
+ this.globalEventManager_.release();
791
+ this.globalEventManager_ = null;
792
+ }
793
+ if (this.attachEventManager_) {
794
+ this.attachEventManager_.release();
795
+ this.attachEventManager_ = null;
796
+ }
797
+ if (this.loadEventManager_) {
798
+ this.loadEventManager_.release();
799
+ this.loadEventManager_ = null;
777
800
  }
778
801
 
779
802
  this.abrManagerFactory_ = null;
@@ -1316,7 +1339,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1316
1339
  has.mediaElement = wants.mediaElement;
1317
1340
 
1318
1341
  const onError = (error) => this.onVideoError_(error);
1319
- this.eventManager_.listen(has.mediaElement, 'error', onError);
1342
+ this.attachEventManager_.listen(has.mediaElement, 'error', onError);
1320
1343
  }
1321
1344
 
1322
1345
  this.video_ = has.mediaElement;
@@ -1343,10 +1366,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1343
1366
  * @private
1344
1367
  */
1345
1368
  onDetach_(has, wants) {
1346
- // If we are going from "detached" to "detached" we wouldn't have
1369
+ // If we were going from "detached" to "detached" we wouldn't have
1347
1370
  // a media element to detach from.
1348
1371
  if (has.mediaElement) {
1349
- this.eventManager_.unlisten(has.mediaElement, 'error');
1372
+ this.attachEventManager_.removeAll();
1350
1373
  has.mediaElement = null;
1351
1374
  }
1352
1375
 
@@ -1404,11 +1427,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1404
1427
  // In most cases we should have a media element. The one exception would
1405
1428
  // be if there was an error and we, by chance, did not have a media element.
1406
1429
  if (has.mediaElement) {
1407
- this.eventManager_.unlisten(has.mediaElement, 'loadedmetadata');
1408
- this.eventManager_.unlisten(has.mediaElement, 'playing');
1409
- this.eventManager_.unlisten(has.mediaElement, 'pause');
1410
- this.eventManager_.unlisten(has.mediaElement, 'ended');
1411
- this.eventManager_.unlisten(has.mediaElement, 'ratechange');
1430
+ this.loadEventManager_.removeAll();
1412
1431
  }
1413
1432
 
1414
1433
  // Some observers use some playback components, shutting down the observers
@@ -1902,18 +1921,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1902
1921
  this.assetUri_ = assetUri;
1903
1922
 
1904
1923
  this.playRateController_ = new shaka.media.PlayRateController({
1905
- getRate: () => has.mediaElement.playbackRate,
1906
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
1907
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
1908
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
1924
+ getRate: () => mediaElement.playbackRate,
1925
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
1926
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
1927
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
1909
1928
  });
1910
1929
 
1911
1930
  const updateStateHistory = () => this.updateStateHistory_();
1912
1931
  const onRateChange = () => this.onRateChange_();
1913
- this.eventManager_.listen(mediaElement, 'playing', updateStateHistory);
1914
- this.eventManager_.listen(mediaElement, 'pause', updateStateHistory);
1915
- this.eventManager_.listen(mediaElement, 'ended', updateStateHistory);
1916
- this.eventManager_.listen(mediaElement, 'ratechange', onRateChange);
1932
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
1933
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
1934
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
1935
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
1917
1936
 
1918
1937
  const abrFactory = this.config_.abrFactory;
1919
1938
  if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
@@ -1976,6 +1995,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1976
1995
  // "streaming" so that they can access internal information.
1977
1996
  this.loadMode_ = shaka.Player.LoadMode.MEDIA_SOURCE;
1978
1997
 
1998
+ if (mediaElement.textTracks) {
1999
+ this.loadEventManager_.listen(
2000
+ mediaElement.textTracks, 'addtrack', (e) => {
2001
+ const trackEvent = /** @type {!TrackEvent} */(e);
2002
+ if (trackEvent.track) {
2003
+ const track = trackEvent.track;
2004
+ goog.asserts.assert(
2005
+ track instanceof TextTrack, 'Wrong track type!');
2006
+
2007
+ switch (track.kind) {
2008
+ case 'chapters':
2009
+ this.activateChaptersTrack_(track);
2010
+ break;
2011
+ }
2012
+ }
2013
+ });
2014
+ }
2015
+
1979
2016
  // The event must be fired after we filter by restrictions but before the
1980
2017
  // active stream is picked to allow those listening for the "streaming"
1981
2018
  // event to make changes before streaming starts.
@@ -2062,7 +2099,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2062
2099
  }
2063
2100
 
2064
2101
  // Wait for the 'loadedmetadata' event to measure load() latency.
2065
- this.eventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2102
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2066
2103
  const now = Date.now() / 1000;
2067
2104
  const delta = now - wants.startTimeOfLoad;
2068
2105
  this.stats_.setLoadLatency(delta);
@@ -2215,11 +2252,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2215
2252
  // Save the uri so that it can be used outside of the load-graph.
2216
2253
  this.assetUri_ = has.uri;
2217
2254
 
2218
- this.playhead_ = new shaka.media.SrcEqualsPlayhead(has.mediaElement);
2255
+ const mediaElement = has.mediaElement;
2256
+
2257
+ this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
2219
2258
 
2220
- // This flag is used below in the language preference setup and
2221
- // track-management to check if this load was canceled before the necessary
2222
- // events fired.
2259
+ // This flag is used below in the language preference setup to check if
2260
+ // this load was canceled before the necessary awaits completed.
2223
2261
  let unloaded = false;
2224
2262
  this.cleanupOnUnload_.push(() => {
2225
2263
  unloaded = true;
@@ -2230,10 +2268,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2230
2268
  }
2231
2269
 
2232
2270
  this.playRateController_ = new shaka.media.PlayRateController({
2233
- getRate: () => has.mediaElement.playbackRate,
2234
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
2235
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
2236
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
2271
+ getRate: () => mediaElement.playbackRate,
2272
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
2273
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
2274
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
2237
2275
  });
2238
2276
 
2239
2277
  // We need to start the buffer management code near the end because it will
@@ -2245,17 +2283,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2245
2283
  // Add all media element listeners.
2246
2284
  const updateStateHistory = () => this.updateStateHistory_();
2247
2285
  const onRateChange = () => this.onRateChange_();
2248
- this.eventManager_.listen(has.mediaElement, 'playing', updateStateHistory);
2249
- this.eventManager_.listen(has.mediaElement, 'pause', updateStateHistory);
2250
- this.eventManager_.listen(has.mediaElement, 'ended', updateStateHistory);
2251
- this.eventManager_.listen(has.mediaElement, 'ratechange', onRateChange);
2286
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
2287
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
2288
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
2289
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
2252
2290
 
2253
2291
  // Wait for the 'loadedmetadata' event to measure load() latency, but only
2254
2292
  // if preload is set in a way that would result in this event firing
2255
2293
  // automatically.
2256
2294
  // See https://github.com/shaka-project/shaka-player/issues/2483
2257
- if (this.video_.preload != 'none') {
2258
- this.eventManager_.listenOnce(this.video_, 'loadedmetadata', () => {
2295
+ if (mediaElement.preload != 'none') {
2296
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2259
2297
  const now = Date.now() / 1000;
2260
2298
  const delta = now - wants.startTimeOfLoad;
2261
2299
  this.stats_.setLoadLatency(delta);
@@ -2265,44 +2303,47 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2265
2303
  // The audio tracks are only available on Safari at the moment, but this
2266
2304
  // drives the tracks API for Safari's native HLS. So when they change,
2267
2305
  // fire the corresponding Shaka Player event.
2268
- if (this.video_.audioTracks) {
2269
- this.eventManager_.listen(
2270
- this.video_.audioTracks, 'addtrack', () => this.onTracksChanged_());
2271
- this.eventManager_.listen(
2272
- this.video_.audioTracks, 'removetrack',
2306
+ if (mediaElement.audioTracks) {
2307
+ this.loadEventManager_.listen(
2308
+ mediaElement.audioTracks, 'addtrack', () => this.onTracksChanged_());
2309
+ this.loadEventManager_.listen(
2310
+ mediaElement.audioTracks, 'removetrack',
2273
2311
  () => this.onTracksChanged_());
2274
- this.eventManager_.listen(
2275
- this.video_.audioTracks, 'change', () => this.onTracksChanged_());
2276
- }
2277
- if (this.video_.textTracks) {
2278
- this.eventManager_.listen(this.video_.textTracks, 'addtrack', (e) => {
2279
- // If we have moved on to another piece of content while waiting for
2280
- // the above event, we should not process tracks here.
2281
- if (unloaded) {
2282
- return;
2283
- }
2312
+ this.loadEventManager_.listen(
2313
+ mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
2314
+ }
2315
+
2316
+ if (mediaElement.textTracks) {
2317
+ this.loadEventManager_.listen(
2318
+ mediaElement.textTracks, 'addtrack', (e) => {
2319
+ const trackEvent = /** @type {!TrackEvent} */(e);
2320
+ if (trackEvent.track) {
2321
+ const track = trackEvent.track;
2322
+ goog.asserts.assert(
2323
+ track instanceof TextTrack, 'Wrong track type!');
2324
+
2325
+ switch (track.kind) {
2326
+ case 'metadata':
2327
+ this.processTimedMetadataSrcEqls_(track);
2328
+ break;
2329
+
2330
+ case 'chapters':
2331
+ this.activateChaptersTrack_(track);
2332
+ break;
2333
+
2334
+ default:
2335
+ this.onTracksChanged_();
2336
+ break;
2337
+ }
2338
+ }
2339
+ });
2284
2340
 
2285
- const trackEvent = /** @type {!TrackEvent} */(e);
2286
- if (trackEvent.track) {
2287
- const track = trackEvent.track;
2288
- goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2289
- switch (track.kind) {
2290
- case 'metadata':
2291
- this.processTimedMetadataSrcEqls_(track);
2292
- break;
2293
- case 'chapters':
2294
- this.processChaptersTrack_(track);
2295
- break;
2296
- default:
2297
- this.onTracksChanged_();
2298
- break;
2299
- }
2300
- }
2301
- });
2302
- this.eventManager_.listen(
2303
- this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
2304
- this.eventManager_.listen(
2305
- this.video_.textTracks, 'change', () => this.onTracksChanged_());
2341
+ this.loadEventManager_.listen(
2342
+ mediaElement.textTracks, 'removetrack',
2343
+ () => this.onTracksChanged_());
2344
+ this.loadEventManager_.listen(
2345
+ mediaElement.textTracks, 'change',
2346
+ () => this.onTracksChanged_());
2306
2347
  }
2307
2348
 
2308
2349
  const extension = shaka.media.ManifestParser.getExtension(has.uri);
@@ -2311,14 +2352,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2311
2352
 
2312
2353
  // By setting |src| we are done "loading" with src=. We don't need to set
2313
2354
  // the current time because |playhead| will do that for us.
2314
- has.mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2355
+ mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2315
2356
 
2316
2357
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2317
2358
  // no matter the value of the preload attribute. This is harmful on some
2318
2359
  // other platforms by triggering unbounded loading of media data, but is
2319
2360
  // necessary here.
2320
2361
  if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
2321
- has.mediaElement.load();
2362
+ mediaElement.load();
2322
2363
  }
2323
2364
 
2324
2365
  // Set the load mode last so that we know that all our components are
@@ -2334,24 +2375,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2334
2375
  // wait for the full data, that won't happen on Safari until the play button
2335
2376
  // is hit.
2336
2377
  const fullyLoaded = new shaka.util.PublicPromise();
2337
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2378
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2338
2379
  HTMLMediaElement.HAVE_METADATA,
2339
- this.eventManager_,
2380
+ this.loadEventManager_,
2340
2381
  () => {
2341
2382
  fullyLoaded.resolve();
2342
2383
  });
2343
2384
 
2344
2385
  // We can't switch to preferred languages, though, until the data is loaded.
2345
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2386
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2346
2387
  HTMLMediaElement.HAVE_CURRENT_DATA,
2347
- this.eventManager_,
2388
+ this.loadEventManager_,
2348
2389
  async () => {
2349
- // If we have moved on to another piece of content while waiting for
2350
- // the above event, we should not change tracks here.
2351
- if (unloaded) {
2352
- return;
2353
- }
2354
-
2355
2390
  this.setupPreferredAudioOnSrc_();
2356
2391
 
2357
2392
  // Applying the text preference too soon can result in it being
@@ -2359,8 +2394,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2359
2394
  const textTracks = this.getFilteredTextTracks_();
2360
2395
  if (!textTracks.find((t) => t.mode != 'disabled')) {
2361
2396
  await new Promise((resolve) => {
2362
- this.eventManager_.listenOnce(
2363
- this.video_.textTracks, 'change', resolve);
2397
+ this.loadEventManager_.listenOnce(
2398
+ mediaElement.textTracks, 'change', resolve);
2399
+
2364
2400
  // We expect the event to fire because it does on Safari.
2365
2401
  // But in case it doesn't on some other platform or future
2366
2402
  // version, move on in 1 second no matter what. This keeps the
@@ -2379,10 +2415,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2379
2415
  this.setupPreferredTextOnSrc_();
2380
2416
  });
2381
2417
 
2382
- if (this.video_.error) {
2418
+ if (mediaElement.error) {
2383
2419
  // Already failed!
2384
2420
  fullyLoaded.reject(this.videoErrorToShakaError_());
2385
- } else if (this.video_.preload == 'none') {
2421
+ } else if (mediaElement.preload == 'none') {
2386
2422
  shaka.log.alwaysWarn(
2387
2423
  'With <video preload="none">, the browser will not load anything ' +
2388
2424
  'until play() is called. We are unable to measure load latency in ' +
@@ -2393,7 +2429,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2393
2429
  fullyLoaded.resolve();
2394
2430
  }
2395
2431
 
2396
- this.eventManager_.listenOnce(this.video_, 'error', () => {
2432
+ this.loadEventManager_.listenOnce(mediaElement, 'error', () => {
2397
2433
  fullyLoaded.reject(this.videoErrorToShakaError_());
2398
2434
  });
2399
2435
 
@@ -2477,7 +2513,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2477
2513
 
2478
2514
  // Hidden mode is required for the cuechange event to launch correctly
2479
2515
  track.mode = 'hidden';
2480
- this.eventManager_.listen(track, 'cuechange', () => {
2516
+ this.loadEventManager_.listen(track, 'cuechange', () => {
2481
2517
  if (!track.activeCues) {
2482
2518
  return;
2483
2519
  }
@@ -2555,12 +2591,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2555
2591
  }
2556
2592
 
2557
2593
  /**
2558
- * We're looking for chapters tracks to process the chapters.
2594
+ * Set the mode on a chapters track so that it loads.
2559
2595
  *
2560
2596
  * @param {?TextTrack} track
2561
2597
  * @private
2562
2598
  */
2563
- processChaptersTrack_(track) {
2599
+ activateChaptersTrack_(track) {
2564
2600
  if (!track || track.kind != 'chapters') {
2565
2601
  return;
2566
2602
  }
@@ -2573,10 +2609,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2573
2609
  // this process to be repeated several times to ensure that it has been put
2574
2610
  // in the correct mode.
2575
2611
  const timer = new shaka.util.Timer(() => {
2576
- const chaptersTracks = this.getChaptersTracks_();
2577
- for (const chaptersTrack of chaptersTracks) {
2578
- chaptersTrack.mode = 'hidden';
2579
- }
2612
+ track.mode = 'hidden';
2580
2613
  }).tickNow().tickAfter(0.5);
2581
2614
 
2582
2615
  this.cleanupOnUnload_.push(() => {
@@ -4721,25 +4754,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4721
4754
  if (!mimeType) {
4722
4755
  mimeType = await this.getTextMimetype_(uri);
4723
4756
  }
4757
+
4724
4758
  let adCuePoints = [];
4725
4759
  if (this.adManager_) {
4726
4760
  try {
4727
4761
  adCuePoints = this.adManager_.getServerSideCuePoints();
4728
4762
  } catch (error) {}
4729
4763
  }
4730
- await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4731
- mimeType, /* label= */ '', adCuePoints);
4764
+
4765
+ /** @type {!HTMLTrackElement} */
4766
+ const trackElement = await this.addSrcTrackElement_(
4767
+ uri, language, /* kind= */ 'chapters', mimeType, /* label= */ '',
4768
+ adCuePoints);
4769
+
4732
4770
  const chaptersTracks = this.getChaptersTracks();
4733
4771
  const chaptersTrack = chaptersTracks.find((t) => {
4734
4772
  return t.language == language;
4735
4773
  });
4774
+
4736
4775
  if (chaptersTrack) {
4737
- const html5ChaptersTracks = this.getChaptersTracks_();
4738
- for (const html5ChaptersTrack of html5ChaptersTracks) {
4739
- this.processChaptersTrack_(html5ChaptersTrack);
4740
- }
4776
+ await new Promise((resolve, reject) => {
4777
+ // The chapter data isn't available until the 'load' event fires, and
4778
+ // that won't happen until the chapters track is activated by the
4779
+ // activateChaptersTrack_ method.
4780
+ this.loadEventManager_.listenOnce(trackElement, 'load', resolve);
4781
+ this.loadEventManager_.listenOnce(trackElement, 'error', (event) => {
4782
+ reject(new shaka.util.Error(
4783
+ shaka.util.Error.Severity.RECOVERABLE,
4784
+ shaka.util.Error.Category.TEXT,
4785
+ shaka.util.Error.Code.CHAPTERS_TRACK_FAILED));
4786
+ });
4787
+ });
4788
+
4741
4789
  return chaptersTrack;
4742
4790
  }
4791
+
4743
4792
  // This should not happen, but there are browser implementations that may
4744
4793
  // not support the Track element.
4745
4794
  shaka.log.error('Cannot add this text when loaded with src=');
@@ -4793,6 +4842,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4793
4842
  * @param {string} mimeType
4794
4843
  * @param {string} label
4795
4844
  * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4845
+ * @return {!Promise.<!HTMLTrackElement>}
4796
4846
  * @private
4797
4847
  */
4798
4848
  async addSrcTrackElement_(uri, language, kind, mimeType, label,
@@ -4808,12 +4858,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4808
4858
  uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4809
4859
  mimeType = 'text/vtt';
4810
4860
  }
4861
+
4811
4862
  const trackElement =
4812
4863
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4813
4864
  trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4814
4865
  trackElement.label = label;
4815
4866
  trackElement.kind = kind;
4816
4867
  trackElement.srclang = language;
4868
+
4817
4869
  // Because we're pulling in the text track file via Javascript, the
4818
4870
  // same-origin policy applies. If you'd like to have a player served
4819
4871
  // from one domain, but the text track served from another, you'll
@@ -4823,7 +4875,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4823
4875
  if (!this.video_.getAttribute('crossorigin')) {
4824
4876
  this.video_.setAttribute('crossorigin', 'anonymous');
4825
4877
  }
4878
+
4826
4879
  this.video_.appendChild(trackElement);
4880
+ return trackElement;
4827
4881
  }
4828
4882
 
4829
4883
  /**
@@ -5193,7 +5247,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5193
5247
  }
5194
5248
 
5195
5249
  await stream.createSegmentIndex();
5196
- const ref = stream.segmentIndex[Symbol.iterator]().seek(time);
5250
+ const iter = stream.segmentIndex.getIteratorForTime(time);
5251
+ const ref = iter ? iter.next().value : null;
5197
5252
  if (!ref) {
5198
5253
  return null;
5199
5254
  }
@@ -5862,7 +5917,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5862
5917
  } // if (keyIds.size)
5863
5918
 
5864
5919
  if (tracksChanged) {
5865
- this.updateAbrManagerVariants_();
5920
+ const variantsUpdated = this.updateAbrManagerVariants_();
5921
+ if (!variantsUpdated) {
5922
+ return;
5923
+ }
5866
5924
  }
5867
5925
 
5868
5926
  const currentVariant = this.streamingEngine_.getCurrentVariant();
@@ -6576,7 +6634,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6576
6634
  * @define {string} A version number taken from git at compile time.
6577
6635
  * @export
6578
6636
  */
6579
- shaka.Player.version = 'v3.3.4-uncompiled';
6637
+ shaka.Player.version = 'v3.3.7-uncompiled';
6580
6638
 
6581
6639
  // Initialize the deprecation system using the version string we just set
6582
6640
  // 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".