@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.
@@ -103860,6 +103860,15 @@ function isFontResourceError(type, text, locationUrl) {
103860
103860
  `${locationUrl} ${text}`
103861
103861
  );
103862
103862
  }
103863
+ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
103864
+ const deadline = Date.now() + timeoutMs;
103865
+ while (Date.now() < deadline) {
103866
+ const ready = Boolean(await page.evaluate(expression));
103867
+ if (ready) return true;
103868
+ await new Promise((resolve13) => setTimeout(resolve13, intervalMs));
103869
+ }
103870
+ return Boolean(await page.evaluate(expression));
103871
+ }
103863
103872
  async function initializeSession(session) {
103864
103873
  const { page, serverUrl } = session;
103865
103874
  page.on("console", (msg) => {
@@ -103889,14 +103898,26 @@ async function initializeSession(session) {
103889
103898
  if (session.captureMode === "screenshot") {
103890
103899
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
103891
103900
  const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
103892
- await page.waitForFunction(
103901
+ const pageReady2 = await pollPageExpression(
103902
+ page,
103893
103903
  `!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
103894
- { timeout: pageReadyTimeout2 }
103904
+ pageReadyTimeout2
103895
103905
  );
103896
- await page.waitForFunction(
103906
+ if (!pageReady2) {
103907
+ throw new Error(
103908
+ `[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
103909
+ );
103910
+ }
103911
+ const videosReady = await pollPageExpression(
103912
+ page,
103897
103913
  `document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
103898
- { timeout: pageReadyTimeout2 }
103914
+ pageReadyTimeout2
103899
103915
  );
103916
+ if (!videosReady) {
103917
+ throw new Error(
103918
+ `[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
103919
+ );
103920
+ }
103900
103921
  await page.evaluate(`document.fonts?.ready`);
103901
103922
  session.isInitialized = true;
103902
103923
  return;
@@ -106036,6 +106057,102 @@ var MIME_TYPES = {
106036
106057
  ".ttf": "font/ttf",
106037
106058
  ".otf": "font/otf"
106038
106059
  };
106060
+ var VIRTUAL_TIME_SHIM = String.raw`(function() {
106061
+ if (window.__HF_VIRTUAL_TIME__) return;
106062
+
106063
+ var virtualNowMs = 0;
106064
+ var rafId = 1;
106065
+ var rafQueue = [];
106066
+ var OriginalDate = Date;
106067
+ var originalSetTimeout = window.setTimeout.bind(window);
106068
+ var originalClearTimeout = window.clearTimeout.bind(window);
106069
+ var originalSetInterval = window.setInterval.bind(window);
106070
+ var originalClearInterval = window.clearInterval.bind(window);
106071
+ var originalRequestAnimationFrame = window.requestAnimationFrame
106072
+ ? window.requestAnimationFrame.bind(window)
106073
+ : null;
106074
+ var originalCancelAnimationFrame = window.cancelAnimationFrame
106075
+ ? window.cancelAnimationFrame.bind(window)
106076
+ : null;
106077
+
106078
+ function flushAnimationFrame() {
106079
+ if (!rafQueue.length) return;
106080
+ var current = rafQueue.slice();
106081
+ rafQueue.length = 0;
106082
+ for (var i = 0; i < current.length; i++) {
106083
+ var entry = current[i];
106084
+ if (entry.cancelled) continue;
106085
+ try {
106086
+ entry.callback(virtualNowMs);
106087
+ } catch {}
106088
+ }
106089
+ }
106090
+
106091
+ function VirtualDate() {
106092
+ var args = Array.prototype.slice.call(arguments);
106093
+ if (!(this instanceof VirtualDate)) {
106094
+ return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
106095
+ }
106096
+ var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
106097
+ Object.setPrototypeOf(instance, VirtualDate.prototype);
106098
+ return instance;
106099
+ }
106100
+
106101
+ VirtualDate.prototype = OriginalDate.prototype;
106102
+ Object.setPrototypeOf(VirtualDate, OriginalDate);
106103
+ VirtualDate.now = function() { return virtualNowMs; };
106104
+ VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
106105
+ VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
106106
+
106107
+ try {
106108
+ Object.defineProperty(window, "Date", {
106109
+ configurable: true,
106110
+ writable: true,
106111
+ value: VirtualDate,
106112
+ });
106113
+ } catch {}
106114
+
106115
+ if (window.performance && typeof window.performance.now === "function") {
106116
+ try {
106117
+ Object.defineProperty(window.performance, "now", {
106118
+ configurable: true,
106119
+ value: function() { return virtualNowMs; },
106120
+ });
106121
+ } catch {}
106122
+ }
106123
+
106124
+ window.requestAnimationFrame = function(callback) {
106125
+ if (typeof callback !== "function") return 0;
106126
+ var entry = { id: rafId++, callback: callback, cancelled: false };
106127
+ rafQueue.push(entry);
106128
+ return entry.id;
106129
+ };
106130
+ window.cancelAnimationFrame = function(id) {
106131
+ for (var i = 0; i < rafQueue.length; i++) {
106132
+ if (rafQueue[i].id === id) {
106133
+ rafQueue[i].cancelled = true;
106134
+ }
106135
+ }
106136
+ };
106137
+
106138
+ window.__HF_VIRTUAL_TIME__ = {
106139
+ originalSetTimeout: originalSetTimeout,
106140
+ originalClearTimeout: originalClearTimeout,
106141
+ originalSetInterval: originalSetInterval,
106142
+ originalClearInterval: originalClearInterval,
106143
+ originalRequestAnimationFrame: originalRequestAnimationFrame,
106144
+ originalCancelAnimationFrame: originalCancelAnimationFrame,
106145
+ seekToTime: function(nextTimeMs) {
106146
+ var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
106147
+ virtualNowMs = safeTimeMs;
106148
+ flushAnimationFrame();
106149
+ return virtualNowMs;
106150
+ },
106151
+ getTime: function() {
106152
+ return virtualNowMs;
106153
+ },
106154
+ };
106155
+ })();`;
106039
106156
  var RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
106040
106157
  var RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
106041
106158
  var RENDER_SEEK_STEP = Math.max(
@@ -106047,6 +106164,10 @@ var RENDER_SEEK_OFFSET_FRACTION = Math.max(
106047
106164
  Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
106048
106165
  );
106049
106166
  var RENDER_MODE_SCRIPT = `(function() {
106167
+ var __realSetTimeout =
106168
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
106169
+ ? window.__HF_VIRTUAL_TIME__.originalSetTimeout
106170
+ : window.setTimeout.bind(window);
106050
106171
  var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
106051
106172
  var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
106052
106173
  var __seekStep = ${RENDER_SEEK_STEP};
@@ -106140,23 +106261,56 @@ var RENDER_MODE_SCRIPT = `(function() {
106140
106261
  window.__renderReady = true;
106141
106262
  return;
106142
106263
  }
106143
- setTimeout(waitForPlayer, 50);
106264
+ __realSetTimeout(waitForPlayer, 50);
106144
106265
  return;
106145
106266
  }
106146
106267
  if (installMediaFallbackPlayer()) {
106147
106268
  return;
106148
106269
  }
106149
- setTimeout(waitForPlayer, 50);
106270
+ __realSetTimeout(waitForPlayer, 50);
106150
106271
  }
106151
106272
  waitForPlayer();
106152
106273
  })();`;
106153
106274
  var HF_BRIDGE_SCRIPT = `(function() {
106275
+ var __realSetInterval =
106276
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
106277
+ ? window.__HF_VIRTUAL_TIME__.originalSetInterval
106278
+ : window.setInterval.bind(window);
106279
+ var __realClearInterval =
106280
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
106281
+ ? window.__HF_VIRTUAL_TIME__.originalClearInterval
106282
+ : window.clearInterval.bind(window);
106154
106283
  function getDeclaredDuration() {
106155
106284
  var root = document.querySelector('[data-composition-id]');
106156
106285
  if (!root) return 0;
106157
106286
  var d = Number(root.getAttribute('data-duration'));
106158
106287
  return Number.isFinite(d) && d > 0 ? d : 0;
106159
106288
  }
106289
+ function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
106290
+ var frames;
106291
+ try {
106292
+ frames = frameWindow.frames;
106293
+ } catch (_error) {
106294
+ return;
106295
+ }
106296
+ if (!frames || typeof frames.length !== "number") return;
106297
+ for (var i = 0; i < frames.length; i++) {
106298
+ var childWindow = null;
106299
+ try {
106300
+ childWindow = frames[i];
106301
+ if (!childWindow || childWindow === frameWindow) continue;
106302
+ if (
106303
+ childWindow.__HF_VIRTUAL_TIME__ &&
106304
+ typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
106305
+ ) {
106306
+ childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
106307
+ }
106308
+ } catch (_error) {
106309
+ continue;
106310
+ }
106311
+ seekSameOriginChildFrames(childWindow, nextTimeMs);
106312
+ }
106313
+ }
106160
106314
  function bridge() {
106161
106315
  var p = window.__player;
106162
106316
  if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
@@ -106167,13 +106321,20 @@ var HF_BRIDGE_SCRIPT = `(function() {
106167
106321
  var d = p.getDuration();
106168
106322
  return d > 0 ? d : getDeclaredDuration();
106169
106323
  },
106170
- seek: function(t) { p.renderSeek(t); },
106324
+ seek: function(t) {
106325
+ p.renderSeek(t);
106326
+ var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
106327
+ if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
106328
+ window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
106329
+ }
106330
+ seekSameOriginChildFrames(window, nextTimeMs);
106331
+ },
106171
106332
  };
106172
106333
  return true;
106173
106334
  }
106174
106335
  if (bridge()) return;
106175
- var iv = setInterval(function() {
106176
- if (bridge()) clearInterval(iv);
106336
+ var iv = __realSetInterval(function() {
106337
+ if (bridge()) __realClearInterval(iv);
106177
106338
  }, 50);
106178
106339
  })();`;
106179
106340
  function stripEmbeddedRuntimeScripts(html) {
@@ -106235,8 +106396,22 @@ function injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbedded) {
106235
106396
  }
106236
106397
  return html;
106237
106398
  }
106399
+ function injectScriptsAtHeadStart(html, scripts) {
106400
+ if (scripts.length === 0) return html;
106401
+ const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
106402
+ if (html.includes("<head")) {
106403
+ return html.replace(/<head\b[^>]*>/i, (match2) => `${match2}
106404
+ ${headTags}`);
106405
+ }
106406
+ if (html.includes("<body")) {
106407
+ return html.replace("<body", () => `${headTags}
106408
+ <body`);
106409
+ }
106410
+ return headTags + "\n" + html;
106411
+ }
106238
106412
  function createFileServer2(options) {
106239
106413
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
106414
+ const preHeadScripts = options.preHeadScripts ?? [];
106240
106415
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
106241
106416
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
106242
106417
  const app = new Hono2();
@@ -106260,7 +106435,11 @@ function createFileServer2(options) {
106260
106435
  if (ext === ".html") {
106261
106436
  const rawHtml = readFileSync6(filePath, "utf-8");
106262
106437
  const isIndex = relativePath === "index.html";
106263
- const html = isIndex ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
106438
+ let html = rawHtml;
106439
+ if (preHeadScripts.length > 0) {
106440
+ html = injectScriptsAtHeadStart(html, preHeadScripts);
106441
+ }
106442
+ html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
106264
106443
  return c.text(html, 200, { "Content-Type": contentType });
106265
106444
  }
106266
106445
  const content = readFileSync6(filePath);
@@ -106708,6 +106887,37 @@ function dedupeElementsById(elements) {
106708
106887
  }
106709
106888
  return Array.from(deduped.values());
106710
106889
  }
106890
+ var INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
106891
+ function stripJsComments(source2) {
106892
+ return source2.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
106893
+ }
106894
+ function detectRenderModeHints(html) {
106895
+ const reasons = [];
106896
+ const { document: document2 } = parseHTML(html);
106897
+ if (document2.querySelector("iframe")) {
106898
+ reasons.push({
106899
+ code: "iframe",
106900
+ message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
106901
+ });
106902
+ }
106903
+ let scriptMatch;
106904
+ const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
106905
+ while ((scriptMatch = scriptPattern.exec(html)) !== null) {
106906
+ const attrs = scriptMatch[1] || "";
106907
+ if (/\bsrc\s*=/i.test(attrs)) continue;
106908
+ const content = stripJsComments(scriptMatch[2] || "");
106909
+ if (!/requestAnimationFrame\s*\(/.test(content)) continue;
106910
+ reasons.push({
106911
+ code: "requestAnimationFrame",
106912
+ message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
106913
+ });
106914
+ break;
106915
+ }
106916
+ return {
106917
+ recommendScreenshot: reasons.length > 0,
106918
+ reasons
106919
+ };
106920
+ }
106711
106921
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
106712
106922
  let filePath = src;
106713
106923
  if (isHttpUrl(src)) {
@@ -107271,6 +107481,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
107271
107481
  /(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
107272
107482
  "$1"
107273
107483
  );
107484
+ const renderModeHints = detectRenderModeHints(sanitizedHtml);
107274
107485
  const coalescedHtml = await injectDeterministicFontFaces(
107275
107486
  coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
107276
107487
  );
@@ -107314,7 +107525,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
107314
107525
  externalAssets,
107315
107526
  width,
107316
107527
  height,
107317
- staticDuration
107528
+ staticDuration,
107529
+ renderModeHints
107318
107530
  };
107319
107531
  }
107320
107532
  async function discoverMediaFromBrowser(page) {
@@ -107408,7 +107620,8 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
107408
107620
  subCompositions,
107409
107621
  videos,
107410
107622
  audios,
107411
- unresolvedCompositions: remaining
107623
+ unresolvedCompositions: remaining,
107624
+ renderModeHints: compiled.renderModeHints
107412
107625
  };
107413
107626
  }
107414
107627
 
@@ -107545,11 +107758,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
107545
107758
  end: a.end,
107546
107759
  mediaStart: a.mediaStart
107547
107760
  })),
107548
- subCompositions: Array.from(compiled.subCompositions.keys())
107761
+ subCompositions: Array.from(compiled.subCompositions.keys()),
107762
+ renderModeHints: compiled.renderModeHints
107549
107763
  };
107550
107764
  writeFileSync4(join15(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
107551
107765
  }
107552
107766
  }
107767
+ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
107768
+ if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
107769
+ cfg.forceScreenshot = true;
107770
+ log.warn("Auto-selected screenshot capture mode for render compatibility", {
107771
+ reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
107772
+ reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
107773
+ });
107774
+ }
107553
107775
  function createRenderJob(config2) {
107554
107776
  return {
107555
107777
  id: randomUUID(),
@@ -107662,6 +107884,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107662
107884
  let compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"));
107663
107885
  assertNotAborted();
107664
107886
  perfStages.compileOnlyMs = Date.now() - compileStart;
107887
+ applyRenderModeHints(cfg, compiled, log);
107665
107888
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
107666
107889
  log.info("Compiled composition metadata", {
107667
107890
  entryFile,
@@ -107669,7 +107892,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107669
107892
  width: compiled.width,
107670
107893
  height: compiled.height,
107671
107894
  videoCount: compiled.videos.length,
107672
- audioCount: compiled.audios.length
107895
+ audioCount: compiled.audios.length,
107896
+ renderModeHints: compiled.renderModeHints
107673
107897
  });
107674
107898
  const composition = {
107675
107899
  duration: compiled.staticDuration,
@@ -107689,7 +107913,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107689
107913
  fileServer = await createFileServer2({
107690
107914
  projectDir,
107691
107915
  compiledDir: join15(workDir, "compiled"),
107692
- port: 0
107916
+ port: 0,
107917
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
107693
107918
  });
107694
107919
  assertNotAborted();
107695
107920
  const captureOpts = {
@@ -107932,7 +108157,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
107932
108157
  fileServer = await createFileServer2({
107933
108158
  projectDir,
107934
108159
  compiledDir: join15(workDir, "compiled"),
107935
- port: 0
108160
+ port: 0,
108161
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
107936
108162
  });
107937
108163
  assertNotAborted();
107938
108164
  }