M1: WebRTC egress PoC (WHEP subscriber, H.264+Opus passthrough) #1

Closed
zgaetano wants to merge 0 commits from m1-webrtc-poc into main
Owner

Summary

First milestone of the Datarhei — Dragon Fork WebRTC egress work. Adds a self-contained core/webrtc package plus a standalone PoC binary (cmd/webrtc-poc) and test helpers (test/publish.sh, test/whep-client/). Nothing in the existing datarhei Core binary is touched — all new code is additive and isolated behind a dedicated import path.

This is not wired into the process-graph or HTTP API yet; that is M2. The goal of M1 was to prove end-to-end: FFmpeg → UDP RTP → Pion → WHEP subscriber can render a live stream with <200ms latency on localhost.

What's in this PR

  • docs/design/ — WebRTC egress design spec + M1 plan (for posterity)
  • core/webrtc/ — the new package (built incrementally, TDD):
    • config.goConfig, DefaultConfig(), Validate() + port-range and ICE-server settings
    • errors.go — 5 sentinel errors mapped to HTTP status codes
    • registry.go — thread-safe stream_id → SourceHandle registry
    • source.go — UDP RTP reader with subscriber fan-out (drop-if-full)
    • ice.goBuildICEConfig producing webrtc.Configuration + *SettingEngine (NAT1To1IPs, ephemeral port range)
    • peer.goPeerFactory + Peer (H.264 video + Opus audio tracks, ICE gather, RTP forward goroutine, OnConnectionStateChange cleanup)
    • forward.go — payload-type dispatch (102 → video, 111 → audio)
    • whep.goWHEPHandler (POST /whep/{stream_id} → 201 + application/sdp + Location header, 404 / 405 / 503 paths)
  • cmd/webrtc-poc/main.go — standalone server (:8787 WHEP, 127.0.0.1:10000 RTP) for manual testing; will be demoted to a test helper in M2
  • test/publish.sh — FFmpeg publisher (testsrc2 + sine → H.264 baseline / Opus RTP)
  • test/whep-client/ — Pion-based WHEP subscriber CLI + in-process end-to-end test
  • chore(deps) — bumped Go 1.21 → 1.24 and resynced vendor/ for Pion WebRTC v4 compat

Test Plan

  • go test -race ./core/webrtc/... — 15 unit tests green (Config, Registry, Source, ICE, Peer, WHEP)
  • go test -race ./test/whep-client/... — in-process e2e (synthetic RTP injection → full subscribe → OnTrack fires for both video+audio)
  • Live verification: webrtc-poc + test/publish.sh + whep-client — Pion subscriber observed video/H264 pt=102 and audio/opus pt=111 RTP within <2s
  • Review: is the PT=102/111 assumption documented clearly enough for M2 to remove?
  • Review: confirm DefaultConfig() values (Cloudflare + Google STUN, 32 peer cap) are sane defaults for Dragon Fork

Out of scope for M1 / coming next

  • Integration with datarhei process-graph output module (M2)
  • HTTP API exposure under /api/v3/... (M2)
  • TURN/coturn authentication layer (M3)
  • Auth token on WHEP POST (M3)
  • Metrics / Prometheus (M3)
  • Re-streaming RTCP to the publisher (M3+)
## Summary First milestone of the **Datarhei — Dragon Fork** WebRTC egress work. Adds a self-contained `core/webrtc` package plus a standalone PoC binary (`cmd/webrtc-poc`) and test helpers (`test/publish.sh`, `test/whep-client/`). Nothing in the existing datarhei Core binary is touched — all new code is additive and isolated behind a dedicated import path. This is **not** wired into the process-graph or HTTP API yet; that is M2. The goal of M1 was to prove end-to-end: FFmpeg → UDP RTP → Pion → WHEP subscriber can render a live stream with <200ms latency on localhost. ## What's in this PR - `docs/design/` — WebRTC egress design spec + M1 plan (for posterity) - `core/webrtc/` — the new package (built incrementally, TDD): - `config.go` — `Config`, `DefaultConfig()`, `Validate()` + port-range and ICE-server settings - `errors.go` — 5 sentinel errors mapped to HTTP status codes - `registry.go` — thread-safe `stream_id → SourceHandle` registry - `source.go` — UDP RTP reader with subscriber fan-out (drop-if-full) - `ice.go` — `BuildICEConfig` producing `webrtc.Configuration` + `*SettingEngine` (NAT1To1IPs, ephemeral port range) - `peer.go` — `PeerFactory` + `Peer` (H.264 video + Opus audio tracks, ICE gather, RTP forward goroutine, OnConnectionStateChange cleanup) - `forward.go` — payload-type dispatch (102 → video, 111 → audio) - `whep.go` — `WHEPHandler` (POST `/whep/{stream_id}` → 201 + `application/sdp` + `Location` header, 404 / 405 / 503 paths) - `cmd/webrtc-poc/main.go` — standalone server (`:8787` WHEP, `127.0.0.1:10000` RTP) for manual testing; will be demoted to a test helper in M2 - `test/publish.sh` — FFmpeg publisher (testsrc2 + sine → H.264 baseline / Opus RTP) - `test/whep-client/` — Pion-based WHEP subscriber CLI + in-process end-to-end test - `chore(deps)` — bumped Go `1.21 → 1.24` and resynced `vendor/` for Pion WebRTC v4 compat ## Test Plan - [x] `go test -race ./core/webrtc/...` — 15 unit tests green (Config, Registry, Source, ICE, Peer, WHEP) - [x] `go test -race ./test/whep-client/...` — in-process e2e (synthetic RTP injection → full subscribe → OnTrack fires for both video+audio) - [x] Live verification: `webrtc-poc` + `test/publish.sh` + `whep-client` — Pion subscriber observed `video/H264 pt=102` and `audio/opus pt=111` RTP within <2s - [ ] Review: is the PT=102/111 assumption documented clearly enough for M2 to remove? - [ ] Review: confirm `DefaultConfig()` values (Cloudflare + Google STUN, 32 peer cap) are sane defaults for Dragon Fork ## Out of scope for M1 / coming next - Integration with datarhei `process-graph` output module (M2) - HTTP API exposure under `/api/v3/...` (M2) - TURN/coturn authentication layer (M3) - Auth token on WHEP POST (M3) - Metrics / Prometheus (M3) - Re-streaming RTCP to the publisher (M3+)
zgaetano added 12 commits 2026-04-17 09:00:05 -04:00
Pion webrtc/v4 (v4.2.11) requires Go 1.24+. Upstream datarhei was at
go 1.21.0. Bumping to go 1.24.0 pulls minor bumps across testify,
golang.org/x/{crypto,net,sync,sys,text,time,tools,mod}; vendor/ is
regenerated via 'go mod vendor' to reflect the new versions.

No application code changes; pure dep bump to unblock M1.
Adds github.com/pion/rtp v1.10.1 as a direct dependency (vendored).
Vendors github.com/pion/webrtc/v4 v4.2.11 and its transitive
dependencies (datachannel, dtls/v3, ice/v4, interceptor, logging,
mdns/v2, sctp, sdp/v3, srtp/v3, stun/v3, transport/v4, turn/v4).
Minimal egress-only server that wires Source, Registry, PeerFactory and
WHEPHandler together on a single stream id. Listens for RTP on a local
UDP port (default 127.0.0.1:10000) and serves WHEP on :8787.

Not part of the Core binary — will be demoted to an internal test helper
once M2 integrates WebRTC output into the process-graph.
Generates a synthetic testsrc2 video + sine audio and pushes H.264/Opus
RTP to the webrtc-poc's UDP port, using the hard-coded payload types
(102 video, 111 audio) the M1 forwarder dispatches on. Intended to be
run alongside test/whep-client (M1 Task 11) for end-to-end verification.
test(webrtc): add Pion WHEP subscriber client + e2e test
Some checks failed
tests / build (push) Failing after 13s
CodeQL / Analyze (pull_request) Failing after 2s
tests / build (pull_request) Failing after 2s
413d0f24b6
whep-client/main.go: minimal Pion subscriber that POSTs a recvonly
offer, applies the answer, and waits for one RTP packet on each of
the video and audio tracks. Used as M1's end-to-end verifier.

whep-client/main_test.go: in-process e2e wiring — stands up Source,
Registry, PeerFactory and WHEPHandler behind an httptest server,
injects synthetic PT=102/111 RTP on the Source's UDP port and calls
Subscribe. Validates the full egress pipeline without requiring
FFmpeg or external network. Skipped under -short.
zgaetano added 1 commit 2026-04-17 09:05:46 -04:00
feat(webrtc): add -rtp-host flag + TrueNAS Docker deploy
Some checks failed
tests / build (push) Failing after 3s
CodeQL / Analyze (pull_request) Failing after 3s
tests / build (pull_request) Failing after 3s
9e3f031f95
- core/webrtc: NewSourceOn(streamID, host, port) allows binding the
  RTP UDP socket on something other than 127.0.0.1, required when the
  PoC runs in a container and must accept RTP from LAN publishers.
  NewSource(streamID, port) stays as a convenience wrapper on
  127.0.0.1 for existing tests and tight local tests.

- cmd/webrtc-poc: new -rtp-host flag (default 127.0.0.1 for safety).

- deploy/docker/Dockerfile: two-stage build, scratch runtime, ~14 MB.

- deploy/truenas/docker-compose.yml: host-networked stack template
  driven by a .env file. Host networking is required for WebRTC ICE
  to work without NAT rewriting per-candidate.

- deploy/truenas/README.md: operator runbook with port picking,
  bring-up, verification curls, and security notes.
Author
Owner

Merged into main via the v0.1.0-dragonfork release stack. The M1 commits land in main via m1-webrtc-poc → m2-webrtc-core-integration → main. Closing.

Merged into `main` via the v0.1.0-dragonfork release stack. The M1 commits land in main via m1-webrtc-poc → m2-webrtc-core-integration → main. Closing.
zgaetano closed this pull request 2026-05-03 08:28:59 -04:00
Some checks failed
tests / build (push) Failing after 3s
CodeQL / Analyze (pull_request) Failing after 3s
tests / build (pull_request) Failing after 3s

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: zgaetano/datarhei-dragonfork-core#1
No description provided.