From f99e90f5245b149f2427355b09aaa304a815f3cd Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 11 Jul 2026 20:30:37 +0200 Subject: [PATCH] =?UTF-8?q?fix(voice):=20Doppel-Ausfuehrung=20=E2=80=94=20?= =?UTF-8?q?identischen=20STT-Endpoint-Text=20<5s=20deduppen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Im Auto cancelt der App-seitige No-Speech-Watchdog den conversing-Stream vorzeitig (Whisper-Partials kommen bei Fahrgeraeusch verspaetet) und startet passives Lauschen, waehrend die Bridge dieselbe Aeusserung parallel noch finalisiert. Ergebnis: derselbe Befehl ("nächstes lied") wird zweimal ausgefuehrt — aus zwei verschiedenen audioRequestIds, darum griff die bestehende ID-Idempotenz nicht. Text-Fenster-Dedup: identischer normalisierter Endpoint-Text innerhalb 5s wird verworfen. Serverseitig, kein APK noetig. Co-Authored-By: Claude Opus 4.8 --- bridge/aria_bridge.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index 4812c80..22f7cbd 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -3006,6 +3006,24 @@ class ARIABridge: if self._is_duplicate_client_msg(client_msg_id): return + # Text-Fenster-Dedup: im Auto cancelt der App-No-Speech-Watchdog den + # Stream vorzeitig und startet passives Lauschen, waehrend die Bridge + # dieselbe Aeusserung parallel noch finalisiert → derselbe Befehl + # kaeme zweimal (aus ZWEI verschiedenen audioRequestIds, darum greift + # die ID-Idempotenz oben nicht). Identischen Endpoint-Text innerhalb + # 5s verwerfen. Genuines "nächstes, nächstes" liegt praktisch immer + # weiter auseinander (man hoert den Wechsel erst). + _norm_txt = " ".join(text.lower().split()) + _now_t = asyncio.get_event_loop().time() + if (_norm_txt + and _norm_txt == getattr(self, "_last_endpoint_norm", "") + and (_now_t - getattr(self, "_last_endpoint_at", 0.0)) < 5.0): + logger.info("[rvs] stt_endpoint Dupe verworfen (gleicher Text <5s): %r", + text[:60]) + return + self._last_endpoint_norm = _norm_txt + self._last_endpoint_at = _now_t + # App-Focus aus stt_stream_start-Registry auflösen (falls die # App-Version die projectId noch nicht mitschickt: leer = Hauptchat). stream_req_id = payload.get("requestId", "") or ""