This commit is contained in:
2026-03-09 14:46:34 +01:00
parent 11806ea614
commit 26cf79a0f7

View File

@@ -150,6 +150,11 @@ function createRecognition() {
r.interimResults = true; r.interimResults = true;
r.maxAlternatives = 1; 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
r.onstart = () => { r.onstart = () => {
recordBtn.classList.add('recording'); recordBtn.classList.add('recording');
recordHint.textContent = 'Tippen zum Stoppen'; recordHint.textContent = 'Tippen zum Stoppen';
@@ -158,18 +163,19 @@ function createRecognition() {
r.onresult = (e) => { r.onresult = (e) => {
let interim = ''; let interim = '';
let finalChunk = ''; for (let i = 0; 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) {
finalChunk += (finalChunk ? ' ' : '') + e.results[i][0].transcript; if (i > highestFinalIndex) {
sessionFinal += (sessionFinal ? ' ' : '') + e.results[i][0].transcript;
highestFinalIndex = i;
}
} else { } else {
interim += e.results[i][0].transcript; interim = e.results[i][0].transcript; // only the latest interim matters
} }
} }
if (finalChunk) { const base = state.finalTranscript;
state.finalTranscript += (state.finalTranscript ? ' ' : '') + finalChunk; const combined = base + (base && sessionFinal ? ' ' : '') + sessionFinal;
} const display = combined + (interim ? (combined ? ' ' : '') + interim : '');
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;
@@ -181,12 +187,15 @@ function createRecognition() {
r.onend = () => { r.onend = () => {
if (!state.isRecording) return; if (!state.isRecording) return;
// Android/Chrome ends session after each phrase — restart to keep listening // Merge this session's final text into the persistent accumulator before restarting
if (sessionFinal) {
state.finalTranscript += (state.finalTranscript ? ' ' : '') + sessionFinal;
}
setTimeout(() => { setTimeout(() => {
if (!state.isRecording) return; if (!state.isRecording) return;
recognition = createRecognition(); recognition = createRecognition();
try { recognition.start(); } catch (_) { stopRecording(); } try { recognition.start(); } catch (_) { stopRecording(); }
}, 80); }, 100);
}; };
// 'aborted' fires during normal restart cycle — ignore it // 'aborted' fires during normal restart cycle — ignore it