slice-5 (rented transport, FOB half): G711Codec + TrunkSession + trunk_driver + MediaLeg + reflex-on-trunk verification (T1/T3/T4/T5/T7/T8) #19
@@ -21,9 +21,10 @@
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rutster_media::{AudioSink, AudioSource};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::session::{TrunkSession, IDLE_TIMEOUT, OUTBOUND_TICK};
|
||||
use crate::session::{IDLE_TIMEOUT, OUTBOUND_TICK, TrunkSession};
|
||||
|
||||
/// One iteration of the trunk-leg driver. Mirrors `rutster_media::loop_driver::drive`
|
||||
/// minus str0m. Returns the sleep-until-next-tick budget.
|
||||
@@ -56,11 +57,7 @@ pub fn drive<P: rutster_media::AudioPipe + Send>(
|
||||
// Brain reply PCM; push to the WSS pump task for µ-law encode
|
||||
// + Twilio Media Streams JSON "media" frame emission. try_send;
|
||||
// drop + observe on full (hot-path policy).
|
||||
if session
|
||||
.outbound_to_twilio_tx
|
||||
.try_send(reply_frame)
|
||||
.is_err()
|
||||
{
|
||||
if session.outbound_to_twilio_tx.try_send(reply_frame).is_err() {
|
||||
warn!(
|
||||
channel_id = %session.channel.id,
|
||||
"outbound_to_twilio_tx full; dropping brain reply frame"
|
||||
@@ -98,8 +95,7 @@ mod tests {
|
||||
// Given: a TrunkSession with an EchoAudioPipe inner + an inbound
|
||||
// mpsc carrying one PCM frame.
|
||||
let now = Instant::now();
|
||||
let (mut session, inbound_tx, _out_rx) =
|
||||
trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
let (mut session, inbound_tx, _out_rx) = trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
|
||||
let mut frame = PcmFrame::zeroed();
|
||||
frame.samples[0] = 4242;
|
||||
@@ -121,7 +117,10 @@ mod tests {
|
||||
// The EchoAudioPipe holds exactly one frame (the one we pushed).
|
||||
// If the tick route worked, we get Some back; if the inbound
|
||||
// mpsc was never drained, we get None (the echopipe is empty).
|
||||
assert!(out_frame.is_some(), "tick must drain inbound mpsc to the pipe");
|
||||
assert!(
|
||||
out_frame.is_some(),
|
||||
"tick must drain inbound mpsc to the pipe"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -130,8 +129,7 @@ mod tests {
|
||||
// holds a frame (pushed via on_pcm_frame), and the outbound mpsc
|
||||
// ready to receive.
|
||||
let now = Instant::now();
|
||||
let (mut session, _in_tx, mut out_rx) =
|
||||
trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
let (mut session, _in_tx, mut out_rx) = trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
|
||||
// Prime the inner echo pipe with a frame.
|
||||
let mut frame = PcmFrame::zeroed();
|
||||
@@ -146,15 +144,17 @@ mod tests {
|
||||
|
||||
// Then: outbound mpsc has the frame (try_recv returns it).
|
||||
let drained = out_rx.try_recv();
|
||||
assert!(drained.is_ok(), "tick must push brain reply PCM to outbound mpsc");
|
||||
assert!(
|
||||
drained.is_ok(),
|
||||
"tick must push brain reply PCM to outbound mpsc"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn idle_timeout_closes_session_after_60s_silence() {
|
||||
// Given: a session whose last_idle_rx is 90s in the past.
|
||||
let now = Instant::now();
|
||||
let (mut session, _in_tx, _out_rx) =
|
||||
trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
let (mut session, _in_tx, _out_rx) = trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
session.last_idle_rx = now - Duration::from_secs(90);
|
||||
|
||||
// When: drive() runs one tick.
|
||||
@@ -174,8 +174,7 @@ mod tests {
|
||||
// Given: a TrunkSession with an inbound frame ready; last_idle_rx
|
||||
// is currently in the past.
|
||||
let now = Instant::now();
|
||||
let (mut session, inbound_tx, _out_rx) =
|
||||
trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
let (mut session, inbound_tx, _out_rx) = trunk_session_with_echo_pipe_and_mpsc(now);
|
||||
session.last_idle_rx = now - Duration::from_secs(30);
|
||||
|
||||
inbound_tx.send(PcmFrame::zeroed()).await.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user