@hyperframes/producer 0.4.40 → 0.4.41

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.
@@ -101459,7 +101459,8 @@ var DEFAULT_CONFIG = {
101459
101459
  forceScreenshot: false,
101460
101460
  enableChunkedEncode: false,
101461
101461
  chunkSizeFrames: 360,
101462
- enableStreamingEncode: false,
101462
+ enableStreamingEncode: true,
101463
+ streamingEncodeMaxDurationSeconds: 240,
101463
101464
  ffmpegEncodeTimeout: 6e5,
101464
101465
  ffmpegProcessTimeout: 3e5,
101465
101466
  ffmpegStreamingTimeout: 6e5,
@@ -101521,6 +101522,13 @@ function resolveConfig(overrides) {
101521
101522
  "PRODUCER_ENABLE_STREAMING_ENCODE",
101522
101523
  DEFAULT_CONFIG.enableStreamingEncode
101523
101524
  ),
101525
+ streamingEncodeMaxDurationSeconds: Math.max(
101526
+ 0,
101527
+ envNum(
101528
+ "PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS",
101529
+ DEFAULT_CONFIG.streamingEncodeMaxDurationSeconds
101530
+ )
101531
+ ),
101524
101532
  ffmpegEncodeTimeout: envNum("FFMPEG_ENCODE_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegEncodeTimeout),
101525
101533
  ffmpegProcessTimeout: envNum("FFMPEG_PROCESS_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegProcessTimeout),
101526
101534
  ffmpegStreamingTimeout: envNum(
@@ -104484,6 +104492,23 @@ async function applyVideoMetadataHints(page, hints) {
104484
104492
  [...hints]
104485
104493
  );
104486
104494
  }
104495
+ async function waitForOptionalTailwindReady(page, timeoutMs) {
104496
+ const hasTailwindReady = await page.evaluate(
104497
+ `(() => { const ready = window.__tailwindReady; return !!ready && typeof ready.then === "function"; })()`
104498
+ );
104499
+ if (!hasTailwindReady) return;
104500
+ const ready = await Promise.race([
104501
+ page.evaluate(
104502
+ `Promise.resolve(window.__tailwindReady).then(() => true, () => false)`
104503
+ ),
104504
+ new Promise((resolve14) => setTimeout(() => resolve14(false), timeoutMs))
104505
+ ]);
104506
+ if (!ready) {
104507
+ throw new Error(
104508
+ `[FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms. Tailwind browser runtime must finish before frame capture starts.`
104509
+ );
104510
+ }
104511
+ }
104487
104512
  async function initializeSession(session) {
104488
104513
  const { page, serverUrl } = session;
104489
104514
  page.on("console", (msg) => {
@@ -104540,6 +104565,7 @@ async function initializeSession(session) {
104540
104565
  );
104541
104566
  }
104542
104567
  await page.evaluate(`document.fonts?.ready`);
104568
+ await waitForOptionalTailwindReady(page, pageReadyTimeout2);
104543
104569
  if (session.options.format === "png") {
104544
104570
  await initTransparentBackground(session.page);
104545
104571
  }
@@ -104605,6 +104631,7 @@ async function initializeSession(session) {
104605
104631
  await new Promise((r) => setTimeout(r, 100));
104606
104632
  }
104607
104633
  await page.evaluate(`document.fonts?.ready`);
104634
+ await waitForOptionalTailwindReady(page, pageReadyTimeout);
104608
104635
  warmupRunning = false;
104609
104636
  session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
104610
104637
  if (session.options.format === "png") {
@@ -111635,6 +111662,27 @@ function createRenderJob(config2) {
111635
111662
  function normalizeCompositionSrcPath(srcPath) {
111636
111663
  return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
111637
111664
  }
111665
+ function createStandaloneEntryRenderClone(root, host) {
111666
+ const hostClone = host.cloneNode(true);
111667
+ hostClone.setAttribute("data-start", "0");
111668
+ if (root === host) return hostClone;
111669
+ const rootClone = root.cloneNode(false);
111670
+ rootClone.appendChild(hostClone);
111671
+ return rootClone;
111672
+ }
111673
+ function replaceBodyWithRenderClone(body, renderClone) {
111674
+ while (body.firstChild) {
111675
+ body.removeChild(body.firstChild);
111676
+ }
111677
+ body.appendChild(renderClone);
111678
+ }
111679
+ function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSeconds) {
111680
+ if (!cfg.enableStreamingEncode) return false;
111681
+ if (outputFormat === "png-sequence") return false;
111682
+ if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
111683
+ if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
111684
+ return workerCount === 1;
111685
+ }
111638
111686
  function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
111639
111687
  const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
111640
111688
  const { document: document2 } = parseHTML(indexHtml);
@@ -111649,16 +111697,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
111649
111697
  (candidate) => candidate.hasAttribute("data-composition-id")
111650
111698
  ) ?? null;
111651
111699
  if (!root) return null;
111652
- const hostClone = host.cloneNode(true);
111653
- hostClone.setAttribute("data-start", "0");
111654
- body.innerHTML = "";
111655
- if (root === host) {
111656
- body.appendChild(hostClone);
111657
- return document2.toString();
111658
- }
111659
- const rootClone = root.cloneNode(false);
111660
- rootClone.appendChild(hostClone);
111661
- body.appendChild(rootClone);
111700
+ const renderClone = createStandaloneEntryRenderClone(root, host);
111701
+ replaceBodyWithRenderClone(body, renderClone);
111662
111702
  return document2.toString();
111663
111703
  }
111664
111704
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
@@ -111690,7 +111730,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111690
111730
  }
111691
111731
  const enableChunkedEncode = cfg.enableChunkedEncode;
111692
111732
  const chunkedEncodeSize = cfg.chunkSizeFrames;
111693
- const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
111694
111733
  let peakRssBytes = 0;
111695
111734
  let peakHeapUsedBytes = 0;
111696
111735
  const sampleMemory = () => {
@@ -112259,6 +112298,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
112259
112298
  await closeCaptureSession(probeSession);
112260
112299
  probeSession = null;
112261
112300
  }
112301
+ let useStreamingEncode = shouldUseStreamingEncode(cfg, outputFormat, workerCount, job.duration);
112302
+ log.info("streaming-encode gate", {
112303
+ enabled: useStreamingEncode,
112304
+ configFlag: cfg.enableStreamingEncode,
112305
+ outputFormat,
112306
+ workerCount,
112307
+ durationSeconds: job.duration,
112308
+ maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
112309
+ });
112262
112310
  const captureAttempts = [];
112263
112311
  const FORMAT_EXT = {
112264
112312
  mp4: ".mp4",
@@ -112798,28 +112846,47 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
112798
112846
  } else {
112799
112847
  let streamingEncoder = null;
112800
112848
  let streamingEncoderClosed = false;
112801
- if (enableStreamingEncode) {
112802
- streamingEncoder = await spawnStreamingEncoder(
112803
- videoOnlyPath,
112804
- {
112805
- fps: job.config.fps,
112806
- width,
112807
- height,
112808
- codec: preset.codec,
112809
- preset: preset.preset,
112810
- quality: effectiveQuality,
112811
- bitrate: effectiveBitrate,
112812
- pixelFormat: preset.pixelFormat,
112813
- useGpu: job.config.useGpu,
112814
- imageFormat: captureOptions.format || "jpeg",
112815
- hdr: preset.hdr
112816
- },
112817
- abortSignal
112818
- );
112819
- assertNotAborted();
112849
+ if (useStreamingEncode) {
112850
+ try {
112851
+ streamingEncoder = await spawnStreamingEncoder(
112852
+ videoOnlyPath,
112853
+ {
112854
+ fps: job.config.fps,
112855
+ width,
112856
+ height,
112857
+ codec: preset.codec,
112858
+ preset: preset.preset,
112859
+ quality: effectiveQuality,
112860
+ bitrate: effectiveBitrate,
112861
+ pixelFormat: preset.pixelFormat,
112862
+ useGpu: job.config.useGpu,
112863
+ imageFormat: captureOptions.format || "jpeg",
112864
+ hdr: preset.hdr
112865
+ },
112866
+ abortSignal
112867
+ );
112868
+ assertNotAborted();
112869
+ } catch (err) {
112870
+ if (abortSignal?.aborted) {
112871
+ if (streamingEncoder && !streamingEncoderClosed) {
112872
+ await streamingEncoder.close().catch(() => {
112873
+ });
112874
+ streamingEncoderClosed = true;
112875
+ }
112876
+ throw err;
112877
+ }
112878
+ useStreamingEncode = false;
112879
+ streamingEncoder = null;
112880
+ log.warn("[Render] Streaming encoder spawn failed; falling back to disk-frame encode.", {
112881
+ error: err instanceof Error ? err.message : String(err),
112882
+ outputFormat,
112883
+ workerCount,
112884
+ durationSeconds: job.duration
112885
+ });
112886
+ }
112820
112887
  }
112821
112888
  try {
112822
- if (enableStreamingEncode && streamingEncoder) {
112889
+ if (useStreamingEncode && streamingEncoder) {
112823
112890
  const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
112824
112891
  const currentEncoder = streamingEncoder;
112825
112892
  if (workerCount > 1) {