mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
barcode scanner beginnings
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// Scanner Speech Hook
|
||||
// Purpose: Speaks server-provided scan labels on the scanner computer.
|
||||
// Scope: Keeps browser TTS as a local output device while leaving scan meaning and phrase selection on the server.
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const SPEECH_START_TIMEOUT_MS = 1500;
|
||||
const SPEECH_RETRY_DELAY_MS = 700;
|
||||
|
||||
export default function useScannerSpeech(scan) {
|
||||
const retryTimerRef = useRef(null);
|
||||
const startTimerRef = useRef(null);
|
||||
const spokenScanKeyRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
window.clearTimeout(retryTimerRef.current);
|
||||
window.clearTimeout(startTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const speechText = String(scan?.speechText || '').trim();
|
||||
const scanKey = scan?.scannedAt ? `${scan.scannedAt}:${speechText}` : '';
|
||||
if (!speechText || !scanKey || spokenScanKeyRef.current === scanKey) return undefined;
|
||||
|
||||
let cancelled = false;
|
||||
spokenScanKeyRef.current = scanKey;
|
||||
|
||||
function clearSpeechTimers() {
|
||||
window.clearTimeout(retryTimerRef.current);
|
||||
window.clearTimeout(startTimerRef.current);
|
||||
retryTimerRef.current = null;
|
||||
startTimerRef.current = null;
|
||||
}
|
||||
|
||||
function retryLater() {
|
||||
if (cancelled) return;
|
||||
clearSpeechTimers();
|
||||
retryTimerRef.current = window.setTimeout(() => speakOnce(), SPEECH_RETRY_DELAY_MS);
|
||||
}
|
||||
|
||||
function speakOnce() {
|
||||
if (cancelled) return;
|
||||
const synth = window.speechSynthesis;
|
||||
if (!synth || typeof window.SpeechSynthesisUtterance !== 'function') {
|
||||
retryLater();
|
||||
return;
|
||||
}
|
||||
|
||||
clearSpeechTimers();
|
||||
// Cancelling before retrying prevents stale utterances from piling up if a
|
||||
// browser reports an error or never fires the expected start callback.
|
||||
synth.cancel();
|
||||
const utterance = new window.SpeechSynthesisUtterance(speechText);
|
||||
utterance.rate = 0.92;
|
||||
utterance.pitch = 1;
|
||||
utterance.volume = 1;
|
||||
utterance.onstart = () => {
|
||||
window.clearTimeout(startTimerRef.current);
|
||||
startTimerRef.current = null;
|
||||
};
|
||||
utterance.onend = () => {
|
||||
clearSpeechTimers();
|
||||
};
|
||||
utterance.onerror = () => {
|
||||
retryLater();
|
||||
};
|
||||
synth.speak(utterance);
|
||||
|
||||
// Some browser/audio states fail without surfacing onerror. A small start
|
||||
// watchdog keeps the required scanner audio from silently dying, while the
|
||||
// UI remains clean and rover-readable.
|
||||
startTimerRef.current = window.setTimeout(() => {
|
||||
synth.cancel();
|
||||
retryLater();
|
||||
}, SPEECH_START_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
speakOnce();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearSpeechTimers();
|
||||
};
|
||||
}, [scan]);
|
||||
}
|
||||
Reference in New Issue
Block a user