use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, warn};
use crate::api::common::error::MediaTransportError;
use crate::api::common::stats::{MediaStats, QualityLevel, StreamStats};
use crate::api::server::transport::core::connection::ClientConnection;
pub fn estimate_quality_level(media_stats: &MediaStats) -> QualityLevel {
if media_stats.streams.is_empty() {
return QualityLevel::Unknown;
}
let mut quality_sum = 0;
let mut stream_count = 0;
for stream in media_stats.streams.values() {
let quality = calculate_stream_quality(stream);
let quality_value = match quality {
QualityLevel::Excellent => 5,
QualityLevel::Good => 4,
QualityLevel::Fair => 3,
QualityLevel::Poor => 2,
QualityLevel::Bad => 1,
QualityLevel::Unknown => 0,
};
if quality_value > 0 {
quality_sum += quality_value;
stream_count += 1;
}
}
if stream_count == 0 {
return QualityLevel::Unknown;
}
let avg_quality = quality_sum as f32 / stream_count as f32;
match avg_quality {
x if x >= 4.5 => QualityLevel::Excellent,
x if x >= 3.5 => QualityLevel::Good,
x if x >= 2.5 => QualityLevel::Fair,
x if x >= 1.5 => QualityLevel::Poor,
_ => QualityLevel::Bad,
}
}
fn calculate_stream_quality(stream: &StreamStats) -> QualityLevel {
if stream.packet_count < 10 {
return QualityLevel::Unknown;
}
let packet_loss = stream.fraction_lost;
let jitter_ms = stream.jitter_ms;
let rtt_ms = stream.rtt_ms.unwrap_or(0.0);
let packet_loss_score = if packet_loss <= 0.001 { 100.0
} else if packet_loss <= 0.005 { 95.0
} else if packet_loss <= 0.01 { 90.0
} else if packet_loss <= 0.03 { 70.0
} else if packet_loss <= 0.05 { 50.0
} else if packet_loss <= 0.10 { 30.0
} else if packet_loss <= 0.20 { 10.0
} else { 0.0
};
let jitter_score = if jitter_ms <= 1.0 {
100.0
} else if jitter_ms <= 5.0 {
90.0
} else if jitter_ms <= 10.0 {
80.0
} else if jitter_ms <= 20.0 {
60.0
} else if jitter_ms <= 30.0 {
40.0
} else if jitter_ms <= 50.0 {
20.0
} else {
0.0
};
let rtt_score = if rtt_ms == 0.0 { 50.0 } else if rtt_ms <= 20.0 {
100.0
} else if rtt_ms <= 50.0 {
90.0
} else if rtt_ms <= 100.0 {
80.0
} else if rtt_ms <= 150.0 {
60.0
} else if rtt_ms <= 200.0 {
40.0
} else if rtt_ms <= 300.0 {
20.0
} else {
0.0
};
let quality_score = packet_loss_score * 0.5 + jitter_score * 0.3 + rtt_score * 0.2;
match quality_score {
x if x >= 90.0 => QualityLevel::Excellent,
x if x >= 75.0 => QualityLevel::Good,
x if x >= 60.0 => QualityLevel::Fair,
x if x >= 40.0 => QualityLevel::Poor,
_ => QualityLevel::Bad,
}
}
pub async fn get_stats(
clients: &Arc<DashMap<String, ClientConnection>>,
) -> Result<MediaStats, MediaTransportError> {
let clients_guard = clients.read().await;
let mut agg_stats = MediaStats::default();
if let Some(client) = clients_guard.values().next() {
agg_stats.session_duration = std::time::Duration::from_secs(0);
}
for client in clients_guard.values() {
if !client.connected {
continue;
}
let session = client.session.lock().await;
let rtp_stats = session.get_stats();
let mut stream_stats = StreamStats {
direction: crate::api::common::stats::Direction::Inbound,
ssrc: session.get_ssrc(),
media_type: crate::api::common::frame::MediaFrameType::Audio, packet_count: rtp_stats.packets_received,
byte_count: rtp_stats.bytes_received,
packets_lost: rtp_stats.packets_lost,
fraction_lost: if rtp_stats.packets_received > 0 {
rtp_stats.packets_lost as f32 / rtp_stats.packets_received as f32
} else {
0.0
},
jitter_ms: rtp_stats.jitter_ms as f32,
rtt_ms: None, mos: None, remote_addr: client.address,
bitrate_bps: 0, discard_rate: 0.0,
quality: QualityLevel::Unknown,
available_bandwidth_bps: None,
};
stream_stats.quality = calculate_stream_quality(&stream_stats);
agg_stats.streams.insert(stream_stats.ssrc, stream_stats);
let now = std::time::SystemTime::now();
let session_duration_secs = client.created_at.elapsed().unwrap_or_default().as_secs_f64();
if session_duration_secs > 0.0 {
let bitrate = (rtp_stats.bytes_received as f64 * 8.0 / session_duration_secs) as u32;
agg_stats.downstream_bandwidth_bps += bitrate;
}
}
agg_stats.quality = estimate_quality_level(&agg_stats);
Ok(agg_stats)
}
pub async fn get_client_stats(
client_id: &str,
clients: &Arc<DashMap<String, ClientConnection>>,
) -> Result<MediaStats, MediaTransportError> {
let clients_guard = clients.read().await;
let client = clients_guard.get(client_id)
.ok_or_else(|| MediaTransportError::ClientNotFound(client_id.to_string()))?;
if !client.connected {
return Err(MediaTransportError::ClientNotConnected(client_id.to_string()));
}
let session = client.session.lock().await;
let rtp_stats = session.get_stats();
let mut media_stats = MediaStats::default();
media_stats.session_duration = client.created_at.elapsed().unwrap_or_default();
let session_duration_secs = media_stats.session_duration.as_secs_f64();
let bitrate = if session_duration_secs > 0.0 {
(rtp_stats.bytes_received as f64 * 8.0 / session_duration_secs) as u32
} else {
0
};
let mut stream_stats = StreamStats {
direction: crate::api::common::stats::Direction::Inbound,
ssrc: session.get_ssrc(),
media_type: crate::api::common::frame::MediaFrameType::Audio, packet_count: rtp_stats.packets_received,
byte_count: rtp_stats.bytes_received,
packets_lost: rtp_stats.packets_lost,
fraction_lost: if rtp_stats.packets_received > 0 {
rtp_stats.packets_lost as f32 / rtp_stats.packets_received as f32
} else {
0.0
},
jitter_ms: rtp_stats.jitter_ms as f32,
rtt_ms: None, mos: None, remote_addr: client.address,
bitrate_bps: bitrate,
discard_rate: 0.0,
quality: QualityLevel::Unknown,
available_bandwidth_bps: None,
};
stream_stats.quality = calculate_stream_quality(&stream_stats);
media_stats.streams.insert(stream_stats.ssrc, stream_stats.clone());
media_stats.downstream_bandwidth_bps = bitrate;
media_stats.quality = calculate_stream_quality(&stream_stats);
Ok(media_stats)
}