diff --git a/src/components/Nickname.js b/src/components/Nickname.js index 369c253..d96bfeb 100644 --- a/src/components/Nickname.js +++ b/src/components/Nickname.js @@ -3,7 +3,7 @@ import { Text, Box, Input, Button, Label, IconButton, Flex } from "theme-ui"; import Modal from "./Modal"; -function Nickname({ nickname, allowChanging, onChange }) { +function Nickname({ nickname, allowChanging, onChange, onStream }) { const [isChangeModalOpen, setIsChangeModalOpen] = useState(false); function openModal() { setIsChangeModalOpen(true); @@ -32,16 +32,8 @@ function Nickname({ nickname, allowChanging, onChange }) { variant="caption" sx={{ fontSize: 10, - cursor: allowChanging ? "pointer" : "default", - ":hover": allowChanging && { - color: "primary", - }, - ":active": allowChanging && { - color: "secondary", - }, position: "relative", }} - onClick={() => allowChanging && openModal()} > {nickname} {allowChanging && ( @@ -54,7 +46,33 @@ function Nickname({ nickname, allowChanging, onChange }) { position: "absolute", bottom: "-2px", }} + aria-label="Start Radio Stream" + onClick={() => allowChanging && onStream()} + > + + + + + )} + {allowChanging && ( + allowChanging && openModal()} > {}, + onStream: () => {}, }; export default Nickname; diff --git a/src/components/Party.js b/src/components/Party.js index 0416bd1..578be6a 100644 --- a/src/components/Party.js +++ b/src/components/Party.js @@ -3,8 +3,17 @@ import { Flex, Box, Text } from "theme-ui"; import AddPartyMemberButton from "./AddPartyMemberButton"; import Nickname from "./Nickname"; +import Stream from "./Stream"; -function Party({ nickname, partyNicknames, gameId, onNicknameChange }) { +function Party({ + nickname, + partyNicknames, + gameId, + onNicknameChange, + stream, + partyStreams, + onStreamStart, +}) { return ( {Object.entries(partyNicknames).map(([id, partyNickname]) => ( ))} + {(stream || Object.keys(partyStreams).length !== 0) && ( + Streams + )} + {stream && } + {partyStreams && + Object.entries(partyStreams).map(([id, partyStream]) => ( + + ))} diff --git a/src/components/Stream.js b/src/components/Stream.js new file mode 100644 index 0000000..be7626e --- /dev/null +++ b/src/components/Stream.js @@ -0,0 +1,26 @@ +import React, { useRef, useEffect } from "react"; + +function Stream({ stream, muted }) { + const videoRef = useRef(); + useEffect(() => { + if (videoRef.current) { + videoRef.current.srcObject = stream; + } + }, [stream]); + + return ( + + ); +} + +Stream.defaultProps = { + muted: false, +}; + +export default Stream; diff --git a/src/helpers/useSession.js b/src/helpers/useSession.js index 90f0ac9..c431bb7 100644 --- a/src/helpers/useSession.js +++ b/src/helpers/useSession.js @@ -6,7 +6,13 @@ import Peer from "../helpers/Peer"; const socket = io("https://broker.owlbear.rodeo"); -function useSession(partyId, onPeerConnected, onPeerDisconnected, onPeerData) { +function useSession( + partyId, + onPeerConnected, + onPeerDisconnected, + onPeerData, + onPeerStream +) { useEffect(() => { socket.emit("join party", partyId); }, [partyId]); @@ -32,6 +38,10 @@ function useSession(partyId, onPeerConnected, onPeerDisconnected, onPeerData) { onPeerData && onPeerData({ id, peer, data }); }); + peer.on("stream", (stream) => { + onPeerStream && onPeerStream({ id, peer, stream }); + }); + peer.on("close", () => { onPeerDisconnected && onPeerDisconnected(id); }); @@ -83,7 +93,7 @@ function useSession(partyId, onPeerConnected, onPeerDisconnected, onPeerData) { socket.removeListener("joined party", handleJoinedParty); socket.removeListener("signal", handleSignal); }; - }, [peers, onPeerConnected, onPeerDisconnected, onPeerData]); + }, [peers, onPeerConnected, onPeerDisconnected, onPeerData, onPeerStream]); return { peers, socket }; } diff --git a/src/routes/Game.js b/src/routes/Game.js index 3fb858e..84f7602 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -18,7 +18,8 @@ function Game() { gameId, handlePeerConnected, handlePeerDisconnected, - handlePeerData + handlePeerData, + handlePeerStream ); const [mapSource, setMapSource] = useState(null); @@ -70,8 +71,13 @@ function Game() { } } + const [stream, setStream] = useState(null); + const [partyStreams, setPartyStreams] = useState({}); function handlePeerConnected({ peer }) { peer.send({ id: "nickname", data: { [socket.id]: nickname } }); + if (stream) { + peer.addStream(stream); + } } function handlePeerData({ data, peer }) { @@ -107,10 +113,28 @@ function Game() { } } + function handlePeerStream({ id, stream: partyStream }) { + setPartyStreams((prevStreams) => ({ + ...prevStreams, + [id]: partyStream, + })); + } + function handlePeerDisconnected(disconnectedId) { setPartyNicknames((prevNicknames) => omit(prevNicknames, [disconnectedId])); } + function handleStreamStart() { + navigator.mediaDevices + .getDisplayMedia({ video: true, audio: true }) + .then((mediaStream) => { + setStream(mediaStream); + for (let peer of Object.values(peers)) { + peer.addStream(mediaStream); + } + }); + } + return (