@amazon/vinyl 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/index.cjs +65 -45
  2. package/dist/index.cjs.map +2 -2
  3. package/dist/index.js +90 -69
  4. package/dist/index.js.map +3 -3
  5. package/dist/test/integ.es5.js +303 -287
  6. package/dist/test/integ.js +328 -71
  7. package/dist/test/integ.js.map +2 -2
  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 +6 -0
  12. package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
  13. package/dist/types/current/drm/DrmControllerImpl.js +11 -1
  14. package/dist/types/current/playback/PlaybackControllerImpl.d.ts +8 -2
  15. package/dist/types/current/playback/PlaybackControllerImpl.d.ts.map +1 -1
  16. package/dist/types/current/playback/PlaybackControllerImpl.js +38 -33
  17. package/dist/types/current/playback/createStallMonitor.d.ts.map +1 -1
  18. package/dist/types/current/playback/createStallMonitor.js +19 -14
  19. package/dist/types/current/track/mse/MseTrack.d.ts +0 -1
  20. package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
  21. package/dist/types/current/track/mse/MseTrack.js +5 -6
  22. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  23. package/dist/types/ts3.7/ad/AdControllerImpl.d.ts +18 -2
  24. package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +6 -0
  25. package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +8 -2
  26. package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -1
  27. package/dist/types/ts4.1/ad/AdControllerImpl.d.ts +18 -2
  28. package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +6 -0
  29. package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +8 -2
  30. package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -1
  31. package/dist/types/ts4.7/ad/AdControllerImpl.d.ts +18 -2
  32. package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +6 -0
  33. package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +8 -2
  34. package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -1
  35. package/dist/unit/types/current/ad/AdControllerImpl.test.js +110 -4
  36. package/dist/unit/types/current/drm/DrmControllerImpl.test.js +19 -1
  37. package/dist/unit/types/current/playback/PlaybackControllerImpl.test.js +15 -0
  38. package/dist/unit/types/current/playback/createStallMonitor.test.js +38 -0
  39. package/dist/unit/types/current/streaming/MseTrack.test.js +15 -0
  40. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  41. package/dist/vinylTestUtil/index.js +10 -1
  42. package/dist/vinylTestUtil/index.js.map +2 -2
  43. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts +5 -0
  44. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts.map +1 -1
  45. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.js +9 -0
  46. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  47. package/dist/vinylTestUtil/types/ts3.7/assets/vinylTestAssets.d.ts +5 -0
  48. package/dist/vinylTestUtil/types/ts4.1/assets/vinylTestAssets.d.ts +5 -0
  49. package/dist/vinylTestUtil/types/ts4.7/assets/vinylTestAssets.d.ts +5 -0
  50. package/package.json +10 -10
package/dist/index.cjs CHANGED
@@ -67,6 +67,7 @@ __export(index_exports, {
67
67
  DrmError: () => DrmError,
68
68
  DrmKeySystem: () => DrmKeySystem,
69
69
  DrmRobustness: () => DrmRobustness,
70
+ EXCESSIVE_SESSION_COUNT: () => EXCESSIVE_SESSION_COUNT,
70
71
  FAIR_PLAY_KEY_SYSTEMS: () => FAIR_PLAY_KEY_SYSTEMS,
71
72
  FixedPlaybackQuality: () => FixedPlaybackQuality,
72
73
  HtmlTextTrackRenderer: () => HtmlTextTrackRenderer,
@@ -84,6 +85,7 @@ __export(index_exports, {
84
85
  MsCommonMediaKeySystemAccess: () => MsCommonMediaKeySystemAccess,
85
86
  MsCommonMediaKeys: () => MsCommonMediaKeys,
86
87
  MseTrack: () => MseTrack,
88
+ PLAYOUT_LIMIT_TOLERANCE: () => PLAYOUT_LIMIT_TOLERANCE,
87
89
  PLAY_READY_KEY_SYSTEMS: () => PLAY_READY_KEY_SYSTEMS,
88
90
  POLL_BUFFER_THROTTLE: () => POLL_BUFFER_THROTTLE,
89
91
  PREFETCH_POLL_THROTTLE: () => PREFETCH_POLL_THROTTLE,
@@ -353,6 +355,7 @@ var AdError = class _AdError extends import_vinyl_util.ReportableError {
353
355
 
354
356
  // src/ad/AdControllerImpl.ts
355
357
  var AD_START_TOLERANCE = 0.05;
358
+ var PLAYOUT_LIMIT_TOLERANCE = 1;
356
359
  var defaultAdControllerImplOptions = {
357
360
  adLoadTimeout: 15,
358
361
  preloadAheadTime: 10
@@ -843,23 +846,29 @@ var AdControllerImpl = class extends import_vinyl_util2.EventHostImpl {
843
846
  });
844
847
  }
845
848
  /**
846
- * Ads breaks can have a playout limit that may cut off ad playback.
847
- * End the ad if that limit is reached.
849
+ * Enforces the break-level playout limit (X-PLAYOUT-LIMIT): a budget shared
850
+ * across all of the break's ads. When cumulative playout reaches it, the
851
+ * whole break ends, dropping any ads that have not started.
852
+ *
853
+ * This is deliberately NOT a per-ad cap. Each ad asset is its own source and
854
+ * ends on its media `ended` (see the 'ended' handler), which is what advances
855
+ * a multi-ad break. Ending an ad at its declared per-ad DURATION instead would
856
+ * truncate the real asset whenever a packager under-declares it — e.g. rounds
857
+ * a 6.732s asset down to a whole-second 6 — cutting off the fractional tail.
848
858
  */
849
859
  enforcePlayoutLimits(adState) {
860
+ if (!adState.started) return;
861
+ const { parentBreak } = adState;
862
+ const limit = parentBreak.adBreak.playoutLimit;
863
+ if (limit == null) return;
850
864
  const pC = this.deps.playbackController;
851
- const { ad, parentBreak } = adState;
852
- if (adState.started) {
853
- const elapsed = Math.max(0, pC.currentTime - adState.timeStart);
854
- const adBreak = parentBreak.adBreak;
855
- const limit = adBreak.playoutLimit;
856
- if (limit != null && parentBreak.playoutElapsed + elapsed >= limit) {
857
- parentBreak.clear();
858
- this.endAd(adState);
859
- } else if (ad.duration != null && elapsed >= ad.duration) {
860
- this.endAd(adState);
861
- }
865
+ const elapsed = Math.max(0, pC.currentTime - adState.timeStart);
866
+ if (parentBreak.playoutElapsed + elapsed < limit) return;
867
+ if (isFinite(pC.duration) && pC.duration > 0 && pC.duration - pC.currentTime <= PLAYOUT_LIMIT_TOLERANCE) {
868
+ return;
862
869
  }
870
+ parentBreak.clear();
871
+ this.endAd(adState);
863
872
  }
864
873
  setPendingBreaks(newPending) {
865
874
  this.pendingAdBreaks = newPending.filter(
@@ -2090,6 +2099,7 @@ function createFairPlaySessionInitData(initData, contentId, certificateData) {
2090
2099
  // src/drm/DrmControllerImpl.ts
2091
2100
  var LICENSE_TIMEOUT = 90;
2092
2101
  var LICENSE_TIMEOUT_MESSAGE = "License provider timed out after {time}s";
2102
+ var EXCESSIVE_SESSION_COUNT = 20;
2093
2103
  var DrmControllerImpl = class extends import_vinyl_util24.EventHostImpl {
2094
2104
  constructor(deps, options) {
2095
2105
  super();
@@ -2306,6 +2316,13 @@ var DrmControllerImpl = class extends import_vinyl_util24.EventHostImpl {
2306
2316
  trackUri
2307
2317
  });
2308
2318
  this.sessions.push(newSession);
2319
+ (0, import_vinyl_util24.logVerbose)(this, `total sessions: ${this.sessions.length}`);
2320
+ if (this.sessions.length === EXCESSIVE_SESSION_COUNT + 1) {
2321
+ (0, import_vinyl_util24.logWarn)(
2322
+ this,
2323
+ `${this.sessions.length} open key sessions exceeds ${EXCESSIVE_SESSION_COUNT}; sessions may be leaking (not closing on track deactivation)`
2324
+ );
2325
+ }
2309
2326
  this.dispatch("sessionCreate", {
2310
2327
  initDataType: newSession.initDataType,
2311
2328
  mimeType: newSession.mimeType
@@ -2817,7 +2834,7 @@ function configureVinylGlobal(options) {
2817
2834
  var import_vinyl_util27 = require("@amazon/vinyl-util");
2818
2835
  var vinylVersion = /* @__PURE__ */ (0, import_vinyl_util27.parseVersion)(
2819
2836
  /* istanbul ignore next */
2820
- "3.2.0"
2837
+ "3.2.2"
2821
2838
  );
2822
2839
 
2823
2840
  // src/global/globalRef.ts
@@ -3016,6 +3033,7 @@ var PlaybackNetworkState = /* @__PURE__ */ ((PlaybackNetworkState2) => {
3016
3033
  })(PlaybackNetworkState || {});
3017
3034
 
3018
3035
  // src/playback/createStallMonitor.ts
3036
+ var PROGRESS_EPSILON = 1e-6;
3019
3037
  function createStallMonitor(controller, callbacks, options = {}) {
3020
3038
  var _a, _b;
3021
3039
  const stallThresholdMs = ((_a = options.stallThreshold) != null ? _a : 1) * 1e3;
@@ -3024,6 +3042,7 @@ function createStallMonitor(controller, callbacks, options = {}) {
3024
3042
  let lastProgressAt = Date.now();
3025
3043
  let inStall = false;
3026
3044
  let pollId = null;
3045
+ const { add, dispose } = (0, import_vinyl_util30.createDisposer)();
3027
3046
  const endStall = (reason) => {
3028
3047
  if (!inStall) return;
3029
3048
  inStall = false;
@@ -3040,6 +3059,7 @@ function createStallMonitor(controller, callbacks, options = {}) {
3040
3059
  clearInterval(pollId);
3041
3060
  pollId = null;
3042
3061
  };
3062
+ add(stopPoll);
3043
3063
  const startPoll = () => {
3044
3064
  if (pollId != null) return;
3045
3065
  pollId = setInterval(() => {
@@ -3055,17 +3075,18 @@ function createStallMonitor(controller, callbacks, options = {}) {
3055
3075
  observedPlaying = false;
3056
3076
  stopPoll();
3057
3077
  };
3058
- const { add, dispose } = (0, import_vinyl_util30.createDisposer)();
3059
3078
  add(
3060
3079
  controller.on("playing", () => {
3061
3080
  if (inStall) endStall("playing" /* PLAYING */);
3062
- else lastProgressAt = Date.now();
3081
+ lastProgressAt = Date.now();
3063
3082
  observedPlaying = true;
3064
3083
  startPoll();
3065
3084
  })
3066
3085
  );
3067
3086
  add(
3068
- controller.on("timeUpdate", () => {
3087
+ controller.on("timeUpdate", ({ previous, current }) => {
3088
+ if (previous != null && current - previous <= PROGRESS_EPSILON)
3089
+ return;
3069
3090
  if (inStall) endStall("playing" /* PLAYING */);
3070
3091
  lastProgressAt = Date.now();
3071
3092
  })
@@ -3073,13 +3094,7 @@ function createStallMonitor(controller, callbacks, options = {}) {
3073
3094
  add(controller.on("pause", () => suspend("pause" /* PAUSE */)));
3074
3095
  add(controller.on("seeking", () => suspend("seeking" /* SEEKING */)));
3075
3096
  add(controller.on("emptied", () => suspend("emptied" /* EMPTIED */)));
3076
- let stopped = false;
3077
- return () => {
3078
- if (stopped) return;
3079
- stopped = true;
3080
- stopPoll();
3081
- dispose();
3082
- };
3097
+ return dispose;
3083
3098
  }
3084
3099
 
3085
3100
  // src/playback/error/InvalidSeekError.ts
@@ -3372,15 +3387,13 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3372
3387
  __publicField(this, "_playing", false);
3373
3388
  __publicField(this, "_seeking", false);
3374
3389
  __publicField(this, "_waiting", false);
3375
- // Monotonic counter identifying the most recent seekTo call.
3376
- __publicField(this, "_seekId", 0);
3390
+ __publicField(this, "seekAbortSlot", /* @__PURE__ */ (0, import_vinyl_util42.createAbortSlot)());
3377
3391
  // Stops the stall detector started after the last seek, if any.
3378
3392
  __publicField(this, "stopStallDetection", null);
3379
3393
  // default user volume (0.0 to 1.0 scale)
3380
3394
  __publicField(this, "userVolume", 1);
3381
3395
  __publicField(this, "_pendingPlay", null);
3382
3396
  __publicField(this, "playAbort", null);
3383
- __publicField(this, "disposeAbort", /* @__PURE__ */ new import_vinyl_util42.Abort());
3384
3397
  __publicField(this, "options");
3385
3398
  this.options = __spreadValues(__spreadProps(__spreadValues({}, defaultPlaybackControllerImplOptions), {
3386
3399
  minSeekableBuffer: getMinSeekableBufferDefault()
@@ -3391,8 +3404,7 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3391
3404
  const { add } = this.disposer;
3392
3405
  add(() => {
3393
3406
  var _a;
3394
- this.disposeAbort.abort();
3395
- this.disposeAbort.dispose();
3407
+ this.seekAbortSlot.abort();
3396
3408
  (_a = this.stopStallDetection) == null ? void 0 : _a.call(this);
3397
3409
  });
3398
3410
  this.domEvents = add(
@@ -3805,14 +3817,10 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3805
3817
  async seekTo(time, tolerance = 0.5) {
3806
3818
  var _a;
3807
3819
  (0, import_vinyl_util42.logDebug)(this, `seekTo: ${time}`);
3808
- const seekId = ++this._seekId;
3809
3820
  (_a = this.stopStallDetection) == null ? void 0 : _a.call(this);
3810
3821
  this.stopStallDetection = null;
3811
- const superseded = () => {
3812
- if (this._seekId === seekId) return false;
3813
- (0, import_vinyl_util42.logDebug)(this, `seekTo: ${time} superseded by a newer seek`);
3814
- return true;
3815
- };
3822
+ this.seekAbortSlot.abort();
3823
+ const seekAbort = this.seekAbortSlot.value;
3816
3824
  const nextEvent = (type, options) => {
3817
3825
  var _a2;
3818
3826
  return (0, import_vinyl_util42.nextEventAsPromise)(this, type, {
@@ -3820,12 +3828,25 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3820
3828
  timeoutMessage: `seek timed out on ${type} event after {time}s`,
3821
3829
  timeoutOrigin: import_vinyl_util42.ErrorOrigin.INTERNAL,
3822
3830
  timeoutLevel: import_vinyl_util42.ErrorLevel.WARN,
3823
- abort: this.disposeAbort
3831
+ abort: seekAbort
3824
3832
  });
3825
3833
  };
3834
+ try {
3835
+ await this.doSeek(time, tolerance, seekAbort, nextEvent);
3836
+ } catch (error) {
3837
+ if (seekAbort.aborted() && !this.disposed) return;
3838
+ throw error;
3839
+ }
3840
+ }
3841
+ /**
3842
+ * The body of a single {@link seekTo} attempt. Extracted so {@link seekTo}
3843
+ * can wrap it with per-seek abort/supersession handling. `seekAbort` is
3844
+ * aborted when a newer seek supersedes this one (or on dispose).
3845
+ * @private
3846
+ */
3847
+ async doSeek(time, tolerance, seekAbort, nextEvent) {
3826
3848
  if (!this.hasMetadata) {
3827
3849
  await nextEvent("loadedMetadata");
3828
- if (superseded()) return;
3829
3850
  }
3830
3851
  const seekable = this.seekable;
3831
3852
  const range = seekable.getRangeAt(
@@ -3856,7 +3877,7 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3856
3877
  );
3857
3878
  } else {
3858
3879
  const nullIfStuck = (error) => {
3859
- if (this.disposeAbort.aborted()) throw error;
3880
+ if (this.disposed) throw error;
3860
3881
  return null;
3861
3882
  };
3862
3883
  const seekOnce = async (reSeeking) => {
@@ -3870,7 +3891,7 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3870
3891
  }).catch(nullIfStuck);
3871
3892
  };
3872
3893
  let seekedEvent = await seekOnce(false);
3873
- for (let attempt = 0; !superseded() && attempt < SEEK_RETRY_LIMIT && (seekedEvent === null || seekedEvent.reason === "seeked" && !(0, import_vinyl_util42.closeTo)(clampedTime, this.currentTime, tolerance)); attempt++) {
3894
+ for (let attempt = 0; !seekAbort.aborted() && attempt < SEEK_RETRY_LIMIT && (seekedEvent === null || seekedEvent.reason === "seeked" && !(0, import_vinyl_util42.closeTo)(clampedTime, this.currentTime, tolerance)); attempt++) {
3874
3895
  if (seekedEvent === null) void this.media.play().catch(import_vinyl_util42.noop);
3875
3896
  (0, import_vinyl_util42.logDebug)(this, `seekTo: re-seeking to ${clampedTime}`);
3876
3897
  seekedEvent = await seekOnce(true);
@@ -3878,7 +3899,7 @@ var PlaybackControllerImpl = class extends import_vinyl_util42.EventHostImpl {
3878
3899
  if ((seekedEvent == null ? void 0 : seekedEvent.reason) === "emptied") {
3879
3900
  throw new import_vinyl_util42.AbortError();
3880
3901
  }
3881
- if (!this.paused) this.startStallDetection();
3902
+ if (!this.paused && !seekAbort.aborted()) this.startStallDetection();
3882
3903
  }
3883
3904
  }
3884
3905
  /**
@@ -7987,7 +8008,6 @@ var MseTrack = class extends TrackBase {
7987
8008
  const deps = depsContainer.dependencies;
7988
8009
  super(uri, type, deps);
7989
8010
  __publicField(this, "streams", []);
7990
- __publicField(this, "disposeAbort", /* @__PURE__ */ new import_vinyl_util80.Abort());
7991
8011
  __publicField(this, "lastPreloadOptions", null);
7992
8012
  __publicField(this, "allFetchedRanges", []);
7993
8013
  __publicField(this, "_fetchedRanges", /* @__PURE__ */ new import_vinyl_util80.IntersectionRanges(
@@ -8012,7 +8032,7 @@ var MseTrack = class extends TrackBase {
8012
8032
  if (this.active) {
8013
8033
  this.deps.drmController.initializeForPlayback(event.current, {
8014
8034
  trackUri: this.uri,
8015
- abort: this.disposeAbort
8035
+ abort: this.drmSessionAbort.value
8016
8036
  });
8017
8037
  }
8018
8038
  });
@@ -8020,7 +8040,7 @@ var MseTrack = class extends TrackBase {
8020
8040
  if (event.current == null) return;
8021
8041
  this.deps.drmController.setBufferingDrmInfo(event.current, {
8022
8042
  trackUri: this.uri,
8023
- abort: this.disposeAbort
8043
+ abort: this.drmSessionAbort.value
8024
8044
  });
8025
8045
  });
8026
8046
  add(
@@ -8258,7 +8278,7 @@ var MseTrack = class extends TrackBase {
8258
8278
  this.streams.forEach((stream) => {
8259
8279
  this.deps.drmController.initializeForPlayback(
8260
8280
  stream.streamingQuality,
8261
- { trackUri: this.uri, abort: this.disposeAbort }
8281
+ { trackUri: this.uri, abort: this.drmSessionAbort.value }
8262
8282
  );
8263
8283
  });
8264
8284
  this.deps.playbackSource.src = this.deps.mediaSourceController.activate();
@@ -8284,7 +8304,7 @@ var MseTrack = class extends TrackBase {
8284
8304
  this.callOnStreams("deactivate");
8285
8305
  this.deps.drmController.setBufferingDrmInfo(null, {
8286
8306
  trackUri: this.uri,
8287
- abort: this.disposeAbort
8307
+ abort: this.drmSessionAbort.value
8288
8308
  });
8289
8309
  (_b = this.deps.textTrackController) == null ? void 0 : _b.deactivate();
8290
8310
  this.deps.playbackSource.src = null;