uaide-backend / src /components /CameraCapture.jsx
ATS-27's picture
Upload folder using huggingface_hub
af980d7 verified
import { useEffect, useRef, useState } from 'react';
import { Camera, CameraOff, RefreshCcw, CheckCircle2 } from 'lucide-react';
import styles from './CameraCapture.module.css';
export default function CameraCapture({ onCapture }) {
const videoRef = useRef(null);
const canvasRef = useRef(null);
const streamRef = useRef(null);
const [status, setStatus] = useState('idle'); // idle | requesting | ready | error
const [error, setError] = useState('');
useEffect(() => {
return () => {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop());
}
};
}, []);
useEffect(() => {
const video = videoRef.current;
const stream = streamRef.current;
if (!video || !stream) return;
let cancelled = false;
const attachStream = async () => {
try {
video.srcObject = stream;
await video.play();
} catch (cameraError) {
if (!cancelled) {
setStatus('error');
setError(cameraError?.message || 'Unable to render camera preview');
}
}
};
attachStream();
return () => {
cancelled = true;
};
}, [status]);
const startCamera = async () => {
setStatus('requesting');
setError('');
try {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop());
}
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'user',
width: { ideal: 1280 },
height: { ideal: 720 },
},
audio: false,
});
streamRef.current = stream;
setStatus('ready');
} catch (cameraError) {
setStatus('error');
setError(cameraError?.message || 'Camera access failed');
}
};
const stopCamera = () => {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop());
streamRef.current = null;
}
if (videoRef.current) {
videoRef.current.srcObject = null;
}
setStatus('idle');
};
const capturePhoto = async () => {
if (!videoRef.current || !canvasRef.current) return;
const video = videoRef.current;
const canvas = canvasRef.current;
canvas.width = video.videoWidth || 1280;
canvas.height = video.videoHeight || 720;
const context = canvas.getContext('2d');
if (!context) return;
context.drawImage(video, 0, 0, canvas.width, canvas.height);
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png', 0.95));
if (!blob) return;
const file = new File([blob], `uaide-capture-${Date.now()}.png`, { type: 'image/png' });
onCapture(file);
};
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles.titleWrap}>
<Camera size={16} />
<span>Real-Time Capture</span>
</div>
{status === 'ready' && (
<button type="button" className={styles.secondaryBtn} onClick={stopCamera}>
<CameraOff size={14} />
<span>Stop</span>
</button>
)}
</div>
<div className={styles.viewport}>
<video
ref={videoRef}
playsInline
muted
autoPlay
className={`${styles.video} ${status === 'ready' ? styles.videoVisible : styles.videoHidden}`}
/>
{status !== 'ready' && (
<div className={styles.placeholder}>
<Camera size={26} />
<p>Capture a live image for immediate forensic analysis.</p>
</div>
)}
<canvas ref={canvasRef} className={styles.canvas} />
</div>
<div className={styles.actions}>
{status === 'idle' && (
<button type="button" className={styles.primaryBtn} onClick={startCamera}>
<Camera size={15} />
<span>Open Camera</span>
</button>
)}
{status === 'requesting' && (
<button type="button" className={styles.primaryBtn} disabled>
<RefreshCcw size={15} className={styles.spin} />
<span>Requesting Access…</span>
</button>
)}
{status === 'ready' && (
<button type="button" className={styles.primaryBtn} onClick={capturePhoto}>
<CheckCircle2 size={15} />
<span>Capture & Analyse</span>
</button>
)}
{status === 'error' && (
<>
<p className={styles.error}>{error}</p>
<button type="button" className={styles.primaryBtn} onClick={startCamera}>
<RefreshCcw size={15} />
<span>Retry Camera</span>
</button>
</>
)}
</div>
</div>
);
}