Require ObservableScopes of state holders to be specified explicitly

Previously we had a ViewModel class which was responsible for little more than creating an ObservableScope. However, since this ObservableScope would be created implicitly upon view model construction, it became a tad bit harder for callers to remember to eventually end the scope (as you wouldn't just have to remember to end ObservableScopes, but also to destroy ViewModels). Requiring the scope to be specified explicitly by the caller also makes it possible for the caller to reuse the scope for other purposes, reducing the number of scopes mentally in flight that need tending to, and for all state holders (not just view models) to be handled uniformly by helper functions such as generateKeyed$.
This commit is contained in:
Robin
2025-10-16 13:57:08 -04:00
parent 2c66e11a0a
commit 717c7420f9
18 changed files with 271 additions and 194 deletions

View File

@@ -46,7 +46,6 @@ import {
throttleTime,
} from "rxjs";
import { ViewModel } from "./ViewModel";
import { alwaysShowSelf } from "../settings/settings";
import { showConnectionStats } from "../settings/settings";
import { accumulate } from "../utils/observable";
@@ -56,6 +55,7 @@ import { type ReactionOption } from "../reactions";
import { platform } from "../Platform";
import { type MediaDevices } from "./MediaDevices";
import { type Behavior } from "./Behavior";
import { type ObservableScope } from "./ObservableScope";
export function observeTrackReference$(
participant: Participant,
@@ -216,7 +216,7 @@ export enum EncryptionStatus {
PasswordInvalid,
}
abstract class BaseMediaViewModel extends ViewModel {
abstract class BaseMediaViewModel {
/**
* The LiveKit video track for this media.
*/
@@ -246,6 +246,7 @@ abstract class BaseMediaViewModel extends ViewModel {
}
public constructor(
protected readonly scope: ObservableScope,
/**
* An opaque identifier for this media.
*/
@@ -269,8 +270,6 @@ abstract class BaseMediaViewModel extends ViewModel {
public readonly focusURL: string,
public readonly displayName$: Behavior<string>,
) {
super();
const audio$ = this.observeTrackReference$(audioSource);
this.video$ = this.observeTrackReference$(videoSource);
@@ -403,6 +402,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel {
public readonly cropVideo$: Behavior<boolean> = this._cropVideo$;
public constructor(
scope: ObservableScope,
id: string,
member: RoomMember,
participant$: Observable<LocalParticipant | RemoteParticipant | undefined>,
@@ -414,6 +414,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel {
public readonly reaction$: Behavior<ReactionOption | null>,
) {
super(
scope,
id,
member,
participant$,
@@ -537,6 +538,7 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel {
);
public constructor(
scope: ObservableScope,
id: string,
member: RoomMember,
participant$: Behavior<LocalParticipant | undefined>,
@@ -549,6 +551,7 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel {
reaction$: Behavior<ReactionOption | null>,
) {
super(
scope,
id,
member,
participant$,
@@ -645,6 +648,7 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
);
public constructor(
scope: ObservableScope,
id: string,
member: RoomMember,
participant$: Observable<RemoteParticipant | undefined>,
@@ -657,6 +661,7 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
reaction$: Behavior<ReactionOption | null>,
) {
super(
scope,
id,
member,
participant$,
@@ -742,6 +747,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel {
);
public constructor(
scope: ObservableScope,
id: string,
member: RoomMember,
participant$: Observable<LocalParticipant | RemoteParticipant>,
@@ -753,6 +759,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel {
public readonly local: boolean,
) {
super(
scope,
id,
member,
participant$,