/** * useCamera Hook * Manages webcam access and frame capture */ import { useRef, useEffect, useState, useCallback } from 'react'; interface UseCameraOptions { onFrameCapture?: (base64: string) => void; captureInterval?: number; isActive?: boolean; } interface UseCameraReturn { videoRef: React.RefObject; canvasRef: React.RefObject; error: string | null; streamActive: boolean; captureFrame: () => string | null; restartCamera: () => Promise; } export const useCamera = ({ onFrameCapture, captureInterval = 4000, isActive = false }: UseCameraOptions = {}): UseCameraReturn => { const videoRef = useRef(null); const canvasRef = useRef(null); const streamRef = useRef(null); const [error, setError] = useState(null); const [streamActive, setStreamActive] = useState(false); const captureFrame = useCallback((): string | null => { if (!videoRef.current || !canvasRef.current) return null; const video = videoRef.current; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx || video.readyState !== 4) return null; try { canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); return canvas.toDataURL('image/jpeg', 0.8); } catch (err) { console.error('Frame capture error:', err); return null; } }, []); const startCamera = useCallback(async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } } }); if (videoRef.current) { videoRef.current.srcObject = stream; streamRef.current = stream; setStreamActive(true); setError(null); } } catch (err) { console.error('Camera access error:', err); setError('Could not access camera. Ensure permissions are granted.'); setStreamActive(false); } }, []); const stopCamera = useCallback(() => { if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); streamRef.current = null; setStreamActive(false); } }, []); const restartCamera = useCallback(async () => { stopCamera(); await startCamera(); }, [startCamera, stopCamera]); // Initialize camera on mount useEffect(() => { startCamera(); return () => stopCamera(); }, [startCamera, stopCamera]); // Auto-capture frames when active useEffect(() => { if (!isActive || !streamActive || !onFrameCapture) return; const intervalId = window.setInterval(() => { const frame = captureFrame(); if (frame) { onFrameCapture(frame); } }, captureInterval); return () => window.clearInterval(intervalId); }, [isActive, streamActive, onFrameCapture, captureInterval, captureFrame]); return { videoRef, canvasRef, error, streamActive, captureFrame, restartCamera }; };