import React, { useState, useEffect, useCallback } from "react"; import Paper from "@mui/material/Paper"; import List from "@mui/material/List"; import "./PlayerList.css"; import { MediaControl, MediaAgent, Peer } from "./MediaControl"; import Box from "@mui/material/Box"; import { Session, Room } from "./GlobalContext"; import useWebSocket from "react-use-websocket"; type Player = { name: string; session_id: string; live: boolean; local: boolean /* Client side variable */; protected?: boolean; has_media?: boolean; // Whether this Player provides audio/video streams bot_run_id?: string; bot_provider_id?: string; bot_instance_id?: string; // For bot instances muted?: boolean; video_on?: boolean; }; type PlayerListProps = { socketUrl: string; session: Session; roomId: string; }; const PlayerList: React.FC = (props: PlayerListProps) => { const { socketUrl, session, roomId } = props; const [Players, setPlayers] = useState(null); const [peers, setPeers] = useState>({}); const sortPlayers = useCallback( (A: any, B: any) => { if (!session) { return 0; } /* active Player first */ if (A.name === session.name) { return -1; } if (B.name === session.name) { return +1; } /* Sort active Players first */ if (A.name && !B.name) { return -1; } if (B.name && !A.name) { return +1; } /* Otherwise, sort by color */ if (A.color && B.color) { return A.color.localeCompare(B.color); } return 0; }, [session] ); // Use the WebSocket hook for room events with automatic reconnection const { sendJsonMessage } = useWebSocket(socketUrl, { share: true, shouldReconnect: (closeEvent) => true, // Auto-reconnect on connection loss reconnectInterval: 5000, onMessage: (event: MessageEvent) => { if (!session) { return; } const message = JSON.parse(event.data); const data: any = message.data; switch (message.type) { case "room_state": { type RoomStateData = { participants: Player[]; }; const room_state = data as RoomStateData; console.log(`Players - room_state`, room_state.participants); room_state.participants.forEach((Player) => { Player.local = Player.session_id === session.id; }); room_state.participants.sort(sortPlayers); setPlayers(room_state.participants); // Initialize peers with remote mute/video state setPeers((prevPeers) => { const updated: Record = { ...prevPeers }; room_state.participants.forEach((Player) => { // Only update remote peers, never overwrite local peer object if (!Player.local && updated[Player.session_id]) { updated[Player.session_id] = { ...updated[Player.session_id], muted: Player.muted ?? false, video_on: Player.video_on ?? true, }; } }); return updated; }); break; } case "update_name": { // Update local session name immediately if (data && typeof data.name === "string") { session.name = data.name; } break; } case "peer_state_update": { // Update peer state in peers, but do not override local mute setPeers((prevPeers) => { const updated = { ...prevPeers }; const peerId = data.peer_id; if (peerId && updated[peerId]) { updated[peerId] = { ...updated[peerId], muted: data.muted, video_on: data.video_on, }; } return updated; }); break; } default: break; } }, }); useEffect(() => { if (Players !== null) { return; } sendJsonMessage({ type: "list_Players", }); }, [Players, sendJsonMessage]); return ( {Players?.map((Player) => (
{Player.name ? Player.name : Player.session_id}
{Player.protected && (
🔒
)} {Player.bot_instance_id && (
🤖
)}
{Player.name && !Player.live &&
}
{Player.name && Player.live && peers[Player.session_id] && (Player.local || Player.has_media !== false) ? ( ) : Player.name && Player.live && Player.has_media === false ? (
💬 Chat Only
) : ( )}
))}
); }; export { PlayerList };