Spaces:
Running
Running
File size: 3,635 Bytes
9a9d18a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
import React, { useEffect, useRef, useState } from "react";
import { Smartphone, WifiOff } from "lucide-react";
import { useApi } from "@/contexts/ApiContext";
interface PhoneCameraFeedProps {
sessionId: string;
}
const PhoneCameraFeed: React.FC<PhoneCameraFeedProps> = ({ sessionId }) => {
const { wsBaseUrl } = useApi();
const videoRef = useRef<HTMLVideoElement>(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const mediaSourceRef = useRef<MediaSource | null>(null);
useEffect(() => {
if (!sessionId) return;
const connectWebSocket = () => {
try {
const ws = new WebSocket(`${wsBaseUrl}/ws/camera/${sessionId}`);
wsRef.current = ws;
ws.onopen = () => {
console.log("Camera feed WebSocket connected");
setIsConnected(true);
setError(null);
};
ws.onmessage = (event) => {
// Handle incoming video chunks
if (event.data instanceof Blob) {
handleVideoChunk(event.data);
} else if (event.data === "camera_connected") {
setIsConnected(true);
}
};
ws.onclose = () => {
console.log("Camera feed WebSocket disconnected");
setIsConnected(false);
};
ws.onerror = (error) => {
console.error("Camera feed WebSocket error:", error);
setError("Failed to connect to camera feed");
setIsConnected(false);
};
} catch (error) {
console.error("Failed to create WebSocket:", error);
setError("Failed to establish connection");
}
};
const handleVideoChunk = (chunk: Blob) => {
// For now, we'll just log that we received a chunk
// In a full implementation, this would use MediaSource API
console.log("Received video chunk:", chunk.size, "bytes");
};
connectWebSocket();
return () => {
if (wsRef.current) {
wsRef.current.close();
}
if (mediaSourceRef.current) {
mediaSourceRef.current.endOfStream();
}
};
}, [sessionId]);
if (error) {
return (
<div className="w-full h-32 bg-gray-800 rounded-lg flex flex-col items-center justify-center">
<WifiOff className="w-6 h-6 text-red-400 mb-2" />
<p className="text-xs text-red-400 text-center">{error}</p>
</div>
);
}
if (!isConnected) {
return (
<div className="w-full h-32 bg-gray-800 rounded-lg flex flex-col items-center justify-center">
<Smartphone className="w-6 h-6 text-gray-500 mb-2" />
<p className="text-xs text-gray-500 text-center">
Waiting for phone camera...
</p>
<div className="w-4 h-4 border-2 border-gray-600 border-t-blue-500 rounded-full animate-spin mt-2"></div>
</div>
);
}
return (
<div className="w-full h-32 bg-gray-800 rounded-lg overflow-hidden relative">
<video
ref={videoRef}
className="w-full h-full object-cover"
autoPlay
muted
playsInline
/>
<div className="absolute top-2 left-2">
<div className="flex items-center gap-1 bg-black/50 px-2 py-1 rounded text-xs">
<div className="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-green-400">LIVE</span>
</div>
</div>
<div className="absolute bottom-2 right-2">
<Smartphone className="w-4 h-4 text-white/70" />
</div>
</div>
);
};
export default PhoneCameraFeed;
|