mergeConfigFrom(__DIR__.'/../config/webrtc.php', 'webrtc'); // Resolve the configured media engine wherever a MediaEngine is type-hinted. $this->app->bind(MediaEngine::class, function ($app) { $class = (string) config('webrtc.media_engine', NullMediaEngine::class); return $app->make($class); }); // Room authorization (private/presence) + the call-event log — host-overridable. $this->app->bind(RoomAuthorizer::class, function ($app) { return $app->make((string) config('webrtc.authorizer', DefaultRoomAuthorizer::class)); }); $this->app->bind(CallEventListener::class, function ($app) { return $app->make((string) config('webrtc.events', NullCallEventListener::class)); }); // Room membership store — swappable so a fork-per-message host (learn-atc's // existing WS) can back it with Cache/Redis instead of process memory. The // RoomManager is per-resolve so a fresh worker reads current state. $this->app->bind(RoomStore::class, function ($app) { return $app->make((string) config('webrtc.room_store', ArrayRoomStore::class)); }); $this->app->bind(RoomManager::class, function ($app) { return new RoomManager($app->make(RoomStore::class)); }); // Where a call's audio is stored (per participant). Host may override the class. $this->app->bind(RecordingStore::class, function () { $recording = (array) config('webrtc.recording', []); return new FileRecordingStore( (string) ($recording['path'] ?? 'webrtc'), (string) ($recording['format'] ?? 'webm'), ); }); // Realtime AI bridge (provider hidden from the browser, model swappable server-side). $this->app->bind(OpenAiRealtimeBridge::class, function () { $endpoint = (string) config('webrtc.bridge.openai.endpoint', 'wss://api.openai.com/v1/realtime'); return new OpenAiRealtimeBridge(endpoint: $endpoint); }); } public function boot(): void { if ($this->app->runningInConsole()) { $this->publishes([ __DIR__.'/../config/webrtc.php' => $this->app->configPath('webrtc.php'), ], 'webrtc-config'); // The bundled ReactPHP relay server needs blax-software/laravel-ws + // reactphp-kernel (dev-only deps). A host that only consumes the domain // layer (rooms/recording/realtime bridge) on its OWN transport won't have // them — checking the dependency classes first avoids autoloading the // command (and its imports) when they're absent. if (class_exists(WebSocketServer::class) && class_exists(Kernel::class)) { $this->commands([ StartWebRtcServer::class, ]); } } } }