diff --git a/package.json b/package.json index 79c5dde..25124db 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "simple-peer": "^9.6.2", "simplebar-react": "^2.1.0", "socket.io-client": "^2.3.0", - "theme-ui": "^0.3.1" + "theme-ui": "^0.3.1", + "webrtc-adapter": "^7.5.1" }, "scripts": { "predeploy": "yarn build", diff --git a/src/components/Party.js b/src/components/Party.js index 20b10b0..ecba181 100644 --- a/src/components/Party.js +++ b/src/components/Party.js @@ -14,6 +14,7 @@ function Party({ stream, partyStreams, onStreamStart, + onStreamEnd, }) { return ( - + diff --git a/src/components/StartStreamButton.js b/src/components/StartStreamButton.js index 613dfd0..de7ad5d 100644 --- a/src/components/StartStreamButton.js +++ b/src/components/StartStreamButton.js @@ -1,19 +1,114 @@ -import React from "react"; -import { IconButton } from "theme-ui"; +import React, { useState } from "react"; +import { IconButton, Box, Text, Button, Label, Flex } from "theme-ui"; +import adapter from "webrtc-adapter"; + +import Modal from "./Modal"; + +function StartStreamButton({ onStreamStart, onStreamEnd, stream }) { + const [isStreamModalOpoen, setIsStreamModalOpen] = useState(false); + function openModal() { + setIsStreamModalOpen(true); + } + function closeModal() { + setIsStreamModalOpen(false); + setNoAudioTrack(false); + } + + const unavailableMessage = ( + + + Browser not supported. +
+
+ See FAQ for more information. +
+
+ ); + + const noAudioMessage = ( + + + No audio found in screen share. +
+ Ensure "Share audio" is selected when sharing. +
+
+ See FAQ for more information. +
+
+ ); + + const isSupported = adapter.browserDetails.browser === "chrome"; + const [noAudioTrack, setNoAudioTrack] = useState(false); + + function handleStreamStart() { + navigator.mediaDevices + .getDisplayMedia({ + video: true, + audio: { + noiseSuppression: false, + autoGainControl: false, + echoCancellation: false, + }, + }) + .then((localStream) => { + const tracks = localStream.getTracks(); + + const hasAudio = tracks.some((track) => track.kind === "audio"); + setNoAudioTrack(!hasAudio); + + // Ensure an audio track is present + if (hasAudio) { + onStreamStart && onStreamStart(localStream); + closeModal(); + } else { + // Stop the stream + for (let track of tracks) { + track.stop(); + } + } + }) + .catch(() => {}); + } -function StartStreamButton({ onStream }) { return ( - - - - - + <> + + + + + + + + + + Share your computers audio with the party + + {!isSupported && unavailableMessage} + {isSupported && !stream && noAudioTrack && noAudioMessage} + + {isSupported && !stream && ( + + )} + {isSupported && stream && ( + + )} + + + + ); } diff --git a/src/routes/Game.js b/src/routes/Game.js index 41232f7..6ba6320 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from "react"; +import React, { useState, useRef, useEffect, useCallback } from "react"; import { Flex } from "theme-ui"; import { useParams } from "react-router-dom"; @@ -136,70 +136,51 @@ function Game() { } } - useEffect(() => { - if (stream) { - const tracks = stream.getTracks(); + function handleStreamStart(localStream) { + setStream(localStream); + const tracks = localStream.getTracks(); + for (let track of tracks) { + // Only add the audio track of the stream to the remote peer + if (track.kind === "audio") { + for (let peer of Object.values(peers)) { + peer.addTrack(track, localStream); + } + } + } + } - 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); - } + const handleStreamEnd = useCallback( + (localStream) => { + setStream(null); + const tracks = localStream.getTracks(); + for (let track of tracks) { + track.stop(); + // Only sending audio so only remove the audio track + if (track.kind === "audio") { + for (let peer of Object.values(peers)) { + peer.removeTrack(track, localStream); } } } + }, + [peers] + ); + useEffect(() => { + if (stream) { + const tracks = stream.getTracks(); // 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); + handleStreamEnd(stream); }; } } } - }, [stream, peers]); - - function handleStreamStart() { - navigator.mediaDevices - .getDisplayMedia({ - video: true, - audio: { - noiseSuppression: false, - autoGainControl: false, - echoCancellation: false, - }, - }) - .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(); - } - } - }); - } + }, [stream, peers, handleStreamEnd]); return ( @@ -214,6 +195,7 @@ function Game() { stream={stream} partyStreams={partyStreams} onStreamStart={handleStreamStart} + onStreamEnd={handleStreamEnd} /> =0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"