mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-18 18:59:23 +00:00
Refactor onLeave to take a sound so we don't need to repeat the sound
This commit is contained in:
@@ -16,6 +16,10 @@ import handSoundOgg from "../sound/raise_hand.ogg";
|
|||||||
import handSoundMp3 from "../sound/raise_hand.mp3";
|
import handSoundMp3 from "../sound/raise_hand.mp3";
|
||||||
import screenShareStartedOgg from "../sound/screen_share_started.ogg";
|
import screenShareStartedOgg from "../sound/screen_share_started.ogg";
|
||||||
import screenShareStartedMp3 from "../sound/screen_share_started.mp3";
|
import screenShareStartedMp3 from "../sound/screen_share_started.mp3";
|
||||||
|
import declineMp3 from "../sound/call_declined.mp3?url";
|
||||||
|
import declineOgg from "../sound/call_declined.ogg?url";
|
||||||
|
import timeoutMp3 from "../sound/call_timeout.mp3?url";
|
||||||
|
import timeoutOgg from "../sound/call_timeout.ogg?url";
|
||||||
import { useAudioContext } from "../useAudioContext";
|
import { useAudioContext } from "../useAudioContext";
|
||||||
import { prefetchSounds } from "../soundUtils";
|
import { prefetchSounds } from "../soundUtils";
|
||||||
import { useLatest } from "../useLatest";
|
import { useLatest } from "../useLatest";
|
||||||
@@ -37,8 +41,18 @@ export const callEventAudioSounds = prefetchSounds({
|
|||||||
mp3: screenShareStartedMp3,
|
mp3: screenShareStartedMp3,
|
||||||
ogg: screenShareStartedOgg,
|
ogg: screenShareStartedOgg,
|
||||||
},
|
},
|
||||||
|
decline: {
|
||||||
|
mp3: declineMp3,
|
||||||
|
ogg: declineOgg,
|
||||||
|
},
|
||||||
|
timeout: {
|
||||||
|
mp3: timeoutMp3,
|
||||||
|
ogg: timeoutOgg,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type CallEventSounds = keyof Awaited<typeof callEventAudioSounds>;
|
||||||
|
|
||||||
export function CallEventAudioRenderer({
|
export function CallEventAudioRenderer({
|
||||||
vm,
|
vm,
|
||||||
muted,
|
muted,
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ beforeEach(() => {
|
|||||||
({ onLeave }) => {
|
({ onLeave }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => onLeave()}>Leave</button>
|
<button onClick={() => onLeave("user")}>Leave</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ import { InviteModal } from "./InviteModal";
|
|||||||
import { HeaderStyle, type UrlParams, useUrlParams } from "../UrlParams";
|
import { HeaderStyle, type UrlParams, useUrlParams } from "../UrlParams";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { useAudioContext } from "../useAudioContext";
|
import { useAudioContext } from "../useAudioContext";
|
||||||
import { callEventAudioSounds } from "./CallEventAudioRenderer";
|
import {
|
||||||
|
callEventAudioSounds,
|
||||||
|
type CallEventSounds,
|
||||||
|
} from "./CallEventAudioRenderer";
|
||||||
import { useLatest } from "../useLatest";
|
import { useLatest } from "../useLatest";
|
||||||
import { usePageTitle } from "../usePageTitle";
|
import { usePageTitle } from "../usePageTitle";
|
||||||
import {
|
import {
|
||||||
@@ -317,8 +320,11 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onLeave = useCallback(
|
const onLeave = useCallback(
|
||||||
(cause: "user" | "error" = "user"): void => {
|
(
|
||||||
const audioPromise = leaveSoundContext.current?.playSound("left");
|
cause: "user" | "error" = "user",
|
||||||
|
playSound: CallEventSounds = "left",
|
||||||
|
): void => {
|
||||||
|
const audioPromise = leaveSoundContext.current?.playSound(playSound);
|
||||||
// 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;
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ import {
|
|||||||
} from "../reactions/useReactionsSender";
|
} from "../reactions/useReactionsSender";
|
||||||
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
||||||
import { ReactionsOverlay } from "./ReactionsOverlay";
|
import { ReactionsOverlay } from "./ReactionsOverlay";
|
||||||
import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
|
import {
|
||||||
|
CallEventAudioRenderer,
|
||||||
|
CallEventSounds,
|
||||||
|
} from "./CallEventAudioRenderer";
|
||||||
import {
|
import {
|
||||||
debugTileLayout as debugTileLayoutSetting,
|
debugTileLayout as debugTileLayoutSetting,
|
||||||
useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting,
|
useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting,
|
||||||
@@ -119,10 +122,6 @@ import { prefetchSounds } from "../soundUtils";
|
|||||||
import { useAudioContext } from "../useAudioContext";
|
import { useAudioContext } from "../useAudioContext";
|
||||||
import ringtoneMp3 from "../sound/ringtone.mp3?url";
|
import ringtoneMp3 from "../sound/ringtone.mp3?url";
|
||||||
import ringtoneOgg from "../sound/ringtone.ogg?url";
|
import ringtoneOgg from "../sound/ringtone.ogg?url";
|
||||||
import declineMp3 from "../sound/call_declined.mp3?url";
|
|
||||||
import declineOgg from "../sound/call_declined.ogg?url";
|
|
||||||
import timeoutMp3 from "../sound/call_timeout.mp3?url";
|
|
||||||
import timeoutOgg from "../sound/call_timeout.ogg?url";
|
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
|
|
||||||
@@ -233,7 +232,7 @@ export interface InCallViewProps {
|
|||||||
livekitRoom: LivekitRoom;
|
livekitRoom: LivekitRoom;
|
||||||
muteStates: MuteStates;
|
muteStates: MuteStates;
|
||||||
/** Function to call when the user explicitly ends the call */
|
/** Function to call when the user explicitly ends the call */
|
||||||
onLeave: () => void;
|
onLeave: (cause: "user", soundFile?: CallEventSounds) => void;
|
||||||
header: HeaderStyle;
|
header: HeaderStyle;
|
||||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||||
connState: ECConnectionState;
|
connState: ECConnectionState;
|
||||||
@@ -283,8 +282,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
const pickupPhaseSoundCache = useInitial(async () => {
|
const pickupPhaseSoundCache = useInitial(async () => {
|
||||||
return prefetchSounds({
|
return prefetchSounds({
|
||||||
waiting: { mp3: ringtoneMp3, ogg: ringtoneOgg },
|
waiting: { mp3: ringtoneMp3, ogg: ringtoneOgg },
|
||||||
decline: { mp3: declineMp3, ogg: declineOgg },
|
|
||||||
timeout: { mp3: timeoutMp3, ogg: timeoutOgg },
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -354,7 +351,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
const showFooter = useBehavior(vm.showFooter$);
|
const showFooter = useBehavior(vm.showFooter$);
|
||||||
const earpieceMode = useBehavior(vm.earpieceMode$);
|
const earpieceMode = useBehavior(vm.earpieceMode$);
|
||||||
const audioOutputSwitcher = useBehavior(vm.audioOutputSwitcher$);
|
const audioOutputSwitcher = useBehavior(vm.audioOutputSwitcher$);
|
||||||
useSubscription(vm.autoLeave$, onLeave);
|
useSubscription(vm.autoLeave$, () => onLeave("user"));
|
||||||
|
|
||||||
// We need to set the proper timings on the animation based upon the sound length.
|
// We need to set the proper timings on the animation based upon the sound length.
|
||||||
const ringDuration = pickupPhaseAudio?.soundDuration["waiting"] ?? 1;
|
const ringDuration = pickupPhaseAudio?.soundDuration["waiting"] ?? 1;
|
||||||
@@ -378,25 +375,10 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
// When we enter timeout or decline we will leave the call.
|
// When we enter timeout or decline we will leave the call.
|
||||||
useEffect((): void | (() => void) => {
|
useEffect((): void | (() => void) => {
|
||||||
if (callPickupState === "timeout") {
|
if (callPickupState === "timeout") {
|
||||||
void pickupPhaseAudio
|
onLeave("user", "timeout");
|
||||||
?.playSound("timeout")
|
|
||||||
.catch((e) => {
|
|
||||||
logger.error("Failed to play timeout sound", e);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
onLeave();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (callPickupState === "decline") {
|
if (callPickupState === "decline") {
|
||||||
// Wait for the sound to finish before leaving
|
onLeave("user", "decline");
|
||||||
void pickupPhaseAudio
|
|
||||||
?.playSound("decline")
|
|
||||||
.catch((e) => {
|
|
||||||
logger.error("Failed to play decline sound", e);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
onLeave();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [callPickupState, onLeave, pickupPhaseAudio]);
|
}, [callPickupState, onLeave, pickupPhaseAudio]);
|
||||||
|
|
||||||
@@ -849,7 +831,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
<EndCallButton
|
<EndCallButton
|
||||||
key="end_call"
|
key="end_call"
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
onLeave();
|
onLeave("user");
|
||||||
}}
|
}}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
onTouchEnd={onControlsTouchEnd}
|
||||||
data-testid="incall_leave"
|
data-testid="incall_leave"
|
||||||
|
|||||||
@@ -980,6 +980,7 @@ export class CallViewModel extends ViewModel {
|
|||||||
this.userMedia$,
|
this.userMedia$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
// Until the call is successful, do not play a leave sound.
|
// Until the call is successful, do not play a leave sound.
|
||||||
|
// If callPickupState$ is null, then we always play the sound as it will not conflict with a decline sound.
|
||||||
skipWhile(([c]) => c !== null && c !== "success"),
|
skipWhile(([c]) => c !== null && c !== "success"),
|
||||||
map(([, userMedia]) => userMedia),
|
map(([, userMedia]) => userMedia),
|
||||||
pairwise(),
|
pairwise(),
|
||||||
|
|||||||
Reference in New Issue
Block a user