From b30fc10648f65cca8735a942679220d93f472778 Mon Sep 17 00:00:00 2001 From: Lucca Pirovano Date: Thu, 16 Jul 2026 17:36:33 -0400 Subject: [PATCH] fixed voice chat crashing when releasing V --- naowalk.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/naowalk.py b/naowalk.py index c786028..931be4e 100644 --- a/naowalk.py +++ b/naowalk.py @@ -167,6 +167,8 @@ class LiveMicRelay: self.input_device_index = input_device_index self.active = False + # "idle" | "connecting" | "live" - read by the HUD to show status + self.state = "idle" self._ssh_client = None self._ssh_channel = None self._sock = None @@ -186,12 +188,30 @@ class LiveMicRelay: if not HAS_SSH: print("🎙️ Live mic needs paramiko: pip install paramiko") return + # If a previous relay thread is still tearing itself down, wait for + # it to finish before reusing self._stream/_sock/_ssh_* for a new one. + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=2.0) self.active = True + self.state = "connecting" self._thread = threading.Thread(target=self._run, daemon=True) self._thread.start() def stop(self): + """Signal the relay thread to stop and wait for it to exit. + + Deliberately does NOT touch self._stream/_sock/_ssh_* here: this is + called from the main/render thread on key-up, and closing a + PortAudio stream from a different thread while the relay thread is + mid blocking-read() on it is what caused the ALSA segfault on + release. Only _run() (via _cleanup, in its own thread) closes them. + """ self.active = False + self.state = "idle" + if self._thread and self._thread.is_alive() and threading.current_thread() is not self._thread: + self._thread.join(timeout=2.0) + + def _cleanup(self): if self._sock: try: self._sock.close() except: pass @@ -237,6 +257,8 @@ class LiveMicRelay: input_device_index=self.input_device_index, frames_per_buffer=1024) print("🎙️ Talk relay: live") + if self.active: # could've been stopped while we were still connecting + self.state = "live" while self.active: try: @@ -247,7 +269,9 @@ class LiveMicRelay: except Exception as e: print(f"🎙️ Talk relay error: {e}") finally: - self.stop() + self.active = False + self.state = "idle" + self._cleanup() print("🎙️ Talk relay: stopped") # ========================================== @@ -685,6 +709,34 @@ class NaoTeleop: text = self.font.render(f"♪ {self.music.current_title[:45]}", True, (0, 255, 100)) self.screen.blit(text, (10, 10)) + # Voice chat HUD (top-right) - only shown while V is held / connecting + relay_state = getattr(self.mic_relay, "state", "idle") + if relay_state == "connecting": + label, color = "VOICE: CONNECTING...", (255, 200, 0) + elif relay_state == "live": + label, color = "VOICE: LIVE - speak now", (0, 255, 100) + else: + label, color = None, None + + if label: + text = self.font.render(label, True, color) + pad = 8 + box_w, box_h = text.get_width() + pad * 2 + 18, text.get_height() + pad + box_x = self.display_width - box_w - 10 + box_y = 10 + s = pygame.Surface((box_w, box_h)) + s.set_alpha(180) + s.fill((0, 0, 0)) + self.screen.blit(s, (box_x, box_y)) + # pulsing dot for "live" so it's easy to catch out of the corner of your eye + if relay_state == "live": + pulse = 0.5 + 0.5 * abs((pygame.time.get_ticks() % 1000) / 500.0 - 1.0) + dot_color = tuple(int(c * pulse) for c in color) + else: + dot_color = color + pygame.draw.circle(self.screen, dot_color, (box_x + pad + 5, box_y + box_h // 2), 5) + self.screen.blit(text, (box_x + pad + 18, box_y + pad // 2)) + # Music search interface if self.music_search_mode: box_h = min(self.display_height - 80, 260)