From d39fc40e94835ac626581acd834d76ffcbb5b417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Schl=C3=BCter?= Date: Sun, 21 Jun 2026 01:02:03 +0200 Subject: [PATCH] =?UTF-8?q?fix(tts):=20Ger=C3=A4te-TTS=20f=C3=A4llt=20auf?= =?UTF-8?q?=20Server-Audio=20zur=C3=BCck,=20wenn=20keine=20Stimmen=20vorha?= =?UTF-8?q?nden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linux-Desktop-Browser haben oft keine speechSynthesis-Stimmen -> "Im Gerät" blieb stumm (auto + Replay). deviceVoicesReady() prüft getVoices(); fehlen Stimmen, holt serverSpeakFallback() Audio via /api/speak (Server-Default, nie "device") und spielt es. Co-Authored-By: Claude Opus 4.8 --- app/web/app.js | 38 ++++++++++++++++++++++++++++++++++---- app/web/index.html | 2 +- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/web/app.js b/app/web/app.js index 1fbd409..95c7cd3 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -79,16 +79,46 @@ function _makeUtterance(text, lang) { return u; } +// Hat der Browser nutzbare Sprachstimmen? (Linux-Desktops haben oft KEINE -> stumm.) +function deviceVoicesReady() { + if (!TTS_SUPPORTED) return false; + if (!_voices.length) loadVoices(); + return _voices.length > 0; +} + +// Server-Fallback: holt Audio via /api/speak (ohne tts_provider -> Server-Default, +// niemals "device") und spielt es ab. Greift, wenn Geräte-TTS keine Stimme hat. +async function serverSpeakFallback(text, lang, btn) { + try { + const body = { text }; + if (lang) body.language = lang; + const r = await fetch("/api/speak", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!r.ok) throw new Error("HTTP " + r.status); + startReplay([await r.arrayBuffer()], btn); + } catch (e) { + setReplayPlaying(btn, false); + statusEl.textContent = "Vorlesen fehlgeschlagen"; + } +} + // Vorlesen ohne Button-Status (automatisches Vorlesen der Antwort). function deviceSpeak(text, lang) { - if (!TTS_SUPPORTED || !text) return; - speechSynthesis.cancel(); - speechSynthesis.speak(_makeUtterance(text, lang)); + if (!text) return; + if (deviceVoicesReady()) { + speechSynthesis.cancel(); + speechSynthesis.speak(_makeUtterance(text, lang)); + } else { + serverSpeakFallback(text, lang, null); // keine Stimmen -> Server-Audio + } } // Vorlesen mit Button-Status (Replay): Icon -> ⏹ während des Sprechens, danach zurück. function deviceSpeakBtn(text, lang, btn) { - if (!TTS_SUPPORTED || !text) { setReplayPlaying(btn, false); return; } + if (!text) { setReplayPlaying(btn, false); return; } + if (!deviceVoicesReady()) { serverSpeakFallback(text, lang, btn); return; } speechSynthesis.cancel(); const u = _makeUtterance(text, lang); u.onend = u.onerror = () => setReplayPlaying(btn, false); diff --git a/app/web/index.html b/app/web/index.html index 18f2b28..002d30f 100644 --- a/app/web/index.html +++ b/app/web/index.html @@ -318,6 +318,6 @@
- +