import { useEffect, useRef } from 'react'; const CameraSetup = ({ videoRef, canvasRef, containerRef, facingMode, setFacingMode, setCameraError, setVideoAspectRatio, updateCanvasSize, isMobile }) => { // Track if component is mounted to prevent state updates after unmount const isMounted = useRef(true); // Track if camera is currently being set up const isSettingUpCamera = useRef(false); const updateDimensions = () => { if (!videoRef.current || !isMounted.current) return; const videoWidth = videoRef.current.videoWidth; const videoHeight = videoRef.current.videoHeight; // Only update if we have valid dimensions if (videoWidth && videoHeight) { const aspectRatio = videoWidth / videoHeight; setVideoAspectRatio(aspectRatio); updateCanvasSize(aspectRatio); } }; // Set up the webcam useEffect(() => { // Set mounted flag isMounted.current = true; const setupCamera = async () => { if (!videoRef.current || !canvasRef.current || !containerRef.current) return; // Prevent multiple simultaneous setup attempts if (isSettingUpCamera.current) return; isSettingUpCamera.current = true; try { // Stop any existing stream first if (videoRef.current.srcObject) { const tracks = videoRef.current.srcObject.getTracks(); tracks.forEach(track => track.stop()); } // Get camera constraints based on device const constraints = { video: { facingMode: facingMode, // For mobile, prioritize height to prevent letterboxing ...(isMobile ? { height: { ideal: 1080, min: 720 }, width: { ideal: 1920, min: 1280 } } : { width: { ideal: 1920 }, height: { ideal: 1080 } }) }, audio: false }; const stream = await navigator.mediaDevices.getUserMedia(constraints); if (!isMounted.current) { stream.getTracks().forEach(track => track.stop()); return; } videoRef.current.srcObject = stream; // Add loadeddata event listener for more reliable dimension detection videoRef.current.addEventListener('loadeddata', updateDimensions); try { await videoRef.current.play(); // Double check dimensions after a short delay to ensure they're correct setTimeout(updateDimensions, 100); } catch (playError) { console.log("Play interrupted, this is normal if component remounted:", playError); if (playError.name !== "AbortError") { throw playError; } } if (isMounted.current) { setCameraError(false); console.log("Camera set up successfully"); } } catch (error) { console.error('Error accessing webcam:', error); if (isMounted.current) { setCameraError(true); } } finally { isSettingUpCamera.current = false; } }; setupCamera(); return () => { // Set mounted flag to false to prevent state updates after unmount isMounted.current = false; if (videoRef.current) { videoRef.current.removeEventListener('loadeddata', updateDimensions); if (videoRef.current.srcObject) { const tracks = videoRef.current.srcObject.getTracks(); tracks.forEach(track => track.stop()); } } }; }, [videoRef, canvasRef, containerRef, facingMode]); // Function to switch camera const switchCamera = async () => { if (!videoRef.current || isSettingUpCamera.current) return; isSettingUpCamera.current = true; // Stop current stream if (videoRef.current.srcObject) { const tracks = videoRef.current.srcObject.getTracks(); tracks.forEach(track => track.stop()); } // Toggle facing mode const newFacingMode = facingMode === 'user' ? 'environment' : 'user'; setFacingMode(newFacingMode); try { // Get new camera stream with updated facing mode const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: newFacingMode, width: { ideal: 1920 }, height: { ideal: 1080 } }, audio: false }); if (!isMounted.current) { stream.getTracks().forEach(track => track.stop()); return; } videoRef.current.srcObject = stream; try { await videoRef.current.play(); } catch (playError) { console.log("Play interrupted during camera switch:", playError); // Don't treat play interruptions as fatal errors if (playError.name !== "AbortError") { throw playError; } } if (isMounted.current) { setCameraError(false); console.log(`Camera switched to ${newFacingMode === 'user' ? 'front' : 'back'} camera`); } } catch (error) { console.error('Error switching camera:', error); if (isMounted.current) { setCameraError(true); } } finally { isSettingUpCamera.current = false; } }; return ( <>