Fix lints, move CallViewModel.test.ts. Fix audio renderer

This commit is contained in:
Timo K
2025-11-07 14:04:40 +01:00
parent 28047217b8
commit e741285b11
19 changed files with 71 additions and 67 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -156,7 +156,11 @@ interface LayoutScanState {
}
type MediaItem = UserMedia | ScreenShare;
type AudioLivekitItem = {
livekitRoom: LivekitRoom;
participants: string[];
url: string;
};
/**
* A view model providing all the application logic needed to show the in-call
* UI (may eventually be expanded to cover the lobby and feedback screens in the
@@ -166,8 +170,6 @@ type MediaItem = UserMedia | ScreenShare;
// state and LiveKit state. We use the common terminology of room "members", RTC
// "memberships", and LiveKit "participants".
export class CallViewModel {
private readonly urlParams = getUrlParams();
private readonly userId = this.matrixRoom.client.getUserId()!;
private readonly deviceId = this.matrixRoom.client.getDeviceId()!;
@@ -285,6 +287,7 @@ export class CallViewModel {
// ------------------------------------------------------------------------
// ROOM MEMBER tracking TODO
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private roomMembers$ = createRoomMembers$(this.scope, this.matrixRoom);
/**
* If there is a configuration error with the call (e.g. misconfigured E2EE).
@@ -311,6 +314,7 @@ export class CallViewModel {
* than whether all connections are truly up and running.
*/
// DISCUSS ? lets think why we need joined and how to do it better
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private readonly joined$ = this.localMembership.connected$;
/**
@@ -327,7 +331,23 @@ export class CallViewModel {
public readonly audioParticipants$ = this.scope.behavior(
this.matrixLivekitMembers$.pipe(
map((members) => members.value.map((m) => m.participant)),
map((members) =>
members.value.reduce<AudioLivekitItem[]>((acc, curr) => {
const url = curr.connection?.transport.livekit_service_url;
const livekitRoom = curr.connection?.livekitRoom;
const participant = curr.participant?.identity;
if (!url || !livekitRoom || !participant) return acc;
const existing = acc.find((item) => item.url === url);
if (existing) {
existing.participants.push(participant);
} else {
acc.push({ livekitRoom, participants: [participant], url });
}
return acc;
}, []),
),
),
);

View File

@@ -135,7 +135,8 @@ export const createLocalMembership$ = ({
startTracks: () => Behavior<LocalTrack[]>;
requestDisconnect: () => Observable<LocalMemberLivekitState> | null;
connectionState: LocalMemberConnectionState;
sharingScreen$: Behavior<boolean | undefined>;
// Use null here since behavior cannot be initialised with undefined.
sharingScreen$: Behavior<boolean | null>;
toggleScreenSharing: (() => void) | null;
// deprecated fields
@@ -432,7 +433,7 @@ export const createLocalMembership$ = ({
const sharingScreen$ = scope.behavior(
connection$.pipe(
switchMap((c) => {
if (!c) return of(undefined);
if (!c) return of(null);
if (c.state$.value.state === "ConnectedToLkRoom")
return observeSharingScreen$(c.livekitRoom.localParticipant);
return of(false);

View File

@@ -31,7 +31,7 @@ import {
} from "../../../livekit/TrackProcessorContext.tsx";
import { getUrlParams } from "../../../UrlParams.ts";
import { observeTrackReference$ } from "../../MediaViewModel.ts";
import { type Connection } from "../CallViewModel/remoteMembers/Connection.ts";
import { type Connection } from "../remoteMembers/Connection.ts";
import { type ObservableScope } from "../../ObservableScope.ts";
/**
@@ -64,7 +64,7 @@ export class Publisher {
const room = connection.livekitRoom;
room.setE2EEEnabled(e2eeLivekitOptions !== undefined)?.catch((e) => {
room.setE2EEEnabled(e2eeLivekitOptions !== undefined)?.catch((e: Error) => {
this.logger?.error("Failed to set E2EE enabled on room", e);
});
@@ -249,7 +249,7 @@ export class Publisher {
) {
lkRoom
.switchActiveDevice(kind, device.id)
.catch((e) =>
.catch((e: Error) =>
this.logger?.error(
`Failed to sync ${kind} device with LiveKit`,
e,

View File

@@ -22,6 +22,7 @@ import {
type Room as LivekitRoom,
RoomEvent,
type RoomOptions,
ConnectionState as LivekitConnectionState,
} from "livekit-client";
import fetchMock from "fetch-mock";
import EventEmitter from "events";
@@ -32,6 +33,7 @@ import type {
LivekitTransport,
} from "matrix-js-sdk/lib/matrixrtc";
import {
Connection,
type ConnectionOpts,
type ConnectionState,
type PublishingParticipant,
@@ -103,7 +105,7 @@ function setupTest(): void {
disconnect: vi.fn(),
remoteParticipants: new Map(),
localParticipant: fakeLocalParticipant,
state: ConnectionState.Disconnected,
state: LivekitConnectionState.Disconnected,
on: fakeRoomEventEmiter.on.bind(fakeRoomEventEmiter),
off: fakeRoomEventEmiter.off.bind(fakeRoomEventEmiter),
addListener: fakeRoomEventEmiter.addListener.bind(fakeRoomEventEmiter),
@@ -115,11 +117,10 @@ function setupTest(): void {
} as unknown as LivekitRoom);
}
function setupRemoteConnection(): RemoteConnection {
function setupRemoteConnection(): Connection {
const opts: ConnectionOpts = {
client: client,
transport: livekitFocus,
remoteTransports$: fakeMembershipsFocusMap$,
scope: testScope,
livekitRoomFactory: () => fakeLivekitRoom,
};
@@ -136,7 +137,7 @@ function setupRemoteConnection(): RemoteConnection {
fakeLivekitRoom.connect.mockResolvedValue(undefined);
return new RemoteConnection(opts, undefined);
return new Connection(opts);
}
afterEach(() => {
@@ -152,11 +153,10 @@ describe("Start connection states", () => {
const opts: ConnectionOpts = {
client: client,
transport: livekitFocus,
remoteTransports$: fakeMembershipsFocusMap$,
scope: testScope,
livekitRoomFactory: () => fakeLivekitRoom,
};
const connection = new RemoteConnection(opts, undefined);
const connection = new Connection(opts);
expect(connection.state$.getValue().state).toEqual("Initialized");
});
@@ -168,12 +168,11 @@ describe("Start connection states", () => {
const opts: ConnectionOpts = {
client: client,
transport: livekitFocus,
remoteTransports$: fakeMembershipsFocusMap$,
scope: testScope,
livekitRoomFactory: () => fakeLivekitRoom,
};
const connection = new RemoteConnection(opts, undefined);
const connection = new Connection(opts, undefined);
const capturedStates: ConnectionState[] = [];
const s = connection.state$.subscribe((value) => {
@@ -221,12 +220,11 @@ describe("Start connection states", () => {
const opts: ConnectionOpts = {
client: client,
transport: livekitFocus,
remoteTransports$: fakeMembershipsFocusMap$,
scope: testScope,
livekitRoomFactory: () => fakeLivekitRoom,
};
const connection = new RemoteConnection(opts, undefined);
const connection = new Connection(opts, undefined);
const capturedStates: ConnectionState[] = [];
const s = connection.state$.subscribe((value) => {
@@ -278,12 +276,11 @@ describe("Start connection states", () => {
const opts: ConnectionOpts = {
client: client,
transport: livekitFocus,
remoteTransports$: fakeMembershipsFocusMap$,
scope: testScope,
livekitRoomFactory: () => fakeLivekitRoom,
};
const connection = new RemoteConnection(opts, undefined);
const connection = new Connection(opts, undefined);
const capturedStates: ConnectionState[] = [];
const s = connection.state$.subscribe((value) => {

View File

@@ -55,7 +55,7 @@ interface Props {
// => Extract an AvatarService instead?
// Better with just `getMember`
matrixRoom: Pick<MatrixRoom, "getMember"> & NodeStyleEventEmitter;
roomMember$: Behavior<Pick<RoomMember, "userId" | "getMxcAvatarUrl">>;
// roomMember$: Behavior<Pick<RoomMember, "userId" | "getMxcAvatarUrl">>;
}
// Alternative structure idea:
// const livekitMatrixMember$ = (callMemberships$,connectionManager,scope): Observable<MatrixLivekitMember[]> => {

View File

@@ -14,7 +14,7 @@ import {
} from "matrix-js-sdk";
import EventEmitter from "events";
import { ObservableScope } from "../../ObservableScope.ts";
import { ObservableScope, trackEpoch } from "../../ObservableScope.ts";
import type { Room as MatrixRoom } from "matrix-js-sdk/lib/models/room";
import { mockCallMembership, withTestScheduler } from "../../../utils/test.ts";
import { memberDisplaynames$ } from "./displayname.ts";
@@ -90,9 +90,7 @@ test("should always have our own user", () => {
mockMatrixRoom,
cold("a", {
a: [],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
expectObservable(dn$).toBe("a", {
@@ -125,9 +123,7 @@ test("should get displayName for users", () => {
mockCallMembership("@alice:example.com", "DEVICE1"),
mockCallMembership("@bob:example.com", "DEVICE1"),
],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
expectObservable(dn$).toBe("a", {
@@ -149,9 +145,7 @@ test("should use userId if no display name", () => {
mockMatrixRoom,
cold("a", {
a: [mockCallMembership("@no-name:foo.bar", "D000")],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
expectObservable(dn$).toBe("a", {
@@ -178,9 +172,7 @@ test("should disambiguate users with same display name", () => {
mockCallMembership("@carl:example.com", "C000"),
mockCallMembership("@evil:example.com", "E000"),
],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
expectObservable(dn$).toBe("a", {
@@ -209,9 +201,7 @@ test("should disambiguate when needed", () => {
mockCallMembership("@bob:example.com", "DEVICE1"),
mockCallMembership("@bob:foo.bar", "BOB000"),
],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
expectObservable(dn$).toBe("ab", {
@@ -241,9 +231,7 @@ test.skip("should keep disambiguated name when other leave", () => {
mockCallMembership("@bob:foo.bar", "BOB000"),
],
b: [mockCallMembership("@bob:example.com", "DEVICE1")],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
expectObservable(dn$).toBe("ab", {
@@ -272,9 +260,7 @@ test("should disambiguate on name change", () => {
mockCallMembership("@bob:example.com", "B000"),
mockCallMembership("@carl:example.com", "C000"),
],
}),
"@local:example.com",
"DEVICE000",
}).pipe(trackEpoch()),
);
schedule("-a", {