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); // 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 stream const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: facingMode, width: { ideal: 1920 }, height: { ideal: 1080 } }, audio: false }); // Check if component is still mounted before continuing if (!isMounted.current) { stream.getTracks().forEach(track => track.stop()); return; } videoRef.current.srcObject = stream; // Use a try-catch block for the play() call try { await videoRef.current.play(); } catch (playError) { console.log("Play interrupted, this is normal if component remounted:", playError); // Don't treat play interruptions as fatal errors if (playError.name !== "AbortError") { throw playError; } } // Get the actual video dimensions once metadata is loaded videoRef.current.onloadedmetadata = () => { if (!isMounted.current) return; const videoWidth = videoRef.current.videoWidth; const videoHeight = videoRef.current.videoHeight; const aspectRatio = videoWidth / videoHeight; setVideoAspectRatio(aspectRatio); // Update canvas size with the correct aspect ratio updateCanvasSize(aspectRatio); }; 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.srcObject) { const tracks = videoRef.current.srcObject.getTracks(); tracks.forEach(track => track.stop()); } }; }, [videoRef, canvasRef, containerRef, facingMode, setCameraError, setVideoAspectRatio, updateCanvasSize]); // 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 ( <>