Files
siprouter/rust/crates/proxy-engine/src/voicemail.rs

138 lines
3.9 KiB
Rust
Raw Normal View History

//! Voicemail session — answer → play greeting → beep → record → done.
use crate::audio_player::{play_beep, play_wav_file};
use crate::ipc::{emit_event, OutTx};
use crate::recorder::Recorder;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::UdpSocket;
/// Run a voicemail session on an RTP port.
///
/// 1. Plays the greeting WAV file to the caller
/// 2. Plays a beep tone
/// 3. Records the caller's message until BYE or max duration
///
/// The RTP receive loop is separate — it feeds packets to the recorder
/// via the returned channel.
pub async fn run_voicemail_session(
rtp_socket: Arc<UdpSocket>,
provider_media: SocketAddr,
codec_pt: u8,
greeting_wav: Option<String>,
recording_path: String,
max_recording_ms: u64,
call_id: String,
caller_number: String,
out_tx: OutTx,
) {
let ssrc: u32 = rand::random();
emit_event(
&out_tx,
"voicemail_started",
serde_json::json!({
"call_id": call_id,
"caller_number": caller_number,
}),
);
// Step 1: Play greeting.
let mut next_seq: u16 = 0;
let mut next_ts: u32 = 0;
if let Some(wav_path) = &greeting_wav {
match play_wav_file(wav_path, rtp_socket.clone(), provider_media, codec_pt, ssrc).await {
Ok(frames) => {
next_seq = frames as u16;
next_ts = frames * crate::rtp::rtp_clock_increment(codec_pt);
}
Err(e) => {
emit_event(
&out_tx,
"voicemail_error",
serde_json::json!({ "call_id": call_id, "error": format!("greeting: {e}") }),
);
}
}
}
// Step 2: Play beep (1kHz, 500ms).
match play_beep(
rtp_socket.clone(),
provider_media,
codec_pt,
ssrc,
next_seq,
next_ts,
1000,
500,
)
.await
{
Ok((_seq, _ts)) => {}
Err(e) => {
emit_event(
&out_tx,
"voicemail_error",
serde_json::json!({ "call_id": call_id, "error": format!("beep: {e}") }),
);
}
}
// Step 3: Record incoming audio.
let recorder = match Recorder::new(&recording_path, codec_pt, Some(max_recording_ms)) {
Ok(r) => r,
Err(e) => {
emit_event(
&out_tx,
"voicemail_error",
serde_json::json!({ "call_id": call_id, "error": format!("recorder: {e}") }),
);
return;
}
};
// Receive RTP and feed to recorder.
let result = record_from_socket(rtp_socket, recorder, max_recording_ms).await;
// Step 4: Done — emit recording result.
emit_event(
&out_tx,
"recording_done",
serde_json::json!({
"call_id": call_id,
"file_path": result.file_path,
"duration_ms": result.duration_ms,
"caller_number": caller_number,
}),
);
}
/// Read RTP packets from the socket and feed them to the recorder.
/// Returns when the socket errors out (BYE closes the call/socket)
/// or max duration is reached.
async fn record_from_socket(
socket: Arc<UdpSocket>,
mut recorder: Recorder,
max_ms: u64,
) -> crate::recorder::RecordingResult {
let mut buf = vec![0u8; 65535];
let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_millis(max_ms + 2000);
loop {
let timeout = tokio::time::timeout_at(deadline, socket.recv_from(&mut buf));
match timeout.await {
Ok(Ok((n, _addr))) => {
if !recorder.process_rtp(&buf[..n]) {
break; // Max duration reached.
}
}
Ok(Err(_)) => break, // Socket error (closed).
Err(_) => break, // Timeout (max duration + grace).
}
}
recorder.stop()
}