You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Native SDK has an audio-capture design in #262, with an implementation in #264. That work establishes useful capture conventions:
Explicit permission checks; starting capture never prompts.
Keyed capture sessions.
Negotiated output formats.
Bounded delivery with observable backpressure.
Clear start, stop, failure and rejection behavior.
Stop-and-drain versus immediate discard.
Zig and TypeScript command/event parity.
Honest capability discovery and null-platform behavior.
Screen and camera capture should reuse those conventions, while acknowledging that raw video cannot travel through the TypeScript model like short PCM chunks.
This issue proposes the shared video-capture contract needed before implementing actual ScreenCaptureKit, AVFoundation, Windows or Linux backends.
Screen/window source discovery is also requested in #240. That issue can build on the contract defined here.
Proposed scope
Shared video-capture source, format, timing, lifecycle and failure types.
A keyed video-capture session API usable by display, window and camera sources.
TypeScript lifecycle commands and fixed event records.
Zig types for native captured video frames.
Optional binding of a capture session to a media-surface for preview.
Capability flags for display, window and camera capture.
Null-platform implementations that report unsupported honestly.
Runtime, fake-executor, journal/replay and lifecycle tests.
No real platform capture backend in the initial contract PR.
Relationship to audio capture
Audio and video should share control-plane concepts but not their data plane.
Audio capture can return short borrowed PCM chunks through audioCaptureRead. A video frame may contain several megabytes and can be GPU-backed, so pixels must not enter the TypeScript Model, Msg, command wire format or session journal.
presentationTimeMicros uses one process-monotonic clock shared by audio, display, window and camera capture. It describes when media should be presented, not when the application happens to receive it.
This lets applications synchronize a future CapturedVideoFrame with the CapturedAudioChunk proposed in #264.
Proposed video types
exporttypeVideoCaptureSource=|{readonlykind: "display";readonlyid: Uint8Array;readonlyincludeCursor?: boolean;readonlyexcludeCurrentProcessWindows?: boolean;}|{readonlykind: "window";readonlyid: Uint8Array;readonlyincludeCursor?: boolean;}|{readonlykind: "camera";readonlydevice: "default"|Uint8Array;};exporttypeVideoPixelFormat=|"bgra8"|"nv12";exportinterfaceVideoCaptureOptions{readonlysource: VideoCaptureSource;// Preferred constraints. The negotiated values are reported by started.readonlypreferredWidth?: number;readonlypreferredHeight?: number;readonlypreferredFrameRate?: number;readonlypreferredPixelFormat?: VideoPixelFormat;}
Display/window and camera enumeration remain source-specific APIs. Both produce selections that can be passed to the shared video-capture start command.
Cmd.videoCaptureStart(key,options,{event: "video_capture"},);Cmd.videoCaptureStop(key);Cmd.videoCaptureDiscard(key);// Attach or replace a latest-wins preview surface.// Surface 0 detaches the preview.Cmd.videoCaptureSetSurface(key,surface);
Unlike the initial audio contract, the video contract must allow multiple concurrent sessions. Screen and camera capture commonly run at the same time.
State behavior
started reports the negotiated format after capture is operational.
format_changed reports a source-driven resolution or format change.
stopped indicates an orderly requested stop.
failed terminates a session after an asynchronous capture failure.
rejected means the start command was not accepted.
stop seals native consumer queues so previously accepted frames can drain.
discard releases the session and queued frames immediately.
Stopping or discarding an unknown key is a no-op.
Late events from a previous generation of the same key must be ignored.
No generic discontinuity Boolean is proposed. Source loss, dropped frames, format changes and backend failures have explicit representations.
Native frame representation
The Zig tier needs an opaque, reference-counted video-frame representation:
pubconstCapturedVideoFrame=struct {
timing: CaptureTiming,
/// Index assigned to frames produced by this capture source.frame_index: u64,
/// Source frames discarded before this delivered frame.dropped_before: u32,
width: u32,
height: u32,
pixel_format: VideoPixelFormat,
color_space: VideoColorSpace,
rotation: VideoRotation,
/// CPU planes or platform-native GPU-backed storage.storage: VideoFrameStorage,
};
This issue defines the type and ownership contract. A follow-up can expose a native consumer along these lines:
Motivation
Native SDK has an audio-capture design in #262, with an implementation in #264. That work establishes useful capture conventions:
Screen and camera capture should reuse those conventions, while acknowledging that raw video cannot travel through the TypeScript model like short PCM chunks.
This issue proposes the shared video-capture contract needed before implementing actual ScreenCaptureKit, AVFoundation, Windows or Linux backends.
Screen/window source discovery is also requested in #240. That issue can build on the contract defined here.
Proposed scope
media-surfacefor preview.Relationship to audio capture
Audio and video should share control-plane concepts but not their data plane.
Audio capture can return short borrowed PCM chunks through
audioCaptureRead. A video frame may contain several megabytes and can be GPU-backed, so pixels must not enter the TypeScriptModel,Msg, command wire format or session journal.The proposed common timing vocabulary is:
The matching TypeScript fields are:
presentationTimeMicrosuses one process-monotonic clock shared by audio, display, window and camera capture. It describes when media should be presented, not when the application happens to receive it.This lets applications synchronize a future
CapturedVideoFramewith theCapturedAudioChunkproposed in #264.Proposed video types
Display/window and camera enumeration remain source-specific APIs. Both produce selections that can be passed to the shared video-capture start command.
Lifecycle contract
Proposed commands:
Unlike the initial audio contract, the video contract must allow multiple concurrent sessions. Screen and camera capture commonly run at the same time.
State behavior
startedreports the negotiated format after capture is operational.format_changedreports a source-driven resolution or format change.stoppedindicates an orderly requested stop.failedterminates a session after an asynchronous capture failure.rejectedmeans the start command was not accepted.stopseals native consumer queues so previously accepted frames can drain.discardreleases the session and queued frames immediately.No generic
discontinuityBoolean is proposed. Source loss, dropped frames, format changes and backend failures have explicit representations.Native frame representation
The Zig tier needs an opaque, reference-counted video-frame representation:
This issue defines the type and ownership contract. A follow-up can expose a native consumer along these lines:
The TypeScript core receives lifecycle and diagnostic events only. It never receives raw pixel bytes or platform frame handles.
Preview delivery
A capture session may feed an existing
media-surface.Preview delivery uses the media surface's latest-wins behavior:
The first implementation may convert frames to the existing RGBA8 surface format. Zero-copy platform texture adoption can be added separately.
Capability flags
Add independent capabilities so applications can describe platform support honestly:
A platform may support only a subset. For example, a backend could initially support display capture but not individual window capture.
The existence of the shared API must not imply that a production host supports a particular source kind.
Null-platform behavior
The null platform should:
videoCaptureStartexactly once withreason = .unsupported.started.Lifecycle tests
Add tests covering:
rejectedevent.started → format_changed → stopped.started → failed.framesProducedandframesDroppedremain diagnostic counters.Suggested implementation split
Out of scope
Follow-up work
Once this contract is accepted, platform work can proceed independently: