@hyperframes/engine 0.8.86 → 0.8.88

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.
@@ -366,9 +366,19 @@ export function prepareBeginFrameTimeline(session, state, warmupIntervalMs) {
366
366
  },
367
367
  };
368
368
  }
369
+ /** Runs BeginFrame session setup; a failure stops the warm-up loop, which would otherwise tick forever. */
370
+ export async function cancelWarmupOnError(state, setup) {
371
+ try {
372
+ return await setup();
373
+ }
374
+ catch (error) {
375
+ state.cancelled = true;
376
+ throw error;
377
+ }
378
+ }
369
379
  export async function driveWarmupTicks(options, state) {
370
380
  const sleep = options.sleep ?? realSleep;
371
- while (true) {
381
+ while (!state.cancelled) {
372
382
  if (options.lockWarmupTicks) {
373
383
  // Locked mode exits on the iteration count, ignoring `state.running` —
374
384
  // the caller flips `running=false` after page-readiness but we keep
@@ -1771,76 +1781,72 @@ export async function initializeSession(session) {
1771
1781
  })();
1772
1782
  warmupLoopPromise.catch(() => { });
1773
1783
  logInitPhase("warmup loop started");
1774
- await gotoEntryPage();
1775
- logInitPhase("page.goto complete");
1776
- // Flush the GSAP proxy queue synchronously. In BeginFrame mode the rAF-based
1777
- // batch drain runs on the warmup loop's 33ms ticks — for tween-heavy
1778
- // compositions this is the dominant init cost. Flushing synchronously
1779
- // eliminates the wait entirely.
1780
- await page.evaluate(`window.__hfFlushSync?.()`);
1781
- logInitPhase("GSAP proxy flush complete");
1782
- // Poll for window.__hf readiness using manual evaluate loop (waitForFunction
1783
- // uses rAF polling internally, which won't fire in beginFrame mode).
1784
- const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
1785
- try {
1784
+ await cancelWarmupOnError(warmupState, async () => {
1785
+ await gotoEntryPage();
1786
+ logInitPhase("page.goto complete");
1787
+ // Flush the GSAP proxy queue synchronously. In BeginFrame mode the rAF-based
1788
+ // batch drain runs on the warmup loop's 33ms ticks — for tween-heavy
1789
+ // compositions this is the dominant init cost. Flushing synchronously
1790
+ // eliminates the wait entirely.
1791
+ await page.evaluate(`window.__hfFlushSync?.()`);
1792
+ logInitPhase("GSAP proxy flush complete");
1793
+ // Poll for window.__hf readiness using manual evaluate loop (waitForFunction
1794
+ // uses rAF polling internally, which won't fire in beginFrame mode).
1795
+ const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
1786
1796
  await pollHfReady(page, pageReadyTimeout);
1787
1797
  logInitPhase("pollHfReady complete");
1788
- }
1789
- catch (err) {
1790
- warmupState.running = false;
1791
- throw err;
1792
- }
1793
- session.subTimelineWaitOutcome = await pollSubCompositionTimelines(page, pageReadyTimeout, undefined, () => session.scriptLoadFailures, undefined, (ids) => {
1794
- session.pendingTimelineIds = [...ids];
1798
+ session.subTimelineWaitOutcome = await pollSubCompositionTimelines(page, pageReadyTimeout, undefined, () => session.scriptLoadFailures, undefined, (ids) => {
1799
+ session.pendingTimelineIds = [...ids];
1800
+ });
1801
+ logInitPhase(`pollSubCompositionTimelines complete (${session.subTimelineWaitOutcome})`);
1802
+ recordSubTimelineWarning(session, pageReadyTimeout);
1803
+ await applyVideoMetadataHints(page, session.options.videoMetadataHints);
1804
+ logInitPhase("applyVideoMetadataHints complete");
1805
+ // Run independent readiness checks in parallel — videos, images, fonts,
1806
+ // and Tailwind don't depend on each other's completion.
1807
+ const bfSkipVideoIds = session.options.skipReadinessVideoIds ?? [];
1808
+ const [bfVideosReady] = await Promise.all([
1809
+ pollVideosReady(page, bfSkipVideoIds, pageReadyTimeout),
1810
+ pollImagesReady(page, pageReadyTimeout).then(async (ready) => {
1811
+ if (!ready) {
1812
+ const failedImages = await page.evaluate(() => {
1813
+ return Array.from(document.querySelectorAll("img"))
1814
+ .filter((img) => {
1815
+ const ie = img;
1816
+ const src = ie.getAttribute("src") || "";
1817
+ if (!src || src.startsWith("data:"))
1818
+ return false;
1819
+ return !(ie.complete && ie.naturalWidth > 0);
1820
+ })
1821
+ .map((img) => img.src || img.getAttribute("src") || "(no src)")
1822
+ .join(", ");
1823
+ });
1824
+ console.warn(`[FrameCapture] Some image elements did not load within ${pageReadyTimeout}ms: ${failedImages}. ` +
1825
+ `Continuing render — affected images may appear blank/missing in early frames.`);
1826
+ }
1827
+ await decodeAllImages(page);
1828
+ return ready;
1829
+ }),
1830
+ page.evaluate(`document.fonts?.ready`),
1831
+ waitForOptionalTailwindReady(page, pageReadyTimeout),
1832
+ ]);
1833
+ logInitPhase("media + fonts + tailwind ready");
1834
+ if (!bfVideosReady) {
1835
+ const failedVideos = await page.evaluate((skipIdList) => {
1836
+ const skip = new Set(skipIdList);
1837
+ return Array.from(document.querySelectorAll("video"))
1838
+ .filter((v) => !skip.has(v.id))
1839
+ .filter((v) => v.readyState < 2 && !v.error)
1840
+ .map((v) => v.src || v.getAttribute("src") || "(no src)")
1841
+ .join(", ");
1842
+ }, bfSkipVideoIds);
1843
+ console.warn(`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. ` +
1844
+ `Continuing render — affected videos will appear as blank/black frames.`);
1845
+ }
1846
+ recordCaptureWarnings(session, await collectMediaReadinessWarnings(page, bfSkipVideoIds, pageReadyTimeout));
1847
+ await recordLiveMapWarning(session, page);
1848
+ await recordSessionInitTelemetry(session, initStart);
1795
1849
  });
1796
- logInitPhase(`pollSubCompositionTimelines complete (${session.subTimelineWaitOutcome})`);
1797
- recordSubTimelineWarning(session, pageReadyTimeout);
1798
- await applyVideoMetadataHints(page, session.options.videoMetadataHints);
1799
- logInitPhase("applyVideoMetadataHints complete");
1800
- // Run independent readiness checks in parallel — videos, images, fonts,
1801
- // and Tailwind don't depend on each other's completion.
1802
- const bfSkipVideoIds = session.options.skipReadinessVideoIds ?? [];
1803
- const [bfVideosReady] = await Promise.all([
1804
- pollVideosReady(page, bfSkipVideoIds, pageReadyTimeout),
1805
- pollImagesReady(page, pageReadyTimeout).then(async (ready) => {
1806
- if (!ready) {
1807
- const failedImages = await page.evaluate(() => {
1808
- return Array.from(document.querySelectorAll("img"))
1809
- .filter((img) => {
1810
- const ie = img;
1811
- const src = ie.getAttribute("src") || "";
1812
- if (!src || src.startsWith("data:"))
1813
- return false;
1814
- return !(ie.complete && ie.naturalWidth > 0);
1815
- })
1816
- .map((img) => img.src || img.getAttribute("src") || "(no src)")
1817
- .join(", ");
1818
- });
1819
- console.warn(`[FrameCapture] Some image elements did not load within ${pageReadyTimeout}ms: ${failedImages}. ` +
1820
- `Continuing render — affected images may appear blank/missing in early frames.`);
1821
- }
1822
- await decodeAllImages(page);
1823
- return ready;
1824
- }),
1825
- page.evaluate(`document.fonts?.ready`),
1826
- waitForOptionalTailwindReady(page, pageReadyTimeout),
1827
- ]);
1828
- logInitPhase("media + fonts + tailwind ready");
1829
- if (!bfVideosReady) {
1830
- const failedVideos = await page.evaluate((skipIdList) => {
1831
- const skip = new Set(skipIdList);
1832
- return Array.from(document.querySelectorAll("video"))
1833
- .filter((v) => !skip.has(v.id))
1834
- .filter((v) => v.readyState < 2 && !v.error)
1835
- .map((v) => v.src || v.getAttribute("src") || "(no src)")
1836
- .join(", ");
1837
- }, bfSkipVideoIds);
1838
- console.warn(`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. ` +
1839
- `Continuing render — affected videos will appear as blank/black frames.`);
1840
- }
1841
- recordCaptureWarnings(session, await collectMediaReadinessWarnings(page, bfSkipVideoIds, pageReadyTimeout));
1842
- await recordLiveMapWarning(session, page);
1843
- await recordSessionInitTelemetry(session, initStart);
1844
1850
  // Stop warmup, then drain the loop in BOTH modes before any further
1845
1851
  // BeginFrame on this session (drawElement init, the render-frame commit tick
1846
1852
  // at the end of init, and frame 0). Clearing the flag only stops new