Skip to content

Commit 61203b8

Browse files
committed
Prevent showing calling view when disconnected from Livekit. (#3491)
* Refactor disconnection handling * Use "unknown" * Update signature * Add tests * Expose livekitConnectionState directly * fix whoopsie
1 parent 1be4218 commit 61203b8

4 files changed

Lines changed: 70 additions & 21 deletions

File tree

src/room/InCallView.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { act, render, type RenderResult } from "@testing-library/react";
1717
import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk";
1818
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
1919
import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container";
20-
import { ConnectionState, type LocalParticipant } from "livekit-client";
20+
import { type LocalParticipant } from "livekit-client";
2121
import { of } from "rxjs";
2222
import { BrowserRouter } from "react-router-dom";
2323
import { TooltipProvider } from "@vector-im/compound-web";
@@ -180,7 +180,6 @@ function createInCallView(): RenderResult & {
180180
onLeave={function (): void {
181181
throw new Error("Function not implemented.");
182182
}}
183-
connState={ConnectionState.Connected}
184183
onShareClick={null}
185184
/>
186185
</RoomContext>

src/room/InCallView.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import useMeasure from "react-use-measure";
2525
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
2626
import classNames from "classnames";
2727
import { BehaviorSubject, map } from "rxjs";
28-
import { useObservable, useSubscription } from "observable-hooks";
28+
import {
29+
useObservable,
30+
useObservableEagerState,
31+
useSubscription,
32+
} from "observable-hooks";
2933
import { logger } from "matrix-js-sdk/lib/logger";
3034
import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
3135
import {
@@ -63,7 +67,6 @@ import { type MuteStates } from "./MuteStates";
6367
import { type MatrixInfo } from "./VideoPreview";
6468
import { InviteButton } from "../button/InviteButton";
6569
import { LayoutToggle } from "./LayoutToggle";
66-
import { type ECConnectionState } from "../livekit/useECConnectionState";
6770
import { useOpenIDSFU } from "../livekit/openIDSFU";
6871
import {
6972
CallViewModel,
@@ -212,12 +215,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
212215
return (
213216
<RoomContext value={livekitRoom}>
214217
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
215-
<InCallView
216-
{...props}
217-
vm={vm}
218-
livekitRoom={livekitRoom}
219-
connState={connState}
220-
/>
218+
<InCallView {...props} vm={vm} livekitRoom={livekitRoom} />
221219
</ReactionsSenderProvider>
222220
</RoomContext>
223221
);
@@ -235,7 +233,6 @@ export interface InCallViewProps {
235233
onLeave: (cause: "user", soundFile?: CallEventSounds) => void;
236234
header: HeaderStyle;
237235
otelGroupCallMembership?: OTelGroupCallMembership;
238-
connState: ECConnectionState;
239236
onShareClick: (() => void) | null;
240237
}
241238

@@ -249,18 +246,18 @@ export const InCallView: FC<InCallViewProps> = ({
249246
muteStates,
250247
onLeave,
251248
header: headerStyle,
252-
connState,
253249
onShareClick,
254250
}) => {
255251
const { t } = useTranslation();
256252
const { supportsReactions, sendReaction, toggleRaisedHand } =
257253
useReactionsSender();
258254

259255
useWakeLock();
256+
const connectionState = useObservableEagerState(vm.livekitConnectionState$);
260257

261258
// annoyingly we don't get the disconnection reason this way,
262259
// only by listening for the emitted event
263-
if (connState === ConnectionState.Disconnected)
260+
if (connectionState === ConnectionState.Disconnected)
264261
throw new ConnectionLostError();
265262

266263
const containerRef1 = useRef<HTMLDivElement | null>(null);

src/state/CallViewModel.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,51 @@ describe("waitForCallPickup$", () => {
12911291
});
12921292
});
12931293

1294+
test("ringing -> unknown if we get disconnected", () => {
1295+
withTestScheduler(({ behavior, schedule, expectObservable }) => {
1296+
const connectionState$ = new BehaviorSubject(ConnectionState.Connected);
1297+
// Someone joins at 20ms (both LiveKit participant and MatrixRTC member)
1298+
withCallViewModel(
1299+
{
1300+
remoteParticipants$: behavior("a 19ms b", {
1301+
a: [],
1302+
b: [aliceParticipant],
1303+
}),
1304+
rtcMembers$: behavior("a 19ms b", {
1305+
a: [localRtcMember],
1306+
b: [localRtcMember, aliceRtcMember],
1307+
}),
1308+
connectionState$,
1309+
},
1310+
(vm, rtcSession) => {
1311+
// Notify at 5ms so we enter ringing, then get disconnected 5ms later
1312+
schedule(" 5ms r 5ms d", {
1313+
r: () => {
1314+
rtcSession.emit(
1315+
MatrixRTCSessionEvent.DidSendCallNotification,
1316+
mockRingEvent("$notif2", 100),
1317+
mockLegacyRingEvent,
1318+
);
1319+
},
1320+
d: () => {
1321+
connectionState$.next(ConnectionState.Disconnected);
1322+
},
1323+
});
1324+
1325+
expectObservable(vm.callPickupState$).toBe("a 4ms b 5ms c", {
1326+
a: "unknown",
1327+
b: "ringing",
1328+
c: "unknown",
1329+
});
1330+
},
1331+
{
1332+
waitForCallPickup: true,
1333+
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
1334+
},
1335+
);
1336+
});
1337+
});
1338+
12941339
test("success when someone joins before we notify", () => {
12951340
withTestScheduler(({ behavior, schedule, expectObservable }) => {
12961341
// Join at 10ms, notify later at 20ms (state should stay success)

src/state/CallViewModel.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,7 @@ export class CallViewModel extends ViewModel {
947947
* The current call pickup state of the call.
948948
* - "unknown": The client has not yet sent the notification event. We don't know if it will because it first needs to send its own membership.
949949
* Then we can conclude if we were the first one to join or not.
950+
* This may also be set if we are disconnected.
950951
* - "ringing": The call is ringing on other devices in this room (This client should give audiovisual feedback that this is happening).
951952
* - "timeout": No-one picked up in the defined time this call should be ringing on others devices.
952953
* The call failed. If desired this can be used as a trigger to exit the call.
@@ -959,13 +960,20 @@ export class CallViewModel extends ViewModel {
959960
? this.scope.behavior<
960961
"unknown" | "ringing" | "timeout" | "decline" | "success"
961962
>(
962-
this.someoneElseJoined$.pipe(
963-
switchMap((someoneElseJoined) =>
964-
someoneElseJoined
965-
? of("success" as const)
966-
: // Show the ringing state of the most recent ringing attempt.
967-
this.ring$.pipe(switchAll()),
968-
),
963+
combineLatest([
964+
this.livekitConnectionState$,
965+
this.someoneElseJoined$,
966+
]).pipe(
967+
switchMap(([livekitConnectionState, someoneElseJoined]) => {
968+
if (livekitConnectionState === ConnectionState.Disconnected) {
969+
// Do not ring until we're connected.
970+
return of("unknown" as const);
971+
} else if (someoneElseJoined) {
972+
return of("success" as const);
973+
}
974+
// Show the ringing state of the most recent ringing attempt.
975+
return this.ring$.pipe(switchAll());
976+
}),
969977
// The state starts as 'unknown' because we don't know if the RTC
970978
// session will actually send a notify event yet. It will only be
971979
// known once we send our own membership and see that we were the
@@ -1682,7 +1690,7 @@ export class CallViewModel extends ViewModel {
16821690
private readonly livekitRoom: LivekitRoom,
16831691
private readonly mediaDevices: MediaDevices,
16841692
private readonly options: CallViewModelOptions,
1685-
private readonly livekitConnectionState$: Observable<ECConnectionState>,
1693+
public readonly livekitConnectionState$: Observable<ECConnectionState>,
16861694
private readonly handsRaisedSubject$: Observable<
16871695
Record<string, RaisedHandInfo>
16881696
>,

0 commit comments

Comments
 (0)