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
|
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(json.dumps(start_frame))
|
||||||
await ws.send(wav)
|
await ws.send(wav)
|
||||||
await ws.send(json.dumps({"type": "end"}))
|
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')})")
|
print(f" ⚠ NOTFALL erkannt (Kategorie: {event.get('category')})")
|
||||||
elif etype == "error":
|
elif etype == "error":
|
||||||
print(f" Fehler {event.get('status','')}: {event.get('detail')}")
|
print(f" Fehler {event.get('status','')}: {event.get('detail')}")
|
||||||
return
|
return b""
|
||||||
elif etype == "done":
|
elif etype == "done":
|
||||||
break
|
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:
|
if audio:
|
||||||
play_pcm(audio)
|
play_pcm(audio)
|
||||||
|
|
||||||
|
|
@ -165,32 +177,35 @@ async def run(args) -> None:
|
||||||
url += f"&token={args.token}"
|
url += f"&token={args.token}"
|
||||||
start_frame = _start_frame(args)
|
start_frame = _start_frame(args)
|
||||||
|
|
||||||
async with websockets.connect(url, max_size=None) as ws:
|
if args.file:
|
||||||
print(f"Verbunden: {args.url} (Session '{args.session}')")
|
with open(args.file, "rb") as fh:
|
||||||
if args.file:
|
wav = fh.read()
|
||||||
with open(args.file, "rb") as fh:
|
print(f"Sende Datei: {args.file}")
|
||||||
wav = fh.read()
|
await _send_and_play(url, wav, start_frame)
|
||||||
print(f"Sende Datei: {args.file}")
|
return
|
||||||
await one_turn(ws, wav, start_frame)
|
|
||||||
|
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.")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
wav = record_utterance(recorder, args.device, args.rate)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nEnde.")
|
||||||
return
|
return
|
||||||
recorder = resolve_recorder(args.recorder)
|
if len(wav) < 1000:
|
||||||
print(f"Aufnahme mit: {recorder}"
|
print(" ⚠ Keine/zu kurze Aufnahme. Moegliche Ursache: Aufnahmewerkzeug"
|
||||||
+ (f" (Gerät: {args.device})" if args.device else " (Standardgerät)"))
|
" oder Geraet nicht nutzbar.")
|
||||||
print("Sprich nach 'START', stoppe mit Enter. Strg+C beendet den Loop.")
|
print(" Verfuegbare Mikrofone: arecord -l")
|
||||||
while True:
|
print(" Direktes ALSA-Geraet verwenden, z. B.:")
|
||||||
try:
|
print(" python scripts/voice_loop.py --recorder arecord --device plughw:2,0")
|
||||||
wav = record_utterance(recorder, args.device, args.rate)
|
continue
|
||||||
except KeyboardInterrupt:
|
try:
|
||||||
print("\nEnde.")
|
await _send_and_play(url, wav, start_frame)
|
||||||
return
|
except Exception as exc: # noqa: BLE001 - Verbindung pro Runde; Fehler nicht fatal
|
||||||
if len(wav) < 1000:
|
print(f" Verbindungsfehler: {exc}")
|
||||||
print(" ⚠ Keine/zu kurze Aufnahme. Moegliche Ursache: Aufnahmewerkzeug"
|
|
||||||
" oder Geraet nicht nutzbar.")
|
|
||||||
print(" Verfuegbare Mikrofone: arecord -l")
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue