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:
- Ensures an active CDP session via
ensureSession. - Instantiates a
VideoRecorderusing the injectablecreateRecorderdependency (defaulting tonew VideoRecorder(options)). - Subscribes to the
Page.screencastFrameCDP event. - Decodes incoming base64 frames and forwards them to
recorder.writeFrame()with millisecond timestamps. - Sends
Page.screencastFrameAckacknowledgments 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.screencastFrameevents 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
screencastdriver insrc/driver/screencast.ts, which bridges CDP sessions and theVideoRecorderclass. VideoRecorderinsrc/video-recorder.tsmanages FFmpeg subprocesses, handling JPEG frame ingestion and VP8 WebM output viastart(),writeFrame(), andstop()methods.- Frame flow follows a strict pipeline: CDP
Page.screencastFrameevents are decoded, acknowledged viaPage.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →