Always consider multi-SFU mode enabled when using sticky events

CallViewModel would pass the wrong transport to enterRtcSession when the user enabled sticky events but didn't manually enable multi-SFU mode as well. This likely would've added some confusion to our attempts to test these modes.
This commit is contained in:
Robin
2025-10-21 13:01:38 -04:00
parent 49d5a54d7e
commit e313cf04a6
2 changed files with 40 additions and 21 deletions

View File

@@ -20,7 +20,6 @@ import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
import { MatrixRTCTransportMissingError } from "./utils/errors"; import { MatrixRTCTransportMissingError } from "./utils/errors";
import { getUrlParams } from "./UrlParams"; import { getUrlParams } from "./UrlParams";
import { getSFUConfigWithOpenID } from "./livekit/openIDSFU.ts"; import { getSFUConfigWithOpenID } from "./livekit/openIDSFU.ts";
import { preferStickyEvents } from "./settings/settings.ts";
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
@@ -100,12 +99,11 @@ export async function makeTransport(
export interface EnterRTCSessionOptions { export interface EnterRTCSessionOptions {
encryptMedia: boolean; encryptMedia: boolean;
// TODO: remove this flag, the new membership manager is stable enough
useNewMembershipManager?: boolean;
// TODO: remove this flag, to-device transport is stable enough now // TODO: remove this flag, to-device transport is stable enough now
useExperimentalToDeviceTransport?: boolean; useExperimentalToDeviceTransport?: boolean;
/** EXPERIMENTAL: If true, will use the multi-sfu codepath where each member connects to its SFU instead of everyone connecting to an elected on. */ /** EXPERIMENTAL: If true, will use the multi-sfu codepath where each member connects to its SFU instead of everyone connecting to an elected on. */
useMultiSfu?: boolean; useMultiSfu: boolean;
preferStickyEvents: boolean;
} }
/** /**
@@ -117,14 +115,13 @@ export interface EnterRTCSessionOptions {
export async function enterRTCSession( export async function enterRTCSession(
rtcSession: MatrixRTCSession, rtcSession: MatrixRTCSession,
transport: LivekitTransport, transport: LivekitTransport,
options: EnterRTCSessionOptions = { {
encryptMedia: true, encryptMedia,
useExperimentalToDeviceTransport: false, useExperimentalToDeviceTransport = false,
useMultiSfu: true, useMultiSfu,
}, preferStickyEvents,
}: EnterRTCSessionOptions,
): Promise<void> { ): Promise<void> {
const { encryptMedia, useExperimentalToDeviceTransport = false } = options;
const useMultiSfu = preferStickyEvents.getValue() || options.useMultiSfu;
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId); PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
@@ -158,7 +155,7 @@ export async function enterRTCSession(
membershipEventExpiryMs: membershipEventExpiryMs:
matrixRtcSessionConfig?.membership_event_expiry_ms, matrixRtcSessionConfig?.membership_event_expiry_ms,
useExperimentalToDeviceTransport, useExperimentalToDeviceTransport,
unstableSendStickyEvents: preferStickyEvents.getValue(), unstableSendStickyEvents: preferStickyEvents,
}, },
); );
if (widget) { if (widget) {

View File

@@ -91,6 +91,7 @@ import {
duplicateTiles, duplicateTiles,
multiSfu, multiSfu,
playReactionsSound, playReactionsSound,
preferStickyEvents,
showReactions, showReactions,
} from "../settings/settings"; } from "../settings/settings";
import { isFirefox } from "../Platform"; import { isFirefox } from "../Platform";
@@ -262,23 +263,32 @@ export class CallViewModel extends ViewModel {
/** /**
* Lists the transports used by ourselves, plus all other MatrixRTC session * Lists the transports used by ourselves, plus all other MatrixRTC session
* members. For completeness this also lists the preferred transport and * members. For completeness this also lists the preferred transport and
* whether we are in multi-SFU mode (because advertisedTransport$ wants to * whether we are in multi-SFU mode or sticky events mode (because
* read them at the same time, and bundling data together when it might change * advertisedTransport$ wants to read them at the same time, and bundling data
* together is what you have to do in RxJS to avoid reading inconsistent state * together when it might change together is what you have to do in RxJS to
* or observing too many changes.) * avoid reading inconsistent state or observing too many changes.)
*/ */
private readonly transports$: Behavior<{ private readonly transports$: Behavior<{
local: Async<LivekitTransport>; local: Async<LivekitTransport>;
remote: { membership: CallMembership; transport: LivekitTransport }[]; remote: { membership: CallMembership; transport: LivekitTransport }[];
preferred: Async<LivekitTransport>; preferred: Async<LivekitTransport>;
multiSfu: boolean; multiSfu: boolean;
preferStickyEvents: boolean;
} | null> = this.scope.behavior( } | null> = this.scope.behavior(
this.joined$.pipe( this.joined$.pipe(
switchMap((joined) => switchMap((joined) =>
joined joined
? combineLatest( ? combineLatest(
[this.preferredTransport$, this.memberships$, multiSfu.value$], [
(preferred, memberships, multiSfu) => { this.preferredTransport$,
this.memberships$,
multiSfu.value$,
preferStickyEvents.value$,
],
(preferred, memberships, preferMultiSfu, preferStickyEvents) => {
// Multi-SFU must be implicitly enabled when using sticky events
const multiSfu = preferStickyEvents || preferMultiSfu;
const oldestMembership = const oldestMembership =
this.matrixRTCSession.getOldestMembership(); this.matrixRTCSession.getOldestMembership();
const remote = memberships.flatMap((m) => { const remote = memberships.flatMap((m) => {
@@ -289,6 +299,7 @@ export class CallViewModel extends ViewModel {
? [{ membership: m, transport: t }] ? [{ membership: m, transport: t }]
: []; : [];
}); });
let local = preferred; let local = preferred;
if (!multiSfu) { if (!multiSfu) {
const oldest = this.matrixRTCSession.getOldestMembership(); const oldest = this.matrixRTCSession.getOldestMembership();
@@ -299,6 +310,7 @@ export class CallViewModel extends ViewModel {
local = ready(selection); local = ready(selection);
} }
} }
if (local.state === "error") { if (local.state === "error") {
this._configError$.next( this._configError$.next(
local.value instanceof ElementCallError local.value instanceof ElementCallError
@@ -306,7 +318,14 @@ export class CallViewModel extends ViewModel {
: new UnknownCallError(local.value), : new UnknownCallError(local.value),
); );
} }
return { local, remote, preferred, multiSfu };
return {
local,
remote,
preferred,
multiSfu,
preferStickyEvents,
};
}, },
) )
: of(null), : of(null),
@@ -336,10 +355,11 @@ export class CallViewModel extends ViewModel {
/** /**
* The transport we should advertise in our MatrixRTC membership (plus whether * The transport we should advertise in our MatrixRTC membership (plus whether
* it is a multi-SFU transport). * it is a multi-SFU transport and whether we should use sticky events).
*/ */
private readonly advertisedTransport$: Behavior<{ private readonly advertisedTransport$: Behavior<{
multiSfu: boolean; multiSfu: boolean;
preferStickyEvents: boolean;
transport: LivekitTransport; transport: LivekitTransport;
} | null> = this.scope.behavior( } | null> = this.scope.behavior(
this.transports$.pipe( this.transports$.pipe(
@@ -348,6 +368,7 @@ export class CallViewModel extends ViewModel {
transports.preferred.state === "ready" transports.preferred.state === "ready"
? { ? {
multiSfu: transports.multiSfu, multiSfu: transports.multiSfu,
preferStickyEvents: transports.preferStickyEvents,
// In non-multi-SFU mode we should always advertise the preferred // In non-multi-SFU mode we should always advertise the preferred
// SFU to minimize the number of membership updates // SFU to minimize the number of membership updates
transport: transports.multiSfu transport: transports.multiSfu
@@ -358,6 +379,7 @@ export class CallViewModel extends ViewModel {
), ),
distinctUntilChanged<{ distinctUntilChanged<{
multiSfu: boolean; multiSfu: boolean;
preferStickyEvents: boolean;
transport: LivekitTransport; transport: LivekitTransport;
} | null>(deepCompare), } | null>(deepCompare),
), ),
@@ -1800,8 +1822,8 @@ export class CallViewModel extends ViewModel {
await enterRTCSession(this.matrixRTCSession, advertised.transport, { await enterRTCSession(this.matrixRTCSession, advertised.transport, {
encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE, encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE,
useExperimentalToDeviceTransport: true, useExperimentalToDeviceTransport: true,
useNewMembershipManager: true,
useMultiSfu: advertised.multiSfu, useMultiSfu: advertised.multiSfu,
preferStickyEvents: advertised.preferStickyEvents,
}); });
} catch (e) { } catch (e) {
logger.error("Error entering RTC session", e); logger.error("Error entering RTC session", e);