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:
Page.captureScreenshothangs indefinitely when--enable-begin-frame-controlis active- Cross-device rename (EXDEV) fails on WSL where temp and output dirs are on different filesystems
- JPEG q60 capture artifacts amplified by re-encoding through x264
Changes
- Replace
Page.captureScreenshotwithHeadlessExperimental.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.
| Format | Before | After | Time | Size |
|---|---|---|---|---|
| GIF | 35.0s / 959K | 27.6s / 341K | -21% | -64% |
| MP4 | 24.5s / 550K | 23.1s / 478K | -6% | -13% |
| WebM | 44.1s / 279K | 45.2s / 284K | ~0% | ~0% |
All 212 tests pass.
Full technical details, per-file breakdown, and capture performance audit are in the commit message.
Activity
8Thanks 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:
- PR #15 already landed the EXDEV fix on main, so this needs a rebase to resolve the overlap in
media.tsand drop the duplicatemoveFileSync. HeadlessExperimental.enable()needs to be called during recording setup —beginFramewon't work without it.- The new
fs-utils.tsin the webreel package is probably unnecessary now that@webreel/corealready exportsmoveFileSync.
Happy to re-review after a rebase. Nice work tracking down the begin-frame-control issue!
Thanks for this PR! I was able to confirm the bug —
Page.captureScreenshotdoes hang indefinitely when--enable-begin-frame-controlis active, so switching toHeadlessExperimental.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:
- 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.tsand drop the duplicatemoveFileSync.HeadlessExperimental.enable()needs to be called during recording setup —beginFramewon't work without it.- The new
fs-utils.tsin the webreel package is probably unnecessary now that@webreel/corealready exportsmoveFileSync.Happy to re-review after a rebase. Nice work tracking down the begin-frame-control issue!
Thanks for the review!
- Rebased on main, media.ts now imports moveFileSync from @webreel/core instead of the local duplicate.
- HeadlessExperimental.enable() was already there, called in runner.ts before the frame pump starts, which is before recorder.start().
- fs-utils.ts removed along with its tests.
@sld0Ant is attempting to deploy a commit to the Vercel Labs Team on Vercel.
A member of the Team first needs to authorize it.