| | import { useEffect, useRef } from 'react'; |
| |
|
| | const CameraSetup = ({ |
| | videoRef, |
| | canvasRef, |
| | containerRef, |
| | facingMode, |
| | setFacingMode, |
| | setCameraError, |
| | setVideoAspectRatio, |
| | updateCanvasSize, |
| | isMobile |
| | }) => { |
| | |
| | const isMounted = useRef(true); |
| | |
| | const isSettingUpCamera = useRef(false); |
| |
|
| | const updateDimensions = () => { |
| | if (!videoRef.current || !isMounted.current) return; |
| | |
| | const videoWidth = videoRef.current.videoWidth; |
| | const videoHeight = videoRef.current.videoHeight; |
| | |
| | |
| | if (videoWidth && videoHeight) { |
| | const aspectRatio = videoWidth / videoHeight; |
| | setVideoAspectRatio(aspectRatio); |
| | updateCanvasSize(aspectRatio); |
| | } |
| | }; |
| |
|
| | |
| | useEffect(() => { |
| | |
| | isMounted.current = true; |
| | |
| | const setupCamera = async () => { |
| | if (!videoRef.current || !canvasRef.current || !containerRef.current) return; |
| | |
| | if (isSettingUpCamera.current) return; |
| | |
| | isSettingUpCamera.current = true; |
| | |
| | try { |
| | |
| | if (videoRef.current.srcObject) { |
| | const tracks = videoRef.current.srcObject.getTracks(); |
| | tracks.forEach(track => track.stop()); |
| | } |
| | |
| | |
| | const constraints = { |
| | video: { |
| | facingMode: facingMode, |
| | |
| | ...(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; |
| | |
| | |
| | videoRef.current.addEventListener('loadeddata', updateDimensions); |
| | |
| | try { |
| | await videoRef.current.play(); |
| | |
| | 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 () => { |
| | |
| | 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]); |
| |
|
| | |
| | const switchCamera = async () => { |
| | if (!videoRef.current || isSettingUpCamera.current) return; |
| | |
| | isSettingUpCamera.current = true; |
| | |
| | |
| | if (videoRef.current.srcObject) { |
| | const tracks = videoRef.current.srcObject.getTracks(); |
| | tracks.forEach(track => track.stop()); |
| | } |
| | |
| | |
| | const newFacingMode = facingMode === 'user' ? 'environment' : 'user'; |
| | setFacingMode(newFacingMode); |
| | |
| | try { |
| | |
| | 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); |
| | |
| | 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 ( |
| | <> |
| | <video |
| | ref={videoRef} |
| | className="hidden" |
| | width="1280" |
| | height="720" |
| | autoPlay |
| | playsInline |
| | muted |
| | /> |
| | |
| | {/* Camera switch button - only shown on mobile */} |
| | {isMobile && ( |
| | <button |
| | onClick={switchCamera} |
| | className="absolute top-4 right-4 bg-white bg-opacity-70 p-2 rounded-full shadow-md z-10 hover:bg-opacity-90 transition-all flex items-center justify-center" |
| | aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`} |
| | style={{ width: '40px', height: '40px' }} |
| | > |
| | <span className="material-symbols-outlined text-gray-800" style={{ display: 'flex', lineHeight: 1 }}> |
| | cameraswitch |
| | </span> |
| | </button> |
| | )} |
| | </> |
| | ); |
| | }; |
| |
|
| | export default CameraSetup; |