use std::{
sync::{
Arc, Mutex,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
use crate::pp_log::{PpLog, pp_error, pp_info};
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender, TrySendError, select};
use str0m::{
change::{SdpAnswer, SdpOffer},
format::Codec,
media::{Direction, MediaKind, Mid},
};
use crate::{
buffer::MediaBuffer,
bus::{Bus, BusEvent},
contract::{InputContract, OutputContract, PortContract},
control::{
ControlMsg, ControlReceiver, RequestKind, apply_finish, apply_one, drain_control,
wait_out_pause,
},
element::{Element, ElementType, Sink, Source, SourceElement, element_pp_log},
error::Result,
pad::SrcPad,
};
use super::{
command::{Command, TrackId, WebRtcError},
stream_info::WebRtcStreamInfo,
};
fn packet_kind(kind: MediaKind) -> crate::contract::MediaKind {
match kind {
MediaKind::Audio => crate::contract::MediaKind::AudioPacket,
MediaKind::Video => crate::contract::MediaKind::VideoPacket,
}
}
pub enum TrackEndpoints {
Send(WebRtcTrackSink),
Recv(WebRtcTrackSource),
SendRecv(WebRtcTrackSink, WebRtcTrackSource),
Inactive,
}
pub struct AttachedTrack {
pub id: TrackId,
pub mid: Mid,
pub kind: MediaKind,
pub endpoints: TrackEndpoints,
}
#[derive(Clone)]
pub struct WebRtcHandle {
pub(super) next_id: Arc<AtomicU64>,
pub(super) command_tx: Sender<Command>,
pub(super) new_track_rx: Receiver<AttachedTrack>,
}
impl WebRtcHandle {
pub fn add_track(
&self,
kind: MediaKind,
direction: Direction,
codec: Codec,
) -> Result<TrackId> {
let id = TrackId(self.next_id.fetch_add(1, Ordering::Relaxed));
self.command_tx
.send(Command::AddTrack(id, kind, direction, codec))
.map_err(|_| WebRtcError::Closed)?;
Ok(id)
}
pub fn next_track(&self) -> Result<AttachedTrack> {
self.new_track_rx
.recv()
.map_err(|_| WebRtcError::Closed.into())
}
pub fn set_answer(&self, answer: SdpAnswer) {
let _ = self.command_tx.send(Command::SetAnswer(answer));
}
pub fn accept_remote_offer(&self, offer: SdpOffer) -> Result<SdpAnswer> {
let (reply_tx, reply_rx) = crossbeam_channel::bounded(0);
self.command_tx
.send(Command::AcceptOffer(offer, reply_tx))
.map_err(|_| WebRtcError::Closed)?;
reply_rx
.recv()
.map_err(|_| WebRtcError::Closed)?
.map_err(Into::into)
}
}
pub struct WebRtcTrackSink {
pp_log: PpLog,
id: TrackId,
kind: MediaKind,
codec: Option<Codec>,
negotiated_codecs: Arc<Mutex<Vec<Codec>>>,
command_tx: Sender<Command>,
timestamp_offset: Option<i64>,
}
impl WebRtcTrackSink {
pub(super) fn new(
id: TrackId,
kind: MediaKind,
codec: Option<Codec>,
negotiated_codecs: Arc<Mutex<Vec<Codec>>>,
command_tx: Sender<Command>,
) -> Self {
Self {
id,
kind,
codec,
negotiated_codecs,
command_tx,
timestamp_offset: None,
pp_log: element_pp_log(
ElementType::WebRtcPeer,
&format!("webrtc-track-{}", id.0),
None,
),
}
}
pub fn negotiated_codecs(&self) -> Vec<Codec> {
self.negotiated_codecs.lock().unwrap().clone()
}
pub fn set_codec(&mut self, codec: Codec) -> Result<()> {
let negotiated = self.negotiated_codecs();
if !negotiated.contains(&codec) {
return Err(WebRtcError::OutboundCodecNotNegotiated {
track_id: self.id,
codec,
negotiated,
}
.into());
}
self.codec = Some(codec);
Ok(())
}
}
impl Element for WebRtcTrackSink {
fn name(&self) -> Arc<str> {
format!("webrtc-track-{}", self.id.0).into()
}
fn element_type(&self) -> ElementType {
ElementType::WebRtcPeer
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl Sink for WebRtcTrackSink {
fn input_contract(&self) -> InputContract {
InputContract::Fixed(PortContract::packet(packet_kind(self.kind)))
}
fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
if !matches!(buf, MediaBuffer::Packet(_) | MediaBuffer::Eos) {
let kind = match buf {
MediaBuffer::Video(_) => "Video",
MediaBuffer::Audio(_) => "Audio",
MediaBuffer::Packet(_) | MediaBuffer::Eos => unreachable!("matched above"),
};
pp_error!(self, "unsupported buffer: {kind}");
return Err(WebRtcError::UnsupportedBuffer(kind).into());
}
if matches!(buf, MediaBuffer::Packet(_)) && self.codec.is_none() {
pp_error!(self, "outbound codec is not declared");
return Err(WebRtcError::OutboundCodecNotDeclared(self.id).into());
}
if let MediaBuffer::Packet(_) = &buf {
let codec = self.codec.expect("checked above");
let negotiated = self.negotiated_codecs();
if !negotiated.contains(&codec) {
pp_error!(self, "outbound codec {codec:?} is not negotiated");
return Err(WebRtcError::OutboundCodecNotNegotiated {
track_id: self.id,
codec,
negotiated,
}
.into());
}
}
let buf = self.normalize_packet_timestamp(buf)?;
match self
.command_tx
.try_send(Command::Push(self.id, self.codec, buf))
{
Ok(()) | Err(TrySendError::Full(_)) => Ok(()),
Err(TrySendError::Disconnected(_)) => {
pp_error!(self, "WebRtcPeer::run gone — track is dead");
Err(WebRtcError::Closed.into())
}
}
}
fn control(&mut self, _msg: ControlMsg) -> Result<()> {
Ok(())
}
}
impl WebRtcTrackSink {
fn normalize_packet_timestamp(&mut self, buf: MediaBuffer) -> Result<MediaBuffer> {
let MediaBuffer::Packet(packet) = buf else {
return Ok(buf);
};
let Some(pts) = packet.pts() else {
return Ok(MediaBuffer::Packet(packet));
};
let offset = match self.timestamp_offset {
Some(offset) => offset,
None if pts < 0 => {
pts.checked_neg()
.ok_or(WebRtcError::PacketTimestampNormalizationOverflow {
value: pts,
offset: 0,
})?
}
None => 0,
};
self.timestamp_offset = Some(offset);
if offset == 0 {
return Ok(MediaBuffer::Packet(packet));
}
let shifted = |value: i64| {
value
.checked_add(offset)
.ok_or(WebRtcError::PacketTimestampNormalizationOverflow { value, offset })
};
let mut normalized = (*packet).clone();
normalized.set_pts(Some(shifted(pts)?));
normalized.set_dts(packet.dts().map(shifted).transpose()?);
Ok(MediaBuffer::Packet(Arc::new(normalized)))
}
}
pub struct WebRtcTrackSource {
id: TrackId,
pp_log: PpLog,
name: Arc<str>,
pad: SrcPad,
data_rx: Receiver<MediaBuffer>,
codec: Arc<Mutex<Option<Codec>>>,
negotiated_codecs: Arc<Mutex<Vec<Codec>>>,
stream_info: Mutex<StreamInfoState>,
}
struct StreamInfoState {
rx: Receiver<WebRtcStreamInfo>,
cached: Option<WebRtcStreamInfo>,
}
impl WebRtcTrackSource {
pub(super) fn new(
id: TrackId,
kind: MediaKind,
name: impl Into<String>,
data_rx: Receiver<MediaBuffer>,
codec: Arc<Mutex<Option<Codec>>>,
negotiated_codecs: Arc<Mutex<Vec<Codec>>>,
stream_info_rx: Receiver<WebRtcStreamInfo>,
) -> Self {
let name: Arc<str> = name.into().into();
let pp_log = element_pp_log(ElementType::WebRtcPeer, &name, None);
let pad = SrcPad::with_contract(
format!("{name}_src"),
OutputContract::Fixed(PortContract::packet(packet_kind(kind))),
);
Self {
id,
name,
pp_log,
pad,
data_rx,
codec,
negotiated_codecs,
stream_info: Mutex::new(StreamInfoState {
rx: stream_info_rx,
cached: None,
}),
}
}
pub fn wait_stream_info(&self, timeout: Duration) -> Result<WebRtcStreamInfo> {
let mut state = self.stream_info.lock().unwrap();
if let Some(info) = &state.cached {
return Ok(info.clone());
}
match state.rx.recv_timeout(timeout) {
Ok(info) => {
state.cached = Some(info.clone());
Ok(info)
}
Err(RecvTimeoutError::Timeout) => Err(WebRtcError::StreamInfoTimeout {
track_id: self.id,
timeout,
}
.into()),
Err(RecvTimeoutError::Disconnected) => Err(WebRtcError::Closed.into()),
}
}
pub fn negotiated_codecs(&self) -> Vec<Codec> {
self.negotiated_codecs.lock().unwrap().clone()
}
pub fn codec(&self) -> Option<Codec> {
*self.codec.lock().unwrap()
}
}
impl Element for WebRtcTrackSource {
fn name(&self) -> Arc<str> {
self.name.clone()
}
fn element_type(&self) -> ElementType {
ElementType::WebRtcPeer
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl Source for WebRtcTrackSource {
fn src_pads(&mut self) -> &mut [SrcPad] {
std::slice::from_mut(&mut self.pad)
}
}
impl SourceElement for WebRtcTrackSource {
fn is_live(&self) -> bool {
true
}
fn is_seekable(&self) -> bool {
false
}
fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
pp_info!(self, "started");
loop {
if drain_control(control, self, bus)?.stopped {
pp_info!(self, "stopped");
return Ok(());
}
select! {
recv(control.rx) -> req => {
match req {
Ok(req) => {
match req.kind {
RequestKind::Finish => {
apply_finish(self, bus, &req.ack);
pp_info!(self, "finished");
return Ok(());
}
RequestKind::Control(msg) => {
if apply_one(self, bus, &msg, &req.ack)? {
pp_info!(self, "stopped");
return Ok(());
}
if msg == ControlMsg::Pause
&& wait_out_pause(control, self, bus)?
{
pp_info!(self, "stopped");
return Ok(());
}
}
}
}
Err(_) => {
pp_info!(self, "run: control channel gone, ending");
return Ok(());
}
}
}
recv(self.data_rx) -> buf => {
match buf {
Ok(buf) if buf.is_eos() => {
pp_info!(self, "event=eos phase=source_received");
break;
}
Ok(buf) => {
if let Err(error) = self.pad.push(buf) {
bus.post(
&self.pp_log,
BusEvent::Error {
element_type: ElementType::WebRtcPeer,
name: self.name.clone(),
error,
},
);
}
}
Err(_) => {
pp_info!(self, "run: WebRtcPeer gone, ending");
break;
}
}
}
}
}
while let Some((_msg, ack)) = control.try_recv() {
let _ = ack.send(());
}
self.pad.push_eos(&self.pp_log)
}
fn seek(&mut self, target: Duration) -> Result<Duration> {
Ok(target)
}
}