shaka-player 4.10.19 → 4.10.21

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.
@@ -199,6 +199,10 @@ shaka.ads.InterstitialAdManager = class {
199
199
  if (this.adContainer_) {
200
200
  shaka.util.Dom.removeAllChildren(this.adContainer_);
201
201
  }
202
+ if (this.pollTimer_) {
203
+ this.pollTimer_.stop();
204
+ this.pollTimer_ = null;
205
+ }
202
206
  this.player_.destroy();
203
207
  }
204
208
 
@@ -127,6 +127,9 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
127
127
  /** @private {!shaka.util.Stats} */
128
128
  this.stats_ = new shaka.util.Stats();
129
129
 
130
+ /** @private {!shaka.util.PublicPromise} */
131
+ this.manifestPromise_ = new shaka.util.PublicPromise();
132
+
130
133
  /** @private {!shaka.util.PublicPromise} */
131
134
  this.successPromise_ = new shaka.util.PublicPromise();
132
135
 
@@ -490,10 +493,15 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
490
493
  this.assetUri_, this.manifestPlayerInterface_);
491
494
  }
492
495
 
496
+ this.manifestPromise_.resolve();
497
+
493
498
  // This event is fired after the manifest is parsed, but before any
494
499
  // filtering takes place.
495
500
  const event =
496
501
  this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
502
+ // Delay event to ensure manifest has been properly propagated
503
+ // to the player.
504
+ await Promise.resolve();
497
505
  this.dispatchEvent(event);
498
506
 
499
507
  // We require all manifests to have at least one variant.
@@ -700,6 +708,18 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
700
708
  return this.successPromise_;
701
709
  }
702
710
 
711
+ /**
712
+ * Waits for the manifest to be loaded (or to fail with an error).
713
+ * @return {!Promise}
714
+ */
715
+ waitForManifest() {
716
+ const promises = [
717
+ this.manifestPromise_,
718
+ this.successPromise_,
719
+ ];
720
+ return Promise.race(promises);
721
+ }
722
+
703
723
  /**
704
724
  * Releases or stops all non-entrusted resources.
705
725
  *
@@ -82,7 +82,7 @@ shaka.media.StreamingEngine = class {
82
82
  this.deferredCloseSegmentIndex_ = new Map();
83
83
 
84
84
  /** @private {number} */
85
- this.bufferingGoalScale_ = 1;
85
+ this.bufferingScale_ = 1;
86
86
 
87
87
  /** @private {?shaka.extern.Variant} */
88
88
  this.currentVariant_ = null;
@@ -148,6 +148,9 @@ shaka.media.StreamingEngine = class {
148
148
 
149
149
  /** @private {number} */
150
150
  this.playRangeEnd_ = Infinity;
151
+
152
+ /** @private {?shaka.media.StreamingEngine.MediaState_} */
153
+ this.lastTextMediaStateBeforeUnload_ = null;
151
154
  }
152
155
 
153
156
  /** @override */
@@ -380,6 +383,8 @@ shaka.media.StreamingEngine = class {
380
383
  if (state) {
381
384
  this.cancelUpdate_(state);
382
385
  this.abortOperations_(state).catch(() => {});
386
+ this.lastTextMediaStateBeforeUnload_ =
387
+ this.mediaStates_.get(ContentType.TEXT);
383
388
  this.mediaStates_.delete(ContentType.TEXT);
384
389
  }
385
390
  this.currentTextStream_ = null;
@@ -476,6 +481,7 @@ shaka.media.StreamingEngine = class {
476
481
  * @param {shaka.extern.Stream} textStream
477
482
  */
478
483
  async switchTextStream(textStream) {
484
+ this.lastTextMediaStateBeforeUnload_ = null;
479
485
  this.currentTextStream_ = textStream;
480
486
 
481
487
  if (!this.startupComplete_) {
@@ -1357,7 +1363,7 @@ shaka.media.StreamingEngine = class {
1357
1363
  this.config_.bufferingGoal);
1358
1364
 
1359
1365
  const scaledBufferingGoal = Math.max(1,
1360
- unscaledBufferingGoal * this.bufferingGoalScale_);
1366
+ unscaledBufferingGoal * this.bufferingScale_);
1361
1367
 
1362
1368
  // Check if we've buffered to the end of the presentation.
1363
1369
  const timeUntilEnd =
@@ -1800,7 +1806,7 @@ shaka.media.StreamingEngine = class {
1800
1806
  }
1801
1807
  this.mediaStates_.delete(ContentType.TEXT);
1802
1808
  } else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
1803
- this.handleQuotaExceeded_(mediaState, error);
1809
+ await this.handleQuotaExceeded_(mediaState, error);
1804
1810
  } else {
1805
1811
  shaka.log.error(logPrefix, 'failed fetch and append: code=' +
1806
1812
  error.code);
@@ -1871,9 +1877,10 @@ shaka.media.StreamingEngine = class {
1871
1877
  *
1872
1878
  * @param {shaka.media.StreamingEngine.MediaState_} mediaState
1873
1879
  * @param {!shaka.util.Error} error
1880
+ * @return {!Promise}
1874
1881
  * @private
1875
1882
  */
1876
- handleQuotaExceeded_(mediaState, error) {
1883
+ async handleQuotaExceeded_(mediaState, error) {
1877
1884
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1878
1885
 
1879
1886
  // The segment cannot fit into the SourceBuffer. Ideally, MediaSource would
@@ -1895,18 +1902,22 @@ shaka.media.StreamingEngine = class {
1895
1902
  });
1896
1903
 
1897
1904
  if (!waitingForAnotherStreamToRecover) {
1905
+ const maxDisabledTime = this.getDisabledTime_(error);
1906
+ if (maxDisabledTime) {
1907
+ shaka.log.debug(logPrefix, 'Disabling stream due to quota', error);
1908
+ }
1898
1909
  const handled = this.playerInterface_.disableStream(
1899
- mediaState.stream, this.config_.maxDisabledTime);
1910
+ mediaState.stream, maxDisabledTime);
1900
1911
  if (handled) {
1901
1912
  return;
1902
1913
  }
1903
1914
  // Reduction schedule: 80%, 60%, 40%, 20%, 16%, 12%, 8%, 4%, fail.
1904
1915
  // Note: percentages are used for comparisons to avoid rounding errors.
1905
- const percentBefore = Math.round(100 * this.bufferingGoalScale_);
1916
+ const percentBefore = Math.round(100 * this.bufferingScale_);
1906
1917
  if (percentBefore > 20) {
1907
- this.bufferingGoalScale_ -= 0.2;
1918
+ this.bufferingScale_ -= 0.2;
1908
1919
  } else if (percentBefore > 4) {
1909
- this.bufferingGoalScale_ -= 0.04;
1920
+ this.bufferingScale_ -= 0.04;
1910
1921
  } else {
1911
1922
  shaka.log.error(
1912
1923
  logPrefix, 'MediaSource threw QuotaExceededError too many times');
@@ -1915,12 +1926,14 @@ shaka.media.StreamingEngine = class {
1915
1926
  this.playerInterface_.onError(error);
1916
1927
  return;
1917
1928
  }
1918
- const percentAfter = Math.round(100 * this.bufferingGoalScale_);
1929
+ const percentAfter = Math.round(100 * this.bufferingScale_);
1919
1930
  shaka.log.warning(
1920
1931
  logPrefix,
1921
1932
  'MediaSource threw QuotaExceededError:',
1922
1933
  'reducing buffering goals by ' + (100 - percentAfter) + '%');
1923
1934
  mediaState.recovering = true;
1935
+ const presentationTime = this.playerInterface_.getPresentationTime();
1936
+ await this.evict_(mediaState, presentationTime);
1924
1937
  } else {
1925
1938
  shaka.log.debug(
1926
1939
  logPrefix,
@@ -2521,12 +2534,16 @@ shaka.media.StreamingEngine = class {
2521
2534
  // Use the max segment duration, if it is longer than the bufferBehind, to
2522
2535
  // avoid accidentally clearing too much data when dealing with a manifest
2523
2536
  // with a long keyframe interval.
2524
- const bufferBehind = Math.max(this.config_.bufferBehind,
2537
+ const bufferBehind = Math.max(
2538
+ this.config_.bufferBehind * this.bufferingScale_,
2525
2539
  this.manifest_.presentationTimeline.getMaxSegmentDuration());
2526
2540
 
2527
2541
  const startTime =
2528
2542
  this.playerInterface_.mediaSourceEngine.bufferStart(mediaState.type);
2529
2543
  if (startTime == null) {
2544
+ if (this.lastTextMediaStateBeforeUnload_ == mediaState) {
2545
+ this.lastTextMediaStateBeforeUnload_ = null;
2546
+ }
2530
2547
  shaka.log.v2(logPrefix,
2531
2548
  'buffer behind okay because nothing buffered:',
2532
2549
  'presentationTime=' + presentationTime,
@@ -2561,6 +2578,11 @@ shaka.media.StreamingEngine = class {
2561
2578
 
2562
2579
  this.destroyer_.ensureNotDestroyed();
2563
2580
  shaka.log.v1(logPrefix, 'evicted ' + overflow + ' seconds');
2581
+
2582
+ if (this.lastTextMediaStateBeforeUnload_) {
2583
+ await this.evict_(this.lastTextMediaStateBeforeUnload_, presentationTime);
2584
+ this.destroyer_.ensureNotDestroyed();
2585
+ }
2564
2586
  }
2565
2587
 
2566
2588
 
@@ -2806,18 +2828,26 @@ shaka.media.StreamingEngine = class {
2806
2828
  * @private
2807
2829
  */
2808
2830
  async handleStreamingError_(mediaState, error) {
2831
+ const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
2832
+
2809
2833
  // If we invoke the callback right away, the application could trigger a
2810
2834
  // rapid retry cycle that could be very unkind to the server. Instead,
2811
2835
  // use the backoff system to delay and backoff the error handling.
2812
2836
  await this.failureCallbackBackoff_.attempt();
2813
2837
  this.destroyer_.ensureNotDestroyed();
2814
- // Try to recover from network errors
2815
- if (error.category === shaka.util.Error.Category.NETWORK) {
2838
+
2839
+ // Try to recover from network errors, but not timeouts.
2840
+ // See https://github.com/shaka-project/shaka-player/issues/7368
2841
+ if (error.category === shaka.util.Error.Category.NETWORK &&
2842
+ error.code != shaka.util.Error.Code.TIMEOUT) {
2816
2843
  if (mediaState.restoreStreamAfterTrickPlay) {
2817
2844
  this.setTrickPlay(/* on= */ false);
2818
2845
  return;
2819
2846
  }
2820
2847
  const maxDisabledTime = this.getDisabledTime_(error);
2848
+ if (maxDisabledTime) {
2849
+ shaka.log.debug(logPrefix, 'Disabling stream due to error', error);
2850
+ }
2821
2851
  error.handled = this.playerInterface_.disableStream(
2822
2852
  mediaState.stream, maxDisabledTime);
2823
2853
 
@@ -2842,6 +2872,7 @@ shaka.media.StreamingEngine = class {
2842
2872
 
2843
2873
  /**
2844
2874
  * @param {!shaka.util.Error} error
2875
+ * @return {number}
2845
2876
  * @private
2846
2877
  */
2847
2878
  getDisabledTime_(error) {
package/lib/player.js CHANGED
@@ -1421,13 +1421,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1421
1421
  //
1422
1422
  // DrmEngine requires this to be done before we destroy DrmEngine itself.
1423
1423
  if (this.video_ && this.video_.src) {
1424
- // TODO: Investigate this more. Only reproduces on Firefox 69.
1425
- // Introduce a delay before detaching the video source. We are seeing
1426
- // spurious Promise rejections involving an AbortError in our tests
1427
- // otherwise.
1428
- await new Promise(
1429
- (resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
1430
-
1431
1424
  this.video_.removeAttribute('src');
1432
1425
  this.video_.load();
1433
1426
  }
@@ -1664,6 +1657,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1664
1657
  await this.srcEqualsInner_(startTimeOfLoad, mimeType);
1665
1658
  }, 'srcEqualsInner_');
1666
1659
  } else {
1660
+ // Wait for the manifest to be parsed.
1661
+ await mutexWrapOperation(async () => {
1662
+ await preloadManager.waitForManifest();
1663
+ // Retrieve the manifest. This is specifically put before the media
1664
+ // source engine is initialized, for the benefit of event handlers.
1665
+ this.parserFactory_ = preloadManager.getParserFactory();
1666
+ this.parser_ = preloadManager.receiveParser();
1667
+ this.manifest_ = preloadManager.getManifest();
1668
+ }, 'waitForFinish');
1669
+
1667
1670
  if (!this.mediaSourceEngine_) {
1668
1671
  await mutexWrapOperation(async () => {
1669
1672
  await this.initializeMediaSourceEngineInner_();
@@ -1678,14 +1681,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1678
1681
  // Get manifest and associated values from preloader.
1679
1682
  this.config_ = preloadManager.getConfiguration();
1680
1683
  this.manifestFilterer_ = preloadManager.getManifestFilterer();
1681
- this.parserFactory_ = preloadManager.getParserFactory();
1682
- this.parser_ = preloadManager.receiveParser();
1683
1684
  if (this.parser_ && this.parser_.setMediaElement && this.video_) {
1684
1685
  this.parser_.setMediaElement(this.video_);
1685
1686
  }
1686
1687
  this.regionTimeline_ = preloadManager.receiveRegionTimeline();
1687
1688
  this.qualityObserver_ = preloadManager.getQualityObserver();
1688
- this.manifest_ = preloadManager.getManifest();
1689
1689
  const currentAdaptationSetCriteria =
1690
1690
  preloadManager.getCurrentAdaptationSetCriteria();
1691
1691
  if (currentAdaptationSetCriteria) {
@@ -7859,7 +7859,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7859
7859
  * @export
7860
7860
  */
7861
7861
  // eslint-disable-next-line no-useless-concat, max-len
7862
- shaka.Player.version = 'v4.10.19' + '-uncompiled'; // x-release-please-version
7862
+ shaka.Player.version = 'v4.10.21' + '-uncompiled'; // x-release-please-version
7863
7863
 
7864
7864
  // Initialize the deprecation system using the version string we just set
7865
7865
  // on the player.
@@ -10,6 +10,7 @@ goog.require('shaka.log');
10
10
  goog.require('shaka.media.Capabilities');
11
11
  goog.require('shaka.polyfill');
12
12
  goog.require('shaka.util.DrmUtils');
13
+ goog.require('shaka.util.MimeUtils');
13
14
  goog.require('shaka.util.Platform');
14
15
 
15
16
 
@@ -222,6 +223,7 @@ shaka.polyfill.MediaCapabilities = class {
222
223
  * @private
223
224
  */
224
225
  static async checkDrmSupport_(videoConfig, audioConfig, mcapKeySystemConfig) {
226
+ const MimeUtils = shaka.util.MimeUtils;
225
227
  const audioCapabilities = [];
226
228
  const videoCapabilities = [];
227
229
 
@@ -277,8 +279,12 @@ shaka.polyfill.MediaCapabilities = class {
277
279
  mediaKeySystemConfig.videoCapabilities = videoCapabilities;
278
280
  }
279
281
 
280
- const videoCodec = videoConfig ? videoConfig.contentType : '';
281
- const audioCodec = audioConfig ? audioConfig.contentType : '';
282
+ const videoMimeType = videoConfig ? videoConfig.contentType : '';
283
+ const audioMimeType = audioConfig ? audioConfig.contentType : '';
284
+ const videoCodec = MimeUtils.getBasicType(videoMimeType) + ';' +
285
+ MimeUtils.getCodecBase(videoMimeType);
286
+ const audioCodec = MimeUtils.getBasicType(audioMimeType) + ';' +
287
+ MimeUtils.getCodecBase(audioMimeType);
282
288
  const keySystem = mcapKeySystemConfig.keySystem;
283
289
 
284
290
  /** @type {MediaKeySystemAccess} */
@@ -75,6 +75,9 @@ shaka.util.PeriodCombiner = class {
75
75
  this.textStreams_ = [];
76
76
  this.imageStreams_ = [];
77
77
  this.variants_ = [];
78
+ this.multiTypeVariantsAllowed_ = false;
79
+ this.useStreamOnce_ = false;
80
+ this.usedPeriodIds_.clear();
78
81
  }
79
82
 
80
83
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.10.19",
4
+ "version": "4.10.21",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -1,30 +1,49 @@
1
1
  {
2
+ "AD_DURATION": "廣告長度",
3
+ "AD_PROGRESS": "廣告 [AD_ON]/[NUM_ADS]",
4
+ "AD_STATISTICS": "廣告統計資料",
5
+ "AD_TIME": "廣告:[AD_TIME]",
6
+ "AIRPLAY": "AirPlay",
2
7
  "AUTO_QUALITY": "自動",
3
8
  "BACK": "返回",
4
9
  "CAPTIONS": "字幕",
5
10
  "CAST": "投放…",
6
- "ENTER_PICTURE_IN_PICTURE": "進入畫中畫",
7
- "EXIT_FULL_SCREEN": "結束全螢幕",
11
+ "CHAPTERS": "章節",
12
+ "DOWNLOAD_VIDEO_FRAME": "儲存影片影格",
13
+ "ENTER_LOOP_MODE": "循環播放目前的影片",
14
+ "ENTER_PICTURE_IN_PICTURE": "進入子母畫面",
15
+ "EXIT_FULL_SCREEN": "退出全螢幕模式",
16
+ "EXIT_LOOP_MODE": "停止循環播放目前的影片",
8
17
  "EXIT_PICTURE_IN_PICTURE": "退出畫中畫",
9
18
  "FAST_FORWARD": "快轉",
10
19
  "FULL_SCREEN": "全螢幕",
11
20
  "LANGUAGE": "語言",
12
21
  "LIVE": "直播",
13
- "MORE_SETTINGS": "更多設定",
22
+ "LOOP": "循環播放",
23
+ "MORE_SETTINGS": "顯示更多設定",
14
24
  "MULTIPLE_LANGUAGES": "多種語言",
15
- "MUTE": "靜音",
25
+ "MUTE": "静音",
16
26
  "NOT_APPLICABLE": "不適用",
17
- "OFF": "未選取",
18
- "ON": "",
27
+ "OFF": "關閉",
28
+ "ON": "開啟",
19
29
  "PAUSE": "暫停",
20
30
  "PICTURE_IN_PICTURE": "畫中畫",
21
31
  "PLAY": "播放",
22
- "RESOLUTION": "",
32
+ "PLAYBACK_RATE": "播放速度",
33
+ "QUALITY": "音質",
34
+ "RECENTER_VR": "重新置中",
35
+ "REPLAY": "重播",
36
+ "RESOLUTION": "解析度",
23
37
  "REWIND": "倒帶",
24
- "SEEK": "搜尋滑桿",
25
- "SKIP_TO_LIVE": "直播",
38
+ "SEEK": "進度",
39
+ "SKIP_AD": "略過廣告",
40
+ "SKIP_TO_LIVE": "跳至当前直播",
41
+ "STATISTICS": "統計資料",
42
+ "SUBTITLE_FORCED": "強制",
43
+ "SURROUND": "環迴音效",
44
+ "TOGGLE_STEREOSCOPIC": "切換立體視覺",
26
45
  "UNDETERMINED_LANGUAGE": "不明",
27
- "UNMUTE": "解除靜音",
28
- "UNRECOGNIZED_LANGUAGE": "不明",
46
+ "UNMUTE": "取消靜音",
47
+ "UNRECOGNIZED_LANGUAGE": "無法辨識",
29
48
  "VOLUME": "音量"
30
49
  }