diff --git a/client/.eslintcache b/client/.eslintcache index cbd9fcd..fe6dee0 100644 --- a/client/.eslintcache +++ b/client/.eslintcache @@ -1 +1 @@ -[{"/Users/domenico/dev/games-party/games-party/client/src/reportWebVitals.tsx":"1","/Users/domenico/dev/games-party/games-party/client/src/App.tsx":"2","/Users/domenico/dev/games-party/games-party/client/src/index.tsx":"3","/Users/domenico/dev/games-party/games-party/client/src/pages/LobbyPage.tsx":"4","/Users/domenico/dev/games-party/games-party/client/src/pages/PlayRoomPage.tsx":"5","/Users/domenico/dev/games-party/games-party/client/src/context/store.tsx":"6","/Users/domenico/dev/games-party/games-party/client/src/components/Player.tsx":"7","/Users/domenico/dev/games-party/games-party/client/src/components/StashManagementDrawer.tsx":"8","/Users/domenico/dev/games-party/games-party/client/src/context/stashManagementContext.tsx":"9","/Users/domenico/dev/games-party/games-party/client/src/components/Hand.tsx":"10","/Users/domenico/dev/games-party/games-party/client/src/components/Prompt.tsx":"11","/Users/domenico/dev/games-party/games-party/client/src/context/chatContext.tsx":"12"},{"size":362,"mtime":1609249563286,"results":"13","hashOfConfig":"14"},{"size":919,"mtime":1610987751265,"results":"15","hashOfConfig":"14"},{"size":504,"mtime":1609784519249,"results":"16","hashOfConfig":"14"},{"size":4531,"mtime":1610826307035,"results":"17","hashOfConfig":"14"},{"size":11671,"mtime":1611241493652,"results":"18","hashOfConfig":"14"},{"size":2713,"mtime":1611222742326,"results":"19","hashOfConfig":"14"},{"size":6545,"mtime":1611241128984,"results":"20","hashOfConfig":"14"},{"size":3492,"mtime":1610732080352,"results":"21","hashOfConfig":"14"},{"size":1366,"mtime":1610176599048,"results":"22","hashOfConfig":"14"},{"size":1005,"mtime":1610576678797,"results":"23","hashOfConfig":"14"},{"size":3854,"mtime":1610826192773,"results":"24","hashOfConfig":"14"},{"size":6310,"mtime":1611243735412,"results":"25","hashOfConfig":"14"},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},"1ynk1tg",{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"35","messages":"36","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"37","usedDeprecatedRules":"28"},{"filePath":"38","messages":"39","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"40","messages":"41","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"42","messages":"43","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"44","messages":"45","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"46","messages":"47","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"48","messages":"49","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"50"},{"filePath":"51","messages":"52","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"53","usedDeprecatedRules":"28"},"/Users/domenico/dev/games-party/games-party/client/src/reportWebVitals.tsx",[],["54","55"],"/Users/domenico/dev/games-party/games-party/client/src/App.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/index.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/pages/LobbyPage.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/pages/PlayRoomPage.tsx",["56"],"import { useCallback, useContext, useEffect, useState } from 'react';\nimport { Badge, Layout, Row, Col, Space, Typography, Button, message, Statistic } from 'antd';\nimport { PauseCircleOutlined, PlaySquareOutlined } from '@ant-design/icons';\nimport { FullScreen, useFullScreenHandle } from \"react-full-screen\";\nimport { useLocation } from 'wouter';\n\nimport { GameContext } from '../context/store';\nimport { StashManagementProvider } from '../context/stashManagementContext';\n\nimport StashManagementDrawer from '../components/StashManagementDrawer';\nimport Player from '../components/Player';\nimport Hand from '../components/Hand';\nimport Prompt from '../components/Prompt';\n\nconst { Content } = Layout;\nconst { Title } = Typography;\n\nconst PlayRoomPage = ({ params }) => {\n const roomId = params.roomId;\n const { state, dispatch } = useContext(GameContext);\n const [, setLocation] = useLocation();\n const fullScreenHandle = useFullScreenHandle();\n\n const [isRoomOwner, setIsRoomOwner] = useState(false);\n const [thisPlayerHand, setThisPlayerHand] = useState([]);\n const [thisPlayerPrompt, setThisPlayerPrompt] = useState([]);\n const [thisPlayerScore, setThisPlayerScore] = useState();\n\n useEffect(() => {\n console.log('Room id:', roomId);\n console.log('Global state:', state);\n console.log('Current user:', state.session?.display_name, state.session?.user_id);\n\n const joinRoom = async () => {\n if (state.room === null) {\n console.log('Room not joined yet:', state.gameState.user_id, state.gameState.display_name);\n try {\n const room = await state.client.joinById(roomId, {\n user_id: state.session?.user_id,\n display_name: state.session?.display_name\n });\n dispatch({ type: 'JOIN_ROOM', payload: room });\n } catch (error) {\n message.error('Stanza non tovata');\n setLocation('/');\n }\n } else {\n console.log('Registering room handlers');\n state.room.onStateChange((newState) => {\n console.log('Room state changed:', newState);\n dispatch({ type: 'GAME_STATE', payload: { ...newState } });\n const isOwner = newState.roomOwner === state.session?.user_id;\n setIsRoomOwner(isOwner);\n });\n\n state.room.onMessage('notification', (msg) => {\n console.log('Message received from server:', msg);\n const msg_type = msg.type || 'info';\n message[msg_type](msg.text);\n });\n }\n };\n joinRoom();\n\n return () => {\n if (state.room) {\n console.log('Removing room listeners...');\n state.room.removeAllListeners();\n dispatch({ type: 'LEAVE_ROOM' });\n }\n }\n }, [state.room]);\n\n useEffect(() => {\n if (state.room === null) return;\n\n // Setting the current player reference\n const p = state.gameState.players.get(state.room.sessionId);\n if (p !== undefined) {\n console.log('Setting current player hand:', p, p.hand);\n setThisPlayerHand(p.hand);\n setThisPlayerPrompt(p.prompt);\n setThisPlayerScore(p.score);\n }\n }, [state.room, state.gameState]);\n\n const handleStartGame = useCallback(() => {\n state.room.send('admin', { command: 'start-game' });\n }, [state.room]);\n\n const handlePauseGame = useCallback(() => {\n state.room.send('admin', { command: 'pause-game' });\n }, [state.room]);\n\n const handleResumeGame = useCallback(() => {\n state.room.send('resume-game', {});\n }, [state.room]);\n\n return (\n \n \n \n \n \n \n {isRoomOwner && (\n \n {!state.gameState.running && (\n */}\n \n \n \n \n \n \n {thisPlayerHand.length > 0 && }\n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n\nexport default PlayRoomPage;\n","/Users/domenico/dev/games-party/games-party/client/src/context/store.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/components/Player.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/components/StashManagementDrawer.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/context/stashManagementContext.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/components/Hand.tsx",[],"/Users/domenico/dev/games-party/games-party/client/src/components/Prompt.tsx",[],["57","58"],"/Users/domenico/dev/games-party/games-party/client/src/context/chatContext.tsx",["59","60"],"import { createContext, useContext, useRef, useState, ReactNode, useEffect, MutableRefObject, useCallback } from 'react';\nimport SimplePeer from 'simple-peer';\n\nimport { GameContext } from './store';\n\n\nexport interface IPlayer {\n sessionId: string;\n}\n\nexport interface IChatContext {\n audio: boolean;\n video: boolean;\n\n toggleAudio: () => void;\n toggleVideo: () => void;\n\n callPlayer: (player: IPlayer, room: any) => void;\n streamsRef: MutableRefObject> | null;\n}\n\nexport const ChatContext = createContext({\n audio: false,\n video: true,\n\n toggleAudio: () => console.warn('No implementation provided'),\n toggleVideo: () => console.warn('No implementation provided'),\n callPlayer: (player, room) => console.warn('No implementation provided', player, room),\n streamsRef: null\n});\n\nexport const useChatContext = () => useContext(ChatContext);\n\ntype Props = {\n children: ReactNode;\n};\n\nexport const ChatProvider = ({ children }: Props) => {\n const [chatInitialized, setChatInitialized] = useState(false);\n const { state } = useContext(GameContext);\n const [audioEnabled, setAudioEnabled] = useState(false);\n const [videoEnabled, setVideoEnabled] = useState(false);\n\n const peersRef = useRef>({});\n const streamsRef = useRef>({});\n\n const setupVideoStream = async () => {\n if (state.room === null) return null;\n const stream = await navigator.mediaDevices.getUserMedia({\n video: true,\n audio: false\n });\n streamsRef.current[state.room.sessionId] = stream;\n console.log('Got self stream:', state.room.sessionId, streamsRef.current);\n return stream;\n };\n\n const toggleAudio = () => {\n setAudioEnabled(!audioEnabled);\n }\n\n const toggleVideo = () => {\n setVideoEnabled(!videoEnabled);\n }\n\n const callPlayer = (player: IPlayer, stream: any) => {\n console.log('Calling player....', player, player.sessionId, stream);\n const existingPeer = peersRef.current[player.sessionId];\n if (!existingPeer) {\n console.log('Creating peer for player:', player.sessionId);\n const peer = createPeer(player.sessionId, state.room.sessionId, stream);\n peersRef.current[player.sessionId] = peer;\n } else {\n console.log('Player is already connected', player.sessionId, existingPeer);\n }\n }\n\n const callRoomParticipants = async () => {\n const stream = await setupVideoStream();\n console.log('Calling room participants:', state.gameState.players.values((p) => { return p.sessionId }));\n state.gameState.players.forEach((player) => {\n if (player.sessionId === state.room.sessionId) return;\n callPlayer(player, stream);\n });\n };\n\n const createPeer = (userToSignal, callerId, stream) => {\n const peer = new SimplePeer({\n initiator: true,\n trickle: false,\n stream,\n });\n\n peer.on(\"signal\", signal => {\n state.room.send('webrtc', {\n command: 'call-player', payload: {\n 'callerId': callerId,\n 'targetId': userToSignal,\n 'signal': signal\n }\n });\n });\n\n peer.on('connect', () => {\n console.log('[webrtc] WEBRTC PEER CONNECTED! (1)');\n });\n\n peer.on('stream', stream => {\n console.log('[webrtc] Got remote stream (1)', userToSignal, stream);\n streamsRef.current[userToSignal] = stream;\n });\n\n return peer;\n }\n\n const addPeer = (incomingSignal, callerId, stream) => {\n const peer = new SimplePeer({\n initiator: false,\n trickle: false,\n stream\n })\n\n peer.on(\"signal\", signal => {\n state.room.send('webrtc', {\n command: 'answer-call', payload: {\n 'callerId': state.room.sessionId,\n 'targetId': callerId,\n 'signal': signal\n }\n });\n });\n\n peer.on('connect', () => {\n console.log('[webrtc] WEBRTC PEER CONNECTED! (2)');\n });\n\n peer.signal(incomingSignal);\n return peer;\n }\n\n useEffect(() => {\n if (chatInitialized || state.room === null || state.gameState.players.size === 0) return;\n console.log('[webrtc] Initializing chat');\n setChatInitialized(true);\n\n console.log('[webrtc] Registering incoming-call handler', state.room);\n\n state.room.onMessage('answer-call', msg => {\n console.log('[webrtc] Receiving answer-call:', msg);\n const peer = peersRef.current[msg.callerId];\n peer.signal(msg.signal);\n });\n\n state.room.onMessage('incoming-call', (msg) => {\n const stream = streamsRef.current[state.room.sessionId];\n console.log('[webrtc] Answering incoming call:', msg, stream);\n const peer = addPeer(msg.signal, msg.callerId, stream);\n peer.on('stream', stream => {\n console.log('[webrtc] Got remote stream (2)', msg.callerId);\n streamsRef.current[msg.callerId] = stream;\n });\n peersRef.current[msg.callerId] = peer;\n });\n\n callRoomParticipants();\n\n return () => {\n console.log('[webrtc] Disposing ChatProvider');\n // if (state.room || state.gameState.players.size > 0) {\n // console.log('[webrtc] Removing listeners for incoming-message')\n // state.room.removeAllListeners('incoming-message');\n // state.room.removeAllListeners('answer-call');\n\n // streamsRef.current = {};\n // setChatInitialized(false);\n // }\n }\n }, [state.room, state.gameState.players]);\n\n return (\n \n {children}\n \n )\n}\n",{"ruleId":"61","replacedBy":"62"},{"ruleId":"63","replacedBy":"64"},{"ruleId":"65","severity":1,"message":"66","line":72,"column":8,"nodeType":"67","endLine":72,"endColumn":20,"suggestions":"68"},{"ruleId":"61","replacedBy":"69"},{"ruleId":"63","replacedBy":"70"},{"ruleId":"71","severity":1,"message":"72","line":1,"column":95,"nodeType":"73","messageId":"74","endLine":1,"endColumn":106},{"ruleId":"65","severity":1,"message":"75","line":178,"column":8,"nodeType":"67","endLine":178,"endColumn":45,"suggestions":"76"},"no-native-reassign",["77"],"no-negated-in-lhs",["78"],"react-hooks/exhaustive-deps","React Hook useEffect has missing dependencies: 'dispatch', 'roomId', 'setLocation', and 'state'. Either include them or remove the dependency array.","ArrayExpression",["79"],["77"],["78"],"@typescript-eslint/no-unused-vars","'useCallback' is defined but never used.","Identifier","unusedVar","React Hook useEffect has missing dependencies: 'addPeer', 'callRoomParticipants', and 'chatInitialized'. Either include them or remove the dependency array.",["80"],"no-global-assign","no-unsafe-negation",{"desc":"81","fix":"82"},{"desc":"83","fix":"84"},"Update the dependencies array to be: [dispatch, roomId, setLocation, state, state.room]",{"range":"85","text":"86"},"Update the dependencies array to be: [state.room, state.gameState.players, chatInitialized, callRoomParticipants, addPeer]",{"range":"87","text":"88"},[3030,3042],"[dispatch, roomId, setLocation, state, state.room]",[5979,6016],"[state.room, state.gameState.players, chatInitialized, callRoomParticipants, addPeer]"] \ No newline at end of file +[{"/Users/domenico/dev/chemmazz/client/src/index.tsx":"1","/Users/domenico/dev/chemmazz/client/src/reportWebVitals.tsx":"2","/Users/domenico/dev/chemmazz/client/src/App.tsx":"3","/Users/domenico/dev/chemmazz/client/src/pages/PlayRoomPage.tsx":"4","/Users/domenico/dev/chemmazz/client/src/pages/LobbyPage.tsx":"5","/Users/domenico/dev/chemmazz/client/src/context/chatContext.tsx":"6","/Users/domenico/dev/chemmazz/client/src/context/store.tsx":"7","/Users/domenico/dev/chemmazz/client/src/context/stashManagementContext.tsx":"8","/Users/domenico/dev/chemmazz/client/src/components/StashManagementDrawer.tsx":"9","/Users/domenico/dev/chemmazz/client/src/components/Hand.tsx":"10","/Users/domenico/dev/chemmazz/client/src/components/Player.tsx":"11","/Users/domenico/dev/chemmazz/client/src/components/Prompt.tsx":"12","/Users/domenico/dev/chemmazz/client/src/components/HighlightedPlayer.tsx":"13"},{"size":504,"mtime":1609784519249,"results":"14","hashOfConfig":"15"},{"size":362,"mtime":1609249563286,"results":"16","hashOfConfig":"15"},{"size":919,"mtime":1610987751265,"results":"17","hashOfConfig":"15"},{"size":9399,"mtime":1612959783402,"results":"18","hashOfConfig":"15"},{"size":4531,"mtime":1610826307035,"results":"19","hashOfConfig":"15"},{"size":6721,"mtime":1612975545641,"results":"20","hashOfConfig":"15"},{"size":2713,"mtime":1611222742326,"results":"21","hashOfConfig":"15"},{"size":1366,"mtime":1610176599048,"results":"22","hashOfConfig":"15"},{"size":3492,"mtime":1610732080352,"results":"23","hashOfConfig":"15"},{"size":1005,"mtime":1610576678797,"results":"24","hashOfConfig":"15"},{"size":3050,"mtime":1612976738162,"results":"25","hashOfConfig":"15"},{"size":3854,"mtime":1610826192773,"results":"26","hashOfConfig":"15"},{"size":1401,"mtime":1612959132305,"results":"27","hashOfConfig":"15"},{"filePath":"28","messages":"29","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},"1vcp6tj",{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"33","messages":"34","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"35","messages":"36","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"51","messages":"52","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"30"},{"filePath":"53","messages":"54","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/domenico/dev/chemmazz/client/src/index.tsx",[],["55","56"],"/Users/domenico/dev/chemmazz/client/src/reportWebVitals.tsx",[],"/Users/domenico/dev/chemmazz/client/src/App.tsx",[],"/Users/domenico/dev/chemmazz/client/src/pages/PlayRoomPage.tsx",["57","58"],"/Users/domenico/dev/chemmazz/client/src/pages/LobbyPage.tsx",[],"/Users/domenico/dev/chemmazz/client/src/context/chatContext.tsx",["59"],"/Users/domenico/dev/chemmazz/client/src/context/store.tsx",[],"/Users/domenico/dev/chemmazz/client/src/context/stashManagementContext.tsx",[],"/Users/domenico/dev/chemmazz/client/src/components/StashManagementDrawer.tsx",[],"/Users/domenico/dev/chemmazz/client/src/components/Hand.tsx",[],"/Users/domenico/dev/chemmazz/client/src/components/Player.tsx",[],"/Users/domenico/dev/chemmazz/client/src/components/Prompt.tsx",[],"/Users/domenico/dev/chemmazz/client/src/components/HighlightedPlayer.tsx",[],{"ruleId":"60","replacedBy":"61"},{"ruleId":"62","replacedBy":"63"},{"ruleId":"64","severity":1,"message":"65","line":2,"column":10,"nodeType":"66","messageId":"67","endLine":2,"endColumn":15},{"ruleId":"68","severity":1,"message":"69","line":73,"column":8,"nodeType":"70","endLine":73,"endColumn":20,"suggestions":"71"},{"ruleId":"68","severity":1,"message":"72","line":186,"column":8,"nodeType":"70","endLine":186,"endColumn":45,"suggestions":"73"},"no-native-reassign",["74"],"no-negated-in-lhs",["75"],"@typescript-eslint/no-unused-vars","'Badge' is defined but never used.","Identifier","unusedVar","react-hooks/exhaustive-deps","React Hook useEffect has missing dependencies: 'dispatch', 'roomId', 'setLocation', and 'state'. Either include them or remove the dependency array.","ArrayExpression",["76"],"React Hook useEffect has missing dependencies: 'addPeer', 'callRoomParticipants', 'chatInitialized', and 'connectedPeers'. Either include them or remove the dependency array. You can also do a functional update 'setConnectedPeers(c => ...)' if you only need 'connectedPeers' in the 'setConnectedPeers' call.",["77"],"no-global-assign","no-unsafe-negation",{"desc":"78","fix":"79"},{"desc":"80","fix":"81"},"Update the dependencies array to be: [dispatch, roomId, setLocation, state, state.room]",{"range":"82","text":"83"},"Update the dependencies array to be: [state.room, state.gameState.players, chatInitialized, callRoomParticipants, addPeer, connectedPeers]",{"range":"84","text":"85"},[3095,3107],"[dispatch, roomId, setLocation, state, state.room]",[6362,6399],"[state.room, state.gameState.players, chatInitialized, callRoomParticipants, addPeer, connectedPeers]"] \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css index 68aa83a..7494ab2 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -103,12 +103,13 @@ html, body, #root { align-items: center; } -.player .picture { - width: 150px; - height: 180px; +.player video { + width: 250px; + padding: 0; + margin: 0; - background: pink url(https://picsum.photos/seed/ciccio/150) no-repeat; - background-size: cover; + /* background: pink url(https://picsum.photos/seed/ciccio/150) no-repeat; */ + /* background-size: cover; */ border: 5px solid white; box-shadow: 0 1px 1px rgba(0,0,0,0.12), @@ -122,7 +123,7 @@ html, body, #root { } .player.dead .picture { -/* filter: grayscale(100%); */ + /* filter: grayscale(100%); */ } .player.dead .picture .badges i { diff --git a/client/src/components/HighlightedPlayer.tsx b/client/src/components/HighlightedPlayer.tsx new file mode 100644 index 0000000..192fd69 --- /dev/null +++ b/client/src/components/HighlightedPlayer.tsx @@ -0,0 +1,37 @@ +import { useEffect, useRef } from 'react'; +import { Badge } from 'antd'; +import { useChatContext } from '../context/chatContext'; + + +const HighlightedPlayer = ({ player }) => { + const { connectedPeers, streamsRef } = useChatContext(); + const videoRef = useRef(null); + + useEffect(() => { + const stream = streamsRef?.current[player.sessionId]; + if (stream && videoRef.current) { + console.log('[webrtc] Attaching stream to video element', player.sessionId, stream, videoRef.current); + const video = videoRef.current; + video.srcObject = stream; + } + }, [player, connectedPeers, streamsRef]); + + return ( +
+
+
+
+ {player.displayName} (Banco) {(player.prompt && player.prompt.visible) && ()} +
+
+ ) +}; + +export default HighlightedPlayer; diff --git a/client/src/components/Player.tsx b/client/src/components/Player.tsx index 342d242..9855fb6 100644 --- a/client/src/components/Player.tsx +++ b/client/src/components/Player.tsx @@ -7,9 +7,9 @@ import { useChatContext } from '../context/chatContext'; const { Meta } = Card; -const Player = ({ player, displayAdminMenu }) => { +const Player = ({ player, displayAdminMenu, isCurrentPlayer }) => { const { state } = useContext(GameContext); - const { streamsRef } = useChatContext(); + const { connectedPeers, streamsRef } = useChatContext(); const { managePlayerStash } = useStashManagement(); const videoRef = useRef(null); @@ -20,7 +20,7 @@ const Player = ({ player, displayAdminMenu }) => { const video = videoRef.current; video.srcObject = stream; } - }, [player, streamsRef]); + }, [player, connectedPeers, streamsRef]); const handleMenuClick = ({ key }) => { switch (key) { @@ -67,20 +67,14 @@ const Player = ({ player, displayAdminMenu }) => { return ( - } - cover={ diff --git a/client/src/context/chatContext.tsx b/client/src/context/chatContext.tsx index ed58d4a..cda8bd9 100644 --- a/client/src/context/chatContext.tsx +++ b/client/src/context/chatContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useRef, useState, ReactNode, useEffect, MutableRefObject, useCallback } from 'react'; +import { createContext, useContext, useRef, useState, ReactNode, useEffect, MutableRefObject } from 'react'; import SimplePeer from 'simple-peer'; import { GameContext } from './store'; @@ -17,16 +17,18 @@ export interface IChatContext { callPlayer: (player: IPlayer, room: any) => void; streamsRef: MutableRefObject> | null; + connectedPeers: Set } export const ChatContext = createContext({ - audio: false, + audio: true, video: true, toggleAudio: () => console.warn('No implementation provided'), toggleVideo: () => console.warn('No implementation provided'), callPlayer: (player, room) => console.warn('No implementation provided', player, room), - streamsRef: null + streamsRef: null, + connectedPeers: new Set() }); export const useChatContext = () => useContext(ChatContext); @@ -43,14 +45,17 @@ export const ChatProvider = ({ children }: Props) => { const peersRef = useRef>({}); const streamsRef = useRef>({}); + const [connectedPeers, setConnectedPeers] = useState>(new Set()); const setupVideoStream = async () => { if (state.room === null) return null; const stream = await navigator.mediaDevices.getUserMedia({ video: true, - audio: false + audio: true }); streamsRef.current[state.room.sessionId] = stream; + setConnectedPeers(new Set([connectedPeers.values(), state.room.sessionId])); + console.log('Got self stream:', state.room.sessionId, streamsRef.current); return stream; }; @@ -91,7 +96,7 @@ export const ChatProvider = ({ children }: Props) => { stream, }); - peer.on("signal", signal => { + peer.on('signal', signal => { state.room.send('webrtc', { command: 'call-player', payload: { 'callerId': callerId, @@ -108,6 +113,7 @@ export const ChatProvider = ({ children }: Props) => { peer.on('stream', stream => { console.log('[webrtc] Got remote stream (1)', userToSignal, stream); streamsRef.current[userToSignal] = stream; + setConnectedPeers(new Set([connectedPeers.values(), userToSignal])); }); return peer; @@ -158,6 +164,8 @@ export const ChatProvider = ({ children }: Props) => { peer.on('stream', stream => { console.log('[webrtc] Got remote stream (2)', msg.callerId); streamsRef.current[msg.callerId] = stream; + + setConnectedPeers(new Set([connectedPeers.values(), msg.callerId])); }); peersRef.current[msg.callerId] = peer; }); @@ -184,7 +192,8 @@ export const ChatProvider = ({ children }: Props) => { toggleAudio, toggleVideo, callPlayer, - streamsRef + streamsRef, + connectedPeers }}> {children} diff --git a/client/src/pages/PlayRoomPage.tsx b/client/src/pages/PlayRoomPage.tsx index 58675ce..911aedc 100644 --- a/client/src/pages/PlayRoomPage.tsx +++ b/client/src/pages/PlayRoomPage.tsx @@ -8,6 +8,7 @@ import { GameContext } from '../context/store'; import { StashManagementProvider } from '../context/stashManagementContext'; import StashManagementDrawer from '../components/StashManagementDrawer'; +import HighlightedPlayer from '../components/HighlightedPlayer'; import Player from '../components/Player'; import Hand from '../components/Hand'; import Prompt from '../components/Prompt'; @@ -124,46 +125,15 @@ const PlayRoomPage = ({ params }) => { -
-
-
- -
- - - -
- -
-
-
-
- {state.gameState.player1.displayName} (Banco) - {(state.gameState.player1.prompt && state.gameState.player1.prompt.visible) && ()} -
+ + -
- -
-
- -
-
- - - -
- -
-
-
-
- {state.gameState.player2.displayName} - {(state.gameState.player2.prompt && state.gameState.player2.prompt.visible) && ()} -
+ + + -
+
)} @@ -175,7 +145,7 @@ const PlayRoomPage = ({ params }) => { {Array.from(state.gameState.players.values()).filter((player, index) => { return player.playing; }).map((player, index) => ( - + ))} @@ -184,7 +154,7 @@ const PlayRoomPage = ({ params }) => { {Array.from(state.gameState.players.values()).filter((player, index) => { return !player.playing; }).map((player, index) => ( - + ))}