Added simple stream sharing

This commit is contained in:
Mitchell McCaffrey 2020-04-08 15:01:17 +10:00
parent efd94a1821
commit 0bd1e86e48
5 changed files with 113 additions and 13 deletions

View File

@ -3,7 +3,7 @@ import { Text, Box, Input, Button, Label, IconButton, Flex } from "theme-ui";
import Modal from "./Modal"; import Modal from "./Modal";
function Nickname({ nickname, allowChanging, onChange }) { function Nickname({ nickname, allowChanging, onChange, onStream }) {
const [isChangeModalOpen, setIsChangeModalOpen] = useState(false); const [isChangeModalOpen, setIsChangeModalOpen] = useState(false);
function openModal() { function openModal() {
setIsChangeModalOpen(true); setIsChangeModalOpen(true);
@ -32,16 +32,8 @@ function Nickname({ nickname, allowChanging, onChange }) {
variant="caption" variant="caption"
sx={{ sx={{
fontSize: 10, fontSize: 10,
cursor: allowChanging ? "pointer" : "default",
":hover": allowChanging && {
color: "primary",
},
":active": allowChanging && {
color: "secondary",
},
position: "relative", position: "relative",
}} }}
onClick={() => allowChanging && openModal()}
> >
{nickname} {nickname}
{allowChanging && ( {allowChanging && (
@ -54,7 +46,33 @@ function Nickname({ nickname, allowChanging, onChange }) {
position: "absolute", position: "absolute",
bottom: "-2px", bottom: "-2px",
}} }}
aria-label="Start Radio Stream"
onClick={() => allowChanging && onStream()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="10"
viewBox="0 0 24 24"
width="10"
fill="currentcolor"
>
<path d="M3.24 6.15C2.51 6.43 2 7.17 2 8v12c0 1.1.9 2 2 2h16c1.11 0 2-.9 2-2V8c0-1.1-.9-2-2-2H8.3l7.43-3c.46-.19.68-.71.49-1.17-.19-.46-.71-.68-1.17-.49L3.24 6.15zM7 20c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm13-8h-2v-1c0-.55-.45-1-1-1s-1 .45-1 1v1H4V9c0-.55.45-1 1-1h14c.55 0 1 .45 1 1v3z" />
</svg>
</IconButton>
)}
{allowChanging && (
<IconButton
sx={{
width: "10px",
height: "10px",
padding: 0,
margin: "2px",
position: "absolute",
bottom: "-2px",
transform: "translateX(12px)",
}}
aria-label="Change Nickname" aria-label="Change Nickname"
onClick={() => allowChanging && openModal()}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -93,6 +111,7 @@ function Nickname({ nickname, allowChanging, onChange }) {
Nickname.defaultProps = { Nickname.defaultProps = {
allowChanging: false, allowChanging: false,
onChange: () => {}, onChange: () => {},
onStream: () => {},
}; };
export default Nickname; export default Nickname;

View File

@ -3,8 +3,17 @@ import { Flex, Box, Text } from "theme-ui";
import AddPartyMemberButton from "./AddPartyMemberButton"; import AddPartyMemberButton from "./AddPartyMemberButton";
import Nickname from "./Nickname"; import Nickname from "./Nickname";
import Stream from "./Stream";
function Party({ nickname, partyNicknames, gameId, onNicknameChange }) { function Party({
nickname,
partyNicknames,
gameId,
onNicknameChange,
stream,
partyStreams,
onStreamStart,
}) {
return ( return (
<Flex <Flex
p={3} p={3}
@ -36,10 +45,19 @@ function Party({ nickname, partyNicknames, gameId, onNicknameChange }) {
nickname={nickname || ""} nickname={nickname || ""}
allowChanging allowChanging
onChange={onNicknameChange} onChange={onNicknameChange}
onStream={onStreamStart}
/> />
{Object.entries(partyNicknames).map(([id, partyNickname]) => ( {Object.entries(partyNicknames).map(([id, partyNickname]) => (
<Nickname nickname={partyNickname} key={id} /> <Nickname nickname={partyNickname} key={id} />
))} ))}
{(stream || Object.keys(partyStreams).length !== 0) && (
<Text>Streams</Text>
)}
{stream && <Stream stream={stream} muted />}
{partyStreams &&
Object.entries(partyStreams).map(([id, partyStream]) => (
<Stream stream={partyStream} key={id} />
))}
</Box> </Box>
<Box> <Box>
<AddPartyMemberButton gameId={gameId} /> <AddPartyMemberButton gameId={gameId} />

26
src/components/Stream.js Normal file
View File

@ -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 (
<video
ref={videoRef}
autoPlay
playsInline
muted={muted}
style={{ width: "100%" }}
/>
);
}
Stream.defaultProps = {
muted: false,
};
export default Stream;

View File

@ -6,7 +6,13 @@ import Peer from "../helpers/Peer";
const socket = io("https://broker.owlbear.rodeo"); const socket = io("https://broker.owlbear.rodeo");
function useSession(partyId, onPeerConnected, onPeerDisconnected, onPeerData) { function useSession(
partyId,
onPeerConnected,
onPeerDisconnected,
onPeerData,
onPeerStream
) {
useEffect(() => { useEffect(() => {
socket.emit("join party", partyId); socket.emit("join party", partyId);
}, [partyId]); }, [partyId]);
@ -32,6 +38,10 @@ function useSession(partyId, onPeerConnected, onPeerDisconnected, onPeerData) {
onPeerData && onPeerData({ id, peer, data }); onPeerData && onPeerData({ id, peer, data });
}); });
peer.on("stream", (stream) => {
onPeerStream && onPeerStream({ id, peer, stream });
});
peer.on("close", () => { peer.on("close", () => {
onPeerDisconnected && onPeerDisconnected(id); onPeerDisconnected && onPeerDisconnected(id);
}); });
@ -83,7 +93,7 @@ function useSession(partyId, onPeerConnected, onPeerDisconnected, onPeerData) {
socket.removeListener("joined party", handleJoinedParty); socket.removeListener("joined party", handleJoinedParty);
socket.removeListener("signal", handleSignal); socket.removeListener("signal", handleSignal);
}; };
}, [peers, onPeerConnected, onPeerDisconnected, onPeerData]); }, [peers, onPeerConnected, onPeerDisconnected, onPeerData, onPeerStream]);
return { peers, socket }; return { peers, socket };
} }

View File

@ -18,7 +18,8 @@ function Game() {
gameId, gameId,
handlePeerConnected, handlePeerConnected,
handlePeerDisconnected, handlePeerDisconnected,
handlePeerData handlePeerData,
handlePeerStream
); );
const [mapSource, setMapSource] = useState(null); const [mapSource, setMapSource] = useState(null);
@ -70,8 +71,13 @@ function Game() {
} }
} }
const [stream, setStream] = useState(null);
const [partyStreams, setPartyStreams] = useState({});
function handlePeerConnected({ peer }) { function handlePeerConnected({ peer }) {
peer.send({ id: "nickname", data: { [socket.id]: nickname } }); peer.send({ id: "nickname", data: { [socket.id]: nickname } });
if (stream) {
peer.addStream(stream);
}
} }
function handlePeerData({ data, peer }) { function handlePeerData({ data, peer }) {
@ -107,10 +113,28 @@ function Game() {
} }
} }
function handlePeerStream({ id, stream: partyStream }) {
setPartyStreams((prevStreams) => ({
...prevStreams,
[id]: partyStream,
}));
}
function handlePeerDisconnected(disconnectedId) { function handlePeerDisconnected(disconnectedId) {
setPartyNicknames((prevNicknames) => omit(prevNicknames, [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 ( return (
<Flex sx={{ flexDirection: "column", height: "100%" }}> <Flex sx={{ flexDirection: "column", height: "100%" }}>
<Flex <Flex
@ -121,6 +145,9 @@ function Game() {
partyNicknames={partyNicknames} partyNicknames={partyNicknames}
gameId={gameId} gameId={gameId}
onNicknameChange={handleNicknameChange} onNicknameChange={handleNicknameChange}
stream={stream}
partyStreams={partyStreams}
onStreamStart={handleStreamStart}
/> />
<Map <Map
mapSource={mapSource} mapSource={mapSource}