Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for playing a sound when the user exits a call. #2860

Draft
wants to merge 1 commit into
base: livekit
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 63 additions & 25 deletions src/room/GroupCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import { useUrlParams } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType";
import { Link } from "../button/Link";

import leftCallSoundMp3 from "../sound/left_call.mp3";
import leftCallSoundOgg from "../sound/left_call.ogg";

declare global {
interface Window {
rtcSession?: MatrixRTCSession;
Expand Down Expand Up @@ -72,6 +75,22 @@ export const GroupCallView: FC<Props> = ({
const memberships = useMatrixRTCSessionMemberships(rtcSession);
const isJoined = useMatrixRTCSessionJoinState(rtcSession);

// Only used in widget mode.
const leaveSound = useRef<HTMLAudioElement|null>(null);
const playLeaveSound = useCallback(async () => {
if (!leaveSound.current) {
return;
}
const ended = new Promise<void>(r => {
leaveSound.current?.addEventListener("ended", () => r());
});
console.log('Playing');
await leaveSound.current.play();
console.log('started playing');
await ended;
console.log('ended');
}, [leaveSound]);

// This should use `useEffectEvent` (only available in experimental versions)
useEffect(() => {
if (memberships.length >= MUTE_PARTICIPANT_COUNT)
Expand Down Expand Up @@ -214,33 +233,34 @@ export const GroupCallView: FC<Props> = ({

const onLeave = useCallback(
(leaveError?: Error): void => {
setLeaveError(leaveError);
setLeft(true);

// 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;
console.log('hangup!', sendInstantly);
setLeaveError(leaveError);
PosthogAnalytics.instance.eventCallEnded.track(
rtcSession.room.roomId,
rtcSession.memberships.length,
sendInstantly,
rtcSession,
);

// Wait for the sound in widget mode (it's not long)
leaveRTCSession(rtcSession, sendInstantly ? playLeaveSound() : undefined)
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
leaveRTCSession(rtcSession)
.then(() => {
if (
!isPasswordlessUser &&
!confineToRoom &&
!PosthogAnalytics.instance.isEnabled()
) {
history.push("/");
}
})
.catch((e) => {
logger.error("Error leaving RTC session", e);
});
.then(() => {
setLeft(true);
if (
!isPasswordlessUser &&
!confineToRoom &&
!PosthogAnalytics.instance.isEnabled()
) {
history.push("/");
}
})
.catch((e) => {
logger.error("Error leaving RTC session", e);
});
},
[rtcSession, isPasswordlessUser, confineToRoom, history],
);
Expand Down Expand Up @@ -308,6 +328,14 @@ export const GroupCallView: FC<Props> = ({
onDismiss={onDismissInviteModal}
/>
);
const callEndedAudio = <audio
ref={leaveSound}
preload="auto"
hidden
>
<source src={leftCallSoundOgg} type="audio/ogg; codecs=vorbis" />
<source src={leftCallSoundMp3} type="audio/mpeg" />
</audio>;
const lobbyView = (
<>
{shareModal}
Expand All @@ -321,6 +349,7 @@ export const GroupCallView: FC<Props> = ({
participantCount={participantCount}
onShareClick={onShareClick}
/>
{callEndedAudio}
</>
);

Expand All @@ -340,6 +369,7 @@ export const GroupCallView: FC<Props> = ({
//otelGroupCallMembership={otelGroupCallMembership}
onShareClick={onShareClick}
/>
{callEndedAudio}
</>
);
} else if (left && widget === null) {
Expand All @@ -357,14 +387,22 @@ 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}
/><audio
autoPlay
hidden
>
<source src={leftCallSoundOgg} type="audio/ogg; codecs=vorbis" />
<source src={leftCallSoundMp3} type="audio/mpeg" />
</audio>;
</>
);
} else {
// If the user is a regular user, we'll have sent them back to the homepage,
Expand All @@ -375,7 +413,7 @@ export const GroupCallView: FC<Props> = ({
} else if (left && widget !== null) {
// Left in widget mode:
if (!returnToLobby) {
return null;
return callEndedAudio;
}
} else if (preload || skipLobby) {
return null;
Expand Down
8 changes: 7 additions & 1 deletion src/rtcSessionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export async function enterRTCSession(

const widgetPostHangupProcedure = async (
widget: WidgetHelpers,
promiseBeforeHangup?: Promise<unknown>,
): Promise<void> => {
// we need to wait until the callEnded event is tracked on posthog.
// Otherwise the iFrame gets killed before the callEnded event got tracked.
Expand All @@ -132,6 +133,8 @@ const widgetPostHangupProcedure = async (
logger.error("Failed to set call widget `alwaysOnScreen` to false", e);
}

// Wait for any last bits before hanging up.
await promiseBeforeHangup;
// We send the hangup event after the memberships have been updated
// calling leaveRTCSession.
// We need to wait because this makes the client hosting this widget killing the IFrame.
Expand All @@ -140,9 +143,12 @@ const widgetPostHangupProcedure = async (

export async function leaveRTCSession(
rtcSession: MatrixRTCSession,
promiseBeforeHangup?: Promise<unknown>,
): Promise<void> {
await rtcSession.leaveRoomSession();
if (widget) {
await widgetPostHangupProcedure(widget);
await widgetPostHangupProcedure(widget, promiseBeforeHangup);
} else {
await promiseBeforeHangup;
}
}
Loading