shaka-player 4.7.13 → 4.7.14

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.
@@ -533,7 +533,9 @@ shaka.ads.ClientSideAdManager = class {
533
533
  shaka.ads.AdManager.AD_STARTED, data));
534
534
  if (this.ad_.isLinear()) {
535
535
  this.adContainer_.setAttribute('ad-active', 'true');
536
- this.video_.pause();
536
+ if (!this.config_.customPlayheadTracker) {
537
+ this.video_.pause();
538
+ }
537
539
  if (this.video_.muted) {
538
540
  this.ad_.setInitialMuted(this.video_.volume);
539
541
  } else {
@@ -551,7 +553,7 @@ shaka.ads.ClientSideAdManager = class {
551
553
  (new Map()).set('originalEvent', e)));
552
554
  if (this.ad_ && this.ad_.isLinear()) {
553
555
  this.adContainer_.removeAttribute('ad-active');
554
- if (!this.video_.ended) {
556
+ if (!this.config_.customPlayheadTracker && !this.video_.ended) {
555
557
  this.video_.play();
556
558
  }
557
559
  }
@@ -394,6 +394,11 @@ shaka.dash.SegmentTemplate = class {
394
394
  Math.ceil(availablePeriodTimes[1] / segmentDuration) - 1,
395
395
  ];
396
396
 
397
+ // For Low Latency we can request the partial current position.
398
+ if (context.representation.availabilityTimeOffset) {
399
+ availablePeriodPositions[1]++;
400
+ }
401
+
397
402
  // In absolute positions.
398
403
  const availablePresentationPositions =
399
404
  availablePeriodPositions.map((x) => x + startNumber);
@@ -696,7 +701,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
696
701
  */
697
702
  appendTemplateInfo(info, periodStart, periodEnd, shouldFit,
698
703
  initSegmentReference) {
699
- this.initSegmentReference_ = initSegmentReference;
704
+ this.updateInitSegmentReference(initSegmentReference);
700
705
  if (!this.templateInfo_) {
701
706
  this.templateInfo_ = info;
702
707
  this.periodStart_ = periodStart;
@@ -727,6 +732,24 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
727
732
  }
728
733
  }
729
734
 
735
+ /**
736
+ * Updates the init segment reference and propagates the update to all
737
+ * references.
738
+ * @param {shaka.media.InitSegmentReference} initSegmentReference
739
+ */
740
+ updateInitSegmentReference(initSegmentReference) {
741
+ if (this.initSegmentReference_ === initSegmentReference) {
742
+ return;
743
+ }
744
+
745
+ this.initSegmentReference_ = initSegmentReference;
746
+ for (const reference of this.references) {
747
+ if (reference) {
748
+ reference.updateInitSegmentReference(initSegmentReference);
749
+ }
750
+ }
751
+ }
752
+
730
753
  /**
731
754
  *
732
755
  * @param {number} time
@@ -1551,6 +1551,9 @@ shaka.media.DrmEngine = class {
1551
1551
  const req = this.playerInterface_.netEngine.request(requestType, request);
1552
1552
  response = await req.promise;
1553
1553
  } catch (error) {
1554
+ if (this.destroyer_.destroyed()) {
1555
+ return;
1556
+ }
1554
1557
  // Request failed!
1555
1558
  goog.asserts.assert(error instanceof shaka.util.Error,
1556
1559
  'Wrong NetworkingEngine error type!');
@@ -1559,9 +1562,20 @@ shaka.media.DrmEngine = class {
1559
1562
  shaka.util.Error.Category.DRM,
1560
1563
  shaka.util.Error.Code.LICENSE_REQUEST_FAILED,
1561
1564
  error);
1562
- this.onError_(shakaErr);
1563
- if (metadata && metadata.updatePromise) {
1564
- metadata.updatePromise.reject(shakaErr);
1565
+ if (this.activeSessions_.size == 1) {
1566
+ this.onError_(shakaErr);
1567
+ if (metadata && metadata.updatePromise) {
1568
+ metadata.updatePromise.reject(shakaErr);
1569
+ }
1570
+ } else {
1571
+ if (metadata && metadata.updatePromise) {
1572
+ metadata.updatePromise.reject(shakaErr);
1573
+ }
1574
+ this.activeSessions_.delete(session);
1575
+ if (this.areAllSessionsLoaded_()) {
1576
+ this.allSessionsLoaded_.resolve();
1577
+ this.keyStatusTimer_.tickAfter(/* seconds= */ 0.1);
1578
+ }
1565
1579
  }
1566
1580
  return;
1567
1581
  }
@@ -1839,12 +1839,17 @@ shaka.media.MediaSourceEngine = class {
1839
1839
 
1840
1840
  const totalOfBuffers = streamsByType.size;
1841
1841
  let numberOfSourceBufferAdded = 0;
1842
- this.eventManager_.listen(sourceBuffers, 'addsourcebuffer', (event) => {
1842
+ const onSourceBufferAdded = () => {
1843
1843
  numberOfSourceBufferAdded++;
1844
1844
  if (numberOfSourceBufferAdded === totalOfBuffers) {
1845
1845
  sourceBufferAdded.resolve();
1846
+ this.eventManager_.unlisten(sourceBuffers, 'addsourcebuffer',
1847
+ onSourceBufferAdded);
1846
1848
  }
1847
- });
1849
+ };
1850
+
1851
+ this.eventManager_.listen(sourceBuffers, 'addsourcebuffer',
1852
+ onSourceBufferAdded);
1848
1853
 
1849
1854
  for (const contentType of streamsByType.keys()) {
1850
1855
  const stream = streamsByType.get(contentType);
@@ -1868,7 +1873,12 @@ shaka.media.MediaSourceEngine = class {
1868
1873
  this.destroyer_.ensureNotDestroyed();
1869
1874
 
1870
1875
  this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
1871
- this.destroyer_.ensureNotDestroyed();
1876
+ // Don't use ensureNotDestroyed() from this event listener, because
1877
+ // that results in an uncaught exception. Instead, just check the
1878
+ // flag.
1879
+ if (this.destroyer_.destroyed()) {
1880
+ return;
1881
+ }
1872
1882
 
1873
1883
  this.video_.autoplay = previousAutoPlayState;
1874
1884
  if (!previousPausedState) {
@@ -382,10 +382,10 @@ shaka.media.MediaSourcePlayhead = class {
382
382
  seekStart = seekEnd - this.minSeekRange_;
383
383
  }
384
384
 
385
- if (currentTime < seekStart || currentTime > seekEnd) {
386
- // playhead is outside of the seekrange. Move playhead to catch up.
385
+ if (currentTime < seekStart) {
386
+ // The seek range has moved past the playhead. Move ahead to catch up.
387
387
  const targetTime = this.reposition_(currentTime);
388
- shaka.log.info('Jumping by ' + (targetTime - currentTime) +
388
+ shaka.log.info('Jumping forward ' + (targetTime - currentTime) +
389
389
  ' seconds to catch up with the seek range.');
390
390
  this.mediaElement_.currentTime = targetTime;
391
391
  }
@@ -694,6 +694,16 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
694
694
  this.indexes_ = [];
695
695
  }
696
696
 
697
+ /**
698
+ * @override
699
+ * @export
700
+ */
701
+ forEachTopLevelReference(fn) {
702
+ for (const index of this.indexes_) {
703
+ index.forEachTopLevelReference(fn);
704
+ }
705
+ }
706
+
697
707
  /**
698
708
  * @override
699
709
  * @export
@@ -598,6 +598,18 @@ shaka.media.SegmentReference = class {
598
598
  this.offset(offset);
599
599
  }
600
600
  }
601
+
602
+ /**
603
+ * Updates the init segment reference and propagates the update to all partial
604
+ * references.
605
+ * @param {shaka.media.InitSegmentReference} initSegmentReference
606
+ */
607
+ updateInitSegmentReference(initSegmentReference) {
608
+ this.initSegmentReference = initSegmentReference;
609
+ for (const partialReference of this.partialReferences) {
610
+ partialReference.updateInitSegmentReference(initSegmentReference);
611
+ }
612
+ }
601
613
  };
602
614
 
603
615
 
@@ -1733,9 +1733,16 @@ shaka.media.StreamingEngine = class {
1733
1733
  otherState = this.mediaStates_.get(ContentType.VIDEO);
1734
1734
  }
1735
1735
  if (otherState) {
1736
+ // First, abort all operations in progress on the other stream.
1737
+ await this.abortOperations_(otherState).catch(() => {});
1738
+ // Then clear our cache of the last init segment, since MSE will be
1739
+ // reloaded and no init segment will be there post-reload.
1736
1740
  otherState.lastInitSegmentReference = null;
1741
+ // Now force the existing buffer to be cleared. It is not necessary
1742
+ // to perform the MSE clear operation, but this has the side-effect
1743
+ // that our state for that stream will then match MSE's post-reload
1744
+ // state.
1737
1745
  this.forceClearBuffer_(otherState);
1738
- this.abortOperations_(otherState).catch(() => {});
1739
1746
  }
1740
1747
  }
1741
1748
 
@@ -1766,19 +1773,6 @@ shaka.media.StreamingEngine = class {
1766
1773
  appendWindowEnd, ignoreTimestampOffset,
1767
1774
  reference.mimeType || mediaState.stream.mimeType,
1768
1775
  reference.codecs || mediaState.stream.codecs, streamsByType);
1769
- if (isResetMediaSourceNecessary) {
1770
- let otherState = null;
1771
- if (mediaState.type === ContentType.VIDEO) {
1772
- otherState = this.mediaStates_.get(ContentType.AUDIO);
1773
- } else if (mediaState.type === ContentType.AUDIO) {
1774
- otherState = this.mediaStates_.get(ContentType.VIDEO);
1775
- }
1776
- if (otherState) {
1777
- otherState.waitingToClearBuffer = false;
1778
- otherState.performingUpdate = false;
1779
- this.forceClearBuffer_(otherState);
1780
- }
1781
- }
1782
1776
  } catch (error) {
1783
1777
  mediaState.lastAppendWindowStart = null;
1784
1778
  mediaState.lastAppendWindowEnd = null;
package/lib/player.js CHANGED
@@ -1977,31 +1977,49 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1977
1977
  this.config_.playRangeStart,
1978
1978
  this.config_.playRangeEnd);
1979
1979
 
1980
- this.playhead_ = this.createPlayhead(this.startTime_);
1981
- this.playheadObservers_ =
1982
- this.createPlayheadObserversForMSE_(startTimeOfLoad);
1983
1980
 
1984
- // We need to start the buffer management code near the end because it
1985
- // will set the initial buffering state and that depends on other
1986
- // components being initialized.
1987
- const rebufferThreshold = Math.max(
1988
- this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
1989
- this.startBufferManagement_(mediaElement, rebufferThreshold);
1981
+ const setupPlayhead = (startTime) => {
1982
+ this.playhead_ = this.createPlayhead(startTime);
1983
+ this.playheadObservers_ =
1984
+ this.createPlayheadObserversForMSE_(startTimeOfLoad);
1985
+
1986
+ // We need to start the buffer management code near the end because it
1987
+ // will set the initial buffering state and that depends on other
1988
+ // components being initialized.
1989
+ const rebufferThreshold = Math.max(
1990
+ this.manifest_.minBufferTime,
1991
+ this.config_.streaming.rebufferingGoal);
1992
+ this.startBufferManagement_(mediaElement, rebufferThreshold);
1993
+ };
1994
+
1995
+ if (!this.config_.streaming.startAtSegmentBoundary) {
1996
+ setupPlayhead(this.startTime_);
1997
+ }
1990
1998
 
1991
1999
  // Now we can switch to the initial variant.
1992
2000
  if (!activeVariant) {
1993
2001
  goog.asserts.assert(initialVariant,
1994
2002
  'Must have choosen an initial variant!');
1995
2003
 
1996
- this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
1997
- /* clearBuffer= */ false, /* safeMargin= */ 0);
1998
-
1999
2004
  // Now that we have initial streams, we may adjust the start time to
2000
2005
  // align to a segment boundary.
2001
2006
  if (this.config_.streaming.startAtSegmentBoundary) {
2002
- this.playhead_.setStartTime(await this.adjustStartTime_(
2003
- initialVariant, this.playhead_.getTime()));
2007
+ const timeline = this.manifest_.presentationTimeline;
2008
+ let initialTime = this.startTime_ || this.video_.currentTime;
2009
+ const seekRangeStart = timeline.getSeekRangeStart();
2010
+ const seekRangeEnd = timeline.getSeekRangeEnd();
2011
+ if (initialTime < seekRangeStart) {
2012
+ initialTime = seekRangeStart;
2013
+ } else if (initialTime > seekRangeEnd) {
2014
+ initialTime = seekRangeEnd;
2015
+ }
2016
+ const startTime = await this.adjustStartTime_(
2017
+ initialVariant, initialTime);
2018
+ setupPlayhead(startTime);
2004
2019
  }
2020
+
2021
+ this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
2022
+ /* clearBuffer= */ false, /* safeMargin= */ 0);
2005
2023
  }
2006
2024
 
2007
2025
  this.playhead_.ready();
@@ -2316,6 +2334,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2316
2334
  mediaElement.load();
2317
2335
  }
2318
2336
 
2337
+ // In Safari using HLS won't load anything unless you call load()
2338
+ // explicitly, no matter the value of the preload attribute.
2339
+ // Note: this only happens when there are not autoplay.
2340
+ if (mediaElement.preload != 'none' && !mediaElement.autoplay &&
2341
+ shaka.util.MimeUtils.isHlsType(mimeType) &&
2342
+ shaka.util.Platform.safariVersion()) {
2343
+ mediaElement.load();
2344
+ }
2345
+
2319
2346
  // Set the load mode last so that we know that all our components are
2320
2347
  // initialized.
2321
2348
  this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
@@ -6960,7 +6987,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6960
6987
  * @export
6961
6988
  */
6962
6989
  // eslint-disable-next-line no-useless-concat
6963
- shaka.Player.version = 'v4.7.13' + '-uncompiled'; // x-release-please-version
6990
+ shaka.Player.version = 'v4.7.14' + '-uncompiled'; // x-release-please-version
6964
6991
 
6965
6992
  // Initialize the deprecation system using the version string we just set
6966
6993
  // on the player.
@@ -72,7 +72,9 @@ shaka.text.UITextDisplayer = class {
72
72
 
73
73
  /** @private {shaka.util.Timer} */
74
74
  this.captionsTimer_ = new shaka.util.Timer(() => {
75
- this.updateCaptions_();
75
+ if (!this.video_.paused) {
76
+ this.updateCaptions_();
77
+ }
76
78
  }).tickEvery(updatePeriod);
77
79
 
78
80
  /**
@@ -93,6 +95,10 @@ shaka.text.UITextDisplayer = class {
93
95
  this.updateCaptions_(/* forceUpdate= */ true);
94
96
  });
95
97
 
98
+ this.eventManager_.listen(this.video_, 'seeking', () => {
99
+ this.updateCaptions_(/* forceUpdate= */ true);
100
+ });
101
+
96
102
  // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
97
103
  // Whenever the natural width or natural height of the video changes
98
104
  // (including, for example, because the selected video track was changed),
@@ -339,6 +345,9 @@ shaka.text.UITextDisplayer = class {
339
345
  const cueRegistry = this.currentCuesMap_.get(cue);
340
346
  goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
341
347
  if (cueRegistry.regionElement) {
348
+ if (cueRegistry.regionElement.contains(container)) {
349
+ cueRegistry.regionElement.removeChild(container);
350
+ }
342
351
  container.appendChild(cueRegistry.regionElement);
343
352
  cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
344
353
  } else {
@@ -149,10 +149,9 @@ shaka.util.Platform = class {
149
149
  * @return {boolean}
150
150
  */
151
151
  static isWebOS3() {
152
- // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
153
- return shaka.util.Platform.userAgentContains_('Web0S') &&
154
- shaka.util.Platform.userAgentContains_(
155
- 'Chrome/38.0.2125.122 Safari/537.36');
152
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
153
+ return shaka.util.Platform.isWebOS() &&
154
+ shaka.util.Platform.chromeVersion() === 38;
156
155
  }
157
156
 
158
157
  /**
@@ -161,18 +160,20 @@ shaka.util.Platform = class {
161
160
  * @return {boolean}
162
161
  */
163
162
  static isWebOS4() {
164
- // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
165
- return !!navigator.userAgent.match(/webOS\/4/i);
163
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
164
+ return shaka.util.Platform.isWebOS() &&
165
+ shaka.util.Platform.chromeVersion() === 53;
166
166
  }
167
167
 
168
168
  /**
169
- * Check if the current platform is a WebOS 4.
169
+ * Check if the current platform is a WebOS 5.
170
170
  *
171
171
  * @return {boolean}
172
172
  */
173
173
  static isWebOS5() {
174
- // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
175
- return !!navigator.userAgent.match(/webOS\/5/i);
174
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
175
+ return shaka.util.Platform.isWebOS() &&
176
+ shaka.util.Platform.chromeVersion() === 68;
176
177
  }
177
178
 
178
179
  /**
@@ -184,6 +185,17 @@ shaka.util.Platform = class {
184
185
  return shaka.util.Platform.userAgentContains_('CrKey');
185
186
  }
186
187
 
188
+ /**
189
+ * Check if the current platform is a Google Chromecast with Android
190
+ * (i.e. Chromecast with GoogleTV).
191
+ *
192
+ * @return {boolean}
193
+ */
194
+ static isAndroidCastDevice() {
195
+ const Platform = shaka.util.Platform;
196
+ return Platform.isChromecast() && Platform.isAndroid();
197
+ }
198
+
187
199
  /**
188
200
  * Returns a major version number for Chrome, or Chromium-based browsers.
189
201
  *
@@ -414,6 +426,9 @@ shaka.util.Platform = class {
414
426
  return navigator.userAgentData.platform.toLowerCase() == 'macos';
415
427
  }
416
428
  // Fall back to the old API, with less strict matching.
429
+ if (!navigator.platform) {
430
+ return false;
431
+ }
417
432
  return navigator.platform.toLowerCase().includes('mac');
418
433
  }
419
434
 
@@ -428,6 +443,9 @@ shaka.util.Platform = class {
428
443
  return navigator.userAgentData.platform.toLowerCase() == 'windows';
429
444
  }
430
445
  // Fall back to the old API, with less strict matching.
446
+ if (!navigator.platform) {
447
+ return false;
448
+ }
431
449
  return navigator.platform.toLowerCase().includes('windows');
432
450
  }
433
451
 
@@ -535,6 +553,10 @@ shaka.util.Platform = class {
535
553
  Platform.isWebOS4() || Platform.isWebOS5()) {
536
554
  return false;
537
555
  }
556
+ // Older chromecasts without GoogleTV seem to not support SMOOTH properly.
557
+ if (Platform.isChromecast() && !Platform.isAndroidCastDevice()) {
558
+ return false;
559
+ }
538
560
  // See: https://chromium-review.googlesource.com/c/chromium/src/+/4577759
539
561
  if (Platform.isWindows() && Platform.isEdge()) {
540
562
  return false;
@@ -973,8 +973,12 @@ shaka.util.StreamUtils = class {
973
973
  // currently don't support 'fLaC', while 'flac' is supported by most
974
974
  // major browsers.
975
975
  // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
976
- if (codecs === 'fLaC' && !shaka.util.Platform.isSafari()) {
977
- return 'flac';
976
+ if (codecs.toLowerCase() == 'flac') {
977
+ if (!shaka.util.Platform.isSafari()) {
978
+ return 'flac';
979
+ } else {
980
+ return 'fLaC';
981
+ }
978
982
  }
979
983
 
980
984
  // The same is true for 'Opus'.
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.7.13",
4
+ "version": "4.7.14",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -96,6 +96,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
96
96
  } else {
97
97
  otherIdx = tracks.findIndex((t) => {
98
98
  return t.height == track.height &&
99
+ t.videoBandwidth == track.videoBandwidth &&
99
100
  t.frameRate == track.frameRate &&
100
101
  t.hdr == track.hdr &&
101
102
  t.videoLayout == track.videoLayout;