# ego-browser Screencast: How to Record Video with FFmpeg-Based Video Recording

> Discover the ego-browser screencast feature for recording browser automation sessions as WebM videos. Learn how it uses FFmpeg and Chrome DevTools Protocol for seamless video capture.

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

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/screencast.ts), this invokes `startScreencast` which performs the following steps:

1. **Validates options** — `path` must end with `.webm`, `quality` must be 0-100, and `size` dimensions must be at least 2 pixels (lines 35-57).

2. **Ensures CDP session** — The driver calls `ensureSession()` to establish the Chrome DevTools Protocol connection.

3. **Computes default size** — If no `size` is 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).

4. **Creates and starts recorder** — A `VideoRecorder` instance 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):

```javascript
// 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.captureScreenshot` ensures the output file isn't empty.
- The CDP command `Page.stopScreencast` terminates 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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/video-recorder.ts) spawns FFmpeg with these critical arguments (lines 44-82):

- **Input format**: `image2pipe` for MJPEG stream ingestion
- **Codec**: VP8 video in WebM container
- **Performance**: `-deadline realtime -speed 8` for 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

```javascript
// 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:

```javascript
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)

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/screencast.ts) | CDP event handling, option validation, `startScreencast`/`stopScreencast` |
| [`package/ego-browser/src/video-recorder.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/video-recorder.ts) | FFmpeg wrapper, frame queuing, WebM finalization |
| [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/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.screencast` events 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`](https://github.com/citrolabs/ego-lite/blob/main/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.