@amazon/vinyl 3.1.0 → 3.2.1

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 (69) hide show
  1. package/dist/index.cjs +236 -187
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +224 -174
  4. package/dist/index.js.map +4 -4
  5. package/dist/test/integ.es5.js +245 -231
  6. package/dist/test/integ.js +676 -271
  7. package/dist/test/integ.js.map +4 -4
  8. package/dist/types/current/ad/AdControllerImpl.d.ts +18 -2
  9. package/dist/types/current/ad/AdControllerImpl.d.ts.map +1 -1
  10. package/dist/types/current/ad/AdControllerImpl.js +42 -20
  11. package/dist/types/current/drm/DrmControllerImpl.d.ts +17 -1
  12. package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
  13. package/dist/types/current/drm/DrmControllerImpl.js +45 -10
  14. package/dist/types/current/playback/createStallMonitor.d.ts.map +1 -1
  15. package/dist/types/current/playback/createStallMonitor.js +19 -14
  16. package/dist/types/current/streaming/LoadMetric.d.ts +15 -1
  17. package/dist/types/current/streaming/LoadMetric.d.ts.map +1 -1
  18. package/dist/types/current/streaming/mediaTimelineFilter.d.ts +4 -2
  19. package/dist/types/current/streaming/mediaTimelineFilter.d.ts.map +1 -1
  20. package/dist/types/current/streaming/mediaTimelineFilter.js +23 -8
  21. package/dist/types/current/track/filters/sampleRateFilter.d.ts +8 -4
  22. package/dist/types/current/track/filters/sampleRateFilter.d.ts.map +1 -1
  23. package/dist/types/current/track/filters/sampleRateFilter.js +31 -21
  24. package/dist/types/current/track/mse/MseTrack.d.ts +0 -9
  25. package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
  26. package/dist/types/current/track/mse/MseTrack.js +2 -29
  27. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  28. package/dist/types/current/vinyl/VinylPlayer.d.ts +5 -0
  29. package/dist/types/current/vinyl/VinylPlayer.d.ts.map +1 -1
  30. package/dist/types/current/vinyl/VinylPlayer.js +23 -1
  31. package/dist/types/ts3.7/ad/AdControllerImpl.d.ts +18 -2
  32. package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +17 -1
  33. package/dist/types/ts3.7/streaming/LoadMetric.d.ts +15 -1
  34. package/dist/types/ts3.7/streaming/mediaTimelineFilter.d.ts +4 -2
  35. package/dist/types/ts3.7/track/filters/sampleRateFilter.d.ts +8 -4
  36. package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -9
  37. package/dist/types/ts3.7/vinyl/VinylPlayer.d.ts +5 -0
  38. package/dist/types/ts4.1/ad/AdControllerImpl.d.ts +18 -2
  39. package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +17 -1
  40. package/dist/types/ts4.1/streaming/LoadMetric.d.ts +15 -1
  41. package/dist/types/ts4.1/streaming/mediaTimelineFilter.d.ts +4 -2
  42. package/dist/types/ts4.1/track/filters/sampleRateFilter.d.ts +8 -4
  43. package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -9
  44. package/dist/types/ts4.1/vinyl/VinylPlayer.d.ts +5 -0
  45. package/dist/types/ts4.7/ad/AdControllerImpl.d.ts +18 -2
  46. package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +17 -1
  47. package/dist/types/ts4.7/streaming/LoadMetric.d.ts +15 -1
  48. package/dist/types/ts4.7/streaming/mediaTimelineFilter.d.ts +4 -2
  49. package/dist/types/ts4.7/track/filters/sampleRateFilter.d.ts +8 -4
  50. package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -9
  51. package/dist/types/ts4.7/vinyl/VinylPlayer.d.ts +5 -0
  52. package/dist/unit/types/current/ad/AdControllerImpl.test.js +110 -4
  53. package/dist/unit/types/current/drm/DrmControllerImpl.test.js +53 -9
  54. package/dist/unit/types/current/playback/createStallMonitor.test.js +38 -0
  55. package/dist/unit/types/current/streaming/MseTrack.test.js +11 -8
  56. package/dist/unit/types/current/streaming/mediaTimelineFilter.test.js +60 -54
  57. package/dist/unit/types/current/track/filters/sampleRateFilter.test.js +75 -0
  58. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  59. package/dist/unit/types/current/vinyl/VinylPlayer.test.js +33 -2
  60. package/dist/vinylTestUtil/index.js +10 -1
  61. package/dist/vinylTestUtil/index.js.map +2 -2
  62. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts +5 -0
  63. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts.map +1 -1
  64. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.js +9 -0
  65. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  66. package/dist/vinylTestUtil/types/ts3.7/assets/vinylTestAssets.d.ts +5 -0
  67. package/dist/vinylTestUtil/types/ts4.1/assets/vinylTestAssets.d.ts +5 -0
  68. package/dist/vinylTestUtil/types/ts4.7/assets/vinylTestAssets.d.ts +5 -0
  69. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -68,6 +68,7 @@ var AdError = class _AdError extends ReportableError {
68
68
 
69
69
  // src/ad/AdControllerImpl.ts
70
70
  var AD_START_TOLERANCE = 0.05;
71
+ var PLAYOUT_LIMIT_TOLERANCE = 1;
71
72
  var defaultAdControllerImplOptions = {
72
73
  adLoadTimeout: 15,
73
74
  preloadAheadTime: 10
@@ -558,23 +559,29 @@ var AdControllerImpl = class extends EventHostImpl {
558
559
  });
559
560
  }
560
561
  /**
561
- * Ads breaks can have a playout limit that may cut off ad playback.
562
- * End the ad if that limit is reached.
562
+ * Enforces the break-level playout limit (X-PLAYOUT-LIMIT): a budget shared
563
+ * across all of the break's ads. When cumulative playout reaches it, the
564
+ * whole break ends, dropping any ads that have not started.
565
+ *
566
+ * This is deliberately NOT a per-ad cap. Each ad asset is its own source and
567
+ * ends on its media `ended` (see the 'ended' handler), which is what advances
568
+ * a multi-ad break. Ending an ad at its declared per-ad DURATION instead would
569
+ * truncate the real asset whenever a packager under-declares it — e.g. rounds
570
+ * a 6.732s asset down to a whole-second 6 — cutting off the fractional tail.
563
571
  */
564
572
  enforcePlayoutLimits(adState) {
573
+ if (!adState.started) return;
574
+ const { parentBreak } = adState;
575
+ const limit = parentBreak.adBreak.playoutLimit;
576
+ if (limit == null) return;
565
577
  const pC = this.deps.playbackController;
566
- const { ad, parentBreak } = adState;
567
- if (adState.started) {
568
- const elapsed = Math.max(0, pC.currentTime - adState.timeStart);
569
- const adBreak = parentBreak.adBreak;
570
- const limit = adBreak.playoutLimit;
571
- if (limit != null && parentBreak.playoutElapsed + elapsed >= limit) {
572
- parentBreak.clear();
573
- this.endAd(adState);
574
- } else if (ad.duration != null && elapsed >= ad.duration) {
575
- this.endAd(adState);
576
- }
578
+ const elapsed = Math.max(0, pC.currentTime - adState.timeStart);
579
+ if (parentBreak.playoutElapsed + elapsed < limit) return;
580
+ if (isFinite(pC.duration) && pC.duration > 0 && pC.duration - pC.currentTime <= PLAYOUT_LIMIT_TOLERANCE) {
581
+ return;
577
582
  }
583
+ parentBreak.clear();
584
+ this.endAd(adState);
578
585
  }
579
586
  setPendingBreaks(newPending) {
580
587
  this.pendingAdBreaks = newPending.filter(
@@ -1892,6 +1899,10 @@ var DrmControllerImpl = class extends EventHostImpl5 {
1892
1899
  __publicField(this, "bufferingTrackUri", null);
1893
1900
  __publicField(this, "sessionAbort", null);
1894
1901
  __publicField(this, "pendingMessageProps", null);
1902
+ // Armed per track load; latches on the first session so only the
1903
+ // critical-path session's EME/CDM setup is measured.
1904
+ __publicField(this, "keySetupSpanArmed", true);
1905
+ __publicField(this, "keySetupSpanStart", null);
1895
1906
  __publicField(this, "disposer", /* @__PURE__ */ createDisposer2());
1896
1907
  __publicField(this, "checkIsSupportedCached", /* @__PURE__ */ memoize4(
1897
1908
  async (params) => {
@@ -2057,6 +2068,7 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2057
2068
  if (!hasMimeType(drmInfo)) {
2058
2069
  throw new DrmError("Encrypted content must have a mimeType.");
2059
2070
  }
2071
+ this.markKeySetupStart();
2060
2072
  const mediaKeys = await this.attachMediaKeys(drmInfo);
2061
2073
  this.abortIfDisposed();
2062
2074
  const keySystemOptions = (_a = this.options.keySystems[mediaKeys.keySystem]) != null ? _a : this.options.keySystems["*"];
@@ -2101,6 +2113,28 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2101
2113
  });
2102
2114
  return newSession;
2103
2115
  }
2116
+ /**
2117
+ * Captures the key-setup span start at the top of the session-creation path
2118
+ * (before media keys are attached), latching so only the first session of
2119
+ * the current track load is measured.
2120
+ */
2121
+ markKeySetupStart() {
2122
+ if (!this.keySetupSpanArmed) return;
2123
+ this.keySetupSpanArmed = false;
2124
+ this.keySetupSpanStart = Date.now();
2125
+ }
2126
+ dispatchLoadSpan(kind, startTime, endTime, messageProps) {
2127
+ this.dispatch("loadSpanMeasured", __spreadValues(__spreadValues({
2128
+ kind,
2129
+ startTime,
2130
+ endTime,
2131
+ mimeType: messageProps.mimeType
2132
+ }, messageProps.trackUri != null && {
2133
+ trackUri: messageProps.trackUri
2134
+ }), messageProps.contentType != null && {
2135
+ contentType: messageProps.contentType
2136
+ }));
2137
+ }
2104
2138
  initializeForPlayback(drmInfo, options) {
2105
2139
  var _a;
2106
2140
  logDebug3(this, "initializeForPlayback", drmInfo);
@@ -2124,6 +2158,8 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2124
2158
  this.drmInfo = drmInfo;
2125
2159
  this.bufferingTrackUri = (_a = options == null ? void 0 : options.trackUri) != null ? _a : null;
2126
2160
  this.sessionAbort = (_b = options == null ? void 0 : options.abort) != null ? _b : null;
2161
+ this.keySetupSpanArmed = true;
2162
+ this.keySetupSpanStart = null;
2127
2163
  }
2128
2164
  /**
2129
2165
  * If the drm metadata contains initialization data for the current key system, create a new session.
@@ -2135,6 +2171,7 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2135
2171
  this.handleError(new DrmError("DRM not supported."));
2136
2172
  return;
2137
2173
  }
2174
+ this.markKeySetupStart();
2138
2175
  const mediaKeys = await this.attachMediaKeys(drmInfo);
2139
2176
  const drmProtection = (_a = drmInfo.contentProtections.find((cP) => {
2140
2177
  return cP.keySystem === mediaKeys.keySystem;
@@ -2273,7 +2310,9 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2273
2310
  event,
2274
2311
  // Captured per session, so a concurrent preloaded track's
2275
2312
  // license exchange keeps its own attribution.
2276
- trackUri
2313
+ trackUri,
2314
+ mimeType: drmInfo.mimeType,
2315
+ contentType: drmInfo.contentType
2277
2316
  }),
2278
2317
  LICENSE_TIMEOUT,
2279
2318
  { message: LICENSE_TIMEOUT_MESSAGE }
@@ -2295,6 +2334,15 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2295
2334
  this.pendingMessageProps = messageProps;
2296
2335
  const keySystem = mediaKeys.keySystem;
2297
2336
  logDebug3(this, "message", keySystem, event.message.byteLength);
2337
+ if (this.keySetupSpanStart != null) {
2338
+ this.dispatchLoadSpan(
2339
+ "keySetup",
2340
+ this.keySetupSpanStart,
2341
+ Date.now(),
2342
+ messageProps
2343
+ );
2344
+ this.keySetupSpanStart = null;
2345
+ }
2298
2346
  const keySystemOptions = (_a = this.options.keySystems[keySystem]) != null ? _a : this.options.keySystems["*"];
2299
2347
  const licenseServerOptionsProvider = keySystemOptions == null ? void 0 : keySystemOptions.licenseServer;
2300
2348
  const licenseServerOptions = (_b = clone(await resolveValueProvider2(licenseServerOptionsProvider))) != null ? _b : {};
@@ -2316,13 +2364,12 @@ var DrmControllerImpl = class extends EventHostImpl5 {
2316
2364
  challenge
2317
2365
  );
2318
2366
  if (this.disposed) return;
2319
- this.dispatch("loadSpanMeasured", __spreadValues({
2320
- kind: "license",
2321
- startTime: licenseSpanStart,
2322
- endTime: Date.now()
2323
- }, messageProps.trackUri != null && {
2324
- trackUri: messageProps.trackUri
2325
- }));
2367
+ this.dispatchLoadSpan(
2368
+ "license",
2369
+ licenseSpanStart,
2370
+ Date.now(),
2371
+ messageProps
2372
+ );
2326
2373
  this.pendingMessageProps = null;
2327
2374
  if (session.disposed) return;
2328
2375
  logDebug3(this, "update");
@@ -2581,7 +2628,7 @@ function configureVinylGlobal(options) {
2581
2628
  import { parseVersion } from "@amazon/vinyl-util";
2582
2629
  var vinylVersion = /* @__PURE__ */ parseVersion(
2583
2630
  /* istanbul ignore next */
2584
- "3.1.0"
2631
+ "3.2.1"
2585
2632
  );
2586
2633
 
2587
2634
  // src/global/globalRef.ts
@@ -2786,6 +2833,7 @@ var PlaybackNetworkState = /* @__PURE__ */ ((PlaybackNetworkState2) => {
2786
2833
  })(PlaybackNetworkState || {});
2787
2834
 
2788
2835
  // src/playback/createStallMonitor.ts
2836
+ var PROGRESS_EPSILON = 1e-6;
2789
2837
  function createStallMonitor(controller, callbacks, options = {}) {
2790
2838
  var _a, _b;
2791
2839
  const stallThresholdMs = ((_a = options.stallThreshold) != null ? _a : 1) * 1e3;
@@ -2794,6 +2842,7 @@ function createStallMonitor(controller, callbacks, options = {}) {
2794
2842
  let lastProgressAt = Date.now();
2795
2843
  let inStall = false;
2796
2844
  let pollId = null;
2845
+ const { add, dispose } = createDisposer3();
2797
2846
  const endStall = (reason) => {
2798
2847
  if (!inStall) return;
2799
2848
  inStall = false;
@@ -2810,6 +2859,7 @@ function createStallMonitor(controller, callbacks, options = {}) {
2810
2859
  clearInterval(pollId);
2811
2860
  pollId = null;
2812
2861
  };
2862
+ add(stopPoll);
2813
2863
  const startPoll = () => {
2814
2864
  if (pollId != null) return;
2815
2865
  pollId = setInterval(() => {
@@ -2825,17 +2875,18 @@ function createStallMonitor(controller, callbacks, options = {}) {
2825
2875
  observedPlaying = false;
2826
2876
  stopPoll();
2827
2877
  };
2828
- const { add, dispose } = createDisposer3();
2829
2878
  add(
2830
2879
  controller.on("playing", () => {
2831
2880
  if (inStall) endStall("playing" /* PLAYING */);
2832
- else lastProgressAt = Date.now();
2881
+ lastProgressAt = Date.now();
2833
2882
  observedPlaying = true;
2834
2883
  startPoll();
2835
2884
  })
2836
2885
  );
2837
2886
  add(
2838
- controller.on("timeUpdate", () => {
2887
+ controller.on("timeUpdate", ({ previous, current }) => {
2888
+ if (previous != null && current - previous <= PROGRESS_EPSILON)
2889
+ return;
2839
2890
  if (inStall) endStall("playing" /* PLAYING */);
2840
2891
  lastProgressAt = Date.now();
2841
2892
  })
@@ -2843,13 +2894,7 @@ function createStallMonitor(controller, callbacks, options = {}) {
2843
2894
  add(controller.on("pause", () => suspend("pause" /* PAUSE */)));
2844
2895
  add(controller.on("seeking", () => suspend("seeking" /* SEEKING */)));
2845
2896
  add(controller.on("emptied", () => suspend("emptied" /* EMPTIED */)));
2846
- let stopped = false;
2847
- return () => {
2848
- if (stopped) return;
2849
- stopped = true;
2850
- stopPoll();
2851
- dispose();
2852
- };
2897
+ return dispose;
2853
2898
  }
2854
2899
 
2855
2900
  // src/playback/error/InvalidSeekError.ts
@@ -5013,6 +5058,107 @@ function filterContentTypes(contentTypes, allowedContentTypes) {
5013
5058
  // src/streaming/createDefaultMediaTimelineTransformer.ts
5014
5059
  import { combineData as combineData2 } from "@amazon/vinyl-observable";
5015
5060
 
5061
+ // src/streaming/MediaQualityMetadata.ts
5062
+ import {
5063
+ createShortUid
5064
+ } from "@amazon/vinyl-util";
5065
+ import {
5066
+ array as array3,
5067
+ enumOf as enumOf3,
5068
+ isOneOf as isOneOf3,
5069
+ number as number6,
5070
+ object as object9,
5071
+ string as string6,
5072
+ tuple
5073
+ } from "@amazon/vinyl-validation";
5074
+
5075
+ // src/streaming/DescriptorRecord.ts
5076
+ import {
5077
+ array as array2,
5078
+ object as object8,
5079
+ record as record2,
5080
+ string as string5
5081
+ } from "@amazon/vinyl-validation";
5082
+ import { first } from "@amazon/vinyl-util";
5083
+ function getDescriptorValue(record4, uri, id) {
5084
+ var _a, _b;
5085
+ let arr = record4[uri];
5086
+ if (!arr) return null;
5087
+ if (id !== void 0) arr = arr.filter((descriptor) => descriptor.id === id);
5088
+ return (_b = (_a = first(arr)) == null ? void 0 : _a.value) != null ? _b : null;
5089
+ }
5090
+ var descriptorValidator = /* @__PURE__ */ object8({
5091
+ id: /* @__PURE__ */ string5().maybe().optional(),
5092
+ value: /* @__PURE__ */ string5().maybe().optional()
5093
+ });
5094
+ var descriptorRecordValidator = /* @__PURE__ */ record2(/* @__PURE__ */ string5(), /* @__PURE__ */ array2(descriptorValidator).readonly().orUndefined());
5095
+
5096
+ // src/streaming/MediaQualityMetadata.ts
5097
+ var ALL_CONTENT_TYPES = [
5098
+ "video",
5099
+ "audio",
5100
+ "text"
5101
+ ];
5102
+ var RESTRICTABLE_CONTENT_TYPES = [
5103
+ "video",
5104
+ "audio"
5105
+ ];
5106
+ var drmProtectionValidator = /* @__PURE__ */ descriptorValidator.extend({
5107
+ keySystem: /* @__PURE__ */ enumOf3(DrmKeySystem),
5108
+ pro: /* @__PURE__ */ string6().maybe().optional(),
5109
+ pssh: /* @__PURE__ */ string6().maybe().optional()
5110
+ });
5111
+ var mediaFormatMetadataValidator = /* @__PURE__ */ object9({
5112
+ contentProtections: /* @__PURE__ */ array3(drmProtectionValidator).readonly(),
5113
+ contentType: /* @__PURE__ */ isOneOf3("audio", "video", "text").orNull(),
5114
+ initDataType: /* @__PURE__ */ string6().orNull(),
5115
+ encryptionScheme: /* @__PURE__ */ string6().orNull(),
5116
+ mimeType: /* @__PURE__ */ string6().orNull()
5117
+ });
5118
+ function createEmptyMediaFormatMetadata() {
5119
+ return {
5120
+ contentType: null,
5121
+ mimeType: null,
5122
+ initDataType: null,
5123
+ encryptionScheme: null,
5124
+ contentProtections: []
5125
+ };
5126
+ }
5127
+ function createEmptyMediaQualityMetadata() {
5128
+ return __spreadProps(__spreadValues({}, createEmptyMediaFormatMetadata()), {
5129
+ decoderId: createShortUid(),
5130
+ qualityId: createShortUid(),
5131
+ codecs: null,
5132
+ bandwidth: null,
5133
+ bandwidthTotal: null,
5134
+ supplementalProperties: {},
5135
+ audioSamplingRate: null,
5136
+ frameRate: null,
5137
+ height: null,
5138
+ width: null,
5139
+ lang: null,
5140
+ characteristics: [],
5141
+ groupId: createShortUid(),
5142
+ switchingGroupIds: null
5143
+ });
5144
+ }
5145
+ var mediaQualityMetadataValidator = /* @__PURE__ */ mediaFormatMetadataValidator.extend({
5146
+ audioSamplingRate: /* @__PURE__ */ array3(/* @__PURE__ */ number6()).readonly().orNull(),
5147
+ bandwidth: /* @__PURE__ */ number6().orNull(),
5148
+ bandwidthTotal: /* @__PURE__ */ number6().orNull(),
5149
+ codecs: /* @__PURE__ */ string6().orNull(),
5150
+ decoderId: /* @__PURE__ */ string6(),
5151
+ frameRate: /* @__PURE__ */ tuple(/* @__PURE__ */ number6(), /* @__PURE__ */ number6()).readonly().orNull(),
5152
+ height: /* @__PURE__ */ number6().orNull(),
5153
+ qualityId: /* @__PURE__ */ string6(),
5154
+ supplementalProperties: descriptorRecordValidator,
5155
+ width: /* @__PURE__ */ number6().orNull(),
5156
+ lang: /* @__PURE__ */ string6().orNull(),
5157
+ characteristics: /* @__PURE__ */ array3(/* @__PURE__ */ string6()).readonly(),
5158
+ groupId: /* @__PURE__ */ string6(),
5159
+ switchingGroupIds: /* @__PURE__ */ array3(/* @__PURE__ */ string6()).readonly().orNull()
5160
+ });
5161
+
5016
5162
  // src/streaming/mediaTimelineFilter.ts
5017
5163
  import { map, memoize as memoize6 } from "@amazon/vinyl-util";
5018
5164
 
@@ -5301,6 +5447,13 @@ function createLanguageFilter(preferredLanguage, contentType2) {
5301
5447
  }
5302
5448
 
5303
5449
  // src/streaming/mediaTimelineFilter.ts
5450
+ function assertMediaStreamsRetained(original, filtered, throwError) {
5451
+ for (const contentType2 of RESTRICTABLE_CONTENT_TYPES) {
5452
+ if (original.some((q) => q.metadata.contentType === contentType2) && !filtered.some((q) => q.metadata.contentType === contentType2)) {
5453
+ throwError();
5454
+ }
5455
+ }
5456
+ }
5304
5457
  function filterTimelineQualities(filter, throwError, timeline) {
5305
5458
  if (!filter) return timeline;
5306
5459
  const mapMetadataArray = memoize6(
@@ -5312,11 +5465,9 @@ function filterTimelineQualities(filter, throwError, timeline) {
5312
5465
  const filtered = period.qualities.filter(
5313
5466
  (q, index) => filter(q.metadata, index, mapMetadataArray(period.qualities))
5314
5467
  );
5468
+ assertMediaStreamsRetained(period.qualities, filtered, throwError);
5315
5469
  return __spreadProps(__spreadValues({}, period), { qualities: filtered });
5316
5470
  });
5317
- if (periods.some((p) => p.qualities.length === 0)) {
5318
- throwError();
5319
- }
5320
5471
  return __spreadProps(__spreadValues({}, timeline), { periods });
5321
5472
  }
5322
5473
  async function filterTimelineQualitiesAsync(filter, throwError, timeline) {
@@ -5334,11 +5485,9 @@ async function filterTimelineQualitiesAsync(filter, throwError, timeline) {
5334
5485
  filtered.push(period.qualities[i]);
5335
5486
  }
5336
5487
  }
5488
+ assertMediaStreamsRetained(period.qualities, filtered, throwError);
5337
5489
  periods.push(__spreadProps(__spreadValues({}, period), { qualities: filtered }));
5338
5490
  }
5339
- if (periods.some((p) => p.qualities.length === 0)) {
5340
- throwError();
5341
- }
5342
5491
  return __spreadProps(__spreadValues({}, timeline), { periods });
5343
5492
  }
5344
5493
 
@@ -5412,7 +5561,6 @@ import {
5412
5561
  Browser as Browser2,
5413
5562
  last,
5414
5563
  MediaUnsupportedError as MediaUnsupportedError6,
5415
- every,
5416
5564
  map as map2,
5417
5565
  max as max2,
5418
5566
  min
@@ -5425,23 +5573,28 @@ function withinAudioSampleRateRange(options, metadata, _index, array6) {
5425
5573
  if (metadata.contentType !== "audio") return true;
5426
5574
  const samplingRate = last(metadata.audioSamplingRate);
5427
5575
  if (!samplingRate) return true;
5576
+ const { minSampleRate } = options;
5577
+ const maxSampleRate = (_a = options.maxSampleRate) != null ? _a : options.capabilities.sampleRate;
5428
5578
  const audioRates = () => map2(
5429
5579
  array6,
5430
5580
  (item) => item.contentType === "audio" ? last(item.audioSamplingRate) : void 0
5431
5581
  ).filter((rate) => rate != null);
5432
- const { minSampleRate } = options;
5582
+ const outOfBandFallback = () => {
5583
+ const rates = audioRates();
5584
+ const inBand = (rate) => (minSampleRate == null || rate >= minSampleRate) && (!maxSampleRate || rate <= maxSampleRate);
5585
+ if (rates.some(inBand)) return void 0;
5586
+ const decodable = maxSampleRate ? rates.filter((rate) => rate <= maxSampleRate) : rates;
5587
+ return decodable.length ? max2(decodable) : min(rates);
5588
+ };
5433
5589
  if (minSampleRate != null && samplingRate < minSampleRate) {
5434
- const rates2 = audioRates();
5435
- return every(rates2, (rate) => rate < minSampleRate) && samplingRate === max2(rates2);
5590
+ return samplingRate === outOfBandFallback();
5436
5591
  }
5437
- const maxSampleRate = (_a = options.maxSampleRate) != null ? _a : options.capabilities.sampleRate;
5438
5592
  if (!maxSampleRate) return true;
5439
5593
  if (hasBrowser2(Browser2.FIREFOX)) {
5440
5594
  return samplingRate <= 48e3;
5441
5595
  }
5442
5596
  if (samplingRate <= maxSampleRate) return true;
5443
- const rates = audioRates();
5444
- return every(rates, (rate) => rate > maxSampleRate) && samplingRate === min(rates);
5597
+ return samplingRate === outOfBandFallback();
5445
5598
  }
5446
5599
 
5447
5600
  // src/util/media/codecOverrides.ts
@@ -5528,27 +5681,6 @@ function createDefaultMediaTimelineTransformer(deps) {
5528
5681
  });
5529
5682
  }
5530
5683
 
5531
- // src/streaming/DescriptorRecord.ts
5532
- import {
5533
- array as array2,
5534
- object as object8,
5535
- record as record2,
5536
- string as string5
5537
- } from "@amazon/vinyl-validation";
5538
- import { first } from "@amazon/vinyl-util";
5539
- function getDescriptorValue(record4, uri, id) {
5540
- var _a, _b;
5541
- let arr = record4[uri];
5542
- if (!arr) return null;
5543
- if (id !== void 0) arr = arr.filter((descriptor) => descriptor.id === id);
5544
- return (_b = (_a = first(arr)) == null ? void 0 : _a.value) != null ? _b : null;
5545
- }
5546
- var descriptorValidator = /* @__PURE__ */ object8({
5547
- id: /* @__PURE__ */ string5().maybe().optional(),
5548
- value: /* @__PURE__ */ string5().maybe().optional()
5549
- });
5550
- var descriptorRecordValidator = /* @__PURE__ */ record2(/* @__PURE__ */ string5(), /* @__PURE__ */ array2(descriptorValidator).readonly().orUndefined());
5551
-
5552
5684
  // src/streaming/ManifestControllerImpl.ts
5553
5685
  import { data } from "@amazon/vinyl-observable";
5554
5686
  import {
@@ -5655,84 +5787,6 @@ var ManifestControllerImpl = class {
5655
5787
  }
5656
5788
  };
5657
5789
 
5658
- // src/streaming/MediaQualityMetadata.ts
5659
- import {
5660
- createShortUid
5661
- } from "@amazon/vinyl-util";
5662
- import {
5663
- array as array3,
5664
- enumOf as enumOf3,
5665
- isOneOf as isOneOf3,
5666
- number as number6,
5667
- object as object9,
5668
- string as string6,
5669
- tuple
5670
- } from "@amazon/vinyl-validation";
5671
- var ALL_CONTENT_TYPES = [
5672
- "video",
5673
- "audio",
5674
- "text"
5675
- ];
5676
- var RESTRICTABLE_CONTENT_TYPES = [
5677
- "video",
5678
- "audio"
5679
- ];
5680
- var drmProtectionValidator = /* @__PURE__ */ descriptorValidator.extend({
5681
- keySystem: /* @__PURE__ */ enumOf3(DrmKeySystem),
5682
- pro: /* @__PURE__ */ string6().maybe().optional(),
5683
- pssh: /* @__PURE__ */ string6().maybe().optional()
5684
- });
5685
- var mediaFormatMetadataValidator = /* @__PURE__ */ object9({
5686
- contentProtections: /* @__PURE__ */ array3(drmProtectionValidator).readonly(),
5687
- contentType: /* @__PURE__ */ isOneOf3("audio", "video", "text").orNull(),
5688
- initDataType: /* @__PURE__ */ string6().orNull(),
5689
- encryptionScheme: /* @__PURE__ */ string6().orNull(),
5690
- mimeType: /* @__PURE__ */ string6().orNull()
5691
- });
5692
- function createEmptyMediaFormatMetadata() {
5693
- return {
5694
- contentType: null,
5695
- mimeType: null,
5696
- initDataType: null,
5697
- encryptionScheme: null,
5698
- contentProtections: []
5699
- };
5700
- }
5701
- function createEmptyMediaQualityMetadata() {
5702
- return __spreadProps(__spreadValues({}, createEmptyMediaFormatMetadata()), {
5703
- decoderId: createShortUid(),
5704
- qualityId: createShortUid(),
5705
- codecs: null,
5706
- bandwidth: null,
5707
- bandwidthTotal: null,
5708
- supplementalProperties: {},
5709
- audioSamplingRate: null,
5710
- frameRate: null,
5711
- height: null,
5712
- width: null,
5713
- lang: null,
5714
- characteristics: [],
5715
- groupId: createShortUid(),
5716
- switchingGroupIds: null
5717
- });
5718
- }
5719
- var mediaQualityMetadataValidator = /* @__PURE__ */ mediaFormatMetadataValidator.extend({
5720
- audioSamplingRate: /* @__PURE__ */ array3(/* @__PURE__ */ number6()).readonly().orNull(),
5721
- bandwidth: /* @__PURE__ */ number6().orNull(),
5722
- bandwidthTotal: /* @__PURE__ */ number6().orNull(),
5723
- codecs: /* @__PURE__ */ string6().orNull(),
5724
- decoderId: /* @__PURE__ */ string6(),
5725
- frameRate: /* @__PURE__ */ tuple(/* @__PURE__ */ number6(), /* @__PURE__ */ number6()).readonly().orNull(),
5726
- height: /* @__PURE__ */ number6().orNull(),
5727
- qualityId: /* @__PURE__ */ string6(),
5728
- supplementalProperties: descriptorRecordValidator,
5729
- width: /* @__PURE__ */ number6().orNull(),
5730
- lang: /* @__PURE__ */ string6().orNull(),
5731
- characteristics: /* @__PURE__ */ array3(/* @__PURE__ */ string6()).readonly(),
5732
- groupId: /* @__PURE__ */ string6(),
5733
- switchingGroupIds: /* @__PURE__ */ array3(/* @__PURE__ */ string6()).readonly().orNull()
5734
- });
5735
-
5736
5790
  // src/streaming/MediaTimeline.ts
5737
5791
  import { sortedInsertionIndex as sortedInsertionIndex2 } from "@amazon/vinyl-util";
5738
5792
  function getMediaPeriodAtTime(timeline, time) {
@@ -7952,7 +8006,6 @@ import {
7952
8006
  first as first2,
7953
8007
  IntersectionRanges,
7954
8008
  logDebug as logDebug16,
7955
- MediaUnsupportedError as MediaUnsupportedError7,
7956
8009
  redispatchEvents as redispatchEvents3
7957
8010
  } from "@amazon/vinyl-util";
7958
8011
  import { createContainer as createContainer2 } from "@amazon/vinyl-di";
@@ -7980,10 +8033,7 @@ var MseTrack = class extends TrackBase {
7980
8033
  this.deps = deps;
7981
8034
  add(
7982
8035
  deps.contentTypesValue.onData((contentTypesPromise) => {
7983
- contentTypesPromise.then((value) => {
7984
- this.setContentTypes(value);
7985
- this.validateRequiredContentTypesPlayable();
7986
- }).catch(this.errorHandler);
8036
+ contentTypesPromise.then((value) => this.setContentTypes(value)).catch(this.errorHandler);
7987
8037
  })
7988
8038
  );
7989
8039
  this.on("streamingQualityChange", (event) => {
@@ -8084,33 +8134,8 @@ var MseTrack = class extends TrackBase {
8084
8134
  });
8085
8135
  }
8086
8136
  }
8087
- this.validateRequiredContentTypesPlayable();
8088
8137
  });
8089
8138
  }
8090
- /**
8091
- * Fails fast when a media content type the manifest declared (audio/video)
8092
- * has no playable qualities left after timeline filtering. Its stream can
8093
- * never create a source buffer, so without this the track waits forever for
8094
- * a source buffer that never arrives, surfacing only as an opaque
8095
- * `readyToAppend` timeout. Runs once both the required content types and the
8096
- * filtered timeline qualities are known (either order).
8097
- */
8098
- validateRequiredContentTypesPlayable() {
8099
- const qualities = this._qualities;
8100
- if (!qualities) return;
8101
- const present = new Set(qualities.map((q) => q.contentType));
8102
- for (const contentType2 of this._contentTypes) {
8103
- if (!present.has(contentType2)) {
8104
- this.errorHandler(
8105
- new MediaUnsupportedError7(
8106
- `No playable ${contentType2} rendition`,
8107
- `no-playable-${contentType2}`
8108
- )
8109
- );
8110
- return;
8111
- }
8112
- }
8113
- }
8114
8139
  async getAds() {
8115
8140
  const timeline = await this.deps.mediaTimeline.value;
8116
8141
  return {
@@ -10550,8 +10575,11 @@ import {
10550
10575
  emptyRanges as emptyRanges3,
10551
10576
  equalDeep as equalDeep4,
10552
10577
  EventHostImpl as EventHostImpl18,
10578
+ getLogLevel as getLogLevel2,
10553
10579
  logDebug as logDebug18,
10554
10580
  logInfo as logInfo3,
10581
+ LogLevel as LogLevel3,
10582
+ logVerbose as logVerbose5,
10555
10583
  logWarn as logWarn6,
10556
10584
  redispatchEvents as redispatchEvents4
10557
10585
  } from "@amazon/vinyl-util";
@@ -10584,6 +10612,7 @@ var VinylPlayer = class extends EventHostImpl18 {
10584
10612
  playerRegistryRef.value.addPlayer(this);
10585
10613
  this.redispatchSubControllerEvents();
10586
10614
  this.redispatchCurrentTrackEvents();
10615
+ this.initializeLoadSpanLogging();
10587
10616
  this.initializeAutoResetHandling();
10588
10617
  }
10589
10618
  get [Symbol.toStringTag]() {
@@ -10601,6 +10630,26 @@ var VinylPlayer = class extends EventHostImpl18 {
10601
10630
  get autoResetController() {
10602
10631
  return this.deps.autoResetController;
10603
10632
  }
10633
+ /**
10634
+ * Logs each republished load span once at the verbose level, formatting its
10635
+ * bounds as ISO date strings and its duration in milliseconds.
10636
+ */
10637
+ initializeLoadSpanLogging() {
10638
+ this.disposer.add(
10639
+ this.on("loadSpan", (span) => {
10640
+ if (getLogLevel2() > LogLevel3.VERBOSE) return;
10641
+ logVerbose5(this, "loadSpan", {
10642
+ kind: span.kind,
10643
+ trackUri: span.trackUri,
10644
+ mimeType: span.mimeType,
10645
+ contentType: span.contentType,
10646
+ startTime: new Date(span.startTime).toISOString(),
10647
+ endTime: new Date(span.endTime).toISOString(),
10648
+ durationMs: (span.endTime - span.startTime).toFixed(2)
10649
+ });
10650
+ })
10651
+ );
10652
+ }
10604
10653
  redispatchSubControllerEvents() {
10605
10654
  const add = this.disposer.add;
10606
10655
  add(
@@ -11221,6 +11270,7 @@ export {
11221
11270
  MsCommonMediaKeySystemAccess,
11222
11271
  MsCommonMediaKeys,
11223
11272
  MseTrack,
11273
+ PLAYOUT_LIMIT_TOLERANCE,
11224
11274
  PLAY_READY_KEY_SYSTEMS,
11225
11275
  POLL_BUFFER_THROTTLE,
11226
11276
  PREFETCH_POLL_THROTTLE,