Add a VU meter-style animation to radio mode

This commit is contained in:
Robin Townsend 2022-05-31 18:01:34 -04:00
parent 9444f43c72
commit 2a69b72bed
4 changed files with 57 additions and 20 deletions

View file

@ -13,13 +13,9 @@
.talking {
background-color: #0dbd8b;
box-shadow: 0px 0px 0px 17px rgba(13, 189, 139, 0.2),
0px 0px 0px 34px rgba(13, 189, 139, 0.2);
}
.error {
background-color: #ff5b55;
border-color: #ff5b55;
box-shadow: 0px 0px 0px 17px rgba(255, 91, 85, 0.2),
0px 0px 0px 34px rgba(255, 91, 85, 0.2);
}

View file

@ -16,6 +16,7 @@ limitations under the License.
import React, { useCallback, useEffect, useState, createRef } from "react";
import classNames from "classnames";
import { useSpring, animated } from "@react-spring/web";
import styles from "./PTTButton.module.css";
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
@ -27,6 +28,7 @@ interface Props {
activeSpeakerDisplayName: string;
activeSpeakerAvatarUrl: string;
activeSpeakerIsLocalUser: boolean;
activeSpeakerVolume: number;
size: number;
startTalking: () => void;
stopTalking: () => void;
@ -44,6 +46,7 @@ export const PTTButton: React.FC<Props> = ({
activeSpeakerDisplayName,
activeSpeakerAvatarUrl,
activeSpeakerIsLocalUser,
activeSpeakerVolume,
size,
startTalking,
stopTalking,
@ -130,12 +133,32 @@ export const PTTButton: React.FC<Props> = ({
);
};
}, [onWindowMouseUp, onWindowTouchEnd, onButtonTouchStart, buttonRef]);
const { shadow } = useSpring({
shadow: (Math.max(activeSpeakerVolume, -70) + 70) * 0.7,
config: {
clamp: true,
tension: 300,
},
});
const shadowColor = showTalkOverError
? "rgba(255, 91, 85, 0.2)"
: "rgba(13, 189, 139, 0.2)";
return (
<button
<animated.button
className={classNames(styles.pttButton, {
[styles.talking]: activeSpeakerUserId,
[styles.error]: showTalkOverError,
})}
style={{
boxShadow: shadow.to(
(s) =>
`0px 0px 0px ${s}px ${shadowColor}, 0px 0px 0px ${
2 * s
}px ${shadowColor}`
),
}}
onMouseDown={onButtonMouseDown}
ref={buttonRef}
>
@ -154,6 +177,6 @@ export const PTTButton: React.FC<Props> = ({
className={styles.avatar}
/>
)}
</button>
</animated.button>
);
};

View file

@ -124,6 +124,7 @@ export const PTTCallView: React.FC<Props> = ({
talkOverEnabled,
setTalkOverEnabled,
activeSpeakerUserId,
activeSpeakerVolume,
startTalking,
stopTalking,
transmitBlocked,
@ -223,6 +224,7 @@ export const PTTCallView: React.FC<Props> = ({
activeSpeakerDisplayName={activeSpeakerDisplayName}
activeSpeakerAvatarUrl={activeSpeakerAvatarUrl}
activeSpeakerIsLocalUser={activeSpeakerIsLocalUser}
activeSpeakerVolume={activeSpeakerVolume}
size={pttButtonSize}
startTalking={startTalking}
stopTalking={stopTalking}

View file

@ -49,6 +49,7 @@ export interface PTTState {
talkOverEnabled: boolean;
setTalkOverEnabled: (boolean) => void;
activeSpeakerUserId: string;
activeSpeakerVolume: number;
startTalking: () => void;
stopTalking: () => void;
transmitBlocked: boolean;
@ -87,6 +88,7 @@ export const usePTT = (
isAdmin,
talkOverEnabled,
activeSpeakerUserId,
activeSpeakerVolume,
transmitBlocked,
},
setState,
@ -100,6 +102,7 @@ export const usePTT = (
talkOverEnabled: false,
pttButtonHeld: false,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
activeSpeakerVolume: -Infinity,
transmitBlocked: false,
};
});
@ -131,15 +134,11 @@ export const usePTT = (
playClip(PTTClipID.BLOCKED);
}
setState((prevState) => {
return {
...prevState,
activeSpeakerUserId: activeSpeakerFeed
? activeSpeakerFeed.userId
: null,
transmitBlocked: blocked,
};
});
setState((prevState) => ({
...prevState,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
transmitBlocked: blocked,
}));
}, [
playClip,
groupCall,
@ -152,7 +151,7 @@ export const usePTT = (
useEffect(() => {
for (const callFeed of userMediaFeeds) {
callFeed.addListener(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
callFeed.on(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
}
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
@ -164,14 +163,30 @@ export const usePTT = (
return () => {
for (const callFeed of userMediaFeeds) {
callFeed.removeListener(
CallFeedEvent.MuteStateChanged,
onMuteStateChanged
);
callFeed.off(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
}
};
}, [userMediaFeeds, onMuteStateChanged, groupCall]);
const onVolumeChanged = useCallback((volume: number) => {
setState((prevState) => ({
...prevState,
activeSpeakerVolume: volume,
}));
}, []);
useEffect(() => {
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
activeSpeakerFeed?.on(CallFeedEvent.VolumeChanged, onVolumeChanged);
return () => {
activeSpeakerFeed?.off(CallFeedEvent.VolumeChanged, onVolumeChanged);
setState((prevState) => ({
...prevState,
activeSpeakerVolume: -Infinity,
}));
};
}, [activeSpeakerUserId, onVolumeChanged, userMediaFeeds, groupCall]);
const startTalking = useCallback(async () => {
if (pttButtonHeld) return;
@ -275,6 +290,7 @@ export const usePTT = (
talkOverEnabled,
setTalkOverEnabled,
activeSpeakerUserId,
activeSpeakerVolume,
startTalking,
stopTalking,
transmitBlocked,