|
import { useEffect, useRef } from 'react'; |
|
|
|
const CameraSetup = ({ |
|
videoRef, |
|
canvasRef, |
|
containerRef, |
|
facingMode, |
|
setFacingMode, |
|
setCameraError, |
|
setVideoAspectRatio, |
|
updateCanvasSize, |
|
isMobile |
|
}) => { |
|
|
|
const isMounted = useRef(true); |
|
|
|
const isSettingUpCamera = useRef(false); |
|
|
|
|
|
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 stream = await navigator.mediaDevices.getUserMedia({ |
|
video: { |
|
facingMode: facingMode, |
|
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, this is normal if component remounted:", playError); |
|
|
|
if (playError.name !== "AbortError") { |
|
throw playError; |
|
} |
|
} |
|
|
|
|
|
videoRef.current.onloadedmetadata = () => { |
|
if (!isMounted.current) return; |
|
|
|
const videoWidth = videoRef.current.videoWidth; |
|
const videoHeight = videoRef.current.videoHeight; |
|
const aspectRatio = videoWidth / videoHeight; |
|
setVideoAspectRatio(aspectRatio); |
|
|
|
|
|
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 () => { |
|
|
|
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]); |
|
|
|
|
|
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; |