Integrate raise hand sound into call event renderer.

This commit is contained in:
Half-Shot
2024-12-03 14:40:43 +00:00
parent 719e6e7977
commit 0c331a05ad
3 changed files with 32 additions and 46 deletions

View File

@@ -51,7 +51,14 @@ afterEach(() => {
window.HTMLMediaElement.prototype.play = originalPlayFn; window.HTMLMediaElement.prototype.play = originalPlayFn;
}); });
test("plays a sound when entering a call", () => { /**
* We don't want to play a sound when loading the call state
* because typically this occurs in two stages. We first join
* the call as a local participant and *then* the remote
* participants join from our perspective. We don't want to make
* a noise every time.
*/
test("does NOT play a sound when entering a call", () => {
const audioIsPlaying: string[] = mockMediaPlay(); const audioIsPlaying: string[] = mockMediaPlay();
const members = new Map([alice, bob].map((p) => [p.userId, p])); const members = new Map([alice, bob].map((p) => [p.userId, p]));
const remoteParticipants = of([aliceParticipant]); const remoteParticipants = of([aliceParticipant]);
@@ -75,10 +82,7 @@ test("plays a sound when entering a call", () => {
); );
render(<CallEventAudioRenderer vm={vm} />); render(<CallEventAudioRenderer vm={vm} />);
expect(audioIsPlaying).toEqual([ expect(audioIsPlaying).toHaveLength(0);
// Joining the call
enterSound,
]);
}); });
test("plays no sound when muted", () => { test("plays no sound when muted", () => {
@@ -141,8 +145,6 @@ test("plays a sound when a user joins", () => {
}); });
// Play a sound when joining a call. // Play a sound when joining a call.
expect(audioIsPlaying).toEqual([ expect(audioIsPlaying).toEqual([
// Joining the call
enterSound,
// Bob leaves // Bob leaves
enterSound, enterSound,
]); ]);
@@ -178,14 +180,12 @@ test("plays a sound when a user leaves", () => {
liveKitRoom.removeParticipant(aliceParticipant); liveKitRoom.removeParticipant(aliceParticipant);
}); });
expect(audioIsPlaying).toEqual([ expect(audioIsPlaying).toEqual([
// Joining the call
enterSound,
// Alice leaves // Alice leaves
leaveSound, leaveSound,
]); ]);
}); });
test("plays no sound when the participant list", () => { test("plays no sound when the participant list is more than the maximum size", () => {
const audioIsPlaying: string[] = mockMediaPlay(); const audioIsPlaying: string[] = mockMediaPlay();
const members = new Map([alice].map((p) => [p.userId, p])); const members = new Map([alice].map((p) => [p.userId, p]));
const remoteParticipants = new Map<string, RemoteParticipant>([ const remoteParticipants = new Map<string, RemoteParticipant>([

View File

@@ -5,14 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details. Please see LICENSE in the repository root for full details.
*/ */
import { ReactNode, useEffect } from "react"; import { ReactNode, useDeferredValue, useEffect, useMemo } from "react";
import { debounce, filter, interval, throttle } from "rxjs"; import { debounce, filter, interval, throttle } from "rxjs";
import { CallViewModel } from "../state/CallViewModel"; import { CallViewModel } from "../state/CallViewModel";
import joinCallSoundMp3 from "../sound/join_call.mp3"; import joinCallSoundMp3 from "../sound/join_call.mp3";
import joinCallSoundOgg from "../sound/join_call.ogg"; import joinCallSoundOgg from "../sound/join_call.ogg";
import leftCallSoundMp3 from "../sound/left_call.mp3"; import leftCallSoundMp3 from "../sound/left_call.mp3";
import leftCallSoundOgg from "../sound/left_call.ogg"; import leftCallSoundOgg from "../sound/left_call.ogg";
import handSoundOgg from "../sound/raise_hand.ogg?url";
import handSoundMp3 from "../sound/raise_hand.mp3?url";
import { prefetchSounds, useAudioContext } from "../useAudioContext"; import { prefetchSounds, useAudioContext } from "../useAudioContext";
import { useReactions } from "../useReactions";
// Do not play any sounds if the participant count has exceeded this // Do not play any sounds if the participant count has exceeded this
// number. // number.
@@ -29,6 +32,10 @@ const Sounds = prefetchSounds({
mp3: leftCallSoundMp3, mp3: leftCallSoundMp3,
ogg: leftCallSoundOgg, ogg: leftCallSoundOgg,
}, },
raiseHand: {
mp3: handSoundMp3,
ogg: handSoundOgg,
},
}); });
export function CallEventAudioRenderer({ export function CallEventAudioRenderer({
@@ -41,6 +48,19 @@ export function CallEventAudioRenderer({
latencyHint: "interactive", latencyHint: "interactive",
}); });
const { raisedHands } = useReactions();
const raisedHandCount = useMemo(
() => Object.keys(raisedHands).length,
[raisedHands],
);
const previousRaisedHandCount = useDeferredValue(raisedHandCount);
useEffect(() => {
if (audioEngineCtx && previousRaisedHandCount < raisedHandCount) {
audioEngineCtx.playSound("raiseHand");
}
}, [audioEngineCtx, previousRaisedHandCount, raisedHandCount]);
useEffect(() => { useEffect(() => {
if (!audioEngineCtx) { if (!audioEngineCtx) {
return; return;

View File

@@ -19,7 +19,6 @@ import {
TouchEvent, TouchEvent,
forwardRef, forwardRef,
useCallback, useCallback,
useDeferredValue,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
@@ -81,11 +80,8 @@ import { makeSpotlightLandscapeLayout } from "../grid/SpotlightLandscapeLayout";
import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout"; import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout";
import { GridTileViewModel, TileViewModel } from "../state/TileViewModel"; import { GridTileViewModel, TileViewModel } from "../state/TileViewModel";
import { ReactionsProvider, useReactions } from "../useReactions"; import { ReactionsProvider, useReactions } from "../useReactions";
import handSoundOgg from "../sound/raise_hand.ogg?url";
import handSoundMp3 from "../sound/raise_hand.mp3?url";
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer"; import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
import { useSwitchCamera } from "./useSwitchCamera"; import { useSwitchCamera } from "./useSwitchCamera";
import { soundEffectVolumeSetting, useSetting } from "../settings/settings";
import { ReactionsOverlay } from "./ReactionsOverlay"; import { ReactionsOverlay } from "./ReactionsOverlay";
import { CallEventAudioRenderer } from "./CallEventAudioRenderer"; import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
@@ -183,14 +179,7 @@ export const InCallView: FC<InCallViewProps> = ({
connState, connState,
onShareClick, onShareClick,
}) => { }) => {
const [soundEffectVolume] = useSetting(soundEffectVolumeSetting); const { supportsReactions, sendReaction, toggleRaisedHand } = useReactions();
const { supportsReactions, raisedHands, sendReaction, toggleRaisedHand } =
useReactions();
const raisedHandCount = useMemo(
() => Object.keys(raisedHands).length,
[raisedHands],
);
const previousRaisedHandCount = useDeferredValue(raisedHandCount);
useWakeLock(); useWakeLock();
@@ -340,25 +329,6 @@ export const InCallView: FC<InCallViewProps> = ({
[vm], [vm],
); );
// Play a sound when the raised hand count increases.
const handRaisePlayer = useRef<HTMLAudioElement>(null);
useEffect(() => {
if (!handRaisePlayer.current) {
return;
}
if (previousRaisedHandCount < raisedHandCount) {
handRaisePlayer.current.volume = soundEffectVolume;
handRaisePlayer.current.play().catch((ex) => {
logger.warn("Failed to play raise hand sound", ex);
});
}
}, [
raisedHandCount,
handRaisePlayer,
previousRaisedHandCount,
soundEffectVolume,
]);
useEffect(() => { useEffect(() => {
widget?.api.transport widget?.api.transport
.send( .send(
@@ -672,10 +642,6 @@ export const InCallView: FC<InCallViewProps> = ({
<RoomAudioRenderer /> <RoomAudioRenderer />
{renderContent()} {renderContent()}
<CallEventAudioRenderer vm={vm} /> <CallEventAudioRenderer vm={vm} />
<audio ref={handRaisePlayer} preload="auto" hidden>
<source src={handSoundOgg} type="audio/ogg; codecs=vorbis" />
<source src={handSoundMp3} type="audio/mpeg" />
</audio>
<ReactionsAudioRenderer /> <ReactionsAudioRenderer />
<ReactionsOverlay /> <ReactionsOverlay />
{footer} {footer}