shaka-player 4.9.28 → 4.9.30

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.
@@ -1285,11 +1285,13 @@ shaka.dash.DashParser = class {
1285
1285
  for (let i = 0; i < periodNodes.length; i++) {
1286
1286
  const elem = periodNodes[i];
1287
1287
  const next = periodNodes[i + 1];
1288
- const start = /** @type {number} */ (
1288
+ let start = /** @type {number} */ (
1289
1289
  TXml.parseAttr(elem, 'start', TXml.parseDuration, prevEnd));
1290
1290
  const periodId = elem.attributes['id'];
1291
1291
  const givenDuration =
1292
1292
  TXml.parseAttr(elem, 'duration', TXml.parseDuration);
1293
+ start = (i == 0 && start == 0 && this.isTransitionFromDynamicToStatic_) ?
1294
+ seekRangeStart : start;
1293
1295
 
1294
1296
  let periodDuration = null;
1295
1297
  if (next) {
@@ -1299,7 +1301,7 @@ shaka.dash.DashParser = class {
1299
1301
  const nextStart =
1300
1302
  TXml.parseAttr(next, 'start', TXml.parseDuration);
1301
1303
  if (nextStart != null) {
1302
- periodDuration = nextStart - start;
1304
+ periodDuration = nextStart - start + seekRangeStart;
1303
1305
  }
1304
1306
  } else if (presentationDuration != null) {
1305
1307
  // "The Period extends until the Period.start of the next Period, or
@@ -2575,25 +2577,51 @@ shaka.dash.DashParser = class {
2575
2577
  return intValue;
2576
2578
  }
2577
2579
 
2580
+ case 'tag:dolby.com,2015:dash:audio_channel_configuration:2015': {
2581
+ // ETSI TS 103 190-2 v1.2.1, Annex G.3
2582
+ // LSB-to-MSB order
2583
+ const channelCountMapping =
2584
+ [2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2];
2585
+ const hexValue = parseInt(value, 16);
2586
+ if (!hexValue) { // 0 or NaN
2587
+ shaka.log.warning('Channel parsing failure! ' +
2588
+ 'Ignoring scheme and value', scheme, value);
2589
+ continue;
2590
+ }
2591
+ let numBits = 0;
2592
+ for (let i = 0; i < channelCountMapping.length; i++) {
2593
+ if (hexValue & (1<<i)) {
2594
+ numBits += channelCountMapping[i];
2595
+ }
2596
+ }
2597
+ if (numBits) {
2598
+ return numBits;
2599
+ }
2600
+ continue;
2601
+ }
2602
+
2578
2603
  case 'tag:dolby.com,2014:dash:audio_channel_configuration:2011':
2579
2604
  case 'urn:dolby:dash:audio_channel_configuration:2011': {
2580
- // A hex-encoded 16-bit integer, in which each bit represents a
2581
- // channel.
2582
- let hexValue = parseInt(value, 16);
2605
+ // Defined by https://ott.dolby.com/OnDelKits/DDP/Dolby_Digital_Plus_Online_Delivery_Kit_v1.5/Documentation/Content_Creation/SDM/help_files/topics/ddp_mpeg_dash_c_mpd_auchlconfig.html
2606
+ // keep list in order of the spec; reverse for LSB-to-MSB order
2607
+ const channelCountMapping =
2608
+ [1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 2, 1, 1].reverse();
2609
+ const hexValue = parseInt(value, 16);
2583
2610
  if (!hexValue) { // 0 or NaN
2584
2611
  shaka.log.warning('Channel parsing failure! ' +
2585
2612
  'Ignoring scheme and value', scheme, value);
2586
2613
  continue;
2587
2614
  }
2588
- // Count the 1-bits in hexValue.
2589
2615
  let numBits = 0;
2590
- while (hexValue) {
2591
- if (hexValue & 1) {
2592
- ++numBits;
2616
+ for (let i = 0; i < channelCountMapping.length; i++) {
2617
+ if (hexValue & (1<<i)) {
2618
+ numBits += channelCountMapping[i];
2593
2619
  }
2594
- hexValue >>= 1;
2595
2620
  }
2596
- return numBits;
2621
+ if (numBits) {
2622
+ return numBits;
2623
+ }
2624
+ continue;
2597
2625
  }
2598
2626
 
2599
2627
  // Defined by https://dashif.org/identifiers/audio_source_metadata/ and clause 8.2, in ISO/IEC 23001-8.
@@ -778,7 +778,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
778
778
  if (currentTimeline.length) {
779
779
  const lastCurrentEntry = currentTimeline[currentTimeline.length - 1];
780
780
  newEntries = info.timeline.filter((entry) => {
781
- return entry.start >= lastCurrentEntry.end;
781
+ return entry.end > lastCurrentEntry.end;
782
782
  });
783
783
  } else {
784
784
  newEntries = info.timeline.slice();
@@ -743,6 +743,17 @@ shaka.media.DrmEngine = class {
743
743
  return;
744
744
  }
745
745
  }
746
+ let duplicate = false;
747
+ this.storedPersistentSessions_.forEach((metadata, sessionId) => {
748
+ if (!duplicate &&
749
+ shaka.util.BufferUtils.equal(initData, metadata.initData)) {
750
+ duplicate = true;
751
+ }
752
+ });
753
+ if (duplicate) {
754
+ shaka.log.debug('Ignoring duplicate init data.');
755
+ return;
756
+ }
746
757
  }
747
758
 
748
759
  // If there are pre-existing sessions that have all been loaded
@@ -116,7 +116,7 @@ shaka.media.MediaSourceEngine = class {
116
116
  /** @private {boolean} */
117
117
  this.playbackHasBegun_ = false;
118
118
 
119
- /** @private {(MediaSource|ManagedMediaSource)} */
119
+ /** @private {MediaSource} */
120
120
  this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
121
121
 
122
122
  /** @private {boolean} */
@@ -175,13 +175,16 @@ shaka.media.MediaSourceEngine = class {
175
175
  * Replaced by unit tests.
176
176
  *
177
177
  * @param {!shaka.util.PublicPromise} p
178
- * @return {!(MediaSource|ManagedMediaSource)}
178
+ * @return {!MediaSource}
179
179
  */
180
180
  createMediaSource(p) {
181
+ /** @type {!MediaSource} */
182
+ let mediaSource;
183
+
181
184
  if (window.ManagedMediaSource) {
182
185
  this.video_.disableRemotePlayback = true;
183
186
 
184
- const mediaSource = new ManagedMediaSource();
187
+ mediaSource = new ManagedMediaSource();
185
188
 
186
189
  this.eventManager_.listen(
187
190
  mediaSource, 'startstreaming', () => {
@@ -192,39 +195,25 @@ shaka.media.MediaSourceEngine = class {
192
195
  mediaSource, 'endstreaming', () => {
193
196
  this.streamingAllowed_ = false;
194
197
  });
195
-
196
- this.eventManager_.listenOnce(
197
- mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
198
-
199
- // Correctly set when playback has begun.
200
- this.eventManager_.listenOnce(this.video_, 'playing', () => {
201
- this.playbackHasBegun_ = true;
202
- });
203
-
204
- this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
205
-
206
- this.video_.src = this.url_;
207
-
208
- return mediaSource;
209
198
  } else {
210
- const mediaSource = new MediaSource();
199
+ mediaSource = new MediaSource();
200
+ }
211
201
 
212
- // Set up MediaSource on the video element.
213
- this.eventManager_.listenOnce(
214
- mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
202
+ // Set up MediaSource on the video element.
203
+ this.eventManager_.listenOnce(
204
+ mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
215
205
 
216
- // Correctly set when playback has begun.
217
- this.eventManager_.listenOnce(this.video_, 'playing', () => {
218
- this.playbackHasBegun_ = true;
219
- });
206
+ // Correctly set when playback has begun.
207
+ this.eventManager_.listenOnce(this.video_, 'playing', () => {
208
+ this.playbackHasBegun_ = true;
209
+ });
220
210
 
221
- // Store the object URL for releasing it later.
222
- this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
211
+ // Store the object URL for releasing it later.
212
+ this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
223
213
 
224
- this.video_.src = this.url_;
214
+ this.video_.src = this.url_;
225
215
 
226
- return mediaSource;
227
- }
216
+ return mediaSource;
228
217
  }
229
218
 
230
219
  /**
@@ -427,7 +427,9 @@ shaka.media.MediaSourcePlayhead = class {
427
427
  // infinite loop on systems where changing currentTime takes a
428
428
  // significant amount of time (e.g. Chromecast).
429
429
  const time = Date.now() / 1000;
430
- if (!this.lastCorrectiveSeek_ || this.lastCorrectiveSeek_ < time - 1) {
430
+ const seekDelay = shaka.util.Platform.isFuchsiaCastDevice() ? 3 : 1;
431
+ if (!this.lastCorrectiveSeek_ ||
432
+ this.lastCorrectiveSeek_ < time - seekDelay) {
431
433
  this.lastCorrectiveSeek_ = time;
432
434
  canCorrectiveSeek = true;
433
435
  }
@@ -131,6 +131,9 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
131
131
  /** @private {!shaka.util.Stats} */
132
132
  this.stats_ = new shaka.util.Stats();
133
133
 
134
+ /** @private {!shaka.util.PublicPromise} */
135
+ this.manifestPromise_ = new shaka.util.PublicPromise();
136
+
134
137
  /** @private {!shaka.util.PublicPromise} */
135
138
  this.successPromise_ = new shaka.util.PublicPromise();
136
139
 
@@ -383,6 +386,8 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
383
386
  await this.parseManifestInner_();
384
387
  this.throwIfDestroyed_();
385
388
 
389
+ this.manifestPromise_.resolve();
390
+
386
391
  await this.initializeDrmInner_();
387
392
  this.throwIfDestroyed_();
388
393
 
@@ -703,6 +708,18 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
703
708
  return this.successPromise_;
704
709
  }
705
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
+
706
723
  /**
707
724
  * Releases or stops all non-entrusted resources.
708
725
  *
@@ -78,6 +78,9 @@ shaka.media.PresentationTimeline = class {
78
78
  /** @private {boolean} */
79
79
  this.static_ = true;
80
80
 
81
+ /** @private {boolean} */
82
+ this.isLive2VodTransition_ = false;
83
+
81
84
  /** @private {number} */
82
85
  this.userSeekStart_ = 0;
83
86
 
@@ -179,6 +182,9 @@ shaka.media.PresentationTimeline = class {
179
182
  */
180
183
  setStatic(isStatic) {
181
184
  // NOTE: the argument name is not "static" because that's a keyword in ES6
185
+ if (isStatic && !this.static_) {
186
+ this.isLive2VodTransition_ = true;
187
+ }
182
188
  this.static_ = isStatic;
183
189
  }
184
190
 
@@ -384,7 +390,7 @@ shaka.media.PresentationTimeline = class {
384
390
  // No data yet, and Math.min(null, startTime) is always 0. So just store
385
391
  // startTime.
386
392
  this.minSegmentStartTime_ = startTime;
387
- } else {
393
+ } else if (!this.isLive2VodTransition_) {
388
394
  this.minSegmentStartTime_ =
389
395
  Math.min(this.minSegmentStartTime_, startTime);
390
396
  }
@@ -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_) {
@@ -1352,7 +1358,7 @@ shaka.media.StreamingEngine = class {
1352
1358
  this.config_.bufferingGoal);
1353
1359
 
1354
1360
  const scaledBufferingGoal = Math.max(1,
1355
- unscaledBufferingGoal * this.bufferingGoalScale_);
1361
+ unscaledBufferingGoal * this.bufferingScale_);
1356
1362
 
1357
1363
  // Check if we've buffered to the end of the presentation.
1358
1364
  const timeUntilEnd =
@@ -1793,7 +1799,7 @@ shaka.media.StreamingEngine = class {
1793
1799
  }
1794
1800
  this.mediaStates_.delete(ContentType.TEXT);
1795
1801
  } else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
1796
- this.handleQuotaExceeded_(mediaState, error);
1802
+ await this.handleQuotaExceeded_(mediaState, error);
1797
1803
  } else {
1798
1804
  shaka.log.error(logPrefix, 'failed fetch and append: code=' +
1799
1805
  error.code);
@@ -1906,9 +1912,10 @@ shaka.media.StreamingEngine = class {
1906
1912
  *
1907
1913
  * @param {shaka.media.StreamingEngine.MediaState_} mediaState
1908
1914
  * @param {!shaka.util.Error} error
1915
+ * @return {!Promise}
1909
1916
  * @private
1910
1917
  */
1911
- handleQuotaExceeded_(mediaState, error) {
1918
+ async handleQuotaExceeded_(mediaState, error) {
1912
1919
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1913
1920
 
1914
1921
  // The segment cannot fit into the SourceBuffer. Ideally, MediaSource would
@@ -1930,18 +1937,22 @@ shaka.media.StreamingEngine = class {
1930
1937
  });
1931
1938
 
1932
1939
  if (!waitingForAnotherStreamToRecover) {
1940
+ const maxDisabledTime = this.getDisabledTime_(error);
1941
+ if (maxDisabledTime) {
1942
+ shaka.log.debug(logPrefix, 'Disabling stream due to quota', error);
1943
+ }
1933
1944
  const handled = this.playerInterface_.disableStream(
1934
- mediaState.stream, this.config_.maxDisabledTime);
1945
+ mediaState.stream, maxDisabledTime);
1935
1946
  if (handled) {
1936
1947
  return;
1937
1948
  }
1938
1949
  // Reduction schedule: 80%, 60%, 40%, 20%, 16%, 12%, 8%, 4%, fail.
1939
1950
  // Note: percentages are used for comparisons to avoid rounding errors.
1940
- const percentBefore = Math.round(100 * this.bufferingGoalScale_);
1951
+ const percentBefore = Math.round(100 * this.bufferingScale_);
1941
1952
  if (percentBefore > 20) {
1942
- this.bufferingGoalScale_ -= 0.2;
1953
+ this.bufferingScale_ -= 0.2;
1943
1954
  } else if (percentBefore > 4) {
1944
- this.bufferingGoalScale_ -= 0.04;
1955
+ this.bufferingScale_ -= 0.04;
1945
1956
  } else {
1946
1957
  shaka.log.error(
1947
1958
  logPrefix, 'MediaSource threw QuotaExceededError too many times');
@@ -1950,12 +1961,14 @@ shaka.media.StreamingEngine = class {
1950
1961
  this.playerInterface_.onError(error);
1951
1962
  return;
1952
1963
  }
1953
- const percentAfter = Math.round(100 * this.bufferingGoalScale_);
1964
+ const percentAfter = Math.round(100 * this.bufferingScale_);
1954
1965
  shaka.log.warning(
1955
1966
  logPrefix,
1956
1967
  'MediaSource threw QuotaExceededError:',
1957
1968
  'reducing buffering goals by ' + (100 - percentAfter) + '%');
1958
1969
  mediaState.recovering = true;
1970
+ const presentationTime = this.playerInterface_.getPresentationTime();
1971
+ await this.evict_(mediaState, presentationTime);
1959
1972
  } else {
1960
1973
  shaka.log.debug(
1961
1974
  logPrefix,
@@ -2484,12 +2497,16 @@ shaka.media.StreamingEngine = class {
2484
2497
  // Use the max segment duration, if it is longer than the bufferBehind, to
2485
2498
  // avoid accidentally clearing too much data when dealing with a manifest
2486
2499
  // with a long keyframe interval.
2487
- const bufferBehind = Math.max(this.config_.bufferBehind,
2500
+ const bufferBehind = Math.max(
2501
+ this.config_.bufferBehind * this.bufferingScale_,
2488
2502
  this.manifest_.presentationTimeline.getMaxSegmentDuration());
2489
2503
 
2490
2504
  const startTime =
2491
2505
  this.playerInterface_.mediaSourceEngine.bufferStart(mediaState.type);
2492
2506
  if (startTime == null) {
2507
+ if (this.lastTextMediaStateBeforeUnload_ == mediaState) {
2508
+ this.lastTextMediaStateBeforeUnload_ = null;
2509
+ }
2493
2510
  shaka.log.v2(logPrefix,
2494
2511
  'buffer behind okay because nothing buffered:',
2495
2512
  'presentationTime=' + presentationTime,
@@ -2524,6 +2541,11 @@ shaka.media.StreamingEngine = class {
2524
2541
 
2525
2542
  this.destroyer_.ensureNotDestroyed();
2526
2543
  shaka.log.v1(logPrefix, 'evicted ' + overflow + ' seconds');
2544
+
2545
+ if (this.lastTextMediaStateBeforeUnload_) {
2546
+ await this.evict_(this.lastTextMediaStateBeforeUnload_, presentationTime);
2547
+ this.destroyer_.ensureNotDestroyed();
2548
+ }
2527
2549
  }
2528
2550
 
2529
2551
 
@@ -2768,18 +2790,26 @@ shaka.media.StreamingEngine = class {
2768
2790
  * @private
2769
2791
  */
2770
2792
  async handleStreamingError_(mediaState, error) {
2793
+ const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
2794
+
2771
2795
  // If we invoke the callback right away, the application could trigger a
2772
2796
  // rapid retry cycle that could be very unkind to the server. Instead,
2773
2797
  // use the backoff system to delay and backoff the error handling.
2774
2798
  await this.failureCallbackBackoff_.attempt();
2775
2799
  this.destroyer_.ensureNotDestroyed();
2776
- // Try to recover from network errors
2777
- if (error.category === shaka.util.Error.Category.NETWORK) {
2800
+
2801
+ // Try to recover from network errors, but not timeouts.
2802
+ // See https://github.com/shaka-project/shaka-player/issues/7368
2803
+ if (error.category === shaka.util.Error.Category.NETWORK &&
2804
+ error.code != shaka.util.Error.Code.TIMEOUT) {
2778
2805
  if (mediaState.restoreStreamAfterTrickPlay) {
2779
2806
  this.setTrickPlay(/* on= */ false);
2780
2807
  return;
2781
2808
  }
2782
2809
  const maxDisabledTime = this.getDisabledTime_(error);
2810
+ if (maxDisabledTime) {
2811
+ shaka.log.debug(logPrefix, 'Disabling stream due to error', error);
2812
+ }
2783
2813
  error.handled = this.playerInterface_.disableStream(
2784
2814
  mediaState.stream, maxDisabledTime);
2785
2815
 
@@ -2804,6 +2834,7 @@ shaka.media.StreamingEngine = class {
2804
2834
 
2805
2835
  /**
2806
2836
  * @param {!shaka.util.Error} error
2837
+ * @return {number}
2807
2838
  * @private
2808
2839
  */
2809
2840
  getDisabledTime_(error) {
package/lib/player.js CHANGED
@@ -1406,13 +1406,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1406
1406
  //
1407
1407
  // DrmEngine requires this to be done before we destroy DrmEngine itself.
1408
1408
  if (this.video_ && this.video_.src) {
1409
- // TODO: Investigate this more. Only reproduces on Firefox 69.
1410
- // Introduce a delay before detaching the video source. We are seeing
1411
- // spurious Promise rejections involving an AbortError in our tests
1412
- // otherwise.
1413
- await new Promise(
1414
- (resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
1415
-
1416
1409
  this.video_.removeAttribute('src');
1417
1410
  this.video_.load();
1418
1411
  }
@@ -1646,6 +1639,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1646
1639
  await this.srcEqualsInner_(startTimeOfLoad, mimeType);
1647
1640
  }, 'srcEqualsInner_');
1648
1641
  } else {
1642
+ // Wait for the manifest to be parsed.
1643
+ await mutexWrapOperation(async () => {
1644
+ await preloadManager.waitForManifest();
1645
+ }, 'waitForFinish');
1646
+
1647
+ // Retrieve the manifest. This is specifically put before the media
1648
+ // source engine is initialized, for the benefit of event handlers.
1649
+ this.parserFactory_ = preloadManager.getParserFactory();
1650
+ this.parser_ = preloadManager.receiveParser();
1651
+ this.manifest_ = preloadManager.getManifest();
1652
+
1649
1653
  if (!this.mediaSourceEngine_) {
1650
1654
  await mutexWrapOperation(async () => {
1651
1655
  await this.initializeMediaSourceEngineInner_();
@@ -1660,11 +1664,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1660
1664
  // Get manifest and associated values from preloader.
1661
1665
  this.config_ = preloadManager.getConfiguration();
1662
1666
  this.manifestFilterer_ = preloadManager.getManifestFilterer();
1663
- this.parserFactory_ = preloadManager.getParserFactory();
1664
- this.parser_ = preloadManager.receiveParser();
1665
1667
  this.regionTimeline_ = preloadManager.receiveRegionTimeline();
1666
1668
  this.qualityObserver_ = preloadManager.getQualityObserver();
1667
- this.manifest_ = preloadManager.getManifest();
1668
1669
  const currentAdaptationSetCriteria =
1669
1670
  preloadManager.getCurrentAdaptationSetCriteria();
1670
1671
  if (currentAdaptationSetCriteria) {
@@ -2802,6 +2803,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2802
2803
  }
2803
2804
  }
2804
2805
  mediaElement.src = playbackUri;
2806
+ // If ManagedMediaSource exists, we've disabled remote playback on our own.
2807
+ // Reenable it so AirPlay can be used via RemotePlayback API.
2808
+ if (this.mediaSourceEngine_ && window.ManagedMediaSource) {
2809
+ mediaElement.disableRemotePlayback = false;
2810
+ }
2805
2811
 
2806
2812
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2807
2813
  // no matter the value of the preload attribute. This is harmful on some
@@ -6589,6 +6595,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6589
6595
  oldTrack = shaka.util.StreamUtils.variantToTrack(currentVariant);
6590
6596
  }
6591
6597
  const newTrack = shaka.util.StreamUtils.variantToTrack(variant);
6598
+ newTrack.active = true;
6592
6599
 
6593
6600
  if (fromAdaptation) {
6594
6601
  // Dispatch an 'adaptation' event
@@ -6740,27 +6747,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6740
6747
  * @private
6741
6748
  */
6742
6749
  onSegmentAppended_(start, end, contentType, isMuxed) {
6743
- // When we append a segment to media source (via streaming engine) we are
6744
- // changing what data we have buffered, so notify the playhead of the
6745
- // change.
6746
- if (this.playhead_) {
6747
- this.playhead_.notifyOfBufferingChange();
6748
- // Skip the initial buffer gap
6749
- const startTime = this.mediaSourceEngine_.bufferStart(contentType);
6750
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
6751
- if (
6752
- !this.isLive() &&
6753
- // If not paused then GapJumpingController will handle this gap.
6754
- this.video_.paused &&
6755
- startTime != null &&
6756
- contentType != ContentType.TEXT &&
6757
- startTime > 0 &&
6758
- this.playhead_.getTime() < startTime
6759
- ) {
6760
- this.playhead_.setStartTime(startTime);
6761
- }
6762
- }
6763
- this.pollBufferState_();
6750
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
6751
+ if (contentType != ContentType.TEXT) {
6752
+ // When we append a segment to media source (via streaming engine) we are
6753
+ // changing what data we have buffered, so notify the playhead of the
6754
+ // change.
6755
+ if (this.playhead_) {
6756
+ this.playhead_.notifyOfBufferingChange();
6757
+ // Skip the initial buffer gap
6758
+ const startTime = this.mediaSourceEngine_.bufferStart(contentType);
6759
+ if (
6760
+ !this.isLive() &&
6761
+ // If not paused then GapJumpingController will handle this gap.
6762
+ this.video_.paused &&
6763
+ startTime != null &&
6764
+ startTime > 0 &&
6765
+ this.playhead_.getTime() < startTime
6766
+ ) {
6767
+ this.playhead_.setStartTime(startTime);
6768
+ }
6769
+ }
6770
+ this.pollBufferState_();
6771
+ }
6764
6772
 
6765
6773
  // Dispatch an event for users to consume, too.
6766
6774
  const data = new Map()
@@ -7527,7 +7535,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7527
7535
  * @export
7528
7536
  */
7529
7537
  // eslint-disable-next-line no-useless-concat, max-len
7530
- shaka.Player.version = 'v4.9.28' + '-uncompiled'; // x-release-please-version
7538
+ shaka.Player.version = 'v4.9.30' + '-uncompiled'; // x-release-please-version
7531
7539
 
7532
7540
  // Initialize the deprecation system using the version string we just set
7533
7541
  // on the player.
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.polyfill.EncryptionScheme');
8
8
 
9
9
  goog.require('shaka.polyfill');
10
+ goog.require('shaka.util.Platform');
10
11
 
11
12
  /**
12
13
  * @summary A polyfill to add support for EncryptionScheme queries in EME.
@@ -23,6 +24,14 @@ shaka.polyfill.EncryptionScheme = class {
23
24
  * @export
24
25
  */
25
26
  static install() {
27
+ // Skip polyfill for PlayStation 4 and SkyQ devices due to known crashes
28
+ // caused by unsupported encryptionScheme handling. These platforms do not
29
+ // require the polyfill, and forcing encryptionScheme processing can result
30
+ // in playback crashes.
31
+ if (shaka.util.Platform.isPS4() || shaka.util.Platform.isSkyQ()) {
32
+ return;
33
+ }
34
+
26
35
  EncryptionSchemePolyfills.install();
27
36
  }
28
37
  };
@@ -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
  /**