Skip to content

video: thin recording bursts by time, never skip the frame that matters - #31

Merged
jaredLunde merged 2 commits into
mainfrom
keep-every-screencast-frame
Sep 24, 2026
Merged

jaredLunde merged 2 commits into
mainfrom
keep-every-screencast-frame

Conversation

@jaredLunde

@jaredLunde jaredLunde commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Page.startScreencast was started with everyNthFrame: 2. Chromium emits a screencast frame only when the compositor paints, so a recording of one interaction has two paints: the state before, and the result. The result's paint was the skipped one, and the clip came back as a single frame of 100 ms. Seen four times in a row on a headless-shell VM while an agent recorded a fixed page transition. A repro of the bug on the same page (click, blank article, content 2.4 s later) has three paints and survived as two frames, which is why "before" recordings looked fine and "after" recordings never did.

The 2 was there for a reason: every WebM frame is a VP8 keyframe, so a 60 Hz animation at every paint doubles bytes and encode time. Skipping by count just can't tell an animation frame from the only frame that matters.

Change

  • Chromium is asked for every paint (DEFAULT_VIDEO_EVERY_NTH_FRAME 1). The builder option stays for callers who want Chromium-side skipping.
  • FrameJournal thins by time instead: a frame at least 33 ms after the last kept one is written at once; a frame inside that interval is the burst's newest and is held, then written when the burst ends (the next frame is a full interval away, so this one was on screen long enough to matter) or at finish, so the final state is always in the clip. with_min_frame_interval_us(0) keeps everything.
  • Net effect: animations still land near 30 fps with the same file sizes; a lone paint after a pause is always kept; the frame a burst leaves on screen is kept (blank → content → five seconds still → scroll used to show blank for five seconds).

Tests

  • Journal: lone paint after a pause; a 60 Hz burst is thinned but ends on its last frame; a burst's on-screen frame is kept while an intermediate replaced within an interval is dropped; interval 0 keeps every frame.
  • Live (skips without Chromium): one DOM change between two pauses yields two frames and a clip that spans the pause.
  • cargo test --locked 397/397, cli and mcp suites unchanged and green, live recording tests 2/2 twice against Playwright's Chromium 1232 on macOS arm64.

🤖 Generated with Claude Code

jaredLunde and others added 2 commits September 24, 2026 15:57
`Page.startScreencast` was started with `everyNthFrame: 2`. Chromium only
emits a frame when the compositor paints, so on a page that changes once
after recording starts there are exactly two paints, and the second one,
the result, was the frame being skipped: the clip came back as a single
frame of 100 ms. Seen four times in a row while an agent recorded a fixed
page transition (a click, then the new page). A repro with three paints
survived as two frames, which hid the problem for "before" clips.

Every paint is now kept. A still page costs nothing however long the
recording runs, an animating page runs at its own rate, and the existing
frame cap bounds a long animation. Adds a live test: one DOM change between
two pauses yields at least two frames and a clip that spans the pause.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The count skip is gone for good; in its place the journal thins frames by
time. A frame at least 33 ms after the last kept one is written at once.
A frame inside that interval is the burst's newest and is held: written
when the burst ends (the next frame is a full interval away, so this one
was on screen long enough to see) or when the journal finishes, so the
final state is never lost to thinning. Animated pages still land near
30 fps with the same file sizes, which is what everyNthFrame 2 was for,
and a lone paint after a pause, the result of a click, is always kept.

Four journal tests cover the lone paint, a 60 Hz burst, a burst whose
last frame stayed on screen (blank, then content, then a five-second
pause: the count skip showed blank for five seconds), and interval zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jaredLunde jaredLunde changed the title video: keep every screencast paint by default video: thin recording bursts by time, never skip the frame that matters Sep 24, 2026
@jaredLunde
jaredLunde merged commit 868ddfe into main Sep 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant