env('WEBRTC_HOST', '127.0.0.1'), 'port' => (int) env('WEBRTC_PORT', 8090), // ReactPHP TLS context; leave both unset for plain TCP. 'tls' => array_filter([ 'local_cert' => env('WEBRTC_TLS_CERT'), 'local_pk' => env('WEBRTC_TLS_KEY'), ]), /* |-------------------------------------------------------------------------- | Media engine |-------------------------------------------------------------------------- | The backend that terminates media (ICE/DTLS/SRTP/RTP/Opus), records, and | bridges to external realtime providers. NullMediaEngine = signaling only | (peer-to-peer mesh); RustMediaEngine = BACKEND-HOSTED rooms on the Rust | SFU sidecar (plug-and-play — see the `sfu` block below). */ 'media_engine' => env('WEBRTC_MEDIA_ENGINE', NullMediaEngine::class), /* |-------------------------------------------------------------------------- | Rust SFU sidecar (RustMediaEngine) |-------------------------------------------------------------------------- | The `blax-webrtc-sfu` binary from this package's rust/ directory: one | process per host that terminates ICE/DTLS/SRTP and forwards audio between | room peers (no decoding), records Ogg/Opus per peer, and reports usage. | | Plug-and-play defaults: the first RustMediaEngine call DOWNLOADS the | checksummed prebuilt binary for this platform (auto_install) and SPAWNS | it detached, flock-guarded (auto_spawn). For supervised deployments run | `php artisan webrtc:sidecar` as a service and disable auto_spawn. | | udp_port 0 = ephemeral (fine same-host); pin it (and publish it on the | container) for production, and set public_ip to the address browsers | can reach — it is advertised in the ICE host candidate. */ 'sfu' => [ 'socket' => env('WEBRTC_SFU_SOCKET', storage_path('app/webrtc/sfu.sock')), 'udp_ip' => env('WEBRTC_SFU_UDP_IP', '0.0.0.0'), 'udp_port' => (int) env('WEBRTC_SFU_UDP_PORT', 0), 'public_ip' => env('WEBRTC_SFU_PUBLIC_IP'), 'auto_spawn' => (bool) env('WEBRTC_SFU_AUTO_SPAWN', true), 'auto_install' => (bool) env('WEBRTC_SFU_AUTO_INSTALL', true), 'spawn_timeout' => (float) env('WEBRTC_SFU_SPAWN_TIMEOUT', 10), 'log' => env('WEBRTC_SFU_LOG', storage_path('logs/webrtc-sfu.log')), // Binary resolution: explicit path > local rust/target build > install. 'binary' => env('WEBRTC_SFU_BINARY'), 'install_path' => env('WEBRTC_SFU_INSTALL_PATH', storage_path('app/webrtc/bin')), 'version' => env('WEBRTC_SFU_VERSION'), // default: the version pinned by this package 'download_url' => env( 'WEBRTC_SFU_DOWNLOAD_URL', 'https://github.com/blax-software/laravel-webrtc/releases/download/{version}/blax-webrtc-sfu-{platform}', ), // sha256 verification is REQUIRED when set; null opts out explicitly. 'checksum_url' => env( 'WEBRTC_SFU_CHECKSUM_URL', 'https://github.com/blax-software/laravel-webrtc/releases/download/{version}/blax-webrtc-sfu-{platform}.sha256', ), ], /* |-------------------------------------------------------------------------- | Recording (store the full call audio) |-------------------------------------------------------------------------- | When enabled, the browser streams its mic to the server as binary WS frames | and RelaySignalingHandler appends them per participant via a RecordingStore | (FileRecordingStore by default: //.). See README. */ 'recording' => [ 'enabled' => (bool) env('WEBRTC_RECORDING', false), 'path' => env('WEBRTC_RECORDING_PATH', storage_path('app/webrtc')), 'format' => env('WEBRTC_RECORDING_FORMAT', 'webm'), ], /* |-------------------------------------------------------------------------- | Backend-defined rooms (the room registry) |-------------------------------------------------------------------------- | The rooms clients are allowed to join. A join to anything NOT listed here is | rejected — clients can't invent rooms. Each room configures its transport: | 'p2p' — browsers mesh, the server only relays signaling (no server media) | 'sfu' — the server terminates media (the Rust SFU; enables recording/scale) | | Shapes (all equivalent granularity): | 'just-talk' => 'p2p', | 'lobby' => ['mode' => 'sfu', 'limit' => 50, 'type' => 'presence'], | 'private-desk' => ['mode' => 'p2p'], // membership type inferred from prefix | | For DYNAMIC rooms (created/edited at runtime), point `room_registry` at your | own Contracts\RoomRegistry (e.g. an Eloquent-backed one); the static `rooms` | map below is the fallback used when no registry class is bound. */ 'room_registry' => env('WEBRTC_ROOM_REGISTRY'), 'rooms' => [], /* |-------------------------------------------------------------------------- | Room authorization + call-event log |-------------------------------------------------------------------------- | `authorizer` gates private-* / presence-* joins (bind your own that checks a | token / the authenticated user; the default DENIES them). `events` receives | call-lifecycle events so you can log the call (bind your own to persist). */ 'authorizer' => env('WEBRTC_AUTHORIZER', DefaultRoomAuthorizer::class), 'events' => env('WEBRTC_EVENTS', NullCallEventListener::class), /* |-------------------------------------------------------------------------- | Room membership store |-------------------------------------------------------------------------- | Where room membership + presence lives. The default ArrayRoomStore keeps it | in process memory (right for the long-lived bundled server). A fork-per- | message host (a classic Laravel WebSocket handling each frame in a fresh | worker) binds a Cache/Redis-backed RoomStore so state survives across | workers — that is how a host runs this relay on its existing WS. */ 'room_store' => env('WEBRTC_ROOM_STORE', ArrayRoomStore::class), /* |-------------------------------------------------------------------------- | ICE servers (STUN/TURN) advertised to browsers |-------------------------------------------------------------------------- */ 'ice_servers' => [ // ['urls' => 'stun:stun.l.google.com:19302'], // ['urls' => 'turn:turn.example.com:3478', 'username' => '...', 'credential' => '...'], ], /* |-------------------------------------------------------------------------- | External realtime bridge (provider hidden from the browser) |-------------------------------------------------------------------------- | OpenAiRealtimeBridge (or the media engine) dials these; the browser only ever | talks to us, so the AI provider is invisible and the model is swappable | server-side. `instructions` is the persona (the host injects its own — e.g. | an ATC prompt) and `voice` the spoken voice. */ 'bridge' => [ 'openai' => [ 'endpoint' => env('WEBRTC_OPENAI_ENDPOINT', 'wss://api.openai.com/v1/realtime'), 'model' => env('WEBRTC_OPENAI_MODEL', 'gpt-realtime'), 'api_key' => env('OPENAI_API_KEY'), 'voice' => env('WEBRTC_OPENAI_VOICE', 'alloy'), 'instructions' => null, ], ], ];