Add support for playing a sound when the user exits a call. (#2860)
* Refactor to use AudioContext * Remove unused audio format. * Reduce update frequency for volume * Port to useAudioContext * Port reactionaudiorenderer to useAudioContext * Integrate raise hand sound into call event renderer. * Simplify reaction sounds * only play one sound per reaction type * Start to build out tests * fixup tests / comments * Fix reaction sound * remove console line * Remove another debug line. * fix lint * Use testing library click * lint * Add support for playing a sound when the user exits a call. * Port GroupCallView to useAudioContext * Remove debug bits. * asyncify * lint * lint * lint * tidy * Add test for group call view * Test widget mode too. * fix ?. * Format * Lint * Lint --------- Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
This commit is contained in:
@@ -26,7 +26,11 @@ import { Heading, Text } from "@vector-im/compound-web";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||
import { widget, ElementWidgetActions, type JoinCallData } from "../widget";
|
||||
import {
|
||||
ElementWidgetActions,
|
||||
type JoinCallData,
|
||||
type WidgetHelpers,
|
||||
} from "../widget";
|
||||
import { FullScreenView } from "../FullScreenView";
|
||||
import { LobbyView } from "./LobbyView";
|
||||
import { type MatrixInfo } from "./VideoPreview";
|
||||
@@ -51,6 +55,9 @@ import { InviteModal } from "./InviteModal";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { Link } from "../button/Link";
|
||||
import { useAudioContext } from "../useAudioContext";
|
||||
import { callEventAudioSounds } from "./CallEventAudioRenderer";
|
||||
import { useLatest } from "../useLatest";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -67,6 +74,7 @@ interface Props {
|
||||
hideHeader: boolean;
|
||||
rtcSession: MatrixRTCSession;
|
||||
muteStates: MuteStates;
|
||||
widget: WidgetHelpers | null;
|
||||
}
|
||||
|
||||
export const GroupCallView: FC<Props> = ({
|
||||
@@ -78,10 +86,16 @@ export const GroupCallView: FC<Props> = ({
|
||||
hideHeader,
|
||||
rtcSession,
|
||||
muteStates,
|
||||
widget,
|
||||
}) => {
|
||||
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
||||
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
|
||||
|
||||
const leaveSoundContext = useLatest(
|
||||
useAudioContext({
|
||||
sounds: callEventAudioSounds,
|
||||
latencyHint: "interactive",
|
||||
}),
|
||||
);
|
||||
// This should use `useEffectEvent` (only available in experimental versions)
|
||||
useEffect(() => {
|
||||
if (memberships.length >= MUTE_PARTICIPANT_COUNT)
|
||||
@@ -195,14 +209,14 @@ export const GroupCallView: FC<Props> = ({
|
||||
ev.detail.data as unknown as JoinCallData,
|
||||
);
|
||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
widget!.api.transport.reply(ev.detail, {});
|
||||
widget.api.transport.reply(ev.detail, {});
|
||||
})().catch((e) => {
|
||||
logger.error("Error joining RTC session", e);
|
||||
});
|
||||
};
|
||||
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
|
||||
return (): void => {
|
||||
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||
widget.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||
};
|
||||
} else {
|
||||
// No lobby and no preload: we enter the rtc session right away
|
||||
@@ -216,7 +230,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
void enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
}
|
||||
}
|
||||
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
|
||||
}, [widget, rtcSession, preload, skipLobby, perParticipantE2EE]);
|
||||
|
||||
const [left, setLeft] = useState(false);
|
||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||
@@ -224,12 +238,12 @@ export const GroupCallView: FC<Props> = ({
|
||||
|
||||
const onLeave = useCallback(
|
||||
(leaveError?: Error): void => {
|
||||
setLeaveError(leaveError);
|
||||
setLeft(true);
|
||||
|
||||
const audioPromise = leaveSoundContext.current?.playSound("left");
|
||||
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
||||
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||
const sendInstantly = !!widget;
|
||||
setLeaveError(leaveError);
|
||||
setLeft(true);
|
||||
PosthogAnalytics.instance.eventCallEnded.track(
|
||||
rtcSession.room.roomId,
|
||||
rtcSession.memberships.length,
|
||||
@@ -237,8 +251,12 @@ export const GroupCallView: FC<Props> = ({
|
||||
rtcSession,
|
||||
);
|
||||
|
||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||
leaveRTCSession(rtcSession)
|
||||
leaveRTCSession(
|
||||
rtcSession,
|
||||
// Wait for the sound in widget mode (it's not long)
|
||||
sendInstantly && audioPromise ? audioPromise : undefined,
|
||||
)
|
||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||
.then(() => {
|
||||
if (
|
||||
!isPasswordlessUser &&
|
||||
@@ -252,18 +270,25 @@ export const GroupCallView: FC<Props> = ({
|
||||
logger.error("Error leaving RTC session", e);
|
||||
});
|
||||
},
|
||||
[rtcSession, isPasswordlessUser, confineToRoom, history],
|
||||
[
|
||||
widget,
|
||||
rtcSession,
|
||||
isPasswordlessUser,
|
||||
confineToRoom,
|
||||
leaveSoundContext,
|
||||
history,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (widget && isJoined) {
|
||||
// set widget to sticky once joined.
|
||||
widget!.api.setAlwaysOnScreen(true).catch((e) => {
|
||||
widget.api.setAlwaysOnScreen(true).catch((e) => {
|
||||
logger.error("Error calling setAlwaysOnScreen(true)", e);
|
||||
});
|
||||
|
||||
const onHangup = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
widget!.api.transport.reply(ev.detail, {});
|
||||
widget.api.transport.reply(ev.detail, {});
|
||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||
leaveRTCSession(rtcSession).catch((e) => {
|
||||
logger.error("Failed to leave RTC session", e);
|
||||
@@ -271,10 +296,10 @@ export const GroupCallView: FC<Props> = ({
|
||||
};
|
||||
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
||||
return (): void => {
|
||||
widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
||||
widget.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
||||
};
|
||||
}
|
||||
}, [isJoined, rtcSession]);
|
||||
}, [widget, isJoined, rtcSession]);
|
||||
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
@@ -367,14 +392,17 @@ export const GroupCallView: FC<Props> = ({
|
||||
leaveError
|
||||
) {
|
||||
return (
|
||||
<CallEndedView
|
||||
endedCallId={rtcSession.room.roomId}
|
||||
client={client}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
confineToRoom={confineToRoom}
|
||||
leaveError={leaveError}
|
||||
reconnect={onReconnect}
|
||||
/>
|
||||
<>
|
||||
<CallEndedView
|
||||
endedCallId={rtcSession.room.roomId}
|
||||
client={client}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
confineToRoom={confineToRoom}
|
||||
leaveError={leaveError}
|
||||
reconnect={onReconnect}
|
||||
/>
|
||||
;
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
// If the user is a regular user, we'll have sent them back to the homepage,
|
||||
|
||||
Reference in New Issue
Block a user