import { useEffect, useRef, useState, useCallback } from 'react'; export interface WebcamState { videoRef: React.RefObject; isReady: boolean; error: string | null; facingMode: 'user' | 'environment'; switchCamera: () => void; } /** * Hook to access the device webcam. * Supports front/back camera toggle for mobile devices. */ export function useWebcam(): WebcamState { const videoRef = useRef(null); const streamRef = useRef(null); const [isReady, setIsReady] = useState(false); const [error, setError] = useState(null); const [facingMode, setFacingMode] = useState<'user' | 'environment'>('user'); const startCamera = useCallback(async (mode: 'user' | 'environment') => { // Stop any existing stream first streamRef.current?.getTracks().forEach(t => t.stop()); setIsReady(false); setError(null); try { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: mode }, audio: false, }); streamRef.current = stream; if (videoRef.current) { videoRef.current.srcObject = stream; videoRef.current.onloadedmetadata = () => { videoRef.current?.play(); setIsReady(true); }; } } catch (err) { const msg = err instanceof Error ? err.message : String(err); setError( msg.includes('Permission') ? 'Camera permission denied. Please allow camera access and reload.' : `Camera error: ${msg}` ); } }, []); // Start camera on mount and whenever facingMode changes useEffect(() => { startCamera(facingMode); return () => { streamRef.current?.getTracks().forEach(t => t.stop()); }; }, [facingMode, startCamera]); const switchCamera = useCallback(() => { setFacingMode(prev => (prev === 'user' ? 'environment' : 'user')); }, []); return { videoRef, isReady, error, facingMode, switchCamera }; }