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 { getUrlParams } from "./UrlParams";
import { getSFUConfigWithOpenID } from "./livekit/openIDSFU.ts";
import { preferStickyEvents } from "./settings/settings.ts";
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
@@ -100,12 +99,11 @@ export async function makeTransport(
export interface EnterRTCSessionOptions {
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
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. */
useMultiSfu?: boolean;
useMultiSfu: boolean;
preferStickyEvents: boolean;
}
/**
@@ -117,14 +115,13 @@ export interface EnterRTCSessionOptions {
export async function enterRTCSession(
rtcSession: MatrixRTCSession,
transport: LivekitTransport,
options: EnterRTCSessionOptions = {
encryptMedia: true,
useExperimentalToDeviceTransport: false,
useMultiSfu: true,
},
{
encryptMedia,
useExperimentalToDeviceTransport = false,
useMultiSfu,
preferStickyEvents,
}: EnterRTCSessionOptions,
): Promise<void> {
const { encryptMedia, useExperimentalToDeviceTransport = false } = options;
const useMultiSfu = preferStickyEvents.getValue() || options.useMultiSfu;
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
@@ -158,7 +155,7 @@ export async function enterRTCSession(
membershipEventExpiryMs:
matrixRtcSessionConfig?.membership_event_expiry_ms,
useExperimentalToDeviceTransport,
unstableSendStickyEvents: preferStickyEvents.getValue(),
unstableSendStickyEvents: preferStickyEvents,
},
);
if (widget) {

View File

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