handspew / components /CameraSetup.js
Tina Tarighian
initial
065d164
raw
history blame
6.24 kB
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 (
<>
<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"
aria-label={`Switch to ${facingMode === 'user' ? 'back' : 'front'} camera`}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.5 7.5L9.5 12.5" />
</svg>
</button>
)}
</>
);
};
export default CameraSetup;