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:
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):
"""Open voice chat, start capture, enable inject."""
self.send_cmd({"cmd": "open_voice"})
await asyncio.sleep(3)
self.send_cmd({"cmd": "start"})
await asyncio.sleep(1)
self.send_cmd({"cmd": "inject_on"})
await asyncio.sleep(0.5)
await self._send_and_wait({"cmd": "open_voice"}, wait_secs=3)
await self._send_and_wait({"cmd": "start"}, wait_secs=1)
await self._send_and_wait({"cmd": "inject_on"}, wait_secs=0.5)
log.info("Voice bridge ready (inject mode)")
async def close(self):
@ -144,15 +155,15 @@ class Relay:
self.opus_decoder = opuslib.Decoder(ESP_SAMPLE_RATE, 1)
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)
await self.bridge.connect()
await self.bridge.setup_voice()
# Now start receiving speaker audio
self.bridge.on_speaker_frame = self._on_speaker_frame
recv_task = asyncio.create_task(self.bridge.start_recv_loop())
# Setup voice chat
await self.bridge.setup_voice()
try:
async for message in websocket:
if isinstance(message, str):
@ -226,7 +237,7 @@ class Relay:
async def _on_speaker_frame(self, pcm_bytes):
"""Receive speaker PCM from bridge, resample, encode Opus, send to ESP32."""
if not self.ws:
if not self.ws or self.ws.closed:
return
try:
samples = np.frombuffer(pcm_bytes, dtype=np.int16)