@hyperframes/engine 0.8.146 → 0.8.147

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.
@@ -20,6 +20,8 @@ export type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSum
20
20
  * `heldVideoTime` (motion-blur samples only) is the output frame's time: a video on screen at
21
21
  * both times shows its frame for `heldVideoTime`, so footage does not smear. */
22
22
  export type BeforeCaptureHook = (page: Page, time: number, heldVideoTime?: number) => Promise<void>;
23
+ /** The per-frame wait a capture is in, worded for the stall error a user reads. */
24
+ type CaptureStep = "seeking the timeline" | "decoding CSS background images" | "injecting video frames" | "waiting for page seek handlers" | "loading color-grading LUTs" | "compositing the page" | "capturing the frame";
23
25
  export interface CaptureSession {
24
26
  browser: Browser;
25
27
  /** Exact ownership token for this browser acquisition. */
@@ -202,6 +204,7 @@ export interface CaptureSession {
202
204
  * retries via screenshot.
203
205
  */
204
206
  deFrameTimeouts?: number;
207
+ captureStep?: CaptureStep;
205
208
  /**
206
209
  * drawElement init passed every gate but stopped before verification +
207
210
  * canvas injection: the session has no video-frame injector yet (probe
@@ -2040,7 +2040,9 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
2040
2040
  assertVfxIntact(session);
2041
2041
  const quantizedTime = quantizeSeekTime(time, fpsToNumber(options.fps), seekOptions?.subFrameDivisions);
2042
2042
  const seekStart = Date.now();
2043
+ session.captureStep = "seeking the timeline";
2043
2044
  const hasPendingComposite = await seekPageTimeline(page, quantizedTime, seekOptions);
2045
+ session.captureStep = "decoding CSS background images";
2044
2046
  await decodeDynamicCssBackgroundImages(page);
2045
2047
  const seekMs = Date.now() - seekStart;
2046
2048
  // Before-capture hook (e.g. video frame injection) — runs before
@@ -2048,9 +2050,12 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
2048
2050
  // replacements for <video> elements.
2049
2051
  const beforeCaptureStart = Date.now();
2050
2052
  if (session.onBeforeCapture) {
2053
+ session.captureStep = "injecting video frames";
2051
2054
  await session.onBeforeCapture(page, quantizedTime, heldVideoTime);
2052
2055
  }
2056
+ session.captureStep = "waiting for page seek handlers";
2053
2057
  await waitForPendingSeekCompletion(page);
2058
+ session.captureStep = "loading color-grading LUTs";
2054
2059
  await page.evaluate(async () => {
2055
2060
  const runtime = window.__hf?.colorGrading;
2056
2061
  await runtime?.waitForActiveLuts?.();
@@ -2063,6 +2068,7 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
2063
2068
  if (hasPendingComposite &&
2064
2069
  session.captureMode !== "beginframe" &&
2065
2070
  session.captureMode !== "drawelement") {
2071
+ session.captureStep = "compositing the page";
2066
2072
  await page.evaluate(async () => {
2067
2073
  const w = window;
2068
2074
  if (typeof w.__hf_page_composite_prepare === "function") {
@@ -2082,6 +2088,7 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
2082
2088
  }
2083
2089
  });
2084
2090
  }
2091
+ session.captureStep = "capturing the frame";
2085
2092
  return { quantizedTime, seekMs, beforeCaptureMs };
2086
2093
  }
2087
2094
  // ── Static-frame dedup (default-on, opt-out HF_STATIC_DEDUP=false) ─────────────
@@ -3095,12 +3102,14 @@ async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan)
3095
3102
  const fps = fpsToNumber(session.options.fps);
3096
3103
  const frameTime = quantizeSeekTime(absFrameIndex / fps, fps);
3097
3104
  const eventfulSeekStart = Date.now();
3105
+ session.captureStep = "seeking the timeline";
3098
3106
  await seekPageTimeline(session.page, frameTime, undefined);
3099
3107
  const totals = { seekMs: Date.now() - eventfulSeekStart, beforeCaptureMs: 0, screenshotMs: 0 };
3100
3108
  if (session.onBeforeCapture) {
3101
3109
  // The injector copies a video's own style only when its frame changes: inject at frameTime
3102
3110
  // first so the held frame carries frameTime's style, not the first sample's.
3103
3111
  const injectStart = Date.now();
3112
+ session.captureStep = "injecting video frames";
3104
3113
  await session.onBeforeCapture(session.page, frameTime, frameTime);
3105
3114
  totals.beforeCaptureMs += Date.now() - injectStart;
3106
3115
  }
@@ -3125,6 +3134,7 @@ async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan)
3125
3134
  accumulator.add(sample.buffer);
3126
3135
  }
3127
3136
  const restoreSeekStart = Date.now();
3137
+ session.captureStep = "seeking the timeline";
3128
3138
  await seekPageTimeline(session.page, frameTime, { suppressEvents: true });
3129
3139
  totals.seekMs += Date.now() - restoreSeekStart;
3130
3140
  const blended = accumulator.finish();
@@ -3396,6 +3406,7 @@ export async function captureFramesBatchPipelined(session, frameIndices, times)
3396
3406
  const startTime = Date.now();
3397
3407
  const fps = fpsToNumber(options.fps);
3398
3408
  const quantized = times.map((t) => quantizeTimeToFrame(t, fps));
3409
+ session.captureStep = "capturing the frame";
3399
3410
  const { encodeResults, failedAt, error } = await produceDrawElementFrameBatch(page, quantized, options.width, options.height, options.quality ?? 80);
3400
3411
  // A later batch failure abandons this prefix. Its asynchronous encodes can
3401
3412
  // still reject while session cleanup shuts the worker down.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.146",
3
+ "version": "0.8.147",
4
4
  "description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,8 +46,8 @@
46
46
  "linkedom": "^0.18.12",
47
47
  "puppeteer": "^25.8.0",
48
48
  "puppeteer-core": "^25.8.0",
49
- "@hyperframes/core": "^0.8.146",
50
- "@hyperframes/parsers": "^0.8.146"
49
+ "@hyperframes/core": "^0.8.147",
50
+ "@hyperframes/parsers": "^0.8.147"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^25.0.10",