shaka-player 4.2.5 → 4.2.6

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
@@ -2243,12 +2243,12 @@ shaka.hls.HlsParser = class {
2243
2243
  /** @type {shaka.extern.HlsAes128Key|undefined} */
2244
2244
  let hlsAes128Key = undefined;
2245
2245
 
2246
- // We may need to look at the media itself to determine a segment start
2247
- // time.
2246
+ let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2247
+ playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
2248
2248
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2249
2249
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
2250
- const skipTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags,
2251
- 'EXT-X-SKIP');
2250
+ const skipTag = shaka.hls.Utils.getFirstTagWithName(
2251
+ playlist.tags, 'EXT-X-SKIP');
2252
2252
  const skippedSegments =
2253
2253
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
2254
2254
  let position = mediaSequenceNumber + skippedSegments;
@@ -2271,6 +2271,12 @@ shaka.hls.HlsParser = class {
2271
2271
  (i == 0) ? firstStartTime : previousReference.endTime;
2272
2272
  position = mediaSequenceNumber + skippedSegments + i;
2273
2273
 
2274
+ const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
2275
+ playlist.tags, 'EXT-X-DISCONTINUITY');
2276
+ if (discontinuityTag) {
2277
+ discontinuitySequence++;
2278
+ }
2279
+
2274
2280
  // Apply new AES-128 tags as you see them, keeping a running total.
2275
2281
  for (const drmTag of item.tags) {
2276
2282
  if (drmTag.name == 'EXT-X-KEY' &&
@@ -2308,6 +2314,8 @@ shaka.hls.HlsParser = class {
2308
2314
  previousReference = reference;
2309
2315
 
2310
2316
  if (reference) {
2317
+ reference.discontinuitySequence = discontinuitySequence;
2318
+
2311
2319
  if (this.config_.hls.ignoreManifestProgramDateTime &&
2312
2320
  this.minSequenceNumber_ != null &&
2313
2321
  position < this.minSequenceNumber_) {
@@ -856,6 +856,22 @@ shaka.media.MediaSourceEngine = class {
856
856
  ]);
857
857
  }
858
858
 
859
+ /**
860
+ * Adjust timestamp offset to maintain AV sync across discontinuities.
861
+ * Only used in sequence mode.
862
+ *
863
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
864
+ * @param {number} timestampOffset
865
+ * @return {!Promise}
866
+ */
867
+ async resync(contentType, timestampOffset) {
868
+ goog.asserts.assert(this.sequenceMode_,
869
+ 'resyncAudio only used with sequence mode!');
870
+ await this.enqueueOperation_(
871
+ contentType,
872
+ () => this.setTimestampOffset_(contentType, timestampOffset));
873
+ }
874
+
859
875
  /**
860
876
  * @param {string=} reason Valid reasons are 'network' and 'decode'.
861
877
  * @return {!Promise}
@@ -239,6 +239,9 @@ shaka.media.SegmentReference = class {
239
239
 
240
240
  /** @type {?shaka.extern.HlsAes128Key} */
241
241
  this.hlsAes128Key = hlsAes128Key;
242
+
243
+ /** @type {number} */
244
+ this.discontinuitySequence = 0;
242
245
  }
243
246
 
244
247
  /**
@@ -618,6 +618,11 @@ shaka.media.StreamingEngine = class {
618
618
  * within the presentation timeline.
619
619
  */
620
620
  seeked() {
621
+ if (!this.playerInterface_) {
622
+ // Already destroyed.
623
+ return;
624
+ }
625
+
621
626
  const presentationTime = this.playerInterface_.getPresentationTime();
622
627
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
623
628
  const newTimeIsBuffered = (type) => {
@@ -675,7 +680,7 @@ shaka.media.StreamingEngine = class {
675
680
 
676
681
  /**
677
682
  * Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
678
- * cases where a MediaState is performing an update. After this runs, every
683
+ * cases where a MediaState is performing an update. After this runs, the
679
684
  * MediaState will have a pending update.
680
685
  * @param {!shaka.media.StreamingEngine.MediaState_} mediaState
681
686
  * @private
@@ -791,9 +796,9 @@ shaka.media.StreamingEngine = class {
791
796
  for (const type of streamsByType.keys()) {
792
797
  const stream = streamsByType.get(type);
793
798
  if (!this.mediaStates_.has(type)) {
794
- const state = this.createMediaState_(stream);
795
- this.mediaStates_.set(type, state);
796
- this.scheduleUpdate_(state, 0);
799
+ const mediaState = this.createMediaState_(stream);
800
+ this.mediaStates_.set(type, mediaState);
801
+ this.scheduleUpdate_(mediaState, 0);
797
802
  }
798
803
  }
799
804
  }
@@ -905,7 +910,7 @@ shaka.media.StreamingEngine = class {
905
910
  'mediastate.stream should not have segmentIndex yet.');
906
911
  thisStream.closeSegmentIndex();
907
912
  }
908
- if (mediaState.updateTimer == null) {
913
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
909
914
  this.scheduleUpdate_(mediaState, 0);
910
915
  }
911
916
  return;
@@ -1389,7 +1394,7 @@ shaka.media.StreamingEngine = class {
1389
1394
  // If the network slows down, abort the current fetch request and start
1390
1395
  // a new one, and ignore the error message.
1391
1396
  mediaState.performingUpdate = false;
1392
- mediaState.updateTimer = null;
1397
+ this.cancelUpdate_(mediaState);
1393
1398
  this.scheduleUpdate_(mediaState, 0);
1394
1399
  } else if (mediaState.type == ContentType.TEXT &&
1395
1400
  this.config_.ignoreTextStreamFailures) {
@@ -1462,7 +1467,10 @@ shaka.media.StreamingEngine = class {
1462
1467
 
1463
1468
  for (const mediaState of this.mediaStates_.values()) {
1464
1469
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1465
- if (mediaState.hasError) {
1470
+ // Only schedule an update if it has an error, but it's not mid-update
1471
+ // and there is not already an update scheduled.
1472
+ if (mediaState.hasError && !mediaState.performingUpdate &&
1473
+ !mediaState.updateTimer) {
1466
1474
  shaka.log.info(logPrefix, 'Retrying after failure...');
1467
1475
  mediaState.hasError = false;
1468
1476
  this.scheduleUpdate_(mediaState, delaySeconds);
@@ -1645,6 +1653,19 @@ shaka.media.StreamingEngine = class {
1645
1653
  }
1646
1654
  }
1647
1655
 
1656
+ if (this.manifest_.sequenceMode) {
1657
+ // Across discontinuity bounds, we should resync timestamps for
1658
+ // sequence mode playbacks. The next segment appended should
1659
+ // land at its theoretical timestamp from the segment index.
1660
+ const lastDiscontinuitySequence =
1661
+ mediaState.lastSegmentReference ?
1662
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1663
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1664
+ operations.push(this.playerInterface_.mediaSourceEngine.resync(
1665
+ mediaState.type, reference.startTime));
1666
+ }
1667
+ }
1668
+
1648
1669
  await Promise.all(operations);
1649
1670
  }
1650
1671
 
@@ -1678,8 +1699,6 @@ shaka.media.StreamingEngine = class {
1678
1699
 
1679
1700
  await this.evict_(mediaState, presentationTime);
1680
1701
  this.destroyer_.ensureNotDestroyed();
1681
- shaka.log.v1(logPrefix, 'appending media segment at',
1682
- (reference.syncTime == null ? 'unknown' : reference.syncTime));
1683
1702
 
1684
1703
  // 'seeked' or 'adaptation' triggered logic applies only to this
1685
1704
  // appendBuffer() call.
@@ -1956,7 +1975,11 @@ shaka.media.StreamingEngine = class {
1956
1975
  shaka.log.debug(logPrefix, 'cleared buffer');
1957
1976
  mediaState.clearingBuffer = false;
1958
1977
  mediaState.endOfStream = false;
1959
- this.scheduleUpdate_(mediaState, 0);
1978
+ // Since the clear operation was async, check to make sure we're not doing
1979
+ // another update and we don't have one scheduled yet.
1980
+ if (!mediaState.performingUpdate && !mediaState.updateTimer) {
1981
+ this.scheduleUpdate_(mediaState, 0);
1982
+ }
1960
1983
  }
1961
1984
 
1962
1985
 
package/lib/player.js CHANGED
@@ -4140,6 +4140,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4140
4140
  return [];
4141
4141
  }
4142
4142
  const chapters = [];
4143
+ const uniqueChapters = new Set();
4143
4144
  for (const chaptersTrack of chaptersTracksWithLanguage) {
4144
4145
  if (chaptersTrack && chaptersTrack.cues) {
4145
4146
  for (const cue of chaptersTrack.cues) {
@@ -4154,7 +4155,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4154
4155
  startTime: cue.startTime,
4155
4156
  endTime: cue.endTime,
4156
4157
  };
4157
- chapters.push(chapter);
4158
+ if (!uniqueChapters.has(id)) {
4159
+ chapters.push(chapter);
4160
+ uniqueChapters.add(id);
4161
+ }
4158
4162
  }
4159
4163
  }
4160
4164
  }
@@ -6595,7 +6599,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6595
6599
  * @define {string} A version number taken from git at compile time.
6596
6600
  * @export
6597
6601
  */
6598
- shaka.Player.version = 'v4.2.5-uncompiled';
6602
+ shaka.Player.version = 'v4.2.6-uncompiled';
6599
6603
 
6600
6604
  // Initialize the deprecation system using the version string we just set
6601
6605
  // 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.2.5",
4
+ "version": "4.2.6",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -898,7 +898,7 @@ describe('Player', () => {
898
898
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
899
899
  video.currentTime = 50;
900
900
  await waitUntilBuffered(30);
901
- expect(getBufferedBehind()).toBeLessThan(30);
901
+ expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
902
902
 
903
903
  player.configure('streaming.bufferBehind', 10);
904
904
  // We only evict content when we append a segment, so increase the
@@ -925,7 +925,7 @@ describe('Player', () => {
925
925
  }
926
926
 
927
927
  async function waitUntilBuffered(amount) {
928
- for (let i = 0; i < 25; i++) {
928
+ for (let i = 0; i < 50; i++) {
929
929
  // We buffer from an internal segment, so this shouldn't take long to
930
930
  // buffer.
931
931
  await Util.delay(0.1); // eslint-disable-line no-await-in-loop
@@ -933,7 +933,14 @@ describe('Player', () => {
933
933
  return;
934
934
  }
935
935
  }
936
- throw new Error('Timeout waiting to buffer');
936
+
937
+ const ranges =
938
+ shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
939
+ const currentTime = video.currentTime;
940
+ const target = currentTime + amount;
941
+
942
+ throw new Error('Timeout waiting to buffer! ' +
943
+ JSON.stringify({ranges, currentTime, target}));
937
944
  }
938
945
  }); // describe('buffering')
939
946
 
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
  };
@@ -405,7 +405,7 @@ const sintelAudioSegment = {
405
405
  mdhdOffset: 0x1b6,
406
406
  segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
407
407
  tfdtOffset: 0x3c,
408
- segmentDuration: 10.005,
408
+ segmentDuration: 10,
409
409
  mimeType: 'audio/mp4',
410
410
  codecs: 'mp4a.40.2',
411
411
  };
@@ -431,7 +431,7 @@ const sintelEncryptedAudio = {
431
431
  mdhdOffset: 0x1b6,
432
432
  segmentUri: '/base/test/test/assets/encrypted-sintel-audio-segment.mp4',
433
433
  tfdtOffset: 0x3c,
434
- segmentDuration: 10.005,
434
+ segmentDuration: 10,
435
435
  mimeType: 'audio/mp4',
436
436
  codecs: 'mp4a.40.2',
437
437
  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_);