Added modal to radio and added error feedback in it
This commit is contained in:
parent
8c5d58ae93
commit
84f201858a
@ -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",
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user