@hyperframes/producer 0.5.0-alpha.13 → 0.5.0-alpha.14

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
@@ -98670,7 +98670,8 @@ var DEFAULT_CONFIG = {
98670
98670
  forceScreenshot: false,
98671
98671
  enableChunkedEncode: false,
98672
98672
  chunkSizeFrames: 360,
98673
- enableStreamingEncode: false,
98673
+ enableStreamingEncode: true,
98674
+ streamingEncodeMaxDurationSeconds: 240,
98674
98675
  ffmpegEncodeTimeout: 6e5,
98675
98676
  ffmpegProcessTimeout: 3e5,
98676
98677
  ffmpegStreamingTimeout: 6e5,
@@ -98732,6 +98733,13 @@ function resolveConfig(overrides) {
98732
98733
  "PRODUCER_ENABLE_STREAMING_ENCODE",
98733
98734
  DEFAULT_CONFIG.enableStreamingEncode
98734
98735
  ),
98736
+ streamingEncodeMaxDurationSeconds: Math.max(
98737
+ 0,
98738
+ envNum(
98739
+ "PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS",
98740
+ DEFAULT_CONFIG.streamingEncodeMaxDurationSeconds
98741
+ )
98742
+ ),
98735
98743
  ffmpegEncodeTimeout: envNum("FFMPEG_ENCODE_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegEncodeTimeout),
98736
98744
  ffmpegProcessTimeout: envNum("FFMPEG_PROCESS_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegProcessTimeout),
98737
98745
  ffmpegStreamingTimeout: envNum(
@@ -101695,6 +101703,23 @@ async function applyVideoMetadataHints(page, hints) {
101695
101703
  [...hints]
101696
101704
  );
101697
101705
  }
101706
+ async function waitForOptionalTailwindReady(page, timeoutMs) {
101707
+ const hasTailwindReady = await page.evaluate(
101708
+ `(() => { const ready = window.__tailwindReady; return !!ready && typeof ready.then === "function"; })()`
101709
+ );
101710
+ if (!hasTailwindReady) return;
101711
+ const ready = await Promise.race([
101712
+ page.evaluate(
101713
+ `Promise.resolve(window.__tailwindReady).then(() => true, () => false)`
101714
+ ),
101715
+ new Promise((resolve14) => setTimeout(() => resolve14(false), timeoutMs))
101716
+ ]);
101717
+ if (!ready) {
101718
+ throw new Error(
101719
+ `[FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms. Tailwind browser runtime must finish before frame capture starts.`
101720
+ );
101721
+ }
101722
+ }
101698
101723
  async function initializeSession(session) {
101699
101724
  const { page, serverUrl } = session;
101700
101725
  page.on("console", (msg) => {
@@ -101751,6 +101776,7 @@ async function initializeSession(session) {
101751
101776
  );
101752
101777
  }
101753
101778
  await page.evaluate(`document.fonts?.ready`);
101779
+ await waitForOptionalTailwindReady(page, pageReadyTimeout2);
101754
101780
  if (session.options.format === "png") {
101755
101781
  await initTransparentBackground(session.page);
101756
101782
  }
@@ -101816,6 +101842,7 @@ async function initializeSession(session) {
101816
101842
  await new Promise((r) => setTimeout(r, 100));
101817
101843
  }
101818
101844
  await page.evaluate(`document.fonts?.ready`);
101845
+ await waitForOptionalTailwindReady(page, pageReadyTimeout);
101819
101846
  warmupRunning = false;
101820
101847
  session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
101821
101848
  if (session.options.format === "png") {
@@ -111470,6 +111497,27 @@ function createRenderJob(config2) {
111470
111497
  function normalizeCompositionSrcPath(srcPath) {
111471
111498
  return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
111472
111499
  }
111500
+ function createStandaloneEntryRenderClone(root, host) {
111501
+ const hostClone = host.cloneNode(true);
111502
+ hostClone.setAttribute("data-start", "0");
111503
+ if (root === host) return hostClone;
111504
+ const rootClone = root.cloneNode(false);
111505
+ rootClone.appendChild(hostClone);
111506
+ return rootClone;
111507
+ }
111508
+ function replaceBodyWithRenderClone(body, renderClone) {
111509
+ while (body.firstChild) {
111510
+ body.removeChild(body.firstChild);
111511
+ }
111512
+ body.appendChild(renderClone);
111513
+ }
111514
+ function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSeconds) {
111515
+ if (!cfg.enableStreamingEncode) return false;
111516
+ if (outputFormat === "png-sequence") return false;
111517
+ if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
111518
+ if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
111519
+ return workerCount === 1;
111520
+ }
111473
111521
  function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
111474
111522
  const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
111475
111523
  const { document: document2 } = parseHTML(indexHtml);
@@ -111484,16 +111532,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
111484
111532
  (candidate) => candidate.hasAttribute("data-composition-id")
111485
111533
  ) ?? null;
111486
111534
  if (!root) return null;
111487
- const hostClone = host.cloneNode(true);
111488
- hostClone.setAttribute("data-start", "0");
111489
- body.innerHTML = "";
111490
- if (root === host) {
111491
- body.appendChild(hostClone);
111492
- return document2.toString();
111493
- }
111494
- const rootClone = root.cloneNode(false);
111495
- rootClone.appendChild(hostClone);
111496
- body.appendChild(rootClone);
111535
+ const renderClone = createStandaloneEntryRenderClone(root, host);
111536
+ replaceBodyWithRenderClone(body, renderClone);
111497
111537
  return document2.toString();
111498
111538
  }
111499
111539
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
@@ -111525,7 +111565,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111525
111565
  }
111526
111566
  const enableChunkedEncode = cfg.enableChunkedEncode;
111527
111567
  const chunkedEncodeSize = cfg.chunkSizeFrames;
111528
- const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
111529
111568
  let peakRssBytes = 0;
111530
111569
  let peakHeapUsedBytes = 0;
111531
111570
  const sampleMemory = () => {
@@ -112094,6 +112133,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
112094
112133
  await closeCaptureSession(probeSession);
112095
112134
  probeSession = null;
112096
112135
  }
112136
+ let useStreamingEncode = shouldUseStreamingEncode(cfg, outputFormat, workerCount, job.duration);
112137
+ log.info("streaming-encode gate", {
112138
+ enabled: useStreamingEncode,
112139
+ configFlag: cfg.enableStreamingEncode,
112140
+ outputFormat,
112141
+ workerCount,
112142
+ durationSeconds: job.duration,
112143
+ maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
112144
+ });
112097
112145
  const captureAttempts = [];
112098
112146
  const FORMAT_EXT = {
112099
112147
  mp4: ".mp4",
@@ -112633,28 +112681,47 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
112633
112681
  } else {
112634
112682
  let streamingEncoder = null;
112635
112683
  let streamingEncoderClosed = false;
112636
- if (enableStreamingEncode) {
112637
- streamingEncoder = await spawnStreamingEncoder(
112638
- videoOnlyPath,
112639
- {
112640
- fps: job.config.fps,
112641
- width,
112642
- height,
112643
- codec: preset.codec,
112644
- preset: preset.preset,
112645
- quality: effectiveQuality,
112646
- bitrate: effectiveBitrate,
112647
- pixelFormat: preset.pixelFormat,
112648
- useGpu: job.config.useGpu,
112649
- imageFormat: captureOptions.format || "jpeg",
112650
- hdr: preset.hdr
112651
- },
112652
- abortSignal
112653
- );
112654
- assertNotAborted();
112684
+ if (useStreamingEncode) {
112685
+ try {
112686
+ streamingEncoder = await spawnStreamingEncoder(
112687
+ videoOnlyPath,
112688
+ {
112689
+ fps: job.config.fps,
112690
+ width,
112691
+ height,
112692
+ codec: preset.codec,
112693
+ preset: preset.preset,
112694
+ quality: effectiveQuality,
112695
+ bitrate: effectiveBitrate,
112696
+ pixelFormat: preset.pixelFormat,
112697
+ useGpu: job.config.useGpu,
112698
+ imageFormat: captureOptions.format || "jpeg",
112699
+ hdr: preset.hdr
112700
+ },
112701
+ abortSignal
112702
+ );
112703
+ assertNotAborted();
112704
+ } catch (err) {
112705
+ if (abortSignal?.aborted) {
112706
+ if (streamingEncoder && !streamingEncoderClosed) {
112707
+ await streamingEncoder.close().catch(() => {
112708
+ });
112709
+ streamingEncoderClosed = true;
112710
+ }
112711
+ throw err;
112712
+ }
112713
+ useStreamingEncode = false;
112714
+ streamingEncoder = null;
112715
+ log.warn("[Render] Streaming encoder spawn failed; falling back to disk-frame encode.", {
112716
+ error: err instanceof Error ? err.message : String(err),
112717
+ outputFormat,
112718
+ workerCount,
112719
+ durationSeconds: job.duration
112720
+ });
112721
+ }
112655
112722
  }
112656
112723
  try {
112657
- if (enableStreamingEncode && streamingEncoder) {
112724
+ if (useStreamingEncode && streamingEncoder) {
112658
112725
  const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
112659
112726
  const currentEncoder = streamingEncoder;
112660
112727
  if (workerCount > 1) {