Refactor disconnection handling

This commit is contained in:
Half-Shot
2025-09-16 10:20:06 +01:00
parent e201258af3
commit b16ba52e13
3 changed files with 25 additions and 22 deletions

View File

@@ -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 MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk";
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container"; 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 { of } from "rxjs";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { TooltipProvider } from "@vector-im/compound-web"; import { TooltipProvider } from "@vector-im/compound-web";
@@ -180,7 +180,6 @@ function createInCallView(): RenderResult & {
onLeave={function (): void { onLeave={function (): void {
throw new Error("Function not implemented."); throw new Error("Function not implemented.");
}} }}
connState={ConnectionState.Connected}
onShareClick={null} onShareClick={null}
/> />
</RoomContext> </RoomContext>

View File

@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
import { RoomContext, useLocalParticipant } from "@livekit/components-react"; import { RoomContext, useLocalParticipant } from "@livekit/components-react";
import { IconButton, Text, Tooltip } from "@vector-im/compound-web"; 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 MatrixClient, type Room as MatrixRoom } from "matrix-js-sdk";
import { import {
type FC, type FC,
@@ -63,7 +63,6 @@ import { type MuteStates } from "./MuteStates";
import { type MatrixInfo } from "./VideoPreview"; import { type MatrixInfo } from "./VideoPreview";
import { InviteButton } from "../button/InviteButton"; import { InviteButton } from "../button/InviteButton";
import { LayoutToggle } from "./LayoutToggle"; import { LayoutToggle } from "./LayoutToggle";
import { type ECConnectionState } from "../livekit/useECConnectionState";
import { useOpenIDSFU } from "../livekit/openIDSFU"; import { useOpenIDSFU } from "../livekit/openIDSFU";
import { import {
CallViewModel, CallViewModel,
@@ -212,12 +211,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
return ( return (
<RoomContext value={livekitRoom}> <RoomContext value={livekitRoom}>
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}> <ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
<InCallView <InCallView {...props} vm={vm} livekitRoom={livekitRoom} />
{...props}
vm={vm}
livekitRoom={livekitRoom}
connState={connState}
/>
</ReactionsSenderProvider> </ReactionsSenderProvider>
</RoomContext> </RoomContext>
); );
@@ -235,7 +229,6 @@ export interface InCallViewProps {
onLeave: (cause: "user", soundFile?: CallEventSounds) => void; onLeave: (cause: "user", soundFile?: CallEventSounds) => void;
header: HeaderStyle; header: HeaderStyle;
otelGroupCallMembership?: OTelGroupCallMembership; otelGroupCallMembership?: OTelGroupCallMembership;
connState: ECConnectionState;
onShareClick: (() => void) | null; onShareClick: (() => void) | null;
} }
@@ -249,7 +242,6 @@ export const InCallView: FC<InCallViewProps> = ({
muteStates, muteStates,
onLeave, onLeave,
header: headerStyle, header: headerStyle,
connState,
onShareClick, onShareClick,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -257,11 +249,11 @@ export const InCallView: FC<InCallViewProps> = ({
useReactionsSender(); useReactionsSender();
useWakeLock(); useWakeLock();
const isDisconnected = useBehavior(vm.livekitDisconnected$);
// annoyingly we don't get the disconnection reason this way, // annoyingly we don't get the disconnection reason this way,
// only by listening for the emitted event // only by listening for the emitted event
if (connState === ConnectionState.Disconnected) if (isDisconnected) throw new ConnectionLostError();
throw new ConnectionLostError();
const containerRef1 = useRef<HTMLDivElement | null>(null); const containerRef1 = useRef<HTMLDivElement | null>(null);
const [containerRef2, bounds] = useMeasure(); const [containerRef2, bounds] = useMeasure();

View File

@@ -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( private readonly connected$ = this.scope.behavior(
and$( and$(
this.matrixConnected$, this.matrixConnected$,
@@ -957,15 +965,19 @@ export class CallViewModel extends ViewModel {
"unknown" | "ringing" | "timeout" | "decline" | "success" | null "unknown" | "ringing" | "timeout" | "decline" | "success" | null
> = this.options.waitForCallPickup > = this.options.waitForCallPickup
? this.scope.behavior< ? this.scope.behavior<
"unknown" | "ringing" | "timeout" | "decline" | "success" "unknown" | "ringing" | "timeout" | "decline" | "success" | null
>( >(
this.someoneElseJoined$.pipe( combineLatest(this.livekitDisconnected$, this.someoneElseJoined$).pipe(
switchMap((someoneElseJoined) => switchMap(([isDisconnected, someoneElseJoined]) => {
someoneElseJoined if (isDisconnected) {
? of("success" as const) // Do not ring until we're connected.
: // Show the ringing state of the most recent ringing attempt. return of(null);
this.ring$.pipe(switchAll()), } 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 // 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 // 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 // known once we send our own membership and see that we were the