use crate::capability::CodecInfo;
use crate::connection::Direction;
use crate::data::DataMessage;
use crate::error::Result;
use crate::ids::{ConnectionId, StreamId};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::sync::Arc;
use tokio::sync::mpsc;
#[must_use = "dropping an uncommitted media receiver reservation restores it"]
pub struct MediaReceiverReservation {
receiver: Option<mpsc::Receiver<MediaFrame>>,
restore: Option<Box<dyn FnOnce(mpsc::Receiver<MediaFrame>) + Send + 'static>>,
on_commit: Option<Box<dyn FnOnce() + Send + 'static>>,
}
impl MediaReceiverReservation {
pub fn new(
receiver: mpsc::Receiver<MediaFrame>,
restore: impl FnOnce(mpsc::Receiver<MediaFrame>) + Send + 'static,
) -> Self {
Self {
receiver: Some(receiver),
restore: Some(Box::new(restore)),
on_commit: None,
}
}
pub fn with_commit_hook(mut self, on_commit: impl FnOnce() + Send + 'static) -> Self {
self.on_commit = Some(Box::new(on_commit));
self
}
pub fn commit(mut self) -> mpsc::Receiver<MediaFrame> {
self.restore.take();
if let Some(on_commit) = self.on_commit.take() {
on_commit();
}
self.receiver
.take()
.expect("media receiver reservation commits exactly once")
}
}
impl Drop for MediaReceiverReservation {
fn drop(&mut self) {
if let (Some(receiver), Some(restore)) = (self.receiver.take(), self.restore.take()) {
restore(receiver);
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum StreamKind {
Audio,
Video,
Data,
}
#[derive(Clone)]
pub struct MediaFrame {
pub stream_id: StreamId,
pub kind: StreamKind,
pub payload: Bytes,
pub timestamp_rtp: u32,
pub captured_at: DateTime<Utc>,
pub payload_type: Option<u8>,
}
impl fmt::Debug for MediaFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MediaFrame")
.field("stream_id", &self.stream_id)
.field("kind", &self.kind)
.field("payload_bytes", &self.payload.len())
.field("timestamp_rtp", &self.timestamp_rtp)
.field("captured_at", &self.captured_at)
.field("payload_type", &self.payload_type)
.finish()
}
}
#[derive(Clone, Debug, Default)]
pub struct QualitySnapshot {
pub jitter_ms: f32,
pub packet_loss_pct: f32,
pub mos: Option<f32>,
}
pub enum BridgedDataMessageDecision {
Forward(DataMessage),
Drop,
}
impl fmt::Debug for BridgedDataMessageDecision {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Forward(message) => formatter.debug_tuple("Forward").field(message).finish(),
Self::Drop => formatter.write_str("Drop"),
}
}
}
pub trait DataMessageBridgePolicy: Send + Sync {
fn decide(
&self,
source: &ConnectionId,
target: &ConnectionId,
message: DataMessage,
) -> BridgedDataMessageDecision;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PassThroughDataMessageBridgePolicy;
impl DataMessageBridgePolicy for PassThroughDataMessageBridgePolicy {
fn decide(
&self,
_source: &ConnectionId,
_target: &ConnectionId,
message: DataMessage,
) -> BridgedDataMessageDecision {
BridgedDataMessageDecision::Forward(message)
}
}
#[async_trait::async_trait]
pub trait MediaStream: Send + Sync {
fn id(&self) -> StreamId;
fn kind(&self) -> StreamKind;
fn codec(&self) -> CodecInfo;
fn direction(&self) -> Direction;
fn source_ready(&self) -> bool {
true
}
fn frames_in(&self) -> mpsc::Receiver<MediaFrame>;
fn try_frames_in(&self) -> Result<mpsc::Receiver<MediaFrame>> {
Ok(self.frames_in())
}
fn reserve_frames_in(&self) -> Result<MediaReceiverReservation> {
Err(crate::error::RvoipError::NotImplemented(
"MediaStream::reserve_frames_in",
))
}
fn frames_out(&self) -> mpsc::Sender<MediaFrame>;
fn try_frames_out(&self) -> Result<mpsc::Sender<MediaFrame>> {
Ok(self.frames_out())
}
fn quality_snapshot(&self) -> QualitySnapshot;
async fn close(self: Arc<Self>) -> Result<()>;
}
#[derive(Clone)]
pub struct MediaStreamHandle(pub Arc<dyn MediaStream>);
impl MediaStreamHandle {
pub fn new(stream: Arc<dyn MediaStream>) -> Self {
Self(stream)
}
pub fn id(&self) -> StreamId {
self.0.id()
}
pub fn kind(&self) -> StreamKind {
self.0.kind()
}
pub fn stream(&self) -> &Arc<dyn MediaStream> {
&self.0
}
}
impl fmt::Debug for MediaStreamHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MediaStreamHandle")
.field("id", &self.0.id())
.field("kind", &self.0.kind())
.finish()
}
}
#[cfg(test)]
mod diagnostic_tests {
use super::*;
#[test]
fn media_frame_debug_reports_shape_without_packet_bytes() {
const CANARY: &[u8] = b"media-frame-canary\r\nAuthorization: exposed";
let frame = MediaFrame {
stream_id: StreamId::from_string("stream-canary"),
kind: StreamKind::Audio,
payload: Bytes::from_static(CANARY),
timestamp_rtp: 123,
captured_at: Utc::now(),
payload_type: Some(111),
};
let debug = format!("{frame:?}");
assert!(!debug.contains("media-frame-canary"));
assert!(!debug.contains("stream-canary"));
assert!(debug.contains(&format!("payload_bytes: {}", CANARY.len())));
}
}