import React, { useRef, useState, useEffect, useCallback } from 'react'; import { Camera, StopCircle, RefreshCw, CheckCircle, X, RotateCcw, Settings2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { useToast } from '@/hooks/use-toast'; interface CameraRecorderProps { onCapture: (file: File) => void; onCancel: () => void; } // Quality configurations type VideoQuality = "4k" | "1080p" | "720p"; interface QualityConfig { label: string; width: number; height: number; bitrate: number; } const QUALITY_CONFIGS: Record = { "4k": { label: "4K Ultra HD", width: 3840, height: 2160, bitrate: 25000000 }, "1080p": { label: "Full HD", width: 1920, height: 1080, bitrate: 8000000 }, "720p": { label: "HD 720p", width: 1280, height: 720, bitrate: 5000000 }, }; export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { const videoRef = useRef(null); const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); const { toast } = useToast(); const [stream, setStream] = useState(null); const [isRecording, setIsRecording] = useState(false); const [recordedBlob, setRecordedBlob] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [timer, setTimer] = useState(0); const [facingMode, setFacingMode] = useState<'user' | 'environment'>('environment'); const [resolutionInfo, setResolutionInfo] = useState(''); const [quality, setQuality] = useState('1080p'); const [showSettings, setShowSettings] = useState(false); // Initialisation de la camĂ©ra const startCamera = useCallback(async () => { try { if (stream) { stream.getTracks().forEach(track => track.stop()); } const config = QUALITY_CONFIGS[quality]; console.log(`📾 Starting camera: ${config.label}, facing: ${facingMode}`); const constraints: MediaStreamConstraints = { audio: true, video: { facingMode: facingMode, width: { ideal: config.width, min: 640 }, height: { ideal: config.height, min: 480 }, } }; const newStream = await navigator.mediaDevices.getUserMedia(constraints); setStream(newStream); if (videoRef.current) { videoRef.current.srcObject = newStream; videoRef.current.volume = 0; } // Check actual resolution const videoTrack = newStream.getVideoTracks()[0]; if (videoTrack) { const settings = videoTrack.getSettings(); const resText = `${settings.width}x${settings.height}`; console.log('✅ Camera started at:', resText); setResolutionInfo(resText); } } catch (error) { console.error('❌ Error accessing camera:', error); toast({ title: "Erreur camĂ©ra", description: "Impossible d'accĂ©der Ă  la camĂ©ra. VĂ©rifiez les permissions.", variant: "destructive" }); onCancel(); } }, [facingMode, quality, onCancel, toast]); useEffect(() => { startCamera(); return () => { // Cleanup on unmount if (stream) { stream.getTracks().forEach(track => track.stop()); } }; }, [startCamera]); // Timer logic useEffect(() => { let interval: NodeJS.Timeout; if (isRecording) { interval = setInterval(() => { setTimer(prev => prev + 1); }, 1000); } else { setTimer(0); } return () => clearInterval(interval); }, [isRecording]); const startRecording = () => { if (!stream) return; chunksRef.current = []; // Essayer les codecs supportĂ©s pour la meilleure qualitĂ© // iOS (Safari) supporte bien video/mp4 ou video/webkit;codecs=h264 let mimeType = 'video/mp4'; if (!MediaRecorder.isTypeSupported(mimeType)) { mimeType = 'video/webm;codecs=vp9'; if (!MediaRecorder.isTypeSupported(mimeType)) { mimeType = 'video/webm'; } } console.log('đŸŽ„ Recording with mimeType:', mimeType); try { const mediaRecorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 8000000 // 8 Mbps target }); mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) { chunksRef.current.push(e.data); } }; mediaRecorder.onstop = () => { const blob = new Blob(chunksRef.current, { type: mimeType }); setRecordedBlob(blob); const url = URL.createObjectURL(blob); setPreviewUrl(url); // Stop stream tracks to save battery while reviewing stream.getTracks().forEach(track => track.stop()); }; mediaRecorder.start(1000); // Collect chunks every second mediaRecorderRef.current = mediaRecorder; setIsRecording(true); } catch (e) { console.error('Failed to start recording:', e); toast({ title: "Erreur enregistrement", description: "Format vidĂ©o non supportĂ© par ce navigateur.", variant: "destructive" }); } }; const stopRecording = () => { if (mediaRecorderRef.current && isRecording) { mediaRecorderRef.current.stop(); setIsRecording(false); } }; const switchCamera = () => { setFacingMode(prev => prev === 'user' ? 'environment' : 'user'); }; const handleRetake = () => { setRecordedBlob(null); setPreviewUrl(null); startCamera(); // Restart stream }; const handleConfirm = () => { if (recordedBlob) { // Create a File object const file = new File([recordedBlob], `capture-${Date.now()}.mp4`, { type: recordedBlob.type }); onCapture(file); } }; const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; // Rendu : Mode Revue if (recordedBlob && previewUrl) { return (
); } // Rendu : Mode Capture return (
{/* Header */}
{isRecording ? formatTime(timer) : (resolutionInfo || 'Camera')}
{!isRecording && ( )}
{/* Quality Settings Panel */} {showSettings && !isRecording && (

Qualité Vidéo

{(Object.keys(QUALITY_CONFIGS) as VideoQuality[]).map((q) => ( ))}

{QUALITY_CONFIGS[quality].label}

)} {/* Viewfinder */}
{/* Controls */}
{isRecording ? ( ) : ( )}
); }