use std::{
sync::{
Arc, Mutex,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
use crate::pp_log::{PpLog, pp_error, pp_info};
use crossbeam_channel::{Receiver, Sender, TrySendError, select};
use str0m::{
change::{SdpAnswer, SdpOffer},
format::Codec,
media::{Direction, MediaKind, Mid},
};
use crate::{
buffer::MediaBuffer,
bus::{Bus, BusEvent},
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};
#[derive(Clone)]
pub struct WebRtcHandle {
pub(super) next_id: Arc<AtomicU64>,
pub(super) command_tx: Sender<Command>,
pub(super) new_track_rx:
Receiver<(TrackId, Mid, MediaKind, WebRtcTrackSink, WebRtcTrackSource)>,
}
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<(TrackId, Mid, MediaKind, WebRtcTrackSink, WebRtcTrackSource)> {
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,
command_tx: Sender<Command>,
}
impl WebRtcTrackSink {
pub(super) fn new(id: TrackId, command_tx: Sender<Command>) -> Self {
Self {
id,
command_tx,
pp_log: element_pp_log(
ElementType::WebRtcPeer,
&format!("webrtc-track-{}", id.0),
None,
),
}
}
}
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 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());
}
match self.command_tx.try_send(Command::Push(self.id, 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(())
}
}
pub struct WebRtcTrackSource {
pp_log: PpLog,
name: Arc<str>,
pad: SrcPad,
data_rx: Receiver<MediaBuffer>,
codec: Arc<Mutex<Option<Codec>>>,
}
impl WebRtcTrackSource {
pub(super) fn new(
name: impl Into<String>,
data_rx: Receiver<MediaBuffer>,
codec: Arc<Mutex<Option<Codec>>>,
) -> Self {
let name: Arc<str> = name.into().into();
let pp_log = element_pp_log(ElementType::WebRtcPeer, &name, None);
let pad = SrcPad::new(format!("{name}_src"));
Self {
name,
pp_log,
pad,
data_rx,
codec,
}
}
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 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)
}
}