This commit is contained in:
2026-09-20 20:59:41 +02:00
parent 8297111060
commit 7f1402dd33
7 changed files with 111 additions and 5 deletions
+2
View File
@@ -18,5 +18,7 @@ pub async fn ws_handler(
.get_by_id(channel_id)
.await?
.ok_or(HTTPError::NotFound)?;
// todo : check des permissions
// aiinsi que vérifier qu'on est sur un chan vocal OU un chan de message privé
Ok(ws.on_upgrade(move |socket| ws_entrypoint_handler(socket, state, channel, user)))
}
+11
View File
@@ -0,0 +1,11 @@
### Step 1 - handshake WebSocket
Le client doit effectuer une connexion HTTP (websocket) sur /rtc/{channel_id}
Le server vérifiera avant d'accepter la connexion si :
- Le channel existe
- Vérifiera les permissions sur le canal (check_perm ou si c'est un canal privé)
### Step 2 - SDP (négociation du type de media)
### Step 3 - ICE (Négociation du flux réseaux)
+25 -4
View File
@@ -1,3 +1,4 @@
use super::messages::VoiceClientMessage;
use crate::models::{channel, user};
use axum::extract::ws::Message;
use rustrtc::peer_connection::PeerConnection;
@@ -7,8 +8,8 @@ use tokio::sync::mpsc::UnboundedSender;
/// Client connecté à un canal RTC.
#[derive(Clone)]
pub struct RtcClient {
pub user: user::Model,
pub channel: channel::Model,
pub user: Arc<user::Model>,
pub channel: Arc<channel::Model>,
pub peer_connection: Arc<PeerConnection>,
pub websocket_sender: UnboundedSender<Message>,
}
@@ -21,13 +22,33 @@ impl RtcClient {
websocket_sender: UnboundedSender<Message>,
) -> Self {
Self {
user,
channel,
user: Arc::new(user),
channel: Arc::new(channel),
peer_connection,
websocket_sender,
}
}
pub fn on_message(&self, raw_message: Message) {
let Message::Text(raw_message) = raw_message else {
return;
};
let parsed = match serde_json::from_str::<VoiceClientMessage>(&raw_message) {
Ok(message) => message,
Err(_) => return,
};
match parsed {
VoiceClientMessage::SDPOffer { channel_id, sdp } => {}
VoiceClientMessage::IceCandidate {
channel_id,
candidate,
} => {}
VoiceClientMessage::Leave { channel_id } => {}
}
}
/// Ferme proprement la connexion WebRTC.
pub fn close(&self) {
self.peer_connection.close();
+36
View File
@@ -0,0 +1,36 @@
use crate::config::NetworkConfig;
use rustrtc::{RtcConfiguration, RtcConfigurationBuilder};
// 1. Client crée une RTCPeerConnection
// 2. Client crée une SDP offer
// 3. Client envoie l'offer via WebSocket
// 4. Serveur crée sa PeerConnection
// 5. Serveur applique l'offer
// 6. Serveur crée une SDP answer
// 7. Serveur renvoie l'answer via WebSocket
// 8. Client et serveur échangent les candidats ICE
// 9. ICE sélectionne un chemin réseau
// 10. La connexion WebRTC devient active
#[derive(Debug, Clone)]
struct RTCManager {
pub config: RtcConfiguration,
}
impl RTCManager {
pub fn new(network: &NetworkConfig) -> Self {
let builder = RtcConfigurationBuilder::new()
.ice_udp_mux(true)
.ice_udp_mux_port(network.udp_port)
.bind_ip(network.host.to_string());
let config = builder.build();
Self { config }
}
/// Permet de se mettre d'accord sur les paramètres media
pub async fn handle_sdp_offer(&self) {}
/// Interactive Connectivity Establishment - Permet de négocier un chemin réseau
pub async fn handle_ice_candidate(&self) {}
}
+18
View File
@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Deserialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum VoiceClientMessage {
SDPOffer { channel_id: Uuid, sdp: String },
IceCandidate { channel_id: Uuid, candidate: String },
Leave { channel_id: Uuid },
}
#[derive(Debug, Serialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum VoiceServerMessage {
Answer { channel_id: Uuid, sdp: String },
IceCandidate { channel_id: Uuid, candidate: String },
Error { message: String },
}
+2
View File
@@ -1,4 +1,6 @@
mod client;
mod manager;
mod messages;
mod metrics;
pub mod ws_entrypoint;
+17 -1
View File
@@ -5,8 +5,23 @@ use crate::core::AppState;
use crate::models::{channel, user};
use axum::extract::ws::{Message, WebSocket};
use futures_util::{SinkExt, StreamExt};
use rustrtc::PeerConnection;
use std::sync::Arc;
use tokio::sync::mpsc;
// 1. Le frontend ouvre /rtc/{channel_id}
// 2. Axum extrait channel_id
// 3. CurrentUser identifie l'utilisateur
// 4. Le serveur vérifie le canal et les permissions
// 5. Le serveur accepte le WebSocket
// 6. Le client envoie son SDP Offer
// 7. Le serveur crée la PeerConnection
// 8. Le serveur applique l'Offer
// 9. Le serveur crée une SDP Answer
// 10. Le serveur renvoie l'Answer
// 11. Les candidats ICE sont échangés
// 12. À la déconnexion, la PeerConnection est fermée
pub async fn ws_entrypoint_handler(
socket: WebSocket,
state: AppState,
@@ -15,7 +30,8 @@ pub async fn ws_entrypoint_handler(
) {
let (mut sender, mut receiver) = socket.split();
let (tx, mut rx) = mpsc::unbounded_channel::<Message>();
// todo : faire le peer connexion.
let peer_connection = Arc::new(PeerConnection::new(state.rtc.config.clone()));
let mut rtc_client = RtcClient::new(user, channel, peer_connection, tx);
let send_task = tokio::spawn(async move {