#12

fix: deterministic headless recording, lossless capture, and compositor optimizations

Opensld0Antwants to merge intofrom

Howdy! These changes were made for my own projects that rely on headless recording. Sharing upstream in case any of it is useful.

Problem

Three issues prevented reliable headless recording:

  1. Page.captureScreenshot hangs indefinitely when --enable-begin-frame-control is active
  2. Cross-device rename (EXDEV) fails on WSL where temp and output dirs are on different filesystems
  3. JPEG q60 capture artifacts amplified by re-encoding through x264

Changes

  • Replace Page.captureScreenshot with HeadlessExperimental.beginFrame() for deterministic frame timing
  • Switch capture from JPEG q60 to lossless PNG with optimizeForSpeed (54 fps at 1280x720)
  • Single-pass GIF output: overlay + palettegen + paletteuse in one ffmpeg filter_complex
  • Producer/consumer prefetch queue in compositor (overlaps sharp rendering with ffmpeg I/O)
  • Whole-pixel cursor cache key rounding for better overlay cache hit rate
  • EXDEV cross-filesystem fallback in media.ts and runner.ts
  • Benchmark script (scripts/benchmark.sh)

Results

Test scenario: 9 toolbar clicks, 1280x720, 30fps.

FormatBeforeAfterTimeSize
GIF35.0s / 959K27.6s / 341K-21%-64%
MP424.5s / 550K23.1s / 478K-6%-13%
WebM44.1s / 279K45.2s / 284K~0%~0%

All 212 tests pass.

Full technical details, per-file breakdown, and capture performance audit are in the commit message.

Activity

8
vercel[bot]8mo ago

@sld0Ant is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

vercel[bot]reviewed8mo ago
ctate8mo ago

Thanks for this PR! I was able to confirm the bug — Page.captureScreenshot does hang indefinitely when --enable-begin-frame-control is active, so switching to HeadlessExperimental.beginFrame() is the right call. The GIF single-pass optimization and PNG capture changes look good too.

Before merging, a few things to sort out:

  1. PR #15 already landed the EXDEV fix on main, so this needs a rebase to resolve the overlap in media.ts and drop the duplicate moveFileSync.
  2. HeadlessExperimental.enable() needs to be called during recording setup — beginFrame won't work without it.
  3. The new fs-utils.ts in the webreel package is probably unnecessary now that @webreel/core already exports moveFileSync.

Happy to re-review after a rebase. Nice work tracking down the begin-frame-control issue!

sld0Ant8mo ago

Thanks for this PR! I was able to confirm the bug — Page.captureScreenshot does hang indefinitely when --enable-begin-frame-control is active, so switching to HeadlessExperimental.beginFrame() is the right call. The GIF single-pass optimization and PNG capture changes look good too.

Before merging, a few things to sort out:

  1. PR Fix cross-device rename error when moving recorded videos #15 already landed the EXDEV fix on main, so this needs a rebase to resolve the overlap in media.ts and drop the duplicate moveFileSync.
  2. HeadlessExperimental.enable() needs to be called during recording setup — beginFrame won't work without it.
  3. The new fs-utils.ts in the webreel package is probably unnecessary now that @webreel/core already exports moveFileSync.

Happy to re-review after a rebase. Nice work tracking down the begin-frame-control issue!

Thanks for the review!

  1. Rebased on main, media.ts now imports moveFileSync from @webreel/core instead of the local duplicate.
  2. HeadlessExperimental.enable() was already there, called in runner.ts before the frame pump starts, which is before recorder.start().
  3. fs-utils.ts removed along with its tests.
carlesandresreviewed6mo ago
carlesandresreviewed6mo ago