From dbdf853d558dc0342cf68098cf09ced93ab18f9e Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 26 Sep 2025 13:20:55 -0400 Subject: [PATCH] Stop connections on view model destroy --- src/state/CallViewModel.ts | 1 - src/state/Connection.ts | 13 +++++++------ src/state/ObservableScope.ts | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 3dff08d3..439d2662 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -359,7 +359,6 @@ class UserMedia { public destroy(): void { this.scope.end(); - this.vm.destroy(); } } diff --git a/src/state/Connection.ts b/src/state/Connection.ts index 2513382c..db456ba0 100644 --- a/src/state/Connection.ts +++ b/src/state/Connection.ts @@ -55,6 +55,7 @@ export class Connection { } public stop(): void { + if (this.stopped) return; void this.livekitRoom.disconnect(); this.stopped = true; } @@ -117,6 +118,8 @@ export class Connection { this.connectionState$ = this.scope.behavior( connectionStateObserver(this.livekitRoom), ); + + this.scope.onEnd(() => this.stop()); } } @@ -137,11 +140,6 @@ export class PublishConnection extends Connection { } } - public stop(): void { - void this.livekitRoom.disconnect(); - this.stopped = true; - } - public constructor( focus: LivekitFocus, livekitAlias: string, @@ -220,7 +218,10 @@ export class PublishConnection extends Connection { } return this.livekitRoom.localParticipant.isCameraEnabled; }); - // TODO-MULTI-SFU: Unset mute state handlers on destroy + this.scope.onEnd(() => { + this.muteStates.audio.unsetHandler(); + this.muteStates.video.unsetHandler(); + }); const syncDevice = ( kind: MediaDeviceKind, diff --git a/src/state/ObservableScope.ts b/src/state/ObservableScope.ts index 1cddfbff..fe99d89b 100644 --- a/src/state/ObservableScope.ts +++ b/src/state/ObservableScope.ts @@ -36,11 +36,16 @@ export class ObservableScope { return this.bindImpl; } - private readonly shareImpl: MonoTypeOperator = share({ resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false }) + private readonly shareImpl: MonoTypeOperator = share({ + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false, + }); /** * Shares (multicasts) the Observable as a hot Observable. */ - public readonly share: MonoTypeOperator = (input$) => input$.pipe(this.bindImpl, this.shareImpl) + public readonly share: MonoTypeOperator = (input$) => + input$.pipe(this.bindImpl, this.shareImpl); /** * Converts an Observable to a Behavior. If no initial value is specified, the @@ -76,6 +81,13 @@ export class ObservableScope { this.ended$.next(); this.ended$.complete(); } + + /** + * Register a callback to be executed when the scope is ended. + */ + public onEnd(callback: () => void): void { + this.ended$.subscribe(callback); + } } /**