@hyperframes/producer 0.4.5 → 0.4.6

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.
package/dist/index.js CHANGED
@@ -101071,6 +101071,15 @@ function isFontResourceError(type, text, locationUrl) {
101071
101071
  `${locationUrl} ${text}`
101072
101072
  );
101073
101073
  }
101074
+ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
101075
+ const deadline = Date.now() + timeoutMs;
101076
+ while (Date.now() < deadline) {
101077
+ const ready = Boolean(await page.evaluate(expression));
101078
+ if (ready) return true;
101079
+ await new Promise((resolve13) => setTimeout(resolve13, intervalMs));
101080
+ }
101081
+ return Boolean(await page.evaluate(expression));
101082
+ }
101074
101083
  async function initializeSession(session) {
101075
101084
  const { page, serverUrl } = session;
101076
101085
  page.on("console", (msg) => {
@@ -101100,14 +101109,26 @@ async function initializeSession(session) {
101100
101109
  if (session.captureMode === "screenshot") {
101101
101110
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
101102
101111
  const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
101103
- await page.waitForFunction(
101112
+ const pageReady2 = await pollPageExpression(
101113
+ page,
101104
101114
  `!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
101105
- { timeout: pageReadyTimeout2 }
101115
+ pageReadyTimeout2
101106
101116
  );
101107
- await page.waitForFunction(
101117
+ if (!pageReady2) {
101118
+ throw new Error(
101119
+ `[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
101120
+ );
101121
+ }
101122
+ const videosReady = await pollPageExpression(
101123
+ page,
101108
101124
  `document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
101109
- { timeout: pageReadyTimeout2 }
101125
+ pageReadyTimeout2
101110
101126
  );
101127
+ if (!videosReady) {
101128
+ throw new Error(
101129
+ `[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
101130
+ );
101131
+ }
101111
101132
  await page.evaluate(`document.fonts?.ready`);
101112
101133
  session.isInitialized = true;
101113
101134
  return;
@@ -105871,6 +105892,102 @@ var MIME_TYPES = {
105871
105892
  ".ttf": "font/ttf",
105872
105893
  ".otf": "font/otf"
105873
105894
  };
105895
+ var VIRTUAL_TIME_SHIM = String.raw`(function() {
105896
+ if (window.__HF_VIRTUAL_TIME__) return;
105897
+
105898
+ var virtualNowMs = 0;
105899
+ var rafId = 1;
105900
+ var rafQueue = [];
105901
+ var OriginalDate = Date;
105902
+ var originalSetTimeout = window.setTimeout.bind(window);
105903
+ var originalClearTimeout = window.clearTimeout.bind(window);
105904
+ var originalSetInterval = window.setInterval.bind(window);
105905
+ var originalClearInterval = window.clearInterval.bind(window);
105906
+ var originalRequestAnimationFrame = window.requestAnimationFrame
105907
+ ? window.requestAnimationFrame.bind(window)
105908
+ : null;
105909
+ var originalCancelAnimationFrame = window.cancelAnimationFrame
105910
+ ? window.cancelAnimationFrame.bind(window)
105911
+ : null;
105912
+
105913
+ function flushAnimationFrame() {
105914
+ if (!rafQueue.length) return;
105915
+ var current = rafQueue.slice();
105916
+ rafQueue.length = 0;
105917
+ for (var i = 0; i < current.length; i++) {
105918
+ var entry = current[i];
105919
+ if (entry.cancelled) continue;
105920
+ try {
105921
+ entry.callback(virtualNowMs);
105922
+ } catch {}
105923
+ }
105924
+ }
105925
+
105926
+ function VirtualDate() {
105927
+ var args = Array.prototype.slice.call(arguments);
105928
+ if (!(this instanceof VirtualDate)) {
105929
+ return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
105930
+ }
105931
+ var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
105932
+ Object.setPrototypeOf(instance, VirtualDate.prototype);
105933
+ return instance;
105934
+ }
105935
+
105936
+ VirtualDate.prototype = OriginalDate.prototype;
105937
+ Object.setPrototypeOf(VirtualDate, OriginalDate);
105938
+ VirtualDate.now = function() { return virtualNowMs; };
105939
+ VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
105940
+ VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
105941
+
105942
+ try {
105943
+ Object.defineProperty(window, "Date", {
105944
+ configurable: true,
105945
+ writable: true,
105946
+ value: VirtualDate,
105947
+ });
105948
+ } catch {}
105949
+
105950
+ if (window.performance && typeof window.performance.now === "function") {
105951
+ try {
105952
+ Object.defineProperty(window.performance, "now", {
105953
+ configurable: true,
105954
+ value: function() { return virtualNowMs; },
105955
+ });
105956
+ } catch {}
105957
+ }
105958
+
105959
+ window.requestAnimationFrame = function(callback) {
105960
+ if (typeof callback !== "function") return 0;
105961
+ var entry = { id: rafId++, callback: callback, cancelled: false };
105962
+ rafQueue.push(entry);
105963
+ return entry.id;
105964
+ };
105965
+ window.cancelAnimationFrame = function(id) {
105966
+ for (var i = 0; i < rafQueue.length; i++) {
105967
+ if (rafQueue[i].id === id) {
105968
+ rafQueue[i].cancelled = true;
105969
+ }
105970
+ }
105971
+ };
105972
+
105973
+ window.__HF_VIRTUAL_TIME__ = {
105974
+ originalSetTimeout: originalSetTimeout,
105975
+ originalClearTimeout: originalClearTimeout,
105976
+ originalSetInterval: originalSetInterval,
105977
+ originalClearInterval: originalClearInterval,
105978
+ originalRequestAnimationFrame: originalRequestAnimationFrame,
105979
+ originalCancelAnimationFrame: originalCancelAnimationFrame,
105980
+ seekToTime: function(nextTimeMs) {
105981
+ var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
105982
+ virtualNowMs = safeTimeMs;
105983
+ flushAnimationFrame();
105984
+ return virtualNowMs;
105985
+ },
105986
+ getTime: function() {
105987
+ return virtualNowMs;
105988
+ },
105989
+ };
105990
+ })();`;
105874
105991
  var RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
105875
105992
  var RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
105876
105993
  var RENDER_SEEK_STEP = Math.max(
@@ -105882,6 +105999,10 @@ var RENDER_SEEK_OFFSET_FRACTION = Math.max(
105882
105999
  Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
105883
106000
  );
105884
106001
  var RENDER_MODE_SCRIPT = `(function() {
106002
+ var __realSetTimeout =
106003
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
106004
+ ? window.__HF_VIRTUAL_TIME__.originalSetTimeout
106005
+ : window.setTimeout.bind(window);
105885
106006
  var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
105886
106007
  var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
105887
106008
  var __seekStep = ${RENDER_SEEK_STEP};
@@ -105975,23 +106096,56 @@ var RENDER_MODE_SCRIPT = `(function() {
105975
106096
  window.__renderReady = true;
105976
106097
  return;
105977
106098
  }
105978
- setTimeout(waitForPlayer, 50);
106099
+ __realSetTimeout(waitForPlayer, 50);
105979
106100
  return;
105980
106101
  }
105981
106102
  if (installMediaFallbackPlayer()) {
105982
106103
  return;
105983
106104
  }
105984
- setTimeout(waitForPlayer, 50);
106105
+ __realSetTimeout(waitForPlayer, 50);
105985
106106
  }
105986
106107
  waitForPlayer();
105987
106108
  })();`;
105988
106109
  var HF_BRIDGE_SCRIPT = `(function() {
106110
+ var __realSetInterval =
106111
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
106112
+ ? window.__HF_VIRTUAL_TIME__.originalSetInterval
106113
+ : window.setInterval.bind(window);
106114
+ var __realClearInterval =
106115
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
106116
+ ? window.__HF_VIRTUAL_TIME__.originalClearInterval
106117
+ : window.clearInterval.bind(window);
105989
106118
  function getDeclaredDuration() {
105990
106119
  var root = document.querySelector('[data-composition-id]');
105991
106120
  if (!root) return 0;
105992
106121
  var d = Number(root.getAttribute('data-duration'));
105993
106122
  return Number.isFinite(d) && d > 0 ? d : 0;
105994
106123
  }
106124
+ function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
106125
+ var frames;
106126
+ try {
106127
+ frames = frameWindow.frames;
106128
+ } catch (_error) {
106129
+ return;
106130
+ }
106131
+ if (!frames || typeof frames.length !== "number") return;
106132
+ for (var i = 0; i < frames.length; i++) {
106133
+ var childWindow = null;
106134
+ try {
106135
+ childWindow = frames[i];
106136
+ if (!childWindow || childWindow === frameWindow) continue;
106137
+ if (
106138
+ childWindow.__HF_VIRTUAL_TIME__ &&
106139
+ typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
106140
+ ) {
106141
+ childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
106142
+ }
106143
+ } catch (_error) {
106144
+ continue;
106145
+ }
106146
+ seekSameOriginChildFrames(childWindow, nextTimeMs);
106147
+ }
106148
+ }
105995
106149
  function bridge() {
105996
106150
  var p = window.__player;
105997
106151
  if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
@@ -106002,13 +106156,20 @@ var HF_BRIDGE_SCRIPT = `(function() {
106002
106156
  var d = p.getDuration();
106003
106157
  return d > 0 ? d : getDeclaredDuration();
106004
106158
  },
106005
- seek: function(t) { p.renderSeek(t); },
106159
+ seek: function(t) {
106160
+ p.renderSeek(t);
106161
+ var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
106162
+ if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
106163
+ window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
106164
+ }
106165
+ seekSameOriginChildFrames(window, nextTimeMs);
106166
+ },
106006
106167
  };
106007
106168
  return true;
106008
106169
  }
106009
106170
  if (bridge()) return;
106010
- var iv = setInterval(function() {
106011
- if (bridge()) clearInterval(iv);
106171
+ var iv = __realSetInterval(function() {
106172
+ if (bridge()) __realClearInterval(iv);
106012
106173
  }, 50);
106013
106174
  })();`;
106014
106175
  function stripEmbeddedRuntimeScripts(html) {
@@ -106070,8 +106231,22 @@ function injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbedded) {
106070
106231
  }
106071
106232
  return html;
106072
106233
  }
106234
+ function injectScriptsAtHeadStart(html, scripts) {
106235
+ if (scripts.length === 0) return html;
106236
+ const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
106237
+ if (html.includes("<head")) {
106238
+ return html.replace(/<head\b[^>]*>/i, (match2) => `${match2}
106239
+ ${headTags}`);
106240
+ }
106241
+ if (html.includes("<body")) {
106242
+ return html.replace("<body", () => `${headTags}
106243
+ <body`);
106244
+ }
106245
+ return headTags + "\n" + html;
106246
+ }
106073
106247
  function createFileServer2(options) {
106074
106248
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
106249
+ const preHeadScripts = options.preHeadScripts ?? [];
106075
106250
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
106076
106251
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
106077
106252
  const app = new Hono2();
@@ -106095,7 +106270,11 @@ function createFileServer2(options) {
106095
106270
  if (ext === ".html") {
106096
106271
  const rawHtml = readFileSync6(filePath, "utf-8");
106097
106272
  const isIndex = relativePath === "index.html";
106098
- const html = isIndex ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
106273
+ let html = rawHtml;
106274
+ if (preHeadScripts.length > 0) {
106275
+ html = injectScriptsAtHeadStart(html, preHeadScripts);
106276
+ }
106277
+ html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
106099
106278
  return c.text(html, 200, { "Content-Type": contentType });
106100
106279
  }
106101
106280
  const content = readFileSync6(filePath);
@@ -106543,6 +106722,37 @@ function dedupeElementsById(elements) {
106543
106722
  }
106544
106723
  return Array.from(deduped.values());
106545
106724
  }
106725
+ var INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
106726
+ function stripJsComments(source2) {
106727
+ return source2.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
106728
+ }
106729
+ function detectRenderModeHints(html) {
106730
+ const reasons = [];
106731
+ const { document: document2 } = parseHTML(html);
106732
+ if (document2.querySelector("iframe")) {
106733
+ reasons.push({
106734
+ code: "iframe",
106735
+ message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
106736
+ });
106737
+ }
106738
+ let scriptMatch;
106739
+ const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
106740
+ while ((scriptMatch = scriptPattern.exec(html)) !== null) {
106741
+ const attrs = scriptMatch[1] || "";
106742
+ if (/\bsrc\s*=/i.test(attrs)) continue;
106743
+ const content = stripJsComments(scriptMatch[2] || "");
106744
+ if (!/requestAnimationFrame\s*\(/.test(content)) continue;
106745
+ reasons.push({
106746
+ code: "requestAnimationFrame",
106747
+ message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
106748
+ });
106749
+ break;
106750
+ }
106751
+ return {
106752
+ recommendScreenshot: reasons.length > 0,
106753
+ reasons
106754
+ };
106755
+ }
106546
106756
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
106547
106757
  let filePath = src;
106548
106758
  if (isHttpUrl(src)) {
@@ -107106,6 +107316,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
107106
107316
  /(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
107107
107317
  "$1"
107108
107318
  );
107319
+ const renderModeHints = detectRenderModeHints(sanitizedHtml);
107109
107320
  const coalescedHtml = await injectDeterministicFontFaces(
107110
107321
  coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
107111
107322
  );
@@ -107149,7 +107360,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
107149
107360
  externalAssets,
107150
107361
  width,
107151
107362
  height,
107152
- staticDuration
107363
+ staticDuration,
107364
+ renderModeHints
107153
107365
  };
107154
107366
  }
107155
107367
  async function discoverMediaFromBrowser(page) {
@@ -107243,7 +107455,8 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
107243
107455
  subCompositions,
107244
107456
  videos,
107245
107457
  audios,
107246
- unresolvedCompositions: remaining
107458
+ unresolvedCompositions: remaining,
107459
+ renderModeHints: compiled.renderModeHints
107247
107460
  };
107248
107461
  }
107249
107462
 
@@ -107380,11 +107593,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
107380
107593
  end: a.end,
107381
107594
  mediaStart: a.mediaStart
107382
107595
  })),
107383
- subCompositions: Array.from(compiled.subCompositions.keys())
107596
+ subCompositions: Array.from(compiled.subCompositions.keys()),
107597
+ renderModeHints: compiled.renderModeHints
107384
107598
  };
107385
107599
  writeFileSync4(join15(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
107386
107600
  }
107387
107601
  }
107602
+ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
107603
+ if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
107604
+ cfg.forceScreenshot = true;
107605
+ log.warn("Auto-selected screenshot capture mode for render compatibility", {
107606
+ reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
107607
+ reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
107608
+ });
107609
+ }
107388
107610
  function createRenderJob(config2) {
107389
107611
  return {
107390
107612
  id: randomUUID(),
@@ -107497,6 +107719,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107497
107719
  let compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"));
107498
107720
  assertNotAborted();
107499
107721
  perfStages.compileOnlyMs = Date.now() - compileStart;
107722
+ applyRenderModeHints(cfg, compiled, log);
107500
107723
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
107501
107724
  log.info("Compiled composition metadata", {
107502
107725
  entryFile,
@@ -107504,7 +107727,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107504
107727
  width: compiled.width,
107505
107728
  height: compiled.height,
107506
107729
  videoCount: compiled.videos.length,
107507
- audioCount: compiled.audios.length
107730
+ audioCount: compiled.audios.length,
107731
+ renderModeHints: compiled.renderModeHints
107508
107732
  });
107509
107733
  const composition = {
107510
107734
  duration: compiled.staticDuration,
@@ -107524,7 +107748,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107524
107748
  fileServer = await createFileServer2({
107525
107749
  projectDir,
107526
107750
  compiledDir: join15(workDir, "compiled"),
107527
- port: 0
107751
+ port: 0,
107752
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
107528
107753
  });
107529
107754
  assertNotAborted();
107530
107755
  const captureOpts = {
@@ -107767,7 +107992,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107767
107992
  fileServer = await createFileServer2({
107768
107993
  projectDir,
107769
107994
  compiledDir: join15(workDir, "compiled"),
107770
- port: 0
107995
+ port: 0,
107996
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
107771
107997
  });
107772
107998
  assertNotAborted();
107773
107999
  }