286 lines
9.9 KiB
Rust
286 lines
9.9 KiB
Rust
//! Configuration WebRTC pour le stream server
|
|
//!
|
|
//! Ce module fournit la configuration WebRTC avec :
|
|
//! - Configuration des serveurs ICE (STUN/TURN)
|
|
//! - Configuration du signaling
|
|
//! - Configuration des codecs audio
|
|
//! - Gestion depuis les variables d'environnement
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::time::Duration;
|
|
// Note: Use tracing::info! macro directly instead of importing
|
|
|
|
use super::{AudioCodec, IceServer};
|
|
|
|
/// Configuration WebRTC pour streaming audio
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WebRTCConfig {
|
|
pub ice_servers: Vec<IceServer>,
|
|
pub signaling_url: String,
|
|
pub max_peers: usize,
|
|
pub connection_timeout: Duration,
|
|
pub heartbeat_interval: Duration,
|
|
pub codec_preferences: Vec<AudioCodec>,
|
|
pub bitrate_adaptation: bool,
|
|
pub jitter_buffer_ms: u32,
|
|
}
|
|
|
|
impl Default for WebRTCConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
ice_servers: vec![IceServer {
|
|
urls: vec!["stun:stun.l.google.com:19302".to_string()],
|
|
username: None,
|
|
credential: None,
|
|
}],
|
|
signaling_url: "ws://localhost:3002/ws/webrtc".to_string(),
|
|
max_peers: 1000,
|
|
connection_timeout: Duration::from_secs(30),
|
|
heartbeat_interval: Duration::from_secs(10),
|
|
codec_preferences: vec![
|
|
AudioCodec::Opus { bitrate: 320 },
|
|
AudioCodec::Aac { bitrate: 256 },
|
|
AudioCodec::Mp3 { bitrate: 192 },
|
|
],
|
|
bitrate_adaptation: true,
|
|
jitter_buffer_ms: 100,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WebRTCConfig {
|
|
/// Crée une configuration WebRTC depuis les variables d'environnement
|
|
pub fn from_env() -> Self {
|
|
let mut config = Self::default();
|
|
|
|
// Configuration des serveurs ICE
|
|
if let Ok(ice_servers) = std::env::var("WEBRTC_ICE_SERVERS") {
|
|
config.ice_servers = Self::parse_ice_servers(&ice_servers);
|
|
tracing::info!("🔧 WebRTC ICE servers configurés depuis WEBRTC_ICE_SERVERS");
|
|
} else {
|
|
// Configuration par défaut avec STUN/TURN si disponibles
|
|
let mut ice_servers = vec![IceServer {
|
|
urls: vec!["stun:stun.l.google.com:19302".to_string()],
|
|
username: None,
|
|
credential: None,
|
|
}];
|
|
|
|
// Ajouter serveur STUN personnalisé si configuré
|
|
if let Ok(stun_url) = std::env::var("WEBRTC_STUN_URL") {
|
|
ice_servers.push(IceServer {
|
|
urls: vec![stun_url],
|
|
username: None,
|
|
credential: None,
|
|
});
|
|
}
|
|
|
|
// Ajouter serveur TURN si configuré
|
|
if let (Ok(turn_url), Ok(turn_username), Ok(turn_credential)) = (
|
|
std::env::var("WEBRTC_TURN_URL"),
|
|
std::env::var("WEBRTC_TURN_USERNAME"),
|
|
std::env::var("WEBRTC_TURN_CREDENTIAL"),
|
|
) {
|
|
ice_servers.push(IceServer {
|
|
urls: vec![turn_url],
|
|
username: Some(turn_username),
|
|
credential: Some(turn_credential),
|
|
});
|
|
tracing::info!("✅ Serveur TURN configuré");
|
|
} else {
|
|
tracing::warn!(
|
|
"⚠️ Serveur TURN non configuré - certaines connexions peuvent échouer"
|
|
);
|
|
}
|
|
|
|
config.ice_servers = ice_servers;
|
|
}
|
|
|
|
// Configuration du signaling URL
|
|
if let Ok(signaling_url) = std::env::var("WEBRTC_SIGNALING_URL") {
|
|
config.signaling_url = signaling_url;
|
|
tracing::info!("🔧 WebRTC signaling URL: {}", config.signaling_url);
|
|
} else {
|
|
// Utiliser le port du serveur si disponible
|
|
let port = std::env::var("STREAM_PORT")
|
|
.or_else(|_| std::env::var("PORT"))
|
|
.unwrap_or_else(|_| "3002".to_string());
|
|
config.signaling_url = format!("ws://localhost:{}/ws/webrtc", port);
|
|
tracing::info!(
|
|
"🔧 WebRTC signaling URL par défaut: {}",
|
|
config.signaling_url
|
|
);
|
|
}
|
|
|
|
// Configuration du nombre maximum de peers
|
|
if let Ok(max_peers) = std::env::var("WEBRTC_MAX_PEERS") {
|
|
if let Ok(max) = max_peers.parse::<usize>() {
|
|
config.max_peers = max;
|
|
}
|
|
}
|
|
|
|
// Configuration du timeout de connexion
|
|
if let Ok(timeout) = std::env::var("WEBRTC_CONNECTION_TIMEOUT") {
|
|
if let Ok(secs) = timeout.parse::<u64>() {
|
|
config.connection_timeout = Duration::from_secs(secs);
|
|
}
|
|
}
|
|
|
|
// Configuration de l'intervalle de heartbeat
|
|
if let Ok(interval) = std::env::var("WEBRTC_HEARTBEAT_INTERVAL") {
|
|
if let Ok(secs) = interval.parse::<u64>() {
|
|
config.heartbeat_interval = Duration::from_secs(secs);
|
|
}
|
|
}
|
|
|
|
// Configuration de l'adaptation de bitrate
|
|
if let Ok(adaptation) = std::env::var("WEBRTC_BITRATE_ADAPTATION") {
|
|
config.bitrate_adaptation = adaptation.parse().unwrap_or(true);
|
|
}
|
|
|
|
// Configuration du jitter buffer
|
|
if let Ok(jitter) = std::env::var("WEBRTC_JITTER_BUFFER_MS") {
|
|
if let Ok(ms) = jitter.parse::<u32>() {
|
|
config.jitter_buffer_ms = ms;
|
|
}
|
|
}
|
|
|
|
tracing::info!(
|
|
"✅ Configuration WebRTC initialisée: {} ICE servers, max_peers={}, signaling={}",
|
|
config.ice_servers.len(),
|
|
config.max_peers,
|
|
config.signaling_url
|
|
);
|
|
|
|
config
|
|
}
|
|
|
|
/// Parse une chaîne de serveurs ICE au format JSON ou CSV
|
|
///
|
|
/// Format JSON: `[{"urls":["stun:stun.example.com"],"username":null,"credential":null}]`
|
|
/// Format CSV: `stun:stun.example.com,turn:turn.example.com:user:pass`
|
|
fn parse_ice_servers(servers_str: &str) -> Vec<IceServer> {
|
|
// Essayer de parser comme JSON d'abord
|
|
if let Ok(servers) = serde_json::from_str::<Vec<IceServer>>(servers_str) {
|
|
return servers;
|
|
}
|
|
|
|
// Sinon, parser comme CSV
|
|
let mut servers = Vec::new();
|
|
for server_str in servers_str.split(',') {
|
|
let server_str = server_str.trim();
|
|
if server_str.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
// Format: "turn:turn.example.com:user:pass" ou "stun:stun.example.com"
|
|
if server_str.contains(':') {
|
|
let parts: Vec<&str> = server_str.split(':').collect();
|
|
if parts.len() >= 3 {
|
|
let protocol = parts[0];
|
|
let url = format!("{}:{}", protocol, parts[1]);
|
|
let username = if parts.len() > 3 {
|
|
Some(parts[2].to_string())
|
|
} else {
|
|
None
|
|
};
|
|
let credential = if parts.len() > 4 {
|
|
Some(parts[3].to_string())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
servers.push(IceServer {
|
|
urls: vec![url],
|
|
username,
|
|
credential,
|
|
});
|
|
} else {
|
|
servers.push(IceServer {
|
|
urls: vec![server_str.to_string()],
|
|
username: None,
|
|
credential: None,
|
|
});
|
|
}
|
|
} else {
|
|
servers.push(IceServer {
|
|
urls: vec![server_str.to_string()],
|
|
username: None,
|
|
credential: None,
|
|
});
|
|
}
|
|
}
|
|
|
|
servers
|
|
}
|
|
|
|
/// Valide la configuration WebRTC
|
|
pub fn validate(&self) -> Result<(), String> {
|
|
if self.ice_servers.is_empty() {
|
|
return Err("Au moins un serveur ICE est requis".to_string());
|
|
}
|
|
|
|
if self.signaling_url.is_empty() {
|
|
return Err("URL de signaling est requise".to_string());
|
|
}
|
|
|
|
if !self.signaling_url.starts_with("ws://") && !self.signaling_url.starts_with("wss://") {
|
|
return Err("URL de signaling doit être un WebSocket (ws:// ou wss://)".to_string());
|
|
}
|
|
|
|
if self.max_peers == 0 {
|
|
return Err("max_peers doit être supérieur à 0".to_string());
|
|
}
|
|
|
|
if self.connection_timeout.as_secs() == 0 {
|
|
return Err("connection_timeout doit être supérieur à 0".to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_default_config() {
|
|
let config = WebRTCConfig::default();
|
|
assert!(!config.ice_servers.is_empty());
|
|
assert!(!config.signaling_url.is_empty());
|
|
assert!(config.max_peers > 0);
|
|
assert!(config.validate().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_ice_servers_json() {
|
|
let json = r#"[{"urls":["stun:stun.example.com"],"username":null,"credential":null}]"#;
|
|
let servers = WebRTCConfig::parse_ice_servers(json);
|
|
assert_eq!(servers.len(), 1);
|
|
assert_eq!(servers[0].urls[0], "stun:stun.example.com");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_ice_servers_csv() {
|
|
let csv = "stun:stun.example.com:19302,turn:turn.example.com:user:pass";
|
|
let servers = WebRTCConfig::parse_ice_servers(csv);
|
|
assert!(servers.len() >= 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_config() {
|
|
let mut config = WebRTCConfig::default();
|
|
assert!(config.validate().is_ok());
|
|
|
|
config.ice_servers.clear();
|
|
assert!(config.validate().is_err());
|
|
|
|
config = WebRTCConfig::default();
|
|
config.signaling_url = "".to_string();
|
|
assert!(config.validate().is_err());
|
|
|
|
config = WebRTCConfig::default();
|
|
config.signaling_url = "http://example.com".to_string();
|
|
assert!(config.validate().is_err());
|
|
}
|
|
}
|