From 9e349f0b15c2aa8edb18d21bbf5ff80e799fa3c5 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Thu, 12 Feb 2026 01:09:30 -0800 Subject: [PATCH] fix(web): prevent speech result duplication on mobile browsers (#187) --- web/src/components/live-demo.tsx | 37 +++++++++----------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/web/src/components/live-demo.tsx b/web/src/components/live-demo.tsx index 9a6dcc7..9959eca 100644 --- a/web/src/components/live-demo.tsx +++ b/web/src/components/live-demo.tsx @@ -126,8 +126,6 @@ export function LiveDemo() { const [detectedLanguage, setDetectedLanguage] = useState("en-US"); const recognitionRef = useRef(null); const isListeningRef = useRef(false); - // Track the index up to which results have been processed to prevent duplicates on mobile - const processedResultIndexRef = useRef(0); // Keep ref in sync with state for use in event handlers useEffect(() => { @@ -165,33 +163,19 @@ export function LiveDemo() { }; recognition.onresult = (event: SpeechRecognitionEvent) => { - // Get the base index - on some browsers (especially mobile), resultIndex - // may be 0 but the array contains all previous results. We need to - // track which results we've already processed to avoid duplicates. - const startIndex = Math.max(event.resultIndex, processedResultIndexRef.current); + // On mobile browsers, the Web Speech API returns all previous results in the array + // with resultIndex staying at 0. Only process the LAST result which is the new one. + // This prevents the exponential duplication bug where text repeats like: + // "Hey Hey how Hey how are Hey how are you..." + const lastResultIndex = event.results.length - 1; + const result = event.results[lastResultIndex]; + const transcript = result[0].transcript; - let interim = ""; - let final = ""; - - // Only process new results (from startIndex to end) - for (let i = startIndex; i < event.results.length; i++) { - const transcript = event.results[i][0].transcript; - if (event.results[i].isFinal) { - final += transcript + " "; - } else { - interim += transcript; - } - } - - // Update processed index to the end of current results - // Any results before this have been handled - processedResultIndexRef.current = event.results.length; - - if (final) { - setTranscript((prev) => prev + final); + if (result.isFinal) { + setTranscript((prev) => prev + transcript + " "); setInterimTranscript(""); } else { - setInterimTranscript(interim); + setInterimTranscript(transcript); } }; @@ -218,7 +202,6 @@ export function LiveDemo() { if (recognitionRef.current) { setTranscript(""); setInterimTranscript(""); - processedResultIndexRef.current = 0; // Reset processed result index try { recognitionRef.current.start(); } catch (e) {