fix(web): prevent speech result duplication on mobile browsers (#187)

This commit is contained in:
Jatin K Malik
2026-02-12 01:09:30 -08:00
committed by GitHub
parent 25aa0116cd
commit 9e349f0b15
+10 -27
View File
@@ -126,8 +126,6 @@ export function LiveDemo() {
const [detectedLanguage, setDetectedLanguage] = useState<string>("en-US");
const recognitionRef = useRef<SpeechRecognition | null>(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) {