Fix resource leaks when we stop using a connection
The execution of certain Observables related to a local or remote connection would continue even after we stopped caring about said connection because we were failing to give these state holders a proper ObservableScope of their own, separate from the CallViewModel's longer-lived scope. With this commit they now have scopes managed by generateKeyed$.
This commit is contained in:
@@ -116,11 +116,7 @@ import {
|
|||||||
} from "../rtcSessionHelpers";
|
} from "../rtcSessionHelpers";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||||
import {
|
import { type Connection, RemoteConnection } from "./Connection";
|
||||||
type Connection,
|
|
||||||
type ConnectionOpts,
|
|
||||||
RemoteConnection,
|
|
||||||
} from "./Connection";
|
|
||||||
import { type MuteStates } from "./MuteStates";
|
import { type MuteStates } from "./MuteStates";
|
||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
import { type ProcessorState } from "../livekit/TrackProcessorContext";
|
import { type ProcessorState } from "../livekit/TrackProcessorContext";
|
||||||
@@ -369,26 +365,36 @@ export class CallViewModel {
|
|||||||
*/
|
*/
|
||||||
private readonly localConnection$: Behavior<Async<PublishConnection> | null> =
|
private readonly localConnection$: Behavior<Async<PublishConnection> | null> =
|
||||||
this.scope.behavior(
|
this.scope.behavior(
|
||||||
this.localTransport$.pipe(
|
generateKeyed$<
|
||||||
map(
|
Async<LivekitTransport> | null,
|
||||||
(transport) =>
|
PublishConnection,
|
||||||
transport &&
|
Async<PublishConnection> | null
|
||||||
mapAsync(transport, (transport) => {
|
>(
|
||||||
const opts: ConnectionOpts = {
|
this.localTransport$,
|
||||||
transport,
|
(transport, createOrGet) =>
|
||||||
client: this.matrixRTCSession.room.client,
|
transport &&
|
||||||
scope: this.scope,
|
mapAsync(transport, (transport) =>
|
||||||
remoteTransports$: this.remoteTransports$,
|
createOrGet(
|
||||||
};
|
// Stable key that uniquely idenifies the transport
|
||||||
return new PublishConnection(
|
JSON.stringify({
|
||||||
opts,
|
url: transport.livekit_service_url,
|
||||||
this.mediaDevices,
|
alias: transport.livekit_alias,
|
||||||
this.muteStates,
|
}),
|
||||||
this.e2eeLivekitOptions(),
|
(scope) =>
|
||||||
this.scope.behavior(this.trackProcessorState$),
|
new PublishConnection(
|
||||||
);
|
{
|
||||||
}),
|
transport,
|
||||||
),
|
client: this.matrixRoom.client,
|
||||||
|
scope,
|
||||||
|
remoteTransports$: this.remoteTransports$,
|
||||||
|
},
|
||||||
|
this.mediaDevices,
|
||||||
|
this.muteStates,
|
||||||
|
this.e2eeLivekitOptions(),
|
||||||
|
this.scope.behavior(this.trackProcessorState$),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -415,61 +421,47 @@ export class CallViewModel {
|
|||||||
* is *distinct* from the local transport.
|
* is *distinct* from the local transport.
|
||||||
*/
|
*/
|
||||||
private readonly remoteConnections$ = this.scope.behavior(
|
private readonly remoteConnections$ = this.scope.behavior(
|
||||||
this.transports$.pipe(
|
generateKeyed$<typeof this.transports$.value, Connection, Connection[]>(
|
||||||
accumulate(new Map<string, Connection>(), (prev, transports) => {
|
this.transports$,
|
||||||
const next = new Map<string, Connection>();
|
(transports, createOrGet) => {
|
||||||
|
const connections: Connection[] = [];
|
||||||
|
|
||||||
// Until the local transport becomes ready we have no idea which
|
// Until the local transport becomes ready we have no idea which
|
||||||
// transports will actually need a dedicated remote connection
|
// transports will actually need a dedicated remote connection
|
||||||
if (transports?.local.state === "ready") {
|
if (transports?.local.state === "ready") {
|
||||||
const oldestMembership = this.matrixRTCSession.getOldestMembership();
|
// TODO: Handle custom transport.livekit_alias values here
|
||||||
const localServiceUrl = transports.local.value.livekit_service_url;
|
const localServiceUrl = transports.local.value.livekit_service_url;
|
||||||
const remoteServiceUrls = new Set(
|
const remoteServiceUrls = new Set(
|
||||||
transports.remote.flatMap(({ membership, transport }) => {
|
transports.remote.map(
|
||||||
const t = membership.getTransport(oldestMembership ?? membership);
|
({ transport }) => transport.livekit_service_url,
|
||||||
return t &&
|
),
|
||||||
isLivekitTransport(t) &&
|
|
||||||
t.livekit_service_url !== localServiceUrl
|
|
||||||
? [t.livekit_service_url]
|
|
||||||
: [];
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
remoteServiceUrls.delete(localServiceUrl);
|
||||||
|
|
||||||
for (const remoteServiceUrl of remoteServiceUrls) {
|
for (const remoteServiceUrl of remoteServiceUrls)
|
||||||
let nextConnection = prev.get(remoteServiceUrl);
|
connections.push(
|
||||||
if (!nextConnection) {
|
createOrGet(
|
||||||
logger.log(
|
|
||||||
"SFU remoteConnections$ construct new connection: ",
|
|
||||||
remoteServiceUrl,
|
remoteServiceUrl,
|
||||||
);
|
(scope) =>
|
||||||
|
new RemoteConnection(
|
||||||
const args: ConnectionOpts = {
|
{
|
||||||
transport: {
|
transport: {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: remoteServiceUrl,
|
livekit_service_url: remoteServiceUrl,
|
||||||
livekit_alias: this.livekitAlias,
|
livekit_alias: this.livekitAlias,
|
||||||
},
|
},
|
||||||
client: this.matrixRTCSession.room.client,
|
client: this.matrixRoom.client,
|
||||||
scope: this.scope,
|
scope,
|
||||||
remoteTransports$: this.remoteTransports$,
|
remoteTransports$: this.remoteTransports$,
|
||||||
};
|
},
|
||||||
nextConnection = new RemoteConnection(
|
this.e2eeLivekitOptions(),
|
||||||
args,
|
),
|
||||||
this.e2eeLivekitOptions(),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
logger.log(
|
|
||||||
"SFU remoteConnections$ use prev connection: ",
|
|
||||||
remoteServiceUrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
next.set(remoteServiceUrl, nextConnection);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
return connections;
|
||||||
}),
|
},
|
||||||
map((transports) => [...transports.values()]),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
type CallMembership,
|
type CallMembership,
|
||||||
type LivekitTransport,
|
type LivekitTransport,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { BehaviorSubject, combineLatest, type Observable } from "rxjs";
|
import { BehaviorSubject, combineLatest, type Observable } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -218,6 +219,9 @@ export class Connection {
|
|||||||
public readonly livekitRoom: LivekitRoom,
|
public readonly livekitRoom: LivekitRoom,
|
||||||
opts: ConnectionOpts,
|
opts: ConnectionOpts,
|
||||||
) {
|
) {
|
||||||
|
logger.log(
|
||||||
|
`[Connection] Creating new connection to ${opts.transport.livekit_service_url} ${opts.transport.livekit_alias}`,
|
||||||
|
);
|
||||||
const { transport, client, scope, remoteTransports$ } = opts;
|
const { transport, client, scope, remoteTransports$ } = opts;
|
||||||
|
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class PublishConnection extends Connection {
|
|||||||
trackerProcessorState$: Behavior<ProcessorState>,
|
trackerProcessorState$: Behavior<ProcessorState>,
|
||||||
) {
|
) {
|
||||||
const { scope } = args;
|
const { scope } = args;
|
||||||
logger.info("[LivekitRoom] Create LiveKit room");
|
logger.info("[PublishConnection] Create LiveKit room");
|
||||||
const { controlledAudioDevices } = getUrlParams();
|
const { controlledAudioDevices } = getUrlParams();
|
||||||
|
|
||||||
const factory =
|
const factory =
|
||||||
|
|||||||
Reference in New Issue
Block a user