gemini_genai_rs/turn/
turn_detection.rs1use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone)]
10pub struct TurnDetectionConfig {
11 pub end_of_speech_delay_ms: u64,
13 pub enabled: bool,
16}
17
18impl Default for TurnDetectionConfig {
19 fn default() -> Self {
20 Self {
21 end_of_speech_delay_ms: 300,
22 enabled: true,
23 }
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum TurnDetectionEvent {
30 SpeechStarted,
32 TurnEnded,
34}
35
36pub struct TurnDetector {
38 config: TurnDetectionConfig,
39 is_speaking: bool,
41 speech_ended_at: Option<Instant>,
43 turn_ended_emitted: bool,
45}
46
47impl TurnDetector {
48 pub fn new(config: TurnDetectionConfig) -> Self {
50 Self {
51 config,
52 is_speaking: false,
53 speech_ended_at: None,
54 turn_ended_emitted: false,
55 }
56 }
57
58 pub fn update(&mut self, vad_is_speaking: bool) -> Option<TurnDetectionEvent> {
62 if !self.config.enabled {
63 return None;
64 }
65
66 if vad_is_speaking && !self.is_speaking {
67 self.is_speaking = true;
69 self.speech_ended_at = None;
70 self.turn_ended_emitted = false;
71 return Some(TurnDetectionEvent::SpeechStarted);
72 }
73
74 if !vad_is_speaking && self.is_speaking {
75 self.is_speaking = false;
77 self.speech_ended_at = Some(Instant::now());
78 }
79
80 if let Some(ended_at) = self.speech_ended_at
82 && !self.turn_ended_emitted
83 && ended_at.elapsed() >= Duration::from_millis(self.config.end_of_speech_delay_ms)
84 {
85 self.turn_ended_emitted = true;
86 self.speech_ended_at = None;
87 return Some(TurnDetectionEvent::TurnEnded);
88 }
89
90 None
91 }
92
93 pub fn is_speaking(&self) -> bool {
95 self.is_speaking
96 }
97
98 pub fn is_pending_turn_end(&self) -> bool {
100 self.speech_ended_at.is_some() && !self.turn_ended_emitted
101 }
102
103 pub fn reset(&mut self) {
105 self.is_speaking = false;
106 self.speech_ended_at = None;
107 self.turn_ended_emitted = false;
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use std::thread;
115
116 #[test]
117 fn speech_start_detected() {
118 let mut detector = TurnDetector::new(TurnDetectionConfig::default());
119
120 let event = detector.update(true);
121 assert_eq!(event, Some(TurnDetectionEvent::SpeechStarted));
122 assert!(detector.is_speaking());
123 }
124
125 #[test]
126 fn turn_end_after_delay() {
127 let mut detector = TurnDetector::new(TurnDetectionConfig {
128 end_of_speech_delay_ms: 50,
129 enabled: true,
130 });
131
132 detector.update(true);
134
135 detector.update(false);
137 assert!(detector.is_pending_turn_end());
138
139 let event = detector.update(false);
141 assert!(event.is_none() || matches!(event, Some(TurnDetectionEvent::TurnEnded)));
142
143 thread::sleep(Duration::from_millis(60));
145 let event = detector.update(false);
146 assert_eq!(event, Some(TurnDetectionEvent::TurnEnded));
147 }
148
149 #[test]
150 fn speech_resume_cancels_turn_end() {
151 let mut detector = TurnDetector::new(TurnDetectionConfig {
152 end_of_speech_delay_ms: 200,
153 enabled: true,
154 });
155
156 detector.update(true);
158 detector.update(false);
159 assert!(detector.is_pending_turn_end());
160
161 let event = detector.update(true);
163 assert_eq!(event, Some(TurnDetectionEvent::SpeechStarted));
164 assert!(!detector.is_pending_turn_end());
165 }
166
167 #[test]
168 fn disabled_detector_emits_nothing() {
169 let mut detector = TurnDetector::new(TurnDetectionConfig {
170 enabled: false,
171 ..Default::default()
172 });
173
174 assert!(detector.update(true).is_none());
175 assert!(detector.update(false).is_none());
176 }
177}