mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-07 05:47:03 +00:00
Fix lints, move CallViewModel.test.ts. Fix audio renderer
This commit is contained in:
@@ -13,7 +13,7 @@ import { type ReactNode } from "react";
|
||||
|
||||
import { ReactionToggleButton } from "./ReactionToggleButton";
|
||||
import { ElementCallReactionEventType } from "../reactions";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||
import { alice, local, localRtcMember } from "../utils/test-fixtures";
|
||||
import { type MockRTCSession } from "../utils/test";
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
ReactionsRowSize,
|
||||
} from "../reactions";
|
||||
import { Modal } from "../Modal";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { useBehavior } from "../useBehavior";
|
||||
|
||||
interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
|
||||
import { useClientState } from "../ClientContext";
|
||||
import { ElementCallReactionEventType, type ReactionOption } from ".";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { useBehavior } from "../useBehavior";
|
||||
|
||||
interface ReactionsSenderContextType {
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
local,
|
||||
localRtcMember,
|
||||
} from "../utils/test-fixtures";
|
||||
import { MAX_PARTICIPANT_COUNT_FOR_SOUND } from "../state/CallViewModel";
|
||||
import { MAX_PARTICIPANT_COUNT_FOR_SOUND } from "../state/CallViewModel/CallViewModel";
|
||||
|
||||
vitest.mock("livekit-client/e2ee-worker?worker");
|
||||
vitest.mock("../useAudioContext");
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { type ReactNode, useEffect } from "react";
|
||||
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import joinCallSoundMp3 from "../sound/join_call.mp3";
|
||||
import joinCallSoundOgg from "../sound/join_call.ogg";
|
||||
import leftCallSoundMp3 from "../sound/left_call.mp3";
|
||||
|
||||
@@ -734,7 +734,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
<ShareScreenButton
|
||||
key="share_screen"
|
||||
className={styles.shareScreen}
|
||||
enabled={sharingScreen}
|
||||
disabled={sharingScreen === undefined}
|
||||
enabled={sharingScreen === true}
|
||||
onClick={vm.toggleScreenSharing}
|
||||
onTouchEnd={onControlsTouchEnd}
|
||||
data-testid="incall_screenshare"
|
||||
@@ -822,7 +823,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
key={url}
|
||||
url={url}
|
||||
livekitRoom={livekitRoom}
|
||||
validIdentities={participants.map((p) => p.identity)}
|
||||
validIdentities={participants}
|
||||
muted={muteAllAudio}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import { useAudioContext } from "../useAudioContext";
|
||||
import { GenericReaction, ReactionSet } from "../reactions";
|
||||
import { prefetchSounds } from "../soundUtils";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||
import {
|
||||
alice,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { GenericReaction, ReactionSet } from "../reactions";
|
||||
import { useAudioContext } from "../useAudioContext";
|
||||
import { prefetchSounds } from "../soundUtils";
|
||||
import { useLatest } from "../useLatest";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
|
||||
const soundMap = Object.fromEntries([
|
||||
...ReactionSet.filter((v) => v.sound !== undefined).map((v) => [
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
||||
import { type ReactNode } from "react";
|
||||
|
||||
import styles from "./ReactionsOverlay.module.css";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { useBehavior } from "../useBehavior";
|
||||
|
||||
export function ReactionsOverlay({ vm }: { vm: CallViewModel }): ReactNode {
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
duplicateTiles as duplicateTilesSetting,
|
||||
debugTileLayout as debugTileLayoutSetting,
|
||||
showConnectionStats as showConnectionStatsSetting,
|
||||
multiSfu as multiSfuSetting,
|
||||
muteAllAudio as muteAllAudioSetting,
|
||||
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
||||
matrixRTCMode as matrixRTCModeSetting,
|
||||
|
||||
@@ -50,7 +50,7 @@ import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
||||
|
||||
import { CallViewModel, type CallViewModelOptions } from "./CallViewModel";
|
||||
import { type Layout } from "./layout-types";
|
||||
import { type Layout } from "../layout-types.ts";
|
||||
import {
|
||||
mockLocalParticipant,
|
||||
mockMatrixRoom,
|
||||
@@ -65,9 +65,9 @@ import {
|
||||
testScope,
|
||||
mockLivekitRoom,
|
||||
exampleTransport,
|
||||
} from "../utils/test";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import type { RaisedHandInfo, ReactionInfo } from "../reactions";
|
||||
} from "../../utils/test.ts";
|
||||
import { E2eeType } from "../../e2ee/e2eeType.ts";
|
||||
import type { RaisedHandInfo, ReactionInfo } from "../../reactions/index.ts";
|
||||
import {
|
||||
alice,
|
||||
aliceDoppelganger,
|
||||
@@ -89,15 +89,15 @@ import {
|
||||
localId,
|
||||
localRtcMember,
|
||||
localRtcMemberDevice2,
|
||||
} from "../utils/test-fixtures";
|
||||
import { MediaDevices } from "./MediaDevices";
|
||||
import { getValue } from "../utils/observable";
|
||||
import { type Behavior, constant } from "./Behavior";
|
||||
import type { ProcessorState } from "../livekit/TrackProcessorContext.tsx";
|
||||
} from "../../utils/test-fixtures.ts";
|
||||
import { MediaDevices } from "../MediaDevices.ts";
|
||||
import { getValue } from "../../utils/observable.ts";
|
||||
import { type Behavior, constant } from "../Behavior.ts";
|
||||
import type { ProcessorState } from "../../livekit/TrackProcessorContext.tsx";
|
||||
import {
|
||||
type ElementCallError,
|
||||
MatrixRTCTransportMissingError,
|
||||
} from "../utils/errors.ts";
|
||||
} from "../../utils/errors.ts";
|
||||
|
||||
vi.mock("rxjs", async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
@@ -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;
|
||||
}, []),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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[]> => {
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { GridTile } from "./GridTile";
|
||||
import { mockRtcMembership, createRemoteMedia } from "../utils/test";
|
||||
import { GridTileViewModel } from "../state/TileViewModel";
|
||||
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
||||
import type { CallViewModel } from "../state/CallViewModel";
|
||||
import type { CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { constant } from "../state/Behavior";
|
||||
|
||||
global.IntersectionObserver = class MockIntersectionObserver {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { E2eeType } from "../e2ee/e2eeType";
|
||||
import {
|
||||
CallViewModel,
|
||||
type CallViewModelOptions,
|
||||
} from "../state/CallViewModel";
|
||||
} from "../state/CallViewModel/CallViewModel";
|
||||
import {
|
||||
mockLivekitRoom,
|
||||
mockLocalParticipant,
|
||||
|
||||
Reference in New Issue
Block a user