From f2e5cdbb5bc0385314968dbad0457f02d581d371 Mon Sep 17 00:00:00 2001 From: Timo K Date: Fri, 8 Aug 2025 16:41:06 +0200 Subject: [PATCH] Make last once leave logic based on matrix user id (was participant id before) Signed-off-by: Timo K --- src/room/CallEventAudioRenderer.tsx | 4 +-- src/state/CallViewModel.ts | 53 ++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/room/CallEventAudioRenderer.tsx b/src/room/CallEventAudioRenderer.tsx index a0d685ff..a39da82a 100644 --- a/src/room/CallEventAudioRenderer.tsx +++ b/src/room/CallEventAudioRenderer.tsx @@ -60,7 +60,7 @@ export function CallEventAudioRenderer({ const audioEngineRef = useLatest(audioEngineCtx); useEffect(() => { - const joinSub = vm.memberChanges$ + const joinSub = vm.participantChanges$ .pipe( filter( ({ joined, ids }) => @@ -72,7 +72,7 @@ export function CallEventAudioRenderer({ void audioEngineRef.current?.playSound("join"); }); - const leftSub = vm.memberChanges$ + const leftSub = vm.participantChanges$ .pipe( filter( ({ ids, left }) => diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index a3a4c31d..70183a37 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -728,7 +728,14 @@ export class CallViewModel extends ViewModel { ), ); - public readonly memberChanges$ = this.userMedia$.pipe( + /** + * This observable tracks the currently connected participants. + * + * - Each participant has one livekit connection + * - Each participant has a corresponding MatrixRTC membership state event + * - There can be multiple participants for one matrix user. + */ + public readonly participantChanges$ = this.userMedia$.pipe( map((mediaItems) => mediaItems.map((m) => m.id)), scan( (prev, ids) => { @@ -740,11 +747,49 @@ export class CallViewModel extends ViewModel { ), ); - public readonly allOthersLeft$ = this.memberChanges$.pipe( + /** + * This observable tracks the matrix users that are currently in the call. + * There can be just one matrix user with multiple participants (see also participantChanges$) + */ + public readonly matrixUserChanges$ = this.userMedia$.pipe( map( - ({ ids, left }) => - ids.length === 1 && ids.includes("local:0") && left.length > 0, + (mediaItems) => + new Set( + mediaItems + .map((m) => m.vm.member?.userId) + .filter((id) => id !== undefined), + ), ), + scan< + Set, + { + userIds: Set; + joinedUserIds: Set; + leftUserIds: Set; + } + >( + (prevState, userIds) => { + const left = new Set( + [...prevState.userIds].filter((id) => !userIds.has(id)), + ); + const joined = new Set( + [...userIds].filter((id) => !prevState.userIds.has(id)), + ); + return { userIds: userIds, joinedUserIds: joined, leftUserIds: left }; + }, + { userIds: new Set(), joinedUserIds: new Set(), leftUserIds: new Set() }, + ), + ); + + public readonly allOthersLeft$ = this.matrixUserChanges$.pipe( + map(({ userIds, leftUserIds }) => { + const userId = this.matrixRTCSession.room.client.getUserId(); + if (!userId) { + logger.warn("Could access client.getUserId to compute allOthersLeft"); + return false; + } + return userIds.size === 1 && userIds.has(userId) && leftUserIds.size > 0; + }), startWith(false), distinctUntilChanged(), );