Convert media view model classes to interfaces

Timo and I agreed previously that we should ditch the class pattern for view models and instead have them be interfaces which are simply created by functions. They're more straightforward to write, mock, and instantiate this way.

The code for media view models and media items is pretty much the last remaining instance of the class pattern. Since I was about to introduce a new media view model for ringing, I wanted to get this refactor out of the way first rather than add to the technical debt.

This refactor also makes things a little easier for https://github.com/element-hq/element-call/pull/3747 by extracting volume controls into their own module.
This commit is contained in:
Robin
2026-02-25 14:02:59 +01:00
parent bc238778ad
commit 6995388a29
14 changed files with 862 additions and 914 deletions

View File

@@ -53,8 +53,10 @@ import {
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
import {
LocalUserMediaViewModel,
RemoteUserMediaViewModel,
createLocalUserMedia,
createRemoteUserMedia,
type LocalUserMediaViewModel,
type RemoteUserMediaViewModel,
} from "../state/MediaViewModel";
import { E2eeType } from "../e2ee/e2eeType";
import {
@@ -323,30 +325,27 @@ export function mockLocalParticipant(
} as Partial<LocalParticipant> as LocalParticipant;
}
export function createLocalMedia(
export function mockLocalMedia(
rtcMember: CallMembership,
roomMember: Partial<RoomMember>,
localParticipant: LocalParticipant,
mediaDevices: MediaDevices,
): LocalUserMediaViewModel {
const member = mockMatrixRoomMember(rtcMember, roomMember);
return new LocalUserMediaViewModel(
testScope(),
"local",
member.userId,
rtcMember.rtcBackendIdentity,
constant(localParticipant),
{
kind: E2eeType.PER_PARTICIPANT,
},
constant(mockLivekitRoom({ localParticipant })),
constant("https://rtc-example.org"),
return createLocalUserMedia(testScope(), {
id: "local",
userId: member.userId,
rtcBackendIdentity: rtcMember.rtcBackendIdentity,
participant$: constant(localParticipant),
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
livekitRoom$: constant(mockLivekitRoom({ localParticipant })),
focusUrl$: constant("https://rtc-example.org"),
mediaDevices,
constant(member.rawDisplayName ?? "nodisplayname"),
constant(member.getMxcAvatarUrl()),
constant(null),
constant(null),
);
displayName$: constant(member.rawDisplayName ?? "nodisplayname"),
mxcAvatarUrl$: constant(member.getMxcAvatarUrl()),
handRaised$: constant(null),
reaction$: constant(null),
});
}
export function mockRemoteParticipant(
@@ -364,7 +363,7 @@ export function mockRemoteParticipant(
} as RemoteParticipant;
}
export function createRemoteMedia(
export function mockRemoteMedia(
rtcMember: CallMembership,
roomMember: Partial<RoomMember>,
participant: RemoteParticipant | null,
@@ -376,23 +375,20 @@ export function createRemoteMedia(
),
): RemoteUserMediaViewModel {
const member = mockMatrixRoomMember(rtcMember, roomMember);
return new RemoteUserMediaViewModel(
testScope(),
"remote",
member.userId,
rtcMember.rtcBackendIdentity,
constant(participant),
{
kind: E2eeType.PER_PARTICIPANT,
},
constant(livekitRoom),
constant("https://rtc-example.org"),
constant(false),
constant(member.rawDisplayName ?? "nodisplayname"),
constant(member.getMxcAvatarUrl()),
constant(null),
constant(null),
);
return createRemoteUserMedia(testScope(), {
id: "remote",
userId: member.userId,
rtcBackendIdentity: rtcMember.rtcBackendIdentity,
participant$: constant(participant),
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
livekitRoom$: constant(livekitRoom),
focusUrl$: constant("https://rtc-example.org"),
pretendToBeDisconnected$: constant(false),
displayName$: constant(member.rawDisplayName ?? "nodisplayname"),
mxcAvatarUrl$: constant(member.getMxcAvatarUrl()),
handRaised$: constant(null),
reaction$: constant(null),
});
}
export function mockConfig(