76 lines
2.1 KiB
JavaScript
76 lines
2.1 KiB
JavaScript
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Camera torch / flash control.
|
||
|
|
*
|
||
|
|
* @param {React.MutableRefObject<MediaStream|null>} streamRef
|
||
|
|
* A ref holding the active MediaStream from the camera hook.
|
||
|
|
*/
|
||
|
|
export function useScannerFlash(streamRef) {
|
||
|
|
const [flashOn, setFlashOn] = useState(false);
|
||
|
|
const [flashSupported, setFlashSupported] = useState(false);
|
||
|
|
|
||
|
|
const streamIdentityRef = useRef(null);
|
||
|
|
|
||
|
|
const getVideoTrack = useCallback(() => {
|
||
|
|
const stream = streamRef?.current;
|
||
|
|
if (!stream) return null;
|
||
|
|
const tracks = stream.getVideoTracks();
|
||
|
|
return tracks.length > 0 ? tracks[0] : null;
|
||
|
|
}, [streamRef]);
|
||
|
|
|
||
|
|
const probeFlashSupport = useCallback(() => {
|
||
|
|
const track = getVideoTrack();
|
||
|
|
if (!track) {
|
||
|
|
setFlashSupported(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const capabilities = track.getCapabilities?.();
|
||
|
|
setFlashSupported(Boolean(capabilities?.torch));
|
||
|
|
|
||
|
|
const handleEnded = () => {
|
||
|
|
setFlashOn(false);
|
||
|
|
setFlashSupported(false);
|
||
|
|
};
|
||
|
|
track.addEventListener('ended', handleEnded);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
track.removeEventListener('ended', handleEnded);
|
||
|
|
};
|
||
|
|
}, [getVideoTrack]);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const check = () => {
|
||
|
|
const currentStream = streamRef?.current ?? null;
|
||
|
|
if (currentStream !== streamIdentityRef.current) {
|
||
|
|
streamIdentityRef.current = currentStream;
|
||
|
|
probeFlashSupport();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const initialTimer = setTimeout(check, 0);
|
||
|
|
const interval = setInterval(check, 500);
|
||
|
|
return () => {
|
||
|
|
clearTimeout(initialTimer);
|
||
|
|
clearInterval(interval);
|
||
|
|
};
|
||
|
|
}, [probeFlashSupport, streamRef]);
|
||
|
|
|
||
|
|
const toggleFlash = useCallback(async () => {
|
||
|
|
if (!flashSupported) return;
|
||
|
|
const track = getVideoTrack();
|
||
|
|
if (!track) return;
|
||
|
|
|
||
|
|
const next = !flashOn;
|
||
|
|
try {
|
||
|
|
await track.applyConstraints({ advanced: [{ torch: next }] });
|
||
|
|
setFlashOn(next);
|
||
|
|
} catch (err) {
|
||
|
|
console.warn('Torch toggle failed:', err);
|
||
|
|
}
|
||
|
|
}, [flashSupported, flashOn, getVideoTrack]);
|
||
|
|
|
||
|
|
return { flashSupported, flashOn, toggleFlash };
|
||
|
|
}
|