Merge pull request #367 from robintown/vu-animation
Add a VU meter-style animation to radio mode
This commit is contained in:
commit
bab5c9aa42
5 changed files with 59 additions and 20 deletions
|
@ -26,6 +26,8 @@ limitations under the License.
|
||||||
--inter-unicode-range: U+0000-20e2, U+20e4-23ce, U+23d0-24c1, U+24c3-259f,
|
--inter-unicode-range: U+0000-20e2, U+20e4-23ce, U+23d0-24c1, U+24c3-259f,
|
||||||
U+25c2-2664, U+2666-2763, U+2765-2b05, U+2b07-2b1b, U+2b1d-10FFFF;
|
U+25c2-2664, U+2666-2763, U+2765-2b05, U+2b07-2b1b, U+2b1d-10FFFF;
|
||||||
--primaryColor: #0dbd8b;
|
--primaryColor: #0dbd8b;
|
||||||
|
--primaryColor-20: #0dbd8b33;
|
||||||
|
--alert-20: #ff5b5533;
|
||||||
--bgColor1: #15191e;
|
--bgColor1: #15191e;
|
||||||
--bgColor2: #21262c;
|
--bgColor2: #21262c;
|
||||||
--bgColor3: #444;
|
--bgColor3: #444;
|
||||||
|
|
|
@ -14,14 +14,10 @@
|
||||||
|
|
||||||
.talking {
|
.talking {
|
||||||
background-color: #0dbd8b;
|
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);
|
|
||||||
cursor: unset;
|
cursor: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: #ff5b55;
|
background-color: #ff5b55;
|
||||||
border-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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState, createRef } from "react";
|
import React, { useCallback, useEffect, useState, createRef } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { useSpring, animated } from "@react-spring/web";
|
||||||
|
|
||||||
import styles from "./PTTButton.module.css";
|
import styles from "./PTTButton.module.css";
|
||||||
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
||||||
|
@ -27,6 +28,7 @@ interface Props {
|
||||||
activeSpeakerDisplayName: string;
|
activeSpeakerDisplayName: string;
|
||||||
activeSpeakerAvatarUrl: string;
|
activeSpeakerAvatarUrl: string;
|
||||||
activeSpeakerIsLocalUser: boolean;
|
activeSpeakerIsLocalUser: boolean;
|
||||||
|
activeSpeakerVolume: number;
|
||||||
size: number;
|
size: number;
|
||||||
startTalking: () => void;
|
startTalking: () => void;
|
||||||
stopTalking: () => void;
|
stopTalking: () => void;
|
||||||
|
@ -44,6 +46,7 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
activeSpeakerDisplayName,
|
activeSpeakerDisplayName,
|
||||||
activeSpeakerAvatarUrl,
|
activeSpeakerAvatarUrl,
|
||||||
activeSpeakerIsLocalUser,
|
activeSpeakerIsLocalUser,
|
||||||
|
activeSpeakerVolume,
|
||||||
size,
|
size,
|
||||||
startTalking,
|
startTalking,
|
||||||
stopTalking,
|
stopTalking,
|
||||||
|
@ -130,12 +133,32 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, [onWindowMouseUp, onWindowTouchEnd, onButtonTouchStart, buttonRef]);
|
}, [onWindowMouseUp, onWindowTouchEnd, onButtonTouchStart, buttonRef]);
|
||||||
|
|
||||||
|
const { shadow } = useSpring({
|
||||||
|
shadow: (Math.max(activeSpeakerVolume, -70) + 70) * 0.6,
|
||||||
|
config: {
|
||||||
|
clamp: true,
|
||||||
|
tension: 300,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const shadowColor = showTalkOverError
|
||||||
|
? "var(--alert-20)"
|
||||||
|
: "var(--primaryColor-20)";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<animated.button
|
||||||
className={classNames(styles.pttButton, {
|
className={classNames(styles.pttButton, {
|
||||||
[styles.talking]: activeSpeakerUserId,
|
[styles.talking]: activeSpeakerUserId,
|
||||||
[styles.error]: showTalkOverError,
|
[styles.error]: showTalkOverError,
|
||||||
})}
|
})}
|
||||||
|
style={{
|
||||||
|
boxShadow: shadow.to(
|
||||||
|
(s) =>
|
||||||
|
`0px 0px 0px ${s}px ${shadowColor}, 0px 0px 0px ${
|
||||||
|
2 * s
|
||||||
|
}px ${shadowColor}`
|
||||||
|
),
|
||||||
|
}}
|
||||||
onMouseDown={onButtonMouseDown}
|
onMouseDown={onButtonMouseDown}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
>
|
>
|
||||||
|
@ -154,6 +177,6 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</animated.button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -123,6 +123,7 @@ export const PTTCallView: React.FC<Props> = ({
|
||||||
talkOverEnabled,
|
talkOverEnabled,
|
||||||
setTalkOverEnabled,
|
setTalkOverEnabled,
|
||||||
activeSpeakerUserId,
|
activeSpeakerUserId,
|
||||||
|
activeSpeakerVolume,
|
||||||
startTalking,
|
startTalking,
|
||||||
stopTalking,
|
stopTalking,
|
||||||
transmitBlocked,
|
transmitBlocked,
|
||||||
|
@ -221,6 +222,7 @@ export const PTTCallView: React.FC<Props> = ({
|
||||||
activeSpeakerDisplayName={activeSpeakerDisplayName}
|
activeSpeakerDisplayName={activeSpeakerDisplayName}
|
||||||
activeSpeakerAvatarUrl={activeSpeakerAvatarUrl}
|
activeSpeakerAvatarUrl={activeSpeakerAvatarUrl}
|
||||||
activeSpeakerIsLocalUser={activeSpeakerIsLocalUser}
|
activeSpeakerIsLocalUser={activeSpeakerIsLocalUser}
|
||||||
|
activeSpeakerVolume={activeSpeakerVolume}
|
||||||
size={pttButtonSize}
|
size={pttButtonSize}
|
||||||
startTalking={startTalking}
|
startTalking={startTalking}
|
||||||
stopTalking={stopTalking}
|
stopTalking={stopTalking}
|
||||||
|
|
|
@ -65,6 +65,7 @@ export interface PTTState {
|
||||||
talkOverEnabled: boolean;
|
talkOverEnabled: boolean;
|
||||||
setTalkOverEnabled: (boolean) => void;
|
setTalkOverEnabled: (boolean) => void;
|
||||||
activeSpeakerUserId: string;
|
activeSpeakerUserId: string;
|
||||||
|
activeSpeakerVolume: number;
|
||||||
startTalking: () => void;
|
startTalking: () => void;
|
||||||
stopTalking: () => void;
|
stopTalking: () => void;
|
||||||
transmitBlocked: boolean;
|
transmitBlocked: boolean;
|
||||||
|
@ -108,6 +109,7 @@ export const usePTT = (
|
||||||
isAdmin,
|
isAdmin,
|
||||||
talkOverEnabled,
|
talkOverEnabled,
|
||||||
activeSpeakerUserId,
|
activeSpeakerUserId,
|
||||||
|
activeSpeakerVolume,
|
||||||
transmitBlocked,
|
transmitBlocked,
|
||||||
},
|
},
|
||||||
setState,
|
setState,
|
||||||
|
@ -121,6 +123,7 @@ export const usePTT = (
|
||||||
talkOverEnabled: false,
|
talkOverEnabled: false,
|
||||||
pttButtonHeld: false,
|
pttButtonHeld: false,
|
||||||
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
||||||
|
activeSpeakerVolume: -Infinity,
|
||||||
transmitBlocked: false,
|
transmitBlocked: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -152,15 +155,11 @@ export const usePTT = (
|
||||||
playClip(PTTClipID.BLOCKED);
|
playClip(PTTClipID.BLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
setState((prevState) => {
|
setState((prevState) => ({
|
||||||
return {
|
...prevState,
|
||||||
...prevState,
|
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
||||||
activeSpeakerUserId: activeSpeakerFeed
|
transmitBlocked: blocked,
|
||||||
? activeSpeakerFeed.userId
|
}));
|
||||||
: null,
|
|
||||||
transmitBlocked: blocked,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [
|
}, [
|
||||||
playClip,
|
playClip,
|
||||||
groupCall,
|
groupCall,
|
||||||
|
@ -173,7 +172,7 @@ export const usePTT = (
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const callFeed of userMediaFeeds) {
|
for (const callFeed of userMediaFeeds) {
|
||||||
callFeed.addListener(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
|
callFeed.on(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
||||||
|
@ -185,14 +184,30 @@ export const usePTT = (
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
for (const callFeed of userMediaFeeds) {
|
for (const callFeed of userMediaFeeds) {
|
||||||
callFeed.removeListener(
|
callFeed.off(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
|
||||||
CallFeedEvent.MuteStateChanged,
|
|
||||||
onMuteStateChanged
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [userMediaFeeds, onMuteStateChanged, groupCall]);
|
}, [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 () => {
|
const startTalking = useCallback(async () => {
|
||||||
if (pttButtonHeld) return;
|
if (pttButtonHeld) return;
|
||||||
|
|
||||||
|
@ -317,6 +332,7 @@ export const usePTT = (
|
||||||
talkOverEnabled,
|
talkOverEnabled,
|
||||||
setTalkOverEnabled,
|
setTalkOverEnabled,
|
||||||
activeSpeakerUserId,
|
activeSpeakerUserId,
|
||||||
|
activeSpeakerVolume,
|
||||||
startTalking,
|
startTalking,
|
||||||
stopTalking,
|
stopTalking,
|
||||||
transmitBlocked,
|
transmitBlocked,
|
||||||
|
|
Loading…
Reference in a new issue