shaka-player 4.11.13 → 4.11.15

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 (52) hide show
  1. package/.csslintrc +2 -1
  2. package/.eslintrc.js +1 -0
  3. package/AUTHORS +1 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/dist/controls.css +3 -3
  6. package/dist/controls.css.map +1 -1
  7. package/dist/deps.js +3 -2
  8. package/dist/locales.js +36 -36
  9. package/dist/shaka-player.compiled.d.ts +7 -7
  10. package/dist/shaka-player.compiled.debug.d.ts +7 -7
  11. package/dist/shaka-player.compiled.debug.js +326 -324
  12. package/dist/shaka-player.compiled.debug.map +4 -4
  13. package/dist/shaka-player.compiled.js +1317 -1313
  14. package/dist/shaka-player.compiled.map +4 -4
  15. package/dist/shaka-player.ui.d.ts +7 -7
  16. package/dist/shaka-player.ui.debug.d.ts +7 -7
  17. package/dist/shaka-player.ui.debug.js +328 -326
  18. package/dist/shaka-player.ui.debug.map +4 -4
  19. package/dist/shaka-player.ui.js +1520 -1516
  20. package/dist/shaka-player.ui.map +4 -4
  21. package/externs/managedmediasource.js +6 -0
  22. package/externs/shaka/abortable.js +36 -36
  23. package/lib/ads/interstitial_ad_manager.js +41 -15
  24. package/lib/cast/cast_utils.js +4 -2
  25. package/lib/hls/hls_parser.js +1 -1
  26. package/lib/media/drm_engine.js +2 -0
  27. package/lib/media/media_source_capabilities.js +6 -8
  28. package/lib/media/media_source_engine.js +61 -4
  29. package/lib/media/preload_manager.js +6 -1
  30. package/lib/media/segment_reference.js +1 -1
  31. package/lib/media/segment_utils.js +7 -3
  32. package/lib/media/streaming_engine.js +9 -11
  33. package/lib/net/http_xhr_plugin.js +1 -0
  34. package/lib/player.js +42 -36
  35. package/lib/polyfill/media_capabilities.js +46 -8
  36. package/lib/text/vtt_text_parser.js +9 -10
  37. package/lib/util/abortable_operation.js +11 -6
  38. package/lib/util/buffer_utils.js +10 -8
  39. package/lib/util/mime_utils.js +2 -0
  40. package/lib/util/number_utils.js +37 -0
  41. package/lib/util/object_utils.js +1 -1
  42. package/lib/util/platform.js +18 -7
  43. package/lib/util/uint8array_utils.js +3 -2
  44. package/package.json +3 -3
  45. package/ui/chapter_selection.js +6 -2
  46. package/ui/language_utils.js +3 -1
  47. package/ui/less/ad_controls.less +3 -3
  48. package/ui/less/buttons.less +4 -4
  49. package/ui/less/containers.less +7 -7
  50. package/ui/less/overflow_menu.less +4 -4
  51. package/ui/less/thumbnails.less +3 -3
  52. package/ui/less/tooltip.less +1 -1
@@ -22,3 +22,9 @@ function ManagedMediaSource() {}
22
22
  * @return {boolean}
23
23
  */
24
24
  ManagedMediaSource.isTypeSupported = function(type) {};
25
+
26
+ /**
27
+ * @constructor
28
+ * @extends {SourceBuffer}
29
+ */
30
+ function ManagedSourceBuffer() {}
@@ -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
+ };
@@ -102,7 +102,7 @@ shaka.ads.InterstitialAdManager = class {
102
102
  }
103
103
  this.lastTime_ = this.baseVideo_.currentTime;
104
104
  const currentInterstitial = this.getCurrentInterstitial_(
105
- /* needPreRoll= */ true, /* numberToSkip= */ 0, this.lastPlayedAd_);
105
+ /* needPreRoll= */ true);
106
106
  if (currentInterstitial) {
107
107
  this.setupAd_(currentInterstitial, /* sequenceLength= */ 1,
108
108
  /* adPosition= */ 1, /* initialTime= */ Date.now());
@@ -121,14 +121,17 @@ shaka.ads.InterstitialAdManager = class {
121
121
  this.lastTime_ < this.lastPlayedAd_.startTime) {
122
122
  this.lastPlayedAd_ = null;
123
123
  }
124
- const currentInterstitial = this.getCurrentInterstitial_(
125
- /* needPreRoll= */ false, /* numberToSkip= */ 0, this.lastPlayedAd_);
124
+ const currentInterstitial = this.getCurrentInterstitial_();
126
125
  if (currentInterstitial) {
127
126
  this.setupAd_(currentInterstitial, /* sequenceLength= */ 1,
128
127
  /* adPosition= */ 1, /* initialTime= */ Date.now());
129
128
  }
130
129
  };
131
130
 
131
+ this.eventManager_.listen(this.baseVideo_, 'ended', () => {
132
+ checkForInterstitials();
133
+ });
134
+
132
135
  /** @private {shaka.util.Timer} */
133
136
  this.timeUpdateTimer_ = new shaka.util.Timer(checkForInterstitials);
134
137
 
@@ -320,7 +323,7 @@ shaka.ads.InterstitialAdManager = class {
320
323
  return;
321
324
  }
322
325
  const interstitial = {
323
- id: null,
326
+ id: region.id,
324
327
  startTime: region.startTime,
325
328
  endTime: region.endTime,
326
329
  uri: alternativeMPDUri,
@@ -433,13 +436,12 @@ shaka.ads.InterstitialAdManager = class {
433
436
 
434
437
 
435
438
  /**
436
- * @param {boolean} needPreRoll
437
- * @param {number} numberToSkip
438
- * @param {?shaka.extern.AdInterstitial=} lastPlayedAd
439
+ * @param {boolean=} needPreRoll
440
+ * @param {?number=} numberToSkip
439
441
  * @return {?shaka.extern.AdInterstitial}
440
442
  * @private
441
443
  */
442
- getCurrentInterstitial_(needPreRoll, numberToSkip, lastPlayedAd) {
444
+ getCurrentInterstitial_(needPreRoll = false, numberToSkip = null) {
443
445
  let skipped = 0;
444
446
  let currentInterstitial = null;
445
447
  if (this.interstitials_.size && this.lastTime_ != null) {
@@ -450,7 +452,15 @@ shaka.ads.InterstitialAdManager = class {
450
452
  const roundDecimals = (number) => {
451
453
  return Math.round(number * 1000) / 1000;
452
454
  };
453
- for (const interstitial of interstitials) {
455
+ let interstitialsToCheck = interstitials;
456
+ if (needPreRoll) {
457
+ interstitialsToCheck = interstitials.filter((i) => i.pre);
458
+ } else if (isEnded) {
459
+ interstitialsToCheck = interstitials.filter((i) => i.post);
460
+ } else {
461
+ interstitialsToCheck = interstitials.filter((i) => !i.pre && !i.post);
462
+ }
463
+ for (const interstitial of interstitialsToCheck) {
454
464
  let isValid = false;
455
465
  if (needPreRoll) {
456
466
  isValid = interstitial.pre;
@@ -461,18 +471,28 @@ shaka.ads.InterstitialAdManager = class {
461
471
  this.lastTime_ - roundDecimals(interstitial.startTime);
462
472
  if (difference > 0 &&
463
473
  (difference <= 1 || !interstitial.canJump)) {
464
- if (lastPlayedAd && !lastPlayedAd.pre && !lastPlayedAd.post &&
465
- lastPlayedAd.startTime >= interstitial.startTime) {
474
+ if (numberToSkip == null && this.lastPlayedAd_ &&
475
+ !this.lastPlayedAd_.pre && !this.lastPlayedAd_.post &&
476
+ this.lastPlayedAd_.startTime >= interstitial.startTime) {
466
477
  isValid = false;
467
478
  } else {
468
479
  isValid = true;
469
480
  }
470
481
  }
471
482
  }
472
- if (isValid) {
473
- if (skipped == numberToSkip) {
483
+ if (isValid && (!this.lastPlayedAd_ ||
484
+ interstitial.startTime >= this.lastPlayedAd_.startTime)) {
485
+ if (skipped == (numberToSkip || 0)) {
474
486
  currentInterstitial = interstitial;
475
- break;
487
+ } else if (currentInterstitial && !interstitial.canJump) {
488
+ const currentStartTime =
489
+ roundDecimals(currentInterstitial.startTime);
490
+ const newStartTime =
491
+ roundDecimals(interstitial.startTime);
492
+ if (newStartTime - currentStartTime > 0.001) {
493
+ currentInterstitial = interstitial;
494
+ skipped = 0;
495
+ }
476
496
  }
477
497
  skipped++;
478
498
  }
@@ -517,7 +537,7 @@ shaka.ads.InterstitialAdManager = class {
517
537
  }).length;
518
538
  }
519
539
 
520
- if (interstitial.once || interstitial.pre) {
540
+ if (interstitial.once) {
521
541
  oncePlayed++;
522
542
  this.interstitials_.delete(interstitial);
523
543
  this.cuepointsChanged_();
@@ -591,6 +611,10 @@ shaka.ads.InterstitialAdManager = class {
591
611
  this.setupAd_(nextCurrentInterstitial, sequenceLength,
592
612
  ++adPosition, initialTime, oncePlayed);
593
613
  } else {
614
+ if (interstitial.post) {
615
+ this.lastTime_ = null;
616
+ this.lastPlayedAd_ = null;
617
+ }
594
618
  if (this.usingBaseVideo_) {
595
619
  await this.player_.detach();
596
620
  } else {
@@ -955,6 +979,8 @@ shaka.ads.InterstitialAdManager = class {
955
979
  goog.asserts.assert(this.player_, 'Must have player');
956
980
  goog.asserts.assert(this.basePlayer_, 'Must have base player');
957
981
  this.player_.configure(this.basePlayer_.getNonDefaultConfiguration());
982
+ this.player_.configure('ads.disableHLSInterstitial', true);
983
+ this.player_.configure('ads.disableDASHInterstitial', true);
958
984
  const netEngine = this.player_.getNetworkingEngine();
959
985
  goog.asserts.assert(netEngine, 'Need networking engine');
960
986
  netEngine.clearAllRequestFilters();
@@ -66,10 +66,12 @@ shaka.cast.CastUtils = class {
66
66
  return shaka.cast.CastUtils.unpackTimeRanges_(value);
67
67
  }
68
68
 
69
- if (value instanceof Uint8Array) {
69
+ if (ArrayBuffer.isView(value) &&
70
+ /** @type {TypedArray} */(value).BYTES_PER_ELEMENT === 1) {
70
71
  // Some of our code cares about Uint8Arrays actually being Uint8Arrays,
71
72
  // so this gives them special treatment.
72
- return shaka.cast.CastUtils.unpackUint8Array_(value);
73
+ return shaka.cast.CastUtils.unpackUint8Array_(
74
+ /** @type {!Uint8Array} */(value));
73
75
  }
74
76
 
75
77
  if (typeof value == 'number') {
@@ -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
 
@@ -23,12 +23,9 @@ shaka.media.Capabilities = class {
23
23
  if (supportMap.has(type)) {
24
24
  return supportMap.get(type);
25
25
  }
26
- if (window.ManagedMediaSource) {
27
- const currentSupport = ManagedMediaSource.isTypeSupported(type);
28
- supportMap.set(type, currentSupport);
29
- return currentSupport;
30
- } else if (window.MediaSource) {
31
- const currentSupport = MediaSource.isTypeSupported(type);
26
+ const mediaSource = window.ManagedMediaSource || window.MediaSource;
27
+ if (mediaSource) {
28
+ const currentSupport = mediaSource.isTypeSupported(type);
32
29
  supportMap.set(type, currentSupport);
33
30
  return currentSupport;
34
31
  }
@@ -40,9 +37,10 @@ shaka.media.Capabilities = class {
40
37
  * @return {boolean}
41
38
  */
42
39
  static isChangeTypeSupported() {
43
- return !!window.SourceBuffer &&
40
+ const sourceBuffer = window.ManagedSourceBuffer || window.SourceBuffer;
41
+ return !!sourceBuffer &&
44
42
  // eslint-disable-next-line no-restricted-syntax
45
- !!SourceBuffer.prototype && !!SourceBuffer.prototype.changeType;
43
+ !!sourceBuffer.prototype && !!sourceBuffer.prototype.changeType;
46
44
  }
47
45
  };
48
46
 
@@ -800,7 +800,7 @@ shaka.media.MediaSourceEngine = class {
800
800
  return frame.description ===
801
801
  'com.apple.streaming.transportStreamTimestamp';
802
802
  });
803
- if (metadataTimestamp && metadataTimestamp.data) {
803
+ if (metadataTimestamp) {
804
804
  timestamp = Math.round(metadataTimestamp.data) / 1000;
805
805
  }
806
806
  /** @private {shaka.extern.ID3Metadata} */
@@ -883,7 +883,7 @@ 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
887
  // This won't be known until the first video segment is appended.
888
888
  const offset = await this.textSequenceModeOffset_;
889
889
  this.textEngine_.setTimestampOffset(offset);
@@ -977,7 +977,7 @@ shaka.media.MediaSourceEngine = class {
977
977
  const isBestSourceBufferForTimestamps =
978
978
  contentType == ContentType.VIDEO ||
979
979
  !(ContentType.VIDEO in this.sourceBuffers_);
980
- if (this.sequenceMode_ && isBestSourceBufferForTimestamps) {
980
+ if (isBestSourceBufferForTimestamps) {
981
981
  this.textSequenceModeOffset_.resolve(timestampOffset);
982
982
  }
983
983
  }
@@ -1306,6 +1306,10 @@ shaka.media.MediaSourceEngine = class {
1306
1306
  this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
1307
1307
  }
1308
1308
 
1309
+ if (!this.sequenceMode_) {
1310
+ return;
1311
+ }
1312
+
1309
1313
  // Queue an abort() to help MSE splice together overlapping segments.
1310
1314
  // We set appendWindowEnd when we change periods in DASH content, and the
1311
1315
  // period transition may result in overlap.
@@ -1839,6 +1843,48 @@ shaka.media.MediaSourceEngine = class {
1839
1843
  null);
1840
1844
  }
1841
1845
 
1846
+ /**
1847
+ * Returns the source buffer parameters
1848
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1849
+ * @return {?shaka.media.MediaSourceEngine.SourceBufferParams}
1850
+ * @private
1851
+ */
1852
+ getSourceBufferParams_(contentType) {
1853
+ if (!this.sourceBuffers_[contentType]) {
1854
+ return null;
1855
+ }
1856
+ return {
1857
+ timestampOffset: this.sourceBuffers_[contentType].timestampOffset,
1858
+ appendWindowStart: this.sourceBuffers_[contentType].appendWindowStart,
1859
+ appendWindowEnd: this.sourceBuffers_[contentType].appendWindowEnd,
1860
+ };
1861
+ }
1862
+
1863
+ /**
1864
+ * Restore source buffer parameters
1865
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1866
+ * @param {?shaka.media.MediaSourceEngine.SourceBufferParams} params
1867
+ * @private
1868
+ */
1869
+ restoreSourceBufferParams_(contentType, params) {
1870
+ if (!params) {
1871
+ return;
1872
+ }
1873
+
1874
+ if (!this.sourceBuffers_[contentType]) {
1875
+ shaka.log.warning('Attempted to restore a non-existent source buffer');
1876
+ return;
1877
+ }
1878
+
1879
+ this.sourceBuffers_[contentType].timestampOffset =
1880
+ params.timestampOffset;
1881
+ // `end` needs to be set before `start`
1882
+ this.sourceBuffers_[contentType].appendWindowEnd =
1883
+ params.appendWindowEnd;
1884
+ this.sourceBuffers_[contentType].appendWindowStart =
1885
+ params.appendWindowStart;
1886
+ }
1887
+
1842
1888
  /**
1843
1889
  * Resets the MediaSource and re-adds source buffers due to codec mismatch
1844
1890
  *
@@ -1899,7 +1945,6 @@ shaka.media.MediaSourceEngine = class {
1899
1945
  }
1900
1946
  await Promise.all(cleanup);
1901
1947
  this.transmuxers_ = {};
1902
- this.sourceBuffers_ = {};
1903
1948
 
1904
1949
  const previousDuration = this.mediaSource_.duration;
1905
1950
  this.mediaSourceOpen_ = new shaka.util.PublicPromise();
@@ -1930,6 +1975,7 @@ shaka.media.MediaSourceEngine = class {
1930
1975
  onSourceBufferAdded);
1931
1976
 
1932
1977
  for (const contentType of streamsByType.keys()) {
1978
+ const previousParams = this.getSourceBufferParams_(contentType);
1933
1979
  const stream = streamsByType.get(contentType);
1934
1980
  // eslint-disable-next-line no-await-in-loop
1935
1981
  await this.initSourceBuffer_(contentType, stream, stream.codecs);
@@ -1939,6 +1985,8 @@ shaka.media.MediaSourceEngine = class {
1939
1985
  } else {
1940
1986
  this.queues_[contentType] = [];
1941
1987
  }
1988
+
1989
+ this.restoreSourceBufferParams_(contentType, previousParams);
1942
1990
  }
1943
1991
 
1944
1992
  // Fake a seek to catchup the playhead.
@@ -2226,3 +2274,12 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
2226
2274
  * Callback to use when metadata arrives.
2227
2275
  */
2228
2276
  shaka.media.MediaSourceEngine.PlayerInterface;
2277
+
2278
+ /**
2279
+ * @typedef {{
2280
+ * timestampOffset: number,
2281
+ * appendWindowStart: number,
2282
+ * appendWindowEnd: number
2283
+ * }}
2284
+ */
2285
+ 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;
@@ -417,9 +417,13 @@ shaka.media.SegmentUtils = class {
417
417
  if (initData) {
418
418
  captionParser.init(initData);
419
419
  }
420
- captionParser.parseFrom(data);
421
- for (const stream of captionParser.getStreams()) {
422
- closedCaptions.set(stream, stream);
420
+ try {
421
+ captionParser.parseFrom(data);
422
+ for (const stream of captionParser.getStreams()) {
423
+ closedCaptions.set(stream, stream);
424
+ }
425
+ } catch (e) {
426
+ shaka.log.debug('Error detecting CC streams', e);
423
427
  }
424
428
  captionParser.reset();
425
429
  }
@@ -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);
@@ -64,6 +64,7 @@ shaka.net.HttpXHRPlugin = class {
64
64
  };
65
65
  xhr.onload = (event) => {
66
66
  const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
67
+ // eslint-disable-next-line shaka-rules/buffersource-no-instanceof
67
68
  goog.asserts.assert(xhr.response instanceof ArrayBuffer,
68
69
  'XHR should have a response by now!');
69
70
  const xhrResponse = xhr.response;
package/lib/player.js CHANGED
@@ -58,6 +58,7 @@ goog.require('shaka.util.ManifestParserUtils');
58
58
  goog.require('shaka.util.MediaReadyState');
59
59
  goog.require('shaka.util.MimeUtils');
60
60
  goog.require('shaka.util.Mutex');
61
+ goog.require('shaka.util.NumberUtils');
61
62
  goog.require('shaka.util.ObjectUtils');
62
63
  goog.require('shaka.util.Platform');
63
64
  goog.require('shaka.util.PlayerConfiguration');
@@ -740,7 +741,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
740
741
  this.externalSrcEqualsThumbnailsStreams_ = [];
741
742
 
742
743
  /** @private {number} */
743
- this.completionPercent_ = NaN;
744
+ this.completionPercent_ = -1;
744
745
 
745
746
  /** @private {?shaka.extern.PlayerConfiguration} */
746
747
  this.config_ = this.defaultConfig_();
@@ -1510,7 +1511,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1510
1511
 
1511
1512
  this.externalSrcEqualsThumbnailsStreams_ = [];
1512
1513
 
1513
- this.completionPercent_ = NaN;
1514
+ this.completionPercent_ = -1;
1514
1515
 
1515
1516
  // Make sure that the app knows of the new buffering state.
1516
1517
  this.updateBufferState_();
@@ -6881,41 +6882,46 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6881
6882
  if (!this.video_) {
6882
6883
  return;
6883
6884
  }
6884
- let hasNewCompletionPercent = false;
6885
- const completionRatio = this.video_.currentTime / this.video_.duration;
6886
- if (!isNaN(completionRatio)) {
6887
- const percent = Math.round(100 * completionRatio);
6888
- if (isNaN(this.completionPercent_)) {
6889
- this.completionPercent_ = percent;
6890
- hasNewCompletionPercent = true;
6891
- } else {
6892
- const newCompletionPercent = Math.max(this.completionPercent_, percent);
6893
- if (this.completionPercent_ != newCompletionPercent) {
6894
- this.completionPercent_ = newCompletionPercent;
6895
- hasNewCompletionPercent = true;
6896
- }
6885
+
6886
+ const isQuartile = (quartilePercent, currentPercent) => {
6887
+ const NumberUtils = shaka.util.NumberUtils;
6888
+
6889
+ if ((NumberUtils.isFloatEqual(quartilePercent, currentPercent) ||
6890
+ currentPercent > quartilePercent) &&
6891
+ this.completionPercent_ < quartilePercent) {
6892
+ this.completionPercent_ = quartilePercent;
6893
+ return true;
6897
6894
  }
6895
+ return false;
6896
+ };
6897
+
6898
+ const completionRatio = this.video_.currentTime / this.video_.duration;
6899
+
6900
+ if (isNaN(completionRatio)) {
6901
+ return;
6898
6902
  }
6899
- if (hasNewCompletionPercent) {
6900
- let event;
6901
- if (this.completionPercent_ == 0) {
6902
- event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
6903
- } else if (this.completionPercent_ == 25) {
6904
- event = shaka.Player.makeEvent_(
6905
- shaka.util.FakeEvent.EventName.FirstQuartile);
6906
- } else if (this.completionPercent_ == 50) {
6907
- event = shaka.Player.makeEvent_(
6908
- shaka.util.FakeEvent.EventName.Midpoint);
6909
- } else if (this.completionPercent_ == 75) {
6910
- event = shaka.Player.makeEvent_(
6911
- shaka.util.FakeEvent.EventName.ThirdQuartile);
6912
- } else if (this.completionPercent_ == 100) {
6913
- event = shaka.Player.makeEvent_(
6914
- shaka.util.FakeEvent.EventName.Complete);
6915
- }
6916
- if (event) {
6917
- this.dispatchEvent(event);
6918
- }
6903
+
6904
+ const percent = completionRatio * 100;
6905
+
6906
+ let event;
6907
+ if (isQuartile(0, percent)) {
6908
+ event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
6909
+ } else if (isQuartile(25, percent)) {
6910
+ event = shaka.Player.makeEvent_(
6911
+ shaka.util.FakeEvent.EventName.FirstQuartile);
6912
+ } else if (isQuartile(50, percent)) {
6913
+ event = shaka.Player.makeEvent_(
6914
+ shaka.util.FakeEvent.EventName.Midpoint);
6915
+ } else if (isQuartile(75, percent)) {
6916
+ event = shaka.Player.makeEvent_(
6917
+ shaka.util.FakeEvent.EventName.ThirdQuartile);
6918
+ } else if (isQuartile(100, percent)) {
6919
+ event = shaka.Player.makeEvent_(
6920
+ shaka.util.FakeEvent.EventName.Complete);
6921
+ }
6922
+
6923
+ if (event) {
6924
+ this.dispatchEvent(event);
6919
6925
  }
6920
6926
  }
6921
6927
 
@@ -8123,7 +8129,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
8123
8129
  * @export
8124
8130
  */
8125
8131
  // eslint-disable-next-line no-useless-concat, max-len
8126
- shaka.Player.version = 'v4.11.13' + '-uncompiled'; // x-release-please-version
8132
+ shaka.Player.version = 'v4.11.15' + '-uncompiled'; // x-release-please-version
8127
8133
 
8128
8134
  // Initialize the deprecation system using the version string we just set
8129
8135
  // on the player.