1use std::any::Any;
17use std::sync::atomic::{AtomicU64, Ordering};
18use std::sync::Arc;
19
20use serde::{Deserialize, Serialize};
21
22use crate::processor::metrics::MetricsData;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Direction {
29 Downstream,
30 Upstream,
31}
32
33impl Direction {
34 pub fn flip(self) -> Direction {
36 match self {
37 Direction::Downstream => Direction::Upstream,
38 Direction::Upstream => Direction::Downstream,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum FrameClass {
50 System,
51 Data,
52 Control,
53}
54
55static FRAME_ID: AtomicU64 = AtomicU64::new(1);
57
58pub fn next_frame_id() -> u64 {
60 FRAME_ID.fetch_add(1, Ordering::Relaxed)
61}
62
63#[derive(Debug, Clone)]
70pub struct FrameMeta {
71 pub id: u64,
73 pub name: &'static str,
75 pub pts: Option<i64>,
77 pub broadcast_sibling_id: Option<u64>,
79 pub extra: Option<Arc<serde_json::Map<String, serde_json::Value>>>,
81 pub transport_source: Option<Arc<str>>,
83 pub transport_destination: Option<Arc<str>>,
84}
85
86impl FrameMeta {
87 pub fn new(frame: &Frame) -> Self {
90 Self {
91 id: next_frame_id(),
92 name: frame.name(),
93 pts: None,
94 broadcast_sibling_id: None,
95 extra: None,
96 transport_source: None,
97 transport_destination: None,
98 }
99 }
100}
101
102pub trait CustomFrame: Any + Send + Sync + std::fmt::Debug {
106 fn frame_class(&self) -> FrameClass;
108 fn uninterruptible(&self) -> bool {
110 false
111 }
112 fn name(&self) -> &'static str {
114 "Custom"
115 }
116 fn as_any(&self) -> &dyn Any;
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct AudioFrame {
126 pub pcm: Vec<i16>,
127 pub sample_rate: u32,
128 pub num_channels: u16,
129}
130
131impl AudioFrame {
132 pub fn mono(pcm: Vec<i16>, sample_rate: u32) -> Self {
134 Self {
135 pcm,
136 sample_rate,
137 num_channels: 1,
138 }
139 }
140
141 pub fn len(&self) -> usize {
143 self.pcm.len()
144 }
145
146 pub fn is_empty(&self) -> bool {
148 self.pcm.is_empty()
149 }
150}
151
152impl From<crate::types::AudioChunk> for AudioFrame {
153 fn from(c: crate::types::AudioChunk) -> Self {
154 AudioFrame::mono(c.pcm, c.sample_rate)
155 }
156}
157
158impl From<&crate::types::AudioChunk> for AudioFrame {
159 fn from(c: &crate::types::AudioChunk) -> Self {
160 AudioFrame::mono(c.pcm.clone(), c.sample_rate)
161 }
162}
163
164impl From<&AudioFrame> for crate::types::AudioChunk {
167 fn from(f: &AudioFrame) -> Self {
168 crate::types::AudioChunk::new(f.pcm.clone(), f.sample_rate)
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct StartParams {
175 pub audio_in_sample_rate: u32,
176 pub audio_out_sample_rate: u32,
177 pub enable_metrics: bool,
178 pub enable_usage_metrics: bool,
179 pub enable_tracing: bool,
180 pub report_only_initial_ttfb: bool,
182}
183
184impl Default for StartParams {
185 fn default() -> Self {
186 Self {
187 audio_in_sample_rate: 16_000,
188 audio_out_sample_rate: 24_000,
189 enable_metrics: false,
190 enable_usage_metrics: false,
191 enable_tracing: false,
192 report_only_initial_ttfb: true,
193 }
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub enum KeypadEntry {
200 Zero,
201 One,
202 Two,
203 Three,
204 Four,
205 Five,
206 Six,
207 Seven,
208 Eight,
209 Nine,
210 Star,
211 Pound,
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218pub struct Language(pub String);
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223pub enum ServiceKind {
224 Stt,
225 Tts,
226 Llm,
227 Filter,
228 Mixer,
229 All,
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
235pub struct VadParams {
236 pub confidence: f32,
237 pub start_secs: f32,
238 pub stop_secs: f32,
239 pub min_volume: f32,
240}
241
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub struct TurnParams {
245 pub stop_secs: f32,
246 pub pre_speech_ms: f32,
247 pub max_duration_secs: Option<f32>,
248}
249
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct FunctionCall {
253 pub function_name: String,
254 pub tool_call_id: String,
255 pub arguments: serde_json::Value,
256}
257
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub struct FunctionCallResult {
262 pub function_name: String,
263 pub tool_call_id: String,
264 pub result: serde_json::Value,
265}
266
267#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
270pub struct LlmContext {
271 pub messages: Vec<serde_json::Value>,
272 pub tools: Vec<serde_json::Value>,
273}
274
275#[derive(Debug, Clone)]
281pub enum Frame {
282 Start(StartParams),
285 Cancel {
287 reason: Option<String>,
288 },
289 Error {
291 message: String,
292 fatal: bool,
293 processor: Option<Arc<str>>,
294 },
295 Interruption,
297 InputAudio(Arc<AudioFrame>),
299 UserAudio {
301 audio: Arc<AudioFrame>,
302 user_id: Arc<str>,
303 },
304 InputText(String),
306 InputDtmf(KeypadEntry),
308 UserStartedSpeaking,
310 UserStoppedSpeaking,
311 UserSpeaking,
312 BotStartedSpeaking,
313 BotStoppedSpeaking,
314 BotSpeaking,
315 VadUserStartedSpeaking {
317 start_secs: f32,
318 },
319 VadUserStoppedSpeaking {
320 stop_secs: f32,
321 },
322 SttMute(bool),
324 Metrics(Vec<MetricsData>),
326 TransportMessage {
328 payload: serde_json::Value,
329 urgent: bool,
330 },
331 ClientConnected,
333 BotConnected,
335 FunctionCallsStarted(Vec<FunctionCall>),
337 FunctionCallInProgress {
339 call: FunctionCall,
340 cancel_on_interruption: bool,
341 },
342 FunctionCallCancel {
344 function_name: String,
345 tool_call_id: String,
346 },
347
348 OutputAudio(Arc<AudioFrame>),
351 TtsAudio {
353 audio: Arc<AudioFrame>,
354 context_id: Option<Arc<str>>,
355 },
356 Text(String),
358 LlmText(String),
360 Transcription {
362 text: String,
363 user_id: Arc<str>,
364 language: Option<Language>,
365 final_: bool,
366 },
367 InterimTranscription {
369 text: String,
370 user_id: Arc<str>,
371 language: Option<Language>,
372 },
373 TtsSpeak {
375 text: String,
376 append_to_context: Option<bool>,
377 },
378 TtsText {
380 text: String,
381 context_id: Option<Arc<str>>,
382 },
383 FunctionCallResult(FunctionCallResult),
386 LlmRun,
388 LlmContext(Arc<LlmContext>),
390 OutputDtmf(Vec<KeypadEntry>),
392
393 End {
396 reason: Option<String>,
397 },
398 Stop,
400 LlmResponseStart,
402 LlmResponseEnd,
404 TtsStarted {
406 context_id: Option<Arc<str>>,
407 },
408 TtsStopped {
410 context_id: Option<Arc<str>>,
411 },
412 UpdateSettings {
415 target: ServiceKind,
416 settings: serde_json::Value,
417 },
418 SpeechControlParams {
421 vad: Option<VadParams>,
422 turn: Option<TurnParams>,
423 },
424 Heartbeat {
426 timestamp_ns: i64,
427 },
428 OutputTransportReady,
430 FilterEnable(bool),
435 MixerEnable(bool),
439
440 Custom(Arc<dyn CustomFrame>),
442}
443
444impl Frame {
445 pub fn class(&self) -> FrameClass {
449 use Frame::*;
450 match self {
451 Start(_)
453 | Cancel { .. }
454 | Error { .. }
455 | Interruption
456 | InputAudio(_)
457 | UserAudio { .. }
458 | InputText(_)
459 | InputDtmf(_)
460 | UserStartedSpeaking
461 | UserStoppedSpeaking
462 | UserSpeaking
463 | BotStartedSpeaking
464 | BotStoppedSpeaking
465 | BotSpeaking
466 | VadUserStartedSpeaking { .. }
467 | VadUserStoppedSpeaking { .. }
468 | SttMute(_)
469 | Metrics(_)
470 | TransportMessage { .. }
471 | ClientConnected
472 | BotConnected
473 | FunctionCallsStarted(_)
474 | FunctionCallInProgress { .. }
475 | FunctionCallCancel { .. } => FrameClass::System,
476
477 End { .. }
479 | Stop
480 | LlmResponseStart
481 | LlmResponseEnd
482 | TtsStarted { .. }
483 | TtsStopped { .. }
484 | UpdateSettings { .. }
485 | SpeechControlParams { .. }
486 | Heartbeat { .. }
487 | OutputTransportReady
488 | FilterEnable(_)
489 | MixerEnable(_) => FrameClass::Control,
490
491 OutputAudio(_)
493 | TtsAudio { .. }
494 | Text(_)
495 | LlmText(_)
496 | Transcription { .. }
497 | InterimTranscription { .. }
498 | TtsSpeak { .. }
499 | TtsText { .. }
500 | FunctionCallResult(_)
501 | LlmRun
502 | LlmContext(_)
503 | OutputDtmf(_) => FrameClass::Data,
504
505 Custom(c) => c.frame_class(),
506 }
507 }
508
509 pub fn uninterruptible(&self) -> bool {
513 use Frame::*;
514 match self {
515 End { .. } | Stop | FunctionCallResult(_) | UpdateSettings { .. } => true,
516 Custom(c) => c.uninterruptible(),
517 _ => false,
518 }
519 }
520
521 pub fn name(&self) -> &'static str {
523 use Frame::*;
524 match self {
525 Start(_) => "Start",
526 Cancel { .. } => "Cancel",
527 Error { .. } => "Error",
528 Interruption => "Interruption",
529 InputAudio(_) => "InputAudio",
530 UserAudio { .. } => "UserAudio",
531 InputText(_) => "InputText",
532 InputDtmf(_) => "InputDtmf",
533 UserStartedSpeaking => "UserStartedSpeaking",
534 UserStoppedSpeaking => "UserStoppedSpeaking",
535 UserSpeaking => "UserSpeaking",
536 BotStartedSpeaking => "BotStartedSpeaking",
537 BotStoppedSpeaking => "BotStoppedSpeaking",
538 BotSpeaking => "BotSpeaking",
539 VadUserStartedSpeaking { .. } => "VadUserStartedSpeaking",
540 VadUserStoppedSpeaking { .. } => "VadUserStoppedSpeaking",
541 SttMute(_) => "SttMute",
542 Metrics(_) => "Metrics",
543 TransportMessage { .. } => "TransportMessage",
544 ClientConnected => "ClientConnected",
545 BotConnected => "BotConnected",
546 FunctionCallsStarted(_) => "FunctionCallsStarted",
547 FunctionCallInProgress { .. } => "FunctionCallInProgress",
548 FunctionCallCancel { .. } => "FunctionCallCancel",
549 OutputAudio(_) => "OutputAudio",
550 TtsAudio { .. } => "TtsAudio",
551 Text(_) => "Text",
552 LlmText(_) => "LlmText",
553 Transcription { .. } => "Transcription",
554 InterimTranscription { .. } => "InterimTranscription",
555 TtsSpeak { .. } => "TtsSpeak",
556 TtsText { .. } => "TtsText",
557 FunctionCallResult(_) => "FunctionCallResult",
558 LlmRun => "LlmRun",
559 LlmContext(_) => "LlmContext",
560 OutputDtmf(_) => "OutputDtmf",
561 End { .. } => "End",
562 Stop => "Stop",
563 LlmResponseStart => "LlmResponseStart",
564 LlmResponseEnd => "LlmResponseEnd",
565 TtsStarted { .. } => "TtsStarted",
566 TtsStopped { .. } => "TtsStopped",
567 UpdateSettings { .. } => "UpdateSettings",
568 SpeechControlParams { .. } => "SpeechControlParams",
569 Heartbeat { .. } => "Heartbeat",
570 OutputTransportReady => "OutputTransportReady",
571 FilterEnable(_) => "FilterEnable",
572 MixerEnable(_) => "MixerEnable",
573 Custom(c) => c.name(),
574 }
575 }
576
577 pub fn is_terminal(&self) -> bool {
581 matches!(self, Frame::End { .. } | Frame::Stop | Frame::Cancel { .. })
582 }
583
584 pub fn kind(&self) -> FrameKind {
587 use Frame::*;
588 match self {
589 Start(_) => FrameKind::Start,
590 Cancel { .. } => FrameKind::Cancel,
591 Error { .. } => FrameKind::Error,
592 Interruption => FrameKind::Interruption,
593 InputAudio(_) => FrameKind::InputAudio,
594 UserAudio { .. } => FrameKind::UserAudio,
595 InputText(_) => FrameKind::InputText,
596 InputDtmf(_) => FrameKind::InputDtmf,
597 UserStartedSpeaking => FrameKind::UserStartedSpeaking,
598 UserStoppedSpeaking => FrameKind::UserStoppedSpeaking,
599 UserSpeaking => FrameKind::UserSpeaking,
600 BotStartedSpeaking => FrameKind::BotStartedSpeaking,
601 BotStoppedSpeaking => FrameKind::BotStoppedSpeaking,
602 BotSpeaking => FrameKind::BotSpeaking,
603 VadUserStartedSpeaking { .. } => FrameKind::VadUserStartedSpeaking,
604 VadUserStoppedSpeaking { .. } => FrameKind::VadUserStoppedSpeaking,
605 SttMute(_) => FrameKind::SttMute,
606 Metrics(_) => FrameKind::Metrics,
607 TransportMessage { .. } => FrameKind::TransportMessage,
608 ClientConnected => FrameKind::ClientConnected,
609 BotConnected => FrameKind::BotConnected,
610 FunctionCallsStarted(_) => FrameKind::FunctionCallsStarted,
611 FunctionCallInProgress { .. } => FrameKind::FunctionCallInProgress,
612 FunctionCallCancel { .. } => FrameKind::FunctionCallCancel,
613 OutputAudio(_) => FrameKind::OutputAudio,
614 TtsAudio { .. } => FrameKind::TtsAudio,
615 Text(_) => FrameKind::Text,
616 LlmText(_) => FrameKind::LlmText,
617 Transcription { .. } => FrameKind::Transcription,
618 InterimTranscription { .. } => FrameKind::InterimTranscription,
619 TtsSpeak { .. } => FrameKind::TtsSpeak,
620 TtsText { .. } => FrameKind::TtsText,
621 FunctionCallResult(_) => FrameKind::FunctionCallResult,
622 LlmRun => FrameKind::LlmRun,
623 LlmContext(_) => FrameKind::LlmContext,
624 OutputDtmf(_) => FrameKind::OutputDtmf,
625 End { .. } => FrameKind::End,
626 Stop => FrameKind::Stop,
627 LlmResponseStart => FrameKind::LlmResponseStart,
628 LlmResponseEnd => FrameKind::LlmResponseEnd,
629 TtsStarted { .. } => FrameKind::TtsStarted,
630 TtsStopped { .. } => FrameKind::TtsStopped,
631 UpdateSettings { .. } => FrameKind::UpdateSettings,
632 SpeechControlParams { .. } => FrameKind::SpeechControlParams,
633 Heartbeat { .. } => FrameKind::Heartbeat,
634 OutputTransportReady => FrameKind::OutputTransportReady,
635 FilterEnable(_) => FrameKind::FilterEnable,
636 MixerEnable(_) => FrameKind::MixerEnable,
637 Custom(_) => FrameKind::Custom,
638 }
639 }
640}
641
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
646pub enum FrameKind {
647 Start,
648 Cancel,
649 Error,
650 Interruption,
651 InputAudio,
652 UserAudio,
653 InputText,
654 InputDtmf,
655 UserStartedSpeaking,
656 UserStoppedSpeaking,
657 UserSpeaking,
658 BotStartedSpeaking,
659 BotStoppedSpeaking,
660 BotSpeaking,
661 VadUserStartedSpeaking,
662 VadUserStoppedSpeaking,
663 SttMute,
664 Metrics,
665 TransportMessage,
666 ClientConnected,
667 BotConnected,
668 FunctionCallsStarted,
669 FunctionCallInProgress,
670 FunctionCallCancel,
671 OutputAudio,
672 TtsAudio,
673 Text,
674 LlmText,
675 Transcription,
676 InterimTranscription,
677 TtsSpeak,
678 TtsText,
679 FunctionCallResult,
680 LlmRun,
681 LlmContext,
682 OutputDtmf,
683 End,
684 Stop,
685 LlmResponseStart,
686 LlmResponseEnd,
687 TtsStarted,
688 TtsStopped,
689 UpdateSettings,
690 SpeechControlParams,
691 Heartbeat,
692 OutputTransportReady,
693 FilterEnable,
694 MixerEnable,
695 Custom,
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 #[derive(Debug)]
704 struct MyCustom {
705 class: FrameClass,
706 keep: bool,
707 payload: u32,
708 }
709 impl CustomFrame for MyCustom {
710 fn frame_class(&self) -> FrameClass {
711 self.class
712 }
713 fn uninterruptible(&self) -> bool {
714 self.keep
715 }
716 fn name(&self) -> &'static str {
717 "MyCustom"
718 }
719 fn as_any(&self) -> &dyn Any {
720 self
721 }
722 }
723
724 #[test]
726 fn class_table_covers_every_variant() {
727 let a = Arc::new(AudioFrame::mono(vec![0], 16_000));
728 let uid: Arc<str> = Arc::from("u");
729 let cases: Vec<(Frame, FrameClass)> = vec![
730 (Frame::Start(StartParams::default()), FrameClass::System),
732 (Frame::Cancel { reason: None }, FrameClass::System),
733 (
734 Frame::Error {
735 message: "x".into(),
736 fatal: false,
737 processor: None,
738 },
739 FrameClass::System,
740 ),
741 (Frame::Interruption, FrameClass::System),
742 (Frame::InputAudio(a.clone()), FrameClass::System),
743 (
744 Frame::UserAudio {
745 audio: a.clone(),
746 user_id: uid.clone(),
747 },
748 FrameClass::System,
749 ),
750 (Frame::InputText("hi".into()), FrameClass::System),
751 (Frame::InputDtmf(KeypadEntry::One), FrameClass::System),
752 (Frame::UserStartedSpeaking, FrameClass::System),
753 (Frame::UserStoppedSpeaking, FrameClass::System),
754 (Frame::UserSpeaking, FrameClass::System),
755 (Frame::BotStartedSpeaking, FrameClass::System),
756 (Frame::BotStoppedSpeaking, FrameClass::System),
757 (Frame::BotSpeaking, FrameClass::System),
758 (
759 Frame::VadUserStartedSpeaking { start_secs: 0.2 },
760 FrameClass::System,
761 ),
762 (
763 Frame::VadUserStoppedSpeaking { stop_secs: 0.5 },
764 FrameClass::System,
765 ),
766 (Frame::SttMute(true), FrameClass::System),
767 (Frame::Metrics(vec![]), FrameClass::System),
768 (
769 Frame::TransportMessage {
770 payload: serde_json::json!({}),
771 urgent: true,
772 },
773 FrameClass::System,
774 ),
775 (Frame::ClientConnected, FrameClass::System),
776 (Frame::BotConnected, FrameClass::System),
777 (Frame::FunctionCallsStarted(vec![]), FrameClass::System),
778 (
779 Frame::FunctionCallInProgress {
780 call: FunctionCall {
781 function_name: "f".into(),
782 tool_call_id: "1".into(),
783 arguments: serde_json::json!({}),
784 },
785 cancel_on_interruption: false,
786 },
787 FrameClass::System,
788 ),
789 (
790 Frame::FunctionCallCancel {
791 function_name: "f".into(),
792 tool_call_id: "1".into(),
793 },
794 FrameClass::System,
795 ),
796 (Frame::OutputAudio(a.clone()), FrameClass::Data),
798 (
799 Frame::TtsAudio {
800 audio: a.clone(),
801 context_id: None,
802 },
803 FrameClass::Data,
804 ),
805 (Frame::Text("t".into()), FrameClass::Data),
806 (Frame::LlmText("t".into()), FrameClass::Data),
807 (
808 Frame::Transcription {
809 text: "t".into(),
810 user_id: uid.clone(),
811 language: None,
812 final_: true,
813 },
814 FrameClass::Data,
815 ),
816 (
817 Frame::InterimTranscription {
818 text: "t".into(),
819 user_id: uid.clone(),
820 language: None,
821 },
822 FrameClass::Data,
823 ),
824 (
825 Frame::TtsSpeak {
826 text: "t".into(),
827 append_to_context: None,
828 },
829 FrameClass::Data,
830 ),
831 (
832 Frame::TtsText {
833 text: "t".into(),
834 context_id: None,
835 },
836 FrameClass::Data,
837 ),
838 (
839 Frame::FunctionCallResult(FunctionCallResult {
840 function_name: "f".into(),
841 tool_call_id: "1".into(),
842 result: serde_json::json!({}),
843 }),
844 FrameClass::Data,
845 ),
846 (Frame::LlmRun, FrameClass::Data),
847 (
848 Frame::LlmContext(Arc::new(LlmContext::default())),
849 FrameClass::Data,
850 ),
851 (
852 Frame::OutputDtmf(vec![KeypadEntry::Pound]),
853 FrameClass::Data,
854 ),
855 (Frame::End { reason: None }, FrameClass::Control),
857 (Frame::Stop, FrameClass::Control),
858 (Frame::LlmResponseStart, FrameClass::Control),
859 (Frame::LlmResponseEnd, FrameClass::Control),
860 (Frame::TtsStarted { context_id: None }, FrameClass::Control),
861 (Frame::TtsStopped { context_id: None }, FrameClass::Control),
862 (
863 Frame::UpdateSettings {
864 target: ServiceKind::Llm,
865 settings: serde_json::json!({}),
866 },
867 FrameClass::Control,
868 ),
869 (
870 Frame::SpeechControlParams {
871 vad: None,
872 turn: None,
873 },
874 FrameClass::Control,
875 ),
876 (Frame::Heartbeat { timestamp_ns: 0 }, FrameClass::Control),
877 (Frame::OutputTransportReady, FrameClass::Control),
878 (Frame::FilterEnable(true), FrameClass::Control),
879 (Frame::MixerEnable(false), FrameClass::Control),
880 ];
881 for (frame, expected) in cases {
882 assert_eq!(
883 frame.class(),
884 expected,
885 "class() mismatch for {}",
886 frame.name()
887 );
888 }
889 }
890
891 #[test]
892 fn uninterruptible_table() {
893 assert!(Frame::End { reason: None }.uninterruptible());
895 assert!(Frame::Stop.uninterruptible());
896 assert!(Frame::FunctionCallResult(FunctionCallResult {
897 function_name: "f".into(),
898 tool_call_id: "1".into(),
899 result: serde_json::json!({}),
900 })
901 .uninterruptible());
902 assert!(Frame::UpdateSettings {
903 target: ServiceKind::All,
904 settings: serde_json::json!({}),
905 }
906 .uninterruptible());
907 assert!(!Frame::Interruption.uninterruptible());
909 assert!(!Frame::Text("t".into()).uninterruptible());
910 assert!(!Frame::OutputAudio(Arc::new(AudioFrame::mono(vec![0], 8000))).uninterruptible());
911 assert!(!Frame::Start(StartParams::default()).uninterruptible());
912 }
913
914 #[test]
915 fn custom_frame_delegates_class_and_uninterruptible() {
916 let c = Frame::Custom(Arc::new(MyCustom {
917 class: FrameClass::Control,
918 keep: true,
919 payload: 7,
920 }));
921 assert_eq!(c.class(), FrameClass::Control);
922 assert!(c.uninterruptible());
923 assert_eq!(c.name(), "MyCustom");
924 assert_eq!(c.kind(), FrameKind::Custom);
925
926 let d = Frame::Custom(Arc::new(MyCustom {
927 class: FrameClass::Data,
928 keep: false,
929 payload: 9,
930 }));
931 assert_eq!(d.class(), FrameClass::Data);
932 assert!(!d.uninterruptible());
933
934 if let Frame::Custom(inner) = &d {
936 let mc = inner.as_any().downcast_ref::<MyCustom>().expect("downcast");
937 assert_eq!(mc.payload, 9);
938 } else {
939 panic!("expected Custom");
940 }
941 }
942
943 #[test]
944 fn frame_ids_are_monotonic_and_unique() {
945 let a = next_frame_id();
946 let b = next_frame_id();
947 let c = next_frame_id();
948 assert!(a < b && b < c);
949 }
950
951 #[test]
952 fn audio_chunk_roundtrips_to_audio_frame() {
953 let chunk = crate::types::AudioChunk::new(vec![1, 2, 3], 16_000);
954 let frame: AudioFrame = (&chunk).into();
955 assert_eq!(frame.num_channels, 1);
956 assert_eq!(frame.pcm, vec![1, 2, 3]);
957 let back: crate::types::AudioChunk = (&frame).into();
958 assert_eq!(back, chunk);
959 }
960}