use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; // Note: Use tracing::info! macro directly instead of importing use uuid::Uuid; /// Qualités audio supportées avec leurs paramètres #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AudioQuality { Low, // 128kbps Medium, // 256kbps High, // 320kbps } impl AudioQuality { pub fn bitrate(&self) -> u32 { match self { AudioQuality::Low => 128000, AudioQuality::Medium => 256000, AudioQuality::High => 320000, } } pub fn compression_level(&self) -> u8 { match self { AudioQuality::Low => 6, // Compression plus agressive AudioQuality::Medium => 4, // Compression modérée AudioQuality::High => 2, // Compression légère } } pub fn sample_rate(&self) -> u32 { match self { AudioQuality::Low => 44100, AudioQuality::Medium => 44100, AudioQuality::High => 48000, } } } /// Métriques de bande passante pour un client #[derive(Debug, Clone)] pub struct BandwidthMetrics { pub client_id: Uuid, pub measured_bandwidth: f64, // bps pub packet_loss_rate: f64, // 0.0 à 1.0 pub latency_ms: u32, pub last_measurement: std::time::Instant, pub measurement_count: u32, } /// Détection automatique de la bande passante #[derive(Debug)] pub struct BandwidthDetector { client_metrics: Arc>>, measurement_window: std::time::Duration, min_measurements: u32, } impl BandwidthDetector { pub fn new() -> Self { Self { client_metrics: Arc::new(RwLock::new(HashMap::new())), measurement_window: std::time::Duration::from_secs(30), min_measurements: 5, } } /// Enregistre une mesure de bande passante pour un client pub async fn record_measurement( &self, client_id: Uuid, bytes_transferred: usize, duration: std::time::Duration, packet_loss: f64, latency_ms: u32, ) { let bandwidth_bps = (bytes_transferred as f64 * 8.0) / duration.as_secs_f64(); let mut metrics_map = self.client_metrics.write().await; let metrics = metrics_map.entry(client_id).or_insert_with(|| BandwidthMetrics { client_id, measured_bandwidth: bandwidth_bps, packet_loss_rate: packet_loss, latency_ms, last_measurement: std::time::Instant::now(), measurement_count: 0, }); // Mise à jour des métriques avec moyenne mobile let alpha = 0.3; // Facteur de lissage metrics.measured_bandwidth = alpha * bandwidth_bps + (1.0 - alpha) * metrics.measured_bandwidth; metrics.packet_loss_rate = alpha * packet_loss + (1.0 - alpha) * metrics.packet_loss_rate; metrics.latency_ms = ((alpha * latency_ms as f64) + ((1.0 - alpha) * metrics.latency_ms as f64)) as u32; metrics.last_measurement = std::time::Instant::now(); metrics.measurement_count += 1; tracing::debug!( "Bandwidth measurement for client {}: {:.2} bps, loss: {:.2}%, latency: {}ms", client_id, bandwidth_bps, packet_loss * 100.0, latency_ms ); } /// Détermine la qualité audio recommandée pour un client pub async fn get_recommended_quality(&self, client_id: Uuid) -> AudioQuality { let metrics_map = self.client_metrics.read().await; if let Some(metrics) = metrics_map.get(&client_id) { // Vérifier si on a assez de mesures if metrics.measurement_count < self.min_measurements { return AudioQuality::Medium; // Qualité par défaut } // Vérifier si les mesures sont récentes if metrics.last_measurement.elapsed() > self.measurement_window { return AudioQuality::Medium; // Qualité par défaut si mesures obsolètes } // Déterminer la qualité basée sur la bande passante et la perte de paquets let bandwidth_mbps = metrics.measured_bandwidth / 1_000_000.0; let packet_loss_percent = metrics.packet_loss_rate * 100.0; if bandwidth_mbps >= 2.0 && packet_loss_percent < 1.0 { AudioQuality::High } else if bandwidth_mbps >= 1.0 && packet_loss_percent < 3.0 { AudioQuality::Medium } else { AudioQuality::Low } } else { AudioQuality::Medium // Qualité par défaut pour nouveaux clients } } /// Nettoie les métriques obsolètes pub async fn cleanup_old_metrics(&self) { let mut metrics_map = self.client_metrics.write().await; let cutoff_time = std::time::Instant::now() - self.measurement_window * 2; metrics_map.retain(|_, metrics| metrics.last_measurement > cutoff_time); tracing::debug!("Cleaned up old bandwidth metrics, {} clients remaining", metrics_map.len()); } /// Obtient les métriques d'un client pub async fn get_client_metrics(&self, client_id: Uuid) -> Option { let metrics_map = self.client_metrics.read().await; metrics_map.get(&client_id).cloned() } } /// Gestionnaire de compression adaptative #[derive(Debug)] pub struct AdaptiveCompression { bandwidth_detector: BandwidthDetector, quality_overrides: Arc>>, // Overrides manuels global_quality_limit: Arc>>, // Limite globale } impl AdaptiveCompression { pub fn new() -> Self { Self { bandwidth_detector: BandwidthDetector::new(), quality_overrides: Arc::new(RwLock::new(HashMap::new())), global_quality_limit: Arc::new(RwLock::new(None)), } } /// Détermine la qualité audio pour un client pub async fn get_audio_quality(&self, client_id: Uuid) -> AudioQuality { // Vérifier les overrides manuels { let overrides = self.quality_overrides.read().await; if let Some(quality) = overrides.get(&client_id) { return quality.clone(); } } // Vérifier la limite globale { let global_limit = self.global_quality_limit.read().await; if let Some(limit) = global_limit.as_ref() { let recommended = self.bandwidth_detector.get_recommended_quality(client_id).await; return self.apply_quality_limit(recommended, limit.clone()); } } // Utiliser la détection automatique self.bandwidth_detector.get_recommended_quality(client_id).await } /// Applique une limite de qualité fn apply_quality_limit(&self, recommended: AudioQuality, limit: AudioQuality) -> AudioQuality { match (&recommended, &limit) { (AudioQuality::High, AudioQuality::High) => AudioQuality::High, (AudioQuality::High, AudioQuality::Medium) => AudioQuality::Medium, (AudioQuality::High, AudioQuality::Low) => AudioQuality::Low, (AudioQuality::Medium, AudioQuality::High) => AudioQuality::Medium, (AudioQuality::Medium, AudioQuality::Medium) => AudioQuality::Medium, (AudioQuality::Medium, AudioQuality::Low) => AudioQuality::Low, (AudioQuality::Low, _) => AudioQuality::Low, } } /// Enregistre une mesure de transfert pub async fn record_transfer( &self, client_id: Uuid, bytes_transferred: usize, duration: std::time::Duration, packet_loss: f64, latency_ms: u32, ) { self.bandwidth_detector .record_measurement(client_id, bytes_transferred, duration, packet_loss, latency_ms) .await; } /// Définit un override de qualité pour un client pub async fn set_client_quality_override(&self, client_id: Uuid, quality: AudioQuality) { let mut overrides = self.quality_overrides.write().await; overrides.insert(client_id, quality); tracing::info!("Set quality override for client {}: {:?}", client_id, quality); } /// Supprime l'override de qualité pour un client pub async fn remove_client_quality_override(&self, client_id: Uuid) { let mut overrides = self.quality_overrides.write().await; overrides.remove(&client_id); tracing::info!("Removed quality override for client {}", client_id); } /// Définit une limite de qualité globale pub async fn set_global_quality_limit(&self, quality: Option) { let mut global_limit = self.global_quality_limit.write().await; *global_limit = quality; match &*global_limit { Some(q) => tracing::info!("Set global quality limit: {:?}", q), None => tracing::info!("Removed global quality limit"), } } /// Nettoie les données obsolètes pub async fn cleanup(&self) { self.bandwidth_detector.cleanup_old_metrics().await; // Nettoyer les overrides pour les clients inactifs let mut overrides = self.quality_overrides.write().await; let cutoff_time = std::time::Instant::now() - std::time::Duration::from_secs(3600); // 1 heure overrides.retain(|client_id, _| { // Ici on pourrait vérifier si le client est encore actif // Pour simplifier, on garde tous les overrides true }); } /// Obtient les statistiques de compression pub async fn get_compression_stats(&self) -> CompressionStats { let metrics_map = self.bandwidth_detector.client_metrics.read().await; let overrides = self.quality_overrides.read().await; let global_limit = self.global_quality_limit.read().await; let mut quality_distribution = HashMap::new(); let mut total_clients = 0; for (client_id, metrics) in metrics_map.iter() { let quality = if overrides.contains_key(client_id) { overrides[client_id].clone() } else { self.bandwidth_detector.get_recommended_quality(*client_id).await }; let final_quality = if let Some(limit) = global_limit.as_ref() { self.apply_quality_limit(quality, limit.clone()) } else { quality }; *quality_distribution.entry(final_quality).or_insert(0) += 1; total_clients += 1; } CompressionStats { total_clients, quality_distribution, has_global_limit: global_limit.is_some(), override_count: overrides.len(), } } } /// Statistiques de compression #[derive(Debug, Clone)] pub struct CompressionStats { pub total_clients: usize, pub quality_distribution: HashMap, pub has_global_limit: bool, pub override_count: usize, } /// Utilitaire pour la compression Brotli des assets pub struct BrotliCompressor { compression_level: u32, } impl BrotliCompressor { pub fn new(level: u32) -> Self { Self { compression_level: level.clamp(0, 11), } } /// Compresse des données avec Brotli pub fn compress(&self, data: &[u8]) -> Result, Box> { use brotli::enc::BrotliEncoderParams; use std::io::Write; let mut params = BrotliEncoderParams::default(); params.quality = self.compression_level as i32; let mut compressed = Vec::new(); { let mut writer = brotli::CompressorWriter::new(&mut compressed, 4096, ¶ms); writer.write_all(data)?; writer.flush()?; } Ok(compressed) } /// Décompresse des données Brotli pub fn decompress(&self, compressed_data: &[u8]) -> Result, Box> { use std::io::Read; let mut decompressed = Vec::new(); { let mut reader = brotli::Decompressor::new(compressed_data, 4096); reader.read_to_end(&mut decompressed)?; } Ok(decompressed) } } #[cfg(test)] mod tests { use super::*; use std::time::Duration; #[tokio::test] async fn test_bandwidth_detection() { let detector = BandwidthDetector::new(); let client_id = Uuid::new_v4(); // Simuler des mesures de bande passante detector.record_measurement(client_id, 1024 * 1024, Duration::from_secs(1), 0.01, 50).await; detector.record_measurement(client_id, 2 * 1024 * 1024, Duration::from_secs(1), 0.005, 45).await; let quality = detector.get_recommended_quality(client_id).await; assert!(matches!(quality, AudioQuality::High | AudioQuality::Medium)); } #[tokio::test] async fn test_adaptive_compression() { let compression = AdaptiveCompression::new(); let client_id = Uuid::new_v4(); // Test avec override compression.set_client_quality_override(client_id, AudioQuality::Low).await; let quality = compression.get_audio_quality(client_id).await; assert_eq!(quality, AudioQuality::Low); // Test sans override compression.remove_client_quality_override(client_id).await; let quality = compression.get_audio_quality(client_id).await; assert_eq!(quality, AudioQuality::Medium); // Qualité par défaut } #[test] fn test_brotli_compression() { let compressor = BrotliCompressor::new(6); let data = b"Hello, world! This is a test string for compression."; let compressed = compressor.compress(data).unwrap(); assert!(compressed.len() < data.len()); let decompressed = compressor.decompress(&compressed).unwrap(); assert_eq!(decompressed, data); } }