use async_trait::async_trait;
use crate::error::FlowcatError;
use crate::serializer::MediaSerializer;
use crate::transport::media::{MediaIn, MediaTransport};
use crate::transport::socket::MediaSocket;
use crate::types::{AudioChunk, SerIn, WsOut};
pub struct WsCarrierTransport<So, Se>
where
So: MediaSocket,
Se: MediaSerializer,
{
socket: So,
serializer: Se,
}
impl<So, Se> WsCarrierTransport<So, Se>
where
So: MediaSocket,
Se: MediaSerializer,
{
pub fn new(socket: So, serializer: Se) -> Self {
Self { socket, serializer }
}
async fn send_out(&mut self, out: WsOut) -> Result<(), FlowcatError> {
match out {
WsOut::Text(s) => self.socket.send_text(s).await,
WsOut::Binary(b) => self.socket.send_binary(b).await,
}
}
}
#[async_trait]
impl<So, Se> MediaTransport for WsCarrierTransport<So, Se>
where
So: MediaSocket,
Se: MediaSerializer,
{
async fn recv(&mut self) -> Option<MediaIn> {
loop {
let frame = self.socket.recv().await?;
match self.serializer.on_message(&frame) {
SerIn::StreamStart { call_id, .. } => {
return Some(MediaIn::StreamStart { call_id });
}
SerIn::Audio(chunk) => return Some(MediaIn::Audio(chunk)),
SerIn::Stop => return Some(MediaIn::Stop),
SerIn::Ignore => continue,
}
}
}
async fn send_audio(&mut self, chunk: AudioChunk) -> Result<(), FlowcatError> {
let out = self.serializer.encode_audio(&chunk);
self.send_out(out).await
}
async fn send_clear(&mut self) -> Result<(), FlowcatError> {
if let Some(out) = self.serializer.encode_clear() {
self.send_out(out).await
} else {
Ok(())
}
}
fn carrier_rate(&self) -> u32 {
self.serializer.carrier_rate()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serializer::PlivoSerializer;
use crate::types::{WsIn, WsOut};
use serde_json::{json, Value};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
fn b64(bytes: &[u8]) -> String {
use base64::Engine as _;
base64::engine::general_purpose::STANDARD.encode(bytes)
}
struct ScriptSocket {
inbound: VecDeque<WsIn>,
sent: Arc<Mutex<Vec<WsOut>>>,
}
impl ScriptSocket {
fn new(inbound: Vec<WsIn>, sent: Arc<Mutex<Vec<WsOut>>>) -> Self {
Self {
inbound: inbound.into(),
sent,
}
}
}
#[async_trait]
impl MediaSocket for ScriptSocket {
async fn recv(&mut self) -> Option<WsIn> {
self.inbound.pop_front()
}
async fn send_text(&mut self, s: String) -> Result<(), FlowcatError> {
self.sent.lock().unwrap().push(WsOut::Text(s));
Ok(())
}
async fn send_binary(&mut self, b: Vec<u8>) -> Result<(), FlowcatError> {
self.sent.lock().unwrap().push(WsOut::Binary(b));
Ok(())
}
}
#[tokio::test]
async fn recv_maps_plivo_start_media_stop_and_skips_ignore() {
let pcm: Vec<i16> = (0..160).map(|i| ((i as i16 % 16) * 300) - 2400).collect();
let ulaw = crate::codec::pcm16_to_ulaw(&pcm);
let inbound = vec![
WsIn::Text(
json!({
"event": "start",
"start": { "streamId": "strm-1", "callId": "call-9",
"mediaFormat": {"encoding": "audio/x-mulaw", "sampleRate": 8000} }
})
.to_string(),
),
WsIn::Text(json!({ "event": "dtmf", "dtmf": { "digit": "5" } }).to_string()),
WsIn::Text(json!({ "event": "media", "media": { "payload": b64(&ulaw) } }).to_string()),
WsIn::Text(json!({ "event": "stop" }).to_string()),
];
let sent = Arc::new(Mutex::new(Vec::new()));
let mut tr = WsCarrierTransport::new(
ScriptSocket::new(inbound, sent.clone()),
PlivoSerializer::new(8000),
);
assert_eq!(tr.carrier_rate(), 8000);
match tr.recv().await {
Some(MediaIn::StreamStart { call_id }) => assert_eq!(call_id, "call-9"),
other => panic!("expected StreamStart, got {other:?}"),
}
match tr.recv().await {
Some(MediaIn::Audio(chunk)) => {
assert_eq!(chunk.sample_rate, 8000);
assert_eq!(chunk.pcm.len(), ulaw.len());
}
other => panic!("expected Audio, got {other:?}"),
}
assert_eq!(tr.recv().await, Some(MediaIn::Stop));
assert_eq!(tr.recv().await, None);
}
#[tokio::test]
async fn recv_maps_socket_close_to_stop() {
let sent = Arc::new(Mutex::new(Vec::new()));
let mut tr = WsCarrierTransport::new(
ScriptSocket::new(vec![WsIn::Close], sent.clone()),
PlivoSerializer::new(8000),
);
assert_eq!(tr.recv().await, Some(MediaIn::Stop));
}
#[tokio::test]
async fn send_audio_and_clear_go_out_through_the_serializer() {
let start = WsIn::Text(
json!({ "event": "start", "start": { "streamId": "S7", "callId": "C7" } }).to_string(),
);
let sent = Arc::new(Mutex::new(Vec::new()));
let mut tr = WsCarrierTransport::new(
ScriptSocket::new(vec![start], sent.clone()),
PlivoSerializer::new(8000),
);
let _ = tr.recv().await;
tr.send_audio(AudioChunk::new(vec![0i16, 100, -100, 200], 8000))
.await
.unwrap();
tr.send_clear().await.unwrap();
let out = sent.lock().unwrap();
assert_eq!(out.len(), 2, "one playAudio + one clearAudio");
let play = match &out[0] {
WsOut::Text(t) => serde_json::from_str::<Value>(t).unwrap(),
other => panic!("expected Text playAudio, got {other:?}"),
};
assert_eq!(play["event"], "playAudio");
assert_eq!(play["media"]["contentType"], "audio/x-mulaw");
assert_eq!(play["streamId"], "S7");
let clear = match &out[1] {
WsOut::Text(t) => serde_json::from_str::<Value>(t).unwrap(),
other => panic!("expected Text clearAudio, got {other:?}"),
};
assert_eq!(clear["event"], "clearAudio");
assert_eq!(clear["streamId"], "S7");
}
}