Merge branch 'voip-team/rebased-multiSFU' into valere/multi-sfu/connection_states

This commit is contained in:
Valere
2025-10-10 15:01:49 +02:00
17 changed files with 118 additions and 89 deletions

View File

@@ -528,10 +528,12 @@ export class CallViewModel extends ViewModel {
multiSfu.value$,
],
(preferred, memberships, multiSfu) => {
const oldestMembership =
this.matrixRTCSession.getOldestMembership();
const remote = memberships.flatMap((m) => {
if (m.sender === this.userId && m.deviceId === this.deviceId)
return [];
const t = this.matrixRTCSession.resolveActiveFocus(m);
const t = m.getTransport(oldestMembership ?? m);
return t && isLivekitTransport(t)
? [{ membership: m, transport: t }]
: [];
@@ -633,10 +635,11 @@ export class CallViewModel extends ViewModel {
// Until the local transport becomes ready we have no idea which
// transports will actually need a dedicated remote connection
if (transports?.local.state === "ready") {
const oldestMembership = this.matrixRTCSession.getOldestMembership();
const localServiceUrl = transports.local.value.livekit_service_url;
const remoteServiceUrls = new Set(
transports.remote.flatMap(({ membership, transport }) => {
const t = this.matrixRTCSession.resolveActiveFocus(membership);
const t = membership.getTransport(oldestMembership ?? membership);
return t &&
isLivekitTransport(t) &&
t.livekit_service_url !== localServiceUrl

View File

@@ -99,7 +99,19 @@ export class ObservableScope {
.subscribe(callback);
}
// TODO-MULTI-SFU Dear Future Robin, please document this. Love, Past Robin.
/**
* For the duration of the scope, sync some external state with the value of
* the provided Behavior by way of an async function which attempts to update
* (reconcile) the external state. The reconciliation function may return a
* clean-up callback which will be called and awaited before the next change
* in value (or the end of the scope).
*
* All calls to the function and its clean-up callbacks are serialized. If the
* value changes faster than the handlers can keep up with, intermediate
* values may be skipped.
*
* Basically, this is like React's useEffect but async and for Behaviors.
*/
public reconcile<T>(
value$: Behavior<T>,
callback: (value: T) => Promise<(() => Promise<void>) | undefined>,
@@ -107,27 +119,27 @@ export class ObservableScope {
let latestValue: T | typeof nothing = nothing;
let reconciledValue: T | typeof nothing = nothing;
let cleanUp: (() => Promise<void>) | undefined = undefined;
let callbackPromise: Promise<(() => Promise<void>) | undefined>;
value$
.pipe(
catchError(() => EMPTY),
this.bind(),
endWith(nothing),
catchError(() => EMPTY), // Ignore errors
this.bind(), // Limit to the duration of the scope
endWith(nothing), // Clean up when the scope ends
)
.subscribe((value) => {
void (async (): Promise<void> => {
if (latestValue === nothing) {
latestValue = value;
while (latestValue !== reconciledValue) {
await cleanUp?.();
await cleanUp?.(); // Call the previous value's clean-up handler
reconciledValue = latestValue;
if (latestValue !== nothing) {
callbackPromise = callback(latestValue);
cleanUp = await callbackPromise;
}
if (latestValue !== nothing)
cleanUp = await callback(latestValue); // Sync current value
}
// Reset to signal that reconciliation is done for now
latestValue = nothing;
} else {
// There's already an instance of the above 'while' loop running
// concurrently. Just update the latest value and let it be handled.
latestValue = value;
}
})();