{"id":45953383,"url":"https://github.com/danielbodart/zerocast","last_synced_at":"2026-04-28T11:01:55.206Z","repository":{"id":341027877,"uuid":"1168505116","full_name":"danielbodart/zerocast","owner":"danielbodart","description":"Highly opinionated application sharing for developers.","archived":false,"fork":false,"pushed_at":"2026-04-07T08:07:04.000Z","size":5034,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"trunk","last_synced_at":"2026-04-07T09:34:22.732Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://zerocast.bodar.com","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielbodart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-27T13:21:17.000Z","updated_at":"2026-04-07T08:03:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/danielbodart/zerocast","commit_stats":null,"previous_names":["danielbodart/barecast","danielbodart/zerocast"],"tags_count":120,"template":false,"template_full_name":null,"purl":"pkg:github/danielbodart/zerocast","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Fzerocast","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Fzerocast/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Fzerocast/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Fzerocast/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielbodart","download_url":"https://codeload.github.com/danielbodart/zerocast/tar.gz/refs/heads/trunk","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Fzerocast/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31962894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-28T13:18:19.180Z","updated_at":"2026-04-18T09:07:57.110Z","avatar_url":"https://github.com/danielbodart.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zerocast\n\nHighly opinionated application sharing for developers.\n\n- **Zero latency\\*** — GPU-direct capture, hardware encode, P2P WebRTC with zero jitter buffer. End-to-end latency measured as low as 1ms on a local network. (\\*We use the [abs-capture-time](https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time/) RTP extension to measure true capture-to-render latency, and [playout-delay](https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay/) set to zero to eliminate the browser's jitter buffer entirely.)\n- **Zero CPU copy** — Pixels never leave the GPU. NvFBC capture, CUDA interop, NVENC encode — all on GPU hardware. The CPU only sees the encoded bitstream.\n- **Zero audio, zero webcam** — Screen only. This is a collaboration tool, not a video call. Use your existing voice chat.\n- **Zero install for viewers** — Open a URL, see the application. No native app, no extension, no plugin.\n- **Zero shaders** — NvFBC gives a GPU texture. CUDA copies it to linear device memory. NVENC encodes with internal ARGB→NV12 color space conversion. No GL shaders, no compute passes.\n- **Zero codec negotiation** — AV1 preferred, HEVC fallback. Auto-detected at startup. One codec per session, no mid-stream switching.\n- **Zero infrastructure** — Signaling runs on Cloudflare Workers (serverless, hibernating Durable Objects). Media flows P2P via STUN. Cloudflare TURN as a last resort.\n- **Zero config** — Run the binary, share the URL. That's it.\n\nBuilt in Zig. Linux + NVIDIA today, macOS + Apple Silicon next.\n\n\u003e This project is a work in progress. The core capture and streaming pipeline is working end-to-end. See [Current Status](#current-status) for details.\n\n## What It Does\n\nTwo sharing modes, both streaming over WebRTC to a browser viewer:\n\n### Application Sharing (`zerocast share app \u003ccommand\u003e`)\n\nLaunches your application in an isolated headless Xorg display, captures it via NvFBC, and streams over WebRTC. Each app gets its own X server — no compositor needed, no interference with your host desktop.\n\n- **Isolated display** — One headless Xorg per app (`:10`, `:11`, etc.), `UseDisplayDevice \"none\"` avoids modesetting conflicts with the host GPU\n- **Viewer-initiated resize** — Viewer resizes their browser window, the pipeline tears down and rebuilds at the new resolution (~300ms)\n- **Frame rate capping** — `fpscap.so` LD_PRELOAD hooks `glXSwapBuffers` with `clock_nanosleep` to prevent apps from spinning at 100% CPU in the vblank-less headless display\n- **Remote input** — Keyboard and mouse events from the browser are injected into the headless display via XTEST. Platform-independent `KeyboardEvent.code` mapped to Linux keycodes\n- **Input isolation** — `AutoAddDevices \"false\"` prevents physical keyboard/mouse from leaking into the headless display\n\n### Terminal Sharing (`zerocast share terminal [command]`)\n\nSpawns a PTY, streams terminal output over a WebRTC data channel to an xterm.js viewer in the browser.\n\n- **Replay buffer** — 256KB ring buffer replays recent output to late-joining viewers\n- **Resize** — Viewer resize propagates back via `TIOCSWINSZ`\n- **Asciinema recording** — Optional recording in asciinema v2 format for playback later\n- **Bidirectional** — Viewer keystrokes sent back over the data channel\n\n### Multi-Cursor Collaboration\n\nEvery viewer gets an assigned color. Cursor positions and draw paths relay through the server to all other viewers in real-time.\n\n- **SVG overlay** — Rendered browser-side with `viewBox` set to native video resolution. Bibata cursor shapes, per-viewer colored paths\n- **Drawing mode** — Toggle with Tab. Left-drag to draw, right-click to undo, long-press right-click to clear\n- **Binary wire protocol** — Compact little-endian messages over unreliable/unordered WebRTC data channel (UDP semantics). Mouse, keyboard, draw, resize, relay, and color assignment message types\n- **Local-first rendering** — Own draw paths render instantly (no round-trip). Remote paths relay via `[0xFE][color_index][msg]` prefix\n\n### Hub Page\n\nEach room gets a landing page showing all active shares as live stats cards (resolution, FPS, bitrate, share type). Click a card to open the viewer in a pop-out window.\n\n## Architecture\n\n```\nzerocast daemon\n├── share app glxgears     → Headless Xorg :10 → NvFBC → CUDA → NVENC (AV1/HEVC) → WebRTC\n├── share app firefox      → Headless Xorg :11 → NvFBC → CUDA → NVENC (AV1/HEVC) → WebRTC\n├── share terminal         → PTY → data channel → xterm.js\n└── Unix socket ← CLI commands (share, unshare, join, status)\n\nCloudflare Worker + Durable Object\n├── WebSocket signaling (SDP/ICE exchange, hibernates when idle)\n├── TURN credential provisioning (Cloudflare TURN API)\n├── Hub page, viewer app, terminal viewer (served as static HTML/JS)\n└── Room auto-creation on first connection (client-generated IDs)\n```\n\n### Capture Pipeline (Linux — NVIDIA + X11)\n\n```\nNvFBC (GPU texture, BGRA)\n  → CUDA resource (cuGraphicsGLRegisterImage, zero-copy)\n    → NVENC hardware encode (AV1 or HEVC, ARGB input, internal CSC to NV12)\n      → libdatachannel (RTP packetization, SRTP, abs-capture-time)\n        → WebRTC P2P to browser\n```\n\nEverything stays GPU-resident. CPU usage near 0%.\n\n### Transport: WebRTC via libdatachannel\n\n[libdatachannel](https://github.com/paullouisageneau/libdatachannel) — lightweight C/C++ WebRTC library (~20MB statically linked, vs Google's libwebrtc at 600MB). We maintain a [fork](https://github.com/danielbodart/libdatachannel) with two additions:\n\n| Extension | What it does |\n|---|---|\n| **abs-capture-time** (extmap 3) | Embeds an NTP timestamp from the moment of GPU capture into each RTP packet. The browser reads this via `getSynchronizationSources().captureTimestamp` to compute true end-to-end latency. |\n| **playout-delay** (extmap 4, min=max=0) | Instructs Chrome to render frames immediately with zero jitter buffer. Trades smoothness for latency — the right trade-off for interactive application sharing. |\n\n**RTCP chain per peer:**\n- Sender Reports for synchronization\n- NACK responder with 512-packet retransmission history\n- PLI handler — triggers instant keyframe on viewer join or packet loss recovery\n\n**Why not Cloudflare Calls?** Their SFU relays all media through Cloudflare's edge (adds latency for same-network peers), has unidirectional data channels (breaks remote input), no C API, and is overkill for 1:1 or 1:few pair programming. We use Cloudflare for TURN relay and signaling only.\n\n### Signaling: Cloudflare Workers\n\nOne Durable Object per room. WebSocket Hibernation API — the DO sleeps between messages, costing nothing during the actual sharing session. Signaling is ~20 messages at setup then silence.\n\n```\nSharer (Zig)              Worker DO              Viewer (Browser)\n  ├── WS connect ────────►│◄── WS connect ────────┤\n  ├── SDP offer ──────────►│──► SDP offer ─────────►│\n  │◄── SDP answer ─────────│◄── SDP answer ─────────┤\n  ├── ICE candidates ─────►│──► ICE candidates ────►│\n  │                        │   (DO hibernates)      │\n  │◄────── direct P2P media + data channel ────────►│\n```\n\nNAT traversal: STUN (`stun.cloudflare.com`) for ~85% of connections, Cloudflare TURN relay as fallback.\n\n## Codec Support\n\nAV1 preferred, HEVC fallback. The codec is auto-detected at startup by querying the GPU's supported encode GUIDs. One codec per session — no mid-stream negotiation.\n\n| Codec | GPU requirement | Browser decode |\n|-------|----------------|---------------|\n| **AV1** (preferred) | NVIDIA RTX 40-series+ (Ada/Blackwell) | Chrome 70+, Firefox 67+, Safari 17+ |\n| **HEVC** (fallback) | NVIDIA GTX 950+ (Maxwell Gen 2+) | Chrome 107+, Safari 11+, Firefox 120+ |\n\n### Why AV1 is Preferred\n\n- **30–50% better compression** than HEVC at same quality — lower bandwidth for remote sessions\n- **Universal browser decode** — all major browsers support AV1 WebRTC without flags\n- **Future-proof** — when hardware encoders add screen content coding tools (IBC, palette mode), AV1 benefits most\n\n### Why HEVC Fallback Matters\n\nAV1 hardware encode requires RTX 40-series or newer. HEVC goes back to 2014 (GTX 950), covering the entire GTX 10-series, RTX 20-series, and RTX 30-series — a vastly larger installed base.\n\n### NVENC Encoding Configuration\n\nBoth codecs share the same pipeline configuration:\n\n- **P-only GOP** — `frameIntervalP=1`, no B-frames. Minimum encode latency. Keyframes only when a viewer joins or requests one via PLI.\n- **Repeat headers** — Every keyframe includes sequence/parameter headers (AV1: `repeatSeqHdr`, HEVC: `repeatSPSPPS`), allowing late-joining viewers to start decoding immediately.\n- **Adaptive VBR** — Linear bitrate scaling: `90kbps + (pixels × fps × 0.012)`, capped at 10Mbps. Max bitrate = 2× average. VBV buffer = 1 second. Calibrated for screen content: 150×150 at 98kbps, 1080p at approx 1.5Mbps, 4K at approx 3Mbps.\n- **HQ tuning preset** — Counterintuitively, NVENC's high-quality preset (P4 + HQ tuning) produces better results for screen content than the low-latency preset.\n- **BT.709 color metadata** — Explicit BT.709 primaries/transfer/matrix with limited range so browsers decode consistently. AV1 uses top-level config fields; HEVC uses VUI parameters.\n- **Direct ARGB input** — NVENC takes ARGB directly (matching NvFBC's BGRA byte order on little-endian) and performs internal CSC to NV12. No CPU color conversion needed.\n\n## Latency Telemetry\n\nThe stats panel in the viewer breaks down latency into components:\n\n| Metric | How it's measured |\n|---|---|\n| **End-to-end** | abs-capture-time NTP timestamp vs `Date.now()` at render |\n| **Server** | e2e − browser delay |\n| **Decode** | `totalDecodeTime` from WebRTC stats (per-frame average) |\n| **Jitter buffer** | `jitterBufferDelay / jitterBufferEmittedCount` (should be ~0 with playout-delay=0) |\n| **Render** | Browser delay − processing delay (compositor + vsync wait) |\n| **Network** | RTT/2 from ICE candidate pair stats |\n\nPlus: resolution, FPS, bitrate, packets lost, decoder implementation (hardware/software), candidate type (host/srflx/relay).\n\n## Project Structure\n\n| File | What it does |\n|---|---|\n| `src/app_share.zig` | App sharing session — headless Xorg, NvFBC capture loop, resize, input |\n| `src/terminal_share.zig` | Terminal sharing — PTY, replay buffer, asciinema recording |\n| `src/session.zig` | WebRTC broadcast — peer lifecycle, signaling, data channels, relay |\n| `src/codec.zig` | Codec enum (AV1/HEVC) — shared across encoder, session, recorder |\n| `src/encoder.zig` | Encode pipeline — CUDA copy, NVENC encode, idle detection, timing telemetry |\n| `src/nvenc.zig` | NVENC SDK 12.0 bindings — AV1/HEVC config, codec detection, VBR rate control |\n| `src/cuda.zig` | CUDA Driver API — GL texture interop, pitched device memory |\n| `src/nvfbc.zig` | NvFBC bindings — GPU texture capture, polling mode |\n| `src/daemon.zig` | Daemon — Unix socket listener, session slots (up to 8), thread lifecycle |\n| `src/cli.zig` | CLI — subcommand parser (`share`, `unshare`, `join`, `status`) |\n| `src/control.zig` | Wire protocol — JSON over Unix socket between CLI and daemon |\n| `src/headless_display.zig` | Headless Xorg — config generation, display discovery, xrandr resize |\n| `src/xorg.zig` | Setuid helper — Xorg process lifecycle (minimal, root-only) |\n| `src/xtest_input.zig` | Input injection — XTEST extension, keycode mapping |\n| `src/input_protocol.zig` | Binary protocol — mouse, keyboard, draw, resize, relay messages |\n| `src/viewer_state.zig` | Multi-viewer state — color assignment, cursor/path tracking |\n| `src/fpscap.zig` | Frame rate cap — LD_PRELOAD `glXSwapBuffers` hook |\n| `src/kms.zig` | KMS helper — DRM plane capture, DMA-BUF export (retained for Wayland) |\n| `worker/src/room.ts` | Durable Object — signaling, TURN credentials, shares-list broadcast |\n| `worker/src/hub.ts` | Hub page — live session cards, pop-out viewer windows |\n| `worker/src/viewer.ts` | App viewer — WebRTC client, stats panel, abs-capture-time e2e latency |\n| `worker/src/terminal-viewer.ts` | Terminal viewer — xterm.js + WebRTC data channel |\n| `worker/src/overlay.ts` | SVG overlay — multi-cursor rendering, draw paths, Bibata cursors |\n| `worker/src/input.ts` | Input controller — binary encoding, coordinate mapping, draw/input modes |\n| `build.zig` | Build system — executables, modules, static libdatachannel, tests |\n| `run.ts` | Task runner — build, test, lint, setup, worker-dev, worker-deploy |\n\n## Hardware Requirements\n\n**Sharer (native Zig binary):**\n- NVIDIA GTX 950+ (HEVC) or RTX 40-series+ (AV1) — auto-detected at startup\n- Intel/AMD VA-API — future Linux backend\n- Apple Silicon (VideoToolbox) — future macOS backend\n\n**Viewer (browser only):**\n- Any modern browser — AV1 and HEVC are both widely supported in WebRTC\n\n## Current Status\n\n**Working end-to-end on Linux + NVIDIA + X11:**\n- Application sharing with headless Xorg, NvFBC capture, hardware encode (AV1/HEVC), WebRTC streaming\n- Terminal sharing with PTY, data channel transport, xterm.js viewer\n- Multi-cursor collaboration with drawing/annotation\n- Remote keyboard/mouse input\n- Viewer-initiated resize\n- Hub page with live session stats\n- Cloudflare Worker signaling with TURN fallback\n\n**Next:**\n- macOS backend (ScreenCaptureKit + VideoToolbox + Metal)\n- Linux Wayland support (KMS/DRM capture path is implemented, needs EGL→CUDA wiring)\n- Intel/AMD GPU support via VA-API (HEVC + AV1)\n\n## Build \u0026 Run\n\nRequires Linux with an NVIDIA GPU. Zig and Bun are installed automatically via `bootstrap.sh` + mise.\n\n```bash\ngit clone \u003crepo\u003e \u0026\u0026 cd zerocast\n./run.ts              # bootstrap, build, lint, test — one command\n./run.ts setup        # install binaries + setcap on helpers\n\n# Run locally\n./run.ts worker-dev                                              # signaling server on :8787\nZEROCAST_URL=http://localhost:8787 dist/bin/zerocast daemon      # start daemon\ndist/bin/zerocast join myroom                                    # join a room\ndist/bin/zerocast share app glxgears                             # share an app\ndist/bin/zerocast share terminal                                 # share a terminal\n```\n\n```\n./run.ts              # build + lint + test (default)\n./run.ts build        # zig build ReleaseSafe\n./run.ts test         # unit + property tests\n./run.ts lint         # zwanzig static analysis + shellcheck\n./run.ts integration  # GPU integration test (captures 3s video, validates with ffprobe)\n./run.ts worker-dev   # local Cloudflare Worker on :8787\n./run.ts worker-deploy # deploy Worker to production\n./run.ts rebuild-libs # rebuild libdatachannel static libs\n./run.ts setup        # build + install + setcap\n./run.ts clean        # rm -rf dist/bin .zig-cache\n```\n\n## Acknowledgements\n\n- [libdatachannel](https://github.com/paullouisageneau/libdatachannel) by Paul-Louis Ageneau — lightweight WebRTC in C/C++\n- [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/) by dec05eba — reference for NvFBC capture and NVENC encoding patterns\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielbodart%2Fzerocast","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielbodart%2Fzerocast","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielbodart%2Fzerocast/lists"}