feat(mixer): enhance mixer functionality with interaction and tool legs

- Updated mixer to handle participant and isolated leg roles, allowing for IVR and consent interactions.
- Introduced commands for starting and canceling interactions, managing tool legs for recording and transcription.
- Implemented per-source audio handling for tool legs, enabling separate audio processing.
- Enhanced DTMF handling to forward events between participant legs only.
- Added support for PCM recording directly from tool legs, with WAV file generation.
- Updated TypeScript definitions and functions to support new interaction and tool leg features.
This commit is contained in:
2026-04-10 14:54:21 +00:00
parent 6a130db7c7
commit 7d59361352
13 changed files with 1448 additions and 94 deletions

View File

@@ -5,6 +5,7 @@
use crate::mixer::{MixerCommand, RtpPacket};
use crate::sip_leg::SipLeg;
use sip_proto::message::SipMessage;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
@@ -59,6 +60,7 @@ pub enum LegKind {
SipDevice,
WebRtc,
Media, // voicemail playback, IVR, recording
Tool, // observer leg for recording, transcription, etc.
}
impl LegKind {
@@ -68,6 +70,7 @@ impl LegKind {
Self::SipDevice => "sip-device",
Self::WebRtc => "webrtc",
Self::Media => "media",
Self::Tool => "tool",
}
}
}
@@ -113,6 +116,10 @@ pub struct LegInfo {
pub remote_media: Option<SocketAddr>,
/// SIP signaling address (provider or device).
pub signaling_addr: Option<SocketAddr>,
/// Flexible key-value metadata (consent state, tool config, etc.).
/// Persisted into call history on call end.
pub metadata: HashMap<String, serde_json::Value>,
}
/// A multiparty call with N legs and a central mixer.
@@ -127,6 +134,10 @@ pub struct Call {
pub callee_number: Option<String>,
pub provider_id: String,
/// Original INVITE from the device (for device-originated outbound calls).
/// Used to construct proper 180/200/error responses back to the device.
pub device_invite: Option<SipMessage>,
/// All legs in this call, keyed by leg ID.
pub legs: HashMap<LegId, LegInfo>,
@@ -153,6 +164,7 @@ impl Call {
caller_number: None,
callee_number: None,
provider_id,
device_invite: None,
legs: HashMap::new(),
mixer_cmd_tx,
mixer_task: Some(mixer_task),
@@ -207,6 +219,13 @@ impl Call {
.values()
.filter(|l| l.state != LegState::Terminated)
.map(|l| {
let metadata: serde_json::Value = if l.metadata.is_empty() {
serde_json::json!({})
} else {
serde_json::Value::Object(
l.metadata.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
)
};
serde_json::json!({
"id": l.id,
"type": l.kind.as_str(),
@@ -214,6 +233,7 @@ impl Call {
"codec": sip_proto::helpers::codec_name(l.codec_pt),
"rtpPort": l.rtp_port,
"remoteMedia": l.remote_media.map(|a| format!("{}:{}", a.ip(), a.port())),
"metadata": metadata,
})
})
.collect();