Moved to audio only, handled stream closure and added mute button to name
This commit is contained in:
parent
81c3e69932
commit
2f18222e99
@ -1,7 +1,11 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Text } from "theme-ui";
|
import { Text, IconButton } from "theme-ui";
|
||||||
|
|
||||||
|
import Stream from "./Stream";
|
||||||
|
|
||||||
|
function Nickname({ nickname, stream }) {
|
||||||
|
const [streamMuted, setStreamMuted] = useState(false);
|
||||||
|
|
||||||
function Nickname({ nickname }) {
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
my={1}
|
my={1}
|
||||||
@ -9,9 +13,39 @@ function Nickname({ nickname }) {
|
|||||||
sx={{
|
sx={{
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
cursor: stream ? "pointer" : "default",
|
||||||
}}
|
}}
|
||||||
|
onClick={() => stream && setStreamMuted(!streamMuted)}
|
||||||
>
|
>
|
||||||
{nickname}
|
{nickname}
|
||||||
|
{stream && (
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
width: "10px",
|
||||||
|
height: "10px",
|
||||||
|
padding: 0,
|
||||||
|
margin: "2px",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "-2px",
|
||||||
|
}}
|
||||||
|
aria-label="Toggle Player Mute"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="10"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
{streamMuted ? (
|
||||||
|
<path d="M3.63 3.63c-.39.39-.39 1.02 0 1.41L7.29 8.7 7 9H4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71v-4.17l4.18 4.18c-.49.37-1.02.68-1.6.91-.36.15-.58.53-.58.92 0 .72.73 1.18 1.39.91.8-.33 1.55-.77 2.22-1.31l1.34 1.34c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L5.05 3.63c-.39-.39-1.02-.39-1.42 0zM19 12c0 .82-.15 1.61-.41 2.34l1.53 1.53c.56-1.17.88-2.48.88-3.87 0-3.83-2.4-7.11-5.78-8.4-.59-.23-1.22.23-1.22.86v.19c0 .38.25.71.61.85C17.18 6.54 19 9.06 19 12zm-8.71-6.29l-.17.17L12 7.76V6.41c0-.89-1.08-1.33-1.71-.7zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v1.79l2.48 2.48c.01-.08.02-.16.02-.24z" />
|
||||||
|
) : (
|
||||||
|
<path d="M3 10v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71V6.41c0-.89-1.08-1.34-1.71-.71L7 9H4c-.55 0-1 .45-1 1zm13.5 2c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 4.45v.2c0 .38.25.71.6.85C17.18 6.53 19 9.06 19 12s-1.82 5.47-4.4 6.5c-.36.14-.6.47-.6.85v.2c0 .63.63 1.07 1.21.85C18.6 19.11 21 15.84 21 12s-2.4-7.11-5.79-8.4c-.58-.23-1.21.22-1.21.85z" />
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
{stream && <Stream stream={stream} muted={streamMuted} />}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ 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";
|
|
||||||
import ChangeNicknameButton from "./ChangeNicknameButton";
|
import ChangeNicknameButton from "./ChangeNicknameButton";
|
||||||
import StartStreamButton from "./StartStreamButton";
|
import StartStreamButton from "./StartStreamButton";
|
||||||
|
|
||||||
@ -45,16 +44,12 @@ function Party({
|
|||||||
>
|
>
|
||||||
<Nickname nickname={nickname || ""} />
|
<Nickname nickname={nickname || ""} />
|
||||||
{Object.entries(partyNicknames).map(([id, partyNickname]) => (
|
{Object.entries(partyNicknames).map(([id, partyNickname]) => (
|
||||||
<Nickname nickname={partyNickname} key={id} />
|
<Nickname
|
||||||
|
nickname={partyNickname}
|
||||||
|
key={id}
|
||||||
|
stream={partyStreams[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>
|
||||||
<Flex sx={{ flexDirection: "column" }}>
|
<Flex sx={{ flexDirection: "column" }}>
|
||||||
<ChangeNicknameButton nickname={nickname} onChange={onNicknameChange} />
|
<ChangeNicknameButton nickname={nickname} onChange={onNicknameChange} />
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
import React, { useRef, useEffect } from "react";
|
import React, { useRef, useEffect } from "react";
|
||||||
|
|
||||||
function Stream({ stream, muted }) {
|
function Stream({ stream, muted }) {
|
||||||
const videoRef = useRef();
|
const audioRef = useRef();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoRef.current) {
|
if (audioRef.current) {
|
||||||
videoRef.current.srcObject = stream;
|
audioRef.current.srcObject = stream;
|
||||||
}
|
}
|
||||||
}, [stream]);
|
}, [stream]);
|
||||||
|
|
||||||
return (
|
return <audio ref={audioRef} autoPlay playsInline muted={muted} />;
|
||||||
<video
|
|
||||||
ref={videoRef}
|
|
||||||
autoPlay
|
|
||||||
playsInline
|
|
||||||
muted={muted}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream.defaultProps = {
|
Stream.defaultProps = {
|
||||||
|
@ -18,3 +18,8 @@ export function fromEntries(iterable) {
|
|||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check to see if all tracks are muted
|
||||||
|
export function isStreamStopped(stream) {
|
||||||
|
return stream.getTracks().reduce((a, b) => a && b, { mute: true });
|
||||||
|
}
|
||||||
|
@ -11,7 +11,8 @@ function useSession(
|
|||||||
onPeerConnected,
|
onPeerConnected,
|
||||||
onPeerDisconnected,
|
onPeerDisconnected,
|
||||||
onPeerData,
|
onPeerData,
|
||||||
onPeerStream
|
onPeerTrackAdded,
|
||||||
|
onPeerTrackRemoved
|
||||||
) {
|
) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.emit("join party", partyId);
|
socket.emit("join party", partyId);
|
||||||
@ -38,8 +39,11 @@ function useSession(
|
|||||||
onPeerData && onPeerData({ id, peer, data });
|
onPeerData && onPeerData({ id, peer, data });
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on("stream", (stream) => {
|
peer.on("track", (track, stream) => {
|
||||||
onPeerStream && onPeerStream({ id, peer, stream });
|
onPeerTrackAdded && onPeerTrackAdded({ id, peer, track, stream });
|
||||||
|
track.addEventListener("mute", () => {
|
||||||
|
onPeerTrackRemoved && onPeerTrackRemoved({ id, peer, track, stream });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on("close", () => {
|
peer.on("close", () => {
|
||||||
@ -93,7 +97,14 @@ function useSession(
|
|||||||
socket.removeListener("joined party", handleJoinedParty);
|
socket.removeListener("joined party", handleJoinedParty);
|
||||||
socket.removeListener("signal", handleSignal);
|
socket.removeListener("signal", handleSignal);
|
||||||
};
|
};
|
||||||
}, [peers, onPeerConnected, onPeerDisconnected, onPeerData, onPeerStream]);
|
}, [
|
||||||
|
peers,
|
||||||
|
onPeerConnected,
|
||||||
|
onPeerDisconnected,
|
||||||
|
onPeerData,
|
||||||
|
onPeerTrackAdded,
|
||||||
|
onPeerTrackRemoved,
|
||||||
|
]);
|
||||||
|
|
||||||
return { peers, socket };
|
return { peers, socket };
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { Flex } from "theme-ui";
|
import { Flex } from "theme-ui";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { omit } from "../helpers/shared";
|
import { omit, isStreamStopped } from "../helpers/shared";
|
||||||
|
|
||||||
import useSession from "../helpers/useSession";
|
import useSession from "../helpers/useSession";
|
||||||
import { getRandomMonster } from "../helpers/monsters";
|
import { getRandomMonster } from "../helpers/monsters";
|
||||||
@ -19,7 +19,8 @@ function Game() {
|
|||||||
handlePeerConnected,
|
handlePeerConnected,
|
||||||
handlePeerDisconnected,
|
handlePeerDisconnected,
|
||||||
handlePeerData,
|
handlePeerData,
|
||||||
handlePeerStream
|
handlePeerTrackAdded,
|
||||||
|
handlePeerTrackRemoved
|
||||||
);
|
);
|
||||||
|
|
||||||
const [mapSource, setMapSource] = useState(null);
|
const [mapSource, setMapSource] = useState(null);
|
||||||
@ -113,17 +114,58 @@ 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 handlePeerTrackAdded({ id, stream: remoteStream }) {
|
||||||
|
setPartyStreams((prevStreams) => ({
|
||||||
|
...prevStreams,
|
||||||
|
[id]: remoteStream,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePeerTrackRemoved({ id, stream: remoteStream }) {
|
||||||
|
if (isStreamStopped(remoteStream)) {
|
||||||
|
setPartyStreams((prevStreams) => omit(prevStreams, [id]));
|
||||||
|
} else {
|
||||||
|
setPartyStreams((prevStreams) => ({
|
||||||
|
...prevStreams,
|
||||||
|
[id]: remoteStream,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stream) {
|
||||||
|
const tracks = stream.getTracks();
|
||||||
|
|
||||||
|
function handleStreamEnd() {
|
||||||
|
setStream(null);
|
||||||
|
for (let track of tracks) {
|
||||||
|
// Only sending audio so only remove the audio track
|
||||||
|
if (track.kind === "audio") {
|
||||||
|
for (let peer of Object.values(peers)) {
|
||||||
|
track.stop();
|
||||||
|
peer.removeTrack(track, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect when someone has ended the screen sharing
|
||||||
|
// by looking at the streams video track onended
|
||||||
|
// the audio track doesn't seem to trigger this event
|
||||||
|
for (let track of tracks) {
|
||||||
|
if (track.kind === "video") {
|
||||||
|
track.onended = function () {
|
||||||
|
handleStreamEnd(track);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [stream, peers]);
|
||||||
|
|
||||||
function handleStreamStart() {
|
function handleStreamStart() {
|
||||||
navigator.mediaDevices
|
navigator.mediaDevices
|
||||||
.getDisplayMedia({
|
.getDisplayMedia({
|
||||||
@ -134,10 +176,27 @@ function Game() {
|
|||||||
echoCancellation: false,
|
echoCancellation: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((mediaStream) => {
|
.then((localStream) => {
|
||||||
setStream(mediaStream);
|
setStream(localStream);
|
||||||
for (let peer of Object.values(peers)) {
|
const tracks = localStream.getTracks();
|
||||||
peer.addStream(mediaStream);
|
|
||||||
|
let noAudio = true;
|
||||||
|
for (let track of tracks) {
|
||||||
|
// Only add the audio track of the stream to the remote peer
|
||||||
|
if (track.kind === "audio") {
|
||||||
|
noAudio = false;
|
||||||
|
for (let peer of Object.values(peers)) {
|
||||||
|
peer.addTrack(track, localStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noAudio) {
|
||||||
|
// TODO: move this to a ui element
|
||||||
|
console.error("No audio tracks found in screen share");
|
||||||
|
for (let track of tracks) {
|
||||||
|
track.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user