shaka-player 4.11.13 → 4.11.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.
@@ -18,39 +18,39 @@
18
18
  * @template T
19
19
  * @exportDoc
20
20
  */
21
- shaka.extern.IAbortableOperation = function() {};
22
-
23
-
24
- /**
25
- * A Promise which represents the underlying operation. It is resolved when
26
- * the operation is complete, and rejected if the operation fails or is
27
- * aborted. Aborted operations should be rejected with a shaka.util.Error
28
- * object using the error code OPERATION_ABORTED.
29
- *
30
- * @const {!Promise.<T>}
31
- * @exportDoc
32
- */
33
- shaka.extern.IAbortableOperation.prototype.promise;
34
-
35
-
36
- /**
37
- * Can be called by anyone holding this object to abort the underlying
38
- * operation. This is not cancelation, and will not necessarily result in
39
- * any work being undone. abort() should return a Promise which is resolved
40
- * when the underlying operation has been aborted. The returned Promise
41
- * should never be rejected.
42
- *
43
- * @return {!Promise}
44
- * @exportDoc
45
- */
46
- shaka.extern.IAbortableOperation.prototype.abort = function() {};
47
-
48
-
49
- /**
50
- * @param {function(boolean)} onFinal A callback to be invoked after the
51
- * operation succeeds or fails. The boolean argument is true if the operation
52
- * succeeded and false if it failed.
53
- * @return {!shaka.extern.IAbortableOperation.<T>} Returns this.
54
- * @exportDoc
55
- */
56
- shaka.extern.IAbortableOperation.prototype.finally = function(onFinal) {};
21
+ shaka.extern.IAbortableOperation = class {
22
+ constructor() {
23
+ /**
24
+ * A Promise which represents the underlying operation. It is resolved when
25
+ * the operation is complete, and rejected if the operation fails or is
26
+ * aborted. Aborted operations should be rejected with a shaka.util.Error
27
+ * object using the error code OPERATION_ABORTED.
28
+ *
29
+ * @const {!Promise.<T>}
30
+ * @exportDoc
31
+ */
32
+ this.promise;
33
+ }
34
+
35
+ /**
36
+ * Can be called by anyone holding this object to abort the underlying
37
+ * operation. This is not cancelation, and will not necessarily result in
38
+ * any work being undone. abort() should return a Promise which is resolved
39
+ * when the underlying operation has been aborted. The returned Promise
40
+ * should never be rejected.
41
+ *
42
+ * @return {!Promise}
43
+ * @exportDoc
44
+ */
45
+ abort() {}
46
+
47
+
48
+ /**
49
+ * @param {function(boolean)} onFinal A callback to be invoked after the
50
+ * operation succeeds or fails. The boolean argument is true
51
+ * if the operation succeeded and false if it failed.
52
+ * @return {!shaka.extern.IAbortableOperation.<T>} Returns this.
53
+ * @exportDoc
54
+ */
55
+ finally(onFinal) {}
56
+ };
@@ -955,6 +955,8 @@ shaka.ads.InterstitialAdManager = class {
955
955
  goog.asserts.assert(this.player_, 'Must have player');
956
956
  goog.asserts.assert(this.basePlayer_, 'Must have base player');
957
957
  this.player_.configure(this.basePlayer_.getNonDefaultConfiguration());
958
+ this.player_.configure('ads.disableHLSInterstitial', true);
959
+ this.player_.configure('ads.disableDASHInterstitial', true);
958
960
  const netEngine = this.player_.getNetworkingEngine();
959
961
  goog.asserts.assert(netEngine, 'Need networking engine');
960
962
  netEngine.clearAllRequestFilters();
@@ -4122,7 +4122,7 @@ shaka.hls.HlsParser = class {
4122
4122
  let aesKey = undefined;
4123
4123
 
4124
4124
  let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
4125
- playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
4125
+ playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', -1);
4126
4126
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
4127
4127
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
4128
4128
  const skipTag = shaka.hls.Utils.getFirstTagWithName(
@@ -1715,6 +1715,7 @@ shaka.media.DrmEngine = class {
1715
1715
  const testKeySystems = [
1716
1716
  'org.w3.clearkey',
1717
1717
  'com.widevine.alpha',
1718
+ 'com.widevine.alpha.experiment', // Widevine L1 in Windows
1718
1719
  'com.microsoft.playready',
1719
1720
  'com.microsoft.playready.hardware',
1720
1721
  'com.microsoft.playready.recommendation',
@@ -1739,6 +1740,7 @@ shaka.media.DrmEngine = class {
1739
1740
 
1740
1741
  const testRobustness = {
1741
1742
  'com.widevine.alpha': widevineRobustness,
1743
+ 'com.widevine.alpha.experiment': widevineRobustness,
1742
1744
  'com.microsoft.playready.recommendation': playreadyRobustness,
1743
1745
  };
1744
1746
 
@@ -883,7 +883,8 @@ shaka.media.MediaSourceEngine = class {
883
883
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
884
884
 
885
885
  if (contentType == ContentType.TEXT) {
886
- if (this.sequenceMode_) {
886
+ if (this.manifestType_ == shaka.media.ManifestParser.HLS &&
887
+ reference.discontinuitySequence >= 0) {
887
888
  // This won't be known until the first video segment is appended.
888
889
  const offset = await this.textSequenceModeOffset_;
889
890
  this.textEngine_.setTimestampOffset(offset);
@@ -977,7 +978,7 @@ shaka.media.MediaSourceEngine = class {
977
978
  const isBestSourceBufferForTimestamps =
978
979
  contentType == ContentType.VIDEO ||
979
980
  !(ContentType.VIDEO in this.sourceBuffers_);
980
- if (this.sequenceMode_ && isBestSourceBufferForTimestamps) {
981
+ if (isBestSourceBufferForTimestamps) {
981
982
  this.textSequenceModeOffset_.resolve(timestampOffset);
982
983
  }
983
984
  }
@@ -1306,6 +1307,10 @@ shaka.media.MediaSourceEngine = class {
1306
1307
  this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
1307
1308
  }
1308
1309
 
1310
+ if (!this.sequenceMode_) {
1311
+ return;
1312
+ }
1313
+
1309
1314
  // Queue an abort() to help MSE splice together overlapping segments.
1310
1315
  // We set appendWindowEnd when we change periods in DASH content, and the
1311
1316
  // period transition may result in overlap.
@@ -1839,6 +1844,48 @@ shaka.media.MediaSourceEngine = class {
1839
1844
  null);
1840
1845
  }
1841
1846
 
1847
+ /**
1848
+ * Returns the source buffer parameters
1849
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1850
+ * @return {?shaka.media.MediaSourceEngine.SourceBufferParams}
1851
+ * @private
1852
+ */
1853
+ getSourceBufferParams_(contentType) {
1854
+ if (!this.sourceBuffers_[contentType]) {
1855
+ return null;
1856
+ }
1857
+ return {
1858
+ timestampOffset: this.sourceBuffers_[contentType].timestampOffset,
1859
+ appendWindowStart: this.sourceBuffers_[contentType].appendWindowStart,
1860
+ appendWindowEnd: this.sourceBuffers_[contentType].appendWindowEnd,
1861
+ };
1862
+ }
1863
+
1864
+ /**
1865
+ * Restore source buffer parameters
1866
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1867
+ * @param {?shaka.media.MediaSourceEngine.SourceBufferParams} params
1868
+ * @private
1869
+ */
1870
+ restoreSourceBufferParams_(contentType, params) {
1871
+ if (!params) {
1872
+ return;
1873
+ }
1874
+
1875
+ if (!this.sourceBuffers_[contentType]) {
1876
+ shaka.log.warning('Attempted to restore a non-existent source buffer');
1877
+ return;
1878
+ }
1879
+
1880
+ this.sourceBuffers_[contentType].timestampOffset =
1881
+ params.timestampOffset;
1882
+ // `end` needs to be set before `start`
1883
+ this.sourceBuffers_[contentType].appendWindowEnd =
1884
+ params.appendWindowEnd;
1885
+ this.sourceBuffers_[contentType].appendWindowStart =
1886
+ params.appendWindowStart;
1887
+ }
1888
+
1842
1889
  /**
1843
1890
  * Resets the MediaSource and re-adds source buffers due to codec mismatch
1844
1891
  *
@@ -1899,7 +1946,6 @@ shaka.media.MediaSourceEngine = class {
1899
1946
  }
1900
1947
  await Promise.all(cleanup);
1901
1948
  this.transmuxers_ = {};
1902
- this.sourceBuffers_ = {};
1903
1949
 
1904
1950
  const previousDuration = this.mediaSource_.duration;
1905
1951
  this.mediaSourceOpen_ = new shaka.util.PublicPromise();
@@ -1930,6 +1976,7 @@ shaka.media.MediaSourceEngine = class {
1930
1976
  onSourceBufferAdded);
1931
1977
 
1932
1978
  for (const contentType of streamsByType.keys()) {
1979
+ const previousParams = this.getSourceBufferParams_(contentType);
1933
1980
  const stream = streamsByType.get(contentType);
1934
1981
  // eslint-disable-next-line no-await-in-loop
1935
1982
  await this.initSourceBuffer_(contentType, stream, stream.codecs);
@@ -1939,6 +1986,8 @@ shaka.media.MediaSourceEngine = class {
1939
1986
  } else {
1940
1987
  this.queues_[contentType] = [];
1941
1988
  }
1989
+
1990
+ this.restoreSourceBufferParams_(contentType, previousParams);
1942
1991
  }
1943
1992
 
1944
1993
  // Fake a seek to catchup the playhead.
@@ -2226,3 +2275,12 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
2226
2275
  * Callback to use when metadata arrives.
2227
2276
  */
2228
2277
  shaka.media.MediaSourceEngine.PlayerInterface;
2278
+
2279
+ /**
2280
+ * @typedef {{
2281
+ * timestampOffset: number,
2282
+ * appendWindowStart: number,
2283
+ * appendWindowEnd: number
2284
+ * }}
2285
+ */
2286
+ shaka.media.MediaSourceEngine.SourceBufferParams;
@@ -403,7 +403,12 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
403
403
 
404
404
  this.successPromise_.resolve();
405
405
  } catch (error) {
406
- this.successPromise_.reject(error);
406
+ // Ignore OPERATION_ABORTED and OBJECT_DESTROYED errors.
407
+ if (!(error instanceof shaka.util.Error) ||
408
+ (error.code != shaka.util.Error.Code.OPERATION_ABORTED &&
409
+ error.code != shaka.util.Error.Code.OBJECT_DESTROYED)) {
410
+ this.successPromise_.reject(error);
411
+ }
407
412
  }
408
413
  })();
409
414
  }
@@ -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;
@@ -2148,17 +2148,15 @@ shaka.media.StreamingEngine = class {
2148
2148
  }
2149
2149
  }
2150
2150
 
2151
- if (this.manifest_.sequenceMode) {
2152
- const lastDiscontinuitySequence =
2153
- mediaState.lastSegmentReference ?
2154
- mediaState.lastSegmentReference.discontinuitySequence : null;
2155
- // Across discontinuity bounds, we should resync timestamps for
2156
- // sequence mode playbacks. The next segment appended should
2157
- // land at its theoretical timestamp from the segment index.
2158
- if (reference.discontinuitySequence != lastDiscontinuitySequence) {
2159
- operations.push(this.playerInterface_.mediaSourceEngine.resync(
2160
- mediaState.type, reference.startTime));
2161
- }
2151
+ const lastDiscontinuitySequence =
2152
+ mediaState.lastSegmentReference ?
2153
+ mediaState.lastSegmentReference.discontinuitySequence : null;
2154
+ // Across discontinuity bounds, we should resync timestamps. The next
2155
+ // segment appended should land at its theoretical timestamp from the
2156
+ // segment index.
2157
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
2158
+ operations.push(this.playerInterface_.mediaSourceEngine.resync(
2159
+ mediaState.type, reference.startTime));
2162
2160
  }
2163
2161
 
2164
2162
  await Promise.all(operations);
package/lib/player.js CHANGED
@@ -8123,7 +8123,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
8123
8123
  * @export
8124
8124
  */
8125
8125
  // eslint-disable-next-line no-useless-concat, max-len
8126
- shaka.Player.version = 'v4.11.13' + '-uncompiled'; // x-release-please-version
8126
+ shaka.Player.version = 'v4.11.14' + '-uncompiled'; // x-release-please-version
8127
8127
 
8128
8128
  // Initialize the deprecation system using the version string we just set
8129
8129
  // on the player.
@@ -327,18 +327,56 @@ shaka.polyfill.MediaCapabilities = class {
327
327
 
328
328
  let displayType = videoConfig.contentType;
329
329
  if (videoConfig.width && videoConfig.height) {
330
- displayType +=
331
- `; 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
+ }
332
335
  }
333
336
  if (videoConfig.framerate) {
334
- 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
+ }
335
342
  }
336
- if (videoConfig.transferFunction === 'pq') {
337
- // A "PQ" transfer function indicates this is an HDR-capable stream;
338
- // "smpte2084" is the published standard. We need to inform the platform
339
- // this query is specifically for HDR.
340
- 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;
341
378
  }
379
+
342
380
  let result = false;
343
381
  if (displayType in shaka.polyfill.MediaCapabilities
344
382
  .memoizedCanDisplayTypeRequests_) {
@@ -86,13 +86,7 @@ shaka.text.VttTextParser = class {
86
86
  // Only use 'X-TIMESTAMP-MAP' with HLS. This overrides offset above.
87
87
  if (blocks[0].includes('X-TIMESTAMP-MAP') &&
88
88
  this.manifestType_ == shaka.media.ManifestParser.HLS) {
89
- if (this.sequenceMode_) {
90
- // Compute a different, rollover-based offset for sequence mode.
91
- offset = this.computeHlsSequenceModeOffset_(blocks[0], time);
92
- } else {
93
- // Calculate the offset from the segment startTime.
94
- offset = time.segmentStart;
95
- }
89
+ offset = this.computeHlsOffset_(blocks[0], time);
96
90
  }
97
91
 
98
92
  // Parse VTT regions.
@@ -129,7 +123,7 @@ shaka.text.VttTextParser = class {
129
123
  * @return {number}
130
124
  * @private
131
125
  */
132
- computeHlsSequenceModeOffset_(headerBlock, time) {
126
+ computeHlsOffset_(headerBlock, time) {
133
127
  // https://bit.ly/2K92l7y
134
128
  // The 'X-TIMESTAMP-MAP' header is used in HLS to align text with
135
129
  // 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
  /**
@@ -48,7 +48,7 @@ shaka.util.AbortableOperation = class {
48
48
  * @export
49
49
  */
50
50
  get aborted() {
51
- return this.aborted_;
51
+ return this.abortPromise_ !== null;
52
52
  }
53
53
 
54
54
  /**
@@ -118,8 +118,10 @@ shaka.util.AbortableOperation = class {
118
118
  * @export
119
119
  */
120
120
  abort() {
121
- this.aborted_ = true;
122
- return this.onAbort_();
121
+ if (!this.abortPromise_) {
122
+ this.abortPromise_ = this.onAbort_();
123
+ }
124
+ return this.abortPromise_;
123
125
  }
124
126
 
125
127
  /**
@@ -174,7 +176,7 @@ shaka.util.AbortableOperation = class {
174
176
 
175
177
  const makeCallback = (isSuccess) => {
176
178
  return (value) => {
177
- if (this.aborted_ && isSuccess) {
179
+ if (this.abortPromise_ && isSuccess) {
178
180
  // If "this" is not abortable(), or if abort() is called after "this"
179
181
  // is complete but before the next stage in the chain begins, we
180
182
  // should stop right away.
@@ -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
  *
@@ -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.11.13",
4
+ "version": "4.11.14",
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",
@@ -119,14 +119,18 @@ shaka.ui.ChapterSelection = class extends shaka.ui.SettingsMenu {
119
119
  const currentLocales = this.localization.getCurrentLocales();
120
120
  for (const locale of Array.from(currentLocales)) {
121
121
  nextLanguage = locale;
122
- nextChapters = this.player.getChapters(nextLanguage);
122
+ // If player is a proxy, and the cast receiver doesn't support this
123
+ // method, you get back undefined.
124
+ nextChapters = this.player.getChapters(nextLanguage) || [];
123
125
  if (nextChapters.length) {
124
126
  break;
125
127
  }
126
128
  }
127
129
  if (!nextChapters.length) {
128
130
  nextLanguage = 'und';
129
- nextChapters = this.player.getChapters(nextLanguage);
131
+ // If player is a proxy, and the cast receiver doesn't support this
132
+ // method, you get back undefined.
133
+ nextChapters = this.player.getChapters(nextLanguage) || [];
130
134
  }
131
135
 
132
136
  const languageChanged = nextLanguage !== this.chaptersLanguage_;