How the Video Recorder Is Integrated with the Ego-Browser Runtime

The Ego-Browser runtime records page video by coupling the VideoRecorder class with the screencast driver.

The citrolabs/ego-lite repository provides a lightweight browser automation framework that captures viewport video through a tightly integrated recording pipeline. Understanding how the video recorder is integrated with the ego-browser runtime reveals an architecture where raw CDP (Chrome DevTools Protocol) screencast frames flow directly into an FFmpeg encoder. This design produces silent VP8 WebM files without requiring external screen capture utilities.

Core Components: VideoRecorder and Screencast Driver

The integration centers on two primary modules that bridge the CDP session and the file system.

VideoRecorder Implementation

Located in src/video-recorder.ts (lines 19-86), the VideoRecorder class handles low-level encoding logic. It spawns an FFmpeg process configured for VP8 WebM output, buffers incoming JPEG frames, and atomically renames the temporary file upon completion. The class exposes three critical methods:

  • start(): Launches the FFmpeg subprocess with the configured viewport size and quality settings.
  • writeFrame(buffer, timestamp): Accepts raw JPEG buffers and timestamps, streaming them to FFmpeg's stdin.
  • stop(): Signals FFmpeg to finalize the WebM container and moves the temporary file to the final output path.

Screencast Driver Integration

The src/driver/screencast.ts module (lines 58-92) serves as the bridge between the CDP session and the VideoRecorder. When startScreencast() is invoked, the driver performs the following sequence:

  1. Ensures an active CDP session via ensureSession.
  2. Instantiates a VideoRecorder using the injectable createRecorder dependency (defaulting to new VideoRecorder(options)).
  3. Subscribes to the Page.screencastFrame CDP event.
  4. Decodes incoming base64 frames and forwards them to recorder.writeFrame() with millisecond timestamps.
  5. Sends Page.screencastFrameAck acknowledgments to prevent browser buffer saturation.

Recording Lifecycle and Frame Flow

Starting the Recording

The recording begins when the driver calls recorder.start() after establishing the CDP subscription. At this point, Chrome begins emitting JPEG frames at the requested quality and maximum frame rate, which the driver immediately pipes to the FFmpeg process.

Stopping and Finalizing Output

The stopScreencast() method (lines 29-67 in src/driver/screencast.ts) manages teardown:

  • Unsubscribes from Page.screencastFrame events to halt incoming frames.
  • Flushes pending frame promises to ensure all buffered data is written.
  • Optionally captures a final screenshot if no frames were received during the session.
  • Invokes recorder.stop() to close FFmpeg's stdin, wait for process exit, and atomically rename the temporary WebM file to the user-specified destination.

Dependency Injection for Testability

The screencast driver accepts a dependencies object (lines 18-27 in src/driver/screencast.ts) that enables complete mocking of the integration. Through the __testing.setOverrides() API, unit tests can replace createRecorder with a stub to verify frame handling logic without spawning real FFmpeg processes or browser instances. This design keeps the video recorder tightly coupled to the runtime for performance while remaining fully testable.

Practical Usage Examples

Basic Screencast Recording

Record the current page viewport to a WebM file using the high-level API exposed through page.screencast:

await page.screencast.start({ path: "output.webm", quality: 85 });
/* … perform browser interactions … */
await page.screencast.stop();   // Finalizes the VP8 WebM file

Custom Recorder Configuration

Override the default FFmpeg path or arguments for specialized environments or custom codecs:

import { startScreencast, __testing } from "ego-browser";

__testing.setOverrides({
  createRecorder: (opts) =>
    new VideoRecorder({ ...opts, ffmpegPath: "/usr/local/bin/ffmpeg" }),
});

await startScreencast({ path: "test.webm" });

Direct VideoRecorder Access

For low-level control, instantiate the recorder directly without the screencast driver:

import { VideoRecorder } from "ego-browser/video-recorder.js";

const recorder = new VideoRecorder({
  outputPath: "raw.webm",
  size: { width: 1280, height: 720 },
});
await recorder.start();
// Manually feed JPEG buffers from any source:
await recorder.writeFrame(jpegBuffer, Date.now());
await recorder.stop();

Summary

  • The video recorder is integrated with the ego-browser runtime through the screencast driver in src/driver/screencast.ts, which bridges CDP sessions and the VideoRecorder class.
  • VideoRecorder in src/video-recorder.ts manages FFmpeg subprocesses, handling JPEG frame ingestion and VP8 WebM output via start(), writeFrame(), and stop() methods.
  • Frame flow follows a strict pipeline: CDP Page.screencastFrame events are decoded, acknowledged via Page.screencastFrameAck, and written to the recorder.
  • The integration supports dependency injection via __testing.setOverrides(), enabling unit tests to mock FFmpeg and CDP interactions without real browser instances.
  • Output files are atomically renamed from temporary storage only after successful FFmpeg finalization, preventing corrupted partial recordings.

Frequently Asked Questions

What video format does the ego-browser runtime produce?

The integration generates silent VP8-encoded WebM files. According to the source code in src/video-recorder.ts, the VideoRecorder class spawns FFmpeg with VP8-specific codec arguments and writes to a temporary file before atomically renaming it to the final destination only upon successful completion.

How does the screencast driver handle backpressure from the browser?

The driver implements CDP-level flow control by sending Page.screencastFrameAck acknowledgments immediately after processing each frame in src/driver/screencast.ts. This prevents Chrome from emitting new frames until the previous frame has been decoded from base64 and written to the FFmpeg process, avoiding memory pressure in the browser.

Can I customize FFmpeg arguments when recording video?

Yes. While the default createRecorder dependency instantiates VideoRecorder with standard options, you can inject a custom factory via __testing.setOverrides() to pass custom ffmpegPath locations or additional codec flags. This allows integration with custom FFmpeg builds or specific encoding requirements without modifying the core library.

Where is the temporary video file stored during recording?

The VideoRecorder writes to a temporary path adjacent to the final output location, appending a temporary extension during the encoding process. Only upon successful stop() does it rename the file to the user-specified path, ensuring that incomplete recordings or process crashes do not leave corrupted files at the final destination.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →