Skip to main content

active_call/
event.rs

1use crate::media::PcmBuf;
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4use std::{collections::HashMap, fmt::Display};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct Attendee {
9    pub username: String,
10    pub realm: String,
11    pub source: String,
12}
13
14impl From<&String> for Attendee {
15    fn from(source: &String) -> Self {
16        let uri = rsipstack::rsip::Uri::try_from(source.as_str()).unwrap_or_default();
17        Self {
18            username: uri.user().unwrap_or_default().to_string(),
19            realm: uri.host().to_string(),
20            source: source.to_string(),
21        }
22    }
23}
24
25#[skip_serializing_none]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(
28    tag = "event",
29    rename_all = "camelCase",
30    rename_all_fields = "camelCase"
31)]
32pub enum SessionEvent {
33    Incoming {
34        track_id: String,
35        timestamp: u64,
36        caller: String,
37        callee: String,
38        sdp: String,
39        headers: Option<HashMap<String, String>>,
40    },
41    Answer {
42        track_id: String,
43        timestamp: u64,
44        sdp: String,
45        refer: Option<bool>,
46    },
47    MediaReady {
48        track_id: String,
49        timestamp: u64,
50    },
51    Reject {
52        track_id: String,
53        timestamp: u64,
54        reason: String,
55        refer: Option<bool>,
56        code: Option<u32>,
57    },
58    Ringing {
59        track_id: String,
60        timestamp: u64,
61        early_media: bool,
62        refer: Option<bool>,
63    },
64    Hangup {
65        track_id: String,
66        timestamp: u64,
67        reason: Option<String>,
68        initiator: Option<String>,
69        start_time: String,
70        hangup_time: String,
71        answer_time: Option<String>,
72        ringing_time: Option<String>,
73        from: Option<Attendee>,
74        to: Option<Attendee>,
75        extra: Option<HashMap<String, serde_json::Value>>,
76        refer: Option<bool>,
77    },
78    AnswerMachineDetection {
79        // Answer machine detection
80        track_id: String,
81        timestamp: u64,
82        start_time: u64,
83        end_time: u64,
84        text: String,
85    },
86    Interrupt {
87        receiver: Option<String>,
88    },
89    FunctionCall {
90        track_id: String,
91        call_id: String,
92        name: String,
93        arguments: String,
94        timestamp: u64,
95    },
96    Speaking {
97        track_id: String,
98        timestamp: u64,
99        start_time: u64,
100        is_filler: Option<bool>,
101        confidence: Option<f32>,
102        refer: Option<bool>,
103    },
104    Silence {
105        track_id: String,
106        timestamp: u64,
107        start_time: u64,
108        duration: u64,
109        refer: Option<bool>,
110        #[serde(skip)]
111        samples: Option<PcmBuf>,
112    },
113    ///End of Utterance
114    Eou {
115        track_id: String,
116        timestamp: u64,
117        completed: bool,
118        interrupt_point: Option<String>,
119        text: Option<String>,
120        refer: Option<bool>,
121    },
122    ///Inactivity timeout
123    Inactivity {
124        track_id: String,
125        timestamp: u64,
126    },
127    Dtmf {
128        track_id: String,
129        timestamp: u64,
130        digit: String,
131        refer: Option<bool>,
132    },
133    Hold {
134        track_id: String,
135        timestamp: u64,
136        on_hold: bool,
137        refer: Option<bool>,
138    },
139    TransferRequest {
140        track_id: String,
141        timestamp: u64,
142        refer_to: String,
143        referred_by: Option<String>,
144        refer: Option<bool>,
145    },
146    Message {
147        track_id: String,
148        timestamp: u64,
149        body: String,
150        content_type: Option<String>,
151        refer: Option<bool>,
152    },
153    TrackStart {
154        track_id: String,
155        timestamp: u64,
156        play_id: Option<String>,
157    },
158    TrackEnd {
159        track_id: String,
160        timestamp: u64,
161        duration: u64,
162        ssrc: u32,
163        play_id: Option<String>,
164        /// Hangup intent carried by the track itself; when set, the call
165        /// should hang up with this reason once the track ends.
166        auto_hangup: Option<crate::callrecord::CallRecordHangupReason>,
167    },
168    Interruption {
169        track_id: String,
170        timestamp: u64,
171        play_id: Option<String>,
172        subtitle: Option<String>, // current tts text
173        position: Option<u32>,    // word index in subtitle
174        total_duration: u32,      // whole tts duration
175        current: u32,             // elapsed time since start of tts
176    },
177    AsrFinal {
178        track_id: String,
179        timestamp: u64,
180        index: u32,
181        start_time: Option<u64>,
182        end_time: Option<u64>,
183        text: String,
184        is_filler: Option<bool>,
185        confidence: Option<f32>,
186        task_id: Option<String>,
187        refer: Option<bool>,
188    },
189    AsrDelta {
190        track_id: String,
191        index: u32,
192        timestamp: u64,
193        start_time: Option<u64>,
194        end_time: Option<u64>,
195        text: String,
196        is_filler: Option<bool>,
197        confidence: Option<f32>,
198        task_id: Option<String>,
199        refer: Option<bool>,
200    },
201    Metrics {
202        timestamp: u64,
203        key: String,
204        duration: u32,
205        data: serde_json::Value,
206    },
207    Error {
208        track_id: String,
209        timestamp: u64,
210        sender: String,
211        error: String,
212        code: Option<u32>,
213    },
214    AddHistory {
215        sender: Option<String>,
216        timestamp: u64,
217        speaker: String,
218        text: String,
219    },
220    Other {
221        track_id: String,
222        timestamp: u64,
223        sender: String,
224        extra: Option<HashMap<String, String>>,
225    },
226    Binary {
227        track_id: String,
228        timestamp: u64,
229        data: Vec<u8>,
230    },
231    Ping {
232        timestamp: u64,
233        payload: Option<String>,
234    },
235    Custom {
236        timestamp: u64,
237        sender: Option<String>,
238        data: serde_json::Value,
239    },
240    RingbackState {
241        track_id: String,
242        timestamp: u64,
243        /// Current line state: "ringing", "human_voice", "busy_tone", "silence", etc.
244        state: String,
245        /// Class index from the TelcoClassifier (3=ringing, 9=human_voice, ...)
246        state_index: u32,
247        /// Confidence of the prediction
248        confidence: f32,
249        /// Previous state (None on first detection)
250        prev_state: Option<String>,
251        prev_confidence: Option<f32>,
252        refer: Option<bool>,
253        /// Whether this is the final determination
254        #[serde(default)]
255        is_final: bool,
256    },
257}
258
259impl Display for SessionEvent {
260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        match self {
262            SessionEvent::Silence {
263                track_id, duration, ..
264            } => {
265                write!(f, "Silence(track_id={}, duration={})", track_id, duration)
266            }
267            SessionEvent::Binary { track_id, data, .. } => {
268                write!(f, "Silence(track_id={}, data_len={})", track_id, data.len())
269            }
270            _ => {
271                write!(f, "{:?}", self)
272            }
273        }
274    }
275}
276
277pub type EventSender = tokio::sync::broadcast::Sender<SessionEvent>;
278pub type EventReceiver = tokio::sync::broadcast::Receiver<SessionEvent>;
279
280pub fn create_event_sender() -> EventSender {
281    EventSender::new(128)
282}