@hyperframes/engine 0.9.20 → 0.9.22

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.
@@ -1002,7 +1002,7 @@ export async function assertWebGpuAdapterAvailable(page, requiresWebGpu, softwar
1002
1002
  return await Promise.race([
1003
1003
  probe(),
1004
1004
  new Promise((resolve) => {
1005
- timer = setTimeout(() => {
1005
+ timer = (globalThis.window?.__HF_VIRTUAL_TIME__?.originalSetTimeout ?? setTimeout)(() => {
1006
1006
  expired = true;
1007
1007
  resolve(false);
1008
1008
  }, timeoutMs);
@@ -488,7 +488,7 @@ syncToPaintEvent = true) {
488
488
  }
489
489
  // Encode OUTSIDE the paint handler — heavy canvas work inside the
490
490
  // paint event can stall the renderer.
491
- setTimeout(() => {
491
+ (window.__HF_VIRTUAL_TIME__?.originalSetTimeout ?? setTimeout)(() => {
492
492
  try {
493
493
  resolveCapture(fmt === "png"
494
494
  ? canvas.toDataURL("image/png")
@@ -520,7 +520,7 @@ syncToPaintEvent = true) {
520
520
  // throttled page), fall back to an unsynchronized draw after 250 ms —
521
521
  // worst case one-frame-stale content (the root's alpha may lag its
522
522
  // transform by that frame) rather than a hung render.
523
- setTimeout(() => {
523
+ (window.__HF_VIRTUAL_TIME__?.originalSetTimeout ?? setTimeout)(() => {
524
524
  canvas.removeEventListener("paint", onPaint);
525
525
  drawAndEncode();
526
526
  }, 250);
@@ -870,7 +870,7 @@ export async function produceDrawElementFrame(page, width, height, quality = 80,
870
870
  // Sentinel dirty + requestPaint() — see __hfDeInvalidate in
871
871
  // injectDrawElementCanvas.
872
872
  usedRequestPaint = aw.__hfDeInvalidate?.() === true;
873
- setTimeout(() => {
873
+ (window.__HF_VIRTUAL_TIME__?.originalSetTimeout ?? setTimeout)(() => {
874
874
  canvas.removeEventListener("paint", onPaint);
875
875
  drawAndKick();
876
876
  }, 250);
@@ -957,7 +957,7 @@ export async function produceDrawElementFrameBatch(page, times, width, height, q
957
957
  // Sentinel dirty + requestPaint() — see __hfDeInvalidate in
958
958
  // injectDrawElementCanvas.
959
959
  usedRequestPaint = aw.__hfDeInvalidate?.() === true;
960
- setTimeout(settle, 250);
960
+ (window.__HF_VIRTUAL_TIME__?.originalSetTimeout ?? setTimeout)(settle, 250);
961
961
  });
962
962
  let prevBitmap = Promise.resolve();
963
963
  let prevBitmapIdx = -1;
@@ -418,7 +418,7 @@ export declare function formatConsoleDiagnostic(type: string, text: string, loca
418
418
  text: string;
419
419
  suppressHostLog: boolean;
420
420
  };
421
- export declare const HF_READY_DIAGNOSTIC_EXPR = "(async function() {\n var hf = window.__hf;\n var player = window.__player;\n var renderReady = !!window.__renderReady;\n var hasSeek = !!(hf && typeof hf.seek === \"function\");\n var duration = hf ? hf.duration : -1;\n var hasTimeline = !!(window.__timelines && Object.keys(window.__timelines).length > 0);\n var root = document.querySelector(\"[data-composition-id]\");\n var declaredDuration = root ? Number(root.getAttribute(\"data-duration\")) : -1;\n var registry = (window.__hf && window.__hf.buildReady) || {};\n var keys = Object.keys(registry);\n // A microtask race mis-sorts a non-native thenable (extra adoption tick)\n // and rejects the whole expression on a rejected entry. Settling each\n // key onto its own record and waiting one macrotask avoids both.\n var settled = {};\n if (keys.length > 0) {\n keys.forEach(function(key) {\n Promise.resolve(registry[key]).then(\n function() { settled[key] = \"resolved\"; },\n function() { settled[key] = \"rejected\"; },\n );\n });\n await new Promise(function(r) { setTimeout(r, 0); });\n }\n // A thenable resolving via its own setTimeout(0) can still misreport as\n // pending here (macrotask registration order, not resolution order), a\n // narrow case that self-corrects on the next ~1s diagnostic tick.\n var pendingBuildReadyKeys = keys.filter(function(key) { return !settled[key]; });\n var rejectedBuildReadyKeys = keys.filter(function(key) { return settled[key] === \"rejected\"; });\n return {\n renderReady: renderReady,\n hasHf: !!hf,\n hasSeek: hasSeek,\n hasPlayer: !!player,\n duration: duration,\n hasTimeline: hasTimeline,\n declaredDuration: declaredDuration,\n pendingBuildReadyKeys: pendingBuildReadyKeys,\n rejectedBuildReadyKeys: rejectedBuildReadyKeys,\n };\n})()";
421
+ export declare const HF_READY_DIAGNOSTIC_EXPR = "(async function() {\n var hf = window.__hf;\n var player = window.__player;\n var renderReady = !!window.__renderReady;\n var hasSeek = !!(hf && typeof hf.seek === \"function\");\n var duration = hf ? hf.duration : -1;\n var hasTimeline = !!(window.__timelines && Object.keys(window.__timelines).length > 0);\n var root = document.querySelector(\"[data-composition-id]\");\n var declaredDuration = root ? Number(root.getAttribute(\"data-duration\")) : -1;\n var registry = (window.__hf && window.__hf.buildReady) || {};\n var keys = Object.keys(registry);\n // A microtask race mis-sorts a non-native thenable (extra adoption tick)\n // and rejects the whole expression on a rejected entry. Settling each\n // key onto its own record and waiting one macrotask avoids both.\n var settled = {};\n if (keys.length > 0) {\n keys.forEach(function(key) {\n Promise.resolve(registry[key]).then(\n function() { settled[key] = \"resolved\"; },\n function() { settled[key] = \"rejected\"; },\n );\n });\n await new Promise(function(r) {\n (window.__HF_VIRTUAL_TIME__ && window.__HF_VIRTUAL_TIME__.originalSetTimeout || setTimeout)(r, 0);\n });\n }\n // A thenable resolving via its own setTimeout(0) can still misreport as\n // pending here (macrotask registration order, not resolution order), a\n // narrow case that self-corrects on the next ~1s diagnostic tick.\n var pendingBuildReadyKeys = keys.filter(function(key) { return !settled[key]; });\n var rejectedBuildReadyKeys = keys.filter(function(key) { return settled[key] === \"rejected\"; });\n return {\n renderReady: renderReady,\n hasHf: !!hf,\n hasSeek: hasSeek,\n hasPlayer: !!player,\n duration: duration,\n hasTimeline: hasTimeline,\n declaredDuration: declaredDuration,\n pendingBuildReadyKeys: pendingBuildReadyKeys,\n rejectedBuildReadyKeys: rejectedBuildReadyKeys,\n };\n})()";
422
422
  export interface HfDiagnostic {
423
423
  renderReady: boolean;
424
424
  hasHf: boolean;
@@ -163,7 +163,7 @@ export async function decodeDynamicCssBackgroundImages(page) {
163
163
  image.src = url;
164
164
  let timer;
165
165
  const expired = new Promise((resolve) => {
166
- timer = setTimeout(() => resolve("late"), limitMs);
166
+ timer = (window.__HF_VIRTUAL_TIME__?.originalSetTimeout ?? setTimeout)(() => resolve("late"), limitMs);
167
167
  });
168
168
  try {
169
169
  const outcome = await Promise.race([
@@ -1208,7 +1208,9 @@ export const HF_READY_DIAGNOSTIC_EXPR = `(async function() {
1208
1208
  function() { settled[key] = "rejected"; },
1209
1209
  );
1210
1210
  });
1211
- await new Promise(function(r) { setTimeout(r, 0); });
1211
+ await new Promise(function(r) {
1212
+ (window.__HF_VIRTUAL_TIME__ && window.__HF_VIRTUAL_TIME__.originalSetTimeout || setTimeout)(r, 0);
1213
+ });
1212
1214
  }
1213
1215
  // A thenable resolving via its own setTimeout(0) can still misreport as
1214
1216
  // pending here (macrotask registration order, not resolution order), a
@@ -506,9 +506,20 @@ export function parseImageElements(html) {
506
506
  return images;
507
507
  }
508
508
  const firstFrameColourCache = new Map();
509
- /** A source's SPS transfers, so a PQ or SDR keyframe keeps an HLG one at 100 nits; null when the read failed. */
509
+ /** A source's SPS transfers, read as the trace streams (a run keeps only stderr's tail); null when the read failed. */
510
510
  function readKeyframeTransfers(videoPath, codec) {
511
- return inExtractionSlot(() => runFfmpeg([...urlInputArgs(videoPath), ...keyframeTransferArgs(videoPath, codec)])).then((result) => (result.success ? parseKeyframeTransfers(result.stderr) : null));
511
+ let traced = "";
512
+ let rest = "";
513
+ const onStderr = (chunk) => {
514
+ const lines = (rest + chunk).split("\n");
515
+ rest = lines.pop() ?? "";
516
+ for (const line of lines)
517
+ if (line.includes("transfer_characteristics"))
518
+ traced += `${line}\n`;
519
+ };
520
+ return inExtractionSlot(() => runFfmpeg([...urlInputArgs(videoPath), ...keyframeTransferArgs(videoPath, codec)], {
521
+ onStderr,
522
+ })).then((result) => (result.success ? parseKeyframeTransfers(traced + rest) : null));
512
523
  }
513
524
  /** The first shown frame's colour tags (see {@link firstFrameColourArgs}), probed once per source file. */
514
525
  function readFirstFrameColour(videoPath, codec) {
package/dist/types.d.ts CHANGED
@@ -423,5 +423,8 @@ export interface CapturePerfSummary {
423
423
  declare global {
424
424
  interface Window {
425
425
  __hf?: HfProtocol;
426
+ __HF_VIRTUAL_TIME__?: {
427
+ originalSetTimeout?: typeof setTimeout;
428
+ };
426
429
  }
427
430
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.9.20",
3
+ "version": "0.9.22",
4
4
  "description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,8 +61,8 @@
61
61
  "puppeteer": "~25.11.0",
62
62
  "puppeteer-core": "^25.11.0",
63
63
  "undici": "7.25.0",
64
- "@hyperframes/parsers": "^0.9.20",
65
- "@hyperframes/core": "^0.9.20"
64
+ "@hyperframes/core": "^0.9.22",
65
+ "@hyperframes/parsers": "^0.9.22"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "^25.0.10",