1
0

Audio is now working, although there are loading race conditions :(

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-03-11 01:20:56 -08:00
parent f49351dcdf
commit 6cee42845d
6 changed files with 189 additions and 96 deletions

View File

@ -46,6 +46,7 @@ const Table = () => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
switch (data.type) { switch (data.type) {
case 'game-update': case 'game-update':
console.log(`ws: message - ${data.type}`, data.update);
if ('name' in data.update && data.update.name !== name) { if ('name' in data.update && data.update.name !== name) {
console.log(`Updating name to ${data.update.name}`); console.log(`Updating name to ${data.update.name}`);
setName(data.update.name); setName(data.update.name);

View File

@ -1,6 +1,6 @@
.MediaAgent { .MediaAgent {
position: absolute; position: absolute;
display: flex; display: none;
top: 0; top: 0;
left: 0; left: 0;
z-index: 50000; z-index: 50000;

View File

@ -278,13 +278,15 @@ const MediaAgent = () => {
return; return;
} }
let update = false; let update = false;
if (!(name in peers)) { if (stream) {
update = true; if (!(name in peers)) {
peers[name] = { update = true;
local: true, peers[name] = {
attributes: { local: true,
} attributes: {
}; }
};
}
} }
for (let key in peers) { for (let key in peers) {
@ -297,14 +299,14 @@ const MediaAgent = () => {
console.log(`MediaAgent - Adding local`, peers); console.log(`MediaAgent - Adding local`, peers);
setPeers(Object.assign({}, peers)); setPeers(Object.assign({}, peers));
} }
}, [peers, name, setPeers]); }, [peers, name, setPeers, stream]);
useEffect(() => { useEffect(async () => {
if (!ws) { if (!ws) {
return; return;
} }
const setup_local_media = async () => { const setup_local_media = () => {
/* Ask user for permission to use the computers microphone and/or camera, /* Ask user for permission to use the computers microphone and/or camera,
* attach it to an <audio> or <video> tag if they give us access. */ * attach it to an <audio> or <video> tag if they give us access. */
console.log("Requesting access to local audio / video inputs"); console.log("Requesting access to local audio / video inputs");
@ -314,28 +316,25 @@ const MediaAgent = () => {
navigator.mozGetUserMedia || navigator.mozGetUserMedia ||
navigator.msGetUserMedia); navigator.msGetUserMedia);
return await navigator.mediaDevices.getUserMedia({audio: true, video: false})//, "video": true}) return navigator.mediaDevices.getUserMedia({audio: true, video: false})//, "video": true})
.then((media) => { /* user accepted access to a/v */ .then((media) => { /* user accepted access to a/v */
console.log("Access granted to audio/video"); console.log("Access granted to audio/video");
setStream(media); setStream(media);
})
.catch((error) => { /* user denied access to a/v */
console.error(error);
console.log("Access denied for audio/video");
window.alert("You chose not to provide access to the microphone!" +
"Ketran will have no audio :(");
}); });
}; };
const join_chat = () => { const join = () => {
ws.send(JSON.stringify({ type: 'join' })); ws.send(JSON.stringify({ type: 'join' }));
} }
console.log(`MediaAgent - WebSocket open request. Attempting to create local media.`) console.log(`MediaAgent - WebSocket open request. Attempting to create local media.`)
setup_local_media().then(() => { await setup_local_media().then(() => {
/* once the user has given us access to their /* once the user has given us access to their
* microphone/camcorder, join the channel and start peering up */ * microphone/camcorder, join the channel and start peering up */
join_chat(); join();
}).catch((error) => { /* user denied access to a/v */
console.error(error);
console.log("Access denied for audio/video");
}); });
}, [ws, setStream]); }, [ws, setStream]);
@ -398,7 +397,6 @@ const MediaControl = ({isSelf, peer}) => {
const [control, setControl] = useState(undefined); const [control, setControl] = useState(undefined);
useEffect(() => { useEffect(() => {
// console.log(peer, peers);
setControl(peers[peer]); setControl(peers[peer]);
}, [peer, peers, setControl]); }, [peer, peers, setControl]);

View File

@ -3,8 +3,21 @@
position: relative; position: relative;
padding: 0.5em; padding: 0.5em;
user-select: none; user-select: none;
flex-direction: column;
} }
.PlayerList .Unselected {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.PlayerList .Unselected > div {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.25rem;
}
.PlayerList .PlayerSelector .PlayerColor { .PlayerList .PlayerSelector .PlayerColor {
width: 1em; width: 1em;
height: 1em; height: 1em;

View File

@ -10,6 +10,7 @@ import { GlobalContext } from "./GlobalContext.js";
const PlayerList = () => { const PlayerList = () => {
const { ws, name } = useContext(GlobalContext); const { ws, name } = useContext(GlobalContext);
const [players, setPlayers] = useState({}); const [players, setPlayers] = useState({});
const [unselected, setUneslected] = useState([]);
const [state, setState] = useState('lobby'); const [state, setState] = useState('lobby');
const [color, setColor] = useState(undefined); const [color, setColor] = useState(undefined);
@ -17,7 +18,9 @@ const PlayerList = () => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
switch (data.type) { switch (data.type) {
case 'game-update': case 'game-update':
console.log(`PlayerList - onWsMessage`); if (data.update.unselected) {
setUneslected(data.update.unselected);
}
if (data.update.players) { if (data.update.players) {
let found = false; let found = false;
for (let key in data.update.players) { for (let key in data.update.players) {
@ -61,7 +64,7 @@ const PlayerList = () => {
} }
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: 'get', type: 'get',
fields: [ 'state', 'players' ] fields: [ 'state', 'players', 'unselected' ]
})); }));
}, [ws]); }, [ws]);
@ -94,11 +97,21 @@ const PlayerList = () => {
); );
} }
const waiting = unselected.map((player) => {
return <div key={player}>
<div>{ player }</div>
<MediaControl peer={player} isSelf={name === player}/>
</div>
})
return ( return (
<Paper className="PlayerList"> <Paper className="PlayerList">
<List className="PlayerSelector"> <List className="PlayerSelector">
{ playerElements } { playerElements }
</List> </List>
<div className="Unselected">
{ waiting }
</div>
</Paper> </Paper>
); );
} }

View File

@ -559,15 +559,33 @@ const loadGame = async (id) => {
game = createGame(id); game = createGame(id);
} }
/* Clear out cached names from player colors and rebuild them
* from the information in the saved game sessions */
for (let color in game.players) {
delete game.players[color].name;
game.players[color].status = 'Not active';
}
/* Reconnect session player colors to the player objects */ /* Reconnect session player colors to the player objects */
game.unselected = [];
for (let id in game.sessions) { for (let id in game.sessions) {
const session = game.sessions[id]; const session = game.sessions[id];
if (session.color && session.color in game.players) { if (session.name && session.color && session.color in game.players) {
session.player = game.players[session.color]; session.player = game.players[session.color];
session.player.name = session.name;
session.player.status = 'Active';
session.player.live = false;
} else { } else {
session.color = undefined; session.color = undefined;
session.player = undefined; session.player = undefined;
} }
session.live = false;
/* Populate the 'unselected' list from the session table */
if (!game.sessions[id].color && game.sessions[id].name) {
game.unselected.push(game.sessions[id]);
}
} }
games[id] = game; games[id] = game;
@ -799,13 +817,22 @@ const setPlayerName = (game, session, name) => {
const id = game.id; const id = game.id;
let rejoin = false; let rejoin = false;
if (!name) {
return `You can not set your name to nothing!`;
}
if (name.toLowerCase() === 'the bank') {
return `You cannot play as the bank!`;
}
/* Check to ensure name is not already in use */ /* Check to ensure name is not already in use */
if (game && name) for (let key in game.sessions) { for (let key in game.sessions) {
const tmp = game.sessions[key]; const tmp = game.sessions[key];
if (tmp === session) { if (tmp === session || !tmp.name) {
continue; continue;
} }
if (tmp.name && tmp.name.toLowerCase() === name.toLowerCase()) { if (tmp.name.toLowerCase() === name.toLowerCase()) {
if (!tmp.player || (Date.now() - tmp.player.lastActive) > 60000) { if (!tmp.player || (Date.now() - tmp.player.lastActive) > 60000) {
rejoin = true; rejoin = true;
/* Update the session object from tmp, but retain websocket /* Update the session object from tmp, but retain websocket
@ -818,22 +845,11 @@ const setPlayerName = (game, session, name) => {
return `${name} is already taken and has been active in the last minute.`; return `${name} is already taken and has been active in the last minute.`;
} }
} }
} else {
console.log(`Attempting to create new player for ${name}`);
} }
if (name.toLowerCase() === 'the bank') {
return `You cannot play as the bank!`;
}
const old = session.name;
let message; let message;
if (!name) { if (!session.name) {
return `You can not set your name to nothing!`;
}
if (!old) {
message = `A new player has entered the lobby as ${name}.`; message = `A new player has entered the lobby as ${name}.`;
session.name = name; session.name = name;
} else { } else {
@ -844,18 +860,23 @@ const setPlayerName = (game, session, name) => {
part(audio[id], session, game.id); part(audio[id], session, game.id);
} }
} else { } else {
message = `${old} has changed their name to ${name}.`; message = `${session.name} has changed their name to ${name}.`;
if (session.ws && id in audio) { if (session.ws && id in audio) {
part(audio[id], session, game.id); part(audio[id], session, game.id);
} }
session.name = name; session.name = name;
} }
} }
if (!session.color) {
console.log(`Adding ${session.name} to the unselected`);
game.unselected.push(session);
}
if (session.ws) { if (session.ws) {
join(audio[id], session, game.id); join(audio[id], session, game.id);
} }
console.log(message);
addChatMessage(game, null, message); addChatMessage(game, null, message);
return undefined; return undefined;
} }
@ -883,6 +904,16 @@ const setPlayerColor = (game, session, color) => {
return; return;
} }
/* Verify the player has a name set */
if (!name) {
return `You may only select a player when you have set your name.`;
}
/* Verify selection is valid */
if (!(color in game.players)) {
return `An invalid player selection was attempted.`;
}
const priorActive = getActiveCount(game); const priorActive = getActiveCount(game);
let message; let message;
@ -899,24 +930,15 @@ const setPlayerColor = (game, session, color) => {
session.color = undefined; session.color = undefined;
} }
/* Verify the player has a name set */
if (!name) {
return `You may only select a player when you have set your name.`;
}
/* If the player is not selecting a color, then return */ /* If the player is not selecting a color, then return */
if (!color) { if (!color) {
if (message) { if (message) {
console.log(message);
addChatMessage(game, null, message); addChatMessage(game, null, message);
} }
return; return;
} }
/* Verify selection is valid */
if (!(color in game.players)) {
return `An invalid player selection was attempted.`;
}
/* Verify selection is not already taken */ /* Verify selection is not already taken */
for (let key in game.sessions) { for (let key in game.sessions) {
const tmp = game.sessions[key].player; const tmp = game.sessions[key].player;
@ -927,12 +949,19 @@ const setPlayerColor = (game, session, color) => {
/* All good -- set this player to requested selection */ /* All good -- set this player to requested selection */
session.player = getPlayer(game, color); session.player = getPlayer(game, color);
session.player.name = name;
session.player.status = `Active`; session.player.status = `Active`;
session.player.lastActive = Date.now(); session.player.lastActive = Date.now();
session.color = color; session.color = color;
game.players[color].name = session.name; game.players[color].name = session.name;
/* Rebuild the unselected list */
game.unselected = [];
for (let id in game.sessions) {
if (!game.sessions.color[id] && game.sessions[id].name) {
game.unselected.push(game.sessions[id]);
}
}
addChatMessage(game, session, `${session.name} has chosen to play as ${colorToWord(color)}.`); addChatMessage(game, session, `${session.name} has chosen to play as ${colorToWord(color)}.`);
const afterActive = getActiveCount(game); const afterActive = getActiveCount(game);
@ -2843,6 +2872,8 @@ const saveGame = async (game) => {
reducedSessions.push(reduced); reducedSessions.push(reduced);
} }
delete reducedGame.unselected;
/* Save per turn while debugging... */ /* Save per turn while debugging... */
await writeFile(`games/${game.id}.${game.turns}`, JSON.stringify(reducedGame, null, 2)) await writeFile(`games/${game.id}.${game.turns}`, JSON.stringify(reducedGame, null, 2))
.catch((error) => { .catch((error) => {
@ -2856,6 +2887,39 @@ const saveGame = async (game) => {
}); });
} }
const departLobby = (game, session) => {
const update = {};
update.unselected = getFilteredUnselected(game);
if (session.player) {
session.player.live = true;
update.players = game.players;
}
if (session.name) {
addChatMessage(game, null, `${session.name} has left the lobby.`);
update.chat = game.chat;
}
const message = JSON.stringify({
type: 'game-update',
update
});
for (let key in game.sessions) {
const _session = game.sessions[key];
if (!_session.ws) {
continue;
}
_session.ws.send(message);
}
}
const getFilteredUnselected = (game) => {
return game.unselected
.filter(session => session.live)
.map(session => session.name);
}
router.ws("/ws/:id", async (ws, req) => { router.ws("/ws/:id", async (ws, req) => {
const { id } = req.params; const { id } = req.params;
const gameId = id; const gameId = id;
@ -2874,13 +2938,17 @@ router.ws("/ws/:id", async (ws, req) => {
ws.on('error', async (event) => { ws.on('error', async (event) => {
console.error(`WebSocket error: `, event.message); console.error(`WebSocket error: `, event.message);
const game = await loadGame(gameId); const game = await loadGame(gameId);
if (game) { if (!game) {
const session = getSession(game, req.session); return;
if (session && session.ws) {
session.ws.close();
session.ws = undefined;
}
} }
const session = getSession(game, req.session);
session.live = false;
if (session.ws) {
session.ws.close();
session.ws = undefined;
}
departLobby(game, session);
}); });
ws.on('open', async (event) => { ws.on('open', async (event) => {
@ -2893,18 +2961,25 @@ router.ws("/ws/:id", async (ws, req) => {
ws.on('close', async (event) => { ws.on('close', async (event) => {
const game = await loadGame(gameId); const game = await loadGame(gameId);
if (game) { if (!game) {
const session = getSession(game, req.session); return;
if (session && session.ws) {
/* Cleanup any voice channels */
if (id in audio) {
part(audio[id], session, id);
}
session.ws.close();
session.ws = undefined;
console.log(`WebSocket closed for ${getName(session)}`);
}
} }
const session = getSession(game, req.session);
if (session.player) {
session.player.live = false;
}
session.live = false;
if (session.ws) {
/* Cleanup any voice channels */
if (id in audio) {
part(audio[id], session, id);
}
session.ws.close();
session.ws = undefined;
console.log(`WebSocket closed for ${getName(session)}`);
}
departLobby(game, session);
console.log(`${id}:${ws.id} - closed connection`); console.log(`${id}:${ws.id} - closed connection`);
}); });
@ -2923,14 +2998,14 @@ router.ws("/ws/:id", async (ws, req) => {
return; return;
} }
const session = getSession(game, req.session); const session = getSession(game, req.session);
if (!session) {
console.error(`Unable to obtain session.`);
return;
}
if (!session.ws) { if (!session.ws) {
session.ws = ws; session.ws = ws;
} }
if (session.player) {
session.player.live = true;
}
session.live = true;
session.lastActive = Date.now();
let error = '', update; let error = '', update;
@ -2986,7 +3061,7 @@ router.ws("/ws/:id", async (ws, req) => {
case 'game-update': case 'game-update':
console.log(`Player ${getName(session)} requested a game update.`); console.log(`Player ${getName(session)} requested a game update.`);
message = JSON.stringify({ message = JSON.stringify({
type: 'game-update', type: 'game-update',
update: filterGameForPlayer(game, session) update: filterGameForPlayer(game, session)
}); });
@ -2997,18 +3072,9 @@ router.ws("/ws/:id", async (ws, req) => {
console.log(`${id}:${getName(session)} - setPlayerName - ${data.name}`) console.log(`${id}:${getName(session)} - setPlayerName - ${data.name}`)
error = setPlayerName(game, session, data.name); error = setPlayerName(game, session, data.name);
if (error) { if (error) {
session.ws.send(JSON.stringify({ error })); session.ws.send(JSON.stringify({ type: 'error', error }));
break; break;
} }
update = {};
session.name = data.name;
update.name = session.name
if (session.color && session.color in game.players) {
game.players[session.color].name = session.name;
update.players = game.players;
}
for (let key in game.sessions) { for (let key in game.sessions) {
const _session = game.sessions[key]; const _session = game.sessions[key];
if (!_session.ws) { if (!_session.ws) {
@ -3024,13 +3090,13 @@ router.ws("/ws/:id", async (ws, req) => {
break; break;
case 'set': case 'set':
console.log(`${id}:${getName(session)} - ${data.type} = ${data.value}`); console.log(`${id}:${getName(session)} - ${data.type} ${data.field} = ${data.value}`);
update = {}; update = {};
switch (data.field) { switch (data.field) {
case 'color': case 'color':
error = setPlayerColor(game, session, data.value); error = setPlayerColor(game, session, data.value);
if (error) { if (error) {
session.ws.send(JSON.stringify({ error })); session.ws.send(JSON.stringify({ type: error, error }));
break; break;
} }
for (let key in game.sessions) { for (let key in game.sessions) {
@ -3053,14 +3119,17 @@ router.ws("/ws/:id", async (ws, req) => {
break; break;
case 'get': case 'get':
console.log(`${id}:${getName(session)} - ${data.type}`); console.log(`${id}:${getName(session)} - ${data.type} ${data.fields.join(',')}`);
update = {}; update = {};
data.fields.forEach((field) => { data.fields.forEach((field) => {
switch (field) { switch (field) {
case 'chat': case 'chat':
case 'startTime': case 'startTime':
case 'state': case 'state':
update[field] = game[field]; update[field] = game[field];
break;
case 'unselected':
update[field] = getFilteredUnselected(game);
break; break;
case 'players': case 'players':
update[field] = game[field]; update[field] = game[field];
@ -3123,10 +3192,7 @@ router.ws("/ws/:id", async (ws, req) => {
} }
const session = getSession(game, req.session); const session = getSession(game, req.session);
if (!session) { session.lastActive = Date.now();
console.error(`Session should never be empty after getSession`,
game, req.session);
}
resetDisconnectCheck(game, req); resetDisconnectCheck(game, req);
@ -3449,6 +3515,8 @@ const filterGameForPlayer = (game, session) => {
/* Strip out data that should not be shared with players */ /* Strip out data that should not be shared with players */
delete reducedGame.developmentCards; delete reducedGame.developmentCards;
reducedGame.unselected = getFilteredUnselected(game);
return Object.assign(reducedGame, { return Object.assign(reducedGame, {
timestamp: Date.now(), timestamp: Date.now(),
status: session.error ? session.error : "success", status: session.error ? session.error : "success",