# How ego-browser Implements Screencast Recording: CDP Events and FFmpeg Pipeline

> Discover how ego-browser records screencasts using CDP events and FFmpeg. Learn about output path, frame quality, and viewport size options for your recordings.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-04

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/screencast.ts) manages CDP communication and frame lifecycle, while the **encoding layer** in [`src/video-recorder.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/screencast.ts) enforces strict constraints on the `options` object passed to `page.screencast.start()`:

- The `path` parameter must end with the `.webm` extension
- The `quality` parameter must be an integer between 0 and 100 (default: 90)
- The optional `size` object must specify width and height values of at least 2 pixels

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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.

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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:

1. If no frames were received, it captures a fallback screenshot using `Page.captureScreenshot` to prevent empty output files
2. Issues the CDP command `Page.stopScreencast` to halt the browser's frame generation
3. Invokes `recorder.stop()` to flush the FFmpeg stdin, wait for process exit, and rename the temporary file to the target path specified in `options.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 `.webm`
- **`quality`** (`number`, default: `90`): JPEG compression quality from 0 to 100
- **`size`** (`object`, optional): Viewport dimensions as `{width, height}`; minimum 2px, forced to even numbers

```javascript
// 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();

```

```javascript
// 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.screencastFrame` events and encoding them via FFmpeg in [`src/video-recorder.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/video-recorder.ts)
- The `startScreencast` function in [`src/driver/screencast.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/screencast.ts) validates that output paths end with `.webm` and 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), and `size`, 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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/screencast.ts).