From f74d2fadb242693076a2efba067b55d631a0eed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Schl=C3=BCter?= Date: Sat, 20 Jun 2026 21:28:39 +0200 Subject: [PATCH] fix(stt): iOS-Live-Vorschau wiederhergestellt + klare Server-Fallback-Meldung - resolveSttMode behandelt iOS-Safari (keine available()-API) wieder als "local" -> on-device SpeechRecognition mit Live-Interim funktioniert auf iPhone wieder. - Server-Fallback zeigt jetzt "Server-Erkennung. Live-Vorschau nur mit lokalem/ Cloud-STT" (startRecording optionaler Status-Text) statt generischem Hinweis. Co-Authored-By: Claude Opus 4.8 --- app/web/app.js | 20 ++++++++++++-------- app/web/index.html | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/web/app.js b/app/web/app.js index b8615df..6ad9a9f 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -172,6 +172,7 @@ let recognition = null; // "server" (Whisper-Fallback). Nutzt die Chrome-API available()/install() für die // On-Device-Sprachpakete — verhindert "language not supported", wenn das Paket fehlt. async function resolveSttMode(bcp) { + // Chrome (neue API): prüft, ob das On-Device-Sprachpaket vorhanden ist. if (SR && typeof SR.available === "function") { try { const a = await SR.available({ langs: [bcp], processLocally: true }); @@ -180,17 +181,22 @@ async function resolveSttMode(bcp) { // On-Device-Paket anstoßen (für künftige Turns); jetzt Cloud/Server. try { if (typeof SR.install === "function") SR.install({ langs: [bcp], processLocally: true }); } catch (e) {} } + return meAllowCloudStt ? "cloud" : "server"; } catch (e) { /* API-Fehler -> unten */ } } + // Keine available()-API: iOS-Safari erkennt on-device (mit Live-Vorschau) -> lokal. + if (isIOS()) return "local"; + // Sonst (z. B. älteres Chrome): SpeechRecognition liefe über die Cloud. return meAllowCloudStt ? "cloud" : "server"; } +const SERVER_STT_MSG = "Server-Erkennung. Live-Vorschau nur mit lokalem/Cloud-STT"; + async function startDeviceStt() { const bcp = LANG_BCP47[langSel.value] || "de-DE"; const mode = await resolveSttMode(bcp); if (mode === "server") { // lokal (für diese Sprache) nicht da & Cloud verboten - statusEl.textContent = "erkenne über Server …"; - startRecording(); + startRecording(SERVER_STT_MSG); return; } const rec = new SR(); @@ -216,8 +222,7 @@ async function startDeviceStt() { if (err === "no-speech") { setMicIdle(); statusEl.textContent = "nichts gehört — bitte erneut"; return; } // Sprache/Dienst/Netz nicht verfügbar -> sauber auf Server-STT zurückfallen statt Fehler. if (err === "language-not-supported" || err === "service-not-allowed" || err === "network") { - statusEl.textContent = "erkenne über Server …"; - startRecording(); + startRecording(SERVER_STT_MSG); return; } setMicIdle(); @@ -232,8 +237,7 @@ async function startDeviceStt() { }; try { rec.start(); } catch (e) { recognition = null; - statusEl.textContent = "erkenne über Server …"; - startRecording(); // Start scheiterte -> Server-Fallback + startRecording(SERVER_STT_MSG); // Start scheiterte -> Server-Fallback } } function stopDeviceStt() { if (recognition) { try { recognition.stop(); } catch (e) {} } } @@ -592,7 +596,7 @@ formEl.addEventListener("submit", (e) => { let mediaRecorder = null; let recChunks = []; -async function startRecording() { +async function startRecording(statusText) { if (busy) return; const md = navigator.mediaDevices; if (!md || !md.getUserMedia) { @@ -630,7 +634,7 @@ async function startRecording() { }; mediaRecorder.start(); setMicRecording(); - statusEl.textContent = "Aufnahme … (zum Stoppen erneut tippen)"; + statusEl.textContent = statusText || "Aufnahme … (zum Stoppen erneut tippen)"; } function stopRecording() { diff --git a/app/web/index.html b/app/web/index.html index 6d5fae0..e408da1 100644 --- a/app/web/index.html +++ b/app/web/index.html @@ -257,6 +257,6 @@
- +