Adding audio support

This commit is contained in:
2021-02-10 18:09:04 +01:00
parent 33eb368dab
commit 6f58a94656
6 changed files with 74 additions and 63 deletions

File diff suppressed because one or more lines are too long

View File

@@ -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),

View 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;

View File

@@ -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<HTMLVideoElement>(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 (
<Badge.Ribbon text={player.stash}>
<Card style={{ width: 120 }}
// 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} />}
<Card style={{ width: 180 }}
actions={displayAdminMenu ? [
<Dropdown overlay={menu} trigger={['click']}>
<EllipsisOutlined key="ellipsis" />
</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} />
</Card>
</Badge.Ribbon>

View File

@@ -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<Record<string, MediaStream>> | null;
connectedPeers: Set<string>
}
export const ChatContext = createContext<IChatContext>({
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<Record<string, SimplePeer.Instance>>({});
const streamsRef = useRef<Record<string, MediaStream>>({});
const [connectedPeers, setConnectedPeers] = useState<Set<string>>(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}
</ChatContext.Provider>

View File

@@ -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 }) => {
<Statistic title="Piatto" value={state.gameState.pot} />
<Space direction="horizontal" align="baseline" split="VS" size="large" className="the-ring">
<div className="player">
<div className="picture" id="player">
<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>
<Space direction="vertical">
<HighlightedPlayer player={state.gameState.player1} />
<Hand cards={state.gameState.player1.hand} showHole={false} />
</div>
<div className="player">
<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>
</Space>
<Space direction="vertical">
<HighlightedPlayer player={state.gameState.player2} />
<Hand cards={state.gameState.player2.hand} showHole={false} />
<Statistic title="Scommessa" value={state.gameState.currentBet} />
</div>
</Space>
</Space>
</Space>
)}
@@ -175,7 +145,7 @@ const PlayRoomPage = ({ params }) => {
{Array.from<any>(state.gameState.players.values()).filter((player, index) => {
return player.playing;
}).map((player, index) => (
<Player key={index} player={player} displayAdminMenu={isRoomOwner} />
<Player key={index} player={player} displayAdminMenu={isRoomOwner} isCurrentPlayer={state.room.sessionId === player.sessionId} />
))}
</Space>
@@ -184,7 +154,7 @@ const PlayRoomPage = ({ params }) => {
{Array.from<any>(state.gameState.players.values()).filter((player, index) => {
return !player.playing;
}).map((player, index) => (
<Player key={index} player={player} displayAdminMenu={isRoomOwner} />
<Player key={index} player={player} displayAdminMenu={isRoomOwner} isCurrentPlayer={state.room.sessionId === player.sessionId} />
))}
</Space>
</Col>