ego-browser Screencast: How to Record Video with FFmpeg-Based Video Recording
The ego-browser screencast feature captures browser automation sessions as WebM videos by streaming JPEG frames from Chrome DevTools Protocol into an internal FFmpeg encoder.
The ego-browser package (from citrolabs/ego-lite) provides a built-in video recording API that lets you create screen recordings of any page interaction. Unlike external screen capture tools, this screencast implementation runs headlessly through the Chrome DevTools Protocol, making it ideal for CI pipelines, automated testing, and documentation generation.
How the ego-browser Screencast System Works
The recording pipeline consists of three coordinated components: the CDP event subscriber, the frame processor, and the FFmpeg-based VideoRecorder.
Starting a Screencast Session
Call page.screencast.start(options) to initiate recording. According to the source code in package/ego-browser/src/driver/screencast.ts, this invokes startScreencast which performs the following steps:
-
Validates options —
pathmust end with.webm,qualitymust be 0-100, andsizedimensions must be at least 2 pixels (lines 35-57). -
Ensures CDP session — The driver calls
ensureSession()to establish the Chrome DevTools Protocol connection. -
Computes default size — If no
sizeis provided, the viewport dimensions are read from the page, scaled to maximum 800px, and forced to even numbers (required by VP8 encoding) (lines 70-77). -
Creates and starts recorder — A
VideoRecorderinstance spawns FFmpeg and begins accepting frames.
Frame Capture and Encoding
While recording is active, the driver subscribes to the CDP event Page.screencastFrame (lines 78-92):
// Pseudo-code illustrating the frame flow
onScreencastFrame = (frame) => {
const buffer = Buffer.from(frame.data, 'base64'); // Decode JPEG
const timestamp = metadata.timestamp ?? Date.now(); // CDP timestamp or fallback
recorder.writeFrame(buffer, timestamp);
sendCDPCommand('Page.screencastFrameAck'); // Keep pipeline flowing
};
Each frame arrives as Base64-encoded JPEG. The driver converts to Buffer, timestamps it, forwards to recorder.writeFrame(), and immediately acknowledges receipt to prevent the browser from throttling frame delivery.
Stopping and Finalizing the Video
page.screencast.stop() triggers stopScreencast (lines 29-68, 120-168):
- If no frames were captured, a fallback screenshot via
Page.captureScreenshotensures the output file isn't empty. - The CDP command
Page.stopScreencastterminates the browser's frame generation. recorder.stop()flushes queued frames, closes FFmpeg's stdin, waits for process exit, and renames the temp file to the target path.
FFmpeg errors (missing binary, non-zero exit codes) propagate with descriptive messages.
Configuring ego-browser Screencast Options
Pass these options to page.screencast.start:
| Option | Type | Default | Constraints |
|---|---|---|---|
path |
string |
— | Must end with .webm — destination file path |
size |
{width, height} |
Page viewport (≤800px) | Width ≥2, Height ≥2, integers; forced to even numbers |
quality |
number |
90 |
JPEG quality integer, 0-100 |
Size Constraints Explained
The source code forces dimensions to even numbers because VP8 encoding requires even-width and even-height frames (lines 70-77). If you specify {width: 641, height: 481}, it becomes {width: 642, height: 482}.
VideoRecorder Internals (FFmpeg Pipeline)
The VideoRecorder class in package/ego-browser/src/video-recorder.ts spawns FFmpeg with these critical arguments (lines 44-82):
- Input format:
image2pipefor MJPEG stream ingestion - Codec: VP8 video in WebM container
- Performance:
-deadline realtime -speed 8for fast encoding - Frame rate: Fixed 25 fps with buffered frame queue (lines 16-26, 21-38)
Frames queue and write at a stable rate to prevent timestamp drift during variable capture intervals.
Practical Code Examples for ego-browser Video Recording
Basic Start/Stop Pattern
// Start recording with quality and size configuration
await page.screencast.start({
path: "./recordings/checkout-flow.webm",
size: { width: 1280, height: 720 },
quality: 85,
});
await page.goto("https://example.com/checkout");
await page.click("#add-to-cart");
await page.fill("#email", "test@example.com");
// Finalize — FFmpeg closes and file is ready
await page.screencast.stop();
Auto-Disposing Disposable Pattern
For scoped recordings that auto-cleanup:
await page.screencast.start({
path: "tmp.webm",
quality: 75
}).then(async (handle) => {
await page.keyboard.type("automated input test");
await page.waitForTimeout(2000);
// stop() + resource cleanup
await handle.dispose();
});
Minimal Configuration (Defaults)
// Uses page viewport size, 90 quality, current working directory
await page.screencast.start({ path: "demo.webm" });
// ... perform actions ...
await page.screencast.stop();
Key Source Files for ego-browser Screencast Implementation
| File | Purpose |
|---|---|
package/ego-browser/src/driver/screencast.ts |
CDP event handling, option validation, startScreencast/stopScreencast |
package/ego-browser/src/video-recorder.ts |
FFmpeg wrapper, frame queuing, WebM finalization |
package/ego-browser/src/helpers.ts |
Public API exposure: page.screencast.start and page.screencast.stop |
Summary
- ego-browser screencast records automation sessions via Chrome DevTools Protocol
Page.screencastevents piped to FFmpeg. - Output format is VP8 WebM with configurable JPEG quality (0-100), dimensions (forced even), and destination path.
- Performance uses realtime FFmpeg encoding at 25 fps with frame buffering for stable timestamps.
- Graceful handling includes fallback screenshot on empty captures and clear error messages for FFmpeg failures.
Frequently Asked Questions
What video format does ego-browser screencast produce?
WebM with VP8 video codec. The path option must end with .webm, and the VideoRecorder hardcodes the FFmpeg output format to WebM. This provides broad browser compatibility without patent concerns.
Why must screencast dimensions be even numbers?
VP8 encoding requires even-width and even-height frames. The driver automatically rounds odd dimensions up to the nearest even integer. This happens in src/driver/screencast.ts when computing defaultSize or validating explicit size options.
How does ego-browser handle screencast quality?
JPEG quality per frame, not video bitrate. The quality option (default 90, range 0-100) controls the compression level of each Base64 JPEG frame sent from Chrome. Higher values reduce compression artifacts but increase file size. Final WebM size depends on both JPEG quality and motion complexity in the captured content.
What happens if no frames arrive during recording?
A fallback screenshot prevents empty files. If stopScreencast detects zero frames received, it triggers Page.captureScreenshot and writes that single image to the WebM output. This ensures valid video files even on instant navigation or hidden pages.
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 →