use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use audio_codec::CodecType;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
pub type SharedMediaSample = Arc<rustrtc::media::frame::MediaSample>;
pub type SipFlowCaptureTx = mpsc::Sender<(crate::media::recorder::Leg, SharedMediaSample, u64)>;
pub type SipFlowCaptureRx = mpsc::Receiver<(crate::media::recorder::Leg, SharedMediaSample, u64)>;
#[derive(Debug, Clone)]
pub struct PcmFrame {
pub samples: Vec<i16>,
pub sample_rate: u32,
pub timestamp: u64,
}
pub enum LegTransport {
Webrtc {
peer_connection: rustrtc::PeerConnection,
},
Rtp {
peer_connection: rustrtc::PeerConnection,
},
File { path: String },
WebSocket {
url: String,
codec: CodecType,
sample_rate: u32,
},
PcmStream {
input: mpsc::Receiver<PcmFrame>,
output: mpsc::Sender<PcmFrame>,
sample_rate: u32,
},
}
impl std::fmt::Display for LegTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Webrtc { .. } => write!(f, "webrtc"),
Self::Rtp { .. } => write!(f, "rtp"),
Self::File { path } => write!(f, "file({})", path),
Self::WebSocket { url, .. } => write!(f, "ws({})", url),
Self::PcmStream { .. } => write!(f, "pcm"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlaySource {
File { path: String },
Url { url: String },
Tts { text: String, voice: String },
Silence,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlayOptions {
pub loop_playback: bool,
pub await_completion: bool,
pub interrupt_on_dtmf: bool,
pub track_id: Option<String>,
pub broadcast_to_all: bool,
}
#[derive(Debug, Clone)]
pub enum InjectTarget {
Both,
Leg(String),
}
#[derive(Debug, Clone)]
pub struct CodecProfile {
pub codec: CodecType,
pub payload_type: u8,
pub clock_rate: u32,
}
impl CodecProfile {
pub fn pcmu() -> Self {
Self {
codec: CodecType::PCMU,
payload_type: 0,
clock_rate: 8000,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordConfig {
pub path: String,
pub max_duration_secs: Option<u32>,
pub beep: bool,
pub format: Option<String>,
}
pub enum MediaCommand {
CreateSession {
session_id: String,
},
DestroySession {
session_id: String,
},
AddLeg {
session_id: String,
leg_id: String,
transport: LegTransport,
codec_profile: Option<CodecProfile>,
},
RemoveLeg {
session_id: String,
leg_id: String,
},
BridgeLegs {
session_id: String,
leg_a: String,
leg_b: String,
},
Unbridge {
session_id: String,
},
Play {
session_id: String,
leg_id: Option<String>,
source: PlaySource,
options: PlayOptions,
},
StopPlayback {
session_id: String,
leg_id: Option<String>,
},
StartRecording {
session_id: String,
config: RecordConfig,
caller_profile: Option<crate::media::negotiate::NegotiatedLegProfile>,
callee_profile: Option<crate::media::negotiate::NegotiatedLegProfile>,
reply: Option<tokio::sync::oneshot::Sender<()>>,
},
StopRecording {
session_id: String,
reply: Option<tokio::sync::oneshot::Sender<crate::media::engine::event::RecordResult>>,
},
PauseRecording {
session_id: String,
},
ResumeRecording {
session_id: String,
},
StartSipFlow {
session_id: String,
},
StopSipFlow {
session_id: String,
},
SendDtmf {
session_id: String,
leg_id: String,
digits: String,
},
CollectDtmf {
session_id: String,
leg_id: Option<String>,
min_digits: u32,
max_digits: u32,
timeout_ms: u64,
terminator: Option<char>,
},
JoinMixer {
session_id: String,
mixer_id: String,
},
LeaveMixer {
session_id: String,
},
SetRouteGain {
mixer_id: String,
src_leg: String,
dst_leg: String,
gain: f32,
},
InjectAudio {
session_id: String,
source: PlaySource,
target: InjectTarget,
mute_peer: bool,
},
Hold {
session_id: String,
leg_id: String,
music: Option<PlaySource>,
},
Unhold {
session_id: String,
leg_id: String,
},
MuteLeg {
session_id: String,
leg_id: String,
},
UnmuteLeg {
session_id: String,
leg_id: String,
},
AttachBridge {
session_id: String,
bridge: Arc<crate::media::bridge::BridgePeer>,
caller_is_webrtc: bool,
caller_codec_info: Vec<crate::media::negotiate::CodecInfo>,
},
DetachBridge {
session_id: String,
},
AttachRecorder {
session_id: String,
recorder: Arc<RwLock<Option<crate::media::recorder::Recorder>>>,
paused: Arc<AtomicBool>,
},
SetSipFlowCapture {
session_id: String,
call_id: String,
backend: Option<Arc<dyn crate::sipflow::SipFlowBackend>>,
receiver: Option<SipFlowCaptureRx>,
},
}
impl MediaCommand {
pub fn session_id(&self) -> Option<&str> {
match self {
Self::CreateSession { session_id } | Self::DestroySession { session_id } => {
Some(session_id)
}
Self::AddLeg { session_id, .. }
| Self::RemoveLeg { session_id, .. }
| Self::BridgeLegs { session_id, .. }
| Self::Unbridge { session_id } => Some(session_id),
Self::Play { session_id, .. } | Self::StopPlayback { session_id, .. } => {
Some(session_id)
}
Self::StartRecording { session_id, .. }
| Self::StopRecording { session_id, .. }
| Self::PauseRecording { session_id }
| Self::ResumeRecording { session_id }
| Self::StartSipFlow { session_id }
| Self::StopSipFlow { session_id } => Some(session_id),
Self::SendDtmf { session_id, .. } | Self::CollectDtmf { session_id, .. } => {
Some(session_id)
}
Self::JoinMixer { session_id, .. } | Self::LeaveMixer { session_id } => {
Some(session_id)
}
Self::InjectAudio { session_id, .. } => Some(session_id),
Self::Hold { session_id, .. } | Self::Unhold { session_id, .. } => Some(session_id),
Self::MuteLeg { session_id, .. } | Self::UnmuteLeg { session_id, .. } => {
Some(session_id)
}
Self::AttachBridge { session_id, .. }
| Self::DetachBridge { session_id }
| Self::AttachRecorder { session_id, .. }
| Self::SetSipFlowCapture { session_id, .. } => Some(session_id),
Self::SetRouteGain { .. } => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::CreateSession { .. } => "create_session",
Self::DestroySession { .. } => "destroy_session",
Self::AddLeg { .. } => "add_leg",
Self::RemoveLeg { .. } => "remove_leg",
Self::BridgeLegs { .. } => "bridge_legs",
Self::Unbridge { .. } => "unbridge",
Self::Play { .. } => "play",
Self::StopPlayback { .. } => "stop_playback",
Self::StartRecording { .. } => "start_recording",
Self::StopRecording { .. } => "stop_recording",
Self::PauseRecording { .. } => "pause_recording",
Self::ResumeRecording { .. } => "resume_recording",
Self::StartSipFlow { .. } => "start_sipflow",
Self::StopSipFlow { .. } => "stop_sipflow",
Self::SendDtmf { .. } => "send_dtmf",
Self::CollectDtmf { .. } => "collect_dtmf",
Self::JoinMixer { .. } => "join_mixer",
Self::LeaveMixer { .. } => "leave_mixer",
Self::SetRouteGain { .. } => "set_route_gain",
Self::InjectAudio { .. } => "inject_audio",
Self::Hold { .. } => "hold",
Self::Unhold { .. } => "unhold",
Self::MuteLeg { .. } => "mute_leg",
Self::UnmuteLeg { .. } => "unmute_leg",
Self::AttachBridge { .. } => "attach_bridge",
Self::DetachBridge { .. } => "detach_bridge",
Self::AttachRecorder { .. } => "attach_recorder",
Self::SetSipFlowCapture { .. } => "set_sipflow_capture",
}
}
}
impl std::fmt::Debug for MediaCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("MediaCommand::")?;
f.write_str(self.name())
}
}