Skip to content

Commit cf453ba

Browse files
authored
Merge pull request #3611 from element-hq/toger5/connection-state-refactor
Unify LiveKit and Matrix connection states
2 parents ee2b0c6 + 7edc97b commit cf453ba

File tree

16 files changed

+651
-527
lines changed

16 files changed

+651
-527
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"@opentelemetry/sdk-trace-base": "^2.0.0",
5555
"@opentelemetry/sdk-trace-web": "^2.0.0",
5656
"@opentelemetry/semantic-conventions": "^1.25.1",
57-
"@playwright/test": "^1.56.1",
57+
"@playwright/test": "^1.57.0",
5858
"@radix-ui/react-dialog": "^1.0.4",
5959
"@radix-ui/react-slider": "^1.1.2",
6060
"@radix-ui/react-visually-hidden": "^1.0.3",

playwright/fixtures/widget-user.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,27 @@ async function registerUser(
111111
await page.getByRole("textbox", { name: "Confirm password" }).click();
112112
await page.getByRole("textbox", { name: "Confirm password" }).fill(PASSWORD);
113113
await page.getByRole("button", { name: "Register" }).click();
114-
const continueButton = page.getByRole("button", { name: "Continue" });
115-
try {
116-
await expect(continueButton).toBeVisible({ timeout: 5000 });
117-
await page
118-
.getByRole("textbox", { name: "Password", exact: true })
119-
.fill(PASSWORD);
120-
await continueButton.click();
121-
} catch {
122-
// continueButton not visible, continue as normal
123-
}
114+
124115
await expect(
125116
page.getByRole("heading", { name: `Welcome ${username}` }),
126117
).toBeVisible();
118+
119+
const browserUnsupportedToast = page
120+
.getByText("Element does not support this browser")
121+
.locator("..")
122+
.locator("..");
123+
124+
// Dismiss incompatible browser toast
125+
const dismissButton = browserUnsupportedToast.getByRole("button", {
126+
name: "Dismiss",
127+
});
128+
try {
129+
await expect(dismissButton).toBeVisible({ timeout: 700 });
130+
await dismissButton.click();
131+
} catch {
132+
// dismissButton not visible, continue as normal
133+
}
134+
127135
await setDevToolElementCallDevUrl(page);
128136

129137
const clientHandle = await page.evaluateHandle(() =>

src/room/GroupCallView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export const GroupCallView: FC<Props> = ({
160160
}, [rtcSession]);
161161

162162
// TODO move this into the callViewModel LocalMembership.ts
163+
// We might actually not need this at all. Since we get into fatalError on those errors already?
163164
useTypedEventEmitter(
164165
rtcSession,
165166
MatrixRTCSessionEvent.MembershipManagerError,
@@ -313,6 +314,7 @@ export const GroupCallView: FC<Props> = ({
313314

314315
const navigate = useNavigate();
315316

317+
// TODO split this into leave and onDisconnect
316318
const onLeft = useCallback(
317319
(
318320
reason: "timeout" | "user" | "allOthersLeft" | "decline" | "error",

src/room/InCallView.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
2424
import classNames from "classnames";
2525
import { BehaviorSubject, map } from "rxjs";
2626
import { useObservable } from "observable-hooks";
27-
import { logger } from "matrix-js-sdk/lib/logger";
27+
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
2828
import {
2929
VoiceCallSolidIcon,
3030
VolumeOnSolidIcon,
@@ -109,6 +109,8 @@ import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.t
109109
import { type Layout } from "../state/layout-types.ts";
110110
import { ObservableScope } from "../state/ObservableScope.ts";
111111

112+
const logger = rootLogger.getChild("[InCallView]");
113+
112114
const maxTapDurationMs = 400;
113115

114116
export interface ActiveCallProps
@@ -127,6 +129,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
127129
const mediaDevices = useMediaDevices();
128130
const trackProcessorState$ = useTrackProcessorObservable$();
129131
useEffect(() => {
132+
logger.info("START CALL VIEW SCOPE");
130133
const scope = new ObservableScope();
131134
const reactionsReader = new ReactionsReader(scope, props.rtcSession);
132135
const { autoLeaveWhenOthersLeft, waitForCallPickup, sendNotificationType } =
@@ -151,7 +154,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
151154
setVm(vm);
152155

153156
vm.leave$.pipe(scope.bind()).subscribe(props.onLeft);
157+
154158
return (): void => {
159+
logger.info("END CALL VIEW SCOPE");
155160
scope.end();
156161
};
157162
}, [
@@ -270,7 +275,10 @@ export const InCallView: FC<InCallViewProps> = ({
270275
const ringOverlay = useBehavior(vm.ringOverlay$);
271276
const fatalCallError = useBehavior(vm.fatalError$);
272277
// Stop the rendering and throw for the error boundary
273-
if (fatalCallError) throw fatalCallError;
278+
if (fatalCallError) {
279+
logger.debug("fatalCallError stop rendering", fatalCallError);
280+
throw fatalCallError;
281+
}
274282

275283
// We need to set the proper timings on the animation based upon the sound length.
276284
const ringDuration = pickupPhaseAudio?.soundDuration["waiting"] ?? 1;

src/room/LobbyView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ export const LobbyView: FC<Props> = ({
7979
waitingForInvite,
8080
}) => {
8181
useEffect(() => {
82-
logger.info("[Lifecycle] GroupCallView Component mounted");
82+
logger.info("[Lifecycle] LobbyView Component mounted");
8383
return (): void => {
84-
logger.info("[Lifecycle] GroupCallView Component unmounted");
84+
logger.info("[Lifecycle] LobbyView Component unmounted");
8585
};
8686
}, []);
8787

src/state/CallViewModel/CallViewModel.ts

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "livekit-client";
1616
import { type Room as MatrixRoom } from "matrix-js-sdk";
1717
import {
18+
catchError,
1819
combineLatest,
1920
distinctUntilChanged,
2021
filter,
@@ -93,14 +94,14 @@ import {
9394
type SpotlightLandscapeLayoutMedia,
9495
type SpotlightPortraitLayoutMedia,
9596
} from "../layout-types.ts";
96-
import { type ElementCallError } from "../../utils/errors.ts";
97+
import { ElementCallError } from "../../utils/errors.ts";
9798
import { type ObservableScope } from "../ObservableScope.ts";
9899
import { createHomeserverConnected$ } from "./localMember/HomeserverConnected.ts";
99100
import {
100101
createLocalMembership$,
101102
enterRTCSession,
102-
RTCBackendState,
103-
} from "./localMember/LocalMembership.ts";
103+
TransportState,
104+
} from "./localMember/LocalMember.ts";
104105
import { createLocalTransport$ } from "./localMember/LocalTransport.ts";
105106
import {
106107
createMemberships$,
@@ -425,7 +426,18 @@ export function createCallViewModel$(
425426
connectionFactory: connectionFactory,
426427
inputTransports$: scope.behavior(
427428
combineLatest(
428-
[localTransport$, membershipsAndTransports.transports$],
429+
[
430+
localTransport$.pipe(
431+
catchError((e: unknown) => {
432+
logger.info(
433+
"dont pass local transport to createConnectionManager$. localTransport$ threw an error",
434+
e,
435+
);
436+
return of(null);
437+
}),
438+
),
439+
membershipsAndTransports.transports$,
440+
],
429441
(localTransport, transports) => {
430442
const localTransportAsArray = localTransport ? [localTransport] : [];
431443
return transports.mapInner((transports) => [
@@ -457,13 +469,13 @@ export function createCallViewModel$(
457469

458470
const localMembership = createLocalMembership$({
459471
scope: scope,
460-
homeserverConnected$: createHomeserverConnected$(
472+
homeserverConnected: createHomeserverConnected$(
461473
scope,
462474
client,
463475
matrixRTCSession,
464476
),
465477
muteStates: muteStates,
466-
joinMatrixRTC: async (transport: LivekitTransport) => {
478+
joinMatrixRTC: (transport: LivekitTransport) => {
467479
return enterRTCSession(
468480
matrixRTCSession,
469481
transport,
@@ -578,17 +590,6 @@ export function createCallViewModel$(
578590
),
579591
);
580592

581-
/**
582-
* Whether various media/event sources should pretend to be disconnected from
583-
* all network input, even if their connection still technically works.
584-
*/
585-
// We do this when the app is in the 'reconnecting' state, because it might be
586-
// that the LiveKit connection is still functional while the homeserver is
587-
// down, for example, and we want to avoid making people worry that the app is
588-
// in a split-brained state.
589-
// DISCUSSION own membership manager ALSO this probably can be simplifis
590-
const reconnecting$ = localMembership.reconnecting$;
591-
592593
const audioParticipants$ = scope.behavior(
593594
matrixLivekitMembers$.pipe(
594595
switchMap((membersWithEpoch) => {
@@ -636,7 +637,7 @@ export function createCallViewModel$(
636637
);
637638

638639
const handsRaised$ = scope.behavior(
639-
handsRaisedSubject$.pipe(pauseWhen(reconnecting$)),
640+
handsRaisedSubject$.pipe(pauseWhen(localMembership.reconnecting$)),
640641
);
641642

642643
const reactions$ = scope.behavior(
@@ -649,7 +650,7 @@ export function createCallViewModel$(
649650
]),
650651
),
651652
),
652-
pauseWhen(reconnecting$),
653+
pauseWhen(localMembership.reconnecting$),
653654
),
654655
);
655656

@@ -740,7 +741,7 @@ export function createCallViewModel$(
740741
livekitRoom$,
741742
focusUrl$,
742743
mediaDevices,
743-
reconnecting$,
744+
localMembership.reconnecting$,
744745
displayName$,
745746
matrixMemberMetadataStore.createAvatarUrlBehavior$(userId),
746747
handsRaised$.pipe(map((v) => v[participantId]?.time ?? null)),
@@ -1423,13 +1424,44 @@ export function createCallViewModel$(
14231424
// reassigned here to make it publicly accessible
14241425
const toggleScreenSharing = localMembership.toggleScreenSharing;
14251426

1427+
const errors$ = scope.behavior<{
1428+
transportError?: ElementCallError;
1429+
matrixError?: ElementCallError;
1430+
connectionError?: ElementCallError;
1431+
publishError?: ElementCallError;
1432+
} | null>(
1433+
localMembership.localMemberState$.pipe(
1434+
map((value) => {
1435+
const returnObject: {
1436+
transportError?: ElementCallError;
1437+
matrixError?: ElementCallError;
1438+
connectionError?: ElementCallError;
1439+
publishError?: ElementCallError;
1440+
} = {};
1441+
if (value instanceof ElementCallError) return { transportError: value };
1442+
if (value === TransportState.Waiting) return null;
1443+
if (value.matrix instanceof ElementCallError)
1444+
returnObject.matrixError = value.matrix;
1445+
if (value.media instanceof ElementCallError)
1446+
returnObject.publishError = value.media;
1447+
else if (
1448+
typeof value.media === "object" &&
1449+
value.media.connection instanceof ElementCallError
1450+
)
1451+
returnObject.connectionError = value.media.connection;
1452+
return returnObject;
1453+
}),
1454+
),
1455+
null,
1456+
);
1457+
14261458
return {
14271459
autoLeave$: autoLeave$,
14281460
callPickupState$: callPickupState$,
14291461
ringOverlay$: ringOverlay$,
14301462
leave$: leave$,
14311463
hangup: (): void => userHangup$.next(),
1432-
join: localMembership.requestConnect,
1464+
join: localMembership.requestJoinAndPublish,
14331465
toggleScreenSharing: toggleScreenSharing,
14341466
sharingScreen$: sharingScreen$,
14351467

@@ -1439,9 +1471,17 @@ export function createCallViewModel$(
14391471
unhoverScreen: (): void => screenUnhover$.next(),
14401472

14411473
fatalError$: scope.behavior(
1442-
localMembership.connectionState.livekit$.pipe(
1443-
filter((v) => v.state === RTCBackendState.Error),
1444-
map((s) => s.error),
1474+
errors$.pipe(
1475+
map((errors) => {
1476+
logger.debug("errors$ to compute any fatal errors:", errors);
1477+
return (
1478+
errors?.transportError ??
1479+
errors?.matrixError ??
1480+
errors?.connectionError ??
1481+
null
1482+
);
1483+
}),
1484+
filter((error) => error !== null),
14451485
),
14461486
null,
14471487
),
@@ -1474,7 +1514,7 @@ export function createCallViewModel$(
14741514
showFooter$: showFooter$,
14751515
earpieceMode$: earpieceMode$,
14761516
audioOutputSwitcher$: audioOutputSwitcher$,
1477-
reconnecting$: reconnecting$,
1517+
reconnecting$: localMembership.reconnecting$,
14781518
};
14791519
}
14801520

0 commit comments

Comments
 (0)