"use client"; import React, { useCallback, useRef, useState } from "react"; import Webcam from "react-webcam"; import { Camera, CameraOff, Video, StopCircle, Loader2 } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select"; import { Alert, AlertDescription } from "~/components/ui/alert"; import { AspectRatio } from "~/components/ui/aspect-ratio"; import { toast } from "sonner"; import { api } from "~/trpc/react"; export function WebcamPanel({ readOnly = false }: { readOnly?: boolean }) { const [deviceId, setDeviceId] = useState(null); const [devices, setDevices] = useState([]); const [isCameraEnabled, setIsCameraEnabled] = useState(false); const [isRecording, setIsRecording] = useState(false); const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const webcamRef = useRef(null); const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); // TRPC mutation for presigned URL const getUploadUrlMutation = api.storage.getUploadPresignedUrl.useMutation(); const handleDevices = useCallback( (mediaDevices: MediaDeviceInfo[]) => { setDevices(mediaDevices.filter(({ kind, deviceId }) => kind === "videoinput" && deviceId !== "")); }, [setDevices], ); React.useEffect(() => { navigator.mediaDevices.enumerateDevices().then(handleDevices); }, [handleDevices]); const handleEnableCamera = () => { setIsCameraEnabled(true); setError(null); }; const handleDisableCamera = () => { if (isRecording) { handleStopRecording(); } setIsCameraEnabled(false); }; const handleStartRecording = () => { if (!webcamRef.current?.stream) return; setIsRecording(true); chunksRef.current = []; try { const recorder = new MediaRecorder(webcamRef.current.stream, { mimeType: "video/webm" }); recorder.ondataavailable = (event) => { if (event.data.size > 0) { chunksRef.current.push(event.data); } }; recorder.onstop = async () => { const blob = new Blob(chunksRef.current, { type: "video/webm" }); await handleUpload(blob); }; recorder.start(); mediaRecorderRef.current = recorder; toast.success("Recording started"); } catch (e) { console.error("Failed to start recorder:", e); toast.error("Failed to start recording"); setIsRecording(false); } }; const handleStopRecording = () => { if (mediaRecorderRef.current && isRecording) { mediaRecorderRef.current.stop(); setIsRecording(false); } }; const handleUpload = async (blob: Blob) => { setUploading(true); const filename = `recording-${Date.now()}.webm`; try { // 1. Get Presigned URL const { url } = await getUploadUrlMutation.mutateAsync({ filename, contentType: "video/webm", }); // 2. Upload to S3 const response = await fetch(url, { method: "PUT", body: blob, headers: { "Content-Type": "video/webm", }, }); if (!response.ok) { throw new Error("Upload failed"); } toast.success("Recording uploaded successfully"); console.log("Uploaded recording:", filename); } catch (e) { console.error("Upload error:", e); toast.error("Failed to upload recording"); } finally { setUploading(false); } }; return (

Webcam Feed

{!readOnly && (
{devices.length > 0 && ( )} {isCameraEnabled && ( !isRecording ? ( ) : ( ) )} {isCameraEnabled ? ( ) : ( )}
)}
{isCameraEnabled ? (
setError(String(err))} className="object-contain w-full h-full" /> {/* Recording Overlay */} {isRecording && (
REC
)} {/* Uploading Overlay */} {uploading && (
Uploading...
)} {error && (
{error}
)}
) : (

Camera is disabled

)}
); }