shaka-player 4.8.1 → 4.8.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.
@@ -1519,11 +1519,14 @@ shaka.extern.AdsConfiguration;
1519
1519
  * @property {boolean} enabled
1520
1520
  * If true, enable adaptation by the current AbrManager. Defaults to true.
1521
1521
  * @property {boolean} useNetworkInformation
1522
- * If true, use Network Information API in the current AbrManager.
1522
+ * If true, use the Network Information API in the current AbrManager, if it
1523
+ * is available in the browser environment. If the Network Information API is
1524
+ * used, Shaka Player will ignore the defaultBandwidthEstimate config.
1523
1525
  * Defaults to true.
1524
1526
  * @property {number} defaultBandwidthEstimate
1525
1527
  * The default bandwidth estimate to use if there is not enough data, in
1526
- * bit/sec.
1528
+ * bit/sec. Only used if useNetworkInformation is false, or if the Network
1529
+ * Information API is not available.
1527
1530
  * @property {shaka.extern.Restrictions} restrictions
1528
1531
  * The restrictions to apply to ABR decisions. These are "soft" restrictions.
1529
1532
  * Any track that fails to meet these restrictions will not be selected
@@ -282,12 +282,6 @@ shaka.dash.MpdUtils = class {
282
282
  const durationStr =
283
283
  MpdUtils.inheritAttribute(context, callback, 'duration');
284
284
  let segmentDuration = TXml.parsePositiveInt(durationStr || '');
285
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
286
- // TODO: The specification is not clear, check this once it is resolved:
287
- // https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/404
288
- if (context.representation.contentType == ContentType.IMAGE) {
289
- segmentDuration = TXml.parseFloat(durationStr || '');
290
- }
291
285
  if (segmentDuration) {
292
286
  segmentDuration /= timescale;
293
287
  }
@@ -398,6 +398,11 @@ shaka.dash.SegmentTemplate = class {
398
398
  Math.ceil(availablePeriodTimes[1] / segmentDuration) - 1,
399
399
  ];
400
400
 
401
+ // For Low Latency we can request the partial current position.
402
+ if (context.representation.availabilityTimeOffset) {
403
+ availablePeriodPositions[1]++;
404
+ }
405
+
401
406
  // In absolute positions.
402
407
  const availablePresentationPositions =
403
408
  availablePeriodPositions.map((x) => x + startNumber);
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.media.PlayRateController');
8
8
 
9
+ goog.require('goog.asserts');
9
10
  goog.require('shaka.log');
10
11
  goog.require('shaka.util.IReleasable');
11
12
  goog.require('shaka.util.Timer');
@@ -27,6 +28,9 @@ shaka.media.PlayRateController = class {
27
28
  /** @private {?shaka.media.PlayRateController.Harness} */
28
29
  this.harness_ = harness;
29
30
 
31
+ /** @private {boolean} */
32
+ this.isBuffering_ = false;
33
+
30
34
  /** @private {number} */
31
35
  this.rate_ = this.harness_.getRate();
32
36
 
@@ -51,15 +55,25 @@ shaka.media.PlayRateController = class {
51
55
  }
52
56
 
53
57
  /**
54
- * Set the playback rate.
58
+ * Sets the buffering flag, which controls the effective playback rate.
59
+ *
60
+ * @param {boolean} isBuffering If true, forces playback rate to 0 internally.
61
+ */
62
+ setBuffering(isBuffering) {
63
+ this.isBuffering_ = isBuffering;
64
+ this.apply_();
65
+ }
66
+
67
+ /**
68
+ * Set the playback rate. This rate will only be used as provided when the
69
+ * player is not buffering. You should never set the rate to 0.
55
70
  *
56
71
  * @param {number} rate
57
72
  */
58
73
  set(rate) {
59
- if (this.rate_ != rate) {
60
- this.rate_ = rate;
61
- this.apply_();
62
- }
74
+ goog.asserts.assert(rate != 0, 'Should never set rate of 0 explicitly!');
75
+ this.rate_ = rate;
76
+ this.apply_();
63
77
  }
64
78
 
65
79
  /**
@@ -93,11 +107,14 @@ shaka.media.PlayRateController = class {
93
107
  // Always stop the timer. We may not start it again.
94
108
  this.timer_.stop();
95
109
 
96
- shaka.log.v1('Changing effective playback rate to', this.rate_);
110
+ /** @type {number} */
111
+ const rate = this.calculateCurrentRate_();
112
+
113
+ shaka.log.v1('Changing effective playback rate to', rate);
97
114
 
98
- if (this.rate_ >= 0) {
115
+ if (rate >= 0) {
99
116
  try {
100
- this.applyRate_(this.rate_);
117
+ this.applyRate_(rate);
101
118
  return;
102
119
  } catch (e) {
103
120
  // Fall through to the next clause.
@@ -117,6 +134,17 @@ shaka.media.PlayRateController = class {
117
134
  this.applyRate_(0);
118
135
  }
119
136
 
137
+ /**
138
+ * Calculate the rate that the controller wants the media element to have
139
+ * based on the current state of the controller.
140
+ *
141
+ * @return {number}
142
+ * @private
143
+ */
144
+ calculateCurrentRate_() {
145
+ return this.isBuffering_ ? 0 : this.rate_;
146
+ }
147
+
120
148
  /**
121
149
  * If the new rate is different than the media element's playback rate, this
122
150
  * will change the playback rate. If the rate does not need to change, it will
@@ -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
  }
@@ -147,7 +147,12 @@ shaka.media.SegmentPrefetch = class {
147
147
  }
148
148
 
149
149
  // init segments are ignored from the prefetch limit
150
- if (!this.initSegmentPrefetchMap_.has(initSegmentReference)) {
150
+ const initSegments = Array.from(this.initSegmentPrefetchMap_.keys());
151
+ const someReference = initSegments.some((reference) => {
152
+ return shaka.media.InitSegmentReference.equal(
153
+ reference, initSegmentReference);
154
+ });
155
+ if (!someReference) {
151
156
  const segmentPrefetchOperation =
152
157
  new shaka.media.SegmentPrefetchOperation(this.fetchDispatcher_);
153
158
  segmentPrefetchOperation.dispatchFetch(
@@ -233,6 +238,15 @@ shaka.media.SegmentPrefetch = class {
233
238
  this.prefetchPosTime_ = 0;
234
239
  }
235
240
 
241
+ /**
242
+ * Remove a reference of prefetched segment if already exists.
243
+ * @param {!shaka.media.SegmentReference} reference
244
+ * @public
245
+ */
246
+ removeReference(reference) {
247
+ this.abortPrefetchedSegment_(reference);
248
+ }
249
+
236
250
  /**
237
251
  * @param {number} time
238
252
  * @param {boolean=} clearInitSegments
@@ -261,9 +275,11 @@ shaka.media.SegmentPrefetch = class {
261
275
  const segmentReferences = Array.from(this.segmentPrefetchMap_.keys());
262
276
  for (const initSegmentReference of this.initSegmentPrefetchMap_.keys()) {
263
277
  // if no segment references this init segment, we should remove it.
264
- if (!segmentReferences.some(
265
- (segmentReference) =>
266
- segmentReference.initSegmentReference === initSegmentReference)) {
278
+ const someReference = segmentReferences.some((segmentReference) => {
279
+ return shaka.media.InitSegmentReference.equal(
280
+ segmentReference.initSegmentReference, initSegmentReference);
281
+ });
282
+ if (!someReference) {
267
283
  this.abortPrefetchedSegment_(initSegmentReference);
268
284
  }
269
285
  }
@@ -174,6 +174,9 @@ shaka.media.StallDetector.MediaElementImplementation = class {
174
174
  if (this.mediaElement_.paused) {
175
175
  return false;
176
176
  }
177
+ if (this.mediaElement_.playbackRate == 0) {
178
+ return false;
179
+ }
177
180
 
178
181
  // If we have don't have enough content, we are not stalled, we are
179
182
  // buffering.
@@ -1702,6 +1702,11 @@ shaka.media.StreamingEngine = class {
1702
1702
  error.code);
1703
1703
  mediaState.hasError = true;
1704
1704
 
1705
+ if (error.category == shaka.util.Error.Category.NETWORK &&
1706
+ mediaState.segmentPrefetch) {
1707
+ mediaState.segmentPrefetch.removeReference(reference);
1708
+ }
1709
+
1705
1710
  error.severity = shaka.util.Error.Severity.CRITICAL;
1706
1711
  await this.handleStreamingError_(mediaState, error);
1707
1712
  }
package/lib/player.js CHANGED
@@ -622,6 +622,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
622
622
  */
623
623
  this.playRateController_ = null;
624
624
 
625
+ // We use the buffering observer and timer to track when we move from having
626
+ // enough buffered content to not enough. They only exist when content has
627
+ // been loaded and are not re-used between loads.
628
+ /** @private {shaka.util.Timer} */
629
+ this.bufferPoller_ = null;
630
+
625
631
  /** @private {shaka.media.BufferingObserver} */
626
632
  this.bufferObserver_ = null;
627
633
 
@@ -1228,6 +1234,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1228
1234
  this.playheadObservers_ = null;
1229
1235
  }
1230
1236
 
1237
+ if (this.bufferPoller_) {
1238
+ this.bufferPoller_.stop();
1239
+ this.bufferPoller_ = null;
1240
+ }
1241
+
1231
1242
  // Stop the parser early. Since it is at the start of the pipeline, it
1232
1243
  // should be start early to avoid is pushing new data downstream.
1233
1244
  if (this.parser_) {
@@ -3101,6 +3112,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3101
3112
  !this.bufferObserver_,
3102
3113
  'No buffering observer should exist before initialization.');
3103
3114
 
3115
+ goog.asserts.assert(
3116
+ !this.bufferPoller_,
3117
+ 'No buffer timer should exist before initialization.');
3118
+
3104
3119
  // Give dummy values, will be updated below.
3105
3120
  this.bufferObserver_ = new shaka.media.BufferingObserver(1, 2);
3106
3121
 
@@ -3110,6 +3125,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3110
3125
  this.updateBufferingSettings_(rebufferingGoal);
3111
3126
  this.updateBufferState_();
3112
3127
 
3128
+ this.bufferPoller_ = new shaka.util.Timer(() => {
3129
+ this.pollBufferState_();
3130
+ }).tickEvery(/* seconds= */ 0.25);
3113
3131
  this.loadEventManager_.listen(mediaElement, 'waiting',
3114
3132
  (e) => this.pollBufferState_());
3115
3133
  this.loadEventManager_.listen(mediaElement, 'stalled',
@@ -5833,6 +5851,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5833
5851
  const loaded = this.stats_ && this.bufferObserver_ && this.playhead_;
5834
5852
 
5835
5853
  if (loaded) {
5854
+ this.playRateController_.setBuffering(isBuffering);
5836
5855
  if (this.cmcdManager_) {
5837
5856
  this.cmcdManager_.setBuffering(isBuffering);
5838
5857
  }
@@ -7182,7 +7201,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7182
7201
  * @export
7183
7202
  */
7184
7203
  // eslint-disable-next-line no-useless-concat
7185
- shaka.Player.version = 'v4.8.1' + '-uncompiled'; // x-release-please-version
7204
+ shaka.Player.version = 'v4.8.3' + '-uncompiled'; // x-release-please-version
7186
7205
 
7187
7206
  // Initialize the deprecation system using the version string we just set
7188
7207
  // on the player.
@@ -8,6 +8,7 @@
8
8
  goog.provide('shaka.text.UITextDisplayer');
9
9
 
10
10
  goog.require('goog.asserts');
11
+ goog.require('shaka.Deprecate');
11
12
  goog.require('shaka.text.Cue');
12
13
  goog.require('shaka.text.CueRegion');
13
14
  goog.require('shaka.util.Dom');
@@ -65,12 +66,22 @@ shaka.text.UITextDisplayer = class {
65
66
 
66
67
  this.videoContainer_.appendChild(this.textContainer_);
67
68
 
69
+ if (!config || !config.captionsUpdatePeriod) {
70
+ shaka.Deprecate.deprecateFeature(5,
71
+ 'UITextDisplayer w/ config',
72
+ 'Please migrate to initializing UITextDisplayer with a config.');
73
+ }
74
+
75
+ /** @private {number} */
76
+ const updatePeriod = (config && config.captionsUpdatePeriod) ?
77
+ config.captionsUpdatePeriod : 0.25;
78
+
68
79
  /** @private {shaka.util.Timer} */
69
80
  this.captionsTimer_ = new shaka.util.Timer(() => {
70
81
  if (!this.video_.paused) {
71
82
  this.updateCaptions_();
72
83
  }
73
- }).tickEvery(config.captionsUpdatePeriod);
84
+ }).tickEvery(updatePeriod);
74
85
 
75
86
  /**
76
87
  * Maps cues to cue elements. Specifically points out the wrapper element of
@@ -186,6 +186,17 @@ shaka.util.Platform = class {
186
186
  return shaka.util.Platform.userAgentContains_('CrKey');
187
187
  }
188
188
 
189
+ /**
190
+ * Check if the current platform is a Google Chromecast with Android
191
+ * (i.e. Chromecast with GoogleTV).
192
+ *
193
+ * @return {boolean}
194
+ */
195
+ static isAndroidCastDevice() {
196
+ const Platform = shaka.util.Platform;
197
+ return Platform.isChromecast() && Platform.isAndroid();
198
+ }
199
+
189
200
  /**
190
201
  * Returns a major version number for Chrome, or Chromium-based browsers.
191
202
  *
@@ -416,6 +427,9 @@ shaka.util.Platform = class {
416
427
  return navigator.userAgentData.platform.toLowerCase() == 'macos';
417
428
  }
418
429
  // Fall back to the old API, with less strict matching.
430
+ if (!navigator.platform) {
431
+ return false;
432
+ }
419
433
  return navigator.platform.toLowerCase().includes('mac');
420
434
  }
421
435
 
@@ -430,6 +444,9 @@ shaka.util.Platform = class {
430
444
  return navigator.userAgentData.platform.toLowerCase() == 'windows';
431
445
  }
432
446
  // Fall back to the old API, with less strict matching.
447
+ if (!navigator.platform) {
448
+ return false;
449
+ }
433
450
  return navigator.platform.toLowerCase().includes('windows');
434
451
  }
435
452
 
@@ -556,6 +573,10 @@ shaka.util.Platform = class {
556
573
  Platform.isWebOS4() || Platform.isWebOS5()) {
557
574
  return false;
558
575
  }
576
+ // Older chromecasts without GoogleTV seem to not support SMOOTH properly.
577
+ if (Platform.isChromecast() && !Platform.isAndroidCastDevice()) {
578
+ return false;
579
+ }
559
580
  // See: https://chromium-review.googlesource.com/c/chromium/src/+/4577759
560
581
  if (Platform.isWindows() && Platform.isEdge()) {
561
582
  return false;
@@ -663,8 +684,8 @@ shaka.util.Platform = class {
663
684
  /** @type {{screenWidth: number, screenHeight: number}} */(
664
685
  JSON.parse(window.PalmSystem.deviceInfo));
665
686
  // WebOS has always been able to do 1080p. Assume a 1080p limit.
666
- maxResolution.width = Math.max(1920, deviceInfo.screenWidth);
667
- maxResolution.height = Math.max(1080, deviceInfo.screenHeight);
687
+ maxResolution.width = Math.max(1920, deviceInfo['screenWidth']);
688
+ maxResolution.height = Math.max(1080, deviceInfo['screenHeight']);
668
689
  } catch (e) {
669
690
  shaka.log.alwaysWarn('WebOS: Error detecting screen size, default ' +
670
691
  'screen size 1920x1080.');
package/lib/util/tXml.js CHANGED
@@ -779,7 +779,7 @@ shaka.util.TXml = class {
779
779
  element.setAttribute(k, v);
780
780
  }
781
781
 
782
- if (doParents && node.parent) {
782
+ if (doParents && node.parent && node.parent.tagName != '?xml') {
783
783
  const parentElement = TXml.txmlNodeToDomElement(
784
784
  node.parent, /* doParents= */ true, /* doChildren= */ false);
785
785
  parentElement.appendChild(element);
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.8.1",
4
+ "version": "4.8.3",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -116,6 +116,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
116
116
  } else {
117
117
  otherIdx = tracks.findIndex((t) => {
118
118
  return t.height == track.height &&
119
+ t.videoBandwidth == track.videoBandwidth &&
119
120
  t.frameRate == track.frameRate &&
120
121
  t.hdr == track.hdr &&
121
122
  t.videoLayout == track.videoLayout;