diff --git a/src/components/Nickname.js b/src/components/Nickname.js
index e384e1e..94fe404 100644
--- a/src/components/Nickname.js
+++ b/src/components/Nickname.js
@@ -1,7 +1,11 @@
-import React from "react";
-import { Text } from "theme-ui";
+import React, { useState } from "react";
+import { Text, IconButton } from "theme-ui";
+
+import Stream from "./Stream";
+
+function Nickname({ nickname, stream }) {
+ const [streamMuted, setStreamMuted] = useState(false);
-function Nickname({ nickname }) {
return (
stream && setStreamMuted(!streamMuted)}
>
{nickname}
+ {stream && (
+
+
+
+ )}
+ {stream && }
);
}
diff --git a/src/components/Party.js b/src/components/Party.js
index 27a552f..20b10b0 100644
--- a/src/components/Party.js
+++ b/src/components/Party.js
@@ -3,7 +3,6 @@ import { Flex, Box, Text } from "theme-ui";
import AddPartyMemberButton from "./AddPartyMemberButton";
import Nickname from "./Nickname";
-import Stream from "./Stream";
import ChangeNicknameButton from "./ChangeNicknameButton";
import StartStreamButton from "./StartStreamButton";
@@ -45,16 +44,12 @@ function Party({
>
{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
index be7626e..95994c1 100644
--- a/src/components/Stream.js
+++ b/src/components/Stream.js
@@ -1,22 +1,14 @@
import React, { useRef, useEffect } from "react";
function Stream({ stream, muted }) {
- const videoRef = useRef();
+ const audioRef = useRef();
useEffect(() => {
- if (videoRef.current) {
- videoRef.current.srcObject = stream;
+ if (audioRef.current) {
+ audioRef.current.srcObject = stream;
}
}, [stream]);
- return (
-
- );
+ return ;
}
Stream.defaultProps = {
diff --git a/src/helpers/shared.js b/src/helpers/shared.js
index dd8ac46..b1367ce 100644
--- a/src/helpers/shared.js
+++ b/src/helpers/shared.js
@@ -18,3 +18,8 @@ export function fromEntries(iterable) {
return obj;
}, {});
}
+
+// Check to see if all tracks are muted
+export function isStreamStopped(stream) {
+ return stream.getTracks().reduce((a, b) => a && b, { mute: true });
+}
diff --git a/src/helpers/useSession.js b/src/helpers/useSession.js
index c431bb7..6a0fdbf 100644
--- a/src/helpers/useSession.js
+++ b/src/helpers/useSession.js
@@ -11,7 +11,8 @@ function useSession(
onPeerConnected,
onPeerDisconnected,
onPeerData,
- onPeerStream
+ onPeerTrackAdded,
+ onPeerTrackRemoved
) {
useEffect(() => {
socket.emit("join party", partyId);
@@ -38,8 +39,11 @@ function useSession(
onPeerData && onPeerData({ id, peer, data });
});
- peer.on("stream", (stream) => {
- onPeerStream && onPeerStream({ id, peer, stream });
+ peer.on("track", (track, stream) => {
+ onPeerTrackAdded && onPeerTrackAdded({ id, peer, track, stream });
+ track.addEventListener("mute", () => {
+ onPeerTrackRemoved && onPeerTrackRemoved({ id, peer, track, stream });
+ });
});
peer.on("close", () => {
@@ -93,7 +97,14 @@ function useSession(
socket.removeListener("joined party", handleJoinedParty);
socket.removeListener("signal", handleSignal);
};
- }, [peers, onPeerConnected, onPeerDisconnected, onPeerData, onPeerStream]);
+ }, [
+ peers,
+ onPeerConnected,
+ onPeerDisconnected,
+ onPeerData,
+ onPeerTrackAdded,
+ onPeerTrackRemoved,
+ ]);
return { peers, socket };
}
diff --git a/src/routes/Game.js b/src/routes/Game.js
index 17486cc..41232f7 100644
--- a/src/routes/Game.js
+++ b/src/routes/Game.js
@@ -1,8 +1,8 @@
-import React, { useState, useRef } from "react";
+import React, { useState, useRef, useEffect } from "react";
import { Flex } from "theme-ui";
import { useParams } from "react-router-dom";
-import { omit } from "../helpers/shared";
+import { omit, isStreamStopped } from "../helpers/shared";
import useSession from "../helpers/useSession";
import { getRandomMonster } from "../helpers/monsters";
@@ -19,7 +19,8 @@ function Game() {
handlePeerConnected,
handlePeerDisconnected,
handlePeerData,
- handlePeerStream
+ handlePeerTrackAdded,
+ handlePeerTrackRemoved
);
const [mapSource, setMapSource] = useState(null);
@@ -113,17 +114,58 @@ function Game() {
}
}
- function handlePeerStream({ id, stream: partyStream }) {
- setPartyStreams((prevStreams) => ({
- ...prevStreams,
- [id]: partyStream,
- }));
- }
-
function handlePeerDisconnected(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() {
navigator.mediaDevices
.getDisplayMedia({
@@ -134,10 +176,27 @@ function Game() {
echoCancellation: false,
},
})
- .then((mediaStream) => {
- setStream(mediaStream);
- for (let peer of Object.values(peers)) {
- peer.addStream(mediaStream);
+ .then((localStream) => {
+ setStream(localStream);
+ const tracks = localStream.getTracks();
+
+ 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();
+ }
}
});
}