@amazon/vinyl 3.2.1 → 3.2.3-next.20261001143710

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 (109) hide show
  1. package/README.md +2 -2
  2. package/dist/index.cjs +98 -43
  3. package/dist/index.cjs.map +3 -3
  4. package/dist/index.js +126 -68
  5. package/dist/index.js.map +3 -3
  6. package/dist/test/benchmark.es5.js +7 -6
  7. package/dist/test/benchmark.js +35 -30
  8. package/dist/test/benchmark.js.map +3 -3
  9. package/dist/test/integ.es5.js +329 -312
  10. package/dist/test/integ.js +519 -187
  11. package/dist/test/integ.js.map +4 -4
  12. package/dist/types/current/ad/AdControllerImpl.d.ts.map +1 -1
  13. package/dist/types/current/ad/AdControllerImpl.js +15 -1
  14. package/dist/types/current/ad/discoverHlsInterstitials.js +13 -1
  15. package/dist/types/current/drm/DrmControllerImpl.d.ts +6 -0
  16. package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
  17. package/dist/types/current/drm/DrmControllerImpl.js +11 -1
  18. package/dist/types/current/drm/commonEme/MsCommonEme.d.ts.map +1 -1
  19. package/dist/types/current/drm/commonEme/MsCommonEme.js +2 -2
  20. package/dist/types/current/drm/commonEme/StandardCommonEme.d.ts.map +1 -1
  21. package/dist/types/current/drm/commonEme/StandardCommonEme.js +3 -2
  22. package/dist/types/current/drm/commonEme/WebKitCommonEme.d.ts.map +1 -1
  23. package/dist/types/current/drm/commonEme/WebKitCommonEme.js +2 -2
  24. package/dist/types/current/drm/dom/MSMediaKeys.d.ts +1 -1
  25. package/dist/types/current/drm/dom/MSMediaKeys.d.ts.map +1 -1
  26. package/dist/types/current/drm/dom/WebKitMediaKeys.d.ts +1 -1
  27. package/dist/types/current/drm/dom/WebKitMediaKeys.d.ts.map +1 -1
  28. package/dist/types/current/drm/util/extractContentId.js +2 -2
  29. package/dist/types/current/playback/PlaybackControllerImpl.d.ts +9 -3
  30. package/dist/types/current/playback/PlaybackControllerImpl.d.ts.map +1 -1
  31. package/dist/types/current/playback/PlaybackControllerImpl.js +38 -33
  32. package/dist/types/current/streaming/MediaQualityMetadata.d.ts +3 -1
  33. package/dist/types/current/streaming/MediaQualityMetadata.d.ts.map +1 -1
  34. package/dist/types/current/streaming/SegmentControllerImpl.d.ts +1 -1
  35. package/dist/types/current/streaming/buffering/BufferingController.d.ts +1 -1
  36. package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
  37. package/dist/types/current/streaming/buffering/BufferingController.js +14 -5
  38. package/dist/types/current/streaming/buffering/MediaSourceController.d.ts.map +1 -1
  39. package/dist/types/current/streaming/buffering/MediaSourceController.js +4 -0
  40. package/dist/types/current/text/discoverDashTextTracks.d.ts +2 -2
  41. package/dist/types/current/text/discoverDashTextTracks.js +4 -3
  42. package/dist/types/current/text/parseWebVtt.d.ts.map +1 -1
  43. package/dist/types/current/text/parseWebVtt.js +3 -2
  44. package/dist/types/current/track/TrackController.d.ts.map +1 -1
  45. package/dist/types/current/track/TrackController.js +7 -0
  46. package/dist/types/current/track/hls/HlsManifestData.d.ts +14 -1
  47. package/dist/types/current/track/hls/HlsManifestData.d.ts.map +1 -1
  48. package/dist/types/current/track/hls/buildHlsMediaTimeline.d.ts.map +1 -1
  49. package/dist/types/current/track/hls/buildHlsMediaTimeline.js +10 -2
  50. package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  51. package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts.map +1 -1
  52. package/dist/types/current/track/hls/createUrlHlsManifestProvider.js +8 -3
  53. package/dist/types/current/track/mse/MseTrack.d.ts +0 -1
  54. package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
  55. package/dist/types/current/track/mse/MseTrack.js +5 -6
  56. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  57. package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +6 -0
  58. package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +9 -3
  59. package/dist/types/ts3.7/streaming/MediaQualityMetadata.d.ts +3 -1
  60. package/dist/types/ts3.7/streaming/SegmentControllerImpl.d.ts +1 -1
  61. package/dist/types/ts3.7/streaming/buffering/BufferingController.d.ts +1 -1
  62. package/dist/types/ts3.7/text/discoverDashTextTracks.d.ts +2 -2
  63. package/dist/types/ts3.7/track/hls/HlsManifestData.d.ts +14 -1
  64. package/dist/types/ts3.7/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  65. package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -1
  66. package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +6 -0
  67. package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +9 -3
  68. package/dist/types/ts4.1/streaming/MediaQualityMetadata.d.ts +3 -1
  69. package/dist/types/ts4.1/streaming/SegmentControllerImpl.d.ts +1 -1
  70. package/dist/types/ts4.1/streaming/buffering/BufferingController.d.ts +1 -1
  71. package/dist/types/ts4.1/text/discoverDashTextTracks.d.ts +2 -2
  72. package/dist/types/ts4.1/track/hls/HlsManifestData.d.ts +14 -1
  73. package/dist/types/ts4.1/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  74. package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -1
  75. package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +6 -0
  76. package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +9 -3
  77. package/dist/types/ts4.7/streaming/MediaQualityMetadata.d.ts +3 -1
  78. package/dist/types/ts4.7/streaming/SegmentControllerImpl.d.ts +1 -1
  79. package/dist/types/ts4.7/streaming/buffering/BufferingController.d.ts +1 -1
  80. package/dist/types/ts4.7/text/discoverDashTextTracks.d.ts +2 -2
  81. package/dist/types/ts4.7/track/hls/HlsManifestData.d.ts +14 -1
  82. package/dist/types/ts4.7/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  83. package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -1
  84. package/dist/unit/types/current/ad/AdControllerImpl.test.js +66 -0
  85. package/dist/unit/types/current/ad/discoverHlsInterstitials.test.js +29 -2
  86. package/dist/unit/types/current/drm/DrmControllerImpl.test.js +19 -1
  87. package/dist/unit/types/current/drm/commonEme/StandardCommonEme.test.js +12 -4
  88. package/dist/unit/types/current/drm/util/extractContentId.test.js +7 -0
  89. package/dist/unit/types/current/index.d.ts +1 -0
  90. package/dist/unit/types/current/index.d.ts.map +1 -1
  91. package/dist/unit/types/current/index.js +1 -0
  92. package/dist/unit/types/current/playback/PlaybackControllerImpl.test.js +15 -0
  93. package/dist/unit/types/current/streaming/MediaQualityMetadata.test.js +8 -0
  94. package/dist/unit/types/current/streaming/MseTrack.test.js +15 -0
  95. package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +26 -0
  96. package/dist/unit/types/current/streaming/buffering/MediaSourceController.test.js +29 -0
  97. package/dist/unit/types/current/text/TextTrack.test.d.ts +2 -0
  98. package/dist/unit/types/current/text/TextTrack.test.d.ts.map +1 -0
  99. package/dist/unit/types/current/text/TextTrack.test.js +12 -0
  100. package/dist/unit/types/current/text/discoverDashTextTracks.test.js +17 -0
  101. package/dist/unit/types/current/text/parseWebVtt.test.js +26 -0
  102. package/dist/unit/types/current/track/TrackController.test.js +15 -60
  103. package/dist/unit/types/current/track/hls/buildHlsMediaTimeline.test.js +22 -0
  104. package/dist/unit/types/current/track/hls/createUrlHlsManifestProvider.test.js +23 -0
  105. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  106. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  107. package/docs/CUSTOMIZATION_GUIDE.md +310 -0
  108. package/package.json +10 -10
  109. package/docs/DASH_MANIFEST_TRANSFORM_GUIDE.md +0 -236
@@ -5832,7 +5832,8 @@
5832
5832
  return true;
5833
5833
  }
5834
5834
  /**
5835
- * Executes a provided function once per each key/value pair in the Map, in insertion order.
5835
+ * Executes a provided function once per each key/value pair in the Map,
5836
+ * ordered from least recently used to most.
5836
5837
  *
5837
5838
  * @param callback A callback to invoke for every element in the cache.
5838
5839
  * @param thisArg Value to use as this when executing callback
@@ -5877,8 +5878,8 @@
5877
5878
  let value = void 0;
5878
5879
  const memoized = () => {
5879
5880
  if (!cached) {
5880
- cached = true;
5881
5881
  value = inner();
5882
+ cached = true;
5882
5883
  }
5883
5884
  return value;
5884
5885
  };
@@ -5907,9 +5908,9 @@
5907
5908
  const memoized = (...args) => {
5908
5909
  const key = keyProvider(...args);
5909
5910
  if (!hasValue || cachedKey !== key) {
5910
- hasValue = true;
5911
- cachedKey = key;
5912
5911
  cachedValue = inner(...args);
5912
+ cachedKey = key;
5913
+ hasValue = true;
5913
5914
  }
5914
5915
  return cachedValue;
5915
5916
  };
@@ -5972,7 +5973,8 @@
5972
5973
  charCode === 13 || // '\r' (carriage return)
5973
5974
  charCode === 10;
5974
5975
  }
5975
- var encodedEntitiesRegex = /&(#(\d+)|\w+);/gi;
5976
+ var encodedEntitiesRegex = /&(?:#(?:x([\da-f]+)|(\d+))|(\w+));/gi;
5977
+ var MAX_CODE_POINT = 1114111;
5976
5978
  var predefinedDecodeEntities = /* @__PURE__ */ new Map([
5977
5979
  ["apos", "'"],
5978
5980
  ["amp", "&"],
@@ -5981,20 +5983,22 @@
5981
5983
  ["quot", '"']
5982
5984
  ]);
5983
5985
  function decodeEntities(str, namedEntities = predefinedDecodeEntities) {
5984
- if (encodedEntitiesRegex.test(str)) {
5985
- return str.replace(
5986
- encodedEntitiesRegex,
5987
- (substring, entityName, codePoint) => {
5988
- if (codePoint !== void 0)
5989
- return String.fromCharCode(parseInt(codePoint));
5990
- const entityValue = namedEntities.get(entityName.toLowerCase());
5991
- if (!entityValue) return substring;
5992
- return entityValue;
5986
+ return str.replace(
5987
+ encodedEntitiesRegex,
5988
+ (substring, hex, decimal, entityName) => {
5989
+ var _a3;
5990
+ if (hex == null && decimal == null) {
5991
+ return (_a3 = namedEntities.get(entityName.toLowerCase())) != null ? _a3 : substring;
5993
5992
  }
5994
- );
5995
- } else {
5996
- return str;
5997
- }
5993
+ const codePoint = hex != null ? parseInt(hex, 16) : parseInt(decimal, 10);
5994
+ if (codePoint > MAX_CODE_POINT || isSurrogate(codePoint))
5995
+ return substring;
5996
+ return String.fromCodePoint(codePoint);
5997
+ }
5998
+ );
5999
+ }
6000
+ function isSurrogate(codePoint) {
6001
+ return codePoint >= 55296 && codePoint <= 57343;
5998
6002
  }
5999
6003
  var standardEntityValueToNames = flipMap(predefinedDecodeEntities);
6000
6004
  function eqStr(a, b, ignoreCase = false) {
@@ -8056,7 +8060,11 @@ ${this.lastInitStack}` : "";
8056
8060
  __publicField(this, "onDownloadSpeedChange", () => {
8057
8061
  var _a3;
8058
8062
  const downlink = (_a3 = this.deps.networkInformation) == null ? void 0 : _a3.downlink;
8059
- const estimatedSpeed = downlink ? downlink * 1024 * 1024 : this.options.unknownDownlinkBandwidthEstimate;
8063
+ const estimatedSpeed = downlink ? (
8064
+ // Megabits are decimal, matching the measured `bytes * 8 / seconds`
8065
+ // this estimate is averaged with.
8066
+ downlink * 1e6
8067
+ ) : this.options.unknownDownlinkBandwidthEstimate;
8060
8068
  updateStatMetrics(
8061
8069
  this._metrics.estimatedDownlinkBandwidth,
8062
8070
  estimatedSpeed,
@@ -8546,38 +8554,41 @@ ${this.lastInitStack}` : "";
8546
8554
  this.dispatch("request", { requestInfo });
8547
8555
  let lastResult = null;
8548
8556
  let currentTry = 0;
8549
- do {
8550
- ++currentTry;
8551
- const attemptInfo = {
8552
- currentTry,
8553
- timestamp: Date.now()
8554
- };
8555
- this.dispatch("requestAttemptStart", {
8556
- requestInfo,
8557
- attemptInfo
8558
- });
8559
- lastResult = await this.doRequest(
8560
- requestInfo,
8561
- requestOptions,
8562
- attemptInfo,
8563
- timeoutController
8564
- ).catch((reason) => {
8565
- var _a4;
8566
- const completeInfo = {
8567
- ok: false,
8568
- type: ((_a4 = requestOptions.abort) == null ? void 0 : _a4.aborted()) ? "abort" /* ABORT */ : "internal" /* INTERNAL */,
8569
- requestInfo,
8570
- reason,
8571
- willRetry: false,
8572
- timestamp: Date.now(),
8573
- retryAfter: null
8557
+ try {
8558
+ do {
8559
+ ++currentTry;
8560
+ const attemptInfo = {
8561
+ currentTry,
8562
+ timestamp: Date.now()
8574
8563
  };
8575
- return { info: completeInfo, response: null };
8576
- });
8564
+ this.dispatch("requestAttemptStart", {
8565
+ requestInfo,
8566
+ attemptInfo
8567
+ });
8568
+ lastResult = await this.doRequest(
8569
+ requestInfo,
8570
+ requestOptions,
8571
+ attemptInfo,
8572
+ timeoutController
8573
+ ).catch((reason) => {
8574
+ var _a4;
8575
+ const completeInfo = {
8576
+ ok: false,
8577
+ type: ((_a4 = requestOptions.abort) == null ? void 0 : _a4.aborted()) ? "abort" /* ABORT */ : "internal" /* INTERNAL */,
8578
+ requestInfo,
8579
+ reason,
8580
+ willRetry: false,
8581
+ timestamp: Date.now(),
8582
+ retryAfter: null
8583
+ };
8584
+ return { info: completeInfo, response: null };
8585
+ });
8586
+ this.reportMetricsEntry(lastResult.info);
8587
+ this.dispatch("requestAttemptComplete", lastResult.info);
8588
+ } while (!lastResult.info.ok && lastResult.info.willRetry);
8589
+ } finally {
8577
8590
  timeoutController.dispose();
8578
- this.reportMetricsEntry(lastResult.info);
8579
- this.dispatch("requestAttemptComplete", lastResult.info);
8580
- } while (!lastResult.info.ok && lastResult.info.willRetry);
8591
+ }
8581
8592
  this.dispatch("requestComplete", lastResult.info);
8582
8593
  if (lastResult.info.ok) return lastResult.response;
8583
8594
  else throw new RequestError(lastResult.response, lastResult.info);
@@ -8942,6 +8953,13 @@ ${this.lastInitStack}` : "";
8942
8953
  function bufferToByteArray(buffer) {
8943
8954
  return buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
8944
8955
  }
8956
+ function bufferToArrayBuffer(buffer) {
8957
+ if (buffer instanceof ArrayBuffer) return buffer;
8958
+ return buffer.buffer.slice(
8959
+ buffer.byteOffset,
8960
+ buffer.byteOffset + buffer.byteLength
8961
+ );
8962
+ }
8945
8963
  function buffersEqual(bufferA, bufferB) {
8946
8964
  if (bufferA === bufferB) return true;
8947
8965
  if (!bufferA && !bufferB) return true;
@@ -10268,7 +10286,11 @@ ${this.lastInitStack}` : "";
10268
10286
  logVerbose(this, "nextAd");
10269
10287
  try {
10270
10288
  const ad = await adBreakState2.nextAd();
10271
- if (this.disposed || adBreakState2.completedReason) return;
10289
+ if (this.disposed) return;
10290
+ if (adBreakState2.completedReason) {
10291
+ if (!this.adBreakState) await nextBreak();
10292
+ return;
10293
+ }
10272
10294
  if (ad) {
10273
10295
  this.startAd(ad);
10274
10296
  } else {
@@ -10402,12 +10424,18 @@ ${this.lastInitStack}` : "";
10402
10424
  const currProgress = pC.currentTimePercent;
10403
10425
  const progressStart = adState.timeStart / pC.duration;
10404
10426
  if (!adState.quartile.first && currProgress > 0.25 && progressStart < AD_START_TOLERANCE) {
10427
+ adState.quartile.first = true;
10428
+ logDebug(this, "adFirstQuartile", adState.ad);
10405
10429
  this.dispatch("adFirstQuartile", createAdProgressEvent(adState));
10406
10430
  }
10407
10431
  if (!adState.quartile.midpoint && currProgress > 0.5 && progressStart < 0.25 + AD_START_TOLERANCE) {
10432
+ adState.quartile.midpoint = true;
10433
+ logDebug(this, "adMidpoint", adState.ad);
10408
10434
  this.dispatch("adMidpoint", createAdProgressEvent(adState));
10409
10435
  }
10410
10436
  if (!adState.quartile.third && currProgress > 0.75 && progressStart < 0.5 + AD_START_TOLERANCE) {
10437
+ adState.quartile.third = true;
10438
+ logDebug(this, "adThirdQuartile", adState.ad);
10411
10439
  this.dispatch("adThirdQuartile", createAdProgressEvent(adState));
10412
10440
  }
10413
10441
  }
@@ -11143,7 +11171,9 @@ ${this.lastInitStack}` : "";
11143
11171
  (length) => length === validators.length
11144
11172
  );
11145
11173
  return createDeepValidator(
11146
- substitute(locale10.tuple, { value: validators.join(", ") }),
11174
+ substitute(locale10.tuple, {
11175
+ value: validators.map((validator) => validator.description).join(", ")
11176
+ }),
11147
11177
  (input, options, path) => {
11148
11178
  const errors = [];
11149
11179
  errors.push(
@@ -11448,7 +11478,7 @@ ${this.lastInitStack}` : "";
11448
11478
  };
11449
11479
  var _NumberSchema = class _NumberSchema extends ValueSchema {
11450
11480
  /**
11451
- * The base Object validator validating that the input is an object.
11481
+ * The base NumberSchema instance validating that the input is a number.
11452
11482
  */
11453
11483
  static get base() {
11454
11484
  if (this._base == null)
@@ -11734,7 +11764,7 @@ ${this.lastInitStack}` : "";
11734
11764
  };
11735
11765
  var _StringSchema = class _StringSchema extends ValueSchema {
11736
11766
  /**
11737
- * The base SetSchema instance validating that the input is a Set.
11767
+ * The base StringSchema instance validating that the input is a string.
11738
11768
  */
11739
11769
  static get base() {
11740
11770
  if (this._base == null)
@@ -12515,6 +12545,7 @@ ${this.lastInitStack}` : "";
12515
12545
  const url = resolveUrl(assetListUrl, baseUrl);
12516
12546
  const response = await requestWithRetry(url);
12517
12547
  const json = await readResponseBody(response, "json");
12548
+ const assetListBaseUrl = response.url || url;
12518
12549
  const assets = (_a3 = json.ASSETS) != null ? _a3 : [];
12519
12550
  const ads = assets.map((asset, i) => {
12520
12551
  var _a4;
@@ -12522,7 +12553,14 @@ ${this.lastInitStack}` : "";
12522
12553
  id: `${rangeId}-${i}`,
12523
12554
  startTime,
12524
12555
  duration: (_a4 = asset.DURATION) != null ? _a4 : null,
12525
- uri: resolveUrl(asset.URI, baseUrl)
12556
+ // Each Asset-Description "URI" MUST be an absolute URI, so the base
12557
+ // only applies to non-conforming lists. As a leniency (not a spec
12558
+ // rule), resolve against the asset list the URI was read from, as
12559
+ // for any relative reference in a retrieved document (RFC 3986
12560
+ // §5.1.3): asset lists are commonly served by an ad server rather
12561
+ // than alongside the playlist.
12562
+ // @see https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-22#appendix-D.2
12563
+ uri: resolveUrl(asset.URI, assetListBaseUrl)
12526
12564
  };
12527
12565
  });
12528
12566
  return { ads, skipControl: parseSkipControl(json["SKIP-CONTROL"]) };
@@ -12813,7 +12851,7 @@ ${this.lastInitStack}` : "";
12813
12851
  __publicField(this, "_disposed", false);
12814
12852
  __publicField(this, "messageHandler", (event) => {
12815
12853
  this.dispatch("message", {
12816
- message: event.message.buffer
12854
+ message: bufferToArrayBuffer(event.message)
12817
12855
  });
12818
12856
  });
12819
12857
  __publicField(this, "errorHandler", () => {
@@ -12956,11 +12994,7 @@ ${this.lastInitStack}` : "";
12956
12994
  return "StandardCommonMediaKeySession";
12957
12995
  }
12958
12996
  generateRequest() {
12959
- const initData = this.initData;
12960
- return this.session.generateRequest(
12961
- this.initDataType,
12962
- initData instanceof ArrayBuffer ? initData : initData.buffer
12963
- ).catch(createDrmErrorHandler());
12997
+ return this.session.generateRequest(this.initDataType, this.initData).catch(createDrmErrorHandler());
12964
12998
  }
12965
12999
  update(key) {
12966
13000
  if (this.disposed) throw new DisposedError();
@@ -13068,7 +13102,7 @@ ${this.lastInitStack}` : "";
13068
13102
  __publicField(this, "_disposed", false);
13069
13103
  __publicField(this, "messageHandler", (event) => {
13070
13104
  this.dispatch("message", {
13071
- message: event.message.buffer
13105
+ message: bufferToArrayBuffer(event.message)
13072
13106
  });
13073
13107
  });
13074
13108
  __publicField(this, "errorHandler", () => {
@@ -13966,7 +14000,7 @@ ${this.lastInitStack}` : "";
13966
14000
 
13967
14001
  // src/drm/util/extractContentId.ts
13968
14002
  function extractContentId(initData) {
13969
- const bufferStr = bufferToUtf16(initData.buffer);
14003
+ const bufferStr = bufferToUtf16(bufferToArrayBuffer(initData));
13970
14004
  const matches = bufferStr.match(/skd:\/\/(.+)$/);
13971
14005
  if (!matches) {
13972
14006
  throw new ValidationError("No contentId match", ErrorOrigin.MEDIA);
@@ -14007,6 +14041,7 @@ ${this.lastInitStack}` : "";
14007
14041
  // src/drm/DrmControllerImpl.ts
14008
14042
  var LICENSE_TIMEOUT = 90;
14009
14043
  var LICENSE_TIMEOUT_MESSAGE = "License provider timed out after {time}s";
14044
+ var EXCESSIVE_SESSION_COUNT = 20;
14010
14045
  var DrmControllerImpl = class extends EventHostImpl {
14011
14046
  constructor(deps, options) {
14012
14047
  super();
@@ -14223,6 +14258,13 @@ ${this.lastInitStack}` : "";
14223
14258
  trackUri
14224
14259
  });
14225
14260
  this.sessions.push(newSession);
14261
+ logVerbose(this, `total sessions: ${this.sessions.length}`);
14262
+ if (this.sessions.length === EXCESSIVE_SESSION_COUNT + 1) {
14263
+ logWarn(
14264
+ this,
14265
+ `${this.sessions.length} open key sessions exceeds ${EXCESSIVE_SESSION_COUNT}; sessions may be leaking (not closing on track deactivation)`
14266
+ );
14267
+ }
14226
14268
  this.dispatch("sessionCreate", {
14227
14269
  initDataType: newSession.initDataType,
14228
14270
  mimeType: newSession.mimeType
@@ -14624,20 +14666,21 @@ ${this.lastInitStack}` : "";
14624
14666
  let abs = Math.abs(value);
14625
14667
  const years = Math.floor(abs / Y);
14626
14668
  abs -= years * Y;
14627
- const months = Math.floor(abs % Y / M);
14669
+ const months = Math.floor(abs / M);
14628
14670
  const days = Math.floor(abs % M / D);
14629
14671
  const hours = Math.floor(abs % D / H);
14630
14672
  const minutes = Math.floor(abs % H / I);
14631
14673
  const seconds = abs % I;
14632
- let isoString = `${sign}P`;
14633
- if (years > 0) isoString += `${years}Y`;
14634
- if (months > 0) isoString += `${months}M`;
14635
- if (days > 0) isoString += `${days}D`;
14636
- isoString += "T";
14637
- if (hours > 0) isoString += `${hours}H`;
14638
- if (minutes > 0) isoString += `${minutes}M`;
14639
- if (seconds > 0 || !value) isoString += `${roundToNearest(seconds, 1e-3)}S`;
14640
- return isoString;
14674
+ let date = "";
14675
+ if (years > 0) date += `${years}Y`;
14676
+ if (months > 0) date += `${months}M`;
14677
+ if (days > 0) date += `${days}D`;
14678
+ let time = "";
14679
+ if (hours > 0) time += `${hours}H`;
14680
+ if (minutes > 0) time += `${minutes}M`;
14681
+ if (seconds > 0) time += `${roundToNearest(seconds, 1e-3)}S`;
14682
+ if (!date && !time) time = "0S";
14683
+ return `${sign}P${date}${time && `T${time}`}`;
14641
14684
  }
14642
14685
 
14643
14686
  // ../vinyl-mpd-parser/src/error/parsingError.ts
@@ -15221,7 +15264,7 @@ ${this.lastInitStack}` : "";
15221
15264
  // src/global/environment.ts
15222
15265
  var vinylVersion = parseVersion(
15223
15266
  /* istanbul ignore next */
15224
- "3.2.1"
15267
+ "3.2.2"
15225
15268
  );
15226
15269
 
15227
15270
  // src/global/globalRef.ts
@@ -15715,15 +15758,13 @@ ${this.lastInitStack}` : "";
15715
15758
  __publicField(this, "_playing", false);
15716
15759
  __publicField(this, "_seeking", false);
15717
15760
  __publicField(this, "_waiting", false);
15718
- // Monotonic counter identifying the most recent seekTo call.
15719
- __publicField(this, "_seekId", 0);
15761
+ __publicField(this, "seekAbortSlot", createAbortSlot());
15720
15762
  // Stops the stall detector started after the last seek, if any.
15721
15763
  __publicField(this, "stopStallDetection", null);
15722
15764
  // default user volume (0.0 to 1.0 scale)
15723
15765
  __publicField(this, "userVolume", 1);
15724
15766
  __publicField(this, "_pendingPlay", null);
15725
15767
  __publicField(this, "playAbort", null);
15726
- __publicField(this, "disposeAbort", new Abort());
15727
15768
  __publicField(this, "options");
15728
15769
  this.options = __spreadValues(__spreadProps(__spreadValues({}, defaultPlaybackControllerImplOptions), {
15729
15770
  minSeekableBuffer: getMinSeekableBufferDefault()
@@ -15734,8 +15775,7 @@ ${this.lastInitStack}` : "";
15734
15775
  const { add } = this.disposer;
15735
15776
  add(() => {
15736
15777
  var _a3;
15737
- this.disposeAbort.abort();
15738
- this.disposeAbort.dispose();
15778
+ this.seekAbortSlot.abort();
15739
15779
  (_a3 = this.stopStallDetection) == null ? void 0 : _a3.call(this);
15740
15780
  });
15741
15781
  this.domEvents = add(
@@ -16148,14 +16188,10 @@ ${this.lastInitStack}` : "";
16148
16188
  async seekTo(time, tolerance = 0.5) {
16149
16189
  var _a3;
16150
16190
  logDebug(this, `seekTo: ${time}`);
16151
- const seekId = ++this._seekId;
16152
16191
  (_a3 = this.stopStallDetection) == null ? void 0 : _a3.call(this);
16153
16192
  this.stopStallDetection = null;
16154
- const superseded = () => {
16155
- if (this._seekId === seekId) return false;
16156
- logDebug(this, `seekTo: ${time} superseded by a newer seek`);
16157
- return true;
16158
- };
16193
+ this.seekAbortSlot.abort();
16194
+ const seekAbort = this.seekAbortSlot.value;
16159
16195
  const nextEvent = (type, options) => {
16160
16196
  var _a4;
16161
16197
  return nextEventAsPromise(this, type, {
@@ -16163,12 +16199,25 @@ ${this.lastInitStack}` : "";
16163
16199
  timeoutMessage: `seek timed out on ${type} event after {time}s`,
16164
16200
  timeoutOrigin: ErrorOrigin.INTERNAL,
16165
16201
  timeoutLevel: "warn" /* WARN */,
16166
- abort: this.disposeAbort
16202
+ abort: seekAbort
16167
16203
  });
16168
16204
  };
16205
+ try {
16206
+ await this.doSeek(time, tolerance, seekAbort, nextEvent);
16207
+ } catch (error) {
16208
+ if (seekAbort.aborted() && !this.disposed) return;
16209
+ throw error;
16210
+ }
16211
+ }
16212
+ /**
16213
+ * The body of a single {@link seekTo} attempt. Extracted so {@link seekTo}
16214
+ * can wrap it with per-seek abort/supersession handling. `seekAbort` is
16215
+ * aborted when a newer seek supersedes this one (or on dispose).
16216
+ * @private
16217
+ */
16218
+ async doSeek(time, tolerance, seekAbort, nextEvent) {
16169
16219
  if (!this.hasMetadata) {
16170
16220
  await nextEvent("loadedMetadata");
16171
- if (superseded()) return;
16172
16221
  }
16173
16222
  const seekable = this.seekable;
16174
16223
  const range = seekable.getRangeAt(
@@ -16199,7 +16248,7 @@ ${this.lastInitStack}` : "";
16199
16248
  );
16200
16249
  } else {
16201
16250
  const nullIfStuck = (error) => {
16202
- if (this.disposeAbort.aborted()) throw error;
16251
+ if (this.disposed) throw error;
16203
16252
  return null;
16204
16253
  };
16205
16254
  const seekOnce = async (reSeeking) => {
@@ -16213,7 +16262,7 @@ ${this.lastInitStack}` : "";
16213
16262
  }).catch(nullIfStuck);
16214
16263
  };
16215
16264
  let seekedEvent = await seekOnce(false);
16216
- for (let attempt = 0; !superseded() && attempt < SEEK_RETRY_LIMIT && (seekedEvent === null || seekedEvent.reason === "seeked" && !closeTo(clampedTime, this.currentTime, tolerance)); attempt++) {
16265
+ for (let attempt = 0; !seekAbort.aborted() && attempt < SEEK_RETRY_LIMIT && (seekedEvent === null || seekedEvent.reason === "seeked" && !closeTo(clampedTime, this.currentTime, tolerance)); attempt++) {
16217
16266
  if (seekedEvent === null) void this.media.play().catch(noop);
16218
16267
  logDebug(this, `seekTo: re-seeking to ${clampedTime}`);
16219
16268
  seekedEvent = await seekOnce(true);
@@ -16221,7 +16270,7 @@ ${this.lastInitStack}` : "";
16221
16270
  if ((seekedEvent == null ? void 0 : seekedEvent.reason) === "emptied") {
16222
16271
  throw new AbortError();
16223
16272
  }
16224
- if (!this.paused) this.startStallDetection();
16273
+ if (!this.paused && !seekAbort.aborted()) this.startStallDetection();
16225
16274
  }
16226
16275
  }
16227
16276
  /**
@@ -16777,14 +16826,16 @@ ${this.lastInitStack}` : "";
16777
16826
  return;
16778
16827
  }
16779
16828
  const byteLength = streamingSegment.data.byteLength;
16780
- if (byteLength > appendSize) {
16829
+ const remaining = byteLength - this.appendOffset;
16830
+ const firstAppend = this.appendOffset === 0;
16831
+ if (remaining > appendSize) {
16781
16832
  const newOffset = this.appendOffset + appendSize;
16782
16833
  const chunk = streamingSegment.data.slice(
16783
16834
  this.appendOffset,
16784
16835
  newOffset
16785
16836
  );
16786
16837
  await sourceBufferController.append(chunk);
16787
- if (this.appendOffset === 0) {
16838
+ if (firstAppend) {
16788
16839
  this.buffered.push(streamingSegment);
16789
16840
  }
16790
16841
  this.appendOffset = newOffset;
@@ -16793,9 +16844,13 @@ ${this.lastInitStack}` : "";
16793
16844
  `appended chunk, this.appendOffset: ${this.appendOffset}`
16794
16845
  );
16795
16846
  } else {
16796
- await sourceBufferController.append(streamingSegment.data);
16797
- this.appendOffset = streamingSegment.data.byteLength;
16798
- this.buffered.push(streamingSegment);
16847
+ await sourceBufferController.append(
16848
+ firstAppend ? streamingSegment.data : streamingSegment.data.slice(this.appendOffset)
16849
+ );
16850
+ this.appendOffset = byteLength;
16851
+ if (firstAppend) {
16852
+ this.buffered.push(streamingSegment);
16853
+ }
16799
16854
  }
16800
16855
  if (!tail) this.refreshHead();
16801
16856
  }
@@ -17078,6 +17133,7 @@ ${this.lastInitStack}` : "";
17078
17133
  this.activateDisposer.dispose();
17079
17134
  this.activateDisposer = null;
17080
17135
  ++this.durationToken;
17136
+ this.durationSet = false;
17081
17137
  try {
17082
17138
  URL.revokeObjectURL(this.objectUrl);
17083
17139
  } catch (error) {
@@ -17626,6 +17682,7 @@ ${this.lastInitStack}` : "";
17626
17682
  dispose() {
17627
17683
  this._disposed = true;
17628
17684
  this.callbacks.length = 0;
17685
+ this.n = 0;
17629
17686
  }
17630
17687
  notify(value, previousValue) {
17631
17688
  this._changeId++;
@@ -17741,23 +17798,26 @@ ${this.lastInitStack}` : "";
17741
17798
  providers[key] = provider;
17742
17799
  current[key] = provider.value;
17743
17800
  }
17744
- return externalData(current, (setData) => {
17801
+ let published = __spreadValues({}, current);
17802
+ const publish = (setData) => {
17803
+ published = __spreadValues({}, current);
17804
+ setData(published);
17805
+ };
17806
+ return externalData(published, (setData) => {
17745
17807
  const subs = [];
17808
+ let subscribing = true;
17746
17809
  for (const key of keys) {
17747
- const provider = providers[key];
17748
- let isFirst = true;
17749
17810
  subs.push(
17750
- provider.onData((value) => {
17751
- if (isFirst) {
17752
- isFirst = false;
17753
- return;
17754
- }
17811
+ providers[key].onData((value) => {
17755
17812
  current[key] = value;
17756
- setData(__spreadValues({}, current));
17813
+ if (!subscribing) publish(setData);
17757
17814
  })
17758
17815
  );
17759
17816
  }
17760
- setData(current);
17817
+ subscribing = false;
17818
+ if (keys.some((key) => current[key] !== published[key])) {
17819
+ publish(setData);
17820
+ }
17761
17821
  return () => {
17762
17822
  for (const unsub of subs) unsub();
17763
17823
  };
@@ -19180,10 +19240,16 @@ ${this.lastInitStack}` : "";
19180
19240
  return out;
19181
19241
  }
19182
19242
  function isTextAdaptationSet(adaptationSet) {
19243
+ var _a3;
19183
19244
  if (adaptationSet.contentType === "text") return true;
19184
19245
  const mt = adaptationSet.mimeType;
19185
19246
  if (mt && mt.startsWith("text/")) return true;
19186
- return false;
19247
+ return ((_a3 = adaptationSet.Representation) != null ? _a3 : []).some(
19248
+ (representation) => {
19249
+ var _a4;
19250
+ return (_a4 = representation.mimeType) == null ? void 0 : _a4.startsWith("text/");
19251
+ }
19252
+ );
19187
19253
  }
19188
19254
  function isSidecarTextMime(mimeType) {
19189
19255
  if (mimeType == null) return true;
@@ -19441,7 +19507,7 @@ ${this.lastInitStack}` : "";
19441
19507
  const cues = [];
19442
19508
  const styles = [];
19443
19509
  let i = 1;
19444
- while (i < lines.length && lines[i] !== "") i++;
19510
+ while (i < lines.length && lines[i] !== "" && !lines[i].includes("-->")) i++;
19445
19511
  while (i < lines.length) {
19446
19512
  if (lines[i] === "") {
19447
19513
  i++;
@@ -20561,7 +20627,6 @@ ${this.lastInitStack}` : "";
20561
20627
  const deps = depsContainer.dependencies;
20562
20628
  super(uri, type, deps);
20563
20629
  __publicField(this, "streams", []);
20564
- __publicField(this, "disposeAbort", new Abort());
20565
20630
  __publicField(this, "lastPreloadOptions", null);
20566
20631
  __publicField(this, "allFetchedRanges", []);
20567
20632
  __publicField(this, "_fetchedRanges", new IntersectionRanges(
@@ -20586,7 +20651,7 @@ ${this.lastInitStack}` : "";
20586
20651
  if (this.active) {
20587
20652
  this.deps.drmController.initializeForPlayback(event.current, {
20588
20653
  trackUri: this.uri,
20589
- abort: this.disposeAbort
20654
+ abort: this.drmSessionAbort.value
20590
20655
  });
20591
20656
  }
20592
20657
  });
@@ -20594,7 +20659,7 @@ ${this.lastInitStack}` : "";
20594
20659
  if (event.current == null) return;
20595
20660
  this.deps.drmController.setBufferingDrmInfo(event.current, {
20596
20661
  trackUri: this.uri,
20597
- abort: this.disposeAbort
20662
+ abort: this.drmSessionAbort.value
20598
20663
  });
20599
20664
  });
20600
20665
  add(
@@ -20832,7 +20897,7 @@ ${this.lastInitStack}` : "";
20832
20897
  this.streams.forEach((stream) => {
20833
20898
  this.deps.drmController.initializeForPlayback(
20834
20899
  stream.streamingQuality,
20835
- { trackUri: this.uri, abort: this.disposeAbort }
20900
+ { trackUri: this.uri, abort: this.drmSessionAbort.value }
20836
20901
  );
20837
20902
  });
20838
20903
  this.deps.playbackSource.src = this.deps.mediaSourceController.activate();
@@ -20858,7 +20923,7 @@ ${this.lastInitStack}` : "";
20858
20923
  this.callOnStreams("deactivate");
20859
20924
  this.deps.drmController.setBufferingDrmInfo(null, {
20860
20925
  trackUri: this.uri,
20861
- abort: this.disposeAbort
20926
+ abort: this.drmSessionAbort.value
20862
20927
  });
20863
20928
  (_b = this.deps.textTrackController) == null ? void 0 : _b.deactivate();
20864
20929
  this.deps.playbackSource.src = null;
@@ -22604,7 +22669,9 @@ ${this.lastInitStack}` : "";
22604
22669
  audioConfig: null,
22605
22670
  videoConfig: null,
22606
22671
  audioDecodeTime: 0,
22607
- videoDecodeTime: 0
22672
+ videoDecodeTime: 0,
22673
+ audioTrackId: null,
22674
+ videoTrackId: null
22608
22675
  };
22609
22676
  return { transmux: (data2) => transmux(state, data2) };
22610
22677
  }
@@ -22659,11 +22726,12 @@ ${this.lastInitStack}` : "";
22659
22726
  sampleRate: sampleRate2,
22660
22727
  audioSpecificConfig: buildAudioSpecificConfig(firstFrame)
22661
22728
  };
22729
+ state.audioTrackId = 1;
22662
22730
  buildInitSegment(
22663
22731
  state,
22664
22732
  moov(
22665
22733
  audioTrak({
22666
- trackId: 1,
22734
+ trackId: state.audioTrackId,
22667
22735
  sampleRate: state.audioConfig.sampleRate,
22668
22736
  channelCount: state.audioConfig.channelConfiguration,
22669
22737
  sampleEntry: mp4aSampleEntry(state.audioConfig)
@@ -22690,7 +22758,7 @@ ${this.lastInitStack}` : "";
22690
22758
  const duration = frames.length * samplesPerFrame / sampleRate;
22691
22759
  const moofBox = moof({
22692
22760
  sequenceNumber: state.sequenceNumber++,
22693
- trackId: 1,
22761
+ trackId: state.audioTrackId,
22694
22762
  baseDecodeTime: state.audioDecodeTime,
22695
22763
  samples
22696
22764
  });
@@ -22796,9 +22864,10 @@ ${this.lastInitStack}` : "";
22796
22864
  if (!state.initSegment && (audioConfig || videoConfig)) {
22797
22865
  const traks = [];
22798
22866
  if (videoConfig) {
22867
+ state.videoTrackId = traks.length + 1;
22799
22868
  traks.push(
22800
22869
  videoTrak({
22801
- trackId: traks.length + 1,
22870
+ trackId: state.videoTrackId,
22802
22871
  width: videoConfig.width,
22803
22872
  height: videoConfig.height,
22804
22873
  sampleEntry: avc1SampleEntry(videoConfig)
@@ -22806,9 +22875,10 @@ ${this.lastInitStack}` : "";
22806
22875
  );
22807
22876
  }
22808
22877
  if (audioConfig) {
22878
+ state.audioTrackId = traks.length + 1;
22809
22879
  traks.push(
22810
22880
  audioTrak({
22811
- trackId: traks.length + 1,
22881
+ trackId: state.audioTrackId,
22812
22882
  sampleRate: audioConfig.sampleRate,
22813
22883
  channelCount: audioConfig.channelConfiguration,
22814
22884
  sampleEntry: mp4aSampleEntry(audioConfig)
@@ -22819,8 +22889,8 @@ ${this.lastInitStack}` : "";
22819
22889
  }
22820
22890
  const fragments = [];
22821
22891
  let duration = 0;
22822
- if (videoConfig && videoNalus.length > 0) {
22823
- const trackId = 1;
22892
+ if (videoConfig && videoNalus.length > 0 && state.videoTrackId != null) {
22893
+ const trackId = state.videoTrackId;
22824
22894
  const accessUnits = [];
22825
22895
  let pendingParams = [];
22826
22896
  for (const nalu of videoNalus) {
@@ -22887,8 +22957,8 @@ ${this.lastInitStack}` : "";
22887
22957
  duration = accessUnits.length * frameDuration / MPEG_TIMESCALE;
22888
22958
  }
22889
22959
  }
22890
- if (audioConfig && audioRawData && audioFrames.length > 0) {
22891
- const trackId = videoConfig ? 2 : 1;
22960
+ if (audioConfig && audioRawData && audioFrames.length > 0 && state.audioTrackId != null) {
22961
+ const trackId = state.audioTrackId;
22892
22962
  const samplesPerFrame = 1024;
22893
22963
  const samples = [];
22894
22964
  const parts = [];
@@ -23188,7 +23258,11 @@ ${this.lastInitStack}` : "";
23188
23258
  async getSegment(time, affordance = 0) {
23189
23259
  var _a3;
23190
23260
  const playlist = await data2.getMediaPlaylist(playlistUri);
23191
- const playlistBaseUrl = resolveUrl(playlistUri, baseUrl);
23261
+ const playlistBaseUrl = getPlaylistBaseUrl(
23262
+ playlist,
23263
+ playlistUri,
23264
+ baseUrl
23265
+ );
23192
23266
  const segments = buildSegmentTimeline(
23193
23267
  deps,
23194
23268
  playlistBaseUrl,
@@ -23235,9 +23309,13 @@ ${this.lastInitStack}` : "";
23235
23309
  const variant2 = data2.mainPlaylist.variants[0];
23236
23310
  const media = await data2.getMediaPlaylist(variant2.uri);
23237
23311
  const contentDuration = media.ended ? media.segments.reduce((sum, s) => sum + s.duration, 0) : null;
23238
- const playlistBaseUrl = resolveUrl(variant2.uri, data2.baseUrl);
23312
+ const playlistBaseUrl = getPlaylistBaseUrl(media, variant2.uri, data2.baseUrl);
23239
23313
  return discoverHlsInterstitials(media, playlistBaseUrl, contentDuration);
23240
23314
  }
23315
+ function getPlaylistBaseUrl(playlist, playlistUri, mainBaseUrl) {
23316
+ var _a3;
23317
+ return (_a3 = playlist.baseUrl) != null ? _a3 : resolveUrl(playlistUri, mainBaseUrl);
23318
+ }
23241
23319
 
23242
23320
  // src/track/hls/createDefaultHlsManifestTransformer.ts
23243
23321
  function createDefaultHlsManifestTransformer(deps) {
@@ -23370,8 +23448,11 @@ ${this.lastInitStack}` : "";
23370
23448
  const variantUrl = resolveUrl(uri, baseUrl);
23371
23449
  const response = await requestWithRetry(variantUrl, requestInit, { abort });
23372
23450
  const text = await readResponseBody(response, "text");
23373
- const queryParams = parseQueryParams(response.url || variantUrl);
23374
- return parseMediaPlaylist(text, defines, queryParams);
23451
+ const playlistUrl = response.url || variantUrl;
23452
+ const queryParams = parseQueryParams(playlistUrl);
23453
+ return __spreadProps(__spreadValues({}, parseMediaPlaylist(text, defines, queryParams)), {
23454
+ baseUrl: playlistUrl
23455
+ });
23375
23456
  }
23376
23457
  function parseQueryParams(url) {
23377
23458
  const queryStart = url.indexOf("?");
@@ -23823,6 +23904,9 @@ ${this.lastInitStack}` : "";
23823
23904
  for (const cachedTrack of this.trackCache.values()) {
23824
23905
  cachedTrack.clearPrefetch();
23825
23906
  }
23907
+ for (const adTracks of this.adTracksByParent.values()) {
23908
+ for (const adTrack of adTracks.values()) adTrack.clearPrefetch();
23909
+ }
23826
23910
  }
23827
23911
  clearQueue() {
23828
23912
  this.setQueue(this._current, []);
@@ -27294,6 +27378,47 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
27294
27378
  await flush();
27295
27379
  expect(c.currentAdBreak).toBeNull();
27296
27380
  });
27381
+ it("still enters a pending break when skipped while its ads resolve", async () => {
27382
+ var _a3;
27383
+ const c = createController();
27384
+ let resolveAds = () => void 0;
27385
+ const entered = [];
27386
+ await setContent(
27387
+ c,
27388
+ trackAds(
27389
+ makeBreak({
27390
+ id: "b1",
27391
+ startTime: 0,
27392
+ duration: 30,
27393
+ ads: () => new Promise((resolve) => {
27394
+ resolveAds = resolve;
27395
+ })
27396
+ }),
27397
+ makeBreak({
27398
+ id: "b2",
27399
+ startTime: 1,
27400
+ duration: 30,
27401
+ ads: [
27402
+ {
27403
+ id: "a2",
27404
+ startTime: 1,
27405
+ duration: 10,
27406
+ uri: "ad2.m3u8"
27407
+ }
27408
+ ]
27409
+ })
27410
+ )
27411
+ );
27412
+ c.on("adBreakEntered", (e) => entered.push(e.adBreak.id));
27413
+ updateTime(2);
27414
+ await flush();
27415
+ expect(entered).toEqual(["b1"]);
27416
+ c.skipAdBreak();
27417
+ resolveAds([]);
27418
+ await flush();
27419
+ expect(entered).toEqual(["b1", "b2"]);
27420
+ expect((_a3 = c.currentAdBreak) == null ? void 0 : _a3.id).toBe("b2");
27421
+ });
27297
27422
  });
27298
27423
  describe("a break with no ads", () => {
27299
27424
  it("completes immediately with a resume position and plays no ad", async () => {
@@ -27574,6 +27699,35 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
27574
27699
  await flush();
27575
27700
  expect(thirds).toEqual(["a1"]);
27576
27701
  });
27702
+ it("dispatches each quartile at most once per ad", async () => {
27703
+ const c = createController();
27704
+ const events = [];
27705
+ c.on("adFirstQuartile", () => events.push("first"));
27706
+ c.on("adMidpoint", () => events.push("mid"));
27707
+ c.on("adThirdQuartile", () => events.push("third"));
27708
+ await setContent(
27709
+ c,
27710
+ trackAds(makeBreak({ startTime: 0, duration: 10 }))
27711
+ );
27712
+ updateTime(0);
27713
+ await flush();
27714
+ playbackController.dispatch("playing", {});
27715
+ playbackController.duration = 10;
27716
+ playbackController.playbackRate = 1;
27717
+ for (const [percent, time] of [
27718
+ [0.3, 3],
27719
+ [0.4, 4],
27720
+ [0.6, 6],
27721
+ [0.7, 7],
27722
+ [0.8, 8],
27723
+ [0.9, 9]
27724
+ ]) {
27725
+ playbackController.currentTimePercent = percent;
27726
+ updateTime(time);
27727
+ await flush();
27728
+ }
27729
+ expect(events).toEqual(["first", "mid", "third"]);
27730
+ });
27577
27731
  it("does not emit quartiles or throw when the ad duration is zero", async () => {
27578
27732
  const c = createController();
27579
27733
  const events = [];
@@ -28617,8 +28771,9 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
28617
28771
  clientAttributes: {}
28618
28772
  }, overrides);
28619
28773
  }
28620
- function okJsonResponse(body) {
28774
+ function okJsonResponse(body, url = "") {
28621
28775
  return {
28776
+ url,
28622
28777
  ok: true,
28623
28778
  status: 200,
28624
28779
  headers: { get: () => null },
@@ -28787,7 +28942,7 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
28787
28942
  "https://example.com/ads.json"
28788
28943
  );
28789
28944
  expect(ads.length).toBe(2);
28790
- expect(ads[0].uri).toBe("https://cdn.example.com/media/mid1.m3u8");
28945
+ expect(ads[0].uri).toBe("https://example.com/mid1.m3u8");
28791
28946
  expect(ads[0].duration).toBe(10);
28792
28947
  expect(ads[1].uri).toBe("https://cdn.example.com/mid2.m3u8");
28793
28948
  expect(ads[1].duration).toBeNull();
@@ -28795,6 +28950,33 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
28795
28950
  globalThis.fetch = origFetch;
28796
28951
  }
28797
28952
  });
28953
+ it("resolves relative asset URIs against the redirected asset list URL", async () => {
28954
+ const origFetch = globalThis.fetch;
28955
+ const fetchSpy = jasmine.createSpy("fetch").and.resolveTo(
28956
+ okJsonResponse(
28957
+ { ASSETS: [{ URI: "mid1.m3u8" }] },
28958
+ "https://ads.example.net/v2/ads.json"
28959
+ )
28960
+ );
28961
+ globalThis.fetch = fetchSpy;
28962
+ try {
28963
+ const playlist = makePlaylist({
28964
+ dateRanges: [
28965
+ makeRange({
28966
+ duration: 10,
28967
+ clientAttributes: {
28968
+ "X-ASSET-LIST": "https://example.com/redirected.json"
28969
+ }
28970
+ })
28971
+ ]
28972
+ });
28973
+ const breaks = await discoverHlsInterstitials(playlist, BASE);
28974
+ const ads = await resolveValueProvider(breaks[0].ads);
28975
+ expect(ads[0].uri).toBe("https://ads.example.net/v2/mid1.m3u8");
28976
+ } finally {
28977
+ globalThis.fetch = origFetch;
28978
+ }
28979
+ });
28798
28980
  it("caches the X-ASSET-LIST fetch across calls", async () => {
28799
28981
  const origFetch = globalThis.fetch;
28800
28982
  const fetchSpy = jasmine.createSpy("fetch").and.resolveTo(okJsonResponse({ ASSETS: [{ URI: "x.m3u8" }] }));
@@ -30172,10 +30354,10 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
30172
30354
  });
30173
30355
  });
30174
30356
  describe("when created", () => {
30175
- it("calls session.generateRequest() with the initData buffer", () => {
30357
+ it("calls session.generateRequest() with the initData", () => {
30176
30358
  expect(
30177
30359
  mediaKeySession.generateRequest
30178
- ).toHaveBeenCalledOnceWith("cenc", initData.buffer);
30360
+ ).toHaveBeenCalledOnceWith("cenc", initData);
30179
30361
  mediaKeySession.generateRequest.calls.reset();
30180
30362
  const arrayBuffer = new ArrayBuffer(0);
30181
30363
  standardCommonMediaKeySession = new StandardCommonMediaKeySession(
@@ -30188,6 +30370,21 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
30188
30370
  mediaKeySession.generateRequest
30189
30371
  ).toHaveBeenCalledOnceWith("cenc", arrayBuffer);
30190
30372
  });
30373
+ it("passes only the bytes an offset initData view spans", () => {
30374
+ mediaKeySession.generateRequest.calls.reset();
30375
+ const view = new Uint8Array([1, 2, 3, 4, 5]).subarray(1, 4);
30376
+ standardCommonMediaKeySession = new StandardCommonMediaKeySession(
30377
+ mediaKeySession,
30378
+ mimeType,
30379
+ "cenc",
30380
+ view
30381
+ );
30382
+ const [, passed] = mediaKeySession.generateRequest.calls.mostRecent().args;
30383
+ expectIterableEquals(
30384
+ new Uint8Array(bufferToArrayBuffer(passed)),
30385
+ [2, 3, 4]
30386
+ );
30387
+ });
30191
30388
  describe("when session.generateRequest rejects", () => {
30192
30389
  it("emits an error event", async () => {
30193
30390
  mediaKeySession.generateRequest.and.rejectWith(
@@ -32139,6 +32336,23 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
32139
32336
  expect(drmController.isEmeSupported()).toBeFalse();
32140
32337
  });
32141
32338
  });
32339
+ describe("excessive sessions", () => {
32340
+ it("warns once when the open session count exceeds the threshold", async () => {
32341
+ drmController.setBufferingDrmInfo(drmInfo);
32342
+ for (let i = 0; i < EXCESSIVE_SESSION_COUNT; i++) {
32343
+ await emitEncrypted(new Uint8Array([i]));
32344
+ }
32345
+ expect(drmController.activeSessions).toBe(EXCESSIVE_SESSION_COUNT);
32346
+ expect(logger.value.warn).not.toHaveBeenCalled();
32347
+ await emitEncrypted(new Uint8Array([EXCESSIVE_SESSION_COUNT]));
32348
+ expect(drmController.activeSessions).toBe(
32349
+ EXCESSIVE_SESSION_COUNT + 1
32350
+ );
32351
+ expect(logger.value.warn).toHaveBeenCalledTimes(1);
32352
+ await emitEncrypted(new Uint8Array([EXCESSIVE_SESSION_COUNT + 1]));
32353
+ expect(logger.value.warn).toHaveBeenCalledTimes(1);
32354
+ });
32355
+ });
32142
32356
  describe("closeSessions", () => {
32143
32357
  it("clears active sessions", async () => {
32144
32358
  const sessionCloseSpy = createEventSpy(
@@ -32628,6 +32842,13 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
32628
32842
  ).toBe("example0");
32629
32843
  });
32630
32844
  });
32845
+ describe("with an offset view", () => {
32846
+ it("reads only the bytes the view spans", () => {
32847
+ const padded = createContentIdByteArray("\0\0skd://example0\0\0");
32848
+ const view = padded.subarray(4, padded.byteLength - 4);
32849
+ expect(extractContentId(view)).toBe("example0");
32850
+ });
32851
+ });
32631
32852
  describe("without match", () => {
32632
32853
  it("throws ValidationError", () => {
32633
32854
  expect(() => {
@@ -34107,6 +34328,13 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
34107
34328
  await clock.tick(controller.options.seekTimeout);
34108
34329
  await seekExpectation;
34109
34330
  });
34331
+ it("resolves a superseded seek at once when its seeking event never fires", async () => {
34332
+ setSeekable([[0, 100]]);
34333
+ const first2 = controller.seekTo(50);
34334
+ const second = controller.seekTo(60);
34335
+ await expectAsync(first2).toBeResolved();
34336
+ second.catch(() => void 0);
34337
+ });
34110
34338
  it("times out pending seeks after seekTimeout", async () => {
34111
34339
  const pendingSeekExpectation = expectAsync(
34112
34340
  controller.seekTo(60)
@@ -35914,6 +36142,24 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
35914
36142
  expect(sourceBufferController.append).toHaveBeenCalledTimes(1);
35915
36143
  });
35916
36144
  });
36145
+ describe("when the append budget grows while a segment is partly appended", () => {
36146
+ it("appends only the bytes remaining in the segment", async () => {
36147
+ bufferingController.dispose();
36148
+ bufferingController = createBufferingController({
36149
+ maxAppendSize: { audio: 500 },
36150
+ minBuffer: 6
36151
+ });
36152
+ bufferingController.activate();
36153
+ setSegmentList([200, 400]);
36154
+ await open();
36155
+ await setTime(5);
36156
+ expect(getMostRecentAppendData().byteLength).toBe(300);
36157
+ sourceBufferController.append.calls.reset();
36158
+ await setTime(15.5);
36159
+ const appended = sourceBufferController.append.calls.allArgs().map(([data2]) => data2.byteLength);
36160
+ expect(appended[0]).toBe(100);
36161
+ });
36162
+ });
35917
36163
  describe("when contentType is undefined", () => {
35918
36164
  it("uses DEFAULT_MAX_APPEND_SIZE for max byte length", async () => {
35919
36165
  setSegmentList(
@@ -36675,6 +36921,28 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
36675
36921
  );
36676
36922
  expect(() => controller.deactivate()).not.toThrow();
36677
36923
  });
36924
+ it("does not report a re-activated media source as ready to append", async () => {
36925
+ open();
36926
+ await flushPromises();
36927
+ const firstRef = controller.createSourceBuffer("audio", "audio/mp4");
36928
+ expect(controller.readyToAppend).withContext("ready on the first media source").toBeTrue();
36929
+ firstRef.dispose();
36930
+ controller.deactivate();
36931
+ const secondSource = new MockMediaSource();
36932
+ implementEventFakes(secondSource);
36933
+ secondSource.addSourceBuffer.and.callFake(
36934
+ () => new MockSourceBuffer()
36935
+ );
36936
+ mediaSourceFactory.and.returnValue(secondSource);
36937
+ controller.activate();
36938
+ await flushPromises();
36939
+ controller.createSourceBuffer("audio", "audio/mp4");
36940
+ expect(controller.readyToAppend).withContext("duration not yet set on the new media source").toBeFalse();
36941
+ secondSource.readyState = "open";
36942
+ secondSource.dispatchEvent(mockEvent("sourceopen"));
36943
+ await flushPromises();
36944
+ expect(controller.readyToAppend).withContext("ready once the new duration is set").toBeTrue();
36945
+ });
36678
36946
  });
36679
36947
  describe("duration", () => {
36680
36948
  it("sets media source duration after sourceopen", async () => {
@@ -38437,6 +38705,11 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
38437
38705
  expect(drmProtectionValidator.isValid({ keySystem: 123 })).toBeFalse();
38438
38706
  });
38439
38707
  });
38708
+ describe("ALL_CONTENT_TYPES", () => {
38709
+ it("provides a comprehensive list of all content types", () => {
38710
+ expectTypeStrictlyEquals(true);
38711
+ });
38712
+ });
38440
38713
 
38441
38714
  // test/unit/streaming/MediaTimeline.test.ts
38442
38715
  describe("getMediaPeriodAtTime", () => {
@@ -39217,6 +39490,16 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
39217
39490
  abort: any14(Abort)
39218
39491
  });
39219
39492
  });
39493
+ it("aborts the drm session abort so key sessions close", async () => {
39494
+ track2 = createTrack();
39495
+ await awaitContentTypes();
39496
+ getAudioStream().streamingQuality = createEmptyMediaQualityMetadata();
39497
+ track2.activate({});
39498
+ const { abort } = deps.drmController.initializeForPlayback.calls.mostRecent().args[1];
39499
+ expect(abort.aborted()).toBeFalse();
39500
+ track2.deactivate();
39501
+ expect(abort.aborted()).toBeTrue();
39502
+ });
39220
39503
  });
39221
39504
  describe("error", () => {
39222
39505
  it("returns the first emitted error", async () => {
@@ -41183,6 +41466,18 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
41183
41466
  expect(result.length).toBe(1);
41184
41467
  expect(result[0].language).toBe("es");
41185
41468
  });
41469
+ it("discovers a text AdaptationSet by its Representation mimeType", () => {
41470
+ const m = manifest3(`
41471
+ <AdaptationSet lang="fr">
41472
+ <Representation id="fr-sub" mimeType="text/vtt" bandwidth="100">
41473
+ <BaseURL>fr.vtt</BaseURL>
41474
+ </Representation>
41475
+ </AdaptationSet>`);
41476
+ const result = discoverDashTextTracks(m, baseUrl);
41477
+ expect(result.length).toBe(1);
41478
+ expect(result[0].language).toBe("fr");
41479
+ expect(result[0].uri).toBe("https://example.com/dash/fr.vtt");
41480
+ });
41186
41481
  it("classifies kind=captions when role=caption is set", () => {
41187
41482
  const m = manifest3(`
41188
41483
  <AdaptationSet contentType="text" lang="en" mimeType="text/vtt">
@@ -41237,6 +41532,10 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
41237
41532
  </AdaptationSet>`);
41238
41533
  expect(discoverDashTextTracks(m, baseUrl)).toEqual([]);
41239
41534
  });
41535
+ it("skips a non-text AdaptationSet with no Representation", () => {
41536
+ const m = manifest3(`<AdaptationSet contentType="audio"/>`);
41537
+ expect(discoverDashTextTracks(m, baseUrl)).toEqual([]);
41538
+ });
41240
41539
  it("skips text AdaptationSets carrying segmented codecs (stpp/wvtt)", () => {
41241
41540
  const m = manifest3(`
41242
41541
  <AdaptationSet contentType="text" mimeType="application/mp4" codecs="stpp">
@@ -42268,6 +42567,29 @@ hi`;
42268
42567
  expect(cues.length).toBe(1);
42269
42568
  expect(cues[0].text).toBe("hi");
42270
42569
  });
42570
+ it("parses a cue that follows the signature with no blank line", () => {
42571
+ const text = `WEBVTT
42572
+ 00:00:01.000 --> 00:00:02.000
42573
+ hi
42574
+
42575
+ 00:00:03.000 --> 00:00:04.000
42576
+ there`;
42577
+ const cues = parseWebVtt(text).cues;
42578
+ expect(cues.length).toBe(2);
42579
+ expect(cues[0].startTime).toBe(1);
42580
+ expect(cues[0].text).toBe("hi");
42581
+ expect(cues[1].text).toBe("there");
42582
+ });
42583
+ it("keeps header lines as header when a cue follows with no blank line", () => {
42584
+ const text = `WEBVTT
42585
+ Kind: captions
42586
+ 00:00:01.000 --> 00:00:02.000
42587
+ hi`;
42588
+ const cues = parseWebVtt(text).cues;
42589
+ expect(cues.length).toBe(1);
42590
+ expect(cues[0].id).toBeNull();
42591
+ expect(cues[0].text).toBe("hi");
42592
+ });
42271
42593
  it("handles trailing identifier without timing line", () => {
42272
42594
  const text = `WEBVTT
42273
42595
 
@@ -43657,6 +43979,13 @@ x`);
43657
43979
  });
43658
43980
  });
43659
43981
 
43982
+ // test/unit/text/TextTrack.test.ts
43983
+ describe("ALL_TEXT_TRACK_EVENTS", () => {
43984
+ it("provides a comprehensive list of all text track events", () => {
43985
+ expectTypeStrictlyEquals(true);
43986
+ });
43987
+ });
43988
+
43660
43989
  // test/unit/text/VttCueStyle.test.ts
43661
43990
  describe("VttCueStyle", () => {
43662
43991
  describe("isVttCue", () => {
@@ -48763,6 +49092,29 @@ x`);
48763
49092
  );
48764
49093
  fetchSpy.and.callThrough();
48765
49094
  });
49095
+ it("resolves segment and map URIs against the media playlist baseUrl", async () => {
49096
+ const variant2 = createVariant("v1.m3u8", 128e3);
49097
+ const playlist = __spreadProps(__spreadValues({}, createMediaPlaylist([10])), {
49098
+ // The media playlist was redirected away from the main playlist's
49099
+ // directory.
49100
+ baseUrl: "https://redirect.example.net/hls/v1.m3u8"
49101
+ });
49102
+ const manifestData2 = createManifestData([variant2], playlist);
49103
+ const fetchSpy = spyOn(globalThis, "fetch").and.callFake(
49104
+ () => Promise.resolve(new Response(new ArrayBuffer(100)))
49105
+ );
49106
+ const timeline = buildHlsMediaTimeline(deps, manifestData2);
49107
+ const quality = timeline.periods[0].qualities[0];
49108
+ const segment = await quality.getSegment(5);
49109
+ await segment.initData();
49110
+ await segment.data();
49111
+ const urls = fetchSpy.calls.allArgs().map(([input]) => String(input));
49112
+ expect(urls).toEqual([
49113
+ "https://redirect.example.net/hls/init.mp4",
49114
+ "https://redirect.example.net/hls/seg0.m4s"
49115
+ ]);
49116
+ fetchSpy.and.callThrough();
49117
+ });
48766
49118
  it("getDuration throws when no variants", async () => {
48767
49119
  const manifestData2 = createManifestData([], createMediaPlaylist([]));
48768
49120
  const timeline = buildHlsMediaTimeline(deps, manifestData2);
@@ -49440,6 +49792,29 @@ x`);
49440
49792
  "https://example.com/path/audio.m3u8"
49441
49793
  );
49442
49794
  });
49795
+ it("sets the baseUrl from the response URL", async () => {
49796
+ mockRequester.request.and.callFake(() => {
49797
+ const resp = new Response(mediaM3u8);
49798
+ Object.defineProperty(resp, "url", {
49799
+ value: "https://redirected.com/hls/audio.m3u8"
49800
+ });
49801
+ return Promise.resolve(resp);
49802
+ });
49803
+ const playlist = await fetchMediaPlaylist({
49804
+ uri: "audio.m3u8",
49805
+ baseUrl: "https://example.com/path/main.m3u8",
49806
+ defines: void 0
49807
+ });
49808
+ expect(playlist.baseUrl).toBe("https://redirected.com/hls/audio.m3u8");
49809
+ });
49810
+ it("falls back to the requested URL when the response has no URL", async () => {
49811
+ const playlist = await fetchMediaPlaylist({
49812
+ uri: "audio.m3u8",
49813
+ baseUrl: "https://example.com/path/main.m3u8",
49814
+ defines: void 0
49815
+ });
49816
+ expect(playlist.baseUrl).toBe("https://example.com/path/audio.m3u8");
49817
+ });
49443
49818
  it("resolves EXT-X-DEFINE:QUERYPARAM from the playlist URL query string", async () => {
49444
49819
  mockRequester.request.and.callFake(
49445
49820
  () => Promise.resolve(
@@ -52073,49 +52448,6 @@ x`);
52073
52448
  expect(track2.clearPrefetch).toHaveBeenCalledTimes(1);
52074
52449
  }
52075
52450
  });
52076
- it("also clears prefetch on preloaded ad tracks", async () => {
52077
- pending(
52078
- "REVIEW: the refactor no longer proactively preloads ad tracks discovered on the current track (ad tracks are created on adEntered only). Decide: intended removal (delete this spec) or regression (restore discovery-time ad preloading)."
52079
- );
52080
- const [main] = createLoadOptionsList(1);
52081
- trackController.load(main);
52082
- const track2 = trackController.activeTrack;
52083
- const ads = {
52084
- trackUri: main.uri,
52085
- adBreaks: [
52086
- {
52087
- id: "b1",
52088
- startTime: 5,
52089
- duration: 10,
52090
- placement: "midroll",
52091
- restrict: {},
52092
- once: false,
52093
- resumeOffset: null,
52094
- playoutLimit: null,
52095
- resolutionTimeOffset: null,
52096
- skipControl: () => null,
52097
- ads: () => Promise.resolve([
52098
- {
52099
- id: "a1",
52100
- startTime: 5,
52101
- duration: 10,
52102
- uri: "https://ads/a.m3u8"
52103
- }
52104
- ])
52105
- }
52106
- ]
52107
- };
52108
- track2.ads = ads;
52109
- track2.dispatch("adsChange", { previous: null, current: ads });
52110
- await Promise.resolve();
52111
- await Promise.resolve();
52112
- await Promise.resolve();
52113
- const adTrack = trackController.getCachedTrack(
52114
- "https://ads/a.m3u8"
52115
- );
52116
- trackController.clearPrefetch();
52117
- expect(adTrack.clearPrefetch).toHaveBeenCalled();
52118
- });
52119
52451
  });
52120
52452
  describe("clearQueue", () => {
52121
52453
  it("clears the queue without unloading the current track", async () => {
@@ -52620,6 +52952,19 @@ x`);
52620
52952
  expect(adTrack).withContext("midroll ad track warmed").toBeDefined();
52621
52953
  expect(adTrack.preload).toHaveBeenCalled();
52622
52954
  });
52955
+ it("clears prefetch on a preloaded ad track", async () => {
52956
+ const [main] = createLoadOptionsList(1);
52957
+ factoryGivingBreak(main.uri, []);
52958
+ trackController.load(main);
52959
+ await flushAds();
52960
+ deps.adController.dispatch("adPreload", {
52961
+ adBreak: midrollBreak("https://ads/mid.m3u8")
52962
+ });
52963
+ await flushAds();
52964
+ const adTrack = created.get("https://ads/mid.m3u8");
52965
+ trackController.clearPrefetch();
52966
+ expect(adTrack.clearPrefetch).toHaveBeenCalledTimes(1);
52967
+ });
52623
52968
  it("ignores adPreload when there is no parent track", () => {
52624
52969
  expect(
52625
52970
  () => deps.adController.dispatch("adPreload", {
@@ -52815,21 +53160,6 @@ x`);
52815
53160
  objectContaining23({ startTime: 3 })
52816
53161
  );
52817
53162
  });
52818
- it("skips the deferred resume if interrupted before the frame", async () => {
52819
- pending(
52820
- "REVIEW: content resume on adBreakCompleted is now synchronous (no deferred/interruptible frame), so this interruption guard no longer applies. Decide: intended or restore deferral."
52821
- );
52822
- const [main] = createLoadOptionsList(1);
52823
- trackController.load(main);
52824
- const changeSpy = createEventSpy(trackController, "trackActivated");
52825
- deps.adController.dispatch("adBreakCompleted", {
52826
- adBreak: adBreak("midroll"),
52827
- resumePosition: 5
52828
- });
52829
- deps.adController.currentAd = __spreadProps(__spreadValues({}, ad), { id: "other" });
52830
- await clock.tick();
52831
- expect(changeSpy).not.toHaveBeenCalled();
52832
- });
52833
53163
  it("advances to the next track after a postroll ad when one exists", async () => {
52834
53164
  var _a3;
52835
53165
  const list = createLoadOptionsList(2);
@@ -55763,13 +56093,15 @@ x`);
55763
56093
  await expectTrackCanSeekTo(player, 5);
55764
56094
  });
55765
56095
  it("can continue seeking", async () => {
55766
- const nextEnded = nextEventAsPromise(player, "ended");
56096
+ let nextEnded = nextEventAsPromise(player, "ended");
55767
56097
  await expectTrackCanSeekTo(player, 999);
55768
56098
  await nextEnded;
55769
56099
  await expectTrackCanSeekTo(player, 0);
55770
56100
  await expectTrackCanSeekTo(player, 5);
55771
56101
  await expectTrackCanSeekTo(player, 15);
55772
56102
  await expectTrackCanSeekTo(player, 35);
56103
+ await player.play();
56104
+ nextEnded = nextEventAsPromise(player, "ended");
55773
56105
  await expectTrackCanSeekTo(player, 999);
55774
56106
  await nextEnded;
55775
56107
  });