fix(voice_loop): Keepalive-Timeout (1011) bei langer Aufnahme/Wiedergabe behoben
Eine Dauerverbindung wurde vom Server geschlossen, wenn Aufnahme/Wiedergabe den Event-Loop blockierten (Keepalive-Ping unbeantwortet) - sichtbar bei langen TTS-Antworten. Jetzt: kurze WebSocket-Verbindung PRO Sprech-Runde; das Audio wird erst NACH dem Schliessen abgespielt. Gespraechsgedaechtnis bleibt serverseitig via session_id erhalten. Verifiziert mit langer Antwort (~12 s Audio) ohne Abbruch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
98706ef3ec
commit
4254475b73
1 changed files with 42 additions and 27 deletions
|
|
@ -131,7 +131,9 @@ def _start_frame(args) -> dict:
|
|||
return frame
|
||||
|
||||
|
||||
async def one_turn(ws, wav: bytes, start_frame: dict) -> None:
|
||||
async def one_turn(ws, wav: bytes, start_frame: dict) -> bytes:
|
||||
"""Sendet eine Aeusserung, gibt das Antwort-Audio (PCM) zurueck. Spielt NICHT ab,
|
||||
damit die Wiedergabe ausserhalb der offenen Verbindung passieren kann."""
|
||||
await ws.send(json.dumps(start_frame))
|
||||
await ws.send(wav)
|
||||
await ws.send(json.dumps({"type": "end"}))
|
||||
|
|
@ -152,9 +154,19 @@ async def one_turn(ws, wav: bytes, start_frame: dict) -> None:
|
|||
print(f" ⚠ NOTFALL erkannt (Kategorie: {event.get('category')})")
|
||||
elif etype == "error":
|
||||
print(f" Fehler {event.get('status','')}: {event.get('detail')}")
|
||||
return
|
||||
return b""
|
||||
elif etype == "done":
|
||||
break
|
||||
return audio
|
||||
|
||||
|
||||
async def _send_and_play(url: str, wav: bytes, start_frame: dict) -> None:
|
||||
"""Oeffnet eine kurze Verbindung pro Runde (Gedaechtnis bleibt serverseitig via
|
||||
session_id erhalten), empfaengt die Antwort und spielt sie NACH dem Schliessen ab.
|
||||
So blockiert die (lange) Aufnahme/Wiedergabe nie eine offene WebSocket-Verbindung
|
||||
(kein Keepalive-Timeout)."""
|
||||
async with websockets.connect(url, max_size=None) as ws:
|
||||
audio = await one_turn(ws, wav, start_frame)
|
||||
if audio:
|
||||
play_pcm(audio)
|
||||
|
||||
|
|
@ -165,15 +177,15 @@ async def run(args) -> None:
|
|||
url += f"&token={args.token}"
|
||||
start_frame = _start_frame(args)
|
||||
|
||||
async with websockets.connect(url, max_size=None) as ws:
|
||||
print(f"Verbunden: {args.url} (Session '{args.session}')")
|
||||
if args.file:
|
||||
with open(args.file, "rb") as fh:
|
||||
wav = fh.read()
|
||||
print(f"Sende Datei: {args.file}")
|
||||
await one_turn(ws, wav, start_frame)
|
||||
await _send_and_play(url, wav, start_frame)
|
||||
return
|
||||
|
||||
recorder = resolve_recorder(args.recorder)
|
||||
print(f"Ziel: {args.url} (Session '{args.session}')")
|
||||
print(f"Aufnahme mit: {recorder}"
|
||||
+ (f" (Gerät: {args.device})" if args.device else " (Standardgerät)"))
|
||||
print("Sprich nach 'START', stoppe mit Enter. Strg+C beendet den Loop.")
|
||||
|
|
@ -190,7 +202,10 @@ async def run(args) -> None:
|
|||
print(" Direktes ALSA-Geraet verwenden, z. B.:")
|
||||
print(" python scripts/voice_loop.py --recorder arecord --device plughw:2,0")
|
||||
continue
|
||||
await one_turn(ws, wav, start_frame)
|
||||
try:
|
||||
await _send_and_play(url, wav, start_frame)
|
||||
except Exception as exc: # noqa: BLE001 - Verbindung pro Runde; Fehler nicht fatal
|
||||
print(f" Verbindungsfehler: {exc}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue