shaka-player 4.3.1 → 4.3.2

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.
@@ -19,8 +19,8 @@ goog.require('shaka.util.IReleasable');
19
19
 
20
20
 
21
21
  /**
22
- * @event shaka.ads.AdManager.ADS_LOADED
23
- * @description Fired when an ad has started playing.
22
+ * @event shaka.ads.AdManager.AdsLoadedEvent
23
+ * @description Fired when a sequence of ads has been loaded.
24
24
  * @property {string} type
25
25
  * 'ads-loaded'
26
26
  * @property {number} loadTime
@@ -2592,12 +2592,12 @@ shaka.hls.HlsParser = class {
2592
2592
  /** @type {shaka.extern.HlsAes128Key|undefined} */
2593
2593
  let hlsAes128Key = undefined;
2594
2594
 
2595
- // We may need to look at the media itself to determine a segment start
2596
- // time.
2595
+ let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2596
+ playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
2597
2597
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2598
2598
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
2599
- const skipTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags,
2600
- 'EXT-X-SKIP');
2599
+ const skipTag = shaka.hls.Utils.getFirstTagWithName(
2600
+ playlist.tags, 'EXT-X-SKIP');
2601
2601
  const skippedSegments =
2602
2602
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
2603
2603
  let position = mediaSequenceNumber + skippedSegments;
@@ -2620,6 +2620,12 @@ shaka.hls.HlsParser = class {
2620
2620
  (i == 0) ? firstStartTime : previousReference.endTime;
2621
2621
  position = mediaSequenceNumber + skippedSegments + i;
2622
2622
 
2623
+ const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
2624
+ playlist.tags, 'EXT-X-DISCONTINUITY');
2625
+ if (discontinuityTag) {
2626
+ discontinuitySequence++;
2627
+ }
2628
+
2623
2629
  // Apply new AES-128 tags as you see them, keeping a running total.
2624
2630
  for (const drmTag of item.tags) {
2625
2631
  if (drmTag.name == 'EXT-X-KEY' &&
@@ -2657,6 +2663,8 @@ shaka.hls.HlsParser = class {
2657
2663
  previousReference = reference;
2658
2664
 
2659
2665
  if (reference) {
2666
+ reference.discontinuitySequence = discontinuitySequence;
2667
+
2660
2668
  if (this.config_.hls.ignoreManifestProgramDateTime &&
2661
2669
  this.minSequenceNumber_ != null &&
2662
2670
  position < this.minSequenceNumber_) {
@@ -924,6 +924,22 @@ shaka.media.MediaSourceEngine = class {
924
924
  ]);
925
925
  }
926
926
 
927
+ /**
928
+ * Adjust timestamp offset to maintain AV sync across discontinuities.
929
+ * Only used in sequence mode.
930
+ *
931
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
932
+ * @param {number} timestampOffset
933
+ * @return {!Promise}
934
+ */
935
+ async resync(contentType, timestampOffset) {
936
+ goog.asserts.assert(this.sequenceMode_,
937
+ 'resyncAudio only used with sequence mode!');
938
+ await this.enqueueOperation_(
939
+ contentType,
940
+ () => this.setTimestampOffset_(contentType, timestampOffset));
941
+ }
942
+
927
943
  /**
928
944
  * @param {string=} reason Valid reasons are 'network' and 'decode'.
929
945
  * @return {!Promise}
@@ -246,6 +246,9 @@ shaka.media.SegmentReference = class {
246
246
 
247
247
  /** @type {?shaka.media.SegmentReference.ThumbnailSprite} */
248
248
  this.thumbnailSprite = null;
249
+
250
+ /** @type {number} */
251
+ this.discontinuitySequence = 0;
249
252
  }
250
253
 
251
254
  /**
@@ -622,6 +622,11 @@ shaka.media.StreamingEngine = class {
622
622
  * within the presentation timeline.
623
623
  */
624
624
  seeked() {
625
+ if (!this.playerInterface_) {
626
+ // Already destroyed.
627
+ return;
628
+ }
629
+
625
630
  const presentationTime = this.playerInterface_.getPresentationTime();
626
631
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
627
632
  const newTimeIsBuffered = (type) => {
@@ -679,7 +684,7 @@ shaka.media.StreamingEngine = class {
679
684
 
680
685
  /**
681
686
  * Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
682
- * cases where a MediaState is performing an update. After this runs, every
687
+ * cases where a MediaState is performing an update. After this runs, the
683
688
  * MediaState will have a pending update.
684
689
  * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
685
690
  * @private
@@ -795,9 +800,9 @@ shaka.media.StreamingEngine = class {
795
800
  for (const type of streamsByType.keys()) {
796
801
  const stream = streamsByType.get(type);
797
802
  if (!this.mediaStates_.has(type)) {
798
- const state = this.createMediaState_(stream);
799
- this.mediaStates_.set(type, state);
800
- this.scheduleUpdate_(state, 0);
803
+ const mediaState = this.createMediaState_(stream);
804
+ this.mediaStates_.set(type, mediaState);
805
+ this.scheduleUpdate_(mediaState, 0);
801
806
  }
802
807
  }
803
808
  }
@@ -909,7 +914,7 @@ shaka.media.StreamingEngine = class {
909
914
  'mediastate.stream should not have segmentIndex yet.');
910
915
  thisStream.closeSegmentIndex();
911
916
  }
912
- if (mediaState.updateTimer == null) {
917
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
913
918
  this.scheduleUpdate_(mediaState, 0);
914
919
  }
915
920
  return;
@@ -1394,7 +1399,7 @@ shaka.media.StreamingEngine = class {
1394
1399
  // If the network slows down, abort the current fetch request and start
1395
1400
  // a new one, and ignore the error message.
1396
1401
  mediaState.performingUpdate = false;
1397
- mediaState.updateTimer = null;
1402
+ this.cancelUpdate_(mediaState);
1398
1403
  this.scheduleUpdate_(mediaState, 0);
1399
1404
  } else if (mediaState.type == ContentType.TEXT &&
1400
1405
  this.config_.ignoreTextStreamFailures) {
@@ -1467,7 +1472,10 @@ shaka.media.StreamingEngine = class {
1467
1472
 
1468
1473
  for (const mediaState of this.mediaStates_.values()) {
1469
1474
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1470
- if (mediaState.hasError) {
1475
+ // Only schedule an update if it has an error, but it's not mid-update
1476
+ // and there is not already an update scheduled.
1477
+ if (mediaState.hasError && !mediaState.performingUpdate &&
1478
+ !mediaState.updateTimer) {
1471
1479
  shaka.log.info(logPrefix, 'Retrying after failure...');
1472
1480
  mediaState.hasError = false;
1473
1481
  this.scheduleUpdate_(mediaState, delaySeconds);
@@ -1661,6 +1669,19 @@ shaka.media.StreamingEngine = class {
1661
1669
  }
1662
1670
  }
1663
1671
 
1672
+ if (this.manifest_.sequenceMode) {
1673
+ // Across discontinuity bounds, we should resync timestamps for
1674
+ // sequence mode playbacks. The next segment appended should
1675
+ // land at its theoretical timestamp from the segment index.
1676
+ const lastDiscontinuitySequence =
1677
+ mediaState.lastSegmentReference ?
1678
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1679
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1680
+ operations.push(this.playerInterface_.mediaSourceEngine.resync(
1681
+ mediaState.type, reference.startTime));
1682
+ }
1683
+ }
1684
+
1664
1685
  await Promise.all(operations);
1665
1686
  }
1666
1687
 
@@ -1715,8 +1736,6 @@ shaka.media.StreamingEngine = class {
1715
1736
 
1716
1737
  await this.evict_(mediaState, presentationTime);
1717
1738
  this.destroyer_.ensureNotDestroyed();
1718
- shaka.log.v1(logPrefix, 'appending media segment at',
1719
- (reference.syncTime == null ? 'unknown' : reference.syncTime));
1720
1739
 
1721
1740
  // 'seeked' or 'adaptation' triggered logic applies only to this
1722
1741
  // appendBuffer() call.
@@ -2079,7 +2098,11 @@ shaka.media.StreamingEngine = class {
2079
2098
  shaka.log.debug(logPrefix, 'cleared buffer');
2080
2099
  mediaState.clearingBuffer = false;
2081
2100
  mediaState.endOfStream = false;
2082
- this.scheduleUpdate_(mediaState, 0);
2101
+ // Since the clear operation was async, check to make sure we're not doing
2102
+ // another update and we don't have one scheduled yet.
2103
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
2104
+ this.scheduleUpdate_(mediaState, 0);
2105
+ }
2083
2106
  }
2084
2107
 
2085
2108
 
package/lib/player.js CHANGED
@@ -4330,6 +4330,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4330
4330
  return [];
4331
4331
  }
4332
4332
  const chapters = [];
4333
+ const uniqueChapters = new Set();
4333
4334
  for (const chaptersTrack of chaptersTracksWithLanguage) {
4334
4335
  if (chaptersTrack && chaptersTrack.cues) {
4335
4336
  for (const cue of chaptersTrack.cues) {
@@ -4344,7 +4345,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4344
4345
  startTime: cue.startTime,
4345
4346
  endTime: cue.endTime,
4346
4347
  };
4347
- chapters.push(chapter);
4348
+ if (!uniqueChapters.has(id)) {
4349
+ chapters.push(chapter);
4350
+ uniqueChapters.add(id);
4351
+ }
4348
4352
  }
4349
4353
  }
4350
4354
  }
@@ -6974,7 +6978,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6974
6978
  * @define {string} A version number taken from git at compile time.
6975
6979
  * @export
6976
6980
  */
6977
- shaka.Player.version = 'v4.3.1-uncompiled';
6981
+ shaka.Player.version = 'v4.3.2-uncompiled';
6978
6982
 
6979
6983
  // Initialize the deprecation system using the version string we just set
6980
6984
  // on the player.
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.3.1",
4
+ "version": "4.3.2",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -970,7 +970,7 @@ describe('Player', () => {
970
970
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
971
971
  video.currentTime = 50;
972
972
  await waitUntilBuffered(30);
973
- expect(getBufferedBehind()).toBeLessThan(30);
973
+ expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
974
974
 
975
975
  player.configure('streaming.bufferBehind', 10);
976
976
  // We only evict content when we append a segment, so increase the
@@ -997,7 +997,7 @@ describe('Player', () => {
997
997
  }
998
998
 
999
999
  async function waitUntilBuffered(amount) {
1000
- for (let i = 0; i < 25; i++) {
1000
+ for (let i = 0; i < 50; i++) {
1001
1001
  // We buffer from an internal segment, so this shouldn't take long to
1002
1002
  // buffer.
1003
1003
  await Util.delay(0.1); // eslint-disable-line no-await-in-loop
@@ -1005,7 +1005,14 @@ describe('Player', () => {
1005
1005
  return;
1006
1006
  }
1007
1007
  }
1008
- throw new Error('Timeout waiting to buffer');
1008
+
1009
+ const ranges =
1010
+ shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
1011
+ const currentTime = video.currentTime;
1012
+ const target = currentTime + amount;
1013
+
1014
+ throw new Error('Timeout waiting to buffer! ' +
1015
+ JSON.stringify({ranges, currentTime, target}));
1009
1016
  }
1010
1017
  }); // describe('buffering')
1011
1018
 
package/test/test/boot.js CHANGED
@@ -66,22 +66,40 @@ function failTestsOnNamespacedElementOrAttributeNames() {
66
66
  }
67
67
 
68
68
  /**
69
- * Listen for unhandled Promise rejections (which may occur after a test) and
70
- * convert them into test failures.
69
+ * Fail the current test on a given error, constructed with a given header and
70
+ * the full stack trace of the error.
71
+ *
72
+ * @param {string} messageHeader
73
+ * @param {!Error} error
71
74
  */
72
- function failTestsOnUnhandledRejections() {
75
+ function failOnError(messageHeader, error) {
76
+ let message = `${messageHeader}: ${error}`;
77
+ // Shaka errors have the stack trace in their toString() already, so don't
78
+ // add it again. For native errors, we need to see where it came from.
79
+ if (error && error.stack && !(error instanceof shaka.util.Error)) {
80
+ message += '\n' + error.stack;
81
+ }
82
+ fail(message);
83
+ }
84
+
85
+ /**
86
+ * Listen for unhandled errors and Promise rejections (which may occur after a
87
+ * test) and convert them into test failures.
88
+ */
89
+ function failTestsOnUnhandledErrors() {
73
90
  // https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection
74
91
  window.addEventListener('unhandledrejection', (event) => {
75
92
  /** @type {?} */
76
93
  const error = event.reason;
77
- let message = 'Unhandled rejection in Promise: ' + error;
94
+ failOnError('Unhandled rejection in Promise', error);
95
+ });
78
96
 
79
- // Shaka errors have the stack trace in their toString() already, so don't
80
- // add it again. For native errors, we need to see where it came from.
81
- if (error && error.stack && !(error instanceof shaka.util.Error)) {
82
- message += '\n' + error.stack;
83
- }
84
- fail(message);
97
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
98
+ // https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
99
+ window.addEventListener('error', (event) => {
100
+ /** @type {?} */
101
+ const error = event['error'];
102
+ failOnError('Unhandled error', error);
85
103
  });
86
104
  }
87
105
 
@@ -387,7 +405,7 @@ function configureJasmineEnvironment() {
387
405
  function setupTestEnvironment() {
388
406
  failTestsOnFailedAssertions();
389
407
  failTestsOnNamespacedElementOrAttributeNames();
390
- failTestsOnUnhandledRejections();
408
+ failTestsOnUnhandledErrors();
391
409
  disableScrollbars();
392
410
  workAroundLegacyEdgePromiseIssues();
393
411
 
@@ -73,7 +73,7 @@ shaka.test.ManifestParser = class {
73
73
  const appendWindowStart = /** @type {?} */(jasmine.any(Number));
74
74
  const appendWindowEnd = /** @type {?} */(jasmine.any(Number));
75
75
 
76
- return new shaka.media.SegmentReference(
76
+ const ref = new shaka.media.SegmentReference(
77
77
  start, end, getUris, startByte, endByte,
78
78
  initSegmentReference,
79
79
  timestampOffset,
@@ -82,7 +82,8 @@ shaka.test.ManifestParser = class {
82
82
  partialReferences,
83
83
  tilesLayout,
84
84
  /* tileDuration= */ undefined,
85
- syncTime,
86
- );
85
+ syncTime);
86
+ ref.discontinuitySequence = /** @type {?} */(jasmine.any(Number));
87
+ return ref;
87
88
  }
88
89
  };
@@ -413,7 +413,7 @@ const sintelAudioSegment = {
413
413
  mdhdOffset: 0x1b6,
414
414
  segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
415
415
  tfdtOffset: 0x3c,
416
- segmentDuration: 10.005,
416
+ segmentDuration: 10,
417
417
  mimeType: 'audio/mp4',
418
418
  codecs: 'mp4a.40.2',
419
419
  };
@@ -439,7 +439,7 @@ const sintelEncryptedAudio = {
439
439
  mdhdOffset: 0x1b6,
440
440
  segmentUri: '/base/test/test/assets/encrypted-sintel-audio-segment.mp4',
441
441
  tfdtOffset: 0x3c,
442
- segmentDuration: 10.005,
442
+ segmentDuration: 10,
443
443
  mimeType: 'audio/mp4',
444
444
  codecs: 'mp4a.40.2',
445
445
  initData:
package/ui/controls.js CHANGED
@@ -599,7 +599,11 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
599
599
  if (this.config_.forceLandscapeOnFullscreen && screen.orientation) {
600
600
  // Locking to 'landscape' should let it be either
601
601
  // 'landscape-primary' or 'landscape-secondary' as appropriate.
602
- await screen.orientation.lock('landscape');
602
+ // We ignore errors from this specific call, since it creates noise
603
+ // on desktop otherwise.
604
+ try {
605
+ await screen.orientation.lock('landscape');
606
+ } catch (error) {}
603
607
  }
604
608
  } else {
605
609
  const video = /** @type {HTMLVideoElement} */(this.localVideo_);