use std::{
collections::BTreeMap,
hash::{Hash, Hasher},
time::{Duration, SystemTime, SystemTimeError},
};
use derive_more::with_trait::{Display, From};
use serde::{Deserialize, Serialize};
#[derive(
Clone,
Debug,
Deserialize,
Display,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
#[serde(untagged)]
pub enum NonExhaustive<T> {
Known(T),
#[display("Unknown: {_0}")]
Unknown(String),
}
#[derive(
Clone, Debug, Deserialize, Display, Eq, From, Hash, PartialEq, Serialize,
)]
#[from(forward)]
pub struct StatId(pub String);
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
pub struct RtcStat {
pub id: StatId,
pub timestamp: HighResTimeStamp,
#[serde(flatten)]
pub stats: RtcStatsType,
}
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum RtcStatsType {
Codec(Box<RtcCodecStats>),
InboundRtp(Box<RtcInboundRtpStreamStats>),
OutboundRtp(Box<RtcOutboundRtpStreamStats>),
RemoteInboundRtp(Box<RtcRemoteInboundRtpStreamStats>),
RemoteOutboundRtp(Box<RtcRemoteOutboundRtpStreamStats>),
MediaSource(Box<RtcMediaSourceStats>),
MediaPlayout(Box<RtcAudioPlayoutStats>),
PeerConnection(Box<RtcPeerConnectionStats>),
DataChannel(Box<RtcDataChannelStats>),
Transport(Box<RtcTransportStats>),
CandidatePair(Box<RtcIceCandidatePairStats>),
LocalCandidate(Box<RtcIceCandidateStats>),
RemoteCandidate(Box<RtcIceCandidateStats>),
Certificate(Box<RtcCertificateStats>),
#[serde(other)]
Other,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcRtpStreamStats {
pub ssrc: Option<u32>,
pub kind: String,
pub transport_id: Option<String>,
pub codec_id: Option<String>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RtcReceivedRtpStreamStats {
#[serde(flatten)]
pub stream: RtcRtpStreamStats,
pub packets_received: Option<u64>,
pub packets_received_with_ect1: Option<u64>,
pub packets_received_with_ce: Option<u64>,
pub packets_reported_as_lost: Option<u64>,
pub packets_reported_as_lost_but_recovered: Option<u64>,
pub packets_lost: Option<i64>,
pub jitter: Option<Double>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct RtcSentRtpStreamStats {
#[serde(flatten)]
pub stream: RtcRtpStreamStats,
pub packets_sent: Option<u64>,
pub bytes_sent: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcCodecStats {
pub payload_type: u32,
pub transport_id: String,
pub mime_type: String,
pub clock_rate: Option<u32>,
pub channels: Option<u32>,
pub sdp_fmtp_line: Option<String>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcInboundRtpStreamStats {
#[serde(flatten)]
pub received_stream: RtcReceivedRtpStreamStats,
#[serde(flatten)]
pub media_specific: InboundRtpMediaType,
pub track_identifier: Option<String>,
pub mid: Option<String>,
pub remote_id: Option<String>,
pub bytes_received: Option<u64>,
pub jitter_buffer_emitted_count: Option<u64>,
pub jitter_buffer_delay: Option<Double>,
pub jitter_buffer_target_delay: Option<Double>,
pub jitter_buffer_minimum_delay: Option<Double>,
pub header_bytes_received: Option<u64>,
pub packets_discarded: Option<u64>,
pub last_packet_received_timestamp: Option<HighResTimeStamp>,
pub estimated_playout_timestamp: Option<HighResTimeStamp>,
pub fec_bytes_received: Option<u64>,
pub fec_packets_received: Option<u64>,
pub fec_packets_discarded: Option<u64>,
pub total_processing_delay: Option<Double>,
pub nack_count: Option<u32>,
pub retransmitted_packets_received: Option<u64>,
pub retransmitted_bytes_received: Option<u64>,
pub rtx_ssrc: Option<u32>,
pub fec_ssrc: Option<u32>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcOutboundRtpStreamStats {
#[serde(flatten)]
pub sent_stream: RtcSentRtpStreamStats,
#[serde(flatten)]
pub media_specific: OutboundRtpMediaType,
pub mid: Option<String>,
pub media_source_id: Option<String>,
pub remote_id: Option<String>,
pub header_bytes_sent: Option<u64>,
pub retransmitted_packets_sent: Option<u64>,
pub retransmitted_bytes_sent: Option<u64>,
pub rtx_ssrc: Option<u32>,
pub target_bitrate: Option<Double>,
pub total_packet_send_delay: Option<Double>,
pub nack_count: Option<u32>,
pub active: Option<bool>,
pub packets_sent_with_ect1: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcRemoteInboundRtpStreamStats {
#[serde(flatten)]
pub received_stream: RtcReceivedRtpStreamStats,
pub local_id: Option<String>,
pub round_trip_time: Option<Double>,
pub total_round_trip_time: Option<Double>,
pub fraction_lost: Option<Double>,
pub round_trip_time_measurements: Option<u64>,
pub packets_with_bleached_ect1_marking: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcRemoteOutboundRtpStreamStats {
#[serde(flatten)]
pub sent_stream: RtcSentRtpStreamStats,
pub local_id: Option<String>,
pub remote_timestamp: Option<HighResTimeStamp>,
pub reports_sent: Option<u64>,
pub round_trip_time: Option<Double>,
pub total_round_trip_time: Option<Double>,
pub round_trip_time_measurements: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcMediaSourceStats {
pub track_identifier: Option<String>,
#[serde(flatten)]
pub kind: MediaSourceKind,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Hash, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RtcAudioPlayoutStats {
pub kind: Option<String>,
pub synthesized_samples_duration: Option<Double>,
pub synthesized_samples_events: Option<u32>,
pub total_samples_duration: Option<Double>,
pub total_playout_delay: Option<Double>,
pub total_samples_count: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcPeerConnectionStats {
pub data_channels_opened: Option<u32>,
pub data_channels_closed: Option<u32>,
}
pub type RtcDataChannelState = NonExhaustive<KnownRtcDataChannelState>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum KnownRtcDataChannelState {
#[display("connecting")]
Connecting,
#[display("open")]
Open,
#[display("closing")]
Closing,
#[display("closed")]
Closed,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcDataChannelStats {
pub label: Option<String>,
pub protocol: Option<String>,
pub data_channel_identifier: Option<u16>,
pub state: Option<RtcDataChannelState>,
pub messages_sent: Option<u32>,
pub bytes_sent: Option<u64>,
pub messages_received: Option<u32>,
pub bytes_received: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcTransportStats {
pub packets_sent: Option<u64>,
pub packets_received: Option<u64>,
pub bytes_sent: Option<u64>,
pub bytes_received: Option<u64>,
pub ice_role: Option<RtcIceRole>,
pub ice_local_username_fragment: Option<String>,
pub ice_state: Option<RtcIceTransportState>,
pub dtls_state: Option<RtcDtlsTransportState>,
pub selected_candidate_pair_id: Option<String>,
pub local_certificate_id: Option<String>,
pub remote_certificate_id: Option<String>,
pub tls_version: Option<String>,
pub dtls_cipher: Option<String>,
pub dtls_role: Option<RtcDtlsRole>,
pub srtp_cipher: Option<String>,
pub ccfb_messages_sent: Option<u32>,
pub ccfb_messages_received: Option<u32>,
pub selected_candidate_pair_changes: Option<u32>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcIceCandidatePairStats {
pub transport_id: Option<String>,
pub local_candidate_id: Option<String>,
pub remote_candidate_id: Option<String>,
pub state: RtcStatsIceCandidatePairState,
pub nominated: Option<bool>,
pub priority: Option<u64>,
pub packets_sent: Option<u64>,
pub packets_received: Option<u64>,
pub bytes_sent: Option<u64>,
pub bytes_received: Option<u64>,
pub last_packet_sent_timestamp: Option<HighResTimeStamp>,
pub last_packet_received_timestamp: Option<HighResTimeStamp>,
pub total_round_trip_time: Option<Double>,
pub current_round_trip_time: Option<Double>,
pub available_outgoing_bitrate: Option<Double>,
pub available_incoming_bitrate: Option<Double>,
pub requests_received: Option<u64>,
pub requests_sent: Option<u64>,
pub responses_received: Option<u64>,
pub responses_sent: Option<u64>,
pub consent_requests_sent: Option<u64>,
pub packets_discarded_on_send: Option<u32>,
pub bytes_discarded_on_send: Option<u64>,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcIceCandidateStats {
pub transport_id: Option<String>,
pub address: Option<String>,
pub port: Option<i32>,
pub protocol: Option<String>,
pub candidate_type: RtcIceCandidateType,
pub priority: Option<i32>,
pub url: Option<String>,
pub relay_protocol: Option<IceServerTransportProtocol>,
pub foundation: Option<String>,
pub related_address: Option<String>,
pub related_port: Option<i32>,
pub username_fragment: Option<String>,
pub tcp_type: Option<RtcIceTcpCandidateType>,
pub network_type: Option<String>,
}
pub type IceServerTransportProtocol =
NonExhaustive<KnownIceServerTransportProtocol>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "lowercase")]
pub enum KnownIceServerTransportProtocol {
#[display("udp")]
Udp,
#[display("tcp")]
Tcp,
#[display("tls")]
Tls,
}
pub type RtcIceTcpCandidateType = NonExhaustive<KnownRtcIceTcpCandidateType>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum KnownRtcIceTcpCandidateType {
#[display("active")]
Active,
#[display("passive")]
Passive,
#[display("so")]
So,
}
#[expect(clippy::module_name_repetitions, reason = "spec compliance")]
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcCertificateStats {
pub fingerprint: String,
pub fingerprint_algorithm: String,
pub base64_certificate: String,
pub issuer_certificate_id: Option<String>,
}
pub type RtcIceRole = NonExhaustive<KnownRtcIceRole>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
pub enum KnownRtcIceRole {
#[display("unknown")]
Unknown,
#[display("controlling")]
Controlling,
#[display("controlled")]
Controlled,
}
pub type RtcDtlsTransportState = NonExhaustive<KnownRtcDtlsTransportState>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
pub enum KnownRtcDtlsTransportState {
#[display("new")]
New,
#[display("connecting")]
Connecting,
#[display("connected")]
Connected,
#[display("closed")]
Closed,
#[display("failed")]
Failed,
}
pub type RtcIceTransportState = NonExhaustive<KnownRtcIceTransportState>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
pub enum KnownRtcIceTransportState {
#[display("closed")]
Closed,
#[display("failed")]
Failed,
#[display("disconnected")]
Disconnected,
#[display("new")]
New,
#[display("checking")]
Checking,
#[display("completed")]
Completed,
#[display("connected")]
Connected,
}
pub type RtcDtlsRole = NonExhaustive<KnownRtcDtlsRole>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
pub enum KnownRtcDtlsRole {
#[display("client")]
Client,
#[display("server")]
Server,
#[display("unknown")]
Unknown,
}
pub type RtcStatsIceCandidatePairState =
NonExhaustive<KnownRtcStatsIceCandidatePairState>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum KnownRtcStatsIceCandidatePairState {
#[display("frozen")]
Frozen,
#[display("waiting")]
Waiting,
#[display("in-progress")]
InProgress,
#[display("failed")]
Failed,
#[display("succeeded")]
Succeeded,
#[display("cancelled")]
Cancelled,
}
pub type RtcIceCandidateType = NonExhaustive<KnownRtcIceCandidateType>;
#[derive(
Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize,
)]
#[serde(rename_all = "lowercase")]
pub enum KnownRtcIceCandidateType {
#[display("host")]
Host,
#[display("srflx")]
Srflx,
#[display("prflx")]
Prflx,
#[display("relay")]
Relay,
}
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(tag = "mediaType", rename_all = "camelCase")]
pub enum InboundRtpMediaType {
#[serde(rename_all = "camelCase")]
Audio {
total_samples_received: Option<u64>,
concealed_samples: Option<u64>,
silent_concealed_samples: Option<u64>,
concealment_events: Option<u64>,
inserted_samples_for_deceleration: Option<u64>,
removed_samples_for_acceleration: Option<u64>,
audio_level: Option<Double>,
total_audio_energy: Option<Double>,
total_samples_duration: Option<Double>,
playout_id: Option<String>,
},
#[serde(rename_all = "camelCase")]
Video {
frames_decoded: Option<u32>,
key_frames_decoded: Option<u32>,
frames_rendered: Option<u32>,
frames_dropped: Option<u32>,
frame_width: Option<u32>,
frame_height: Option<u32>,
frames_per_second: Option<Double>,
qp_sum: Option<u64>,
total_decode_time: Option<Double>,
total_inter_frame_delay: Option<Double>,
total_squared_inter_frame_delay: Option<Double>,
pause_count: Option<u32>,
total_pauses_duration: Option<Double>,
freeze_count: Option<u32>,
total_freezes_duration: Option<Double>,
fir_count: Option<u32>,
pli_count: Option<u32>,
frames_received: Option<u32>,
decoder_implementation: Option<String>,
power_efficient_decoder: Option<bool>,
frames_assembled_from_multiple_packets: Option<u32>,
total_assembly_time: Option<Double>,
total_corruption_probability: Option<Double>,
total_squared_corruption_probability: Option<Double>,
corruption_measurements: Option<u64>,
},
}
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(tag = "mediaType", rename_all = "camelCase")]
pub enum OutboundRtpMediaType {
#[serde(rename_all = "camelCase")]
Audio {
total_samples_sent: Option<u64>,
voice_activity_flag: Option<bool>,
},
#[serde(rename_all = "camelCase")]
Video(Box<RtcOutboundRtpStreamVideo>),
}
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcOutboundRtpStreamVideo {
pub rid: Option<String>,
pub encoding_index: Option<u32>,
pub total_encoded_bytes_target: Option<u64>,
pub frame_width: Option<u32>,
pub frame_height: Option<u32>,
pub frames_per_second: Option<Double>,
pub frames_sent: Option<u32>,
pub huge_frames_sent: Option<u32>,
pub frames_encoded: Option<u32>,
pub key_frames_encoded: Option<u32>,
pub qp_sum: Option<u64>,
pub psnr_sum: Option<BTreeMap<String, Double>>,
pub psnr_measurements: Option<u64>,
pub total_encode_time: Option<Double>,
pub fir_count: Option<u32>,
pub pli_count: Option<u32>,
pub encoder_implementation: Option<String>,
pub power_efficient_encoder: Option<bool>,
pub quality_limitation_reason: Option<RtcQualityLimitationReason>,
pub quality_limitation_durations:
Option<BTreeMap<RtcQualityLimitationReason, Double>>,
pub quality_limitation_resolution_changes: Option<u32>,
pub scalability_mode: Option<String>,
}
pub type RtcQualityLimitationReason =
NonExhaustive<KnownRtcQualityLimitationReason>;
#[derive(
Clone,
Copy,
Debug,
Deserialize,
Display,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum KnownRtcQualityLimitationReason {
#[display("none")]
None,
#[display("cpu")]
Cpu,
#[display("bandwidth")]
Bandwidth,
#[display("other")]
Other,
}
#[serde_with::skip_serializing_none]
#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum MediaSourceKind {
#[serde(rename_all = "camelCase")]
Video {
width: Option<u32>,
height: Option<u32>,
frames: Option<u32>,
frames_per_second: Option<Double>,
},
#[serde(rename_all = "camelCase")]
Audio {
audio_level: Option<Double>,
total_audio_energy: Option<Double>,
total_samples_duration: Option<Double>,
echo_return_loss: Option<Double>,
echo_return_loss_enhancement: Option<Double>,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct HighResTimeStamp(pub f64);
impl From<HighResTimeStamp> for SystemTime {
fn from(timestamp: HighResTimeStamp) -> Self {
Self::UNIX_EPOCH + Duration::from_secs_f64(timestamp.0 / 1000.0)
}
}
impl TryFrom<SystemTime> for HighResTimeStamp {
type Error = SystemTimeError;
fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
Ok(Self(
time.duration_since(SystemTime::UNIX_EPOCH)?.as_secs_f64() * 1000.0,
))
}
}
impl Hash for HighResTimeStamp {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_string().hash(state);
}
}
impl PartialEq for HighResTimeStamp {
fn eq(&self, other: &Self) -> bool {
self.0.to_string().eq(&other.0.to_string())
}
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct Double(pub f64);
impl Hash for Double {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_string().hash(state);
}
}
impl PartialEq for Double {
fn eq(&self, other: &Self) -> bool {
self.0.to_string().eq(&other.0.to_string())
}
}