shaka-player 4.0.0 → 4.0.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 (84) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/AUTHORS +1 -0
  4. package/CHANGELOG.md +39 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/README.md +4 -4
  7. package/build/misspellings.txt +1 -0
  8. package/build/test.py +1 -1
  9. package/demo/common/assets.js +1 -1
  10. package/demo/demo.less +0 -1
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +3 -3
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +3 -3
  17. package/dist/demo.compiled.map +2 -2
  18. package/dist/demo.css +1 -1
  19. package/dist/demo.css.map +1 -1
  20. package/dist/deps.js +1 -1
  21. package/dist/locales.js +33 -33
  22. package/dist/receiver.compiled.debug.js +1 -1
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.js +1 -1
  25. package/dist/receiver.compiled.map +1 -1
  26. package/dist/shaka-player.compiled.d.ts +2 -1
  27. package/dist/shaka-player.compiled.debug.d.ts +2 -1
  28. package/dist/shaka-player.compiled.debug.externs.js +4 -2
  29. package/dist/shaka-player.compiled.debug.js +278 -267
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +4 -2
  32. package/dist/shaka-player.compiled.js +815 -806
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +2 -1
  35. package/dist/shaka-player.ui.debug.d.ts +2 -1
  36. package/dist/shaka-player.ui.debug.externs.js +4 -2
  37. package/dist/shaka-player.ui.debug.js +305 -293
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +4 -2
  40. package/dist/shaka-player.ui.js +966 -959
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/upgrade.md +4 -2
  43. package/externs/shaka/player.js +15 -2
  44. package/karma.conf.js +4 -0
  45. package/lib/abr/simple_abr_manager.js +2 -1
  46. package/lib/dash/dash_parser.js +2 -2
  47. package/lib/hls/hls_parser.js +211 -82
  48. package/lib/media/drm_engine.js +19 -0
  49. package/lib/media/media_source_engine.js +91 -37
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +2 -3
  53. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  54. package/lib/player.js +165 -107
  55. package/lib/text/vtt_text_parser.js +7 -7
  56. package/lib/util/config_utils.js +25 -0
  57. package/lib/util/error.js +7 -0
  58. package/lib/util/manifest_parser_utils.js +1 -1
  59. package/lib/util/mime_utils.js +62 -6
  60. package/lib/util/periods.js +5 -4
  61. package/lib/util/player_configuration.js +14 -14
  62. package/lib/util/stream_utils.js +5 -2
  63. package/package.json +6 -3
  64. package/test/hls/hls_live_unit.js +79 -7
  65. package/test/hls/hls_parser_unit.js +102 -8
  66. package/test/hls/manifest_text_parser_unit.js +19 -0
  67. package/test/offline/indexeddb_storage_unit.js +1 -1
  68. package/test/offline/storage_compatibility_unit.js +1 -1
  69. package/test/player_external.js +7 -1
  70. package/test/player_integration.js +6 -10
  71. package/test/player_src_equals_integration.js +81 -83
  72. package/test/player_unit.js +98 -1
  73. package/test/test/boot.js +9 -0
  74. package/test/test/externs/filters.js +4 -0
  75. package/test/test/util/simple_fakes.js +31 -2
  76. package/test/text/vtt_text_parser_unit.js +64 -5
  77. package/test/util/mime_utils_unit.js +54 -0
  78. package/test/util/periods_unit.js +79 -0
  79. package/ui/less/containers.less +1 -1
  80. package/ui/less/general.less +1 -1
  81. package/ui/less/range_elements.less +1 -0
  82. package/ui/resolution_selection.js +1 -1
  83. package/.github/pull_request_template.md +0 -10
  84. package/docs/tutorials/gsoc-ideas-list.md +0 -3
package/lib/player.js CHANGED
@@ -423,8 +423,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
423
423
  */
424
424
  this.isTextVisible_ = false;
425
425
 
426
- /** @private {shaka.util.EventManager} */
427
- this.eventManager_ = new shaka.util.EventManager();
426
+ /**
427
+ * For listeners scoped to the lifetime of the Player instance.
428
+ * @private {shaka.util.EventManager}
429
+ */
430
+ this.globalEventManager_ = new shaka.util.EventManager();
431
+
432
+ /**
433
+ * For listeners scoped to the lifetime of the media element attachment.
434
+ * @private {shaka.util.EventManager}
435
+ */
436
+ this.attachEventManager_ = new shaka.util.EventManager();
437
+
438
+ /**
439
+ * For listeners scoped to the lifetime of the loaded content.
440
+ * @private {shaka.util.EventManager}
441
+ */
442
+ this.loadEventManager_ = new shaka.util.EventManager();
428
443
 
429
444
  /** @private {shaka.net.NetworkingEngine} */
430
445
  this.networkingEngine_ = null;
@@ -564,7 +579,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
564
579
 
565
580
  // If the browser comes back online after being offline, then try to play
566
581
  // again.
567
- this.eventManager_.listen(window, 'online', () => {
582
+ this.globalEventManager_.listen(window, 'online', () => {
568
583
  this.retryStreaming();
569
584
  });
570
585
 
@@ -752,10 +767,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
752
767
  });
753
768
  await this.walker_.destroy();
754
769
 
755
- // Tear-down the event manager to ensure messages stop moving around.
756
- if (this.eventManager_) {
757
- this.eventManager_.release();
758
- this.eventManager_ = null;
770
+ // Tear-down the event managers to ensure handlers stop firing.
771
+ if (this.globalEventManager_) {
772
+ this.globalEventManager_.release();
773
+ this.globalEventManager_ = null;
774
+ }
775
+ if (this.attachEventManager_) {
776
+ this.attachEventManager_.release();
777
+ this.attachEventManager_ = null;
778
+ }
779
+ if (this.loadEventManager_) {
780
+ this.loadEventManager_.release();
781
+ this.loadEventManager_ = null;
759
782
  }
760
783
 
761
784
  this.abrManagerFactory_ = null;
@@ -1298,7 +1321,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1298
1321
  has.mediaElement = wants.mediaElement;
1299
1322
 
1300
1323
  const onError = (error) => this.onVideoError_(error);
1301
- this.eventManager_.listen(has.mediaElement, 'error', onError);
1324
+ this.attachEventManager_.listen(has.mediaElement, 'error', onError);
1302
1325
  }
1303
1326
 
1304
1327
  this.video_ = has.mediaElement;
@@ -1325,10 +1348,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1325
1348
  * @private
1326
1349
  */
1327
1350
  onDetach_(has, wants) {
1328
- // If we are going from "detached" to "detached" we wouldn't have
1351
+ // If we were going from "detached" to "detached" we wouldn't have
1329
1352
  // a media element to detach from.
1330
1353
  if (has.mediaElement) {
1331
- this.eventManager_.unlisten(has.mediaElement, 'error');
1354
+ this.attachEventManager_.removeAll();
1332
1355
  has.mediaElement = null;
1333
1356
  }
1334
1357
 
@@ -1386,11 +1409,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1386
1409
  // In most cases we should have a media element. The one exception would
1387
1410
  // be if there was an error and we, by chance, did not have a media element.
1388
1411
  if (has.mediaElement) {
1389
- this.eventManager_.unlisten(has.mediaElement, 'loadedmetadata');
1390
- this.eventManager_.unlisten(has.mediaElement, 'playing');
1391
- this.eventManager_.unlisten(has.mediaElement, 'pause');
1392
- this.eventManager_.unlisten(has.mediaElement, 'ended');
1393
- this.eventManager_.unlisten(has.mediaElement, 'ratechange');
1412
+ this.loadEventManager_.removeAll();
1394
1413
  }
1395
1414
 
1396
1415
  // Some observers use some playback components, shutting down the observers
@@ -1885,18 +1904,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1885
1904
  this.assetUri_ = assetUri;
1886
1905
 
1887
1906
  this.playRateController_ = new shaka.media.PlayRateController({
1888
- getRate: () => has.mediaElement.playbackRate,
1889
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
1890
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
1891
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
1907
+ getRate: () => mediaElement.playbackRate,
1908
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
1909
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
1910
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
1892
1911
  });
1893
1912
 
1894
1913
  const updateStateHistory = () => this.updateStateHistory_();
1895
1914
  const onRateChange = () => this.onRateChange_();
1896
- this.eventManager_.listen(mediaElement, 'playing', updateStateHistory);
1897
- this.eventManager_.listen(mediaElement, 'pause', updateStateHistory);
1898
- this.eventManager_.listen(mediaElement, 'ended', updateStateHistory);
1899
- this.eventManager_.listen(mediaElement, 'ratechange', onRateChange);
1915
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
1916
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
1917
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
1918
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
1900
1919
 
1901
1920
  const abrFactory = this.config_.abrFactory;
1902
1921
  if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
@@ -1953,6 +1972,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1953
1972
  // "streaming" so that they can access internal information.
1954
1973
  this.loadMode_ = shaka.Player.LoadMode.MEDIA_SOURCE;
1955
1974
 
1975
+ if (mediaElement.textTracks) {
1976
+ this.loadEventManager_.listen(
1977
+ mediaElement.textTracks, 'addtrack', (e) => {
1978
+ const trackEvent = /** @type {!TrackEvent} */(e);
1979
+ if (trackEvent.track) {
1980
+ const track = trackEvent.track;
1981
+ goog.asserts.assert(
1982
+ track instanceof TextTrack, 'Wrong track type!');
1983
+
1984
+ switch (track.kind) {
1985
+ case 'chapters':
1986
+ this.activateChaptersTrack_(track);
1987
+ break;
1988
+ }
1989
+ }
1990
+ });
1991
+ }
1992
+
1956
1993
  // The event must be fired after we filter by restrictions but before the
1957
1994
  // active stream is picked to allow those listening for the "streaming"
1958
1995
  // event to make changes before streaming starts.
@@ -2039,7 +2076,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2039
2076
  }
2040
2077
 
2041
2078
  // Wait for the 'loadedmetadata' event to measure load() latency.
2042
- this.eventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2079
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2043
2080
  const now = Date.now() / 1000;
2044
2081
  const delta = now - wants.startTimeOfLoad;
2045
2082
  this.stats_.setLoadLatency(delta);
@@ -2192,11 +2229,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2192
2229
  // Save the uri so that it can be used outside of the load-graph.
2193
2230
  this.assetUri_ = has.uri;
2194
2231
 
2195
- this.playhead_ = new shaka.media.SrcEqualsPlayhead(has.mediaElement);
2232
+ const mediaElement = has.mediaElement;
2233
+
2234
+ this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
2196
2235
 
2197
- // This flag is used below in the language preference setup and
2198
- // track-management to check if this load was canceled before the necessary
2199
- // events fired.
2236
+ // This flag is used below in the language preference setup to check if
2237
+ // this load was canceled before the necessary awaits completed.
2200
2238
  let unloaded = false;
2201
2239
  this.cleanupOnUnload_.push(() => {
2202
2240
  unloaded = true;
@@ -2207,10 +2245,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2207
2245
  }
2208
2246
 
2209
2247
  this.playRateController_ = new shaka.media.PlayRateController({
2210
- getRate: () => has.mediaElement.playbackRate,
2211
- getDefaultRate: () => has.mediaElement.defaultPlaybackRate,
2212
- setRate: (rate) => { has.mediaElement.playbackRate = rate; },
2213
- movePlayhead: (delta) => { has.mediaElement.currentTime += delta; },
2248
+ getRate: () => mediaElement.playbackRate,
2249
+ getDefaultRate: () => mediaElement.defaultPlaybackRate,
2250
+ setRate: (rate) => { mediaElement.playbackRate = rate; },
2251
+ movePlayhead: (delta) => { mediaElement.currentTime += delta; },
2214
2252
  });
2215
2253
 
2216
2254
  // We need to start the buffer management code near the end because it will
@@ -2222,17 +2260,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2222
2260
  // Add all media element listeners.
2223
2261
  const updateStateHistory = () => this.updateStateHistory_();
2224
2262
  const onRateChange = () => this.onRateChange_();
2225
- this.eventManager_.listen(has.mediaElement, 'playing', updateStateHistory);
2226
- this.eventManager_.listen(has.mediaElement, 'pause', updateStateHistory);
2227
- this.eventManager_.listen(has.mediaElement, 'ended', updateStateHistory);
2228
- this.eventManager_.listen(has.mediaElement, 'ratechange', onRateChange);
2263
+ this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
2264
+ this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
2265
+ this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
2266
+ this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
2229
2267
 
2230
2268
  // Wait for the 'loadedmetadata' event to measure load() latency, but only
2231
2269
  // if preload is set in a way that would result in this event firing
2232
2270
  // automatically.
2233
2271
  // See https://github.com/shaka-project/shaka-player/issues/2483
2234
- if (this.video_.preload != 'none') {
2235
- this.eventManager_.listenOnce(this.video_, 'loadedmetadata', () => {
2272
+ if (mediaElement.preload != 'none') {
2273
+ this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
2236
2274
  const now = Date.now() / 1000;
2237
2275
  const delta = now - wants.startTimeOfLoad;
2238
2276
  this.stats_.setLoadLatency(delta);
@@ -2242,44 +2280,47 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2242
2280
  // The audio tracks are only available on Safari at the moment, but this
2243
2281
  // drives the tracks API for Safari's native HLS. So when they change,
2244
2282
  // fire the corresponding Shaka Player event.
2245
- if (this.video_.audioTracks) {
2246
- this.eventManager_.listen(
2247
- this.video_.audioTracks, 'addtrack', () => this.onTracksChanged_());
2248
- this.eventManager_.listen(
2249
- this.video_.audioTracks, 'removetrack',
2283
+ if (mediaElement.audioTracks) {
2284
+ this.loadEventManager_.listen(
2285
+ mediaElement.audioTracks, 'addtrack', () => this.onTracksChanged_());
2286
+ this.loadEventManager_.listen(
2287
+ mediaElement.audioTracks, 'removetrack',
2250
2288
  () => this.onTracksChanged_());
2251
- this.eventManager_.listen(
2252
- this.video_.audioTracks, 'change', () => this.onTracksChanged_());
2253
- }
2254
- if (this.video_.textTracks) {
2255
- this.eventManager_.listen(this.video_.textTracks, 'addtrack', (e) => {
2256
- // If we have moved on to another piece of content while waiting for
2257
- // the above event, we should not process tracks here.
2258
- if (unloaded) {
2259
- return;
2260
- }
2289
+ this.loadEventManager_.listen(
2290
+ mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
2291
+ }
2292
+
2293
+ if (mediaElement.textTracks) {
2294
+ this.loadEventManager_.listen(
2295
+ mediaElement.textTracks, 'addtrack', (e) => {
2296
+ const trackEvent = /** @type {!TrackEvent} */(e);
2297
+ if (trackEvent.track) {
2298
+ const track = trackEvent.track;
2299
+ goog.asserts.assert(
2300
+ track instanceof TextTrack, 'Wrong track type!');
2301
+
2302
+ switch (track.kind) {
2303
+ case 'metadata':
2304
+ this.processTimedMetadataSrcEqls_(track);
2305
+ break;
2306
+
2307
+ case 'chapters':
2308
+ this.activateChaptersTrack_(track);
2309
+ break;
2310
+
2311
+ default:
2312
+ this.onTracksChanged_();
2313
+ break;
2314
+ }
2315
+ }
2316
+ });
2261
2317
 
2262
- const trackEvent = /** @type {!TrackEvent} */(e);
2263
- if (trackEvent.track) {
2264
- const track = trackEvent.track;
2265
- goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2266
- switch (track.kind) {
2267
- case 'metadata':
2268
- this.processTimedMetadataSrcEqls_(track);
2269
- break;
2270
- case 'chapters':
2271
- this.processChaptersTrack_(track);
2272
- break;
2273
- default:
2274
- this.onTracksChanged_();
2275
- break;
2276
- }
2277
- }
2278
- });
2279
- this.eventManager_.listen(
2280
- this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
2281
- this.eventManager_.listen(
2282
- this.video_.textTracks, 'change', () => this.onTracksChanged_());
2318
+ this.loadEventManager_.listen(
2319
+ mediaElement.textTracks, 'removetrack',
2320
+ () => this.onTracksChanged_());
2321
+ this.loadEventManager_.listen(
2322
+ mediaElement.textTracks, 'change',
2323
+ () => this.onTracksChanged_());
2283
2324
  }
2284
2325
 
2285
2326
  const extension = shaka.media.ManifestParser.getExtension(has.uri);
@@ -2288,14 +2329,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2288
2329
 
2289
2330
  // By setting |src| we are done "loading" with src=. We don't need to set
2290
2331
  // the current time because |playhead| will do that for us.
2291
- has.mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2332
+ mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2292
2333
 
2293
2334
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2294
2335
  // no matter the value of the preload attribute. This is harmful on some
2295
2336
  // other platforms by triggering unbounded loading of media data, but is
2296
2337
  // necessary here.
2297
2338
  if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
2298
- has.mediaElement.load();
2339
+ mediaElement.load();
2299
2340
  }
2300
2341
 
2301
2342
  // Set the load mode last so that we know that all our components are
@@ -2311,24 +2352,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2311
2352
  // wait for the full data, that won't happen on Safari until the play button
2312
2353
  // is hit.
2313
2354
  const fullyLoaded = new shaka.util.PublicPromise();
2314
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2355
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2315
2356
  HTMLMediaElement.HAVE_METADATA,
2316
- this.eventManager_,
2357
+ this.loadEventManager_,
2317
2358
  () => {
2318
2359
  fullyLoaded.resolve();
2319
2360
  });
2320
2361
 
2321
2362
  // We can't switch to preferred languages, though, until the data is loaded.
2322
- shaka.util.MediaReadyState.waitForReadyState(this.video_,
2363
+ shaka.util.MediaReadyState.waitForReadyState(mediaElement,
2323
2364
  HTMLMediaElement.HAVE_CURRENT_DATA,
2324
- this.eventManager_,
2365
+ this.loadEventManager_,
2325
2366
  async () => {
2326
- // If we have moved on to another piece of content while waiting for
2327
- // the above event, we should not change tracks here.
2328
- if (unloaded) {
2329
- return;
2330
- }
2331
-
2332
2367
  this.setupPreferredAudioOnSrc_();
2333
2368
 
2334
2369
  // Applying the text preference too soon can result in it being
@@ -2336,8 +2371,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2336
2371
  const textTracks = this.getFilteredTextTracks_();
2337
2372
  if (!textTracks.find((t) => t.mode != 'disabled')) {
2338
2373
  await new Promise((resolve) => {
2339
- this.eventManager_.listenOnce(
2340
- this.video_.textTracks, 'change', resolve);
2374
+ this.loadEventManager_.listenOnce(
2375
+ mediaElement.textTracks, 'change', resolve);
2376
+
2341
2377
  // We expect the event to fire because it does on Safari.
2342
2378
  // But in case it doesn't on some other platform or future
2343
2379
  // version, move on in 1 second no matter what. This keeps the
@@ -2356,10 +2392,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2356
2392
  this.setupPreferredTextOnSrc_();
2357
2393
  });
2358
2394
 
2359
- if (this.video_.error) {
2395
+ if (mediaElement.error) {
2360
2396
  // Already failed!
2361
2397
  fullyLoaded.reject(this.videoErrorToShakaError_());
2362
- } else if (this.video_.preload == 'none') {
2398
+ } else if (mediaElement.preload == 'none') {
2363
2399
  shaka.log.alwaysWarn(
2364
2400
  'With <video preload="none">, the browser will not load anything ' +
2365
2401
  'until play() is called. We are unable to measure load latency in ' +
@@ -2370,7 +2406,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2370
2406
  fullyLoaded.resolve();
2371
2407
  }
2372
2408
 
2373
- this.eventManager_.listenOnce(this.video_, 'error', () => {
2409
+ this.loadEventManager_.listenOnce(mediaElement, 'error', () => {
2374
2410
  fullyLoaded.reject(this.videoErrorToShakaError_());
2375
2411
  });
2376
2412
 
@@ -2454,7 +2490,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2454
2490
 
2455
2491
  // Hidden mode is required for the cuechange event to launch correctly
2456
2492
  track.mode = 'hidden';
2457
- this.eventManager_.listen(track, 'cuechange', () => {
2493
+ this.loadEventManager_.listen(track, 'cuechange', () => {
2458
2494
  if (!track.activeCues) {
2459
2495
  return;
2460
2496
  }
@@ -2532,12 +2568,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2532
2568
  }
2533
2569
 
2534
2570
  /**
2535
- * We're looking for chapters tracks to process the chapters.
2571
+ * Set the mode on a chapters track so that it loads.
2536
2572
  *
2537
2573
  * @param {?TextTrack} track
2538
2574
  * @private
2539
2575
  */
2540
- processChaptersTrack_(track) {
2576
+ activateChaptersTrack_(track) {
2541
2577
  if (!track || track.kind != 'chapters') {
2542
2578
  return;
2543
2579
  }
@@ -2550,10 +2586,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2550
2586
  // this process to be repeated several times to ensure that it has been put
2551
2587
  // in the correct mode.
2552
2588
  const timer = new shaka.util.Timer(() => {
2553
- const chaptersTracks = this.getChaptersTracks_();
2554
- for (const chaptersTrack of chaptersTracks) {
2555
- chaptersTrack.mode = 'hidden';
2556
- }
2589
+ track.mode = 'hidden';
2557
2590
  }).tickNow().tickAfter(0.5);
2558
2591
 
2559
2592
  this.cleanupOnUnload_.push(() => {
@@ -4537,25 +4570,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4537
4570
  if (!mimeType) {
4538
4571
  mimeType = await this.getTextMimetype_(uri);
4539
4572
  }
4573
+
4540
4574
  let adCuePoints = [];
4541
4575
  if (this.adManager_) {
4542
4576
  try {
4543
4577
  adCuePoints = this.adManager_.getServerSideCuePoints();
4544
4578
  } catch (error) {}
4545
4579
  }
4546
- await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4547
- mimeType, /* label= */ '', adCuePoints);
4580
+
4581
+ /** @type {!HTMLTrackElement} */
4582
+ const trackElement = await this.addSrcTrackElement_(
4583
+ uri, language, /* kind= */ 'chapters', mimeType, /* label= */ '',
4584
+ adCuePoints);
4585
+
4548
4586
  const chaptersTracks = this.getChaptersTracks();
4549
4587
  const chaptersTrack = chaptersTracks.find((t) => {
4550
4588
  return t.language == language;
4551
4589
  });
4590
+
4552
4591
  if (chaptersTrack) {
4553
- const html5ChaptersTracks = this.getChaptersTracks_();
4554
- for (const html5ChaptersTrack of html5ChaptersTracks) {
4555
- this.processChaptersTrack_(html5ChaptersTrack);
4556
- }
4592
+ await new Promise((resolve, reject) => {
4593
+ // The chapter data isn't available until the 'load' event fires, and
4594
+ // that won't happen until the chapters track is activated by the
4595
+ // activateChaptersTrack_ method.
4596
+ this.loadEventManager_.listenOnce(trackElement, 'load', resolve);
4597
+ this.loadEventManager_.listenOnce(trackElement, 'error', (event) => {
4598
+ reject(new shaka.util.Error(
4599
+ shaka.util.Error.Severity.RECOVERABLE,
4600
+ shaka.util.Error.Category.TEXT,
4601
+ shaka.util.Error.Code.CHAPTERS_TRACK_FAILED));
4602
+ });
4603
+ });
4604
+
4557
4605
  return chaptersTrack;
4558
4606
  }
4607
+
4559
4608
  // This should not happen, but there are browser implementations that may
4560
4609
  // not support the Track element.
4561
4610
  shaka.log.error('Cannot add this text when loaded with src=');
@@ -4609,6 +4658,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4609
4658
  * @param {string} mimeType
4610
4659
  * @param {string} label
4611
4660
  * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4661
+ * @return {!Promise.<!HTMLTrackElement>}
4612
4662
  * @private
4613
4663
  */
4614
4664
  async addSrcTrackElement_(uri, language, kind, mimeType, label,
@@ -4624,12 +4674,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4624
4674
  uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4625
4675
  mimeType = 'text/vtt';
4626
4676
  }
4677
+
4627
4678
  const trackElement =
4628
4679
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4629
4680
  trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4630
4681
  trackElement.label = label;
4631
4682
  trackElement.kind = kind;
4632
4683
  trackElement.srclang = language;
4684
+
4633
4685
  // Because we're pulling in the text track file via Javascript, the
4634
4686
  // same-origin policy applies. If you'd like to have a player served
4635
4687
  // from one domain, but the text track served from another, you'll
@@ -4639,7 +4691,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4639
4691
  if (!this.video_.getAttribute('crossorigin')) {
4640
4692
  this.video_.setAttribute('crossorigin', 'anonymous');
4641
4693
  }
4694
+
4642
4695
  this.video_.appendChild(trackElement);
4696
+ return trackElement;
4643
4697
  }
4644
4698
 
4645
4699
  /**
@@ -5010,7 +5064,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5010
5064
  }
5011
5065
 
5012
5066
  await stream.createSegmentIndex();
5013
- const ref = stream.segmentIndex[Symbol.iterator]().seek(time);
5067
+ const iter = stream.segmentIndex.getIteratorForTime(time);
5068
+ const ref = iter ? iter.next().value : null;
5014
5069
  if (!ref) {
5015
5070
  return null;
5016
5071
  }
@@ -5679,7 +5734,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5679
5734
  } // if (keyIds.size)
5680
5735
 
5681
5736
  if (tracksChanged) {
5682
- this.updateAbrManagerVariants_();
5737
+ const variantsUpdated = this.updateAbrManagerVariants_();
5738
+ if (!variantsUpdated) {
5739
+ return;
5740
+ }
5683
5741
  }
5684
5742
 
5685
5743
  const currentVariant = this.streamingEngine_.getCurrentVariant();
@@ -6392,7 +6450,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6392
6450
  * @define {string} A version number taken from git at compile time.
6393
6451
  * @export
6394
6452
  */
6395
- shaka.Player.version = 'v4.0.0-uncompiled';
6453
+ shaka.Player.version = 'v4.0.3-uncompiled';
6396
6454
 
6397
6455
  // Initialize the deprecation system using the version string we just set
6398
6456
  // on the player.
@@ -68,13 +68,15 @@ shaka.text.VttTextParser = class {
68
68
  // to the beginning of each segment.
69
69
  // NOTE: "periodStart" is the timestamp offset applied via TextEngine.
70
70
  // It is no longer closely tied to periods, but the name stuck around.
71
+ // NOTE: This offset and the flag choosing its meaning have no effect on
72
+ // HLS content, which should use X-TIMESTAMP-MAP and periodStart instead.
71
73
  let offset = time.vttOffset;
72
74
 
73
- // Do not honor the 'X-TIMESTAMP-MAP' value when in sequence mode.
74
- // That is because it is used mainly (solely?) to account for the timestamp
75
- // offset of the video/audio; when in sequence mode, we normalize that
76
- // timestamp offset to 0, so we should not account for it.
77
- if (blocks[0].includes('X-TIMESTAMP-MAP') && !this.sequenceMode_) {
75
+ // Only use 'X-TIMESTAMP-MAP' in sequence mode, as that is currently
76
+ // shorthand for HLS. Note that an offset based on the first video
77
+ // timestamp has already been extracted, and appears in periodStart.
78
+ // The relative offset from X-TIMESTAMP-MAP will be added to that for HLS.
79
+ if (blocks[0].includes('X-TIMESTAMP-MAP') && this.sequenceMode_) {
78
80
  // https://bit.ly/2K92l7y
79
81
  // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with
80
82
  // the rest of the media.
@@ -109,8 +111,6 @@ shaka.text.VttTextParser = class {
109
111
  mpegTime += shaka.text.VttTextParser.TS_ROLLOVER_;
110
112
  }
111
113
 
112
- // Apple-encoded HLS content uses absolute timestamps, so assume the
113
- // presence of the map tag means the content uses absolute timestamps.
114
114
  offset = time.periodStart + mpegTime / mpegTimescale - cueTime;
115
115
  }
116
116
  }
@@ -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