From a4a0a58a72d1fe034fc0e53669ae375fc8102ee8 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 26 Sep 2025 13:26:42 -0400 Subject: [PATCH] Remove the option to show non-member ("ghost") participants As we'd like to get the multi-SFU feature branch shipped, this is not the most important debugging tool to expend effort on at the moment. --- locales/en/app.json | 1 - src/settings/DeveloperSettingsTab.tsx | 18 --- src/settings/settings.ts | 4 - src/state/CallViewModel.test.ts | 48 ------- src/state/CallViewModel.ts | 187 ++++++++------------------ 5 files changed, 59 insertions(+), 199 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 007e372a..dc027c92 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -74,7 +74,6 @@ "matrix_id": "Matrix ID: {{id}}", "mute_all_audio": "Mute all audio (participants, reactions, join sounds)", "show_connection_stats": "Show connection statistics", - "show_non_member_tiles": "Show tiles for non-member media", "url_params": "URL parameters", "use_new_membership_manager": "Use the new implementation of the call MembershipManager", "use_to_device_key_transport": "Use to device key transport. This will fallback to room key transport when another call member sent a room key" diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index d503385b..1949ecf7 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -13,7 +13,6 @@ import { useSetting, duplicateTiles as duplicateTilesSetting, debugTileLayout as debugTileLayoutSetting, - showNonMemberTiles as showNonMemberTilesSetting, showConnectionStats as showConnectionStatsSetting, useNewMembershipManager as useNewMembershipManagerSetting, useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting, @@ -35,9 +34,6 @@ export const DeveloperSettingsTab: FC = ({ client, livekitRooms }) => { const [debugTileLayout, setDebugTileLayout] = useSetting( debugTileLayoutSetting, ); - const [showNonMemberTiles, setShowNonMemberTiles] = useSetting( - showNonMemberTilesSetting, - ); const [showConnectionStats, setShowConnectionStats] = useSetting( showConnectionStatsSetting, @@ -128,20 +124,6 @@ export const DeveloperSettingsTab: FC = ({ client, livekitRooms }) => { } /> - - ): void => { - setShowNonMemberTiles(event.target.checked); - }, - [setShowNonMemberTiles], - )} - /> - ( - "show-non-member-tiles", - false, -); export const debugTileLayout = new Setting("debug-tile-layout", false); export const showConnectionStats = new Setting( diff --git a/src/state/CallViewModel.test.ts b/src/state/CallViewModel.test.ts index b736b780..07c78ef6 100644 --- a/src/state/CallViewModel.test.ts +++ b/src/state/CallViewModel.test.ts @@ -69,7 +69,6 @@ import { } from "../livekit/useECConnectionState"; import { E2eeType } from "../e2ee/e2eeType"; import type { RaisedHandInfo } from "../reactions"; -import { showNonMemberTiles } from "../settings/settings"; import { alice, aliceDoppelganger, @@ -824,53 +823,6 @@ test("participants must have a MatrixRTCSession to be visible", () => { }); }); -test("shows participants without MatrixRTCSession when enabled in settings", () => { - try { - // enable the setting: - showNonMemberTiles.setValue(true); - withTestScheduler(({ behavior, expectObservable }) => { - const scenarioInputMarbles = " abc"; - const expectedLayoutMarbles = "abc"; - - withCallViewModel( - { - remoteParticipants$: behavior(scenarioInputMarbles, { - a: [], - b: [aliceParticipant], - c: [aliceParticipant, bobParticipant], - }), - rtcMembers$: constant([localRtcMember]), // No one else joins the MatrixRTC session - }, - (vm) => { - vm.setGridMode("grid"); - expectObservable(summarizeLayout$(vm.layout$)).toBe( - expectedLayoutMarbles, - { - a: { - type: "grid", - spotlight: undefined, - grid: ["local:0"], - }, - b: { - type: "one-on-one", - local: "local:0", - remote: `${aliceId}:0`, - }, - c: { - type: "grid", - spotlight: undefined, - grid: ["local:0", `${aliceId}:0`, `${bobId}:0`], - }, - }, - ); - }, - ); - }); - } finally { - showNonMemberTiles.setValue(showNonMemberTiles.defaultValue); - } -}); - it("should show at least one tile per MatrixRTCSession", () => { withTestScheduler(({ behavior, expectObservable }) => { // iterate through some combinations of MatrixRTC memberships diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 439d2662..7e3a5bdf 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -92,7 +92,6 @@ import { duplicateTiles, playReactionsSound, showReactions, - showNonMemberTiles, } from "../settings/settings"; import { isFirefox } from "../Platform"; import { setPipEnabled$ } from "../controls"; @@ -812,152 +811,84 @@ export class CallViewModel extends ViewModel { this.participantsByRoom$, duplicateTiles.value$, this.memberships$, - showNonMemberTiles.value$, ]).pipe( - scan( - ( - prevItems, - [participantsByRoom, duplicateTiles, memberships, showNonMemberTiles], - ) => { - 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; + scan((prevItems, [participantsByRoom, duplicateTiles, memberships]) => { + 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 (let i = 0; i < 1 + duplicateTiles; i++) { - const mediaId = `${matrixId}:${i}`; - let prevMedia = prevItems.get(mediaId); - if (prevMedia && prevMedia instanceof UserMedia) { - prevMedia.updateParticipant(participant); - if (prevMedia.vm.member === undefined) { - // 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; - } + for (let i = 0; i < 1 + duplicateTiles; i++) { + const mediaId = `${matrixId}:${i}`; + let prevMedia = prevItems.get(mediaId); + if (prevMedia && prevMedia instanceof UserMedia) { + prevMedia.updateParticipant(participant); + if (prevMedia.vm.member === undefined) { + // 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, + // We create UserMedia with or without a participant. + // This will be the initial value of a BehaviourSubject. + // Once a participant appears we will update the BehaviourSubject. (see above) + prevMedia ?? + new UserMedia( + mediaId, + member, + participant, + this.options.encryptionSystem, + livekitRoom, + 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), + ), + ), + ]; + + if (participant?.isScreenShareEnabled) { + const screenShareId = `${mediaId}:screen-share`; yield [ - mediaId, - // We create UserMedia with or without a participant. - // This will be the initial value of a BehaviourSubject. - // Once a participant appears we will update the BehaviourSubject. (see above) - prevMedia ?? - new UserMedia( - mediaId, + screenShareId, + prevItems.get(screenShareId) ?? + new ScreenShare( + screenShareId, member, participant, this.options.encryptionSystem, livekitRoom, - 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), - ), ), ]; - - if (participant?.isScreenShareEnabled) { - const screenShareId = `${mediaId}:screen-share`; - yield [ - screenShareId, - prevItems.get(screenShareId) ?? - new ScreenShare( - screenShareId, - member, - participant, - this.options.encryptionSystem, - livekitRoom, - this.pretendToBeDisconnected$, - this.memberDisplaynames$.pipe( - map((m) => m.get(matrixId) ?? "[👻]"), - ), - ), - ]; - } } } } - }.bind(this)(), - ); + } + }.bind(this)(), + ); - // Generate non member items (items without a corresponding MatrixRTC member) - // Those items should not be rendered, they are participants in LiveKit that do not have a corresponding - // MatrixRTC members. This cannot be any good: - // - A malicious user impersonates someone - // - Someone injects abusive content - // - The user cannot have encryption keys so it makes no sense to participate - // We can only trust users that have a MatrixRTC member event. - // - // This is still available as a debug option. This can be useful - // - If one wants to test scalability using the LiveKit CLI. - // - If an experimental project does not yet do the MatrixRTC bits. - // - If someone wants to debug if the LiveKit connection works but MatrixRTC room state failed to arrive. - // TODO-MULTI-SFU - // const newNonMemberItems = showNonMemberTiles - // ? new Map( - // function* ( - // this: CallViewModel, - // ): Iterable<[string, MediaItem]> { - // for (const participant of remoteParticipants) { - // for (let i = 0; i < 1 + duplicateTiles; i++) { - // const maybeNonMemberParticipantId = - // participant.identity + ":" + i; - // if (!newItems.has(maybeNonMemberParticipantId)) { - // const nonMemberId = maybeNonMemberParticipantId; - // yield [ - // nonMemberId, - // prevItems.get(nonMemberId) ?? - // new UserMedia( - // nonMemberId, - // undefined, - // participant, - // this.options.encryptionSystem, - // localConnection.livekitRoom, - // this.mediaDevices, - // this.pretendToBeDisconnected$, - // this.memberDisplaynames$.pipe( - // map( - // (m) => - // m.get(participant.identity) ?? "[👻]", - // ), - // ), - // of(null), - // of(null), - // ), - // ]; - // } - // } - // } - // }.bind(this)(), - // ) - // : new Map(); - // if (newNonMemberItems.size > 0) { - // logger.debug("Added NonMember items: ", newNonMemberItems); - // } - - const combinedNew = new Map([ - // ...newNonMemberItems.entries(), - ...newItems.entries(), - ]); - - for (const [id, t] of prevItems) - if (!combinedNew.has(id)) t.destroy(); - return combinedNew; - }, - new Map(), - ), + for (const [id, t] of prevItems) if (!newItems.has(id)) t.destroy(); + return newItems; + }, new Map()), map((mediaItems) => [...mediaItems.values()]), finalizeValue((ts) => { for (const t of ts) t.destroy();