From 8ecd1a2ac23c1d751b30d246e3a0a0ab824edb93 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 21 Oct 2020 13:56:29 -0700 Subject: [PATCH] Add VoiceButtonContainer logic --- .storybook/main.js | 5 ++- src/components/mute-mic.js | 1 - src/react-components/icons/Microphone.svg | 1 + .../icons/MicrophoneMuted.svg | 1 + .../room/MicSetupModalContainer.js | 4 +- src/react-components/room/PeopleSidebar.js | 6 +-- .../room/VoiceButtonContainer.js | 45 +++++++++++++++++++ ...seMicrophoneVolume.js => useMicrophone.js} | 30 ++++++++++--- src/react-components/ui-root.js | 4 +- webpack.config.js | 5 ++- 10 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 src/react-components/room/VoiceButtonContainer.js rename src/react-components/room/{useMicrophoneVolume.js => useMicrophone.js} (54%) diff --git a/.storybook/main.js b/.storybook/main.js index 6bc6d75b2..b11cdf7d1 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -35,7 +35,10 @@ module.exports = { template: require("../src/react-components/icons/IconTemplate"), svgoConfig: { plugins: { - removeViewBox: false + removeViewBox: false, + mergePaths: false, + convertShapeToPath: false, + removeHiddenElems: false } } } diff --git a/src/components/mute-mic.js b/src/components/mute-mic.js index 41c0ad8fd..c723f317f 100644 --- a/src/components/mute-mic.js +++ b/src/components/mute-mic.js @@ -55,7 +55,6 @@ AFRAME.registerComponent("mute-mic", { if (!NAF.connection.adapter) return; if (!this.el.sceneEl.is("entered")) return; - this.el.sceneEl.systems["hubs-systems"].soundEffectsSystem.playSoundOneShot(SOUND_TOGGLE_MIC); if (this.el.is("muted")) { NAF.connection.adapter.enableMicrophone(true); this.el.removeState("muted"); diff --git a/src/react-components/icons/Microphone.svg b/src/react-components/icons/Microphone.svg index 3c19ecdae..5478383c3 100644 --- a/src/react-components/icons/Microphone.svg +++ b/src/react-components/icons/Microphone.svg @@ -1,5 +1,6 @@ + diff --git a/src/react-components/icons/MicrophoneMuted.svg b/src/react-components/icons/MicrophoneMuted.svg index 8bb2e5a17..98e5d9af9 100644 --- a/src/react-components/icons/MicrophoneMuted.svg +++ b/src/react-components/icons/MicrophoneMuted.svg @@ -1,5 +1,6 @@ + diff --git a/src/react-components/room/MicSetupModalContainer.js b/src/react-components/room/MicSetupModalContainer.js index d168db9c4..623845e11 100644 --- a/src/react-components/room/MicSetupModalContainer.js +++ b/src/react-components/room/MicSetupModalContainer.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import { MicSetupModal } from "./MicSetupModal"; -import { useMicrophoneVolume } from "./useMicrophoneVolume"; +import { useMicrophone } from "./useMicrophone"; import { useSound } from "./useSound"; import webmSrc from "../../assets/sfx/tone.webm"; import mp3Src from "../../assets/sfx/tone.mp3"; @@ -9,7 +9,7 @@ import oggSrc from "../../assets/sfx/tone.ogg"; import wavSrc from "../../assets/sfx/tone.wav"; export function MicSetupModalContainer({ scene, ...rest }) { - const volume = useMicrophoneVolume(scene); + const { volume } = useMicrophone(scene); const [soundPlaying, playSound] = useSound({ webmSrc, mp3Src, oggSrc, wavSrc }); return ; } diff --git a/src/react-components/room/PeopleSidebar.js b/src/react-components/room/PeopleSidebar.js index dbdc6da13..0992c7bce 100644 --- a/src/react-components/room/PeopleSidebar.js +++ b/src/react-components/room/PeopleSidebar.js @@ -54,10 +54,10 @@ function getVoiceLabel(micPresence) { function getVoiceIconComponent(micPresence) { if (micPresence) { - if (micPresence.talking) { - return VolumeHighIcon; - } else if (micPresence.muted) { + if (micPresence.muted) { return VolumeMutedIcon; + } else if (micPresence.talking) { + return VolumeHighIcon; } } diff --git a/src/react-components/room/VoiceButtonContainer.js b/src/react-components/room/VoiceButtonContainer.js new file mode 100644 index 000000000..f78c0e88c --- /dev/null +++ b/src/react-components/room/VoiceButtonContainer.js @@ -0,0 +1,45 @@ +import React, { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import { ReactComponent as MicrophoneIcon } from "../icons/Microphone.svg"; +import { ReactComponent as MicrophoneMutedIcon } from "../icons/MicrophoneMuted.svg"; +import { ToolbarButton } from "../input/ToolbarButton"; +import { useMicrophone } from "./useMicrophone"; + +export function VoiceButtonContainer({ scene, microphoneEnabled }) { + const buttonRef = useRef(); + + const { isMuted, volume, toggleMute } = useMicrophone(scene); + + useEffect( + () => { + const rect = buttonRef.current.querySelector("rect"); + + if (volume < 0.05) { + rect.setAttribute("height", 0); + } else if (volume < 0.3) { + rect.setAttribute("y", 8); + rect.setAttribute("height", 4); + } else { + rect.setAttribute("y", 4); + rect.setAttribute("height", 8); + } + }, + [volume, isMuted] + ); + + return ( + : } + label="Voice" + preset="basic" + onClick={toggleMute} + statusColor={microphoneEnabled ? "green" : undefined} + /> + ); +} + +VoiceButtonContainer.propTypes = { + scene: PropTypes.object.isRequired, + microphoneEnabled: PropTypes.bool +}; diff --git a/src/react-components/room/useMicrophoneVolume.js b/src/react-components/room/useMicrophone.js similarity index 54% rename from src/react-components/room/useMicrophoneVolume.js rename to src/react-components/room/useMicrophone.js index 348556752..68a78715c 100644 --- a/src/react-components/room/useMicrophoneVolume.js +++ b/src/react-components/room/useMicrophone.js @@ -1,17 +1,17 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import MovingAverage from "moving-average"; -const MOVING_AVG_TIMESPAN = 100; const UPDATE_RATE = 50; -export function useMicrophoneVolume(scene) { +export function useMicrophone(scene, updateRate = UPDATE_RATE) { const movingAvgRef = useRef(); + const [isMuted, setIsMuted] = useState(scene.is("muted")); const [volume, setVolume] = useState(0); useEffect( () => { if (!movingAvgRef.current) { - movingAvgRef.current = MovingAverage(MOVING_AVG_TIMESPAN); + movingAvgRef.current = MovingAverage(updateRate * 2); } let max = 0; @@ -31,12 +31,30 @@ export function useMicrophoneVolume(scene) { updateMicVolume(); + function onSceneStateChange(event) { + if (event.detail === "muted") { + setIsMuted(scene.is("muted")); + } + } + + scene.addEventListener("stateadded", onSceneStateChange); + scene.addEventListener("stateremoved", onSceneStateChange); + return () => { clearTimeout(timeout); + scene.removeEventListener("stateadded", onSceneStateChange); + scene.removeEventListener("stateremoved", onSceneStateChange); }; }, - [setVolume, scene] + [setVolume, scene, updateRate] ); - return volume; + const toggleMute = useCallback( + () => { + scene.emit("action_mute"); + }, + [scene] + ); + + return { isMuted, volume, toggleMute }; } diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index d14e42a63..470ec5d82 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -91,7 +91,6 @@ import { ReactComponent as DiscordIcon } from "./icons/Discord.svg"; import { ReactComponent as VRIcon } from "./icons/VR.svg"; import { ReactComponent as PeopleIcon } from "./icons/People.svg"; import { ReactComponent as ObjectsIcon } from "./icons/Objects.svg"; -import { ReactComponent as MicrophoneIcon } from "./icons/Microphone.svg"; import { ReactComponent as ReactionIcon } from "./icons/Reaction.svg"; import { ReactComponent as LeaveIcon } from "./icons/Leave.svg"; import { PeopleSidebarContainer, userFromPresence } from "./room/PeopleSidebarContainer"; @@ -101,6 +100,7 @@ import { ObjectMenuContainer } from "./room/ObjectMenuContainer"; import { useCssBreakpoints } from "react-use-css-breakpoints"; import { PlacePopoverContainer } from "./room/PlacePopoverContainer"; import { SharePopoverContainer } from "./room/SharePopoverContainer"; +import { VoiceButtonContainer } from "./room/VoiceButtonContainer"; const avatarEditorDebug = qsTruthy("avatarEditorDebug"); @@ -1894,7 +1894,7 @@ class UIRoot extends Component { <> {entered && ( <> - } label="Voice" preset="basic" /> + { template: require("./src/react-components/icons/IconTemplate"), svgoConfig: { plugins: { - removeViewBox: false + removeViewBox: false, + mergePaths: false, + convertShapeToPath: false, + removeHiddenElems: false } } }