@amazon/vinyl 3.2.2 → 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 (92) hide show
  1. package/README.md +2 -2
  2. package/dist/index.cjs +62 -21
  3. package/dist/index.cjs.map +3 -3
  4. package/dist/index.js +65 -22
  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 +184 -171
  10. package/dist/test/integ.js +450 -165
  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/commonEme/MsCommonEme.d.ts.map +1 -1
  16. package/dist/types/current/drm/commonEme/MsCommonEme.js +2 -2
  17. package/dist/types/current/drm/commonEme/StandardCommonEme.d.ts.map +1 -1
  18. package/dist/types/current/drm/commonEme/StandardCommonEme.js +3 -2
  19. package/dist/types/current/drm/commonEme/WebKitCommonEme.d.ts.map +1 -1
  20. package/dist/types/current/drm/commonEme/WebKitCommonEme.js +2 -2
  21. package/dist/types/current/drm/dom/MSMediaKeys.d.ts +1 -1
  22. package/dist/types/current/drm/dom/MSMediaKeys.d.ts.map +1 -1
  23. package/dist/types/current/drm/dom/WebKitMediaKeys.d.ts +1 -1
  24. package/dist/types/current/drm/dom/WebKitMediaKeys.d.ts.map +1 -1
  25. package/dist/types/current/drm/util/extractContentId.js +2 -2
  26. package/dist/types/current/playback/PlaybackControllerImpl.d.ts +1 -1
  27. package/dist/types/current/streaming/MediaQualityMetadata.d.ts +3 -1
  28. package/dist/types/current/streaming/MediaQualityMetadata.d.ts.map +1 -1
  29. package/dist/types/current/streaming/SegmentControllerImpl.d.ts +1 -1
  30. package/dist/types/current/streaming/buffering/BufferingController.d.ts +1 -1
  31. package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
  32. package/dist/types/current/streaming/buffering/BufferingController.js +14 -5
  33. package/dist/types/current/streaming/buffering/MediaSourceController.d.ts.map +1 -1
  34. package/dist/types/current/streaming/buffering/MediaSourceController.js +4 -0
  35. package/dist/types/current/text/discoverDashTextTracks.d.ts +2 -2
  36. package/dist/types/current/text/discoverDashTextTracks.js +4 -3
  37. package/dist/types/current/text/parseWebVtt.d.ts.map +1 -1
  38. package/dist/types/current/text/parseWebVtt.js +3 -2
  39. package/dist/types/current/track/TrackController.d.ts.map +1 -1
  40. package/dist/types/current/track/TrackController.js +7 -0
  41. package/dist/types/current/track/hls/HlsManifestData.d.ts +14 -1
  42. package/dist/types/current/track/hls/HlsManifestData.d.ts.map +1 -1
  43. package/dist/types/current/track/hls/buildHlsMediaTimeline.d.ts.map +1 -1
  44. package/dist/types/current/track/hls/buildHlsMediaTimeline.js +10 -2
  45. package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  46. package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts.map +1 -1
  47. package/dist/types/current/track/hls/createUrlHlsManifestProvider.js +8 -3
  48. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  49. package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +1 -1
  50. package/dist/types/ts3.7/streaming/MediaQualityMetadata.d.ts +3 -1
  51. package/dist/types/ts3.7/streaming/SegmentControllerImpl.d.ts +1 -1
  52. package/dist/types/ts3.7/streaming/buffering/BufferingController.d.ts +1 -1
  53. package/dist/types/ts3.7/text/discoverDashTextTracks.d.ts +2 -2
  54. package/dist/types/ts3.7/track/hls/HlsManifestData.d.ts +14 -1
  55. package/dist/types/ts3.7/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  56. package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +1 -1
  57. package/dist/types/ts4.1/streaming/MediaQualityMetadata.d.ts +3 -1
  58. package/dist/types/ts4.1/streaming/SegmentControllerImpl.d.ts +1 -1
  59. package/dist/types/ts4.1/streaming/buffering/BufferingController.d.ts +1 -1
  60. package/dist/types/ts4.1/text/discoverDashTextTracks.d.ts +2 -2
  61. package/dist/types/ts4.1/track/hls/HlsManifestData.d.ts +14 -1
  62. package/dist/types/ts4.1/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  63. package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +1 -1
  64. package/dist/types/ts4.7/streaming/MediaQualityMetadata.d.ts +3 -1
  65. package/dist/types/ts4.7/streaming/SegmentControllerImpl.d.ts +1 -1
  66. package/dist/types/ts4.7/streaming/buffering/BufferingController.d.ts +1 -1
  67. package/dist/types/ts4.7/text/discoverDashTextTracks.d.ts +2 -2
  68. package/dist/types/ts4.7/track/hls/HlsManifestData.d.ts +14 -1
  69. package/dist/types/ts4.7/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
  70. package/dist/unit/types/current/ad/AdControllerImpl.test.js +66 -0
  71. package/dist/unit/types/current/ad/discoverHlsInterstitials.test.js +29 -2
  72. package/dist/unit/types/current/drm/commonEme/StandardCommonEme.test.js +12 -4
  73. package/dist/unit/types/current/drm/util/extractContentId.test.js +7 -0
  74. package/dist/unit/types/current/index.d.ts +1 -0
  75. package/dist/unit/types/current/index.d.ts.map +1 -1
  76. package/dist/unit/types/current/index.js +1 -0
  77. package/dist/unit/types/current/streaming/MediaQualityMetadata.test.js +8 -0
  78. package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +26 -0
  79. package/dist/unit/types/current/streaming/buffering/MediaSourceController.test.js +29 -0
  80. package/dist/unit/types/current/text/TextTrack.test.d.ts +2 -0
  81. package/dist/unit/types/current/text/TextTrack.test.d.ts.map +1 -0
  82. package/dist/unit/types/current/text/TextTrack.test.js +12 -0
  83. package/dist/unit/types/current/text/discoverDashTextTracks.test.js +17 -0
  84. package/dist/unit/types/current/text/parseWebVtt.test.js +26 -0
  85. package/dist/unit/types/current/track/TrackController.test.js +15 -60
  86. package/dist/unit/types/current/track/hls/buildHlsMediaTimeline.test.js +22 -0
  87. package/dist/unit/types/current/track/hls/createUrlHlsManifestProvider.test.js +23 -0
  88. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  89. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  90. package/docs/CUSTOMIZATION_GUIDE.md +310 -0
  91. package/package.json +10 -10
  92. 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);
@@ -14632,20 +14666,21 @@ ${this.lastInitStack}` : "";
14632
14666
  let abs = Math.abs(value);
14633
14667
  const years = Math.floor(abs / Y);
14634
14668
  abs -= years * Y;
14635
- const months = Math.floor(abs % Y / M);
14669
+ const months = Math.floor(abs / M);
14636
14670
  const days = Math.floor(abs % M / D);
14637
14671
  const hours = Math.floor(abs % D / H);
14638
14672
  const minutes = Math.floor(abs % H / I);
14639
14673
  const seconds = abs % I;
14640
- let isoString = `${sign}P`;
14641
- if (years > 0) isoString += `${years}Y`;
14642
- if (months > 0) isoString += `${months}M`;
14643
- if (days > 0) isoString += `${days}D`;
14644
- isoString += "T";
14645
- if (hours > 0) isoString += `${hours}H`;
14646
- if (minutes > 0) isoString += `${minutes}M`;
14647
- if (seconds > 0 || !value) isoString += `${roundToNearest(seconds, 1e-3)}S`;
14648
- 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}`}`;
14649
14684
  }
14650
14685
 
14651
14686
  // ../vinyl-mpd-parser/src/error/parsingError.ts
@@ -16791,14 +16826,16 @@ ${this.lastInitStack}` : "";
16791
16826
  return;
16792
16827
  }
16793
16828
  const byteLength = streamingSegment.data.byteLength;
16794
- if (byteLength > appendSize) {
16829
+ const remaining = byteLength - this.appendOffset;
16830
+ const firstAppend = this.appendOffset === 0;
16831
+ if (remaining > appendSize) {
16795
16832
  const newOffset = this.appendOffset + appendSize;
16796
16833
  const chunk = streamingSegment.data.slice(
16797
16834
  this.appendOffset,
16798
16835
  newOffset
16799
16836
  );
16800
16837
  await sourceBufferController.append(chunk);
16801
- if (this.appendOffset === 0) {
16838
+ if (firstAppend) {
16802
16839
  this.buffered.push(streamingSegment);
16803
16840
  }
16804
16841
  this.appendOffset = newOffset;
@@ -16807,9 +16844,13 @@ ${this.lastInitStack}` : "";
16807
16844
  `appended chunk, this.appendOffset: ${this.appendOffset}`
16808
16845
  );
16809
16846
  } else {
16810
- await sourceBufferController.append(streamingSegment.data);
16811
- this.appendOffset = streamingSegment.data.byteLength;
16812
- 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
+ }
16813
16854
  }
16814
16855
  if (!tail) this.refreshHead();
16815
16856
  }
@@ -17092,6 +17133,7 @@ ${this.lastInitStack}` : "";
17092
17133
  this.activateDisposer.dispose();
17093
17134
  this.activateDisposer = null;
17094
17135
  ++this.durationToken;
17136
+ this.durationSet = false;
17095
17137
  try {
17096
17138
  URL.revokeObjectURL(this.objectUrl);
17097
17139
  } catch (error) {
@@ -17640,6 +17682,7 @@ ${this.lastInitStack}` : "";
17640
17682
  dispose() {
17641
17683
  this._disposed = true;
17642
17684
  this.callbacks.length = 0;
17685
+ this.n = 0;
17643
17686
  }
17644
17687
  notify(value, previousValue) {
17645
17688
  this._changeId++;
@@ -17755,23 +17798,26 @@ ${this.lastInitStack}` : "";
17755
17798
  providers[key] = provider;
17756
17799
  current[key] = provider.value;
17757
17800
  }
17758
- 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) => {
17759
17807
  const subs = [];
17808
+ let subscribing = true;
17760
17809
  for (const key of keys) {
17761
- const provider = providers[key];
17762
- let isFirst = true;
17763
17810
  subs.push(
17764
- provider.onData((value) => {
17765
- if (isFirst) {
17766
- isFirst = false;
17767
- return;
17768
- }
17811
+ providers[key].onData((value) => {
17769
17812
  current[key] = value;
17770
- setData(__spreadValues({}, current));
17813
+ if (!subscribing) publish(setData);
17771
17814
  })
17772
17815
  );
17773
17816
  }
17774
- setData(current);
17817
+ subscribing = false;
17818
+ if (keys.some((key) => current[key] !== published[key])) {
17819
+ publish(setData);
17820
+ }
17775
17821
  return () => {
17776
17822
  for (const unsub of subs) unsub();
17777
17823
  };
@@ -19194,10 +19240,16 @@ ${this.lastInitStack}` : "";
19194
19240
  return out;
19195
19241
  }
19196
19242
  function isTextAdaptationSet(adaptationSet) {
19243
+ var _a3;
19197
19244
  if (adaptationSet.contentType === "text") return true;
19198
19245
  const mt = adaptationSet.mimeType;
19199
19246
  if (mt && mt.startsWith("text/")) return true;
19200
- 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
+ );
19201
19253
  }
19202
19254
  function isSidecarTextMime(mimeType) {
19203
19255
  if (mimeType == null) return true;
@@ -19455,7 +19507,7 @@ ${this.lastInitStack}` : "";
19455
19507
  const cues = [];
19456
19508
  const styles = [];
19457
19509
  let i = 1;
19458
- while (i < lines.length && lines[i] !== "") i++;
19510
+ while (i < lines.length && lines[i] !== "" && !lines[i].includes("-->")) i++;
19459
19511
  while (i < lines.length) {
19460
19512
  if (lines[i] === "") {
19461
19513
  i++;
@@ -22617,7 +22669,9 @@ ${this.lastInitStack}` : "";
22617
22669
  audioConfig: null,
22618
22670
  videoConfig: null,
22619
22671
  audioDecodeTime: 0,
22620
- videoDecodeTime: 0
22672
+ videoDecodeTime: 0,
22673
+ audioTrackId: null,
22674
+ videoTrackId: null
22621
22675
  };
22622
22676
  return { transmux: (data2) => transmux(state, data2) };
22623
22677
  }
@@ -22672,11 +22726,12 @@ ${this.lastInitStack}` : "";
22672
22726
  sampleRate: sampleRate2,
22673
22727
  audioSpecificConfig: buildAudioSpecificConfig(firstFrame)
22674
22728
  };
22729
+ state.audioTrackId = 1;
22675
22730
  buildInitSegment(
22676
22731
  state,
22677
22732
  moov(
22678
22733
  audioTrak({
22679
- trackId: 1,
22734
+ trackId: state.audioTrackId,
22680
22735
  sampleRate: state.audioConfig.sampleRate,
22681
22736
  channelCount: state.audioConfig.channelConfiguration,
22682
22737
  sampleEntry: mp4aSampleEntry(state.audioConfig)
@@ -22703,7 +22758,7 @@ ${this.lastInitStack}` : "";
22703
22758
  const duration = frames.length * samplesPerFrame / sampleRate;
22704
22759
  const moofBox = moof({
22705
22760
  sequenceNumber: state.sequenceNumber++,
22706
- trackId: 1,
22761
+ trackId: state.audioTrackId,
22707
22762
  baseDecodeTime: state.audioDecodeTime,
22708
22763
  samples
22709
22764
  });
@@ -22809,9 +22864,10 @@ ${this.lastInitStack}` : "";
22809
22864
  if (!state.initSegment && (audioConfig || videoConfig)) {
22810
22865
  const traks = [];
22811
22866
  if (videoConfig) {
22867
+ state.videoTrackId = traks.length + 1;
22812
22868
  traks.push(
22813
22869
  videoTrak({
22814
- trackId: traks.length + 1,
22870
+ trackId: state.videoTrackId,
22815
22871
  width: videoConfig.width,
22816
22872
  height: videoConfig.height,
22817
22873
  sampleEntry: avc1SampleEntry(videoConfig)
@@ -22819,9 +22875,10 @@ ${this.lastInitStack}` : "";
22819
22875
  );
22820
22876
  }
22821
22877
  if (audioConfig) {
22878
+ state.audioTrackId = traks.length + 1;
22822
22879
  traks.push(
22823
22880
  audioTrak({
22824
- trackId: traks.length + 1,
22881
+ trackId: state.audioTrackId,
22825
22882
  sampleRate: audioConfig.sampleRate,
22826
22883
  channelCount: audioConfig.channelConfiguration,
22827
22884
  sampleEntry: mp4aSampleEntry(audioConfig)
@@ -22832,8 +22889,8 @@ ${this.lastInitStack}` : "";
22832
22889
  }
22833
22890
  const fragments = [];
22834
22891
  let duration = 0;
22835
- if (videoConfig && videoNalus.length > 0) {
22836
- const trackId = 1;
22892
+ if (videoConfig && videoNalus.length > 0 && state.videoTrackId != null) {
22893
+ const trackId = state.videoTrackId;
22837
22894
  const accessUnits = [];
22838
22895
  let pendingParams = [];
22839
22896
  for (const nalu of videoNalus) {
@@ -22900,8 +22957,8 @@ ${this.lastInitStack}` : "";
22900
22957
  duration = accessUnits.length * frameDuration / MPEG_TIMESCALE;
22901
22958
  }
22902
22959
  }
22903
- if (audioConfig && audioRawData && audioFrames.length > 0) {
22904
- const trackId = videoConfig ? 2 : 1;
22960
+ if (audioConfig && audioRawData && audioFrames.length > 0 && state.audioTrackId != null) {
22961
+ const trackId = state.audioTrackId;
22905
22962
  const samplesPerFrame = 1024;
22906
22963
  const samples = [];
22907
22964
  const parts = [];
@@ -23201,7 +23258,11 @@ ${this.lastInitStack}` : "";
23201
23258
  async getSegment(time, affordance = 0) {
23202
23259
  var _a3;
23203
23260
  const playlist = await data2.getMediaPlaylist(playlistUri);
23204
- const playlistBaseUrl = resolveUrl(playlistUri, baseUrl);
23261
+ const playlistBaseUrl = getPlaylistBaseUrl(
23262
+ playlist,
23263
+ playlistUri,
23264
+ baseUrl
23265
+ );
23205
23266
  const segments = buildSegmentTimeline(
23206
23267
  deps,
23207
23268
  playlistBaseUrl,
@@ -23248,9 +23309,13 @@ ${this.lastInitStack}` : "";
23248
23309
  const variant2 = data2.mainPlaylist.variants[0];
23249
23310
  const media = await data2.getMediaPlaylist(variant2.uri);
23250
23311
  const contentDuration = media.ended ? media.segments.reduce((sum, s) => sum + s.duration, 0) : null;
23251
- const playlistBaseUrl = resolveUrl(variant2.uri, data2.baseUrl);
23312
+ const playlistBaseUrl = getPlaylistBaseUrl(media, variant2.uri, data2.baseUrl);
23252
23313
  return discoverHlsInterstitials(media, playlistBaseUrl, contentDuration);
23253
23314
  }
23315
+ function getPlaylistBaseUrl(playlist, playlistUri, mainBaseUrl) {
23316
+ var _a3;
23317
+ return (_a3 = playlist.baseUrl) != null ? _a3 : resolveUrl(playlistUri, mainBaseUrl);
23318
+ }
23254
23319
 
23255
23320
  // src/track/hls/createDefaultHlsManifestTransformer.ts
23256
23321
  function createDefaultHlsManifestTransformer(deps) {
@@ -23383,8 +23448,11 @@ ${this.lastInitStack}` : "";
23383
23448
  const variantUrl = resolveUrl(uri, baseUrl);
23384
23449
  const response = await requestWithRetry(variantUrl, requestInit, { abort });
23385
23450
  const text = await readResponseBody(response, "text");
23386
- const queryParams = parseQueryParams(response.url || variantUrl);
23387
- 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
+ });
23388
23456
  }
23389
23457
  function parseQueryParams(url) {
23390
23458
  const queryStart = url.indexOf("?");
@@ -23836,6 +23904,9 @@ ${this.lastInitStack}` : "";
23836
23904
  for (const cachedTrack of this.trackCache.values()) {
23837
23905
  cachedTrack.clearPrefetch();
23838
23906
  }
23907
+ for (const adTracks of this.adTracksByParent.values()) {
23908
+ for (const adTrack of adTracks.values()) adTrack.clearPrefetch();
23909
+ }
23839
23910
  }
23840
23911
  clearQueue() {
23841
23912
  this.setQueue(this._current, []);
@@ -27307,6 +27378,47 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
27307
27378
  await flush();
27308
27379
  expect(c.currentAdBreak).toBeNull();
27309
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
+ });
27310
27422
  });
27311
27423
  describe("a break with no ads", () => {
27312
27424
  it("completes immediately with a resume position and plays no ad", async () => {
@@ -27587,6 +27699,35 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
27587
27699
  await flush();
27588
27700
  expect(thirds).toEqual(["a1"]);
27589
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
+ });
27590
27731
  it("does not emit quartiles or throw when the ad duration is zero", async () => {
27591
27732
  const c = createController();
27592
27733
  const events = [];
@@ -28630,8 +28771,9 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
28630
28771
  clientAttributes: {}
28631
28772
  }, overrides);
28632
28773
  }
28633
- function okJsonResponse(body) {
28774
+ function okJsonResponse(body, url = "") {
28634
28775
  return {
28776
+ url,
28635
28777
  ok: true,
28636
28778
  status: 200,
28637
28779
  headers: { get: () => null },
@@ -28800,7 +28942,7 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
28800
28942
  "https://example.com/ads.json"
28801
28943
  );
28802
28944
  expect(ads.length).toBe(2);
28803
- expect(ads[0].uri).toBe("https://cdn.example.com/media/mid1.m3u8");
28945
+ expect(ads[0].uri).toBe("https://example.com/mid1.m3u8");
28804
28946
  expect(ads[0].duration).toBe(10);
28805
28947
  expect(ads[1].uri).toBe("https://cdn.example.com/mid2.m3u8");
28806
28948
  expect(ads[1].duration).toBeNull();
@@ -28808,6 +28950,33 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
28808
28950
  globalThis.fetch = origFetch;
28809
28951
  }
28810
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
+ });
28811
28980
  it("caches the X-ASSET-LIST fetch across calls", async () => {
28812
28981
  const origFetch = globalThis.fetch;
28813
28982
  const fetchSpy = jasmine.createSpy("fetch").and.resolveTo(okJsonResponse({ ASSETS: [{ URI: "x.m3u8" }] }));
@@ -30185,10 +30354,10 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
30185
30354
  });
30186
30355
  });
30187
30356
  describe("when created", () => {
30188
- it("calls session.generateRequest() with the initData buffer", () => {
30357
+ it("calls session.generateRequest() with the initData", () => {
30189
30358
  expect(
30190
30359
  mediaKeySession.generateRequest
30191
- ).toHaveBeenCalledOnceWith("cenc", initData.buffer);
30360
+ ).toHaveBeenCalledOnceWith("cenc", initData);
30192
30361
  mediaKeySession.generateRequest.calls.reset();
30193
30362
  const arrayBuffer = new ArrayBuffer(0);
30194
30363
  standardCommonMediaKeySession = new StandardCommonMediaKeySession(
@@ -30201,6 +30370,21 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
30201
30370
  mediaKeySession.generateRequest
30202
30371
  ).toHaveBeenCalledOnceWith("cenc", arrayBuffer);
30203
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
+ });
30204
30388
  describe("when session.generateRequest rejects", () => {
30205
30389
  it("emits an error event", async () => {
30206
30390
  mediaKeySession.generateRequest.and.rejectWith(
@@ -32658,6 +32842,13 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
32658
32842
  ).toBe("example0");
32659
32843
  });
32660
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
+ });
32661
32852
  describe("without match", () => {
32662
32853
  it("throws ValidationError", () => {
32663
32854
  expect(() => {
@@ -35951,6 +36142,24 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
35951
36142
  expect(sourceBufferController.append).toHaveBeenCalledTimes(1);
35952
36143
  });
35953
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
+ });
35954
36163
  describe("when contentType is undefined", () => {
35955
36164
  it("uses DEFAULT_MAX_APPEND_SIZE for max byte length", async () => {
35956
36165
  setSegmentList(
@@ -36712,6 +36921,28 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
36712
36921
  );
36713
36922
  expect(() => controller.deactivate()).not.toThrow();
36714
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
+ });
36715
36946
  });
36716
36947
  describe("duration", () => {
36717
36948
  it("sets media source duration after sourceopen", async () => {
@@ -38474,6 +38705,11 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
38474
38705
  expect(drmProtectionValidator.isValid({ keySystem: 123 })).toBeFalse();
38475
38706
  });
38476
38707
  });
38708
+ describe("ALL_CONTENT_TYPES", () => {
38709
+ it("provides a comprehensive list of all content types", () => {
38710
+ expectTypeStrictlyEquals(true);
38711
+ });
38712
+ });
38477
38713
 
38478
38714
  // test/unit/streaming/MediaTimeline.test.ts
38479
38715
  describe("getMediaPeriodAtTime", () => {
@@ -41230,6 +41466,18 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
41230
41466
  expect(result.length).toBe(1);
41231
41467
  expect(result[0].language).toBe("es");
41232
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
+ });
41233
41481
  it("classifies kind=captions when role=caption is set", () => {
41234
41482
  const m = manifest3(`
41235
41483
  <AdaptationSet contentType="text" lang="en" mimeType="text/vtt">
@@ -41284,6 +41532,10 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
41284
41532
  </AdaptationSet>`);
41285
41533
  expect(discoverDashTextTracks(m, baseUrl)).toEqual([]);
41286
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
+ });
41287
41539
  it("skips text AdaptationSets carrying segmented codecs (stpp/wvtt)", () => {
41288
41540
  const m = manifest3(`
41289
41541
  <AdaptationSet contentType="text" mimeType="application/mp4" codecs="stpp">
@@ -42315,6 +42567,29 @@ hi`;
42315
42567
  expect(cues.length).toBe(1);
42316
42568
  expect(cues[0].text).toBe("hi");
42317
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
+ });
42318
42593
  it("handles trailing identifier without timing line", () => {
42319
42594
  const text = `WEBVTT
42320
42595
 
@@ -43704,6 +43979,13 @@ x`);
43704
43979
  });
43705
43980
  });
43706
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
+
43707
43989
  // test/unit/text/VttCueStyle.test.ts
43708
43990
  describe("VttCueStyle", () => {
43709
43991
  describe("isVttCue", () => {
@@ -48810,6 +49092,29 @@ x`);
48810
49092
  );
48811
49093
  fetchSpy.and.callThrough();
48812
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
+ });
48813
49118
  it("getDuration throws when no variants", async () => {
48814
49119
  const manifestData2 = createManifestData([], createMediaPlaylist([]));
48815
49120
  const timeline = buildHlsMediaTimeline(deps, manifestData2);
@@ -49487,6 +49792,29 @@ x`);
49487
49792
  "https://example.com/path/audio.m3u8"
49488
49793
  );
49489
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
+ });
49490
49818
  it("resolves EXT-X-DEFINE:QUERYPARAM from the playlist URL query string", async () => {
49491
49819
  mockRequester.request.and.callFake(
49492
49820
  () => Promise.resolve(
@@ -52120,49 +52448,6 @@ x`);
52120
52448
  expect(track2.clearPrefetch).toHaveBeenCalledTimes(1);
52121
52449
  }
52122
52450
  });
52123
- it("also clears prefetch on preloaded ad tracks", async () => {
52124
- pending(
52125
- "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)."
52126
- );
52127
- const [main] = createLoadOptionsList(1);
52128
- trackController.load(main);
52129
- const track2 = trackController.activeTrack;
52130
- const ads = {
52131
- trackUri: main.uri,
52132
- adBreaks: [
52133
- {
52134
- id: "b1",
52135
- startTime: 5,
52136
- duration: 10,
52137
- placement: "midroll",
52138
- restrict: {},
52139
- once: false,
52140
- resumeOffset: null,
52141
- playoutLimit: null,
52142
- resolutionTimeOffset: null,
52143
- skipControl: () => null,
52144
- ads: () => Promise.resolve([
52145
- {
52146
- id: "a1",
52147
- startTime: 5,
52148
- duration: 10,
52149
- uri: "https://ads/a.m3u8"
52150
- }
52151
- ])
52152
- }
52153
- ]
52154
- };
52155
- track2.ads = ads;
52156
- track2.dispatch("adsChange", { previous: null, current: ads });
52157
- await Promise.resolve();
52158
- await Promise.resolve();
52159
- await Promise.resolve();
52160
- const adTrack = trackController.getCachedTrack(
52161
- "https://ads/a.m3u8"
52162
- );
52163
- trackController.clearPrefetch();
52164
- expect(adTrack.clearPrefetch).toHaveBeenCalled();
52165
- });
52166
52451
  });
52167
52452
  describe("clearQueue", () => {
52168
52453
  it("clears the queue without unloading the current track", async () => {
@@ -52667,6 +52952,19 @@ x`);
52667
52952
  expect(adTrack).withContext("midroll ad track warmed").toBeDefined();
52668
52953
  expect(adTrack.preload).toHaveBeenCalled();
52669
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
+ });
52670
52968
  it("ignores adPreload when there is no parent track", () => {
52671
52969
  expect(
52672
52970
  () => deps.adController.dispatch("adPreload", {
@@ -52862,21 +53160,6 @@ x`);
52862
53160
  objectContaining23({ startTime: 3 })
52863
53161
  );
52864
53162
  });
52865
- it("skips the deferred resume if interrupted before the frame", async () => {
52866
- pending(
52867
- "REVIEW: content resume on adBreakCompleted is now synchronous (no deferred/interruptible frame), so this interruption guard no longer applies. Decide: intended or restore deferral."
52868
- );
52869
- const [main] = createLoadOptionsList(1);
52870
- trackController.load(main);
52871
- const changeSpy = createEventSpy(trackController, "trackActivated");
52872
- deps.adController.dispatch("adBreakCompleted", {
52873
- adBreak: adBreak("midroll"),
52874
- resumePosition: 5
52875
- });
52876
- deps.adController.currentAd = __spreadProps(__spreadValues({}, ad), { id: "other" });
52877
- await clock.tick();
52878
- expect(changeSpy).not.toHaveBeenCalled();
52879
- });
52880
53163
  it("advances to the next track after a postroll ad when one exists", async () => {
52881
53164
  var _a3;
52882
53165
  const list = createLoadOptionsList(2);
@@ -55810,13 +56093,15 @@ x`);
55810
56093
  await expectTrackCanSeekTo(player, 5);
55811
56094
  });
55812
56095
  it("can continue seeking", async () => {
55813
- const nextEnded = nextEventAsPromise(player, "ended");
56096
+ let nextEnded = nextEventAsPromise(player, "ended");
55814
56097
  await expectTrackCanSeekTo(player, 999);
55815
56098
  await nextEnded;
55816
56099
  await expectTrackCanSeekTo(player, 0);
55817
56100
  await expectTrackCanSeekTo(player, 5);
55818
56101
  await expectTrackCanSeekTo(player, 15);
55819
56102
  await expectTrackCanSeekTo(player, 35);
56103
+ await player.play();
56104
+ nextEnded = nextEventAsPromise(player, "ended");
55820
56105
  await expectTrackCanSeekTo(player, 999);
55821
56106
  await nextEnded;
55822
56107
  });