import React, { useState, useEffect, useCallback, useContext } from "react"; import Paper from "@mui/material/Paper"; import List from "@mui/material/List"; import "./PlayerList.css"; import { MediaControl, MediaAgent, Peer } from "./MediaControl"; import { PlayerColor } from "./PlayerColor"; import Box from "@mui/material/Box"; import { GlobalContext } from "./GlobalContext"; 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 color?: string; bot_run_id?: string; bot_provider_id?: string; bot_instance_id?: string; // For bot instances muted?: boolean; video_on?: boolean; }; const PlayerList: React.FC = () => { const { session, socketUrl, lastJsonMessage, sendJsonMessage } = useContext(GlobalContext); const [players, setPlayers] = useState(null); const [peers, setPeers] = useState>({}); useEffect(() => { console.log("player-list - Mounted - requesting fields"); if (sendJsonMessage) { sendJsonMessage({ type: "get", fields: ["participants"], }); } }, [sendJsonMessage]); 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] ); useEffect(() => { if (!players) { return; } players.forEach((player) => { console.log("rabbit - player:", { name: player.name, live: player.live, in_peers: peers[player.session_id], local_or_media: player.local || player.has_media !== false, }); }); }, [players]); // Use the WebSocket hook for room events with automatic reconnection useEffect(() => { if (!lastJsonMessage) { return; } const data: any = lastJsonMessage; switch (data.type) { case "game-update": { console.log(`player-list - game-update:`, data.update); // Handle participants list if ("participants" in data.update && data.update.participants) { const participantsList: Player[] = data.update.participants; console.log(`player-list - participants:`, participantsList); participantsList.forEach((player) => { player.local = player.session_id === session?.id; }); participantsList.sort(sortPlayers); console.log(`player-list - sorted participants:`, participantsList); setPlayers(participantsList); // Initialize peers with remote mute/video state setPeers((prevPeers) => { const updated: Record = { ...prevPeers }; participantsList.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 "peer_state_update": { // Update peer state in peers, but do not override local mute setPeers((prevPeers) => { const updated = { ...prevPeers }; const peerId = data.data?.peer_id || data.peer_id; if (peerId && updated[peerId]) { updated[peerId] = { ...updated[peerId], muted: data.data?.muted ?? data.muted, video_on: data.data?.video_on ?? data.video_on, }; } return updated; }); break; } default: // console.log(`player-list - ignoring message: ${data.type}`); break; } }, [lastJsonMessage, session, sortPlayers, setPeers, setPlayers]); useEffect(() => { if (players !== null || !sendJsonMessage) { return; } // Request participants list sendJsonMessage({ type: "get", fields: ["participants"], }); }, [players, sendJsonMessage]); return ( {players?.map((player) => { const peerObj = peers[player.session_id] || peers[player.name]; return (
{player.name ? player.name : player.session_id}
{player.protected && (
🔒
)} {player.bot_instance_id && (
🤖
)}
{player.name && !player.live &&
}
{player.name && player.live && peerObj && (player.local || player.has_media !== false) ? ( <> {/* If this is the local player and they haven't picked a color, show a picker */} {player.local && player.color === "unassigned" && (
Pick your color:
{["orange", "red", "white", "blue"].map((c) => ( { if (!sendJsonMessage) return; sendJsonMessage({ type: "set", field: "color", value: c[0].toUpperCase() }); }} > ))}
)} ) : player.name && player.live && player.has_media === false ? (
💬 Chat Only
) : ( )}
); })}
); }; export { PlayerList };