diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index b0fa8c69..2e930f62 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -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; @@ -72,6 +75,22 @@ export const GroupCallView: FC = ({ const memberships = useMatrixRTCSessionMemberships(rtcSession); const isJoined = useMatrixRTCSessionJoinState(rtcSession); + // Only used in widget mode. + const leaveSound = useRef(null); + const playLeaveSound = useCallback(async () => { + if (!leaveSound.current) { + return; + } + const ended = new Promise(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) @@ -214,12 +233,11 @@ export const GroupCallView: FC = ({ 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, @@ -227,20 +245,22 @@ export const GroupCallView: FC = ({ 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], ); @@ -308,6 +328,14 @@ export const GroupCallView: FC = ({ onDismiss={onDismissInviteModal} /> ); + const callEndedAudio = ; const lobbyView = ( <> {shareModal} @@ -321,6 +349,7 @@ export const GroupCallView: FC = ({ participantCount={participantCount} onShareClick={onShareClick} /> + {callEndedAudio} ); @@ -340,6 +369,7 @@ export const GroupCallView: FC = ({ //otelGroupCallMembership={otelGroupCallMembership} onShareClick={onShareClick} /> + {callEndedAudio} ); } else if (left && widget === null) { @@ -357,14 +387,22 @@ export const GroupCallView: FC = ({ leaveError ) { return ( - + <> + ; + ); } else { // If the user is a regular user, we'll have sent them back to the homepage, @@ -375,7 +413,7 @@ export const GroupCallView: FC = ({ } else if (left && widget !== null) { // Left in widget mode: if (!returnToLobby) { - return null; + return callEndedAudio; } } else if (preload || skipLobby) { return null; diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 9e699319..e5d71e9a 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -120,6 +120,7 @@ export async function enterRTCSession( const widgetPostHangupProcedure = async ( widget: WidgetHelpers, + promiseBeforeHangup?: Promise, ): Promise => { // we need to wait until the callEnded event is tracked on posthog. // Otherwise the iFrame gets killed before the callEnded event got tracked. @@ -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. @@ -140,9 +143,12 @@ const widgetPostHangupProcedure = async ( export async function leaveRTCSession( rtcSession: MatrixRTCSession, + promiseBeforeHangup?: Promise, ): Promise { await rtcSession.leaveRoomSession(); if (widget) { - await widgetPostHangupProcedure(widget); + await widgetPostHangupProcedure(widget, promiseBeforeHangup); + } else { + await promiseBeforeHangup; } }