/** * Browser Video Capture Handler * * Handles WebCodecs-based video streaming with WebRTC DataChannel transport. * * Video Architecture (Chrome-Remote-Desktop-style adaptive quality): * 1. Frames are captured at the resolution the viewer can actually display * (fit-scale × devicePixelRatio, capped by the host's pixel budget) * 2. Capture runs either in-page (getDisplayMedia → VideoFrame, no round-trip) * or via CDP screencast, which is ack-gated so Chrome never rasters a frame * we would discard * 3. The page encodes with VideoEncoder — VP9 quantizer mode, H.264 when the * viewer has hardware for it, VP8 as the universal floor * 4. When the page goes still, one near-lossless refresh frame is sent so text * sharpens after motion stops * 5. Encoded chunks travel over a reliable ordered RTCDataChannel; latency is * bounded by source-side frame dropping, driven by three independent * signals: network buffer, encoder queue, and the viewer's decode queue * * Audio Architecture: * 1. AudioContext interception (handled by BrowserAudioCapture) * 2. Audio encoded with AudioEncoder (Opus) in headless browser * 3. Encoded chunks sent via sendAudioChunk() to same DataChannel * * Client: * - Receives video + audio chunks via DataChannel * - Decodes with VideoDecoder (off the main thread when the browser allows it) * - Renders video to canvas, plays audio with proper scheduling */ import { EventEmitter } from 'events'; import type { CDPSession, Page } from 'puppeteer'; import type { BrowserTab, ClientCodecSupport, ClientDisplayMetrics, ClientStreamFeedback, PeerHealth, StreamingConfig } from './types'; import { DEFAULT_STREAMING_CONFIG, resolveCodecCandidates } from './types'; import { videoEncoderScript } from './scripts/video-stream'; import { audioCaptureScript } from './scripts/audio-stream'; /** * Install the audio tap in every frame. * * `page.evaluate` only reaches the main frame, so a `