From 80585040d708f1ea84902a782b9ef635b6c06559 Mon Sep 17 00:00:00 2001 From: balex Date: Mon, 9 Mar 2026 14:29:22 +0100 Subject: [PATCH] fix bug --- src/public/js/app.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/public/js/app.js b/src/public/js/app.js index c51f985..33d545c 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -10,6 +10,7 @@ const state = { currentIndex: 0, questionQueue: [], transcript: '', + finalTranscript: '', isRecording: false, isChecking: false, sessionHistory: [], @@ -101,6 +102,7 @@ function loadQuestion() { function resetPracticeUI() { state.transcript = ''; + state.finalTranscript = ''; transcriptBox.contentEditable = 'false'; updateTranscriptBox(''); feedbackBox.classList.remove('visible'); @@ -140,18 +142,15 @@ speakBtn.addEventListener('click', () => { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; let recognition = null; -function initRecognition() { +function createRecognition() { if (!SpeechRecognition) return null; const r = new SpeechRecognition(); r.lang = 'de-DE'; - r.continuous = true; + r.continuous = false; // false is more reliable on Android r.interimResults = true; r.maxAlternatives = 1; - let finalText = ''; - r.onstart = () => { - finalText = state.transcript; recordBtn.classList.add('recording'); recordHint.textContent = 'Tippen zum Stoppen'; state.isRecording = true; @@ -159,14 +158,18 @@ function initRecognition() { r.onresult = (e) => { let interim = ''; + let finalChunk = ''; for (let i = e.resultIndex; i < e.results.length; i++) { if (e.results[i].isFinal) { - finalText += (finalText ? ' ' : '') + e.results[i][0].transcript; + finalChunk += (finalChunk ? ' ' : '') + e.results[i][0].transcript; } else { interim += e.results[i][0].transcript; } } - const display = finalText + (interim ? (finalText ? ' ' : '') + interim : ''); + if (finalChunk) { + state.finalTranscript += (state.finalTranscript ? ' ' : '') + finalChunk; + } + const display = state.finalTranscript + (interim ? (state.finalTranscript ? ' ' : '') + interim : ''); if (display.trim().split(/\s+/).length >= MAX_RECORD_WORDS) { stopRecording(); return; @@ -177,12 +180,19 @@ function initRecognition() { }; r.onend = () => { - if (state.isRecording) { - // Android stops recognition on its own after silence — restart it - try { r.start(); } catch (_) {} - } + if (!state.isRecording) return; + // Android/Chrome ends session after each phrase — restart to keep listening + setTimeout(() => { + if (!state.isRecording) return; + recognition = createRecognition(); + try { recognition.start(); } catch (_) { stopRecording(); } + }, 80); + }; + + // 'aborted' fires during normal restart cycle — ignore it + r.onerror = (e) => { + if (e.error !== 'no-speech' && e.error !== 'aborted') stopRecording(); }; - r.onerror = (e) => { if (e.error !== 'no-speech') stopRecording(); }; return r; } @@ -205,7 +215,7 @@ async function startRecording() { // Start SpeechRecognition first — triggers mic permission dialog on Android transcriptBox.contentEditable = 'false'; - recognition = initRecognition(); + recognition = createRecognition(); state.isRecording = true; try { recognition.start(); } catch (_) {} @@ -280,6 +290,7 @@ downloadBtn.addEventListener('click', () => { clearBtn.addEventListener('click', () => { stopRecording(); state.transcript = ''; + state.finalTranscript = ''; transcriptBox.contentEditable = 'false'; updateTranscriptBox(''); checkBtn.disabled = true;