1
0

Meida working

This commit is contained in:
James Ketr 2025-10-10 20:01:42 -07:00
parent 579632c293
commit a586f3b491
3 changed files with 123 additions and 113 deletions

View File

@ -163,97 +163,100 @@ const PlayerList: React.FC = () => {
> >
<MediaAgent {...{ session, peers, setPeers }} /> <MediaAgent {...{ session, peers, setPeers }} />
<List className="PlayerSelector"> <List className="PlayerSelector">
{players?.map((player) => ( {players?.map((player) => {
<Box const peerObj = peers[player.session_id] || peers[player.name];
key={player.session_id} return (
sx={{ display: "flex", flexDirection: "column", alignItems: "center" }} <Box
className={`PlayerEntry ${player.local ? "PlayerSelf" : ""}`} key={player.session_id}
> sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}
<Box> className={`PlayerEntry ${player.local ? "PlayerSelf" : ""}`}
<Box style={{ display: "flex-wrap", alignItems: "center", justifyContent: "space-between" }}> >
<Box style={{ display: "flex-wrap", alignItems: "center" }}> <Box>
<div className="Name">{player.name ? player.name : player.session_id}</div> <Box style={{ display: "flex-wrap", alignItems: "center", justifyContent: "space-between" }}>
{player.protected && ( <Box style={{ display: "flex-wrap", alignItems: "center" }}>
<div <div className="Name">{player.name ? player.name : player.session_id}</div>
style={{ marginLeft: 8, fontSize: "0.8em", color: "#a00" }} {player.protected && (
title="This name is protected with a password" <div
> style={{ marginLeft: 8, fontSize: "0.8em", color: "#a00" }}
🔒 title="This name is protected with a password"
</div> >
)} 🔒
{player.bot_instance_id && ( </div>
<div style={{ marginLeft: 8, fontSize: "0.8em", color: "#00a" }} title="This is a bot"> )}
🤖 {player.bot_instance_id && (
</div> <div style={{ marginLeft: 8, fontSize: "0.8em", color: "#00a" }} title="This is a bot">
)} 🤖
</div>
)}
</Box>
</Box> </Box>
{player.name && !player.live && <div className="NoNetwork"></div>}
</Box> </Box>
{player.name && !player.live && <div className="NoNetwork"></div>} {player.name && player.live && peerObj && (player.local || player.has_media !== false) ? (
</Box> <>
{player.name && player.live && peers[player.session_id] && (player.local || player.has_media !== false) ? ( <MediaControl
<> sx={{ border: "3px solid blue" }}
<MediaControl className="Medium"
sx={{ border: "3px solid blue" }} key={player.session_id}
className="Medium" peer={peerObj}
key={player.session_id} isSelf={player.local}
peer={peers[player.session_id]} sendJsonMessage={player.local ? sendJsonMessage : undefined}
isSelf={player.local} remoteAudioMuted={peerObj?.muted}
sendJsonMessage={player.local ? sendJsonMessage : undefined} remoteVideoOff={peerObj?.video_on === false}
remoteAudioMuted={peers[player.session_id].muted} />
remoteVideoOff={peers[player.session_id].video_on === false}
/>
{/* If this is the local player and they haven't picked a color, show a picker */} {/* If this is the local player and they haven't picked a color, show a picker */}
{player.local && player.color === "unassigned" && ( {player.local && player.color === "unassigned" && (
<div style={{ marginTop: 8, width: "100%" }}> <div style={{ marginTop: 8, width: "100%" }}>
<div style={{ marginBottom: 6, fontSize: "0.9em" }}>Pick your color:</div> <div style={{ marginBottom: 6, fontSize: "0.9em" }}>Pick your color:</div>
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: "flex", gap: 8 }}>
{["orange", "red", "white", "blue"].map((c) => ( {["orange", "red", "white", "blue"].map((c) => (
<Box <Box
key={c} key={c}
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: 8, gap: 8,
padding: "6px 8px", padding: "6px 8px",
borderRadius: 6, borderRadius: 6,
border: "1px solid #ccc", border: "1px solid #ccc",
background: "#fff", background: "#fff",
cursor: sendJsonMessage ? "pointer" : "not-allowed", cursor: sendJsonMessage ? "pointer" : "not-allowed",
}} }}
onClick={() => { onClick={() => {
if (!sendJsonMessage) return; if (!sendJsonMessage) return;
sendJsonMessage({ type: "set", field: "color", value: c[0].toUpperCase() }); sendJsonMessage({ type: "set", field: "color", value: c[0].toUpperCase() });
}} }}
> >
<PlayerColor color={c} /> <PlayerColor color={c} />
</Box> </Box>
))} ))}
</div>
</div> </div>
</div> )}
)} </>
</> ) : player.name && player.live && player.has_media === false ? (
) : player.name && player.live && player.has_media === false ? ( <div
<div className="Video fade-in"
className="Video fade-in" style={{
style={{ background: "#333",
background: "#333", color: "#fff",
color: "#fff", display: "flex",
display: "flex", alignItems: "center",
alignItems: "center", justifyContent: "center",
justifyContent: "center", width: "100%",
width: "100%", height: "100%",
height: "100%", fontSize: "14px",
fontSize: "14px", }}
}} >
> 💬 Chat Only
💬 Chat Only </div>
</div> ) : (
) : ( <video className="Video"></video>
<video className="Video"></video> )}
)} </Box>
</Box> );
))} })}
</List> </List>
</Paper> </Paper>
</Box> </Box>

View File

@ -1222,13 +1222,13 @@ const setPlayerName = (game: Game, session: Session, name: string): string | und
message = `${name} has rejoined the lobby.`; message = `${name} has rejoined the lobby.`;
} }
session.name = name; session.name = name;
if (session.ws && game.id in audio && session.id in audio[game.id]) { if (session.ws && game.id in audio && session.id in audio[game.id]!) {
webrtcPart(audio[game.id], session); webrtcPart(audio[game.id]!, session);
} }
} else { } else {
message = `${session.name} has changed their name to ${name}.`; message = `${session.name} hs changed their name to ${name}.`;
if (session.ws && game.id in audio) { if (session.ws && game.id in audio) {
webrtcPart(audio[game.id], session); webrtcPart(audio[game.id]!, session);
} }
} }
} }
@ -1245,7 +1245,7 @@ const setPlayerName = (game: Game, session: Session, name: string): string | und
} }
if (session.ws && session.hasAudio) { if (session.ws && session.hasAudio) {
webrtcJoin(audio[game.id], session); webrtcJoin(audio[game.id]!, session);
} }
console.log(`${info}: ${message}`); console.log(`${info}: ${message}`);
addChatMessage(game, null, message); addChatMessage(game, null, message);
@ -3726,7 +3726,7 @@ router.ws("/ws/:id", async (ws, req) => {
/* ignore logging errors */ /* ignore logging errors */
} }
if (!(gameId in audio)) { if (!(gameId in audio)) {
audio[gameId] = {}; /* List of peer sockets using session.id as index. */ audio[gameId] = {}; /* List of peer sockets using session.id as index. */
console.log(`${short}: Game ${gameId} - New Game Audio`); console.log(`${short}: Game ${gameId} - New Game Audio`);
} else { } else {
console.log(`${short}: Game ${gameId} - Already has Audio`); console.log(`${short}: Game ${gameId} - Already has Audio`);
@ -3822,7 +3822,7 @@ router.ws("/ws/:id", async (ws, req) => {
/* Cleanup any voice channels */ /* Cleanup any voice channels */
if (gameId in audio) { if (gameId in audio) {
try { try {
webrtcPart(audio[gameId], session); webrtcPart(audio[gameId]!, session);
} catch (e) { } catch (e) {
console.warn(`${short}: Error during part():`, e); console.warn(`${short}: Error during part():`, e);
} }
@ -3932,7 +3932,7 @@ router.ws("/ws/:id", async (ws, req) => {
// Clean up peer from audio registry before replacing WebSocket // Clean up peer from audio registry before replacing WebSocket
if (gameId in audio) { if (gameId in audio) {
try { try {
webrtcPart(audio[gameId], session); webrtcPart(audio[gameId]!, session);
console.log(`${short}: Cleaned up peer ${session.name} from audio registry during reconnection`); console.log(`${short}: Cleaned up peer ${session.name} from audio registry during reconnection`);
} catch (e) { } catch (e) {
console.warn(`${short}: Error cleaning up peer during reconnection:`, e); console.warn(`${short}: Error cleaning up peer during reconnection:`, e);
@ -3968,11 +3968,11 @@ router.ws("/ws/:id", async (ws, req) => {
// Accept either legacy `config`, newer `data`, or flat payloads where // Accept either legacy `config`, newer `data`, or flat payloads where
// the client sent fields at the top level (normalizeIncoming will // the client sent fields at the top level (normalizeIncoming will
// populate `data` with the parsed object in that case). // populate `data` with the parsed object in that case).
webrtcJoin(audio[gameId], session); webrtcJoin(audio[gameId]!, session);
break; break;
case "part": case "part":
webrtcPart(audio[gameId], session); webrtcPart(audio[gameId]!, session);
break; break;
case "relayICECandidate": case "relayICECandidate":

View File

@ -10,7 +10,13 @@
import { Session } from "./games/types"; import { Session } from "./games/types";
export const audio: Record<string, any> = {}; interface Peer {
ws: any;
name: string;
}
/* Map of session => peer_id => peer */
export const audio: Record<string, Record<string, Peer>> = {};
// Default send helper used when caller doesn't provide a safeSend implementation. // Default send helper used when caller doesn't provide a safeSend implementation.
const defaultSend = (targetOrSession: any, message: any): boolean => { const defaultSend = (targetOrSession: any, message: any): boolean => {
@ -29,7 +35,7 @@ const defaultSend = (targetOrSession: any, message: any): boolean => {
} }
}; };
export const join = (peers: any, session: any): void => { export const join = (peers: Record<string, Peer>, session: Session): void => {
const send = defaultSend; const send = defaultSend;
const ws = session.ws; const ws = session.ws;
@ -45,18 +51,19 @@ export const join = (peers: any, session: any): void => {
console.log(`${session.short}: <- join - ${session.name}`); console.log(`${session.short}: <- join - ${session.name}`);
const peer = peers[session.id];
// Use session.id as the canonical peer key // Use session.id as the canonical peer key
if (session.id in peers) { if (peer) {
console.log(`${session.short}:${session.id} - Already joined to Audio, updating WebSocket reference.`); console.log(`${session.short}:${session.id} - Already joined to Audio, updating WebSocket reference.`);
try { try {
const prev = peers[session.id] && peers[session.id].ws; const prev = peer.ws;
if (prev && prev._pingInterval) { if (prev && prev._pingInterval) {
clearInterval(prev._pingInterval); clearInterval(prev._pingInterval);
} }
} catch (e) { } catch (e) {
/* ignore */ /* ignore */
} }
peers[session.id].ws = ws; peer.ws = ws;
send(ws, { send(ws, {
type: "join_status", type: "join_status",
@ -72,7 +79,7 @@ export const join = (peers: any, session: any): void => {
type: "addPeer", type: "addPeer",
data: { data: {
peer_id: peerId, peer_id: peerId,
peer_name: peers[peerId].name || peerId, peer_name: peers[peerId]!.name,
should_create_offer: true, should_create_offer: true,
}, },
}); });
@ -82,7 +89,7 @@ export const join = (peers: any, session: any): void => {
for (const peerId in peers) { for (const peerId in peers) {
if (peerId === session.id) continue; if (peerId === session.id) continue;
send(peers[peerId].ws, { send(peers[peerId]!.ws, {
type: "addPeer", type: "addPeer",
data: { data: {
peer_id: session.id, peer_id: session.id,
@ -97,7 +104,7 @@ export const join = (peers: any, session: any): void => {
for (let peerId in peers) { for (let peerId in peers) {
// notify existing peers about the new client // notify existing peers about the new client
send(peers[peerId].ws, { send(peers[peerId]!.ws, {
type: "addPeer", type: "addPeer",
data: { data: {
peer_id: session.id, peer_id: session.id,
@ -111,7 +118,7 @@ export const join = (peers: any, session: any): void => {
type: "addPeer", type: "addPeer",
data: { data: {
peer_id: peerId, peer_id: peerId,
peer_name: peers[peerId].name || peerId, peer_name: peers[peerId]!.name || peerId,
should_create_offer: true, should_create_offer: true,
}, },
}); });
@ -130,7 +137,7 @@ export const join = (peers: any, session: any): void => {
}); });
}; };
export const part = (peers: any, session: any): void => { export const part = (peers: Record<string, Peer>, session: Session): void => {
const ws = session.ws; const ws = session.ws;
const send = defaultSend; const send = defaultSend;
@ -151,7 +158,7 @@ export const part = (peers: any, session: any): void => {
delete peers[session.id]; delete peers[session.id];
for (let peerId in peers) { for (let peerId in peers) {
send(peers[peerId].ws, { send(peers[peerId]!.ws, {
type: "removePeer", type: "removePeer",
data: { data: {
peer_id: session.id, peer_id: session.id,
@ -162,7 +169,7 @@ export const part = (peers: any, session: any): void => {
type: "removePeer", type: "removePeer",
data: { data: {
peer_id: peerId, peer_id: peerId,
peer_name: peers[peerId].name || peerId, peer_name: peers[peerId]!.name || peerId,
}, },
}); });
} }
@ -195,8 +202,8 @@ export const handleRelayICECandidate = (gameId: string, cfg: any, session: Sessi
}, },
}); });
if (peer_id in audio[gameId]) { if (peer_id in audio[gameId]!) {
const target = audio[gameId][peer_id] as any; const target = audio[gameId]![peer_id] as any;
if (!target || !target.ws) { if (!target || !target.ws) {
console.warn(`${session.id}:${gameId} relayICECandidate - target ${peer_id} has no ws`); console.warn(`${session.id}:${gameId} relayICECandidate - target ${peer_id} has no ws`);
} else if (!send(target.ws, message)) { } else if (!send(target.ws, message)) {
@ -236,8 +243,8 @@ export const handleRelaySessionDescription = (gameId: string, cfg: any, session:
session_description, session_description,
}, },
}); });
if (peer_id in audio[gameId]) { if (peer_id in audio[gameId]!) {
const target = audio[gameId][peer_id] as any; const target = audio[gameId]![peer_id] as any;
if (!target || !target.ws) { if (!target || !target.ws) {
console.warn(`${session.id}:${gameId} relaySessionDescription - target ${peer_id} has no ws`); console.warn(`${session.id}:${gameId} relaySessionDescription - target ${peer_id} has no ws`);
} else if (!send(target.ws, message)) { } else if (!send(target.ws, message)) {