How ego-browser Implements Screencast Recording: CDP Events and FFmpeg Pipeline
ego-browser records screencasts by bridging Chrome DevTools Protocol Page.screencast events to an internal FFmpeg-based VideoRecorder, exposing options for output path, frame quality, and viewport size.
The screencast functionality in the citrolabs/ego-lite repository enables automated capture of browser sessions as WebM video files. By leveraging the Chrome DevTools Protocol (CDP) and an FFmpeg encoding pipeline, ego-browser converts live page frames into compressed video streams with configurable resolution and quality settings.
Architecture Overview
The recording system operates in two layers. The driver layer in src/driver/screencast.ts manages CDP communication and frame lifecycle, while the encoding layer in src/video-recorder.ts handles the FFmpeg process that assembles frames into the final WebM container.
When you invoke page.screencast.start(), the driver initiates a CDP session and subscribes to Page.screencastFrame events. Each event delivers a Base64-encoded JPEG screenshot from the browser, which the driver forwards to the VideoRecorder instance for timestamped encoding.
Starting a Screencast Recording
Option Validation
The startScreencast function in src/driver/screencast.ts enforces strict constraints on the options object passed to page.screencast.start():
- The
pathparameter must end with the.webmextension - The
qualityparameter must be an integer between 0 and 100 (default: 90) - The optional
sizeobject must specify width and height values of at least 2 pixels
// From src/driver/screencast.ts
await page.screencast.start({
path: "./recordings/session.webm",
quality: 85,
size: { width: 1280, height: 720 }
});
Session Initialization and Sizing
If you omit the size option, the driver computes a default viewport from the page dimensions, scales it to a maximum of 800 pixels, and forces both dimensions to even numbers (required by the VP8 codec). The ensureSession method validates the CDP session before the VideoRecorder is instantiated via createRecorder.
Frame Capture and Encoding Pipeline
Handling Page.screencastFrame Events
While recording is active, the driver listens for CDP Page.screencastFrame events. Each frame arrives as a Base64-encoded JPEG string. The implementation in src/driver/screencast.ts converts this to a Buffer, extracts the timestamp from CDP metadata (falling back to Date.now()), and pushes the frame to recorder.writeFrame().
Immediately after writing, the driver sends Page.screencastFrameAck to the browser to acknowledge receipt and maintain the streaming pipeline.
// Conceptual flow from src/driver/screencast.ts
const buffer = Buffer.from(frame.data, 'base64');
recorder.writeFrame(buffer, metadata.timestamp);
// Acknowledge to keep frames flowing
cdp.send('Page.screencastFrameAck', { sessionId: frame.sessionId });
FFmpeg VideoRecorder Internals
The VideoRecorder class spawns an FFmpeg child process configured for MJPEG input and VP8 WebM output. In src/video-recorder.ts, the FFmpeg arguments enforce real-time encoding with -deadline realtime and -speed 8, and explicitly set the frame rate to 25 fps to maintain stable timestamps.
The recorder queues incoming frames and writes them at fixed intervals, ensuring that temporary file buffers are flushed before the final WebM is sealed.
Stopping and Finalizing the Recording
When you call page.screencast.stop(), the stopScreencast function executes a graceful shutdown sequence:
- If no frames were received, it captures a fallback screenshot using
Page.captureScreenshotto prevent empty output files - Issues the CDP command
Page.stopScreencastto halt the browser's frame generation - Invokes
recorder.stop()to flush the FFmpeg stdin, wait for process exit, and rename the temporary file to the target path specified inoptions.path
Errors from FFmpeg (missing binary, non-zero exit codes) propagate with descriptive messages to aid debugging.
Configuration Options and Code Examples
ego-browser exposes three public configuration options through page.screencast.start():
path(string, required): Destination file path; must end with.webmquality(number, default:90): JPEG compression quality from 0 to 100size(object, optional): Viewport dimensions as{width, height}; minimum 2px, forced to even numbers
// Basic recording with default settings
await page.screencast.start({ path: "output.webm" });
// Navigate and interact
await page.goto("https://example.com");
await page.click("button");
// Stop and save
await page.screencast.stop();
// Disposable pattern for automatic cleanup
const handle = await page.screencast.start({
path: "tmp.webm",
size: { width: 640, height: 480 },
quality: 80
});
await performActions(page);
await handle.dispose(); // Equivalent to stop()
Summary
- ego-browser captures screencasts by subscribing to CDP
Page.screencastFrameevents and encoding them via FFmpeg insrc/video-recorder.ts - The
startScreencastfunction insrc/driver/screencast.tsvalidates that output paths end with.webmand enforces minimum dimensions of 2 pixels - Frame timestamps derive from CDP metadata or system time, with a fixed 25 fps output rate maintained by the
VideoRecorder - The stop sequence includes a fallback screenshot mechanism and graceful FFmpeg process termination to ensure valid WebM files
- Public API options include
path,quality(0-100), andsize, with automatic viewport scaling when size is omitted
Frequently Asked Questions
What file format does ego-browser use for screencast recordings?
ego-browser outputs recordings in WebM format using the VP8 video codec. The implementation specifically requires that the path parameter end with the .webm extension, and the VideoRecorder class in src/video-recorder.ts configures FFmpeg with VP8 encoding to ensure browser compatibility.
How does ego-browser handle frame timestamps during recording?
The driver extracts timestamps from the CDP Page.screencastFrame event metadata when available, falling back to Date.now() if the metadata lacks timing information. These timestamps are passed to recorder.writeFrame() in src/driver/screencast.ts, though the final WebM is encoded at a constant 25 fps regardless of the source interval.
Can I specify a custom frame rate for screencast recordings in ego-browser?
No, the frame rate is hardcoded to 25 fps in the VideoRecorder implementation within src/video-recorder.ts. The FFmpeg arguments include -r 25 to enforce this rate, which provides stable playback regardless of the browser's frame delivery timing. Internal test overrides exist but are not exposed through the public page.screencast API.
What happens if no frames are captured during a screencast session?
If the stopScreencast function detects that no frames were written to the VideoRecorder, it automatically captures a single fallback screenshot using the CDP Page.captureScreenshot command. This ensures the output file contains valid video data rather than an empty or corrupted WebM file, as implemented in src/driver/screencast.ts.
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 →