shaka-player 4.1.5 → 4.1.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 (68) hide show
  1. package/.github/workflows/build-and-test.yaml +11 -6
  2. package/.github/workflows/compute-incremental-coverage.py +14 -3
  3. package/.github/workflows/demo-version-index.yaml +1 -1
  4. package/.github/workflows/nightly-demo.yaml +1 -1
  5. package/.github/workflows/release-please.yaml +5 -5
  6. package/.github/workflows/report-incremental-coverage.yaml +1 -1
  7. package/.github/workflows/selenium-lab-tests.yaml +8 -6
  8. package/AUTHORS +2 -0
  9. package/CHANGELOG.md +32 -0
  10. package/CONTRIBUTORS +2 -0
  11. package/dist/deps.js +5 -5
  12. package/dist/locales.js +29 -29
  13. package/dist/shaka-player.compiled.d.ts +6 -1
  14. package/dist/shaka-player.compiled.debug.d.ts +6 -1
  15. package/dist/shaka-player.compiled.debug.externs.js +3 -1
  16. package/dist/shaka-player.compiled.debug.js +119 -117
  17. package/dist/shaka-player.compiled.debug.map +4 -4
  18. package/dist/shaka-player.compiled.externs.js +3 -1
  19. package/dist/shaka-player.compiled.js +771 -768
  20. package/dist/shaka-player.compiled.map +4 -4
  21. package/dist/shaka-player.ui.d.ts +6 -1
  22. package/dist/shaka-player.ui.debug.d.ts +6 -1
  23. package/dist/shaka-player.ui.debug.externs.js +3 -1
  24. package/dist/shaka-player.ui.debug.js +135 -133
  25. package/dist/shaka-player.ui.debug.map +4 -4
  26. package/dist/shaka-player.ui.externs.js +3 -1
  27. package/dist/shaka-player.ui.js +896 -893
  28. package/dist/shaka-player.ui.map +4 -4
  29. package/docs/tutorials/welcome.md +2 -2
  30. package/externs/mux.js +3 -0
  31. package/lib/ads/client_side_ad_manager.js +5 -2
  32. package/lib/cast/cast_proxy.js +2 -1
  33. package/lib/cast/cast_receiver.js +19 -5
  34. package/lib/cast/cast_sender.js +2 -1
  35. package/lib/cast/cast_utils.js +16 -6
  36. package/lib/hls/hls_parser.js +27 -9
  37. package/lib/media/drm_engine.js +7 -6
  38. package/lib/media/media_source_engine.js +6 -0
  39. package/lib/media/playhead.js +24 -2
  40. package/lib/media/streaming_engine.js +3 -12
  41. package/lib/media/transmuxer.js +6 -0
  42. package/lib/offline/indexeddb/storage_mechanism.js +24 -0
  43. package/lib/player.js +24 -11
  44. package/lib/polyfill/media_capabilities.js +7 -6
  45. package/lib/text/ttml_text_parser.js +69 -93
  46. package/lib/text/vtt_text_parser.js +1 -1
  47. package/lib/util/error.js +7 -0
  48. package/lib/util/manifest_parser_utils.js +4 -0
  49. package/lib/util/periods.js +9 -9
  50. package/lib/util/platform.js +0 -10
  51. package/lib/util/xml_utils.js +45 -6
  52. package/package.json +1 -1
  53. package/roadmap.md +24 -6
  54. package/test/cast/cast_receiver_integration.js +97 -16
  55. package/test/cast/cast_receiver_unit.js +17 -12
  56. package/test/cast/cast_utils_unit.js +1 -0
  57. package/test/media/media_source_engine_integration.js +31 -0
  58. package/test/media/playhead_unit.js +41 -0
  59. package/test/media/streaming_engine_integration.js +13 -0
  60. package/test/media/streaming_engine_unit.js +36 -23
  61. package/test/offline/storage_integration.js +30 -0
  62. package/test/player_unit.js +87 -0
  63. package/test/test/util/simple_fakes.js +3 -0
  64. package/test/text/vtt_text_parser_unit.js +23 -1
  65. package/test/util/periods_unit.js +149 -11
  66. package/test/util/xml_utils_unit.js +64 -0
  67. package/ui/controls.js +4 -1
  68. package/ui/settings_menu.js +11 -2
@@ -66,8 +66,8 @@ python build/all.py
66
66
 
67
67
  Alternatively you can use a docker container:
68
68
  ```sh
69
- cd build/docker
70
- docker-compose run --rm shaka-compiler
69
+ docker build -t shaka-player-build build/docker
70
+ docker run -v $(pwd):/usr/src --user $(id -u):$(id -g) shaka-player-build
71
71
  ```
72
72
 
73
73
  The output is:
package/externs/mux.js CHANGED
@@ -74,6 +74,9 @@ muxjs.mp4.Transmuxer = class {
74
74
 
75
75
  /** Remove all handlers and clean up. */
76
76
  dispose() {}
77
+
78
+ /** Reset captions. */
79
+ resetCaptions() {}
77
80
  };
78
81
 
79
82
 
@@ -15,7 +15,6 @@ goog.require('goog.asserts');
15
15
  goog.require('shaka.ads.ClientSideAd');
16
16
  goog.require('shaka.util.IReleasable');
17
17
 
18
-
19
18
  /**
20
19
  * A class responsible for client-side ad interactions.
21
20
  * @implements {shaka.util.IReleasable}
@@ -218,7 +217,11 @@ shaka.ads.ClientSideAdManager = class {
218
217
  // TODO (ismena): Need a better inderstanding of what this does.
219
218
  // The docs say it's called to 'start playing the ads,' but I haven't
220
219
  // seen the ads actually play until requestAds() is called.
221
- this.imaAdsManager_.start();
220
+ // Note: We listen for a play event to avoid autoplay issues that might
221
+ // crash IMA.
222
+ this.video_.addEventListener('play', () => {
223
+ this.imaAdsManager_.start();
224
+ }, {once: true});
222
225
  } catch (adError) {
223
226
  // If there was a problem with the VAST response,
224
227
  // we we won't be getting an ad. Hide ad UI if we showed it already
@@ -705,7 +705,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
705
705
  // If we are casting, but the first update has not come in yet, use local
706
706
  // getters, but not local methods.
707
707
  if (this.sender_.isCasting() && !this.sender_.hasRemoteProperties()) {
708
- if (shaka.cast.CastUtils.PlayerGetterMethods[name]) {
708
+ if (shaka.cast.CastUtils.PlayerGetterMethods[name] ||
709
+ shaka.cast.CastUtils.LargePlayerGetterMethods[name]) {
709
710
  const value = /** @type {Object} */(this.localPlayer_)[name];
710
711
  goog.asserts.assert(typeof value == 'function',
711
712
  'only methods on Player');
@@ -518,6 +518,25 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
518
518
  update['video']['muted'] = systemVolume.muted;
519
519
  }
520
520
 
521
+ this.sendMessage_({
522
+ 'type': 'update',
523
+ 'update': update,
524
+ }, this.shakaBus_);
525
+
526
+ // Getters with large outputs each get sent in their own update message.
527
+ for (const name in shaka.cast.CastUtils.LargePlayerGetterMethods) {
528
+ const frequency = shaka.cast.CastUtils.LargePlayerGetterMethods[name];
529
+ if (this.updateNumber_ % frequency == 0) {
530
+ const update = {'player': {}};
531
+ update['player'][name] = /** @type {Object} */ (this.player_)[name]();
532
+
533
+ this.sendMessage_({
534
+ 'type': 'update',
535
+ 'update': update,
536
+ }, this.shakaBus_);
537
+ }
538
+ }
539
+
521
540
  // Only start progressing the update number once data is loaded,
522
541
  // just in case any of the "rarely changing" properties with less frequent
523
542
  // update messages changes significantly during the loading process.
@@ -525,11 +544,6 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
525
544
  this.updateNumber_ += 1;
526
545
  }
527
546
 
528
- this.sendMessage_({
529
- 'type': 'update',
530
- 'update': update,
531
- }, this.shakaBus_);
532
-
533
547
  this.maybeSendMediaInfoMessage_();
534
548
  }
535
549
 
@@ -370,7 +370,8 @@ shaka.cast.CastSender = class {
370
370
  return (...args) =>
371
371
  this.remoteAsyncCall_(targetName, property, ...args);
372
372
  }
373
- if (CastUtils.PlayerGetterMethods[property]) {
373
+ if (CastUtils.PlayerGetterMethods[property] ||
374
+ CastUtils.LargePlayerGetterMethods[property]) {
374
375
  return () => this.propertyGetter_(targetName, property);
375
376
  }
376
377
  }
@@ -307,9 +307,6 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
307
307
  'getAudioLanguagesAndRoles': 4,
308
308
  'getBufferFullness': 1,
309
309
  'getBufferedInfo': 2,
310
- // NOTE: The 'getSharedConfiguration' property is not proxied as it would
311
- // not be possible to share a reference.
312
- 'getConfiguration': 4,
313
310
  'getExpiration': 2,
314
311
  'getKeyStatuses': 2,
315
312
  // NOTE: The 'getManifest' property is not proxied, as it is very large.
@@ -318,9 +315,6 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
318
315
  'getPlaybackRate': 2,
319
316
  'getTextLanguages': 4,
320
317
  'getTextLanguagesAndRoles': 4,
321
- 'getTextTracks': 2,
322
- 'getStats': 5,
323
- 'getVariantTracks': 2,
324
318
  'getImageTracks': 2,
325
319
  'getThumbnails': 2,
326
320
  'isAudioOnly': 10,
@@ -334,6 +328,22 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
334
328
  };
335
329
 
336
330
 
331
+ /**
332
+ * Player getter methods with data large enough to be sent in their own update
333
+ * messages, to reduce the size of each message. The format of this is
334
+ * identical to PlayerGetterMethods.
335
+ * @const {!Object.<string, number>}
336
+ */
337
+ shaka.cast.CastUtils.LargePlayerGetterMethods = {
338
+ // NOTE: The 'getSharedConfiguration' property is not proxied as it would
339
+ // not be possible to share a reference.
340
+ 'getConfiguration': 4,
341
+ 'getStats': 5,
342
+ 'getTextTracks': 2,
343
+ 'getVariantTracks': 2,
344
+ };
345
+
346
+
337
347
  /**
338
348
  * Player getter methods that are proxied while casting, but only when casting
339
349
  * a livestream.
@@ -846,13 +846,19 @@ shaka.hls.HlsParser = class {
846
846
  shaka.util.ArrayUtils.remove(allCodecs, textCodecs);
847
847
  }
848
848
  if (audioGroupId) {
849
- const codecs = shaka.util.ManifestParserUtils.guessCodecs(
849
+ let codecs = shaka.util.ManifestParserUtils.guessCodecsSafe(
850
850
  ContentType.AUDIO, allCodecs);
851
+ if (!codecs) {
852
+ codecs = this.config_.hls.defaultAudioCodec;
853
+ }
851
854
  this.groupIdToCodecsMap_.set(audioGroupId, codecs);
852
855
  }
853
856
  if (videoGroupId) {
854
- const codecs = shaka.util.ManifestParserUtils.guessCodecs(
857
+ let codecs = shaka.util.ManifestParserUtils.guessCodecsSafe(
855
858
  ContentType.VIDEO, allCodecs);
859
+ if (!codecs) {
860
+ codecs = this.config_.hls.defaultVideoCodec;
861
+ }
856
862
  this.groupIdToCodecsMap_.set(videoGroupId, codecs);
857
863
  }
858
864
  }
@@ -2408,7 +2414,12 @@ shaka.hls.HlsParser = class {
2408
2414
  const extension = parsedUri.getPath().split('.').pop();
2409
2415
  const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
2410
2416
 
2411
- const mimeType = map[extension];
2417
+ let mimeType = map[extension];
2418
+ if (mimeType) {
2419
+ return mimeType;
2420
+ }
2421
+
2422
+ mimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
2412
2423
  if (mimeType) {
2413
2424
  return mimeType;
2414
2425
  }
@@ -2769,6 +2780,18 @@ shaka.hls.HlsParser.StreamInfo;
2769
2780
  shaka.hls.HlsParser.StreamInfos;
2770
2781
 
2771
2782
 
2783
+ /**
2784
+ * @const {!Object.<string, string>}
2785
+ * @private
2786
+ */
2787
+ shaka.hls.HlsParser.RAW_FORMATS_TO_MIME_TYPES_ = {
2788
+ 'aac': 'audio/aac',
2789
+ 'ac3': 'audio/ac3',
2790
+ 'ec3': 'audio/ec3',
2791
+ 'mp3': 'audio/mpeg',
2792
+ };
2793
+
2794
+
2772
2795
  /**
2773
2796
  * @const {!Object.<string, string>}
2774
2797
  * @private
@@ -2783,12 +2806,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2783
2806
  'cmfa': 'audio/mp4',
2784
2807
  // MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
2785
2808
  'ts': 'video/mp2t',
2786
-
2787
- // Raw formats:
2788
- 'aac': 'audio/aac',
2789
- 'ac3': 'audio/ac3',
2790
- 'ec3': 'audio/ec3',
2791
- 'mp3': 'audio/mpeg',
2792
2809
  };
2793
2810
 
2794
2811
 
@@ -2819,6 +2836,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
2819
2836
  'm4f': 'application/mp4',
2820
2837
  'cmft': 'application/mp4',
2821
2838
  'vtt': 'text/vtt',
2839
+ 'webvtt': 'txt/vtt',
2822
2840
  'ttml': 'application/ttml+xml',
2823
2841
  };
2824
2842
 
@@ -28,6 +28,7 @@ goog.require('shaka.util.StreamUtils');
28
28
  goog.require('shaka.util.StringUtils');
29
29
  goog.require('shaka.util.Timer');
30
30
  goog.require('shaka.util.Uint8ArrayUtils');
31
+ goog.require('shaka.util.XmlUtils');
31
32
 
32
33
 
33
34
  /** @implements {shaka.util.IDestroyable} */
@@ -406,6 +407,7 @@ shaka.media.DrmEngine = class {
406
407
  if (this.mediaKeysAttached_) {
407
408
  await this.mediaKeysAttached_;
408
409
 
410
+ this.destroyer_.ensureNotDestroyed();
409
411
  return;
410
412
  }
411
413
 
@@ -483,14 +485,12 @@ shaka.media.DrmEngine = class {
483
485
 
484
486
  /**
485
487
  * We can attach media keys before the playback actually begins when:
486
- * - Using legacy implementations requires MediaKeys to be set before
487
- * having webkitneedkey / msneedkey event, which will be translated as
488
- * an encrypted event by the polyfills
488
+ * - If we are not using FairPlay Modern EME
489
489
  * - Some initData already has been generated (through the manifest)
490
490
  * - In case of an offline session
491
491
  */
492
492
  if (manifestInitData ||
493
- shaka.util.Platform.isMediaKeysPolyfilled() ||
493
+ this.currentDrmInfo_.keySystem !== 'com.apple.fps' ||
494
494
  this.offlineSessionIds_.length) {
495
495
  await this.attachMediaKeys_();
496
496
  }
@@ -1506,7 +1506,8 @@ shaka.media.DrmEngine = class {
1506
1506
  return;
1507
1507
  }
1508
1508
  shaka.log.debug('Unwrapping PlayReady request.');
1509
- const dom = new DOMParser().parseFromString(xml, 'application/xml');
1509
+ const dom = shaka.util.XmlUtils.parseXmlString(xml, 'PlayReadyKeyMessage');
1510
+ goog.asserts.assert(dom, 'Failed to parse PlayReady XML!');
1510
1511
 
1511
1512
  // Set request headers.
1512
1513
  const headers = dom.getElementsByTagName('HttpHeader');
@@ -1590,7 +1591,7 @@ shaka.media.DrmEngine = class {
1590
1591
  hasExpiredKeys = true;
1591
1592
  }
1592
1593
 
1593
- const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId);
1594
+ const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId).slice(0, 32);
1594
1595
 
1595
1596
  this.keyStatusByKeyId_.set(keyIdHex, status);
1596
1597
  });
@@ -545,6 +545,12 @@ shaka.media.MediaSourceEngine = class {
545
545
  }
546
546
 
547
547
  if (this.transmuxers_[contentType]) {
548
+ // When seeked we should reset the transmuxer captionstreams
549
+ // so it does not ignores the captions from previous segments
550
+ if (seeked) {
551
+ this.transmuxers_[contentType].resetCaptions();
552
+ }
553
+
548
554
  const transmuxedData =
549
555
  await this.transmuxers_[contentType].transmux(data);
550
556
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
@@ -33,6 +33,14 @@ goog.requireType('shaka.media.PresentationTimeline');
33
33
  * @interface
34
34
  */
35
35
  shaka.media.Playhead = class {
36
+ /**
37
+ * Called when the Player is ready to begin playback. Anything that depends
38
+ * on setStartTime() should be done here, not in the constructor.
39
+ *
40
+ * @see https://github.com/shaka-project/shaka-player/issues/4244
41
+ */
42
+ ready() {}
43
+
36
44
  /**
37
45
  * Set the start time. If the content has already started playback, this will
38
46
  * be ignored.
@@ -90,6 +98,14 @@ shaka.media.SrcEqualsPlayhead = class {
90
98
 
91
99
  /** @private {shaka.util.EventManager} */
92
100
  this.eventManager_ = new shaka.util.EventManager();
101
+ }
102
+
103
+ /** @override */
104
+ ready() {
105
+ goog.asserts.assert(
106
+ this.mediaElement_ != null,
107
+ 'Playhead should not be released before calling ready()',
108
+ );
93
109
 
94
110
  // We listen for the loaded-data-event so that we know when we can
95
111
  // interact with |currentTime|.
@@ -102,6 +118,7 @@ shaka.media.SrcEqualsPlayhead = class {
102
118
  this.eventManager_.listenOnce(this.mediaElement_, 'seeking', () => {
103
119
  this.started_ = true;
104
120
  });
121
+
105
122
  const currentTime = this.mediaElement_.currentTime;
106
123
  // Using the currentTime allows using a negative number in Live HLS
107
124
  const newTime = Math.max(0, currentTime + this.startTime_);
@@ -239,7 +256,12 @@ shaka.media.MediaSourcePlayhead = class {
239
256
  /** @type {shaka.util.Timer} */
240
257
  this.checkWindowTimer_ = new shaka.util.Timer(() => {
241
258
  this.onPollWindow_();
242
- }).tickEvery(/* seconds= */ 0.25);
259
+ });
260
+ }
261
+
262
+ /** @override */
263
+ ready() {
264
+ this.checkWindowTimer_.tickEvery(/* seconds= */ 0.25);
243
265
  }
244
266
 
245
267
  /** @override */
@@ -294,7 +316,7 @@ shaka.media.MediaSourcePlayhead = class {
294
316
 
295
317
  /** @override */
296
318
  getStallsDetected() {
297
- return this.stallDetector_.getStallsDetected();
319
+ return this.stallDetector_ ? this.stallDetector_.getStallsDetected() : 0;
298
320
  }
299
321
 
300
322
  /** @override */
@@ -1395,16 +1395,6 @@ shaka.media.StreamingEngine = class {
1395
1395
  this.mediaStates_.delete(ContentType.TEXT);
1396
1396
  } else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
1397
1397
  this.handleQuotaExceeded_(mediaState, error);
1398
- } else if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
1399
- error.data && error.data[1] == 404) {
1400
- // The segment could not be found, does not exist, or is not available.
1401
- // In any case just try again.
1402
- // The current segment is not available. Schedule another update to
1403
- // fetch the segment again.
1404
- shaka.log.v2(logPrefix, 'segment not available.');
1405
- mediaState.performingUpdate = false;
1406
- mediaState.updateTimer = null;
1407
- this.scheduleUpdate_(mediaState, 1);
1408
1398
  } else {
1409
1399
  shaka.log.error(logPrefix, 'failed fetch and append: code=' +
1410
1400
  error.code);
@@ -1419,9 +1409,10 @@ shaka.media.StreamingEngine = class {
1419
1409
 
1420
1410
  /**
1421
1411
  * Clear per-stream error states and retry any failed streams.
1412
+ * @param {number} delaySeconds
1422
1413
  * @return {boolean} False if unable to retry.
1423
1414
  */
1424
- retry() {
1415
+ retry(delaySeconds) {
1425
1416
  if (this.destroyer_.destroyed()) {
1426
1417
  shaka.log.error('Unable to retry after StreamingEngine is destroyed!');
1427
1418
  return false;
@@ -1438,7 +1429,7 @@ shaka.media.StreamingEngine = class {
1438
1429
  if (mediaState.hasError) {
1439
1430
  shaka.log.info(logPrefix, 'Retrying after failure...');
1440
1431
  mediaState.hasError = false;
1441
- this.scheduleUpdate_(mediaState, 0.1);
1432
+ this.scheduleUpdate_(mediaState, delaySeconds);
1442
1433
  }
1443
1434
  }
1444
1435
 
@@ -182,6 +182,12 @@ shaka.media.Transmuxer = class {
182
182
  return this.transmuxPromise_;
183
183
  }
184
184
 
185
+ /**
186
+ * Reset captions from Transport stream to MP4, using the mux.js library.
187
+ */
188
+ resetCaptions() {
189
+ this.muxTransmuxer_.resetCaptions();
190
+ }
185
191
 
186
192
  /**
187
193
  * Handles the 'data' event of the transmuxer.
@@ -16,6 +16,7 @@ goog.require('shaka.offline.indexeddb.V5StorageCell');
16
16
  goog.require('shaka.util.Error');
17
17
  goog.require('shaka.util.PublicPromise');
18
18
  goog.require('shaka.util.Platform');
19
+ goog.require('shaka.util.Timer');
19
20
 
20
21
 
21
22
  /**
@@ -55,8 +56,25 @@ shaka.offline.indexeddb.StorageMechanism = class {
55
56
  const version = shaka.offline.indexeddb.StorageMechanism.VERSION;
56
57
 
57
58
  const p = new shaka.util.PublicPromise();
59
+
60
+ // Add a timeout mechanism, for the (rare?) case where no callbacks are
61
+ // called at all, so that this method doesn't hang forever.
62
+ let timedOut = false;
63
+ const timeOutTimer = new shaka.util.Timer(() => {
64
+ timedOut = true;
65
+ p.reject(new shaka.util.Error(
66
+ shaka.util.Error.Severity.CRITICAL,
67
+ shaka.util.Error.Category.STORAGE,
68
+ shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
69
+ });
70
+ timeOutTimer.tickAfter(2);
71
+
58
72
  const open = window.indexedDB.open(name, version);
59
73
  open.onsuccess = (event) => {
74
+ if (timedOut) {
75
+ // Too late, we have already given up on opening the storage mechanism.
76
+ return;
77
+ }
60
78
  const db = open.result;
61
79
  this.db_ = db;
62
80
  this.v1_ = shaka.offline.indexeddb.StorageMechanism.createV1_(db);
@@ -68,6 +86,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
68
86
  this.v5_ = shaka.offline.indexeddb.StorageMechanism.createV5_(db);
69
87
  this.sessions_ =
70
88
  shaka.offline.indexeddb.StorageMechanism.createEmeSessionCell_(db);
89
+ timeOutTimer.stop();
71
90
  p.resolve();
72
91
  };
73
92
  open.onupgradeneeded = (event) => {
@@ -75,11 +94,16 @@ shaka.offline.indexeddb.StorageMechanism = class {
75
94
  this.createStores_(open.result);
76
95
  };
77
96
  open.onerror = (event) => {
97
+ if (timedOut) {
98
+ // Too late, we have already given up on opening the storage mechanism.
99
+ return;
100
+ }
78
101
  p.reject(new shaka.util.Error(
79
102
  shaka.util.Error.Severity.CRITICAL,
80
103
  shaka.util.Error.Category.STORAGE,
81
104
  shaka.util.Error.Code.INDEXED_DB_ERROR,
82
105
  open.error));
106
+ timeOutTimer.stop();
83
107
 
84
108
  // Firefox will raise an error on the main thread unless we stop it here.
85
109
  event.preventDefault();
package/lib/player.js CHANGED
@@ -2055,6 +2055,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2055
2055
  shaka.util.StreamUtils.variantToTrack(initialVariant));
2056
2056
  }
2057
2057
 
2058
+ this.playhead_.ready();
2059
+
2058
2060
  // Decide if text should be shown automatically.
2059
2061
  // similar to video/audio track, we would skip switch initial text track
2060
2062
  // if user already pick text track (via selectTextTrack api)
@@ -2392,6 +2394,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2392
2394
  HTMLMediaElement.HAVE_METADATA,
2393
2395
  this.loadEventManager_,
2394
2396
  () => {
2397
+ this.playhead_.ready();
2395
2398
  fullyLoaded.resolve();
2396
2399
  });
2397
2400
 
@@ -4834,12 +4837,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4834
4837
  * If the player has loaded content, and streaming seen an error, but the
4835
4838
  * could not resume streaming, this will return <code>false</code>.
4836
4839
  *
4840
+ * @param {number=} retryDelaySeconds
4837
4841
  * @return {boolean}
4838
4842
  * @export
4839
4843
  */
4840
- retryStreaming() {
4844
+ retryStreaming(retryDelaySeconds = 0.1) {
4841
4845
  return this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE ?
4842
- this.streamingEngine_.retry() :
4846
+ this.streamingEngine_.retry(retryDelaySeconds) :
4843
4847
  false;
4844
4848
  }
4845
4849
 
@@ -4935,17 +4939,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4935
4939
  * @private
4936
4940
  */
4937
4941
  defaultStreamingFailureCallback_(error) {
4938
- const retryErrorCodes = [
4939
- shaka.util.Error.Code.BAD_HTTP_STATUS,
4940
- shaka.util.Error.Code.HTTP_ERROR,
4941
- shaka.util.Error.Code.TIMEOUT,
4942
- ];
4942
+ // For live streams, we retry streaming automatically for certain errors.
4943
+ // For VOD streams, all streaming failures are fatal.
4944
+ if (!this.isLive()) {
4945
+ return;
4946
+ }
4943
4947
 
4944
- if (this.isLive() && retryErrorCodes.includes(error.code)) {
4945
- error.severity = shaka.util.Error.Severity.RECOVERABLE;
4948
+ let retryDelaySeconds = null;
4949
+ if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS ||
4950
+ error.code == shaka.util.Error.Code.HTTP_ERROR) {
4951
+ // These errors can be near-instant, so delay a bit before retrying.
4952
+ retryDelaySeconds = 1;
4953
+ } else if (error.code == shaka.util.Error.Code.TIMEOUT) {
4954
+ // We already waited for a timeout, so retry quickly.
4955
+ retryDelaySeconds = 0.1;
4956
+ }
4946
4957
 
4958
+ if (retryDelaySeconds != null) {
4959
+ error.severity = shaka.util.Error.Severity.RECOVERABLE;
4947
4960
  shaka.log.warning('Live streaming error. Retrying automatically...');
4948
- this.retryStreaming();
4961
+ this.retryStreaming(retryDelaySeconds);
4949
4962
  }
4950
4963
  }
4951
4964
 
@@ -6575,7 +6588,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6575
6588
  * @define {string} A version number taken from git at compile time.
6576
6589
  * @export
6577
6590
  */
6578
- shaka.Player.version = 'v4.1.5-uncompiled';
6591
+ shaka.Player.version = 'v4.1.7-uncompiled';
6579
6592
 
6580
6593
  // Initialize the deprecation system using the version string we just set
6581
6594
  // on the player.
@@ -24,8 +24,9 @@ shaka.polyfill.MediaCapabilities = class {
24
24
  * @export
25
25
  */
26
26
  static install() {
27
- // Since MediaCapabilities is not fully supported on some Chromecast yet,
28
- // we should always install polyfill for all Chromecast not Android-based.
27
+ // Since MediaCapabilities implementation is buggy on the Chromecast
28
+ // platform (see https://github.com/shaka-project/shaka-player/issues/4569),
29
+ // we should always install polyfills on all Chromecast models.
29
30
  // TODO: re-evaluate MediaCapabilities in the future versions of Chromecast.
30
31
  // Since MediaCapabilities implementation is buggy in Apple browsers, we
31
32
  // should always install polyfill for Apple browsers.
@@ -43,18 +44,18 @@ shaka.polyfill.MediaCapabilities = class {
43
44
  // should always install polyfill for Tizen browsers.
44
45
  // Since MediaCapabilities implementation is buggy in WebOS browsers, we
45
46
  // should always install polyfill for WebOS browsers.
47
+ // Since MediaCapabilities implementation is buggy in EOS browsers, we
48
+ // should always install polyfill for EOS browsers.
46
49
  let canUseNativeMCap = true;
47
50
  if (shaka.util.Platform.isApple() ||
48
51
  shaka.util.Platform.isPS5() ||
49
52
  shaka.util.Platform.isPS4() ||
50
53
  shaka.util.Platform.isWebOS() ||
51
54
  shaka.util.Platform.isTizen() ||
52
- shaka.util.Platform.isChromecast()) {
55
+ shaka.util.Platform.isChromecast() ||
56
+ shaka.util.Platform.isEOS()) {
53
57
  canUseNativeMCap = false;
54
58
  }
55
- if (shaka.util.Platform.isAndroidCastDevice()) {
56
- canUseNativeMCap = true;
57
- }
58
59
  if (canUseNativeMCap && navigator.mediaCapabilities) {
59
60
  shaka.log.info(
60
61
  'MediaCapabilities: Native mediaCapabilities support found.');