How the Video Recorder (Screencast) Works in Ego‑Lite: Capturing Browser Sessions with FFmpeg and CDP
Ego‑Lite records browser sessions by orchestrating Chrome DevTools Protocol (CDP) commands to capture JPEG frames and streaming them through an FFmpeg pipeline that produces silent VP8‑encoded WebM files.
The video recorder (screencast) capability in the citrolabs/ego-lite repository enables automated capture of browser automation sessions as portable video files. By wiring together CDP screenshot commands and an FFmpeg child process, Ego‑Lite produces high‑fidelity WebM recordings without requiring external screen capture software.
Starting a Screencast Session
When a script invokes page.screencast.start({path, size, quality}), the driver validates inputs and establishes the recording pipeline through three coordinated phases.
Input Validation and CDP Setup
The driver first enforces constraints in src/driver/screencast.ts (lines 36‑45). It verifies that the path ends with .webm, that quality falls between 0 and 100, and that any supplied size contains integer dimensions of at least 2 pixels. After validation, it ensures an active CDP session exists by calling dependencies.ensureSession() (line 59) and calculates the target frame size using evenSize() to guarantee dimensions divisible by two (line 60).
Initializing the VideoRecorder and FFmpeg
With the session established, the driver instantiates a VideoRecorder (lines 61‑64) and immediately spawns FFmpeg by calling recorder.start() (line 65). Finally, it issues the Page.startScreencast CDP command (lines 103‑112), instructing the browser to begin emitting JPEG frames with the specified quality and dimensions. The function returns a disposable object exposing a dispose() method that triggers the stop sequence.
Processing Frames from the Browser
Once screencasting begins, the browser emits Page.screencastFrame events containing base64‑encoded JPEG data. The driver manages these frames through a subscription handler defined in lines 76‑102 of screencast.ts.
Subscribing to ScreencastFrame Events
For each incoming frame, the driver:
- Decodes the base64 data and timestamps the buffer.
- Invokes
recorder.writeFrame(buffer, timestamp)to queue the image for FFmpeg. - Sends a
Page.screencastFrameAckback to the browser to request the next frame.
This acknowledgment loop ensures the browser only sends new frames when the pipeline is ready, preventing memory pressure from unprocessed screenshots.
The VideoRecorder FFmpeg Pipeline
The VideoRecorder class in src/video-recorder.ts abstracts the FFmpeg child process responsible for encoding raw images into WebM video.
Spawning FFmpeg with VP8 Encoding
When start() is called, the recorder spawns FFmpeg using either the system binary or a custom path specified by the EGO_BROWSER_FFMPEG_PATH environment variable. The process arguments (lines 44‑84) configure:
- Input format:
image2pipeexpecting JPEG streams. - Video codec: VP8 (
libvpx) withforce_divisible_by=2to maintain even dimensions. - Frame rate: Fixed at 25 fps.
- Output: A temporary
.webmfile that is renamed upon completion.
Frame Timing and Queueing
The writeFrame(buffer, timestamp) method (lines 21‑34) calculates the appropriate frame index based on the elapsed time since the first frame. It then calls _queueFrames() (lines 88‑98) to serialize writes to FFmpeg’s stdin, duplicating frames when necessary to maintain timing accuracy. This queuing mechanism ensures that video playback speed mirrors the actual browser session duration.
Stopping the Recording and Finalizing Output
When the disposable’s dispose() method or explicit stop() is invoked, stopScreencast() performs a graceful shutdown sequence.
Cleanup and Fallback Mechanisms
The stop routine first unsubscribes from CDP events to halt frame ingestion (line 34). If no frames were captured—possible in headless or static pages—the driver executes a fallback Page.captureScreenshot (lines 38‑48) to ensure the output file is not empty. It then sends the Page.stopScreencast command (lines 54‑58) to terminate browser-side capture.
Finalizing the WebM File
The driver awaits recorder.stop() (line 64), which closes FFmpeg’s stdin, waits for the process to exit, and renames the temporary file to the user‑requested path. Any errors encountered during the pipeline are captured from FFmpeg’s stderr and re‑thrown (line 67), providing clear diagnostics for encoding failures.
Practical Implementation Examples
Start a recording, perform work, and stop programmatically:
await page.screencast.start({ path: 'session.webm', quality: 80 });
// Perform browser automation for 10 seconds
await new Promise(r => setTimeout(r, 10_000));
await page.screencast.stop();
Alternatively, use the disposable pattern for automatic cleanup:
const screencast = await page.screencast.start({
path: 'run.webm',
size: { w: 1280, h: 720 }
});
await performComplexWorkflow();
await screencast.dispose(); // Stops recording and finalizes the file
Summary
- Ego‑Lite’s video recorder leverages CDP
Page.screencastFrameevents and FFmpeg to generate WebM videos without external dependencies. - Input validation in
screencast.tsensures output paths, quality settings, and dimensions meet codec requirements before recording begins. - Frame pipeline uses acknowledgment signals (
Page.screencastFrameAck) to throttle browser capture and maintain stable memory usage. - FFmpeg configuration enforces VP8 encoding at 25 fps with even dimensions, writing to a temporary file that is atomically renamed on success.
- Graceful shutdown includes a screenshot fallback for empty sessions and surfaces FFmpeg stderr for debugging encoding errors.
Frequently Asked Questions
What video format does the Ego‑Lite screencast produce?
Ego‑Lite generates silent WebM files encoded with the VP8 video codec. The FFmpeg pipeline in src/video-recorder.ts explicitly configures libvpx with a fixed 25 fps frame rate and forces even dimensions using force_divisible_by=2 to ensure compatibility.
How does the system handle frame timing and synchronization?
The VideoRecorder calculates frame indices based on the delta between the current and first timestamps. It queues JPEG buffers through _queueFrames(), writing repeated frames when necessary to maintain the 25 fps target rate. This preserves accurate timing even when the browser emits frames at irregular intervals.
What happens if no frames are captured during a screencast session?
If the driver detects that zero frames arrived during recording, it executes a fallback Page.captureScreenshot before finalizing (lines 38‑48 in screencast.ts). This guarantees the output file contains at least one image, preventing empty or corrupted WebM files.
Can I use a custom FFmpeg binary with Ego‑Lite?
Yes. Set the EGO_BROWSER_FFMPEG_PATH environment variable to the full path of your FFmpeg executable before starting the recording. The VideoRecorder will spawn that binary instead of searching the system PATH, allowing use of custom builds or specific versions required by your environment.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →