fix: setup voice before recv_loop, drain responses, check ws state

Root cause: recv_loop started before open_voice completed, bridge
connection died during UI transition. Now setup completes first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hailin 2026-04-06 05:13:54 -07:00
parent 216f2fe6a0
commit ec17c085b2
1 changed files with 22 additions and 11 deletions

View File

@ -100,14 +100,25 @@ class BridgeClient:
except Exception as e: except Exception as e:
log.error(f"Bridge recv error: {e}") log.error(f"Bridge recv error: {e}")
async def _send_and_wait(self, cmd, wait_secs=1):
"""Send command and consume the response."""
self.send_cmd(cmd)
await asyncio.sleep(wait_secs)
# Drain any pending responses
while True:
try:
ftype, data = await asyncio.wait_for(self._recv_frame(), timeout=0.5)
if ftype == 1:
msg = json.loads(data.decode())
log.info(f"Bridge: {msg}")
except asyncio.TimeoutError:
break
async def setup_voice(self): async def setup_voice(self):
"""Open voice chat, start capture, enable inject.""" """Open voice chat, start capture, enable inject."""
self.send_cmd({"cmd": "open_voice"}) await self._send_and_wait({"cmd": "open_voice"}, wait_secs=3)
await asyncio.sleep(3) await self._send_and_wait({"cmd": "start"}, wait_secs=1)
self.send_cmd({"cmd": "start"}) await self._send_and_wait({"cmd": "inject_on"}, wait_secs=0.5)
await asyncio.sleep(1)
self.send_cmd({"cmd": "inject_on"})
await asyncio.sleep(0.5)
log.info("Voice bridge ready (inject mode)") log.info("Voice bridge ready (inject mode)")
async def close(self): async def close(self):
@ -144,15 +155,15 @@ class Relay:
self.opus_decoder = opuslib.Decoder(ESP_SAMPLE_RATE, 1) self.opus_decoder = opuslib.Decoder(ESP_SAMPLE_RATE, 1)
self.opus_encoder = opuslib.Encoder(ESP_SAMPLE_RATE, 1, opuslib.APPLICATION_AUDIO) self.opus_encoder = opuslib.Encoder(ESP_SAMPLE_RATE, 1, opuslib.APPLICATION_AUDIO)
# Connect to voice bridge # Connect to voice bridge and setup voice chat first
self.bridge = BridgeClient(self.bridge_host, self.bridge_port) self.bridge = BridgeClient(self.bridge_host, self.bridge_port)
await self.bridge.connect() await self.bridge.connect()
await self.bridge.setup_voice()
# Now start receiving speaker audio
self.bridge.on_speaker_frame = self._on_speaker_frame self.bridge.on_speaker_frame = self._on_speaker_frame
recv_task = asyncio.create_task(self.bridge.start_recv_loop()) recv_task = asyncio.create_task(self.bridge.start_recv_loop())
# Setup voice chat
await self.bridge.setup_voice()
try: try:
async for message in websocket: async for message in websocket:
if isinstance(message, str): if isinstance(message, str):
@ -226,7 +237,7 @@ class Relay:
async def _on_speaker_frame(self, pcm_bytes): async def _on_speaker_frame(self, pcm_bytes):
"""Receive speaker PCM from bridge, resample, encode Opus, send to ESP32.""" """Receive speaker PCM from bridge, resample, encode Opus, send to ESP32."""
if not self.ws: if not self.ws or self.ws.closed:
return return
try: try:
samples = np.frombuffer(pcm_bytes, dtype=np.int16) samples = np.frombuffer(pcm_bytes, dtype=np.int16)