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",
"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",

View File

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

View File

@ -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 = (
<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 (
<IconButton m={1} aria-label="Start Radio Stream" onClick={onStream}>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
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>
</IconButton>
<>
<IconButton m={1} aria-label="Start Radio Stream" onClick={openModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
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>
</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 { 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 (
<Flex sx={{ flexDirection: "column", height: "100%" }}>
@ -214,6 +195,7 @@ function Game() {
stream={stream}
partyStreams={partyStreams}
onStreamStart={handleStreamStart}
onStreamEnd={handleStreamEnd}
/>
<Map
mapSource={mapSource}

View File

@ -9622,6 +9622,13 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
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:
version "2.3.0"
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-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:
version "2.0.0"
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"
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:
version "0.7.3"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"