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.
This commit is contained in:
Robin
2025-09-26 13:26:42 -04:00
parent dbdf853d55
commit a4a0a58a72
5 changed files with 59 additions and 199 deletions

View File

@@ -74,7 +74,6 @@
"matrix_id": "Matrix ID: {{id}}", "matrix_id": "Matrix ID: {{id}}",
"mute_all_audio": "Mute all audio (participants, reactions, join sounds)", "mute_all_audio": "Mute all audio (participants, reactions, join sounds)",
"show_connection_stats": "Show connection statistics", "show_connection_stats": "Show connection statistics",
"show_non_member_tiles": "Show tiles for non-member media",
"url_params": "URL parameters", "url_params": "URL parameters",
"use_new_membership_manager": "Use the new implementation of the call MembershipManager", "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" "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"

View File

@@ -13,7 +13,6 @@ import {
useSetting, useSetting,
duplicateTiles as duplicateTilesSetting, duplicateTiles as duplicateTilesSetting,
debugTileLayout as debugTileLayoutSetting, debugTileLayout as debugTileLayoutSetting,
showNonMemberTiles as showNonMemberTilesSetting,
showConnectionStats as showConnectionStatsSetting, showConnectionStats as showConnectionStatsSetting,
useNewMembershipManager as useNewMembershipManagerSetting, useNewMembershipManager as useNewMembershipManagerSetting,
useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting, useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting,
@@ -35,9 +34,6 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRooms }) => {
const [debugTileLayout, setDebugTileLayout] = useSetting( const [debugTileLayout, setDebugTileLayout] = useSetting(
debugTileLayoutSetting, debugTileLayoutSetting,
); );
const [showNonMemberTiles, setShowNonMemberTiles] = useSetting(
showNonMemberTilesSetting,
);
const [showConnectionStats, setShowConnectionStats] = useSetting( const [showConnectionStats, setShowConnectionStats] = useSetting(
showConnectionStatsSetting, showConnectionStatsSetting,
@@ -128,20 +124,6 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRooms }) => {
} }
/> />
</FieldRow> </FieldRow>
<FieldRow>
<InputField
id="showNonMemberTiles"
type="checkbox"
label={t("developer_mode.show_non_member_tiles")}
checked={!!showNonMemberTiles}
onChange={useCallback(
(event: ChangeEvent<HTMLInputElement>): void => {
setShowNonMemberTiles(event.target.checked);
},
[setShowNonMemberTiles],
)}
/>
</FieldRow>
<FieldRow> <FieldRow>
<InputField <InputField
id="showConnectionStats" id="showConnectionStats"

View File

@@ -76,10 +76,6 @@ export const developerMode = new Setting("developer-settings-tab", false);
export const duplicateTiles = new Setting("duplicate-tiles", 0); export const duplicateTiles = new Setting("duplicate-tiles", 0);
export const showNonMemberTiles = new Setting<boolean>(
"show-non-member-tiles",
false,
);
export const debugTileLayout = new Setting("debug-tile-layout", false); export const debugTileLayout = new Setting("debug-tile-layout", false);
export const showConnectionStats = new Setting<boolean>( export const showConnectionStats = new Setting<boolean>(

View File

@@ -69,7 +69,6 @@ import {
} from "../livekit/useECConnectionState"; } from "../livekit/useECConnectionState";
import { E2eeType } from "../e2ee/e2eeType"; import { E2eeType } from "../e2ee/e2eeType";
import type { RaisedHandInfo } from "../reactions"; import type { RaisedHandInfo } from "../reactions";
import { showNonMemberTiles } from "../settings/settings";
import { import {
alice, alice,
aliceDoppelganger, 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", () => { it("should show at least one tile per MatrixRTCSession", () => {
withTestScheduler(({ behavior, expectObservable }) => { withTestScheduler(({ behavior, expectObservable }) => {
// iterate through some combinations of MatrixRTC memberships // iterate through some combinations of MatrixRTC memberships

View File

@@ -92,7 +92,6 @@ import {
duplicateTiles, duplicateTiles,
playReactionsSound, playReactionsSound,
showReactions, showReactions,
showNonMemberTiles,
} from "../settings/settings"; } from "../settings/settings";
import { isFirefox } from "../Platform"; import { isFirefox } from "../Platform";
import { setPipEnabled$ } from "../controls"; import { setPipEnabled$ } from "../controls";
@@ -812,152 +811,84 @@ export class CallViewModel extends ViewModel {
this.participantsByRoom$, this.participantsByRoom$,
duplicateTiles.value$, duplicateTiles.value$,
this.memberships$, this.memberships$,
showNonMemberTiles.value$,
]).pipe( ]).pipe(
scan( scan((prevItems, [participantsByRoom, duplicateTiles, memberships]) => {
( const newItems: Map<string, UserMedia | ScreenShare> = new Map(
prevItems, function* (this: CallViewModel): Iterable<[string, MediaItem]> {
[participantsByRoom, duplicateTiles, memberships, showNonMemberTiles], for (const { livekitRoom, participants } of participantsByRoom) {
) => { for (const { participant, member } of participants) {
const newItems: Map<string, UserMedia | ScreenShare> = new Map( const matrixId = participant.isLocal
function* (this: CallViewModel): Iterable<[string, MediaItem]> { ? "local"
for (const { livekitRoom, participants } of participantsByRoom) { : participant.identity;
for (const { participant, member } of participants) {
const matrixId = participant.isLocal
? "local"
: participant.identity;
for (let i = 0; i < 1 + duplicateTiles; i++) { for (let i = 0; i < 1 + duplicateTiles; i++) {
const mediaId = `${matrixId}:${i}`; const mediaId = `${matrixId}:${i}`;
let prevMedia = prevItems.get(mediaId); let prevMedia = prevItems.get(mediaId);
if (prevMedia && prevMedia instanceof UserMedia) { if (prevMedia && prevMedia instanceof UserMedia) {
prevMedia.updateParticipant(participant); prevMedia.updateParticipant(participant);
if (prevMedia.vm.member === undefined) { if (prevMedia.vm.member === undefined) {
// We have a previous media created because of the `debugShowNonMember` flag. // We have a previous media created because of the `debugShowNonMember` flag.
// In this case we actually replace the media item. // In this case we actually replace the media item.
// This "hack" never occurs if we do not use the `debugShowNonMember` debugging // 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 // option and if we always find a room member for each rtc member (which also
// only fails if we have a fundamental problem) // only fails if we have a fundamental problem)
prevMedia = undefined; 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 [ yield [
mediaId, screenShareId,
// We create UserMedia with or without a participant. prevItems.get(screenShareId) ??
// This will be the initial value of a BehaviourSubject. new ScreenShare(
// Once a participant appears we will update the BehaviourSubject. (see above) screenShareId,
prevMedia ??
new UserMedia(
mediaId,
member, member,
participant, participant,
this.options.encryptionSystem, this.options.encryptionSystem,
livekitRoom, livekitRoom,
this.mediaDevices,
this.pretendToBeDisconnected$, this.pretendToBeDisconnected$,
this.memberDisplaynames$.pipe( this.memberDisplaynames$.pipe(
map((m) => m.get(matrixId) ?? "[👻]"), 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) for (const [id, t] of prevItems) if (!newItems.has(id)) t.destroy();
// Those items should not be rendered, they are participants in LiveKit that do not have a corresponding return newItems;
// MatrixRTC members. This cannot be any good: }, new Map<string, MediaItem>()),
// - 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<string, MediaItem>(),
),
map((mediaItems) => [...mediaItems.values()]), map((mediaItems) => [...mediaItems.values()]),
finalizeValue((ts) => { finalizeValue((ts) => {
for (const t of ts) t.destroy(); for (const t of ts) t.destroy();