// useBeep.ts import { useEffect, useRef } from "react"; import { useSoundEnabled } from "react-sounds"; // so it respects your SoundBtn toggle /** * Plays a sound whenever `latestRef` changes. * * @param src Path to the sound file * @param latestRef The primitive value to watch (e.g. sighting.ref) * @param opts volume: 0..1, enabledOverride: force enable/disable, minGapMs: throttle interval */ export function useBeep( src: string, latestRef: number | null, opts?: { volume?: number; enabledOverride?: boolean; minGapMs?: number } ) { const audioRef = useRef(undefined); const prevRef = useRef(null); const lastPlay = useRef(0); const [enabled] = useSoundEnabled(); const minGap = opts?.minGapMs ?? 250; // don’t play more than 4 times/sec // Create the audio element once useEffect(() => { const a = new Audio(src); a.preload = "auto"; if (opts?.volume !== undefined) a.volume = opts.volume; audioRef.current = a; return () => { a.pause(); }; }, [src, opts?.volume]); // Watch for ref changes useEffect(() => { if (latestRef == null) return; const canPlay = (opts?.enabledOverride ?? enabled) && document.visibilityState === "visible"; if (!canPlay) { prevRef.current = latestRef; // consume the change return; } if (prevRef.current !== null && latestRef !== prevRef.current) { const now = Date.now(); if (now - lastPlay.current >= minGap) { const a = audioRef.current; if (a) { try { a.currentTime = 0; // restart from beginning void a.play(); // fire and forget lastPlay.current = now; } catch (err) { console.warn("Audio play failed:", err); } } } } prevRef.current = latestRef; }, [latestRef, enabled, opts?.enabledOverride, minGap]); }