This commit is contained in:
2026-03-09 14:29:22 +01:00
parent 2461dd32e3
commit 80585040d7

View File

@@ -10,6 +10,7 @@ const state = {
currentIndex: 0, currentIndex: 0,
questionQueue: [], questionQueue: [],
transcript: '', transcript: '',
finalTranscript: '',
isRecording: false, isRecording: false,
isChecking: false, isChecking: false,
sessionHistory: [], sessionHistory: [],
@@ -101,6 +102,7 @@ function loadQuestion() {
function resetPracticeUI() { function resetPracticeUI() {
state.transcript = ''; state.transcript = '';
state.finalTranscript = '';
transcriptBox.contentEditable = 'false'; transcriptBox.contentEditable = 'false';
updateTranscriptBox(''); updateTranscriptBox('');
feedbackBox.classList.remove('visible'); feedbackBox.classList.remove('visible');
@@ -140,18 +142,15 @@ speakBtn.addEventListener('click', () => {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = null; let recognition = null;
function initRecognition() { function createRecognition() {
if (!SpeechRecognition) return null; if (!SpeechRecognition) return null;
const r = new SpeechRecognition(); const r = new SpeechRecognition();
r.lang = 'de-DE'; r.lang = 'de-DE';
r.continuous = true; r.continuous = false; // false is more reliable on Android
r.interimResults = true; r.interimResults = true;
r.maxAlternatives = 1; r.maxAlternatives = 1;
let finalText = '';
r.onstart = () => { r.onstart = () => {
finalText = state.transcript;
recordBtn.classList.add('recording'); recordBtn.classList.add('recording');
recordHint.textContent = 'Tippen zum Stoppen'; recordHint.textContent = 'Tippen zum Stoppen';
state.isRecording = true; state.isRecording = true;
@@ -159,14 +158,18 @@ function initRecognition() {
r.onresult = (e) => { r.onresult = (e) => {
let interim = ''; let interim = '';
let finalChunk = '';
for (let i = e.resultIndex; i < e.results.length; i++) { for (let i = e.resultIndex; i < e.results.length; i++) {
if (e.results[i].isFinal) { if (e.results[i].isFinal) {
finalText += (finalText ? ' ' : '') + e.results[i][0].transcript; finalChunk += (finalChunk ? ' ' : '') + e.results[i][0].transcript;
} else { } else {
interim += e.results[i][0].transcript; 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) { if (display.trim().split(/\s+/).length >= MAX_RECORD_WORDS) {
stopRecording(); stopRecording();
return; return;
@@ -177,12 +180,19 @@ function initRecognition() {
}; };
r.onend = () => { r.onend = () => {
if (state.isRecording) { if (!state.isRecording) return;
// Android stops recognition on its own after silence — restart it // Android/Chrome ends session after each phrase — restart to keep listening
try { r.start(); } catch (_) {} 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; return r;
} }
@@ -205,7 +215,7 @@ async function startRecording() {
// Start SpeechRecognition first — triggers mic permission dialog on Android // Start SpeechRecognition first — triggers mic permission dialog on Android
transcriptBox.contentEditable = 'false'; transcriptBox.contentEditable = 'false';
recognition = initRecognition(); recognition = createRecognition();
state.isRecording = true; state.isRecording = true;
try { recognition.start(); } catch (_) {} try { recognition.start(); } catch (_) {}
@@ -280,6 +290,7 @@ downloadBtn.addEventListener('click', () => {
clearBtn.addEventListener('click', () => { clearBtn.addEventListener('click', () => {
stopRecording(); stopRecording();
state.transcript = ''; state.transcript = '';
state.finalTranscript = '';
transcriptBox.contentEditable = 'false'; transcriptBox.contentEditable = 'false';
updateTranscriptBox(''); updateTranscriptBox('');
checkBtn.disabled = true; checkBtn.disabled = true;