| 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; |