shaka-player 4.9.30 → 4.9.32

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 (50) hide show
  1. package/.eslintrc.js +1 -0
  2. package/AUTHORS +2 -0
  3. package/CONTRIBUTORS +2 -0
  4. package/dist/controls.css +1 -1
  5. package/dist/controls.css.map +1 -1
  6. package/dist/deps.js +2 -2
  7. package/dist/locales.js +33 -33
  8. package/dist/shaka-player.compiled.d.ts +8 -7
  9. package/dist/shaka-player.compiled.debug.d.ts +8 -7
  10. package/dist/shaka-player.compiled.debug.externs.js +3 -0
  11. package/dist/shaka-player.compiled.debug.js +264 -260
  12. package/dist/shaka-player.compiled.debug.map +3 -3
  13. package/dist/shaka-player.compiled.externs.js +3 -0
  14. package/dist/shaka-player.compiled.js +1214 -1212
  15. package/dist/shaka-player.compiled.map +3 -3
  16. package/dist/shaka-player.ui.d.ts +8 -7
  17. package/dist/shaka-player.ui.debug.d.ts +8 -7
  18. package/dist/shaka-player.ui.debug.externs.js +3 -0
  19. package/dist/shaka-player.ui.debug.js +290 -285
  20. package/dist/shaka-player.ui.debug.map +3 -3
  21. package/dist/shaka-player.ui.externs.js +3 -0
  22. package/dist/shaka-player.ui.js +1417 -1414
  23. package/dist/shaka-player.ui.map +3 -3
  24. package/externs/shaka/abortable.js +36 -36
  25. package/lib/dash/segment_template.js +19 -2
  26. package/lib/hls/hls_parser.js +1 -1
  27. package/lib/media/adaptation_set_criteria.js +8 -1
  28. package/lib/media/content_workarounds.js +2 -1
  29. package/lib/media/drm_engine.js +10 -115
  30. package/lib/media/media_source_engine.js +99 -36
  31. package/lib/media/preload_manager.js +6 -1
  32. package/lib/media/segment_index.js +20 -0
  33. package/lib/media/segment_prefetch.js +7 -0
  34. package/lib/media/segment_reference.js +1 -1
  35. package/lib/media/streaming_engine.js +16 -13
  36. package/lib/mss/mss_parser.js +6 -0
  37. package/lib/net/networking_engine.js +3 -3
  38. package/lib/player.js +16 -2
  39. package/lib/polyfill/media_capabilities.js +48 -9
  40. package/lib/polyfill/mediasource.js +12 -0
  41. package/lib/text/ui_text_displayer.js +8 -0
  42. package/lib/text/vtt_text_parser.js +2 -8
  43. package/lib/util/abortable_operation.js +7 -5
  44. package/lib/util/mp4_generator.js +1 -1
  45. package/lib/util/platform.js +22 -11
  46. package/package.json +2 -2
  47. package/ui/controls.js +2 -2
  48. package/ui/less/containers.less +8 -0
  49. package/ui/remote_button.js +42 -17
  50. package/ui/statistics_button.js +0 -2
@@ -396,7 +396,12 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
396
396
 
397
397
  this.successPromise_.resolve();
398
398
  } catch (error) {
399
- this.successPromise_.reject(error);
399
+ // Ignore OPERATION_ABORTED and OBJECT_DESTROYED errors.
400
+ if (!(error instanceof shaka.util.Error) ||
401
+ (error.code != shaka.util.Error.Code.OPERATION_ABORTED &&
402
+ error.code != shaka.util.Error.Code.OBJECT_DESTROYED)) {
403
+ this.successPromise_.reject(error);
404
+ }
400
405
  }
401
406
  })();
402
407
  }
@@ -679,6 +679,26 @@ shaka.media.SegmentIterator = class {
679
679
  'done': !res,
680
680
  };
681
681
  }
682
+
683
+ /**
684
+ * @export
685
+ */
686
+ resetToLastIndependent() {
687
+ const current = this.current();
688
+ if (current.isPartial() && !current.isIndependent()) {
689
+ const ref = this.segmentIndex_.get(this.currentPosition_);
690
+ if (ref && ref.hasPartialSegments()) {
691
+ let partial = ref.partialReferences[this.currentPartialPosition_];
692
+ while (partial.isIndependent()) {
693
+ if (this.currentPartialPosition_ <= 0) {
694
+ break;
695
+ }
696
+ this.currentPartialPosition_--;
697
+ partial = ref.partialReferences[this.currentPartialPosition_];
698
+ }
699
+ }
700
+ }
701
+ }
682
702
  };
683
703
 
684
704
 
@@ -111,6 +111,9 @@ shaka.media.SegmentPrefetch = class {
111
111
  shaka.media.SegmentReference.Status.MISSING) {
112
112
  prefetchAllowed = false;
113
113
  }
114
+ if (reference.getSegmentData()) {
115
+ prefetchAllowed = false;
116
+ }
114
117
  if (prefetchAllowed && reference.initSegmentReference) {
115
118
  this.prefetchInitSegment(reference.initSegmentReference);
116
119
  }
@@ -143,6 +146,10 @@ shaka.media.SegmentPrefetch = class {
143
146
  return;
144
147
  }
145
148
 
149
+ if (initSegmentReference.getSegmentData()) {
150
+ return;
151
+ }
152
+
146
153
  // init segments are ignored from the prefetch limit
147
154
  const initSegments = Array.from(this.initSegmentPrefetchMap_.keys());
148
155
  const someReference = initSegments.some((reference) => {
@@ -295,7 +295,7 @@ shaka.media.SegmentReference = class {
295
295
  this.thumbnailSprite = null;
296
296
 
297
297
  /** @type {number} */
298
- this.discontinuitySequence = 0;
298
+ this.discontinuitySequence = -1;
299
299
 
300
300
  /** @type {boolean} */
301
301
  this.allPartialSegments = allPartialSegments;
@@ -2001,8 +2001,7 @@ shaka.media.StreamingEngine = class {
2001
2001
  const MimeUtils = shaka.util.MimeUtils;
2002
2002
  const StreamingEngine = shaka.media.StreamingEngine;
2003
2003
  const logPrefix = StreamingEngine.logPrefix_(mediaState);
2004
- const nullLastReferences = mediaState.lastSegmentReference == null &&
2005
- mediaState.lastInitSegmentReference == null;
2004
+ const nullLastReferences = mediaState.lastSegmentReference == null;
2006
2005
 
2007
2006
  /** @type {!Array.<!Promise>} */
2008
2007
  const operations = [];
@@ -2217,17 +2216,15 @@ shaka.media.StreamingEngine = class {
2217
2216
  }
2218
2217
  }
2219
2218
 
2220
- if (this.manifest_.sequenceMode) {
2221
- const lastDiscontinuitySequence =
2222
- mediaState.lastSegmentReference ?
2223
- mediaState.lastSegmentReference.discontinuitySequence : null;
2224
- // Across discontinuity bounds, we should resync timestamps for
2225
- // sequence mode playbacks. The next segment appended should
2226
- // land at its theoretical timestamp from the segment index.
2227
- if (reference.discontinuitySequence != lastDiscontinuitySequence) {
2228
- operations.push(this.playerInterface_.mediaSourceEngine.resync(
2229
- mediaState.type, reference.startTime));
2230
- }
2219
+ const lastDiscontinuitySequence =
2220
+ mediaState.lastSegmentReference ?
2221
+ mediaState.lastSegmentReference.discontinuitySequence : null;
2222
+ // Across discontinuity bounds, we should resync timestamps. The next
2223
+ // segment appended should land at its theoretical timestamp from the
2224
+ // segment index.
2225
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
2226
+ operations.push(this.playerInterface_.mediaSourceEngine.resync(
2227
+ mediaState.type, reference.startTime));
2231
2228
  }
2232
2229
 
2233
2230
  await Promise.all(operations);
@@ -2871,12 +2868,18 @@ shaka.media.StreamingEngine = class {
2871
2868
  audioMediaState.lastInitSegmentReference = null;
2872
2869
  this.forceClearBuffer_(audioMediaState);
2873
2870
  this.abortOperations_(audioMediaState).catch(() => {});
2871
+ if (audioMediaState.segmentIterator) {
2872
+ audioMediaState.segmentIterator.resetToLastIndependent();
2873
+ }
2874
2874
  }
2875
2875
  const videoMediaState = this.mediaStates_.get(ContentType.VIDEO);
2876
2876
  if (videoMediaState) {
2877
2877
  videoMediaState.lastInitSegmentReference = null;
2878
2878
  this.forceClearBuffer_(videoMediaState);
2879
2879
  this.abortOperations_(videoMediaState).catch(() => {});
2880
+ if (videoMediaState.segmentIterator) {
2881
+ videoMediaState.segmentIterator.resetToLastIndependent();
2882
+ }
2880
2883
  }
2881
2884
  /**
2882
2885
  * @type {!Map.<shaka.util.ManifestParserUtils.ContentType,
@@ -943,6 +943,12 @@ shaka.mss.MssParser = class {
943
943
  let startTime = t != null ? t : lastEndTime;
944
944
 
945
945
  let repeat = r || 0;
946
+ // Unlike in DASH, in MSS r does not start counting repetitions at 0 but
947
+ // at 1, to maintain the code equivalent to DASH if r exists we
948
+ // subtract 1.
949
+ if (repeat) {
950
+ repeat--;
951
+ }
946
952
  if (repeat < 0) {
947
953
  if (next) {
948
954
  const nextStartTime =
@@ -565,12 +565,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
565
565
  }
566
566
  };
567
567
  const headersReceived = (headers) => {
568
- if (this.onHeadersReceived_) {
569
- this.onHeadersReceived_(headers, request, type);
570
- }
571
568
  headersReceivedCalled = true;
572
569
  request.timeToFirstByte = Date.now() -
573
570
  /** @type {number} */ (request.requestStartTime);
571
+ if (this.onHeadersReceived_) {
572
+ this.onHeadersReceived_(headers, request, type);
573
+ }
574
574
  };
575
575
  request.requestStartTime = Date.now();
576
576
 
package/lib/player.js CHANGED
@@ -5922,7 +5922,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5922
5922
  // Because this.video_ may not be set when the config is built, the default
5923
5923
  // TextDisplay factory must capture a reference to "this".
5924
5924
  config.textDisplayFactory = () => {
5925
- if (this.videoContainer_) {
5925
+ // On iOS where the Fullscreen API is not available we prefer
5926
+ // SimpleTextDisplayer because it works with the Fullscreen API of the
5927
+ // video element itself.
5928
+ const Platform = shaka.util.Platform;
5929
+ if (this.videoContainer_ &&
5930
+ (!Platform.safariVersion() || document.fullscreenEnabled)) {
5926
5931
  const latestConfig = this.getConfiguration();
5927
5932
  return new shaka.text.UITextDisplayer(
5928
5933
  this.video_, this.videoContainer_, latestConfig.textDisplayer);
@@ -6253,6 +6258,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6253
6258
  return;
6254
6259
  }
6255
6260
 
6261
+ // We don't want to block the user from pausing the stream.
6262
+ if (this.video_.paused) {
6263
+ return;
6264
+ }
6265
+
6256
6266
  let liveSyncMaxLatency;
6257
6267
  let liveSyncPlaybackRate;
6258
6268
  if (this.config_.streaming.liveSync) {
@@ -6943,6 +6953,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6943
6953
  return false;
6944
6954
  }
6945
6955
 
6956
+ if (!this.manifest_) {
6957
+ return false;
6958
+ }
6959
+
6946
6960
  // It only makes sense to disable a stream if we have an alternative else we
6947
6961
  // end up disabling all variants.
6948
6962
  const hasAltStream = this.manifest_.variants.some((variant) => {
@@ -7535,7 +7549,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7535
7549
  * @export
7536
7550
  */
7537
7551
  // eslint-disable-next-line no-useless-concat, max-len
7538
- shaka.Player.version = 'v4.9.30' + '-uncompiled'; // x-release-please-version
7552
+ shaka.Player.version = 'v4.9.32' + '-uncompiled'; // x-release-please-version
7539
7553
 
7540
7554
  // Initialize the deprecation system using the version string we just set
7541
7555
  // on the player.
@@ -61,7 +61,8 @@ shaka.polyfill.MediaCapabilities = class {
61
61
  shaka.util.Platform.isWebOS() ||
62
62
  shaka.util.Platform.isTizen() ||
63
63
  shaka.util.Platform.isEOS() ||
64
- shaka.util.Platform.isHisense()) {
64
+ shaka.util.Platform.isHisense() ||
65
+ shaka.util.Platform.isComcastX1()) {
65
66
  canUseNativeMCap = false;
66
67
  }
67
68
  if (canUseNativeMCap && navigator.mediaCapabilities) {
@@ -326,18 +327,56 @@ shaka.polyfill.MediaCapabilities = class {
326
327
 
327
328
  let displayType = videoConfig.contentType;
328
329
  if (videoConfig.width && videoConfig.height) {
329
- displayType +=
330
- `; width=${videoConfig.width}; height=${videoConfig.height}`;
330
+ // All Chromecast can support 720p videos
331
+ if (videoConfig.width > 1280 || videoConfig.height > 720) {
332
+ displayType +=
333
+ `; width=${videoConfig.width}; height=${videoConfig.height}`;
334
+ }
331
335
  }
332
336
  if (videoConfig.framerate) {
333
- displayType += `; framerate=${videoConfig.framerate}`;
337
+ // All Chromecast can support a framerate of 24, 25 or 30.
338
+ const framerate = Math.round(videoConfig.framerate);
339
+ if (framerate < 24 || framerate > 30) {
340
+ displayType += `; framerate=${videoConfig.framerate}`;
341
+ }
334
342
  }
335
- if (videoConfig.transferFunction === 'pq') {
336
- // A "PQ" transfer function indicates this is an HDR-capable stream;
337
- // "smpte2084" is the published standard. We need to inform the platform
338
- // this query is specifically for HDR.
339
- displayType += '; eotf=smpte2084';
343
+
344
+ // Don't trust Closure types here. Although transferFunction is string or
345
+ // undefined, we don't want to count on the input type. A switch statement
346
+ // will, however, differentiate between null and undefined. So we default
347
+ // to a blank string.
348
+ const transferFunction = videoConfig.transferFunction || '';
349
+
350
+ // Based on internal sources. Googlers, see go/cast-hdr-queries for source.
351
+ switch (transferFunction) {
352
+ // The empty case falls through to SDR.
353
+ case '':
354
+ // These are the only 3 values defined by MCap as of November 2024.
355
+ case 'srgb':
356
+ // https://en.wikipedia.org/wiki/Standard-dynamic-range_video
357
+ // https://en.wikipedia.org/wiki/SRGB
358
+ // https://en.wikipedia.org/wiki/Rec._709
359
+ // This is SDR, standardized in BT 709.
360
+ // The platform recognizes "eotf=bt709", but we can also omit it.
361
+ break;
362
+
363
+ case 'pq':
364
+ // https://en.wikipedia.org/wiki/Perceptual_quantizer
365
+ // This HDR transfer function is standardized as SMPTE ST 2084.
366
+ displayType += '; eotf=smpte2084';
367
+ break;
368
+
369
+ case 'hlg':
370
+ // https://en.wikipedia.org/wiki/Hybrid_log%E2%80%93gamma
371
+ // This HDR transfer function is standardized as ARIB STD-B67.
372
+ displayType += '; eotf=arib-std-b67';
373
+ break;
374
+
375
+ default:
376
+ // An unrecognized transfer function. Reject this query.
377
+ return false;
340
378
  }
379
+
341
380
  let result = false;
342
381
  if (displayType in shaka.polyfill.MediaCapabilities
343
382
  .memoizedCanDisplayTypeRequests_) {
@@ -87,6 +87,18 @@ shaka.polyfill.MediaSource = class {
87
87
  // the player from trying to play opus on Tizen, we will override media
88
88
  // source to always reject opus content.
89
89
  shaka.polyfill.MediaSource.rejectCodec_('opus');
90
+ } else if (shaka.util.Platform.isComcastX1()) {
91
+ // XOne look to be like safari 8 which do not correctly
92
+ // implement abort() on SourceBuffer.
93
+ // Calling abort() before appending a segment causes that segment to be
94
+ // incomplete in the buffer.
95
+ // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
96
+ shaka.polyfill.MediaSource.stubAbort_();
97
+ // If you remove up to a keyframe, Webkit 601.x.x incorrectly will also
98
+ // remove that keyframe and the content up to the next.
99
+ // Offsetting the end of the removal range seems to help.
100
+ // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
101
+ shaka.polyfill.MediaSource.patchRemovalRange_();
90
102
  } else {
91
103
  shaka.log.info('Using native MSE as-is.');
92
104
  }
@@ -9,6 +9,7 @@ goog.provide('shaka.text.UITextDisplayer');
9
9
 
10
10
  goog.require('goog.asserts');
11
11
  goog.require('shaka.Deprecate');
12
+ goog.require('shaka.log');
12
13
  goog.require('shaka.text.Cue');
13
14
  goog.require('shaka.text.CueRegion');
14
15
  goog.require('shaka.text.Utils');
@@ -35,6 +36,12 @@ shaka.text.UITextDisplayer = class {
35
36
  constructor(video, videoContainer, config) {
36
37
  goog.asserts.assert(videoContainer, 'videoContainer should be valid.');
37
38
 
39
+ if (!document.fullscreenEnabled) {
40
+ shaka.log.alwaysWarn('Using UITextDisplayer in a browser without ' +
41
+ 'Fullscreen API support causes subtitles to not be rendered in ' +
42
+ 'fullscreen');
43
+ }
44
+
38
45
  /** @private {boolean} */
39
46
  this.isTextVisible_ = false;
40
47
 
@@ -248,6 +255,7 @@ shaka.text.UITextDisplayer = class {
248
255
  */
249
256
  setTextVisibility(on) {
250
257
  this.isTextVisible_ = on;
258
+ this.updateCaptions_(/* forceUpdate= */ true);
251
259
  }
252
260
 
253
261
  /**
@@ -87,13 +87,7 @@ shaka.text.VttTextParser = class {
87
87
  // Only use 'X-TIMESTAMP-MAP' with HLS. This overrides offset above.
88
88
  if (blocks[0].includes('X-TIMESTAMP-MAP') &&
89
89
  this.manifestType_ == shaka.media.ManifestParser.HLS) {
90
- if (this.sequenceMode_) {
91
- // Compute a different, rollover-based offset for sequence mode.
92
- offset = this.computeHlsSequenceModeOffset_(blocks[0], time);
93
- } else {
94
- // Calculate the offset from the segment startTime.
95
- offset = time.segmentStart;
96
- }
90
+ offset = this.computeHlsOffset_(blocks[0], time);
97
91
  }
98
92
 
99
93
  // Parse VTT regions.
@@ -130,7 +124,7 @@ shaka.text.VttTextParser = class {
130
124
  * @return {number}
131
125
  * @private
132
126
  */
133
- computeHlsSequenceModeOffset_(headerBlock, time) {
127
+ computeHlsOffset_(headerBlock, time) {
134
128
  // https://bit.ly/2K92l7y
135
129
  // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with
136
130
  // the rest of the media.
@@ -39,8 +39,8 @@ shaka.util.AbortableOperation = class {
39
39
  /** @private {function():!Promise} */
40
40
  this.onAbort_ = onAbort;
41
41
 
42
- /** @private {boolean} */
43
- this.aborted_ = false;
42
+ /** @private {?Promise} */
43
+ this.abortPromise_ = null;
44
44
  }
45
45
 
46
46
  /**
@@ -110,8 +110,10 @@ shaka.util.AbortableOperation = class {
110
110
  * @export
111
111
  */
112
112
  abort() {
113
- this.aborted_ = true;
114
- return this.onAbort_();
113
+ if (!this.abortPromise_) {
114
+ this.abortPromise_ = this.onAbort_();
115
+ }
116
+ return this.abortPromise_;
115
117
  }
116
118
 
117
119
  /**
@@ -166,7 +168,7 @@ shaka.util.AbortableOperation = class {
166
168
 
167
169
  const makeCallback = (isSuccess) => {
168
170
  return (value) => {
169
- if (this.aborted_ && isSuccess) {
171
+ if (this.abortPromise_ && isSuccess) {
170
172
  // If "this" is not abortable(), or if abort() is called after "this"
171
173
  // is complete but before the next stage in the chain begins, we
172
174
  // should stop right away.
@@ -765,7 +765,7 @@ shaka.util.Mp4Generator = class {
765
765
  0x00, 0x00, 0x00, 0x01, // default_sample_description_index
766
766
  0x00, 0x00, 0x00, 0x00, // default_sample_duration
767
767
  0x00, 0x00, 0x00, 0x00, // default_sample_size
768
- 0x00, 0x01, 0x00, 0x01, // default_sample_flags
768
+ 0x00, 0x00, 0x00, 0x00, // default_sample_flags
769
769
  ]);
770
770
  return Mp4Generator.box('trex', bytes);
771
771
  }
@@ -205,6 +205,17 @@ shaka.util.Platform = class {
205
205
  shaka.util.Platform.chromeVersion() === 68;
206
206
  }
207
207
 
208
+ /**
209
+ * Check if the current platform is a WebOS 6.
210
+ *
211
+ * @return {boolean}
212
+ */
213
+ static isWebOS6() {
214
+ // See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
215
+ return shaka.util.Platform.isWebOS() &&
216
+ shaka.util.Platform.chromeVersion() === 79;
217
+ }
218
+
208
219
  /**
209
220
  * Check if the current platform is a Google Chromecast.
210
221
  *
@@ -300,7 +311,7 @@ shaka.util.Platform = class {
300
311
  !shaka.util.Platform.isOrange() &&
301
312
  !shaka.util.Platform.isPS4() &&
302
313
  !shaka.util.Platform.isAmazonFireTV() &&
303
- !shaka.util.Platform.isWPE() &&
314
+ !shaka.util.Platform.isComcastX1() &&
304
315
  !shaka.util.Platform.isZenterio() &&
305
316
  !shaka.util.Platform.isSkyQ();
306
317
  }
@@ -368,7 +379,7 @@ shaka.util.Platform = class {
368
379
  * Check if the current platform is Comcast X1.
369
380
  * @return {boolean}
370
381
  */
371
- static isWPE() {
382
+ static isComcastX1() {
372
383
  return shaka.util.Platform.userAgentContains_('WPE');
373
384
  }
374
385
 
@@ -503,7 +514,7 @@ shaka.util.Platform = class {
503
514
  if (!navigator.platform) {
504
515
  return false;
505
516
  }
506
- return navigator.platform.toLowerCase().includes('windows');
517
+ return navigator.platform.toLowerCase().includes('win32');
507
518
  }
508
519
 
509
520
  /**
@@ -536,7 +547,7 @@ shaka.util.Platform = class {
536
547
  Platform.isPS5() || Platform.isAmazonFireTV() ||
537
548
  Platform.isEOS() || Platform.isAPL() ||
538
549
  Platform.isVirginMedia() || Platform.isOrange() ||
539
- Platform.isWPE() || Platform.isChromecast() ||
550
+ Platform.isComcastX1() || Platform.isChromecast() ||
540
551
  Platform.isHisense() || Platform.isZenterio()) {
541
552
  return true;
542
553
  }
@@ -627,19 +638,19 @@ shaka.util.Platform = class {
627
638
  /**
628
639
  * Returns if codec switching SMOOTH is known reliable device support.
629
640
  *
630
- * Some devices are known not to support `MediaSource.changeType`
641
+ * Some devices are known not to support <code>SourceBuffer.changeType</code>
631
642
  * well. These devices should use the reload strategy. If a device
632
- * reports that it supports `changeType` but support it unreliably
633
- * it should be added to this list.
643
+ * reports that it supports <code<changeType</code> but supports it unreliably
644
+ * it should be disallowed in this method.
634
645
  *
635
646
  * @return {boolean}
636
647
  */
637
648
  static supportsSmoothCodecSwitching() {
638
649
  const Platform = shaka.util.Platform;
639
- if (Platform.isTizen2() || Platform.isTizen3() || Platform.isTizen4() ||
640
- Platform.isTizen5() || Platform.isTizen6() || Platform.isWebOS3() ||
641
- Platform.isWebOS4() || Platform.isWebOS5() || Platform.isPS4() ||
642
- Platform.isPS5()) {
650
+ // All Tizen versions (up to Tizen 8) do not support SMOOTH so far.
651
+ // webOS seems to support SMOOTH from webOS 22.
652
+ if (Platform.isTizen() || Platform.isPS4() || Platform.isPS5() ||
653
+ Platform.isWebOS6()) {
643
654
  return false;
644
655
  }
645
656
  // Older chromecasts without GoogleTV seem to not support SMOOTH properly.
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.9.30",
4
+ "version": "4.9.32",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -26,7 +26,7 @@
26
26
  "core-js": "^3.21.1",
27
27
  "dialog-polyfill": "^0.5.6",
28
28
  "es6-promise-polyfill": "^1.2.0",
29
- "eslint": "^8.9.0",
29
+ "eslint": "^8.57.1",
30
30
  "eslint-config-google": "^0.14.0",
31
31
  "eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules",
32
32
  "esprima": "^4.0.1",
package/ui/controls.js CHANGED
@@ -1301,7 +1301,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1301
1301
  }
1302
1302
 
1303
1303
  // Use the cursor specified in the CSS file.
1304
- this.videoContainer_.style.cursor = '';
1304
+ this.videoContainer_.classList.remove('no-cursor');
1305
1305
 
1306
1306
  this.recentMouseMovement_ = true;
1307
1307
 
@@ -1354,7 +1354,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1354
1354
  */
1355
1355
  onMouseStill_() {
1356
1356
  // Hide the cursor.
1357
- this.videoContainer_.style.cursor = 'none';
1357
+ this.videoContainer_.classList.add('no-cursor');
1358
1358
  this.recentMouseMovement_ = false;
1359
1359
  this.computeOpacity();
1360
1360
  }
@@ -394,3 +394,11 @@
394
394
  /* keep the rewind button to the left */
395
395
  .absolute-position();
396
396
  }
397
+
398
+ .shaka-video-container.no-cursor {
399
+ cursor: none !important;
400
+
401
+ * {
402
+ cursor: none !important;
403
+ }
404
+ }
@@ -33,6 +33,9 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
33
33
  constructor(parent, controls) {
34
34
  super(parent, controls);
35
35
 
36
+ /** @private {boolean} */
37
+ this.isAirPlay_ = shaka.util.Platform.isSafari();
38
+
36
39
  /** @private {!HTMLButtonElement} */
37
40
  this.remoteButton_ = shaka.util.Dom.createButton();
38
41
  this.remoteButton_.classList.add('shaka-remote-button');
@@ -42,12 +45,9 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
42
45
  /** @private {!HTMLElement} */
43
46
  this.remoteIcon_ = shaka.util.Dom.createHTMLElement('i');
44
47
  this.remoteIcon_.classList.add('material-icons-round');
45
- let icon = shaka.ui.Enums.MaterialDesignIcons.CAST;
46
- const safariVersion = shaka.util.Platform.safariVersion();
47
- if (safariVersion && safariVersion >= 13) {
48
- icon = shaka.ui.Enums.MaterialDesignIcons.AIRPLAY;
49
- }
50
- this.remoteIcon_.textContent = icon;
48
+ this.remoteIcon_.textContent = this.isAirPlay_ ?
49
+ shaka.ui.Enums.MaterialDesignIcons.AIRPLAY :
50
+ shaka.ui.Enums.MaterialDesignIcons.CAST;
51
51
  this.remoteButton_.appendChild(this.remoteIcon_);
52
52
 
53
53
  const label = shaka.util.Dom.createHTMLElement('label');
@@ -95,21 +95,25 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
95
95
 
96
96
  this.eventManager.listen(this.video.remote, 'connect', () => {
97
97
  this.updateRemoteState_();
98
+ this.updateIcon_();
98
99
  });
99
100
 
100
101
  this.eventManager.listen(this.video.remote, 'connecting', () => {
101
102
  this.updateRemoteState_();
103
+ this.updateIcon_();
102
104
  });
103
105
 
104
106
  this.eventManager.listen(this.video.remote, 'disconnect', () => {
105
107
  this.updateRemoteState_();
108
+ this.updateIcon_();
106
109
  });
107
110
 
108
111
  this.eventManager.listen(this.player, 'loaded', () => {
109
112
  this.updateRemoteState_();
110
113
  });
111
114
 
112
- this.updateRemoteState_();
115
+ this.updateRemoteState_(/* force= */ true);
116
+ this.updateIcon_();
113
117
  }
114
118
  }
115
119
 
@@ -125,9 +129,10 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
125
129
  }
126
130
 
127
131
  /**
132
+ * @param {boolean=} force
128
133
  * @private
129
134
  */
130
- async updateRemoteState_() {
135
+ async updateRemoteState_(force = false) {
131
136
  if (this.controls.getCastProxy().canCast() &&
132
137
  this.controls.isCastAllowed()) {
133
138
  shaka.ui.Utils.setDisplay(this.remoteButton_, false);
@@ -135,14 +140,20 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
135
140
  this.video.remote.cancelWatchAvailability(this.callbackId_);
136
141
  this.callbackId_ = -1;
137
142
  }
138
- } else if (this.video.remote.state == 'disconnected') {
143
+ } else if (this.video.remote.state == 'disconnected' || force) {
139
144
  const handleAvailabilityChange = (availability) => {
140
145
  if (this.player) {
141
146
  const disableRemote = this.video.disableRemotePlayback;
142
- const loadMode = this.player.getLoadMode();
143
- const srcMode = loadMode == shaka.Player.LoadMode.SRC_EQUALS;
147
+ let canCast = true;
148
+ if (shaka.util.Platform.isSafari()) {
149
+ const loadMode = this.player.getLoadMode();
150
+ const mseMode = loadMode == shaka.Player.LoadMode.MEDIA_SOURCE;
151
+ if (mseMode && this.player.getManifestType() != 'HLS') {
152
+ canCast = false;
153
+ }
154
+ }
144
155
  shaka.ui.Utils.setDisplay(
145
- this.remoteButton_, srcMode && availability && !disableRemote);
156
+ this.remoteButton_, canCast && availability && !disableRemote);
146
157
  } else {
147
158
  shaka.ui.Utils.setDisplay(this.remoteButton_, false);
148
159
  }
@@ -175,14 +186,28 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
175
186
  */
176
187
  updateLocalizedStrings_() {
177
188
  const LocIds = shaka.ui.Locales.Ids;
178
- let text = this.localization.resolve(LocIds.CAST);
179
- const safariVersion = shaka.util.Platform.safariVersion();
180
- if (safariVersion && safariVersion >= 13) {
181
- text = this.localization.resolve(LocIds.AIRPLAY);
182
- }
189
+ const text = this.isAirPlay_ ?
190
+ this.localization.resolve(LocIds.AIRPLAY) :
191
+ this.localization.resolve(LocIds.CAST);
183
192
  this.remoteButton_.ariaLabel = text;
184
193
  this.remoteNameSpan_.textContent = text;
185
194
  }
195
+
196
+ /**
197
+ * @private
198
+ */
199
+ updateIcon_() {
200
+ if (this.isAirPlay_) {
201
+ return;
202
+ }
203
+ if (this.video.remote.state == 'disconnected') {
204
+ this.remoteIcon_.textContent =
205
+ shaka.ui.Enums.MaterialDesignIcons.CAST;
206
+ } else {
207
+ this.remoteIcon_.textContent =
208
+ shaka.ui.Enums.MaterialDesignIcons.EXIT_CAST;
209
+ }
210
+ }
186
211
  };
187
212
 
188
213
 
@@ -232,8 +232,6 @@ shaka.ui.StatisticsButton = class extends shaka.ui.Element {
232
232
  const element = this.generateComponent_(name);
233
233
  this.container_.appendChild(element);
234
234
  this.statisticsList_.push(name);
235
- shaka.ui.Utils.setDisplay(element.parentElement,
236
- !isNaN(this.currentStats_[name]));
237
235
  } else {
238
236
  shaka.log.alwaysWarn('Unrecognized statistic element:', name);
239
237
  }