Adding audio support
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -103,12 +103,13 @@ html, body, #root {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .picture {
|
.player video {
|
||||||
width: 150px;
|
width: 250px;
|
||||||
height: 180px;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
background: pink url(https://picsum.photos/seed/ciccio/150) no-repeat;
|
/* background: pink url(https://picsum.photos/seed/ciccio/150) no-repeat; */
|
||||||
background-size: cover;
|
/* background-size: cover; */
|
||||||
border: 5px solid white;
|
border: 5px solid white;
|
||||||
|
|
||||||
box-shadow: 0 1px 1px rgba(0,0,0,0.12),
|
box-shadow: 0 1px 1px rgba(0,0,0,0.12),
|
||||||
@@ -122,7 +123,7 @@ html, body, #root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.player.dead .picture {
|
.player.dead .picture {
|
||||||
/* filter: grayscale(100%); */
|
/* filter: grayscale(100%); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.player.dead .picture .badges i {
|
.player.dead .picture .badges i {
|
||||||
|
|||||||
37
client/src/components/HighlightedPlayer.tsx
Normal file
37
client/src/components/HighlightedPlayer.tsx
Normal file
@@ -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<HTMLVideoElement>(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 (
|
||||||
|
<div className="player">
|
||||||
|
<div className="picture">
|
||||||
|
<video ref={videoRef} title={player.displayName} autoPlay />
|
||||||
|
<div className="lives">
|
||||||
|
<i className="fas fa-heart" title="1 Vita"></i>
|
||||||
|
<i className="fas fa-heart" title="1 Vita"></i>
|
||||||
|
<i className="fas fa-heart" title="1 Vita"></i>
|
||||||
|
</div>
|
||||||
|
<div className="status"></div>
|
||||||
|
</div>
|
||||||
|
<div className="name">
|
||||||
|
{player.displayName} (Banco) {(player.prompt && player.prompt.visible) && (<Badge status="processing" />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HighlightedPlayer;
|
||||||
@@ -7,9 +7,9 @@ import { useChatContext } from '../context/chatContext';
|
|||||||
|
|
||||||
const { Meta } = Card;
|
const { Meta } = Card;
|
||||||
|
|
||||||
const Player = ({ player, displayAdminMenu }) => {
|
const Player = ({ player, displayAdminMenu, isCurrentPlayer }) => {
|
||||||
const { state } = useContext(GameContext);
|
const { state } = useContext(GameContext);
|
||||||
const { streamsRef } = useChatContext();
|
const { connectedPeers, streamsRef } = useChatContext();
|
||||||
const { managePlayerStash } = useStashManagement();
|
const { managePlayerStash } = useStashManagement();
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ const Player = ({ player, displayAdminMenu }) => {
|
|||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
video.srcObject = stream;
|
video.srcObject = stream;
|
||||||
}
|
}
|
||||||
}, [player, streamsRef]);
|
}, [player, connectedPeers, streamsRef]);
|
||||||
|
|
||||||
const handleMenuClick = ({ key }) => {
|
const handleMenuClick = ({ key }) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -67,20 +67,14 @@ const Player = ({ player, displayAdminMenu }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge.Ribbon text={player.stash}>
|
<Badge.Ribbon text={player.stash}>
|
||||||
<Card style={{ width: 120 }}
|
<Card style={{ width: 180 }}
|
||||||
// cover={<Image
|
|
||||||
// width={120}
|
|
||||||
// title={player.displayName}
|
|
||||||
// src={`https://i.pravatar.cc/120?u=${player.id}`}
|
|
||||||
// fallback="data:image/png;base64,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" />}
|
|
||||||
cover={<video playsInline autoPlay ref={videoRef} width={120} title={player.displayName} />}
|
|
||||||
actions={displayAdminMenu ? [
|
actions={displayAdminMenu ? [
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
<EllipsisOutlined key="ellipsis" />
|
<EllipsisOutlined key="ellipsis" />
|
||||||
</Dropdown>,
|
</Dropdown>,
|
||||||
] : []}
|
] : []}
|
||||||
>
|
>
|
||||||
<video ref={videoRef} width={120} title={player.displayName} autoPlay />
|
<video ref={videoRef} width={130} title={player.displayName} autoPlay muted={isCurrentPlayer} />
|
||||||
<Meta title={player.displayName} />
|
<Meta title={player.displayName} />
|
||||||
</Card>
|
</Card>
|
||||||
</Badge.Ribbon>
|
</Badge.Ribbon>
|
||||||
|
|||||||
@@ -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 SimplePeer from 'simple-peer';
|
||||||
|
|
||||||
import { GameContext } from './store';
|
import { GameContext } from './store';
|
||||||
@@ -17,16 +17,18 @@ export interface IChatContext {
|
|||||||
|
|
||||||
callPlayer: (player: IPlayer, room: any) => void;
|
callPlayer: (player: IPlayer, room: any) => void;
|
||||||
streamsRef: MutableRefObject<Record<string, MediaStream>> | null;
|
streamsRef: MutableRefObject<Record<string, MediaStream>> | null;
|
||||||
|
connectedPeers: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatContext = createContext<IChatContext>({
|
export const ChatContext = createContext<IChatContext>({
|
||||||
audio: false,
|
audio: true,
|
||||||
video: true,
|
video: true,
|
||||||
|
|
||||||
toggleAudio: () => console.warn('No implementation provided'),
|
toggleAudio: () => console.warn('No implementation provided'),
|
||||||
toggleVideo: () => console.warn('No implementation provided'),
|
toggleVideo: () => console.warn('No implementation provided'),
|
||||||
callPlayer: (player, room) => console.warn('No implementation provided', player, room),
|
callPlayer: (player, room) => console.warn('No implementation provided', player, room),
|
||||||
streamsRef: null
|
streamsRef: null,
|
||||||
|
connectedPeers: new Set()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useChatContext = () => useContext(ChatContext);
|
export const useChatContext = () => useContext(ChatContext);
|
||||||
@@ -43,14 +45,17 @@ export const ChatProvider = ({ children }: Props) => {
|
|||||||
|
|
||||||
const peersRef = useRef<Record<string, SimplePeer.Instance>>({});
|
const peersRef = useRef<Record<string, SimplePeer.Instance>>({});
|
||||||
const streamsRef = useRef<Record<string, MediaStream>>({});
|
const streamsRef = useRef<Record<string, MediaStream>>({});
|
||||||
|
const [connectedPeers, setConnectedPeers] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const setupVideoStream = async () => {
|
const setupVideoStream = async () => {
|
||||||
if (state.room === null) return null;
|
if (state.room === null) return null;
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: true,
|
video: true,
|
||||||
audio: false
|
audio: true
|
||||||
});
|
});
|
||||||
streamsRef.current[state.room.sessionId] = stream;
|
streamsRef.current[state.room.sessionId] = stream;
|
||||||
|
setConnectedPeers(new Set([connectedPeers.values(), state.room.sessionId]));
|
||||||
|
|
||||||
console.log('Got self stream:', state.room.sessionId, streamsRef.current);
|
console.log('Got self stream:', state.room.sessionId, streamsRef.current);
|
||||||
return stream;
|
return stream;
|
||||||
};
|
};
|
||||||
@@ -91,7 +96,7 @@ export const ChatProvider = ({ children }: Props) => {
|
|||||||
stream,
|
stream,
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on("signal", signal => {
|
peer.on('signal', signal => {
|
||||||
state.room.send('webrtc', {
|
state.room.send('webrtc', {
|
||||||
command: 'call-player', payload: {
|
command: 'call-player', payload: {
|
||||||
'callerId': callerId,
|
'callerId': callerId,
|
||||||
@@ -108,6 +113,7 @@ export const ChatProvider = ({ children }: Props) => {
|
|||||||
peer.on('stream', stream => {
|
peer.on('stream', stream => {
|
||||||
console.log('[webrtc] Got remote stream (1)', userToSignal, stream);
|
console.log('[webrtc] Got remote stream (1)', userToSignal, stream);
|
||||||
streamsRef.current[userToSignal] = stream;
|
streamsRef.current[userToSignal] = stream;
|
||||||
|
setConnectedPeers(new Set([connectedPeers.values(), userToSignal]));
|
||||||
});
|
});
|
||||||
|
|
||||||
return peer;
|
return peer;
|
||||||
@@ -158,6 +164,8 @@ export const ChatProvider = ({ children }: Props) => {
|
|||||||
peer.on('stream', stream => {
|
peer.on('stream', stream => {
|
||||||
console.log('[webrtc] Got remote stream (2)', msg.callerId);
|
console.log('[webrtc] Got remote stream (2)', msg.callerId);
|
||||||
streamsRef.current[msg.callerId] = stream;
|
streamsRef.current[msg.callerId] = stream;
|
||||||
|
|
||||||
|
setConnectedPeers(new Set([connectedPeers.values(), msg.callerId]));
|
||||||
});
|
});
|
||||||
peersRef.current[msg.callerId] = peer;
|
peersRef.current[msg.callerId] = peer;
|
||||||
});
|
});
|
||||||
@@ -184,7 +192,8 @@ export const ChatProvider = ({ children }: Props) => {
|
|||||||
toggleAudio,
|
toggleAudio,
|
||||||
toggleVideo,
|
toggleVideo,
|
||||||
callPlayer,
|
callPlayer,
|
||||||
streamsRef
|
streamsRef,
|
||||||
|
connectedPeers
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</ChatContext.Provider>
|
</ChatContext.Provider>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { GameContext } from '../context/store';
|
|||||||
import { StashManagementProvider } from '../context/stashManagementContext';
|
import { StashManagementProvider } from '../context/stashManagementContext';
|
||||||
|
|
||||||
import StashManagementDrawer from '../components/StashManagementDrawer';
|
import StashManagementDrawer from '../components/StashManagementDrawer';
|
||||||
|
import HighlightedPlayer from '../components/HighlightedPlayer';
|
||||||
import Player from '../components/Player';
|
import Player from '../components/Player';
|
||||||
import Hand from '../components/Hand';
|
import Hand from '../components/Hand';
|
||||||
import Prompt from '../components/Prompt';
|
import Prompt from '../components/Prompt';
|
||||||
@@ -124,46 +125,15 @@ const PlayRoomPage = ({ params }) => {
|
|||||||
<Statistic title="Piatto" value={state.gameState.pot} />
|
<Statistic title="Piatto" value={state.gameState.pot} />
|
||||||
|
|
||||||
<Space direction="horizontal" align="baseline" split="VS" size="large" className="the-ring">
|
<Space direction="horizontal" align="baseline" split="VS" size="large" className="the-ring">
|
||||||
<div className="player">
|
<Space direction="vertical">
|
||||||
<div className="picture" id="player">
|
<HighlightedPlayer player={state.gameState.player1} />
|
||||||
<div className="badges">
|
|
||||||
<Player player={state.gameState.player1} displayAdminMenu={false} />
|
|
||||||
<div className="lives">
|
|
||||||
<i className="fas fa-heart" title="1 Vita"></i>
|
|
||||||
<i className="fas fa-heart" title="1 Vita"></i>
|
|
||||||
<i className="fas fa-heart" title="1 Vita"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="status"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="name">
|
|
||||||
{state.gameState.player1.displayName} (Banco)
|
|
||||||
{(state.gameState.player1.prompt && state.gameState.player1.prompt.visible) && (<Badge status="processing" />)}
|
|
||||||
</div>
|
|
||||||
<Hand cards={state.gameState.player1.hand} showHole={false} />
|
<Hand cards={state.gameState.player1.hand} showHole={false} />
|
||||||
</div>
|
</Space>
|
||||||
|
<Space direction="vertical">
|
||||||
<div className="player">
|
<HighlightedPlayer player={state.gameState.player2} />
|
||||||
<div className="picture" id="player">
|
|
||||||
<Player player={state.gameState.player2} displayAdminMenu={false} />
|
|
||||||
<div className="badges">
|
|
||||||
<div className="lives">
|
|
||||||
<i className="fas fa-heart" title="1 Vita"></i>
|
|
||||||
<i className="fas fa-heart" title="1 Vita"></i>
|
|
||||||
<i className="fas fa-heart" title="1 Vita"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="status"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="name">
|
|
||||||
{state.gameState.player2.displayName}
|
|
||||||
{(state.gameState.player2.prompt && state.gameState.player2.prompt.visible) && (<Badge status="processing" />)}
|
|
||||||
</div>
|
|
||||||
<Hand cards={state.gameState.player2.hand} showHole={false} />
|
<Hand cards={state.gameState.player2.hand} showHole={false} />
|
||||||
<Statistic title="Scommessa" value={state.gameState.currentBet} />
|
<Statistic title="Scommessa" value={state.gameState.currentBet} />
|
||||||
</div>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
@@ -175,7 +145,7 @@ const PlayRoomPage = ({ params }) => {
|
|||||||
{Array.from<any>(state.gameState.players.values()).filter((player, index) => {
|
{Array.from<any>(state.gameState.players.values()).filter((player, index) => {
|
||||||
return player.playing;
|
return player.playing;
|
||||||
}).map((player, index) => (
|
}).map((player, index) => (
|
||||||
<Player key={index} player={player} displayAdminMenu={isRoomOwner} />
|
<Player key={index} player={player} displayAdminMenu={isRoomOwner} isCurrentPlayer={state.room.sessionId === player.sessionId} />
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
@@ -184,7 +154,7 @@ const PlayRoomPage = ({ params }) => {
|
|||||||
{Array.from<any>(state.gameState.players.values()).filter((player, index) => {
|
{Array.from<any>(state.gameState.players.values()).filter((player, index) => {
|
||||||
return !player.playing;
|
return !player.playing;
|
||||||
}).map((player, index) => (
|
}).map((player, index) => (
|
||||||
<Player key={index} player={player} displayAdminMenu={isRoomOwner} />
|
<Player key={index} player={player} displayAdminMenu={isRoomOwner} isCurrentPlayer={state.room.sessionId === player.sessionId} />
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
Reference in New Issue
Block a user