use async_trait::async_trait;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use crate::api::common::error::StatsError;
use crate::api::common::frame::MediaFrameType;
mod stats_collector_impl;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QualityLevel {
Excellent,
Good,
Fair,
Poor,
Bad,
Unknown,
}
impl Default for QualityLevel {
fn default() -> Self {
QualityLevel::Unknown
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Inbound,
Outbound,
}
#[derive(Debug, Clone)]
pub struct StreamStats {
pub direction: Direction,
pub ssrc: u32,
pub media_type: MediaFrameType,
pub packet_count: u64,
pub byte_count: u64,
pub packets_lost: u64,
pub fraction_lost: f32,
pub jitter_ms: f32,
pub rtt_ms: Option<f32>,
pub mos: Option<f32>,
pub remote_addr: SocketAddr,
pub bitrate_bps: u32,
pub discard_rate: f32,
pub quality: QualityLevel,
pub available_bandwidth_bps: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct MediaStats {
pub timestamp: SystemTime,
pub session_duration: Duration,
pub streams: HashMap<u32, StreamStats>,
pub quality: QualityLevel,
pub upstream_bandwidth_bps: u32,
pub downstream_bandwidth_bps: u32,
pub available_bandwidth_bps: Option<u32>,
pub network_rtt_ms: Option<f32>,
}
impl Default for MediaStats {
fn default() -> Self {
Self {
timestamp: SystemTime::now(),
session_duration: Duration::from_secs(0),
streams: HashMap::new(),
quality: QualityLevel::default(),
upstream_bandwidth_bps: 0,
downstream_bandwidth_bps: 0,
available_bandwidth_bps: None,
network_rtt_ms: None,
}
}
}
#[async_trait]
pub trait MediaStatsCollector: Send + Sync {
async fn get_stats(&self) -> Result<MediaStats, StatsError>;
async fn get_stream_stats(&self, ssrc: u32) -> Result<StreamStats, StatsError>;
async fn reset(&self);
async fn on_quality_change(&self, callback: Box<dyn Fn(QualityLevel) + Send + Sync>);
async fn on_bandwidth_update(&self, callback: Box<dyn Fn(u32) + Send + Sync>);
}
pub struct StatsFactory;
impl StatsFactory {
pub fn create_collector() -> Arc<dyn MediaStatsCollector> {
stats_collector_impl::DefaultMediaStatsCollector::new()
}
}
pub struct QualityUtils;
impl QualityUtils {
pub fn mos_to_quality(mos: f32) -> QualityLevel {
match mos {
x if x >= 4.3 => QualityLevel::Excellent,
x if x >= 3.6 => QualityLevel::Good,
x if x >= 2.6 => QualityLevel::Fair,
x if x >= 1.6 => QualityLevel::Poor,
x if x >= 1.0 => QualityLevel::Bad,
_ => QualityLevel::Unknown,
}
}
pub fn r_factor_to_mos(r: f32) -> f32 {
if r < 0.0 {
return 1.0;
}
let mos = if r < 100.0 {
1.0 + 0.035 * r + 0.000007 * r * (r - 60.0) * (100.0 - r)
} else {
4.5
};
mos.max(1.0).min(5.0)
}
pub fn calculate_quality(
packet_loss: f32,
jitter_ms: f32,
rtt_ms: Option<f32>,
) -> QualityLevel {
let mut quality = QualityLevel::Excellent;
quality = match packet_loss {
x if x < 0.01 => quality,
x if x < 0.03 => quality.min(QualityLevel::Good),
x if x < 0.08 => quality.min(QualityLevel::Fair),
x if x < 0.15 => quality.min(QualityLevel::Poor),
_ => QualityLevel::Bad,
};
quality = match jitter_ms {
x if x < 10.0 => quality,
x if x < 30.0 => quality.min(QualityLevel::Good),
x if x < 50.0 => quality.min(QualityLevel::Fair),
x if x < 100.0 => quality.min(QualityLevel::Poor),
_ => QualityLevel::Bad,
};
if let Some(rtt) = rtt_ms {
quality = match rtt {
x if x < 100.0 => quality,
x if x < 200.0 => quality.min(QualityLevel::Good),
x if x < 400.0 => quality.min(QualityLevel::Fair),
x if x < 1000.0 => quality.min(QualityLevel::Poor),
_ => QualityLevel::Bad,
};
}
quality
}
}
impl Ord for QualityLevel {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let self_val = match self {
QualityLevel::Excellent => 5,
QualityLevel::Good => 4,
QualityLevel::Fair => 3,
QualityLevel::Poor => 2,
QualityLevel::Bad => 1,
QualityLevel::Unknown => 0,
};
let other_val = match other {
QualityLevel::Excellent => 5,
QualityLevel::Good => 4,
QualityLevel::Fair => 3,
QualityLevel::Poor => 2,
QualityLevel::Bad => 1,
QualityLevel::Unknown => 0,
};
self_val.cmp(&other_val)
}
}
impl PartialOrd for QualityLevel {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}