From 1820cac3f66e9b9801209990d5ad5469ad21e97a Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 3 Oct 2025 19:14:48 -0400 Subject: [PATCH] Create media items for session members not joined to LiveKit --- src/state/CallViewModel.ts | 49 ++++++++++++++----------------------- src/state/Connection.ts | 14 +++++------ src/state/MediaViewModel.ts | 10 ++++---- src/tile/MediaView.tsx | 2 +- src/tile/SpotlightTile.tsx | 2 +- 5 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 8988e518..6e333bec 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -304,7 +304,7 @@ class UserMedia { public readonly presenter$: Behavior; public constructor( public readonly id: string, - member: RoomMember | undefined, + member: RoomMember, participant: LocalParticipant | RemoteParticipant | undefined, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, @@ -377,7 +377,7 @@ class ScreenShare { public constructor( id: string, - member: RoomMember | undefined, + member: RoomMember, participant: LocalParticipant | RemoteParticipant, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, @@ -799,7 +799,8 @@ export class CallViewModel extends ViewModel { livekitRoom: LivekitRoom; url: string; participants: { - participant: LocalParticipant | RemoteParticipant; + id: string; + participant: LocalParticipant | RemoteParticipant | undefined; member: RoomMember; }[]; }[] @@ -814,6 +815,7 @@ export class CallViewModel extends ViewModel { throw new Error("No room member for call membership"); }; const localParticipant = { + id: "local", participant: localConnection.livekitRoom.localParticipant, member: this.matrixRoom.getMember(this.userId ?? "") ?? memberError(), @@ -826,9 +828,14 @@ export class CallViewModel extends ViewModel { c.publishingParticipants$.pipe( map((ps) => { const participants: { - participant: LocalParticipant | RemoteParticipant; + id: string; + participant: + | LocalParticipant + | RemoteParticipant + | undefined; member: RoomMember; }[] = ps.map(({ participant, membership }) => ({ + id: `${membership.sender}:${membership.deviceId}`, participant, member: getRoomMemberFromRtcMember( @@ -929,26 +936,12 @@ export class CallViewModel extends ViewModel { const newItems: Map = new Map( function* (this: CallViewModel): Iterable<[string, MediaItem]> { for (const { livekitRoom, participants } of participantsByRoom) { - for (const { participant, member } of participants) { - const matrixId = participant.isLocal - ? "local" - : participant.identity; - + for (const { id, participant, member } of participants) { for (let i = 0; i < 1 + duplicateTiles; i++) { - const mediaId = `${matrixId}:${i}`; - let prevMedia = prevItems.get(mediaId); - if (prevMedia && prevMedia instanceof UserMedia) { + const mediaId = `${id}:${i}`; + const prevMedia = prevItems.get(mediaId); + if (prevMedia instanceof UserMedia) prevMedia.updateParticipant(participant); - if (prevMedia.vm.member === undefined) { - // TODO-MULTI-SFU: This is outdated. - // We have a previous media created because of the `debugShowNonMember` flag. - // In this case we actually replace the media item. - // This "hack" never occurs if we do not use the `debugShowNonMember` debugging - // option and if we always find a room member for each rtc member (which also - // only fails if we have a fundamental problem) - prevMedia = undefined; - } - } yield [ mediaId, @@ -965,14 +958,10 @@ export class CallViewModel extends ViewModel { this.mediaDevices, this.pretendToBeDisconnected$, this.memberDisplaynames$.pipe( - map((m) => m.get(matrixId) ?? "[👻]"), - ), - this.handsRaised$.pipe( - map((v) => v[matrixId]?.time ?? null), - ), - this.reactions$.pipe( - map((v) => v[matrixId] ?? undefined), + map((m) => m.get(id) ?? "[👻]"), ), + this.handsRaised$.pipe(map((v) => v[id]?.time ?? null)), + this.reactions$.pipe(map((v) => v[id] ?? undefined)), ), ]; @@ -989,7 +978,7 @@ export class CallViewModel extends ViewModel { livekitRoom, this.pretendToBeDisconnected$, this.memberDisplaynames$.pipe( - map((m) => m.get(matrixId) ?? "[👻]"), + map((m) => m.get(id) ?? "[👻]"), ), ), ]; diff --git a/src/state/Connection.ts b/src/state/Connection.ts index 992d8840..4908e42f 100644 --- a/src/state/Connection.ts +++ b/src/state/Connection.ts @@ -66,7 +66,7 @@ export class Connection { this.livekitAlias, ); - public readonly participantsIncludingSubscribers$; + private readonly participantsIncludingSubscribers$; public readonly publishingParticipants$; public readonly livekitRoom: LivekitRoom; @@ -105,13 +105,11 @@ export class Connection { ? [membership] : [], ) - // Find all associated publishing livekit participant objects - .flatMap((membership) => { - const participant = participants.find( - (p) => - p.identity === `${membership.sender}:${membership.deviceId}`, - ); - return participant ? [{ participant, membership }] : []; + // Pair with their associated LiveKit participant (if any) + .map((membership) => { + const id = `${membership.sender}:${membership.deviceId}`; + const participant = participants.find((p) => p.identity === id); + return { participant, membership }; }), ), [], diff --git a/src/state/MediaViewModel.ts b/src/state/MediaViewModel.ts index dc2c135a..016c6a49 100644 --- a/src/state/MediaViewModel.ts +++ b/src/state/MediaViewModel.ts @@ -255,7 +255,7 @@ abstract class BaseMediaViewModel extends ViewModel { */ // TODO: Fully separate the data layer from the UI layer by keeping the // member object internal - public readonly member: RoomMember | undefined, + public readonly member: RoomMember, // We don't necessarily have a participant if a user connects via MatrixRTC but not (yet) through // livekit. protected readonly participant$: Observable< @@ -403,7 +403,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { public constructor( id: string, - member: RoomMember | undefined, + member: RoomMember, participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, @@ -535,7 +535,7 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { public constructor( id: string, - member: RoomMember | undefined, + member: RoomMember, participant$: Behavior, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, @@ -641,7 +641,7 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { public constructor( id: string, - member: RoomMember | undefined, + member: RoomMember, participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, @@ -736,7 +736,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel { public constructor( id: string, - member: RoomMember | undefined, + member: RoomMember, participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom: LivekitRoom, diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx index a4fd0402..8506a650 100644 --- a/src/tile/MediaView.tsx +++ b/src/tile/MediaView.tsx @@ -32,7 +32,7 @@ interface Props extends ComponentProps { video: TrackReferenceOrPlaceholder | undefined; videoFit: "cover" | "contain"; mirror: boolean; - member: RoomMember | undefined; + member: RoomMember; videoEnabled: boolean; unencryptedWarning: boolean; encryptionStatus: EncryptionStatus; diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index 663fb912..b1a15332 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -55,7 +55,7 @@ interface SpotlightItemBaseProps { targetHeight: number; video: TrackReferenceOrPlaceholder | undefined; videoEnabled: boolean; - member: RoomMember | undefined; + member: RoomMember; unencryptedWarning: boolean; encryptionStatus: EncryptionStatus; displayName: string;