diff --git a/src/public/js/app.js b/src/public/js/app.js index a936f1f..e27b4a3 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -146,14 +146,11 @@ function createRecognition() { if (!SpeechRecognition) return null; const r = new SpeechRecognition(); r.lang = 'de-DE'; - r.continuous = true; - r.interimResults = true; + r.continuous = false; // one phrase per session — avoids all Android e.resultIndex bugs + r.interimResults = true; // stream words within the phrase r.maxAlternatives = 1; - // Android Chrome bug: onresult re-fires old results with e.resultIndex=0. - // highestFinalIndex guards against processing the same final result twice. - let highestFinalIndex = -1; - let sessionFinal = ''; // final text committed within this recognition session + let committed = false; // guard: each session commits at most one final result r.onstart = () => { recordBtn.classList.add('recording'); @@ -162,35 +159,27 @@ function createRecognition() { }; r.onresult = (e) => { - let interim = ''; - for (let i = 0; i < e.results.length; i++) { - if (e.results[i].isFinal) { - if (i > highestFinalIndex) { - sessionFinal += (sessionFinal ? ' ' : '') + e.results[i][0].transcript; - highestFinalIndex = i; - } - } else { - interim = e.results[i][0].transcript; // only the latest interim matters - } + if (committed) return; + const result = e.results[0]; + const text = result[0].transcript; + if (result.isFinal) { + committed = true; + state.finalTranscript += (state.finalTranscript ? ' ' : '') + text; + state.transcript = state.finalTranscript; + } else { + // interim — show but don't commit + state.transcript = (state.finalTranscript ? state.finalTranscript + ' ' : '') + text; } - const base = state.finalTranscript; - const combined = base + (base && sessionFinal ? ' ' : '') + sessionFinal; - const display = combined + (interim ? (combined ? ' ' : '') + interim : ''); - if (display.trim().split(/\s+/).length >= MAX_RECORD_WORDS) { + if (state.transcript.trim().split(/\s+/).length >= MAX_RECORD_WORDS) { stopRecording(); return; } - updateTranscriptBox(display); - state.transcript = display; - checkBtn.disabled = !display.trim(); + updateTranscriptBox(state.transcript); + checkBtn.disabled = !state.transcript.trim(); }; r.onend = () => { if (!state.isRecording) return; - // Merge this session's final text into the persistent accumulator before restarting - if (sessionFinal) { - state.finalTranscript += (state.finalTranscript ? ' ' : '') + sessionFinal; - } setTimeout(() => { if (!state.isRecording) return; recognition = createRecognition(); @@ -198,7 +187,6 @@ function createRecognition() { }, 100); }; - // 'aborted' fires during normal restart cycle — ignore it r.onerror = (e) => { if (e.error !== 'no-speech' && e.error !== 'aborted') stopRecording(); };