import React, { useEffect, useRef, useState } from 'react'; import { Camera, CameraOff, RefreshCw, AlertTriangle } from 'lucide-react'; const CameraFeed = ({ onMetadataReceived, wsUrl = "ws://localhost:8000/ws" }) => { const [isActive, setIsActive] = useState(false); const [isConnected, setIsConnected] = useState(false); const [errorMsg, setErrorMsg] = useState(""); const [fps, setFps] = useState(0); // Camera devices and pacing const [devices, setDevices] = useState([]); const [selectedDeviceId, setSelectedDeviceId] = useState(''); const isProcessingRef = useRef(false); const videoRef = useRef(null); const canvasRef = useRef(null); const wsRef = useRef(null); const streamRef = useRef(null); const requestRef = useRef(null); const lastFrameTimeRef = useRef(0); const lastFrameSentTimeRef = useRef(0); const fpsIntervalRef = useRef(1000 / 12); const frameCountRef = useRef(0); const fpsTimeRef = useRef(0); const [processedImgSrc, setProcessedImgSrc] = useState(null); const getDevices = async () => { try { const allDevices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = allDevices.filter(device => device.kind === 'videoinput'); setDevices(videoDevices); if (videoDevices.length > 0 && !selectedDeviceId) { setSelectedDeviceId(videoDevices[0].deviceId); } } catch (err) { console.error("Error listing cameras:", err); } }; useEffect(() => { canvasRef.current = document.createElement('canvas'); canvasRef.current.width = 640; canvasRef.current.height = 480; getDevices(); return () => { stopCamera(); }; }, []); const connectWebSocket = () => { if (wsRef.current) { wsRef.current.close(); } console.log("Connecting to WebSocket: ", wsUrl); const ws = new WebSocket(wsUrl); wsRef.current = ws; ws.onopen = () => { setIsConnected(true); setErrorMsg(""); console.log("WebSocket connection established"); }; ws.onmessage = (event) => { try { const data = jsonParseSafely(event.data); if (!data) return; if (data.type === "frame_data") { setProcessedImgSrc(data.image); onMetadataReceived(data); frameCountRef.current++; const now = performance.now(); if (now - fpsTimeRef.current >= 1000) { setFps(frameCountRef.current); frameCountRef.current = 0; fpsTimeRef.current = now; } // Unlock frame-processing and schedule next frame isProcessingRef.current = false; requestRef.current = requestAnimationFrame(processLoop); } else if (data.type === "game_update") { onMetadataReceived({ game: data.game }); } } catch (err) { console.error("Error reading WebSocket message:", err); } }; ws.onclose = () => { setIsConnected(false); console.log("WebSocket connection closed"); }; ws.onerror = (err) => { console.error("WebSocket error:", err); setErrorMsg("خادم الخلفية (Python Backend) لا يستجيب. تأكد من تشغيل ملف backend/server.py"); }; }; const jsonParseSafely = (str) => { try { return JSON.parse(str); } catch (e) { return null; } }; const startCamera = async () => { try { setErrorMsg(""); const constraints = { video: { width: 640, height: 480, frameRate: { max: 15 }, deviceId: selectedDeviceId ? { exact: selectedDeviceId } : undefined }, audio: false }; const stream = await navigator.mediaDevices.getUserMedia(constraints); streamRef.current = stream; if (videoRef.current) { videoRef.current.srcObject = stream; } setIsActive(true); connectWebSocket(); lastFrameTimeRef.current = performance.now(); fpsTimeRef.current = performance.now(); frameCountRef.current = 0; isProcessingRef.current = false; requestRef.current = requestAnimationFrame(processLoop); // Refresh devices to get labels setTimeout(getDevices, 500); } catch (err) { console.error("Failed to access camera:", err); setErrorMsg("تم رفض الوصول إلى كاميرا الويب. يرجى السماح بالوصول للكاميرا من إعدادات المتصفح."); } }; const stopCamera = () => { setIsActive(false); setProcessedImgSrc(null); if (requestRef.current) { cancelAnimationFrame(requestRef.current); } if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); streamRef.current = null; } if (videoRef.current) { videoRef.current.srcObject = null; } if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } setIsConnected(false); setFps(0); isProcessingRef.current = false; }; const processLoop = () => { if (!videoRef.current || !canvasRef.current || !wsRef.current) { return; } const now = performance.now(); // If we've been waiting for a response for more than 450ms, force unlock to prevent freeze if (isProcessingRef.current && (now - lastFrameSentTimeRef.current > 450)) { isProcessingRef.current = false; } if (isProcessingRef.current) { requestRef.current = requestAnimationFrame(processLoop); return; } const video = videoRef.current; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (video.readyState === video.HAVE_ENOUGH_DATA) { isProcessingRef.current = true; lastFrameSentTimeRef.current = performance.now(); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const base64Img = canvas.toDataURL('image/jpeg', 0.6); if (wsRef.current.readyState === WebSocket.OPEN) { const payload = { action: "process_frame", image: base64Img }; wsRef.current.send(JSON.stringify(payload)); } else { isProcessingRef.current = false; requestRef.current = requestAnimationFrame(processLoop); } } else { requestRef.current = requestAnimationFrame(processLoop); } }; const sendGameAction = (action, payload = {}) => { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify({ action, ...payload })); } }; window.sendGameActionToWs = sendGameAction; return (
محرك المعالجة البيومترية غير نشط.
يرجى تفعيل بث الكاميرا من الزر في الأسفل لتشغيل المعالجة والتحليل الذاتي للغرفة في الوقت الفعلي.
جاري تحميل معالجة الذكاء الاصطناعي...