refactor extract inner classes to their own files
This commit is contained in:
@@ -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
54
src/state/ScreenShare.ts
Normal 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
117
src/state/UserMedia.ts
Normal 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));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user