refactor extract inner classes to their own files

This commit is contained in:
Valere
2025-10-13 15:43:12 +02:00
parent 8e6eb70e5b
commit 8823be67c5
3 changed files with 190 additions and 148 deletions

View File

@@ -5,39 +5,32 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details. Please see LICENSE in the repository root for full details.
*/ */
import { observeParticipantEvents } from "@livekit/components-core";
import { import {
ConnectionState,
type BaseKeyProvider, type BaseKeyProvider,
ConnectionState,
type E2EEOptions, type E2EEOptions,
ExternalE2EEKeyProvider, ExternalE2EEKeyProvider,
type Room as LivekitRoom,
type LocalParticipant, type LocalParticipant,
ParticipantEvent,
RemoteParticipant, RemoteParticipant,
type Participant, type Room as LivekitRoom,
} from "livekit-client"; } from "livekit-client";
import E2EEWorker from "livekit-client/e2ee-worker?worker"; import E2EEWorker from "livekit-client/e2ee-worker?worker";
import { import {
ClientEvent, ClientEvent,
type EventTimelineSetHandlerMap,
EventType,
type Room as MatrixRoom,
RoomEvent,
type RoomMember, type RoomMember,
RoomStateEvent, RoomStateEvent,
SyncState, SyncState,
type Room as MatrixRoom,
type EventTimelineSetHandlerMap,
EventType,
RoomEvent,
} from "matrix-js-sdk"; } from "matrix-js-sdk";
import { deepCompare } from "matrix-js-sdk/lib/utils"; import { deepCompare } from "matrix-js-sdk/lib/utils";
import { import {
BehaviorSubject,
EMPTY,
NEVER,
type Observable,
Subject,
combineLatest, combineLatest,
concat, concat,
distinctUntilChanged, distinctUntilChanged,
EMPTY,
endWith, endWith,
filter, filter,
from, from,
@@ -45,6 +38,8 @@ import {
ignoreElements, ignoreElements,
map, map,
merge, merge,
NEVER,
type Observable,
of, of,
pairwise, pairwise,
race, race,
@@ -53,6 +48,7 @@ import {
skip, skip,
skipWhile, skipWhile,
startWith, startWith,
Subject,
switchAll, switchAll,
switchMap, switchMap,
switchScan, switchScan,
@@ -90,7 +86,6 @@ import {
finalizeValue, finalizeValue,
pauseWhen, pauseWhen,
} from "../utils/observable"; } from "../utils/observable";
import { ObservableScope } from "./ObservableScope";
import { import {
duplicateTiles, duplicateTiles,
multiSfu, multiSfu,
@@ -114,11 +109,10 @@ import {
type ReactionInfo, type ReactionInfo,
type ReactionOption, type ReactionOption,
} from "../reactions"; } from "../reactions";
import { observeSpeaker$ } from "./observeSpeaker";
import { shallowEquals } from "../utils/array"; import { shallowEquals } from "../utils/array";
import { calculateDisplayName, shouldDisambiguate } from "../utils/displayname"; import { calculateDisplayName, shouldDisambiguate } from "../utils/displayname";
import { type MediaDevices } from "./MediaDevices"; import { type MediaDevices } from "./MediaDevices";
import { constant, type Behavior } from "./Behavior"; import { type Behavior, constant } from "./Behavior";
import { import {
enterRTCSession, enterRTCSession,
getLivekitAlias, getLivekitAlias,
@@ -137,6 +131,8 @@ import { type ProcessorState } from "../livekit/TrackProcessorContext";
import { ElementWidgetActions, widget } from "../widget"; import { ElementWidgetActions, widget } from "../widget";
import { PublishConnection } from "./PublishConnection.ts"; import { PublishConnection } from "./PublishConnection.ts";
import { type Async, async$, mapAsync, ready } from "./Async"; import { type Async, async$, mapAsync, ready } from "./Async";
import { sharingScreen$, UserMedia } from "./UserMedia.ts";
import { ScreenShare } from "./ScreenShare.ts";
export interface CallViewModelOptions { export interface CallViewModelOptions {
encryptionSystem: EncryptionSystem; encryptionSystem: EncryptionSystem;
@@ -297,117 +293,6 @@ interface LayoutScanState {
tiles: TileStore; tiles: TileStore;
} }
class UserMedia {
private readonly scope = new ObservableScope();
public readonly vm: UserMediaViewModel;
private readonly participant$: BehaviorSubject<
LocalParticipant | RemoteParticipant | undefined
>;
public readonly speaker$: Behavior<boolean>;
public readonly presenter$: Behavior<boolean>;
public constructor(
public readonly id: string,
member: RoomMember,
participant: LocalParticipant | RemoteParticipant | undefined,
encryptionSystem: EncryptionSystem,
livekitRoom: LivekitRoom,
mediaDevices: MediaDevices,
pretendToBeDisconnected$: Behavior<boolean>,
displayname$: Observable<string>,
handRaised$: Observable<Date | null>,
reaction$: Observable<ReactionOption | null>,
) {
this.participant$ = new BehaviorSubject(participant);
if (participant?.isLocal) {
this.vm = new LocalUserMediaViewModel(
this.id,
member,
this.participant$ as Behavior<LocalParticipant>,
encryptionSystem,
livekitRoom,
mediaDevices,
this.scope.behavior(displayname$),
this.scope.behavior(handRaised$),
this.scope.behavior(reaction$),
);
} else {
this.vm = new RemoteUserMediaViewModel(
id,
member,
this.participant$.asObservable() as Observable<
RemoteParticipant | undefined
>,
encryptionSystem,
livekitRoom,
pretendToBeDisconnected$,
this.scope.behavior(displayname$),
this.scope.behavior(handRaised$),
this.scope.behavior(reaction$),
);
}
this.speaker$ = this.scope.behavior(observeSpeaker$(this.vm.speaking$));
this.presenter$ = this.scope.behavior(
this.participant$.pipe(
switchMap((p) => (p === undefined ? of(false) : sharingScreen$(p))),
),
);
}
public updateParticipant(
newParticipant: LocalParticipant | RemoteParticipant | undefined,
): void {
if (this.participant$.value !== newParticipant) {
// Update the BehaviourSubject in the UserMedia.
this.participant$.next(newParticipant);
}
}
public destroy(): void {
this.scope.end();
this.vm.destroy();
}
}
class ScreenShare {
private readonly scope = new ObservableScope();
public readonly vm: ScreenShareViewModel;
private readonly participant$: BehaviorSubject<
LocalParticipant | RemoteParticipant
>;
public constructor(
id: string,
member: RoomMember,
participant: LocalParticipant | RemoteParticipant,
encryptionSystem: EncryptionSystem,
livekitRoom: LivekitRoom,
pretendToBeDisconnected$: Behavior<boolean>,
displayName$: Observable<string>,
) {
this.participant$ = new BehaviorSubject(participant);
this.vm = new ScreenShareViewModel(
id,
member,
this.participant$.asObservable(),
encryptionSystem,
livekitRoom,
pretendToBeDisconnected$,
this.scope.behavior(displayName$),
participant.isLocal,
);
}
public destroy(): void {
this.scope.end();
this.vm.destroy();
}
}
type MediaItem = UserMedia | ScreenShare; type MediaItem = UserMedia | ScreenShare;
function getRoomMemberFromRtcMember( function getRoomMemberFromRtcMember(
@@ -432,16 +317,6 @@ function getRoomMemberFromRtcMember(
return { id, member }; return { id, member };
} }
function sharingScreen$(p: Participant): Observable<boolean> {
return observeParticipantEvents(
p,
ParticipantEvent.TrackPublished,
ParticipantEvent.TrackUnpublished,
ParticipantEvent.LocalTrackPublished,
ParticipantEvent.LocalTrackUnpublished,
).pipe(map((p) => p.isScreenShareEnabled));
}
export class CallViewModel extends ViewModel { export class CallViewModel extends ViewModel {
private readonly urlParams = getUrlParams(); private readonly urlParams = getUrlParams();
@@ -2013,16 +1888,12 @@ export class CallViewModel extends ViewModel {
this.scope.reconcile(this.localTransport$, async (localTransport) => { this.scope.reconcile(this.localTransport$, async (localTransport) => {
if (localTransport?.state === "ready") { if (localTransport?.state === "ready") {
try { try {
await enterRTCSession( await enterRTCSession(this.matrixRTCSession, localTransport.value, {
this.matrixRTCSession, encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE,
localTransport.value, useExperimentalToDeviceTransport: true,
{ useNewMembershipManager: true,
encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE, useMultiSfu: multiSfu.value$.value,
useExperimentalToDeviceTransport: true, });
useNewMembershipManager: true,
useMultiSfu: multiSfu.value$.value
}
);
} catch (e) { } catch (e) {
logger.error("Error entering RTC session", e); logger.error("Error entering RTC session", e);
} }

54
src/state/ScreenShare.ts Normal file
View File

@@ -0,0 +1,54 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { ObservableScope } from "./ObservableScope.ts";
import { ScreenShareViewModel } from "./MediaViewModel.ts";
import { BehaviorSubject, type Observable } from "rxjs";
import {
LocalParticipant,
RemoteParticipant,
type Room as LivekitRoom,
} from "livekit-client";
import type { RoomMember } from "matrix-js-sdk";
import type { EncryptionSystem } from "../e2ee/sharedKeyManagement.ts";
import type { Behavior } from "./Behavior.ts";
// TODO Document this
export class ScreenShare {
private readonly scope = new ObservableScope();
public readonly vm: ScreenShareViewModel;
private readonly participant$: BehaviorSubject<
LocalParticipant | RemoteParticipant
>;
public constructor(
id: string,
member: RoomMember,
participant: LocalParticipant | RemoteParticipant,
encryptionSystem: EncryptionSystem,
livekitRoom: LivekitRoom,
pretendToBeDisconnected$: Behavior<boolean>,
displayName$: Observable<string>,
) {
this.participant$ = new BehaviorSubject(participant);
this.vm = new ScreenShareViewModel(
id,
member,
this.participant$.asObservable(),
encryptionSystem,
livekitRoom,
pretendToBeDisconnected$,
this.scope.behavior(displayName$),
participant.isLocal,
);
}
public destroy(): void {
this.scope.end();
this.vm.destroy();
}
}

117
src/state/UserMedia.ts Normal file
View File

@@ -0,0 +1,117 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { ObservableScope } from "./ObservableScope.ts";
import {
LocalUserMediaViewModel,
RemoteUserMediaViewModel,
UserMediaViewModel,
} from "./MediaViewModel.ts";
import { BehaviorSubject, map, type Observable, of, switchMap } from "rxjs";
import {
LocalParticipant,
Participant,
ParticipantEvent,
RemoteParticipant,
type Room as LivekitRoom,
} from "livekit-client";
import type { Behavior } from "./Behavior.ts";
import type { RoomMember } from "matrix-js-sdk";
import type { EncryptionSystem } from "../e2ee/sharedKeyManagement.ts";
import type { MediaDevices } from "./MediaDevices.ts";
import type { ReactionOption } from "../reactions";
import { observeSpeaker$ } from "./observeSpeaker.ts";
import { observeParticipantEvents } from "@livekit/components-core";
/**
* TODO Document this
*/
export class UserMedia {
private readonly scope = new ObservableScope();
public readonly vm: UserMediaViewModel;
private readonly participant$: BehaviorSubject<
LocalParticipant | RemoteParticipant | undefined
>;
public readonly speaker$: Behavior<boolean>;
public readonly presenter$: Behavior<boolean>;
public constructor(
public readonly id: string,
member: RoomMember,
participant: LocalParticipant | RemoteParticipant | undefined,
encryptionSystem: EncryptionSystem,
livekitRoom: LivekitRoom,
mediaDevices: MediaDevices,
pretendToBeDisconnected$: Behavior<boolean>,
displayname$: Observable<string>,
handRaised$: Observable<Date | null>,
reaction$: Observable<ReactionOption | null>,
) {
this.participant$ = new BehaviorSubject(participant);
if (participant?.isLocal) {
this.vm = new LocalUserMediaViewModel(
this.id,
member,
this.participant$ as Behavior<LocalParticipant>,
encryptionSystem,
livekitRoom,
mediaDevices,
this.scope.behavior(displayname$),
this.scope.behavior(handRaised$),
this.scope.behavior(reaction$),
);
} else {
this.vm = new RemoteUserMediaViewModel(
id,
member,
this.participant$.asObservable() as Observable<
RemoteParticipant | undefined
>,
encryptionSystem,
livekitRoom,
pretendToBeDisconnected$,
this.scope.behavior(displayname$),
this.scope.behavior(handRaised$),
this.scope.behavior(reaction$),
);
}
this.speaker$ = this.scope.behavior(observeSpeaker$(this.vm.speaking$));
this.presenter$ = this.scope.behavior(
this.participant$.pipe(
switchMap((p) => (p === undefined ? of(false) : sharingScreen$(p))),
),
);
}
public updateParticipant(
newParticipant: LocalParticipant | RemoteParticipant | undefined,
): void {
if (this.participant$.value !== newParticipant) {
// Update the BehaviourSubject in the UserMedia.
this.participant$.next(newParticipant);
}
}
public destroy(): void {
this.scope.end();
this.vm.destroy();
}
}
export function sharingScreen$(p: Participant): Observable<boolean> {
return observeParticipantEvents(
p,
ParticipantEvent.TrackPublished,
ParticipantEvent.TrackUnpublished,
ParticipantEvent.LocalTrackPublished,
ParticipantEvent.LocalTrackUnpublished,
).pipe(map((p) => p.isScreenShareEnabled));
}