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);
switch (data.type) {
case 'game-update':
console.log(`ws: message - ${data.type}`, data.update);
if ('name' in data.update && data.update.name !== name) {
console.log(`Updating name to ${data.update.name}`);
setName(data.update.name);

View File

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

View File

@ -278,13 +278,15 @@ const MediaAgent = () => {
return;
}
let update = false;
if (!(name in peers)) {
update = true;
peers[name] = {
local: true,
attributes: {
}
};
if (stream) {
if (!(name in peers)) {
update = true;
peers[name] = {
local: true,
attributes: {
}
};
}
}
for (let key in peers) {
@ -297,14 +299,14 @@ const MediaAgent = () => {
console.log(`MediaAgent - Adding local`, peers);
setPeers(Object.assign({}, peers));
}
}, [peers, name, setPeers]);
}, [peers, name, setPeers, stream]);
useEffect(() => {
useEffect(async () => {
if (!ws) {
return;
}
const setup_local_media = async () => {
const setup_local_media = () => {
/* 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. */
console.log("Requesting access to local audio / video inputs");
@ -314,28 +316,25 @@ const MediaAgent = () => {
navigator.mozGetUserMedia ||
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 */
console.log("Access granted to audio/video");
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' }));
}
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
* 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]);
@ -398,7 +397,6 @@ const MediaControl = ({isSelf, peer}) => {
const [control, setControl] = useState(undefined);
useEffect(() => {
// console.log(peer, peers);
setControl(peers[peer]);
}, [peer, peers, setControl]);

View File

@ -3,8 +3,21 @@
position: relative;
padding: 0.5em;
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 {
width: 1em;
height: 1em;

View File

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

View File

@ -559,15 +559,33 @@ const loadGame = async (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 */
game.unselected = [];
for (let id in game.sessions) {
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.name = session.name;
session.player.status = 'Active';
session.player.live = false;
} else {
session.color = 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;
@ -799,13 +817,22 @@ const setPlayerName = (game, session, name) => {
const id = game.id;
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 */
if (game && name) for (let key in game.sessions) {
for (let key in game.sessions) {
const tmp = game.sessions[key];
if (tmp === session) {
if (tmp === session || !tmp.name) {
continue;
}
if (tmp.name && tmp.name.toLowerCase() === name.toLowerCase()) {
if (tmp.name.toLowerCase() === name.toLowerCase()) {
if (!tmp.player || (Date.now() - tmp.player.lastActive) > 60000) {
rejoin = true;
/* 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.`;
}
}
} 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;
if (!name) {
return `You can not set your name to nothing!`;
}
if (!old) {
if (!session.name) {
message = `A new player has entered the lobby as ${name}.`;
session.name = name;
} else {
@ -844,18 +860,23 @@ const setPlayerName = (game, session, name) => {
part(audio[id], session, game.id);
}
} else {
message = `${old} has changed their name to ${name}.`;
message = `${session.name} has changed their name to ${name}.`;
if (session.ws && id in audio) {
part(audio[id], session, game.id);
}
session.name = name;
}
}
}
if (!session.color) {
console.log(`Adding ${session.name} to the unselected`);
game.unselected.push(session);
}
if (session.ws) {
join(audio[id], session, game.id);
}
console.log(message);
addChatMessage(game, null, message);
return undefined;
}
@ -883,6 +904,16 @@ const setPlayerColor = (game, session, color) => {
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);
let message;
@ -899,24 +930,15 @@ const setPlayerColor = (game, session, color) => {
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 (!color) {
if (message) {
console.log(message);
addChatMessage(game, null, message);
}
return;
}
/* Verify selection is valid */
if (!(color in game.players)) {
return `An invalid player selection was attempted.`;
}
/* Verify selection is not already taken */
for (let key in game.sessions) {
const tmp = game.sessions[key].player;
@ -927,12 +949,19 @@ const setPlayerColor = (game, session, color) => {
/* All good -- set this player to requested selection */
session.player = getPlayer(game, color);
session.player.name = name;
session.player.status = `Active`;
session.player.lastActive = Date.now();
session.color = color;
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)}.`);
const afterActive = getActiveCount(game);
@ -2843,6 +2872,8 @@ const saveGame = async (game) => {
reducedSessions.push(reduced);
}
delete reducedGame.unselected;
/* Save per turn while debugging... */
await writeFile(`games/${game.id}.${game.turns}`, JSON.stringify(reducedGame, null, 2))
.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) => {
const { id } = req.params;
const gameId = id;
@ -2874,13 +2938,17 @@ router.ws("/ws/:id", async (ws, req) => {
ws.on('error', async (event) => {
console.error(`WebSocket error: `, event.message);
const game = await loadGame(gameId);
if (game) {
const session = getSession(game, req.session);
if (session && session.ws) {
session.ws.close();
session.ws = undefined;
}
if (!game) {
return;
}
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) => {
@ -2893,19 +2961,26 @@ router.ws("/ws/:id", async (ws, req) => {
ws.on('close', async (event) => {
const game = await loadGame(gameId);
if (game) {
const session = getSession(game, req.session);
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)}`);
if (!game) {
return;
}
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`);
});
@ -2923,14 +2998,14 @@ router.ws("/ws/:id", async (ws, req) => {
return;
}
const session = getSession(game, req.session);
if (!session) {
console.error(`Unable to obtain session.`);
return;
}
if (!session.ws) {
session.ws = ws;
}
if (session.player) {
session.player.live = true;
}
session.live = true;
session.lastActive = Date.now();
let error = '', update;
@ -2986,7 +3061,7 @@ router.ws("/ws/:id", async (ws, req) => {
case 'game-update':
console.log(`Player ${getName(session)} requested a game update.`);
message = JSON.stringify({
message = JSON.stringify({
type: 'game-update',
update: filterGameForPlayer(game, session)
});
@ -2997,18 +3072,9 @@ router.ws("/ws/:id", async (ws, req) => {
console.log(`${id}:${getName(session)} - setPlayerName - ${data.name}`)
error = setPlayerName(game, session, data.name);
if (error) {
session.ws.send(JSON.stringify({ error }));
session.ws.send(JSON.stringify({ type: 'error', error }));
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) {
const _session = game.sessions[key];
if (!_session.ws) {
@ -3024,13 +3090,13 @@ router.ws("/ws/:id", async (ws, req) => {
break;
case 'set':
console.log(`${id}:${getName(session)} - ${data.type} = ${data.value}`);
console.log(`${id}:${getName(session)} - ${data.type} ${data.field} = ${data.value}`);
update = {};
switch (data.field) {
case 'color':
error = setPlayerColor(game, session, data.value);
if (error) {
session.ws.send(JSON.stringify({ error }));
session.ws.send(JSON.stringify({ type: error, error }));
break;
}
for (let key in game.sessions) {
@ -3053,14 +3119,17 @@ router.ws("/ws/:id", async (ws, req) => {
break;
case 'get':
console.log(`${id}:${getName(session)} - ${data.type}`);
console.log(`${id}:${getName(session)} - ${data.type} ${data.fields.join(',')}`);
update = {};
data.fields.forEach((field) => {
switch (field) {
case 'chat':
case 'startTime':
case 'state':
update[field] = game[field];
update[field] = game[field];
break;
case 'unselected':
update[field] = getFilteredUnselected(game);
break;
case 'players':
update[field] = game[field];
@ -3123,11 +3192,8 @@ router.ws("/ws/:id", async (ws, req) => {
}
const session = getSession(game, req.session);
if (!session) {
console.error(`Session should never be empty after getSession`,
game, req.session);
}
session.lastActive = Date.now();
resetDisconnectCheck(game, req);
console.log(`WebSocket connect from game ${id}:${session.name ? session.name : "Unnamed"}`);
@ -3362,7 +3428,7 @@ const filterGameForPlayer = (game, session) => {
const active = getActiveCount(game);
game.active = active;
/* Calculate points and determine if there is a winner */
for (let key in game.players) {
const player = game.players[key];
@ -3449,6 +3515,8 @@ const filterGameForPlayer = (game, session) => {
/* Strip out data that should not be shared with players */
delete reducedGame.developmentCards;
reducedGame.unselected = getFilteredUnselected(game);
return Object.assign(reducedGame, {
timestamp: Date.now(),
status: session.error ? session.error : "success",