Don't render audio from participants that aren't meant to be publishing

This commit is contained in:
Robin
2025-09-25 21:29:02 -04:00
parent 4980d8a622
commit 0759f9b27d
4 changed files with 122 additions and 98 deletions

View File

@@ -480,7 +480,7 @@ export class CallViewModel extends ViewModel {
*/
// Note that MatrixRTCSession already filters the call memberships by users
// that are joined to the room; we don't need to perform extra filtering here.
public readonly memberships$ = this.scope.behavior(
private readonly memberships$ = this.scope.behavior(
fromEvent(
this.matrixRTCSession,
MatrixRTCSessionEvent.MembershipsChanged,
@@ -679,16 +679,19 @@ export class CallViewModel extends ViewModel {
// in a split-brained state.
private readonly pretendToBeDisconnected$ = this.reconnecting$;
private readonly participants$ = this.scope.behavior<
public readonly participantsByRoom$ = this.scope.behavior<
{
participant: LocalParticipant | RemoteParticipant;
member: RoomMember;
livekitRoom: LivekitRoom;
url: string;
participants: {
participant: LocalParticipant | RemoteParticipant;
member: RoomMember;
}[];
}[]
>(
from(this.localConnection)
combineLatest([this.localConnection, this.localFocus])
.pipe(
switchMap((localConnection) => {
switchMap(([localConnection, localFocus]) => {
const memberError = (): never => {
throw new Error("No room member for call membership");
};
@@ -696,32 +699,41 @@ export class CallViewModel extends ViewModel {
participant: localConnection.livekitRoom.localParticipant,
member:
this.matrixRoom.getMember(this.userId ?? "") ?? memberError(),
livekitRoom: localConnection.livekitRoom,
};
return this.remoteConnections$.pipe(
switchMap((connections) =>
combineLatest(
[localConnection, ...connections.values()].map((c) =>
[
[localFocus.livekit_service_url, localConnection] as const,
...connections,
].map(([url, c]) =>
c.publishingParticipants$.pipe(
map((ps) =>
ps.map(({ participant, membership }) => ({
map((ps) => {
const participants: {
participant: LocalParticipant | RemoteParticipant;
member: RoomMember;
}[] = ps.map(({ participant, membership }) => ({
participant,
member:
getRoomMemberFromRtcMember(
membership,
this.matrixRoom,
)?.member ?? memberError(),
}));
if (c === localConnection)
participants.push(localParticipant);
return {
livekitRoom: c.livekitRoom,
})),
),
url,
participants,
};
}),
),
),
),
),
map((remoteParticipants) => [
localParticipant,
...remoteParticipants.flat(1),
]),
);
}),
)
@@ -798,7 +810,7 @@ export class CallViewModel extends ViewModel {
*/
private readonly mediaItems$ = this.scope.behavior<MediaItem[]>(
combineLatest([
this.participants$,
this.participantsByRoom$,
duplicateTiles.value$,
this.memberships$,
showNonMemberTiles.value$,
@@ -806,71 +818,75 @@ export class CallViewModel extends ViewModel {
scan(
(
prevItems,
[participants, duplicateTiles, memberships, showNonMemberTiles],
[participantsByRoom, duplicateTiles, memberships, showNonMemberTiles],
) => {
const newItems: Map<string, UserMedia | ScreenShare> = new Map(
function* (this: CallViewModel): Iterable<[string, MediaItem]> {
for (const { participant, member, livekitRoom } 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;
}
}
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),
),
),
];
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;
}
}
if (participant?.isScreenShareEnabled) {
const screenShareId = `${mediaId}:screen-share`;
yield [
screenShareId,
prevItems.get(screenShareId) ??
new ScreenShare(
screenShareId,
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 [
screenShareId,
prevItems.get(screenShareId) ??
new ScreenShare(
screenShareId,
member,
participant,
this.options.encryptionSystem,
livekitRoom,
this.pretendToBeDisconnected$,
this.memberDisplaynames$.pipe(
map((m) => m.get(matrixId) ?? "[👻]"),
),
),
];
}
}
}
}