Backend-hosted rooms now work on a plain `composer require`/`update` with no
download step: rust/dist/blax-webrtc-sfu-linux-x86_64 (+ .sha256) is committed
and BinaryManager::find() resolves rust/dist/blax-webrtc-sfu-<platform> before
the download fallback. A consuming app just sets WEBRTC_MEDIA_ENGINE=
RustMediaEngine (+ pin/publish WEBRTC_SFU_UDP_PORT and WEBRTC_SFU_PUBLIC_IP for
prod) and the sidecar auto-spawns.
- .gitignore: keep rust/target/ ignored, COMMIT rust/dist/ (shipped binaries)
- BinaryManager: add shipped-binary resolution + doc; README/doc updated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rooms become entities the BACKEND defines — clients can only join a registered
room, and each room configures how its media is carried:
- Rooms\RoomMode: 'p2p' (browsers mesh; server relays signaling only) vs 'sfu'
(server terminates media via the MediaEngine/Rust SFU).
- Rooms\RoomDefinition: id + mode + limit + RoomType (explicit or from the id
prefix) + free-form meta for the host UI.
- Contracts\RoomRegistry + Rooms\ConfigRoomRegistry (reads config webrtc.rooms;
supports shorthand 'room' => 'p2p'). The provider binding is host-overridable
via config webrtc.room_registry so DYNAMIC (DB/Eloquent) registries plug in —
that is how learn-atc sources rooms from its talk_rooms table at runtime.
5 new tests (66 total green).
Card #1087 (rel #1083#1085).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Rooms / recording / realtime bridge no longer require the bundled ReactPHP
server, so a host (e.g. a fork-per-message Laravel WS) can consume the package
on its own transport:
- RoomStore contract + ArrayRoomStore; RoomManager takes a store so membership
can be Cache/Redis-backed and survive across workers
- Realtime\OpenAiRealtimeBridge (+ RealtimeTransport / TextalkRealtimeTransport /
RealtimeTurn): the OpenAI GA realtime WS mechanism, turn-based, unit-tested via
a scripted transport
- Support\Audio (PCM16 -> WAV); RecordingStore::discard()
- provider binds RoomStore/RoomManager/RecordingStore/OpenAiRealtimeBridge and
guards webrtc:serve behind class_exists(laravel-ws + reactphp-kernel)
- laravel-ws + reactphp-kernel + react/* -> require-dev; runtime needs only
illuminate + textalk/websocket
- 40 tests green
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rooms
- Rooms\RoomType derived from the name prefix (like laravel-websockets channels):
public, private-* (authorized), presence-* (authorized + member list),
open-presence-* (public + member list). Room/RoomManager carry per-member info.
- Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence
until the host binds one). Presence rooms broadcast the member list + info.
Recording (store the FULL call audio, in PHP, today)
- Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead
the browser MediaRecords its mic and streams chunks over the WS as BINARY frames
(needs the new laravel-ws onBinary); RelaySignalingHandler appends them per
participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm.
welcome/joined carry a record flag telling the client to stream.
- Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls
are auditable even when unrecorded.
Wiring
- RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider +
config/webrtc.php (recording enabled/path/format, authorizer, events).
Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating,
binary recording+finalize+events, RoomType, and the real WS round-trip. README
documents room types + the browser MediaRecorder->WS recording flow.
rel learn-atc #1060#1055#1057#1051
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The key insight: a 2-party / small-mesh call needs NO server-side media — the
server only relays SDP/ICE and the browsers connect peer-to-peer. So the webrtc
package now has a genuinely working call MVP with no Rust/media engine.
- Signaling\RelaySignalingHandler: a blax-software/laravel-ws MessageHandler that
relays between browsers — welcome/join/joined/peer-joined/relay/broadcast/
peer-left. Peers in the kernel ConnectionRegistry, rooms in RoomManager.
- webrtc:serve now runs the relay over a laravel-ws WebSocketServer (browser-usable
ws://). require blax-software/laravel-ws.
- The engine-backed path (SignalingHandler/SignalingServer + MediaEngine) stays
for server-terminated media (recording/AI-bridge/SFU, Str0m = #1051).
- Tests 26 / 60 incl a REAL WebSocket welcome round-trip (browser->laravel-ws->relay);
verified with laravel-ws + reactphp-kernel resolved from Forgejo. README leads
with the browser P2P quickstart + JS example.
rel learn-atc #1055#1051#1019
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rooms\Room + Rooms\RoomManager: track peers per room, auto-mesh a new joiner
with everyone already there, drop empty rooms
- SignalingHandler: join/leave message types (auto-connectPeers on join), and
close now also leaves the room; expose rooms()
- SignalingServer::address() for the bound listen address
- Tests: real offer->answer round-trip over a live TCP socket on the kernel loop,
RoomManager + join/leave/close coverage. Suite: 17 tests / 42 assertions
- Verified on the updated reactphp-kernel (logging + registry)
rel learn-atc #1055#1051#1019
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WebRTC for Laravel on blax-software/reactphp-kernel. PHP owns signaling +
orchestration; a pluggable MediaEngine owns real-time media, with the production
engine backed by a Rust core (str0m via ext-php-rs) so PHP never runs per-20ms DSP.
- WebRtcServiceProvider + config/webrtc.php + webrtc:serve command
- Contracts\MediaEngine (offer/ice/record/connect/bridge/close)
- Media\NullMediaEngine (signaling-only) + Media\Str0mMediaEngine (Rust seam)
- Signaling\SignalingHandler (transport-agnostic) + Server\SignalingServer (on the kernel)
- rust/ crate stub (blax_webrtc: str0m + ext-php-rs) documenting the media plan
Enables: server-side record/intercept, provider-hidden AI-realtime bridging,
and party-to-party/group calls. rel learn-atc #1055#1051#1019
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>