diff --git a/ui/src/App.jsx b/ui/src/App.jsx index b488479..15c4b63 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -45,8 +45,16 @@ function App() { ws.onmessage = null; ws.onerror = null; ws.onclose = null; - if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { + if (ws.readyState === WebSocket.OPEN) { ws.close(); + } else if (ws.readyState === WebSocket.CONNECTING) { + ws.onopen = () => { + try { + ws.close(); + } catch { + // ignore + } + }; } } }; diff --git a/ui/src/components/VideoChat.jsx b/ui/src/components/VideoChat.jsx index 60203dc..b41fb7b 100644 --- a/ui/src/components/VideoChat.jsx +++ b/ui/src/components/VideoChat.jsx @@ -3,100 +3,172 @@ import { useVideoChat } from '../hooks/useVideoChat.js'; import { Mic, MicOff, Video, VideoOff, Monitor, MonitorOff, SkipForward, LogOut, Flag, MessageCircle, X, Send, Search, - Crown, UserX, Users + Crown, UserX, Users, ArrowLeftRight, Move, Sparkles } from 'lucide-react'; import { useVisualViewport } from '../hooks/useVisualViewport.js'; /** - * Remote Participant Video Tile + * Reusable Video Stream Player + * Safely attaches MediaStream to a video element with fallback avatar and status badges. */ -const RemoteParticipantTile = ({ peer, index, isCurrentUserHost, hostId, onKick, onMute }) => { +const VideoStreamPlayer = ({ + stream, + muted = false, + mirrored = false, + className = '', + videoEnabled = true, + fallbackLabel = 'User', + fallbackSubtext = 'Camera Off', + isSpeaking = false, + badgeText = null, + isHost = false, + isAudioEnabled = true, + isScreenSharing = false, + children +}) => { const videoRef = useRef(null); - const isPeerHost = peer.socketId === hostId; useEffect(() => { - if (videoRef.current && peer.stream) { - videoRef.current.srcObject = peer.stream; - videoRef.current.play().catch(e => console.warn("Remote stream play error:", e)); + const videoEl = videoRef.current; + if (!videoEl) return; + + if (stream) { + if (videoEl.srcObject !== stream) { + videoEl.srcObject = stream; + } + const playPromise = videoEl.play(); + if (playPromise !== undefined) { + playPromise.catch(e => { + if (e.name !== 'AbortError') { + console.warn("Stream playback warning:", e); + } + }); + } + } else { + videoEl.srcObject = null; } - }, [peer.stream]); + }, [stream]); return ( -
- {/* Video Stream */} +
+ {/* Video Element */}