rust/ becomes a standalone static binary (blax-webrtc-sfu, tokio-free std-thread loop on str0m): terminates ICE/DTLS/SRTP, forwards Opus between room peers without decoding, records per-peer Ogg/Opus (ffprobe-verified), server-side mute/kick, stats + peer_connected/peer_left usage events, all driven by PHP over a JSON-lines Unix control socket. Renegotiation rides the browser data channel (str0m chat model) — signaling only carries the initial offer/answer. PHP side: RustMediaEngine (MediaEngine contract + room-aware API) is plug-and-play — first use downloads the sha256-verified prebuilt binary (BinaryManager, RoadRunner pattern) and spawns it detached, flock-guarded (SidecarSupervisor). webrtc:install / webrtc:sidecar provided for explicit control; config gains the webrtc.sfu block. Replaces the ext-php-rs Str0mMediaEngine seam. Tests: 61 PHP (incl. a real PHP<->Rust E2E that spawns the sidecar from PHPUnit) + 7 Rust (incl. an end-to-end control-socket integration test). build-release.sh produces the static-musl release + .sha256 (verified: static-pie, 9.2MB, runs). Card #1083 (rel #1051 #1060 #1064). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| build-release.sh | ||
README.md
blax-webrtc-sfu — the Rust data plane
The sidecar binary behind Blax\WebRtc\Media\RustMediaEngine: backend-hosted
rooms. PHP/Laravel owns the control plane (rooms, auth, signaling
orchestration, billing hooks); this process owns everything per-packet.
- ICE (Interactive Connectivity Establishment — finds a working network path to each browser), DTLS (the UDP TLS handshake that negotiates keys) and SRTP (the encrypted media packets) are terminated here via str0m, a sans-IO Rust WebRTC stack.
- SFU forwarding (Selective Forwarding Unit): each peer's Opus audio is
received once and forwarded to the other peers of its room without
decoding — one loop thread, one shared UDP socket,
rtc.accepts()demultiplexing. - Recording: per-peer Ogg/Opus written straight from the depayloaded stream — playable by ffmpeg/browsers, no transcoding, no client cooperation.
- Negotiation: the initial offer/answer arrives via the control socket
(from PHP signaling). Renegotiation as peers join rides each browser's
WebRTC data channel directly — PHP never relays it (see str0m's
chatexample, which this follows).
Control protocol (PHP ⇄ sidecar)
JSON-lines over a Unix socket. Requests carry an id echoed by the reply;
notices are pushed without one.
→ {"id":1,"cmd":"add_peer","room":"lobby","peer":"alice","offer":"v=0…","record":"/rec/alice.ogg"}
← {"id":1,"ok":true,"answer":"v=0…"}
→ {"id":2,"cmd":"mute_peer","room":"lobby","peer":"alice","muted":true}
→ {"id":3,"cmd":"record_start","room":"lobby","peer":"alice","path":"/rec/a.ogg"} (record_stop too)
→ {"id":4,"cmd":"remove_peer","room":"lobby","peer":"alice"}
→ {"id":5,"cmd":"stats"} ← rooms → peers → {connected, muted, seconds}
→ {"id":6,"cmd":"ping"} / {"id":7,"cmd":"shutdown"}
← {"event":"peer_connected","room":"lobby","peer":"alice"}
← {"event":"peer_left","room":"lobby","peer":"alice","seconds":42.5}
Build & test
cargo build && cargo test # 7 tests incl. an end-to-end control-socket run
./build-release.sh # static musl release + .sha256 into dist/
On NixOS: nix-shell -p gcc pkg-config openssl perl gnumake musl --run ./build-release.sh.
Releasing
BinaryManager::VERSION (PHP) pins the release tag it downloads from. To ship:
build dist/ artifacts (linux-x86_64 at minimum), attach both files to the
Forgejo release with that tag, and bump VERSION when the protocol changes.
PHP resolves binaries in this order: webrtc.sfu.binary override → a local
target/{release,debug} build (dev) → the downloaded install.
CLI
blax-webrtc-sfu --socket /run/sfu.sock --udp-ip 0.0.0.0 --udp-port 41000 --public-ip 203.0.113.9
--udp-port 0 (default) picks an ephemeral port — fine on one host, pin it in
production and publish it (UDP) on the container. --public-ip is what
browsers are told to reach in the ICE host candidate; it defaults to the
default-route interface address.