fix(tts): Geräte-TTS fällt auf Server-Audio zurück, wenn keine Stimmen vorhanden
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 <noreply@anthropic.com>
This commit is contained in:
parent
7bac2952c5
commit
d39fc40e94
2 changed files with 35 additions and 5 deletions
|
|
@ -79,16 +79,46 @@ function _makeUtterance(text, lang) {
|
||||||
return u;
|
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).
|
// Vorlesen ohne Button-Status (automatisches Vorlesen der Antwort).
|
||||||
function deviceSpeak(text, lang) {
|
function deviceSpeak(text, lang) {
|
||||||
if (!TTS_SUPPORTED || !text) return;
|
if (!text) return;
|
||||||
|
if (deviceVoicesReady()) {
|
||||||
speechSynthesis.cancel();
|
speechSynthesis.cancel();
|
||||||
speechSynthesis.speak(_makeUtterance(text, lang));
|
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.
|
// Vorlesen mit Button-Status (Replay): Icon -> ⏹ während des Sprechens, danach zurück.
|
||||||
function deviceSpeakBtn(text, lang, btn) {
|
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();
|
speechSynthesis.cancel();
|
||||||
const u = _makeUtterance(text, lang);
|
const u = _makeUtterance(text, lang);
|
||||||
u.onend = u.onerror = () => setReplayPlaying(btn, false);
|
u.onend = u.onerror = () => setReplayPlaying(btn, false);
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,6 @@
|
||||||
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/app.js?v=38"></script>
|
<script src="/app.js?v=39"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue