From 986f9741c16c14fe540b6fefa438e0b2bdafaf57 Mon Sep 17 00:00:00 2001 From: Jacques Date: Thu, 22 Jan 2026 20:46:20 +0100 Subject: [PATCH] fix: reduce text overlay font size to 10 and add camera diagnostics --- client/src/components/camera-recorder.tsx | 281 +++++++++------------- ffmpeg-service/main.py | 2 +- 2 files changed, 114 insertions(+), 169 deletions(-) diff --git a/client/src/components/camera-recorder.tsx b/client/src/components/camera-recorder.tsx index abb4c21..8d35df1 100644 --- a/client/src/components/camera-recorder.tsx +++ b/client/src/components/camera-recorder.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState, useEffect, useCallback } from 'react'; -import { Camera, StopCircle, RefreshCw, CheckCircle, X, RotateCcw, Settings2, AlertCircle } from 'lucide-react'; +import { Camera, StopCircle, RefreshCw, CheckCircle, X, RotateCcw, Settings2, AlertCircle, Bug } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useToast } from '@/hooks/use-toast'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -46,8 +46,9 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { const [resolutionInfo, setResolutionInfo] = useState(''); const [quality, setQuality] = useState('1080p'); const [showSettings, setShowSettings] = useState(false); + const [showDebug, setShowDebug] = useState(false); - // Fetch video devices + // Initial Device Fetch useEffect(() => { const getDevices = async () => { try { @@ -57,22 +58,21 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { const allDevices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = allDevices.filter(device => device.kind === 'videoinput'); setDevices(videoDevices); + console.log("Devices found:", videoDevices); if (videoDevices.length > 0 && !selectedDeviceId) { setSelectedDeviceId(videoDevices[0].deviceId); } } catch (err) { console.error("Error enumerating devices:", err); - setPermissionError("Accès caméra refusé ou non disponible."); + setPermissionError("Accès caméra refusé. Vérifiez les permissions du navigateur."); } }; getDevices(); }, []); - // Initialisation de la caméra + // Camera Start Logic const startCamera = useCallback(async () => { - if (!selectedDeviceId && devices.length > 0) return; // Wait for selection logic - try { setPermissionError(null); @@ -83,7 +83,7 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { const config = QUALITY_CONFIGS[quality]; console.log(`📸 Starting camera: ${config.label}, deviceId: ${selectedDeviceId}`); - // Constraints with specific deviceId if selected, otherwise facingMode fallback + // Constraints const constraints: MediaStreamConstraints = { audio: { echoCancellation: true, @@ -94,7 +94,7 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { width: { ideal: config.width }, height: { ideal: config.height }, } : { - facingMode: 'user', // Default fallback + facingMode: 'user', width: { ideal: config.width }, height: { ideal: config.height }, } @@ -104,15 +104,14 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { try { newStream = await navigator.mediaDevices.getUserMedia(constraints); - console.log('✅ Camera obtained'); } catch (err) { - console.warn('⚠️ Constraints failed, trying minimal fallback:', err); - // Fallback to minimal constraints (any video source) + console.warn('⚠️ Standard constraints failed:', err); + setPermissionError(`Erreur: ${err instanceof Error ? err.message : String(err)}`); + // Fallback newStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); - console.log('✅ Camera obtained with minimal constraints'); } setStream(newStream); @@ -120,37 +119,32 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { if (videoRef.current) { videoRef.current.srcObject = newStream; videoRef.current.volume = 0; - // Explicit play try - try { - await videoRef.current.play(); - } catch (playErr) { - console.error("Video play failed:", playErr); - } + await videoRef.current.play(); } - // Check actual resolution + // Resolution check 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); + setResolutionInfo(`${settings.width}x${settings.height}`); } } catch (error) { console.error('❌ Error accessing camera:', error); - setPermissionError(error instanceof Error ? error.message : "Erreur inconnue"); + const msg = error instanceof Error ? error.message : "Erreur inconnue"; + setPermissionError(msg); toast({ title: "Erreur caméra", - description: "Impossible d'accéder à la caméra. Vérifiez les permissions.", + description: msg, variant: "destructive" }); } - }, [selectedDeviceId, quality, toast, devices]); // Removed complex deps + }, [selectedDeviceId, quality, toast]); - // Restart camera when device or quality changes + // Restart on quality/device change useEffect(() => { - if (selectedDeviceId) { + if (selectedDeviceId || devices.length === 0) { + // Attempt start even if no devices found (might work with default) startCamera(); } return () => { @@ -174,65 +168,38 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { return () => clearInterval(interval); }, [isRecording]); + // Recording Functions const startRecording = () => { if (!stream) return; - chunksRef.current = []; - // Essayer les codecs supportés pour la meilleure qualité - const mimeTypes = [ - 'video/mp4', - 'video/webm;codecs=vp9,opus', - 'video/webm;codecs=vp8,opus', - 'video/webm' - ]; - - let mimeType = mimeTypes.find(type => MediaRecorder.isTypeSupported(type)) || ''; - - if (!mimeType) { - console.warn("No supported mimeType found, letting browser decide default."); - } - - console.log('🎥 Recording with mimeType:', mimeType); + // Codec selection + const mimeTypes = ['video/mp4', 'video/webm;codecs=vp9,opus', 'video/webm']; + const mimeType = mimeTypes.find(type => MediaRecorder.isTypeSupported(type)) || ''; try { - const options: MediaRecorderOptions = { - videoBitsPerSecond: 8000000 // 8 Mbps target - }; - if (mimeType) { - options.mimeType = mimeType; - } - - const mediaRecorder = new MediaRecorder(stream, options); + const mediaRecorder = new MediaRecorder(stream, { + mimeType, + videoBitsPerSecond: 8000000 + }); mediaRecorder.ondataavailable = (e) => { - if (e.data.size > 0) { - chunksRef.current.push(e.data); - } + if (e.data.size > 0) chunksRef.current.push(e.data); }; mediaRecorder.onstop = () => { const blob = new Blob(chunksRef.current, { type: mimeType || 'video/webm' }); setRecordedBlob(blob); - const url = URL.createObjectURL(blob); - setPreviewUrl(url); - - // Stop stream tracks to save battery while reviewing - // stream.getTracks().forEach(track => track.stop()); // Don't stop yet if we want retake to be fast? - // Actually retake restarts camera, so stopping here is fine. + setPreviewUrl(URL.createObjectURL(blob)); stream.getTracks().forEach(track => track.stop()); }; - mediaRecorder.start(1000); // Collect chunks every second + mediaRecorder.start(1000); 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" - }); + console.error('Recording failed:', e); + toast({ title: "Erreur enregistrement", description: "Format non supporté.", variant: "destructive" }); } }; @@ -243,19 +210,9 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { } }; - const handleRetake = () => { - setRecordedBlob(null); - setPreviewUrl(null); - // Force restart - startCamera(); - }; - const handleConfirm = () => { if (recordedBlob) { - const file = new File([recordedBlob], `capture-${Date.now()}.mp4`, { - type: recordedBlob.type - }); - onCapture(file); + onCapture(new File([recordedBlob], `capture-${Date.now()}.mp4`, { type: recordedBlob.type })); } }; @@ -265,44 +222,27 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { return `${mins}:${secs.toString().padStart(2, '0')}`; }; - // Rendu : Mode Revue + // Render Review Mode if (recordedBlob && previewUrl) { return (
-
); } - // Rendu : Mode Capture + // Render Capture Mode return (
- {/* Header */} + {/* Header / Top Bar */}
- {/* Recording timer or Settings Trigger */} + {/* Debug Toggle */} + + {isRecording ? (
@@ -327,119 +271,120 @@ export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) { )}
- -
{/* Spacer for alignment */} +
- {/* Error Message */} - {permissionError && ( -
+ {/* DEBUG OVERLAY */} + {showDebug && ( +
+

Diagnostic Caméra

+

Secure Context: {window.isSecureContext ? "YES (HTTPS/Local)" : "NO (HTTP) - CAMERA BLOCKED"}

+

Err Permission: {permissionError || "Aucune"}

+

Resolution: {resolutionInfo}

+

Selected Device: {selectedDeviceId || "Auto"}

+

Stream Active: {stream ? "Oui" : "Non"}

+
+ Devices ({devices.length}): + {devices.map((d, i) => ( +
+ [{d.kind}] {d.label || "Sans étiquette"} ({d.deviceId.slice(0, 8)}...) +
+ ))} +
+ )} + + {/* Critical Error Display */} + {!window.isSecureContext && ( +
+ +

Connexion non sécurisée (HTTP)

+

Les navigateurs bloquent l'accès caméra sur les sites non sécurisés (sauf localhost).

+

Veuillez passer en HTTPS ou utiliser localhost.

+
+ )} + + {/* Regular Error Display */} + {permissionError && window.isSecureContext && ( +
-

Erreur Caméra

-

{permissionError}

-
)} - {/* Settings Panel Overlay */} + {/* Settings Modal */} {showSettings && !isRecording && (
-

Paramètres Caméra

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

- {QUALITY_CONFIGS[quality].label} - {resolutionInfo} -

- -
)} - {/* Viewfinder */} + {/* Video Feed */}
- {!permissionError && ( -
- {/* Controls */} + {/* Controls Footer */}
- {isRecording ? ( - - ) : ( - - )} +
); } -// Ensure TypeScript treats this as a module export default CameraRecorder; diff --git a/ffmpeg-service/main.py b/ffmpeg-service/main.py index 7832c57..1d244c9 100644 --- a/ffmpeg-service/main.py +++ b/ffmpeg-service/main.py @@ -30,7 +30,7 @@ class ReelRequest(BaseModel): music_id: Optional[str] = None music_url: Optional[str] = None word_duration: float = 0.6 - font_size: int = 30 + font_size: int = 10 music_volume: float = 0.25 tts_enabled: bool = False tts_voice: str = "fr-FR-VivienneMultilingualNeural"