From b16ba52e137292736ee7bd6d7f742e0fe36cfa24 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 16 Sep 2025 10:20:06 +0100 Subject: [PATCH] Refactor disconnection handling --- src/room/InCallView.test.tsx | 3 +-- src/room/InCallView.tsx | 16 ++++------------ src/state/CallViewModel.ts | 28 ++++++++++++++++++++-------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/room/InCallView.test.tsx b/src/room/InCallView.test.tsx index eb192f66..f20ffada 100644 --- a/src/room/InCallView.test.tsx +++ b/src/room/InCallView.test.tsx @@ -17,7 +17,7 @@ import { act, render, type RenderResult } from "@testing-library/react"; import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk"; import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container"; -import { ConnectionState, type LocalParticipant } from "livekit-client"; +import { type LocalParticipant } from "livekit-client"; import { of } from "rxjs"; import { BrowserRouter } from "react-router-dom"; import { TooltipProvider } from "@vector-im/compound-web"; @@ -180,7 +180,6 @@ function createInCallView(): RenderResult & { onLeave={function (): void { throw new Error("Function not implemented."); }} - connState={ConnectionState.Connected} onShareClick={null} /> diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 3d7044be..0b1a2be2 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. import { RoomContext, useLocalParticipant } from "@livekit/components-react"; import { IconButton, Text, Tooltip } from "@vector-im/compound-web"; -import { ConnectionState, type Room as LivekitRoom } from "livekit-client"; +import { type Room as LivekitRoom } from "livekit-client"; import { type MatrixClient, type Room as MatrixRoom } from "matrix-js-sdk"; import { type FC, @@ -63,7 +63,6 @@ import { type MuteStates } from "./MuteStates"; import { type MatrixInfo } from "./VideoPreview"; import { InviteButton } from "../button/InviteButton"; import { LayoutToggle } from "./LayoutToggle"; -import { type ECConnectionState } from "../livekit/useECConnectionState"; import { useOpenIDSFU } from "../livekit/openIDSFU"; import { CallViewModel, @@ -212,12 +211,7 @@ export const ActiveCall: FC = (props) => { return ( - + ); @@ -235,7 +229,6 @@ export interface InCallViewProps { onLeave: (cause: "user", soundFile?: CallEventSounds) => void; header: HeaderStyle; otelGroupCallMembership?: OTelGroupCallMembership; - connState: ECConnectionState; onShareClick: (() => void) | null; } @@ -249,7 +242,6 @@ export const InCallView: FC = ({ muteStates, onLeave, header: headerStyle, - connState, onShareClick, }) => { const { t } = useTranslation(); @@ -257,11 +249,11 @@ export const InCallView: FC = ({ useReactionsSender(); useWakeLock(); + const isDisconnected = useBehavior(vm.livekitDisconnected$); // annoyingly we don't get the disconnection reason this way, // only by listening for the emitted event - if (connState === ConnectionState.Disconnected) - throw new ConnectionLostError(); + if (isDisconnected) throw new ConnectionLostError(); const containerRef1 = useRef(null); const [containerRef2, bounds] = useMeasure(); diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index ca797aa8..8b8ce61e 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -465,6 +465,14 @@ export class CallViewModel extends ViewModel { ), ); + public readonly livekitDisconnected$ = this.scope.behavior( + and$( + this.livekitConnectionState$.pipe( + map((state) => state === ConnectionState.Disconnected), + ), + ), + ); + private readonly connected$ = this.scope.behavior( and$( this.matrixConnected$, @@ -957,15 +965,19 @@ export class CallViewModel extends ViewModel { "unknown" | "ringing" | "timeout" | "decline" | "success" | null > = this.options.waitForCallPickup ? this.scope.behavior< - "unknown" | "ringing" | "timeout" | "decline" | "success" + "unknown" | "ringing" | "timeout" | "decline" | "success" | null >( - this.someoneElseJoined$.pipe( - switchMap((someoneElseJoined) => - someoneElseJoined - ? of("success" as const) - : // Show the ringing state of the most recent ringing attempt. - this.ring$.pipe(switchAll()), - ), + combineLatest(this.livekitDisconnected$, this.someoneElseJoined$).pipe( + switchMap(([isDisconnected, someoneElseJoined]) => { + if (isDisconnected) { + // Do not ring until we're connected. + return of(null); + } else if (someoneElseJoined) { + of("success" as const); + } + // Show the ringing state of the most recent ringing attempt. + return this.ring$.pipe(switchAll()); + }), // The state starts as 'unknown' because we don't know if the RTC // session will actually send a notify event yet. It will only be // known once we send our own membership and see that we were the