diff --git a/app/web/app.js b/app/web/app.js index c575b65..7371de8 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -151,11 +151,29 @@ function deviceSpeakBtn(text, lang, btn) { speechSynthesis.speak(u); } -// iOS: speechSynthesis darf nur nach einer Nutzergeste starten -> beim ersten Tap freischalten. +// iOS: sowohl SpeechSynthesis (Geräte-TTS) als auch der Web-Audio-Kontext (Server-Chunks) +// dürfen nur nach einer Nutzergeste starten -> beim ersten Tap beide freischalten. let _ttsUnlocked = false; +let _audioUnlocked = false; function unlockTTS() { - if (_ttsUnlocked || !TTS_SUPPORTED) return; - try { speechSynthesis.speak(new SpeechSynthesisUtterance("")); _ttsUnlocked = true; } catch (e) {} + // Geräte-TTS per Leer-Utterance freischalten. + if (TTS_SUPPORTED && !_ttsUnlocked) { + try { speechSynthesis.speak(new SpeechSynthesisUtterance("")); _ttsUnlocked = true; } catch (e) {} + } + // Web-Audio-Kontext jetzt (in der Geste) anlegen + per Stille-Buffer aufwecken, damit + // die satzweise Chunk-Wiedergabe in den Server-Modi auch auf iOS läuft (sonst bleibt ein + // erst im WS-Callback erzeugter AudioContext dort "suspended"). + if (!_audioUnlocked) { + try { + audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)(); + if (audioCtx.state === "suspended") audioCtx.resume(); + const src = audioCtx.createBufferSource(); + src.buffer = audioCtx.createBuffer(1, 1, 22050); // 1 Sample Stille + src.connect(audioCtx.destination); + src.start(0); + _audioUnlocked = true; + } catch (e) {} + } } const PLAYBACK_KEY = "va-playback"; // geräte-lokal: "device" wenn Geräte-TTS gewählt