mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Add support for playing a sound when the user exits a call.
This commit is contained in:
@@ -42,6 +42,9 @@ import { useUrlParams } from "../UrlParams";
|
|||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { Link } from "../button/Link";
|
import { Link } from "../button/Link";
|
||||||
|
|
||||||
|
import leftCallSoundMp3 from "../sound/left_call.mp3";
|
||||||
|
import leftCallSoundOgg from "../sound/left_call.ogg";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
rtcSession?: MatrixRTCSession;
|
rtcSession?: MatrixRTCSession;
|
||||||
@@ -72,6 +75,22 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
||||||
const isJoined = useMatrixRTCSessionJoinState(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)
|
// This should use `useEffectEvent` (only available in experimental versions)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (memberships.length >= MUTE_PARTICIPANT_COUNT)
|
if (memberships.length >= MUTE_PARTICIPANT_COUNT)
|
||||||
@@ -214,12 +233,11 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
|
|
||||||
const onLeave = useCallback(
|
const onLeave = useCallback(
|
||||||
(leaveError?: Error): void => {
|
(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,
|
// 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.
|
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||||
const sendInstantly = !!widget;
|
const sendInstantly = !!widget;
|
||||||
|
console.log('hangup!', sendInstantly);
|
||||||
|
setLeaveError(leaveError);
|
||||||
PosthogAnalytics.instance.eventCallEnded.track(
|
PosthogAnalytics.instance.eventCallEnded.track(
|
||||||
rtcSession.room.roomId,
|
rtcSession.room.roomId,
|
||||||
rtcSession.memberships.length,
|
rtcSession.memberships.length,
|
||||||
@@ -227,20 +245,22 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
rtcSession,
|
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.
|
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||||
leaveRTCSession(rtcSession)
|
.then(() => {
|
||||||
.then(() => {
|
setLeft(true);
|
||||||
if (
|
if (
|
||||||
!isPasswordlessUser &&
|
!isPasswordlessUser &&
|
||||||
!confineToRoom &&
|
!confineToRoom &&
|
||||||
!PosthogAnalytics.instance.isEnabled()
|
!PosthogAnalytics.instance.isEnabled()
|
||||||
) {
|
) {
|
||||||
history.push("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
logger.error("Error leaving RTC session", e);
|
logger.error("Error leaving RTC session", e);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[rtcSession, isPasswordlessUser, confineToRoom, history],
|
[rtcSession, isPasswordlessUser, confineToRoom, history],
|
||||||
);
|
);
|
||||||
@@ -308,6 +328,14 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
onDismiss={onDismissInviteModal}
|
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 = (
|
const lobbyView = (
|
||||||
<>
|
<>
|
||||||
{shareModal}
|
{shareModal}
|
||||||
@@ -321,6 +349,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
participantCount={participantCount}
|
participantCount={participantCount}
|
||||||
onShareClick={onShareClick}
|
onShareClick={onShareClick}
|
||||||
/>
|
/>
|
||||||
|
{callEndedAudio}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -340,6 +369,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
//otelGroupCallMembership={otelGroupCallMembership}
|
//otelGroupCallMembership={otelGroupCallMembership}
|
||||||
onShareClick={onShareClick}
|
onShareClick={onShareClick}
|
||||||
/>
|
/>
|
||||||
|
{callEndedAudio}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (left && widget === null) {
|
} else if (left && widget === null) {
|
||||||
@@ -357,14 +387,22 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
leaveError
|
leaveError
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<CallEndedView
|
<>
|
||||||
endedCallId={rtcSession.room.roomId}
|
<CallEndedView
|
||||||
client={client}
|
endedCallId={rtcSession.room.roomId}
|
||||||
isPasswordlessUser={isPasswordlessUser}
|
client={client}
|
||||||
confineToRoom={confineToRoom}
|
isPasswordlessUser={isPasswordlessUser}
|
||||||
leaveError={leaveError}
|
confineToRoom={confineToRoom}
|
||||||
reconnect={onReconnect}
|
leaveError={leaveError}
|
||||||
/>
|
reconnect={onReconnect}
|
||||||
|
/><audio
|
||||||
|
autoPlay
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<source src={leftCallSoundOgg} type="audio/ogg; codecs=vorbis" />
|
||||||
|
<source src={leftCallSoundMp3} type="audio/mpeg" />
|
||||||
|
</audio>;
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If the user is a regular user, we'll have sent them back to the homepage,
|
// If the user is a regular user, we'll have sent them back to the homepage,
|
||||||
@@ -375,7 +413,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
} else if (left && widget !== null) {
|
} else if (left && widget !== null) {
|
||||||
// Left in widget mode:
|
// Left in widget mode:
|
||||||
if (!returnToLobby) {
|
if (!returnToLobby) {
|
||||||
return null;
|
return callEndedAudio;
|
||||||
}
|
}
|
||||||
} else if (preload || skipLobby) {
|
} else if (preload || skipLobby) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export async function enterRTCSession(
|
|||||||
|
|
||||||
const widgetPostHangupProcedure = async (
|
const widgetPostHangupProcedure = async (
|
||||||
widget: WidgetHelpers,
|
widget: WidgetHelpers,
|
||||||
|
promiseBeforeHangup?: Promise<unknown>,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
// we need to wait until the callEnded event is tracked on posthog.
|
// we need to wait until the callEnded event is tracked on posthog.
|
||||||
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
// 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);
|
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
|
// We send the hangup event after the memberships have been updated
|
||||||
// calling leaveRTCSession.
|
// calling leaveRTCSession.
|
||||||
// We need to wait because this makes the client hosting this widget killing the IFrame.
|
// 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(
|
export async function leaveRTCSession(
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
|
promiseBeforeHangup?: Promise<unknown>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await rtcSession.leaveRoomSession();
|
await rtcSession.leaveRoomSession();
|
||||||
if (widget) {
|
if (widget) {
|
||||||
await widgetPostHangupProcedure(widget);
|
await widgetPostHangupProcedure(widget, promiseBeforeHangup);
|
||||||
|
} else {
|
||||||
|
await promiseBeforeHangup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user