Added modal to radio and added error feedback in it

This commit is contained in:
Mitchell McCaffrey 2020-04-09 10:36:28 +10:00
parent 8c5d58ae93
commit 84f201858a
5 changed files with 169 additions and 66 deletions

View File

@ -20,7 +20,8 @@
"simple-peer": "^9.6.2", "simple-peer": "^9.6.2",
"simplebar-react": "^2.1.0", "simplebar-react": "^2.1.0",
"socket.io-client": "^2.3.0", "socket.io-client": "^2.3.0",
"theme-ui": "^0.3.1" "theme-ui": "^0.3.1",
"webrtc-adapter": "^7.5.1"
}, },
"scripts": { "scripts": {
"predeploy": "yarn build", "predeploy": "yarn build",

View File

@ -14,6 +14,7 @@ function Party({
stream, stream,
partyStreams, partyStreams,
onStreamStart, onStreamStart,
onStreamEnd,
}) { }) {
return ( return (
<Flex <Flex
@ -53,7 +54,11 @@ function Party({
</Box> </Box>
<Flex sx={{ flexDirection: "column" }}> <Flex sx={{ flexDirection: "column" }}>
<ChangeNicknameButton nickname={nickname} onChange={onNicknameChange} /> <ChangeNicknameButton nickname={nickname} onChange={onNicknameChange} />
<StartStreamButton onStream={onStreamStart} /> <StartStreamButton
onStreamStart={onStreamStart}
onStreamEnd={onStreamEnd}
stream={stream}
/>
<AddPartyMemberButton gameId={gameId} /> <AddPartyMemberButton gameId={gameId} />
</Flex> </Flex>
</Flex> </Flex>

View File

@ -1,19 +1,114 @@
import React from "react"; import React, { useState } from "react";
import { IconButton } from "theme-ui"; 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 = (
<Box p={2} bg="hsla(230, 20%, 0%, 20%)">
<Text>
Browser not supported.
<br />
<br />
See FAQ for more information.
</Text>
</Box>
);
const noAudioMessage = (
<Box p={2} bg="hsla(230, 20%, 0%, 20%)">
<Text>
No audio found in screen share.
<br />
Ensure "Share audio" is selected when sharing.
<br />
<br />
See FAQ for more information.
</Text>
</Box>
);
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 ( return (
<IconButton m={1} aria-label="Start Radio Stream" onClick={onStream}> <>
<svg <IconButton m={1} aria-label="Start Radio Stream" onClick={openModal}>
xmlns="http://www.w3.org/2000/svg" <svg
height="24" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" height="24"
width="24" viewBox="0 0 24 24"
fill="currentcolor" width="24"
> 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> <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" />
</IconButton> </svg>
</IconButton>
<Modal isOpen={isStreamModalOpoen} onRequestClose={closeModal}>
<Box>
<Label pt={2} pb={1}>
Radio (experimental)
</Label>
<Text mb={2} variant="caption">
Share your computers audio with the party
</Text>
{!isSupported && unavailableMessage}
{isSupported && !stream && noAudioTrack && noAudioMessage}
<Flex py={2}>
{isSupported && !stream && (
<Button sx={{ flexGrow: 1 }} onClick={handleStreamStart}>
Start Radio
</Button>
)}
{isSupported && stream && (
<Button sx={{ flexGrow: 1 }} onClick={() => onStreamEnd(stream)}>
Stop Radio
</Button>
)}
</Flex>
</Box>
</Modal>
</>
); );
} }

View File

@ -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 { Flex } from "theme-ui";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@ -136,70 +136,51 @@ function Game() {
} }
} }
useEffect(() => { function handleStreamStart(localStream) {
if (stream) { setStream(localStream);
const tracks = stream.getTracks(); 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() { const handleStreamEnd = useCallback(
setStream(null); (localStream) => {
for (let track of tracks) { setStream(null);
// Only sending audio so only remove the audio track const tracks = localStream.getTracks();
if (track.kind === "audio") { for (let track of tracks) {
for (let peer of Object.values(peers)) { track.stop();
track.stop(); // Only sending audio so only remove the audio track
peer.removeTrack(track, stream); 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 // Detect when someone has ended the screen sharing
// by looking at the streams video track onended // by looking at the streams video track onended
// the audio track doesn't seem to trigger this event // the audio track doesn't seem to trigger this event
for (let track of tracks) { for (let track of tracks) {
if (track.kind === "video") { if (track.kind === "video") {
track.onended = function () { track.onended = function () {
handleStreamEnd(track); handleStreamEnd(stream);
}; };
} }
} }
} }
}, [stream, peers]); }, [stream, peers, handleStreamEnd]);
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();
}
}
});
}
return ( return (
<Flex sx={{ flexDirection: "column", height: "100%" }}> <Flex sx={{ flexDirection: "column", height: "100%" }}>
@ -214,6 +195,7 @@ function Game() {
stream={stream} stream={stream}
partyStreams={partyStreams} partyStreams={partyStreams}
onStreamStart={handleStreamStart} onStreamStart={handleStreamStart}
onStreamEnd={handleStreamEnd}
/> />
<Map <Map
mapSource={mapSource} mapSource={mapSource}

View File

@ -9622,6 +9622,13 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
rtcpeerconnection-shim@^1.2.15:
version "1.2.15"
resolved "https://registry.yarnpkg.com/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz#e7cc189a81b435324c4949aa3dfb51888684b243"
integrity sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==
dependencies:
sdp "^2.6.0"
run-async@^2.2.0: run-async@^2.2.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@ -9733,6 +9740,11 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6
ajv "^6.10.2" ajv "^6.10.2"
ajv-keywords "^3.4.1" ajv-keywords "^3.4.1"
sdp@^2.12.0, sdp@^2.6.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/sdp/-/sdp-2.12.0.tgz#338a106af7560c86e4523f858349680350d53b22"
integrity sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==
select-hose@^2.0.0: select-hose@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@ -11175,6 +11187,14 @@ webpack@4.41.5:
watchpack "^1.6.0" watchpack "^1.6.0"
webpack-sources "^1.4.1" webpack-sources "^1.4.1"
webrtc-adapter@^7.5.1:
version "7.5.1"
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-7.5.1.tgz#30895069da4fed9578597c066e6194fe83b97b37"
integrity sha512-R5LkIR/APjODkstSXFOztOmINXQ0nqIGfUoKTtCzjyiDXHNgwhkqZ9vi8UzGyjfUBibuZ0ZzVyV10qtuLGW3CQ==
dependencies:
rtcpeerconnection-shim "^1.2.15"
sdp "^2.12.0"
websocket-driver@>=0.5.1: websocket-driver@>=0.5.1:
version "0.7.3" version "0.7.3"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"