| import { useEffect, useRef, useState } from "react"; | |
| /** | |
| * Attach a live browser camera stream to a `<video>` element by deviceId. | |
| * Set `paused=true` to release the stream (e.g. so cv2.VideoCapture can claim | |
| * the camera exclusively). The stream is auto-stopped on unmount. | |
| */ | |
| export function useCameraStream(deviceId: string, paused: boolean) { | |
| const videoRef = useRef<HTMLVideoElement>(null); | |
| const [hasError, setHasError] = useState(false); | |
| useEffect(() => { | |
| if (paused || !deviceId) { | |
| if (!deviceId) setHasError(true); | |
| return; | |
| } | |
| let cancelled = false; | |
| let stream: MediaStream | null = null; | |
| setHasError(false); | |
| (async () => { | |
| try { | |
| stream = await navigator.mediaDevices.getUserMedia({ | |
| video: { deviceId: { exact: deviceId } }, | |
| }); | |
| if (cancelled) { | |
| stream.getTracks().forEach((t) => t.stop()); | |
| return; | |
| } | |
| if (videoRef.current) { | |
| videoRef.current.srcObject = stream; | |
| await videoRef.current.play().catch(() => {}); | |
| } | |
| } catch { | |
| setHasError(true); | |
| } | |
| })(); | |
| return () => { | |
| cancelled = true; | |
| if (stream) stream.getTracks().forEach((t) => t.stop()); | |
| }; | |
| }, [deviceId, paused]); | |
| return { videoRef, hasError }; | |
| } | |