diff --git a/.junie/plans/replace-udp-with-rustrtc-2.md b/.junie/plans/replace-udp-with-rustrtc-2.md index 2411acf..180c459 100644 --- a/.junie/plans/replace-udp-with-rustrtc-2.md +++ b/.junie/plans/replace-udp-with-rustrtc-2.md @@ -5,66 +5,109 @@ sessionId: session-260912-132834-fuse # Requirements ### Overview & Goals -Replace the current placeholder raw-UDP voice relay (`src/udp/*`) with a real WebRTC media stack built on the `rustrtc` crate (already declared in `Cargo.toml`). The server becomes a centralized **SFU** (Selective Forwarding Unit): each connected client opens **one `PeerConnection`** with the server, the server decrypts/receives that client's audio (and later video) track and forwards it to every other member of the same voice `Channel`. + +Replace the current placeholder raw-UDP voice relay (`src/udp/*`) with a real WebRTC media stack built on the `rustrtc` +crate (already declared in `Cargo.toml`). The server becomes a centralized **SFU** (Selective Forwarding Unit): each +connected client opens **one `PeerConnection`** with the server, the server decrypts/receives that client's audio (and +later video) track and forwards it to every other member of the same voice `Channel`. ### Scope + **In Scope** + - Remove `src/udp/server.rs`, `src/udp/router.rs` raw-socket logic (metrics module is kept/adapted). -- Add a `voice` module hosting: `PeerConnection` lifecycle management via `rustrtc`, SDP offer/answer exchange, ICE candidate exchange, and SFU-style track forwarding scoped by `channel_id`. -- Reuse the existing WebSocket gateway (`GatewayManager` / `RealtimeRouter` / `on_message` in `src/routes/gateway/mod.rs`) as the signaling transport — add a `Voice` namespace to the existing JSON event envelope (`GatewayEvent { namespace, action, content }`) instead of introducing a new endpoint. -- Reuse `channel::ChannelType::Voice`, `channel_user`, and the `JOIN_VOICE` / `SPEAK` permission bits already defined in `src/permissions.rs` to authorize who may join/publish in a voice channel. -- Update `config.toml` / `NetworkConfig` so the single `udp_port` continues to be the one UDP port used, bound through `rustrtc`'s `ice_udp_mux` (single-port ICE) instead of a raw `UdpSocket`. -- Update `AppMetrics`/`core::mod::App::run` wiring so the new voice service starts/stops alongside the HTTP server, replacing `UdpServer`. +- Add a `voice` module hosting: `PeerConnection` lifecycle management via `rustrtc`, SDP offer/answer exchange, ICE + candidate exchange, and SFU-style track forwarding scoped by `channel_id`. +- Reuse the existing WebSocket gateway (`GatewayManager` / `RealtimeRouter` / `ws_on_message` in + `src/routes/gateway/mod.rs`) as the signaling transport — add a `Voice` namespace to the existing JSON event envelope + (`GatewayEvent { namespace, action, content }`) instead of introducing a new endpoint. +- Reuse `channel::ChannelType::Voice`, `channel_user`, and the `JOIN_VOICE` / `SPEAK` permission bits already defined in + `src/permissions.rs` to authorize who may join/publish in a voice channel. +- Update `config.toml` / `NetworkConfig` so the single `udp_port` continues to be the one UDP port used, bound through + `rustrtc`'s `ice_udp_mux` (single-port ICE) instead of a raw `UdpSocket`. +- Update `AppMetrics`/`core::mod::App::run` wiring so the new voice service starts/stops alongside the HTTP server, + replacing `UdpServer`. **Out of Scope** + - TURN/STUN server configuration (kept minimal for LAN/local network scenarios for now). - Video/screen-share specific handling (structure will allow it later, but only audio forwarding is implemented now). - Client-side (frontend) implementation details beyond the signaling contract needed by the server. ### User Stories -- As a user with `JOIN_VOICE` permission on a voice channel, I want to connect and have my microphone audio heard by other members of that same channel. -- As a user already in a voice channel, I want to hear every other member's audio forwarded by the server without opening a direct connection to each peer. -- As a server operator, I want the previous ad-hoc UDP relay removed and replaced by a maintained WebRTC stack (rustrtc) so voice traffic is encrypted (SRTP) and NAT-traversal (ICE) works out of the box. + +- As a user with `JOIN_VOICE` permission on a voice channel, I want to connect and have my microphone audio heard by + other members of that same channel. +- As a user already in a voice channel, I want to hear every other member's audio forwarded by the server without + opening a direct connection to each peer. +- As a server operator, I want the previous ad-hoc UDP relay removed and replaced by a maintained WebRTC stack (rustrtc) + so voice traffic is encrypted (SRTP) and NAT-traversal (ICE) works out of the box. ### Functional Requirements -- A client sends an SDP offer through the gateway (`namespace: "Voice"`), scoped to a `channel_id`; the server validates channel membership/permissions, creates a `rustrtc::PeerConnection`, and returns an SDP answer through the same gateway connection. -- ICE candidates generated by either side are exchanged as additional `Voice` gateway events. -- Once connected, the server subscribes the client's inbound audio track to every other `PeerConnection` currently joined to the same voice channel (SFU fan-out), and adds new joiners' tracks to previously-connected peers as they arrive. -- When a user disconnects (gateway socket closes) or leaves the voice channel, their `PeerConnection` is closed and their track is removed from all other peers' forwarding sets. -- Existing `UdpMetrics`-style counters (packets/bytes in/out, errors) are preserved in spirit, adapted for voice metrics and reported by the periodic `reporter`. +- A client sends an SDP offer through the gateway (`namespace: "Voice"`), scoped to a `channel_id`; the server validates + channel membership/permissions, creates a `rustrtc::PeerConnection`, and returns an SDP answer through the same + gateway connection. +- ICE candidates generated by either side are exchanged as additional `Voice` gateway events. +- Once connected, the server subscribes the client's inbound audio track to every other `PeerConnection` currently + joined to the same voice channel (SFU fan-out), and adds new joiners' tracks to previously-connected peers as they + arrive. +- When a user disconnects (gateway socket closes) or leaves the voice channel, their `PeerConnection` is closed and + their track is removed from all other peers' forwarding sets. +- Existing `UdpMetrics`-style counters (packets/bytes in/out, errors) are preserved in spirit, adapted for voice metrics + and reported by the periodic `reporter`. # Technical Design ### Current Implementation -- `src/udp/server.rs`: binds a raw `tokio::net::UdpSocket` on `NetworkConfig.udp_port`, loops on `recv_from`, and blindly forwards datagrams to registered peers under a hard-coded `"default"` channel without protocol isolation or encryption. + +- `src/udp/server.rs`: binds a raw `tokio::net::UdpSocket` on `NetworkConfig.udp_port`, loops on `recv_from`, and + blindly forwards datagrams to registered peers under a hard-coded `"default"` channel without protocol isolation or + encryption. - `src/udp/router.rs`: `RoutingTable` maps `ChannelId -> Vec`; dead placeholder API. -- `src/udp/metrics.rs`: atomic counters (`packets_received`, `bytes_sent`, etc.) + `spawn_reporter` logging every interval, plugged into `AppMetrics.udp` (`src/metrics/mod.rs`). -- `src/core/mod.rs`: `App::build`/`App::run` constructs `UdpServer::new(&config.network, udp_metrics)` and `tokio::spawn(udp_server.run())`, joined into shutdown-broadcast pattern. -- `src/routes/gateway/mod.rs`: `GatewayManager` tracks client WebSocket connections; `GatewayClient::on_message` handles incoming messages and acts as the extension point for voice signaling. -- `src/services/realtime_registry.rs`: maintains `channel_id -> HashSet` membership computed from `computed_permission` (READ_CHANNEL). -- `src/permissions.rs`: `JOIN_VOICE`, `SPEAK`, `STREAM`, `MOVE_OTHERS`, `DISCONNECT_OTHERS`, `MANAGE_VOICE_CHANNEL` bits already defined. +- `src/udp/metrics.rs`: atomic counters (`packets_received`, `bytes_sent`, etc.) + `spawn_reporter` logging every + interval, plugged into `AppMetrics.udp` (`src/metrics/mod.rs`). +- `src/core/mod.rs`: `App::build`/`App::run` constructs `UdpServer::new(&config.network, udp_metrics)` and + `tokio::spawn(udp_server.run())`, joined into shutdown-broadcast pattern. +- `src/routes/gateway/mod.rs`: `GatewayManager` tracks client WebSocket connections; `GatewayClient::on_message` handles + incoming messages and acts as the extension point for voice signaling. +- `src/services/realtime_registry.rs`: maintains `channel_id -> HashSet` membership computed from + `computed_permission` (READ_CHANNEL). +- `src/permissions.rs`: `JOIN_VOICE`, `SPEAK`, `STREAM`, `MOVE_OTHERS`, `DISCONNECT_OTHERS`, `MANAGE_VOICE_CHANNEL` bits + already defined. - `src/models/channel.rs`: `ChannelType::Voice` variant already exists. ### Key Decisions -1. **Topology: centralized SFU** — one `rustrtc::PeerConnection` per connected client per voice channel; the server forwards each publisher's RTP track to every other subscriber in that channel. No direct peer-to-peer mesh. -2. **Signaling transport: existing WebSocket gateway** — SDP offer/answer and ICE candidates are carried as `Voice` namespace events inside the current `GatewayEvent` envelope, handled in `GatewayClient::on_message` and dispatched to `VoiceService`. -3. **Single UDP port reuse** — `rustrtc`'s `RtcConfiguration` is set up with `ice_udp_mux = true` and bound to `NetworkConfig.udp_port`, preserving existing firewall/network port configurations. + +1. **Topology: centralized SFU** — one `rustrtc::PeerConnection` per connected client per voice channel; the server + forwards each publisher's RTP track to every other subscriber in that channel. No direct peer-to-peer mesh. +2. **Signaling transport: existing WebSocket gateway** — SDP offer/answer and ICE candidates are carried as `Voice` + namespace events inside the current `GatewayEvent` envelope, handled in `GatewayClient::on_message` and dispatched to + `VoiceService`. +3. **Single UDP port reuse** — `rustrtc`'s `RtcConfiguration` is set up with `ice_udp_mux = true` and bound to + `NetworkConfig.udp_port`, preserving existing firewall/network port configurations. 4. **No TURN/STUN for now** — `RtcConfiguration.ice_servers` left default/empty for local and direct network setups. -5. **Voice presence tracked separately from `RealtimeRegistry`** — an in-memory registry (`VoiceRoom` per `channel_id`) tracks active WebRTC connections independently from general WebSocket channel presence. +5. **Voice presence tracked separately from `RealtimeRegistry`** — an in-memory registry (`VoiceRoom` per `channel_id`) + tracks active WebRTC connections independently from general WebSocket channel presence. ### Proposed Changes + - Replace `src/udp` module with `src/voice`: - - `voice/mod.rs`: public API, exports `VoiceService`. - - `voice/service.rs`: `VoiceService` owns `rustrtc::RtcConfiguration`, a map `channel_id -> VoiceRoom`, and handler methods for offer, ICE candidates, and channel leaves. - - `voice/room.rs`: `VoiceRoom` holds `HashMap>` for a channel and orchestrates track fan-out across peers. - - `voice/metrics.rs`: tracks voice counters (packets/bytes/errors), exposed through `AppMetrics.voice` and the existing reporter cadence. + - `voice/mod.rs`: public API, exports `VoiceService`. + - `voice/service.rs`: `VoiceService` owns `rustrtc::RtcConfiguration`, a map `channel_id -> VoiceRoom`, and handler + methods for offer, ICE candidates, and channel leaves. + - `voice/room.rs`: `VoiceRoom` holds `HashMap>` for a channel and orchestrates track + fan-out across peers. + - `voice/metrics.rs`: tracks voice counters (packets/bytes/errors), exposed through `AppMetrics.voice` and the + existing reporter cadence. - Extend `GatewayEvent` handling: add `"Voice"` namespace with actions `offer`, `answer`, `ice-candidate`, `leave`. -- Permission check on `offer`: verify `ChannelPermission::JOIN_VOICE` and `SPEAK` before initializing a `PeerConnection`. +- Permission check on `offer`: verify `ChannelPermission::JOIN_VOICE` and `SPEAK` before initializing a + `PeerConnection`. - Update `src/core/mod.rs`: replace `UdpServer` initialization with `VoiceService` held inside `AppState`. - Update `src/config.rs`: keep `NetworkConfig.udp_port` feeding `rustrtc`'s `ice_udp_mux_port`. ### Data Models / Contracts + ```rust // Gateway JSON contract additions (namespace = "Voice") { "namespace": "Voice", "action": "offer", "content": { "channel_id": Uuid, "sdp": String } } @@ -87,6 +130,7 @@ impl VoiceService { ``` ### Architecture Diagram + ```mermaid graph TD ClientA[Client A] -- WebSocket: Voice offer/answer/ICE --> Gateway[GatewayManager / on_message] @@ -102,39 +146,66 @@ graph TD ``` ### Risks -- `rustrtc` API details: verify track forwarding primitives against `rustrtc 0.3.133` during implementation and encapsulate track re-subscription in `voice/room.rs`. -- Permission enforcement: ensure permission checks take place before creating peer connections to prevent unauthorized audio relay. -- Signaling message volume: ensure gateway handling remains non-blocking for high frequency ICE candidates. +- `rustrtc` API details: verify track forwarding primitives against `rustrtc 0.3.133` during implementation and + encapsulate track re-subscription in `voice/room.rs`. +- Permission enforcement: ensure permission checks take place before creating peer connections to prevent unauthorized + audio relay. +- Signaling message volume: ensure gateway handling remains non-blocking for high frequency ICE candidates. # Delivery Steps -### Step 1: Scaffold the voice module and RtcConfiguration from NetworkConfig -A new `src/voice` module exists with a `VoiceService` capable of building a `rustrtc::RtcConfiguration` from the app's network config, replacing the old `src/udp` module's role in `core/mod.rs`. +### Step 1: Scaffold the voice module and RtcConfiguration from NetworkConfig + +A new `src/voice` module exists with a `VoiceService` capable of building a `rustrtc::RtcConfiguration` from the app's +network config, replacing the old `src/udp` module's role in `core/mod.rs`. + - Create `src/voice/mod.rs`, `src/voice/service.rs`, `src/voice/room.rs`, and `src/voice/metrics.rs`. -- Port `UdpMetrics`-style counters into `voice/metrics.rs` (packets/bytes in/out, errors), keeping the `Metrics`/`MetricsSnapshot` trait implementations used by `crate::metrics::reporter`. -- Build `RtcConfiguration`/`RtcConfigurationBuilder` in `VoiceService::new(&NetworkConfig, metrics)` using `ice_udp_mux = true` and `ice_udp_mux_port = network.udp_port`, with no external ICE servers configured. +- Port `UdpMetrics`-style counters into `voice/metrics.rs` (packets/bytes in/out, errors), keeping the `Metrics`/ + `MetricsSnapshot` trait implementations used by `crate::metrics::reporter`. +- Build `RtcConfiguration`/`RtcConfigurationBuilder` in `VoiceService::new(&NetworkConfig, metrics)` using + `ice_udp_mux = true` and `ice_udp_mux_port = network.udp_port`, with no external ICE servers configured. - Remove `src/udp/server.rs` and `src/udp/router.rs`; delete unused `RoutingTable`. -- Update `src/core/mod.rs` (`App::build`/`App::run`) to construct `VoiceService` instead of `UdpServer`, store it on `AppState`, and drop unneeded `udp_shutdown_tx`/`udp_handle` tasks. +- Update `src/core/mod.rs` (`App::build`/`App::run`) to construct `VoiceService` instead of `UdpServer`, store it on + `AppState`, and drop unneeded `udp_shutdown_tx`/`udp_handle` tasks. - Update `src/metrics/mod.rs` (`AppMetrics`) to reference the new voice metrics type. -### Step 2: Implement per-channel PeerConnection lifecycle in VoiceRoom -`VoiceService`/`VoiceRoom` can accept an SDP offer for a given channel, create a `rustrtc::PeerConnection`, and return an SDP answer, tracking connections per `channel_id`. +### Step 2: Implement per-channel PeerConnection lifecycle in VoiceRoom + +`VoiceService`/`VoiceRoom` can accept an SDP offer for a given channel, create a `rustrtc::PeerConnection`, and return +an SDP answer, tracking connections per `channel_id`. + - Implement `VoiceRoom` (`channel_id -> HashMap>`). -- Implement `VoiceService::handle_offer(user_id, channel_id, sdp)`: looks up or creates the room, creates a `PeerConnection` via `rustrtc`, invokes `set_remote_description`/`create_answer`/`set_local_description`, and returns the answer SDP. -- Implement `VoiceService::handle_ice_candidate` and `VoiceService::leave`, closing and removing the `PeerConnection` from its room. -- Enforce `ChannelPermission::JOIN_VOICE` (and `SPEAK` for publishing) before creating a `PeerConnection`, reusing the `computed_permission` lookup pattern from `src/services/realtime_registry.rs`. +- Implement `VoiceService::handle_offer(user_id, channel_id, sdp)`: looks up or creates the room, creates a + `PeerConnection` via `rustrtc`, invokes `set_remote_description`/`create_answer`/`set_local_description`, and returns + the answer SDP. +- Implement `VoiceService::handle_ice_candidate` and `VoiceService::leave`, closing and removing the `PeerConnection` + from its room. +- Enforce `ChannelPermission::JOIN_VOICE` (and `SPEAK` for publishing) before creating a `PeerConnection`, reusing the + `computed_permission` lookup pattern from `src/services/realtime_registry.rs`. + +### Step 3: Wire SFU track forwarding between peers in the same channel + +Audio published by one connected client in a voice channel is forwarded by the server to every other client connected to +the same channel. -### Step 3: Wire SFU track forwarding between peers in the same channel -Audio published by one connected client in a voice channel is forwarded by the server to every other client connected to the same channel. - On `PeerConnection::on_track` for a given user's connection, register the inbound track on the owning `VoiceRoom`. -- For every other `PeerConnection` already in that `VoiceRoom`, add and forward the new track (SFU fan-out) using `rustrtc`'s track/`MediaCapabilities` APIs. +- For every other `PeerConnection` already in that `VoiceRoom`, add and forward the new track (SFU fan-out) using + `rustrtc`'s track/`MediaCapabilities` APIs. - When a new peer joins an existing room, subscribe it to all tracks already being forwarded by other room members. -- On `leave` or disconnect, remove the peer's published track from all other peers' forwarding sets and close its `PeerConnection`. +- On `leave` or disconnect, remove the peer's published track from all other peers' forwarding sets and close its + `PeerConnection`. -### Step 4: Expose Voice signaling through the existing WebSocket gateway -Clients can perform the full offer/answer/ICE handshake over the existing gateway WebSocket connection using a new `Voice` namespace, with no new HTTP/WS endpoint introduced. -- Extend `GatewayClient::on_message` in `src/routes/gateway/mod.rs` to parse `GatewayEvent{namespace: "Voice", action, content}` messages (`offer`, `answer`, `ice-candidate`, `leave`). -- Dispatch parsed messages to `VoiceService` (accessed via `AppState`) and send the resulting answer/ICE-candidate events back through the client's existing `mpsc::UnboundedSender`. -- On gateway disconnect (`GatewayClient::on_disconnect`), call `VoiceService::leave` for any channel the user was actively connected to in voice. -- Update `config.toml`'s `DEFAULT_CONFIG_TOML` comment for `udp_port` to reflect its new role as the rustrtc ICE/media mux port. \ No newline at end of file +### Step 4: Expose Voice signaling through the existing WebSocket gateway + +Clients can perform the full offer/answer/ICE handshake over the existing gateway WebSocket connection using a new +`Voice` namespace, with no new HTTP/WS endpoint introduced. + +- Extend `GatewayClient::on_message` in `src/routes/gateway/mod.rs` to parse + `GatewayEvent{namespace: "Voice", action, content}` messages (`offer`, `answer`, `ice-candidate`, `leave`). +- Dispatch parsed messages to `VoiceService` (accessed via `AppState`) and send the resulting answer/ICE-candidate + events back through the client's existing `mpsc::UnboundedSender`. +- On gateway disconnect (`GatewayClient::on_disconnect`), call `VoiceService::leave` for any channel the user was + actively connected to in voice. +- Update `config.toml`'s `DEFAULT_CONFIG_TOML` comment for `udp_port` to reflect its new role as the rustrtc ICE/media + mux port. \ No newline at end of file diff --git a/.junie/plans/replace-udp-with-rustrtc.md b/.junie/plans/replace-udp-with-rustrtc.md index 916b11e..e83bd8a 100644 --- a/.junie/plans/replace-udp-with-rustrtc.md +++ b/.junie/plans/replace-udp-with-rustrtc.md @@ -5,66 +5,132 @@ sessionId: session-260912-101455-1pxv # Requirements ### Overview & Goals -Replace the current placeholder raw-UDP voice relay (`src/udp/*`) with a real WebRTC media stack built on the `rustrtc` crate (already declared in `Cargo.toml`). The server becomes a centralized **SFU** (Selective Forwarding Unit): each connected client opens **one `PeerConnection`** with the server, the server decrypts/receives that client's audio (and later video) track and re-encodes/forwards it to every other member of the same voice `Channel`. + +Replace the current placeholder raw-UDP voice relay (`src/udp/*`) with a real WebRTC media stack built on the `rustrtc` +crate (already declared in `Cargo.toml`). The server becomes a centralized **SFU** (Selective Forwarding Unit): each +connected client opens **one `PeerConnection`** with the server, the server decrypts/receives that client's audio (and +later video) track and re-encodes/forwards it to every other member of the same voice `Channel`. ### Scope + **In Scope** + - Remove `src/udp/server.rs`, `src/udp/router.rs` raw-socket logic (metrics module is kept/adapted). -- Add a `voice` module hosting: `PeerConnection` lifecycle management via `rustrtc`, SDP offer/answer exchange, ICE candidate exchange, and SFU-style track forwarding scoped by `channel_id`. -- Reuse the existing WebSocket gateway (`GatewayManager` / `RealtimeRouter` / `on_message` in `src/routes/gateway/mod.rs`) as the signaling transport — add a `Voice` namespace to the existing JSON event envelope (`GatewayEvent { namespace, action, content }`) instead of introducing a new endpoint, per explicit request to avoid breaking the current structure. -- Reuse `channel::ChannelType::Voice`, `channel_user`, and the `JOIN_VOICE` / `SPEAK` permission bits already defined in `src/permissions.rs` to authorize who may join/publish in a voice channel. -- Update `config.toml` / `NetworkConfig` so the single `udp_port` continues to be the one UDP port used, now bound through `rustrtc`'s `ice_udp_mux` (single-port ICE) instead of a raw `UdpSocket`. -- Update `AppMetrics`/`core::mod::App::run` wiring so the new voice service starts/stops alongside the HTTP server, replacing `UdpServer`. +- Add a `voice` module hosting: `PeerConnection` lifecycle management via `rustrtc`, SDP offer/answer exchange, ICE + candidate exchange, and SFU-style track forwarding scoped by `channel_id`. +- Reuse the existing WebSocket gateway (`GatewayManager` / `RealtimeRouter` / `ws_on_message` in + `src/routes/gateway/mod.rs`) as the signaling transport — add a `Voice` namespace to the existing JSON event envelope + (`GatewayEvent { namespace, action, content }`) instead of introducing a new endpoint, per explicit request to avoid + breaking the current structure. +- Reuse `channel::ChannelType::Voice`, `channel_user`, and the `JOIN_VOICE` / `SPEAK` permission bits already defined in + `src/permissions.rs` to authorize who may join/publish in a voice channel. +- Update `config.toml` / `NetworkConfig` so the single `udp_port` continues to be the one UDP port used, now bound + through `rustrtc`'s `ice_udp_mux` (single-port ICE) instead of a raw `UdpSocket`. +- Update `AppMetrics`/`core::mod::App::run` wiring so the new voice service starts/stops alongside the HTTP server, + replacing `UdpServer`. **Out of Scope** + - TURN/STUN server configuration (kept minimal per user's answer — LAN/local network scenario for now). - Video/screen-share specific handling (structure will allow it later, but only audio forwarding is implemented now). - Client-side (frontend) implementation details beyond the signaling contract needed by the server. ### User Stories -- As a user with `JOIN_VOICE` permission on a voice channel, I want to connect and have my microphone audio heard by other members of that same channel. -- As a user already in a voice channel, I want to hear every other member's audio mixed/forwarded by the server without opening a direct connection to each peer. -- As a server operator, I want the previous ad-hoc UDP relay removed and replaced by a maintained WebRTC stack (rustrtc) so voice traffic is encrypted (SRTP) and NAT-traversal (ICE) works out of the box. + +- As a user with `JOIN_VOICE` permission on a voice channel, I want to connect and have my microphone audio heard by + other members of that same channel. +- As a user already in a voice channel, I want to hear every other member's audio mixed/forwarded by the server without + opening a direct connection to each peer. +- As a server operator, I want the previous ad-hoc UDP relay removed and replaced by a maintained WebRTC stack (rustrtc) + so voice traffic is encrypted (SRTP) and NAT-traversal (ICE) works out of the box. ### Functional Requirements -- A client sends an SDP offer through the gateway (`namespace: "Voice"`), scoped to a `channel_id`; the server validates channel membership/permissions, creates a `rustrtc::PeerConnection`, and returns an SDP answer through the same gateway connection. + +- A client sends an SDP offer through the gateway (`namespace: "Voice"`), scoped to a `channel_id`; the server validates + channel membership/permissions, creates a `rustrtc::PeerConnection`, and returns an SDP answer through the same + gateway connection. - ICE candidates generated by either side are exchanged as additional `Voice` gateway events. -- Once connected, the server subscribes the client's inbound audio track to every other `PeerConnection` currently joined to the same voice channel (SFU fan-out), and adds new joiners' tracks to previously-connected peers as they arrive. -- When a user disconnects (gateway socket closes) or leaves the voice channel, their `PeerConnection` is closed and their track is removed from all other peers' forwarding sets. -- Existing `UdpMetrics`-style counters (packets/bytes in/out, errors) are preserved in spirit, adapted to whatever counters `rustrtc` exposes (or wrapped manually) for the periodic `reporter`. +- Once connected, the server subscribes the client's inbound audio track to every other `PeerConnection` currently + joined to the same voice channel (SFU fan-out), and adds new joiners' tracks to previously-connected peers as they + arrive. +- When a user disconnects (gateway socket closes) or leaves the voice channel, their `PeerConnection` is closed and + their track is removed from all other peers' forwarding sets. +- Existing `UdpMetrics`-style counters (packets/bytes in/out, errors) are preserved in spirit, adapted to whatever + counters `rustrtc` exposes (or wrapped manually) for the periodic `reporter`. # Technical Design ### Current Implementation -- `src/udp/server.rs`: binds a raw `tokio::net::UdpSocket` on `NetworkConfig.udp_port`, loops on `recv_from`, and blindly forwards every datagram to all peers registered in a `RoutingTable` under a **hard-coded** `"default"` channel — there is no real protocol, no per-voice-channel isolation, no encryption. -- `src/udp/router.rs`: `RoutingTable` maps `ChannelId -> Vec`; `join`/`leave`/`routing_table_mut()` exist but are **never called** anywhere in the codebase — dead placeholder API. -- `src/udp/metrics.rs`: atomic counters (`packets_received`, `bytes_sent`, …) + `spawn_reporter` logging every interval. Plugged into `AppMetrics.udp` (`src/metrics/mod.rs`). -- `src/core/mod.rs`: `App::build`/`App::run` construct `UdpServer::new(&config.network, udp_metrics)` and `tokio::spawn(udp_server.run())`, joined into the same `tokio::select!`/shutdown-broadcast pattern as `HttpServer`. -- `src/routes/gateway/mod.rs`: `GatewayManager` keeps `ConnectionKey{user_id, connection_id} -> GatewayClient{sender: mpsc::UnboundedSender}`; events are pushed as JSON `GatewayEvent{namespace, action, content}`. `GatewayClient::on_message` currently just logs incoming text — this is the extension point for voice signaling. -- `src/services/realtime_registry.rs`: already maintains `channel_id -> HashSet` membership computed from `computed_permission` (READ_CHANNEL) — reusable to know who is allowed in a channel, but voice-specific "currently connected to voice" state does not exist yet and must be tracked separately (WebSocket presence in a channel != actively broadcasting audio). -- `src/permissions.rs`: `JOIN_VOICE`, `SPEAK`, `STREAM`, `MOVE_OTHERS`, `DISCONNECT_OTHERS`, `MANAGE_VOICE_CHANNEL` bits already defined but unused by any voice logic today. + +- `src/udp/server.rs`: binds a raw `tokio::net::UdpSocket` on `NetworkConfig.udp_port`, loops on `recv_from`, and + blindly forwards every datagram to all peers registered in a `RoutingTable` under a **hard-coded** `"default"` + channel — there is no real protocol, no per-voice-channel isolation, no encryption. +- `src/udp/router.rs`: `RoutingTable` maps `ChannelId -> Vec`; `join`/`leave`/`routing_table_mut()` exist + but are **never called** anywhere in the codebase — dead placeholder API. +- `src/udp/metrics.rs`: atomic counters (`packets_received`, `bytes_sent`, …) + `spawn_reporter` logging every interval. + Plugged into `AppMetrics.udp` (`src/metrics/mod.rs`). +- `src/core/mod.rs`: `App::build`/`App::run` construct `UdpServer::new(&config.network, udp_metrics)` and + `tokio::spawn(udp_server.run())`, joined into the same `tokio::select!`/shutdown-broadcast pattern as `HttpServer`. +- `src/routes/gateway/mod.rs`: `GatewayManager` keeps + `ConnectionKey{user_id, connection_id} -> GatewayClient{sender: mpsc::UnboundedSender}`; events are pushed as + JSON `GatewayEvent{namespace, action, content}`. `GatewayClient::on_message` currently just logs incoming text — this + is the extension point for voice signaling. +- `src/services/realtime_registry.rs`: already maintains `channel_id -> HashSet` membership computed from + `computed_permission` (READ_CHANNEL) — reusable to know who is allowed in a channel, but voice-specific "currently + connected to voice" state does not exist yet and must be tracked separately (WebSocket presence in a channel != + actively broadcasting audio). +- `src/permissions.rs`: `JOIN_VOICE`, `SPEAK`, `STREAM`, `MOVE_OTHERS`, `DISCONNECT_OTHERS`, `MANAGE_VOICE_CHANNEL` bits + already defined but unused by any voice logic today. - `src/models/channel.rs`: `ChannelType::Voice` variant already exists. ### Key Decisions -1. **Topology: centralized SFU** (confirmed by user) — one `rustrtc::PeerConnection` per connected client per voice channel; the server forwards each publisher's decoded RTP track to every other subscriber in that channel. No client-to-client PeerConnections. -2. **Signaling transport: existing WebSocket gateway** (confirmed by user, to avoid restructuring) — SDP offer/answer and ICE candidates are carried as new `Voice` namespace events inside the current `GatewayEvent` envelope, handled in `GatewayClient::on_message` (currently a stub) and dispatched to a new `VoiceService`. -3. **Single UDP port reuse** — `rustrtc`'s `RtcConfiguration` will be set up with `ice_udp_mux = true` and bound to the existing `NetworkConfig.udp_port`, so the media/ICE traffic keeps using the same single port previously owned by the raw `UdpServer`, minimizing config/infra changes (firewall rules, `config.toml` stay compatible). -4. **No TURN/STUN for now** (confirmed by user) — `RtcConfiguration.ice_servers` left empty/default; can be added later via `config.toml` without further architecture changes. -5. **Voice presence tracked separately from `RealtimeRegistry`** — a new lightweight in-memory registry (`VoiceRoom` per `channel_id`) tracks which `PeerConnection`s are actively publishing/subscribing in a voice channel, since being subscribed to gateway events (`RealtimeRegistry`) is not the same as being connected to voice media. + +1. **Topology: centralized SFU** (confirmed by user) — one `rustrtc::PeerConnection` per connected client per voice + channel; the server forwards each publisher's decoded RTP track to every other subscriber in that channel. No + client-to-client PeerConnections. +2. **Signaling transport: existing WebSocket gateway** (confirmed by user, to avoid restructuring) — SDP offer/answer + and ICE candidates are carried as new `Voice` namespace events inside the current `GatewayEvent` envelope, handled in + `GatewayClient::on_message` (currently a stub) and dispatched to a new `VoiceService`. +3. **Single UDP port reuse** — `rustrtc`'s `RtcConfiguration` will be set up with `ice_udp_mux = true` and bound to the + existing `NetworkConfig.udp_port`, so the media/ICE traffic keeps using the same single port previously owned by the + raw `UdpServer`, minimizing config/infra changes (firewall rules, `config.toml` stay compatible). +4. **No TURN/STUN for now** (confirmed by user) — `RtcConfiguration.ice_servers` left empty/default; can be added later + via `config.toml` without further architecture changes. +5. **Voice presence tracked separately from `RealtimeRegistry`** — a new lightweight in-memory registry (`VoiceRoom` per + `channel_id`) tracks which `PeerConnection`s are actively publishing/subscribing in a voice channel, since being + subscribed to gateway events (`RealtimeRegistry`) is not the same as being connected to voice media. ### Proposed Changes + - Replace the `src/udp` module with a new `src/voice` module: - - `voice/mod.rs`: public API, exports `VoiceService`. - - `voice/service.rs`: `VoiceService` owns a `rustrtc::RtcConfiguration` (built from `NetworkConfig`), a map `channel_id -> VoiceRoom`, and methods `handle_offer(user_id, channel_id, sdp) -> answer_sdp`, `handle_ice_candidate(...)`, `leave(user_id, channel_id)`. - - `voice/room.rs`: `VoiceRoom` holds `HashMap>` for one channel; implements track fan-out — on receiving a remote track from peer A, it calls into every other peer's `PeerConnection` to add/forward that track (rustrtc `PeerConnection` API, per its `Usage` example: create connection, `set_remote_description`, `create_answer`, `set_local_description`, subscribe to `on_track`). - - `voice/metrics.rs`: keep counters analogous to today's `UdpMetrics` (packets/bytes/errors), fed by hooks around track forwarding, still exposed through `AppMetrics.udp` (renamed `AppMetrics.voice` where feasible) and the existing `reporter::spawn_reporter` cadence. -- Extend `GatewayEvent` handling: add `"Voice"` namespace with actions like `offer`, `answer`, `ice-candidate`, `leave`; `GatewayClient::on_message` parses these and calls `VoiceService` through `AppState`, then pushes the answer/ICE-candidate response back on the same `mpsc::UnboundedSender` used for all other gateway events (no protocol reinvention). -- Permission check on `offer`: verify the user has `ChannelPermission::JOIN_VOICE` (and `SPEAK` to publish) on the target channel, reusing the same `computed_permission` lookups already used by `RealtimeRegistry`/`http/permissions.rs`. -- Update `src/core/mod.rs`: drop `UdpServer::new` / `udp_server.run()` / `udp_shutdown_tx`; construct `VoiceService` instead and store it in `AppState` (alongside `gateway`, `services`) so gateway handlers can call it; no UDP socket bind/run task is spawned by `App::run` anymore — `rustrtc` manages its own I/O internally once configured. -- Update `src/config.rs`: keep `NetworkConfig.udp_port` (renamed conceptually to "voice/media port" in comments) feeding `rustrtc`'s `ice_udp_mux_port`; update `DEFAULT_CONFIG_TOML` comment accordingly. -- Remove `src/udp/` directory entirely once `voice/` fully replaces its responsibilities; update `Cargo.toml`/module declarations (`lib.rs`) accordingly. + - `voice/mod.rs`: public API, exports `VoiceService`. + - `voice/service.rs`: `VoiceService` owns a `rustrtc::RtcConfiguration` (built from `NetworkConfig`), a map + `channel_id -> VoiceRoom`, and methods `handle_offer(user_id, channel_id, sdp) -> answer_sdp`, + `handle_ice_candidate(...)`, `leave(user_id, channel_id)`. + - `voice/room.rs`: `VoiceRoom` holds `HashMap>` for one channel; implements track + fan-out — on receiving a remote track from peer A, it calls into every other peer's `PeerConnection` to + add/forward that track (rustrtc `PeerConnection` API, per its `Usage` example: create connection, + `set_remote_description`, `create_answer`, `set_local_description`, subscribe to `on_track`). + - `voice/metrics.rs`: keep counters analogous to today's `UdpMetrics` (packets/bytes/errors), fed by hooks around + track forwarding, still exposed through `AppMetrics.udp` (renamed `AppMetrics.voice` where feasible) and the + existing `reporter::spawn_reporter` cadence. +- Extend `GatewayEvent` handling: add `"Voice"` namespace with actions like `offer`, `answer`, `ice-candidate`, `leave`; + `GatewayClient::on_message` parses these and calls `VoiceService` through `AppState`, then pushes the + answer/ICE-candidate response back on the same `mpsc::UnboundedSender` used for all other gateway events (no + protocol reinvention). +- Permission check on `offer`: verify the user has `ChannelPermission::JOIN_VOICE` (and `SPEAK` to publish) on the + target channel, reusing the same `computed_permission` lookups already used by `RealtimeRegistry`/ + `http/permissions.rs`. +- Update `src/core/mod.rs`: drop `UdpServer::new` / `udp_server.run()` / `udp_shutdown_tx`; construct `VoiceService` + instead and store it in `AppState` (alongside `gateway`, `services`) so gateway handlers can call it; no UDP socket + bind/run task is spawned by `App::run` anymore — `rustrtc` manages its own I/O internally once configured. +- Update `src/config.rs`: keep `NetworkConfig.udp_port` (renamed conceptually to "voice/media port" in comments) feeding + `rustrtc`'s `ice_udp_mux_port`; update `DEFAULT_CONFIG_TOML` comment accordingly. +- Remove `src/udp/` directory entirely once `voice/` fully replaces its responsibilities; update `Cargo.toml`/module + declarations (`lib.rs`) accordingly. ### Data Models / Contracts + ```rust // Gateway JSON contract additions (namespace = "Voice") { "namespace": "Voice", "action": "offer", "content": { "channel_id": Uuid, "sdp": String } } @@ -72,6 +138,7 @@ Replace the current placeholder raw-UDP voice relay (`src/udp/*`) with a real We { "namespace": "Voice", "action": "ice-candidate", "content": { "channel_id": Uuid, "candidate": String } } { "namespace": "Voice", "action": "leave", "content": { "channel_id": Uuid } } ``` + ```rust pub struct VoiceService { config: rustrtc::RtcConfiguration, @@ -86,6 +153,7 @@ impl VoiceService { ``` ### Architecture Diagram + ```mermaid graph TD ClientA[Client A - browser] -- WebSocket gateway: Voice offer/answer/ICE --> Gateway[GatewayManager / on_message] @@ -101,38 +169,72 @@ graph TD ``` ### Risks -- `rustrtc` is a young/fast-moving crate (frequent point releases per the benchmark notes found) — API surface for track-forwarding/SFU usage should be validated against the pinned `0.3.133` docs during implementation; if a needed primitive (e.g., explicit track re-publishing helper) is missing, a thin adapter layer will be needed inside `voice/room.rs`. -- Moving from "no real protocol" to full SDP/ICE negotiation is a larger surface than the previous placeholder; permission checks (`JOIN_VOICE`/`SPEAK`) must be enforced before any `PeerConnection` is created to avoid unauthorized channel joins. -- Since signaling now flows through the same WebSocket used for all other realtime events, malformed/large SDP payloads must not block the `GatewayClient` message loop — the voice service calls will be dispatched without blocking other event types. + +- `rustrtc` is a young/fast-moving crate (frequent point releases per the benchmark notes found) — API surface for + track-forwarding/SFU usage should be validated against the pinned `0.3.133` docs during implementation; if a needed + primitive (e.g., explicit track re-publishing helper) is missing, a thin adapter layer will be needed inside + `voice/room.rs`. +- Moving from "no real protocol" to full SDP/ICE negotiation is a larger surface than the previous placeholder; + permission checks (`JOIN_VOICE`/`SPEAK`) must be enforced before any `PeerConnection` is created to avoid unauthorized + channel joins. +- Since signaling now flows through the same WebSocket used for all other realtime events, malformed/large SDP payloads + must not block the `GatewayClient` message loop — the voice service calls will be dispatched without blocking other + event types. # Delivery Steps -### * Step 1: Scaffold the voice module and RtcConfiguration from NetworkConfig -A new `src/voice` module exists with a `VoiceService` capable of building a `rustrtc::RtcConfiguration` from the app's network config, replacing the old `src/udp` module's role in `core/mod.rs`. +### * Step 1: Scaffold the voice module and RtcConfiguration from NetworkConfig + +A new `src/voice` module exists with a `VoiceService` capable of building a `rustrtc::RtcConfiguration` from the app's +network config, replacing the old `src/udp` module's role in `core/mod.rs`. + - Create `src/voice/mod.rs`, `src/voice/service.rs`, `src/voice/room.rs`, `src/voice/metrics.rs`. -- Port `UdpMetrics`-style counters into `voice/metrics.rs` (packets/bytes in/out, errors), keeping the `Metrics`/`MetricsSnapshot` trait implementations used by `crate::metrics::reporter`. -- Build `RtcConfiguration`/`RtcConfigurationBuilder` in `VoiceService::new(&NetworkConfig, metrics)` using `ice_udp_mux = true` and `ice_udp_mux_port = network.udp_port`, no ICE servers configured. -- Remove `src/udp/server.rs` and `src/udp/router.rs`; delete unused `RoutingTable` (dead code confirmed unused elsewhere). -- Update `src/core/mod.rs` (`App::build`/`App::run`) to construct `VoiceService` instead of `UdpServer`, store it on `AppState`, and drop the now-unneeded `udp_shutdown_tx`/`udp_handle` spawn/join wiring. +- Port `UdpMetrics`-style counters into `voice/metrics.rs` (packets/bytes in/out, errors), keeping the `Metrics`/ + `MetricsSnapshot` trait implementations used by `crate::metrics::reporter`. +- Build `RtcConfiguration`/`RtcConfigurationBuilder` in `VoiceService::new(&NetworkConfig, metrics)` using + `ice_udp_mux = true` and `ice_udp_mux_port = network.udp_port`, no ICE servers configured. +- Remove `src/udp/server.rs` and `src/udp/router.rs`; delete unused `RoutingTable` (dead code confirmed unused + elsewhere). +- Update `src/core/mod.rs` (`App::build`/`App::run`) to construct `VoiceService` instead of `UdpServer`, store it on + `AppState`, and drop the now-unneeded `udp_shutdown_tx`/`udp_handle` spawn/join wiring. - Update `src/metrics/mod.rs` (`AppMetrics`) to reference the new voice metrics type. -### Step 2: Implement per-channel PeerConnection lifecycle in VoiceRoom -`VoiceService`/`VoiceRoom` can accept an SDP offer for a given channel, create a `rustrtc::PeerConnection`, and return an SDP answer, tracking connections per `channel_id`. +### Step 2: Implement per-channel PeerConnection lifecycle in VoiceRoom + +`VoiceService`/`VoiceRoom` can accept an SDP offer for a given channel, create a `rustrtc::PeerConnection`, and return +an SDP answer, tracking connections per `channel_id`. + - Implement `VoiceRoom` (`channel_id -> HashMap>`). -- Implement `VoiceService::handle_offer(user_id, channel_id, sdp)`: looks up/creates the room, creates a `PeerConnection` via `rustrtc`, calls `set_remote_description`/`create_answer`/`set_local_description`, returns the answer SDP. -- Implement `VoiceService::handle_ice_candidate` and `VoiceService::leave`, closing and removing the `PeerConnection` from its room. -- Enforce `ChannelPermission::JOIN_VOICE` (and `SPEAK` for publishing) before creating a `PeerConnection`, reusing the `computed_permission` lookup pattern from `src/services/realtime_registry.rs`. +- Implement `VoiceService::handle_offer(user_id, channel_id, sdp)`: looks up/creates the room, creates a + `PeerConnection` via `rustrtc`, calls `set_remote_description`/`create_answer`/`set_local_description`, returns the + answer SDP. +- Implement `VoiceService::handle_ice_candidate` and `VoiceService::leave`, closing and removing the `PeerConnection` + from its room. +- Enforce `ChannelPermission::JOIN_VOICE` (and `SPEAK` for publishing) before creating a `PeerConnection`, reusing the + `computed_permission` lookup pattern from `src/services/realtime_registry.rs`. + +### Step 3: Wire SFU track forwarding between peers in the same channel + +Audio published by one connected client in a voice channel is forwarded by the server to every other client connected to +the same channel. -### Step 3: Wire SFU track forwarding between peers in the same channel -Audio published by one connected client in a voice channel is forwarded by the server to every other client connected to the same channel. - On `PeerConnection::on_track` for a given user's connection, register the inbound track on the owning `VoiceRoom`. -- For every other `PeerConnection` already in that `VoiceRoom`, add/forward the new track (SFU fan-out) using `rustrtc`'s track/`MediaCapabilities` APIs. +- For every other `PeerConnection` already in that `VoiceRoom`, add/forward the new track (SFU fan-out) using `rustrtc` + 's track/`MediaCapabilities` APIs. - When a new peer joins an existing room, subscribe it to all tracks already being forwarded by other room members. -- On `leave`/disconnect, remove the peer's published track from all other peers' forwarding sets and close its `PeerConnection`. +- On `leave`/disconnect, remove the peer's published track from all other peers' forwarding sets and close its + `PeerConnection`. -### Step 4: Expose Voice signaling through the existing WebSocket gateway -Clients can perform the full offer/answer/ICE handshake over the existing gateway WebSocket connection using a new `Voice` namespace, with no new HTTP/WS endpoint introduced. -- Extend `GatewayClient::on_message` in `src/routes/gateway/mod.rs` to parse `GatewayEvent{namespace: "Voice", action, content}` messages (`offer`, `answer`, `ice-candidate`, `leave`). -- Dispatch parsed messages to `VoiceService` (accessed via `AppState`) and send the resulting answer/ICE-candidate events back through the client's existing `mpsc::UnboundedSender`. -- On gateway disconnect (`GatewayClient::on_disconnect`), call `VoiceService::leave` for any channel the user was actively connected to in voice. -- Update `config.toml`'s `DEFAULT_CONFIG_TOML` comment for `udp_port` to reflect its new role as the rustrtc ICE/media mux port. \ No newline at end of file +### Step 4: Expose Voice signaling through the existing WebSocket gateway + +Clients can perform the full offer/answer/ICE handshake over the existing gateway WebSocket connection using a new +`Voice` namespace, with no new HTTP/WS endpoint introduced. + +- Extend `GatewayClient::on_message` in `src/routes/gateway/mod.rs` to parse + `GatewayEvent{namespace: "Voice", action, content}` messages (`offer`, `answer`, `ice-candidate`, `leave`). +- Dispatch parsed messages to `VoiceService` (accessed via `AppState`) and send the resulting answer/ICE-candidate + events back through the client's existing `mpsc::UnboundedSender`. +- On gateway disconnect (`GatewayClient::on_disconnect`), call `VoiceService::leave` for any channel the user was + actively connected to in voice. +- Update `config.toml`'s `DEFAULT_CONFIG_TOML` comment for `udp_port` to reflect its new role as the rustrtc ICE/media + mux port. \ No newline at end of file diff --git a/src/rtc/client.rs b/src/rtc/client.rs index fd75d42..245fcbf 100644 --- a/src/rtc/client.rs +++ b/src/rtc/client.rs @@ -29,7 +29,7 @@ impl RTCClient { } } - pub async fn on_message(&self, raw_message: Message) { + pub async fn ws_on_message(&self, raw_message: Message) { let Message::Text(raw_message) = raw_message else { return; }; @@ -49,6 +49,10 @@ impl RTCClient { } } + pub async fn ws_send_message(&self, raw_message: Message) { + let _ = self.websocket_sender.send(raw_message); + } + /// Ferme proprement la connexion WebRTC. pub fn close(&self) { self.peer_connection.close(); diff --git a/src/rtc/mod.rs b/src/rtc/mod.rs index 7ad7dae..9489151 100644 --- a/src/rtc/mod.rs +++ b/src/rtc/mod.rs @@ -4,11 +4,14 @@ mod metrics; pub mod ws_entrypoint; use crate::config::NetworkConfig; +use crate::models::channel; use crate::repositories::Repositories; use crate::rtc::client::RTCClient; use crate::services::Services; use event_bus::EventBus; use rustrtc::{PeerConnection, RtcConfiguration, RtcConfigurationBuilder}; +use std::collections::HashMap; +use std::fmt; use std::sync::Arc; // 1. Client crée une RTCPeerConnection // 2. Client crée une SDP offer @@ -21,12 +24,44 @@ use std::sync::Arc; // 9. ICE sélectionne un chemin réseau // 10. La connexion WebRTC devient active -#[derive(Debug, Clone)] +pub struct VoiceRoom {} + +pub struct VoiceRoomManager { + rooms: HashMap, +} + +#[derive(Clone)] pub struct RTCManager { pub config: RtcConfiguration, pub repositories: Arc, pub services: Arc, pub event_bus: Arc, + + // + rooms: Arc, +} + +impl fmt::Debug for RTCManager { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VoiceService") + .field("config", &self.config) + // .field("metrics", &self.metrics) + .finish() + } +} + +impl VoiceRoom { + pub fn new() -> Self { + Self {} + } +} + +impl VoiceRoomManager { + pub fn new() -> Self { + Self { + rooms: HashMap::new(), + } + } } impl RTCManager { @@ -42,16 +77,21 @@ impl RTCManager { .bind_ip(network.host.to_string()); let config = builder.build(); + let rooms = Arc::new(VoiceRoomManager::new()); + Self { config, repositories, services, event_bus, + rooms, } } /// Session Description Protocol - Permet de se mettre d'accord sur les paramètres media - pub async fn handle_sdp_offer(&self, rtc_client: RTCClient, offer_sdp: String) {} + pub async fn handle_sdp_offer(&self, rtc_client: &RTCClient, offer_sdp: String) { + let pc = rtc_client.peer_connection.clone(); + } /// Interactive Connectivity Establishment - Permet de négocier un chemin réseau pub async fn handle_ice_candidate(&self) {} diff --git a/src/rtc/ws_entrypoint.rs b/src/rtc/ws_entrypoint.rs index ad72bb6..8edfadd 100644 --- a/src/rtc/ws_entrypoint.rs +++ b/src/rtc/ws_entrypoint.rs @@ -45,7 +45,7 @@ pub async fn ws_entrypoint_handler( let client_clone = rtc_client.clone(); let mut recv_task = tokio::spawn(async move { while let Some(Ok(message)) = receiver.next().await { - client_clone.on_message(message).await; + client_clone.ws_on_message(message).await; } });