Simplify type of audio participants exposed from CallViewModel

This commit is contained in:
Robin
2025-10-08 16:40:06 -04:00
parent e346c8c148
commit c96e81bfd3
4 changed files with 36 additions and 39 deletions

View File

@@ -23,12 +23,7 @@ import { useTracks } from "@livekit/components-react";
import { testAudioContext } from "../useAudioContext.test";
import * as MediaDevicesContext from "../MediaDevicesContext";
import { LivekitRoomAudioRenderer } from "./MatrixAudioRenderer";
import {
mockMatrixRoomMember,
mockMediaDevices,
mockRtcMembership,
mockTrack,
} from "../utils/test";
import { mockMediaDevices, mockTrack } from "../utils/test";
export const TestAudioContextConstructor = vi.fn(() => testAudioContext);
@@ -80,17 +75,11 @@ function renderTestComponent(
isLocal,
} as unknown as RemoteParticipant);
});
const participants = rtcMembers.map(({ userId, deviceId }) => {
const participants = rtcMembers.flatMap(({ userId, deviceId }) => {
const p = liveKitParticipants.find(
(p) => p.identity === `${userId}:${deviceId}`,
);
const localRtcMember = mockRtcMembership(userId, deviceId);
const member = mockMatrixRoomMember(localRtcMember);
return {
id: `${userId}:${deviceId}`,
participant: p,
member,
};
return p === undefined ? [] : [p];
});
const livekitRoom = vi.mocked<Room>({
remoteParticipants: new Map<string, Participant>(
@@ -98,9 +87,7 @@ function renderTestComponent(
),
} as unknown as Room);
tracks = participants
.filter((p) => p.participant)
.map((p) => mockTrack(p.participant!)) as TrackReference[];
tracks = participants.map((p) => mockTrack(p));
vi.mocked(useTracks).mockReturnValue(tracks);
return render(

View File

@@ -6,7 +6,10 @@ Please see LICENSE in the repository root for full details.
*/
import { getTrackReferenceId } from "@livekit/components-core";
import { type Room as LivekitRoom, type Participant } from "livekit-client";
import {
type RemoteParticipant,
type Room as LivekitRoom,
} from "livekit-client";
import { type RemoteAudioTrack, Track } from "livekit-client";
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import {
@@ -14,13 +17,13 @@ import {
AudioTrack,
type AudioTrackProps,
} from "@livekit/components-react";
import { type RoomMember } from "matrix-js-sdk";
import { logger } from "matrix-js-sdk/lib/logger";
import { useEarpieceAudioConfig } from "../MediaDevicesContext";
import { useReactiveState } from "../useReactiveState";
import * as controls from "../controls";
import {} from "@livekit/components-core";
export interface MatrixAudioRendererProps {
/**
* The service URL of the LiveKit room.
@@ -32,13 +35,7 @@ export interface MatrixAudioRendererProps {
* This list needs to be composed based on the matrixRTC members so that we do not play audio from users
* that are not expected to be in the rtc session.
*/
// TODO: Why do we have this structure? looks like we only need the valid/active participants (not the room member or id)?
participants: {
id: string;
// TODO it appears to be optional as per InCallView? but what does that mean here? a rtc member not yet joined in livekit?
participant: Participant | undefined;
member: RoomMember;
}[];
participants: RemoteParticipant[];
/**
* If set to `true`, mutes all audio tracks rendered by the component.
* @remarks
@@ -48,8 +45,7 @@ export interface MatrixAudioRendererProps {
}
/**
* The `MatrixAudioRenderer` component is a drop-in solution for adding audio to your LiveKit app.
* It takes care of handling remote participants audio tracks and makes sure that microphones and screen share are audible.
* Takes care of handling remote participants audio tracks and makes sure that microphones and screen share are audible.
*
* It also takes care of the earpiece audio configuration for iOS devices.
* This is done by using the WebAudio API to create a stereo pan effect that mimics the earpiece audio.
@@ -70,12 +66,7 @@ export function LivekitRoomAudioRenderer({
// This is the list of valid identities that are allowed to play audio.
// It is derived from the list of matrix rtc members.
const validIdentities = useMemo(
() =>
new Set(
participants
.filter(({ participant }) => participant) // filter out participants that are not yet joined in livekit
.map(({ participant }) => participant!.identity),
),
() => new Set(participants.map((p) => p.identity)),
[participants],
);
@@ -92,7 +83,7 @@ export function LivekitRoomAudioRenderer({
if (loggedInvalidIdentities.current.has(identity)) return;
logger.warn(
`[MatrixAudioRenderer] Audio track ${identity} from ${url} has no matching matrix call member`,
`current members: ${participants.map((p) => p.participant?.identity)}`,
`current members: ${participants.map((p) => p.identity)}`,
`track will not get rendered`,
);
loggedInvalidIdentities.current.add(identity);

View File

@@ -286,7 +286,7 @@ export const InCallView: FC<InCallViewProps> = ({
);
const allLivekitRooms = useBehavior(vm.allLivekitRooms$);
const participantsByRoom = useBehavior(vm.participantsByRoom$);
const audioParticipants = useBehavior(vm.audioParticipants$);
const participantCount = useBehavior(vm.participantCount$);
const reconnecting = useBehavior(vm.reconnecting$);
const windowMode = useBehavior(vm.windowMode$);
@@ -860,7 +860,7 @@ export const InCallView: FC<InCallViewProps> = ({
</Text>
)
}
{participantsByRoom.map(({ livekitRoom, url, participants }) => (
{audioParticipants.map(({ livekitRoom, url, participants }) => (
<LivekitRoomAudioRenderer
key={url}
url={url}

View File

@@ -14,7 +14,7 @@ import {
type Room as LivekitRoom,
type LocalParticipant,
ParticipantEvent,
type RemoteParticipant,
RemoteParticipant,
type Participant,
} from "livekit-client";
import E2EEWorker from "livekit-client/e2ee-worker?worker";
@@ -793,7 +793,7 @@ export class CallViewModel extends ViewModel {
* Lists, for each LiveKit room, the LiveKit participants whose media should
* be presented.
*/
public readonly participantsByRoom$ = this.scope.behavior<
private readonly participantsByRoom$ = this.scope.behavior<
{
livekitRoom: LivekitRoom;
url: string;
@@ -861,6 +861,25 @@ export class CallViewModel extends ViewModel {
.pipe(startWith([]), pauseWhen(this.pretendToBeDisconnected$)),
);
/**
* Lists, for each LiveKit room, the LiveKit participants whose audio should
* be rendered.
*/
// (This is effectively just participantsByRoom$ with a stricter type)
public readonly audioParticipants$ = this.scope.behavior(
this.participantsByRoom$.pipe(
map((data) =>
data.map(({ livekitRoom, url, participants }) => ({
livekitRoom,
url,
participants: participants.flatMap(({ participant }) =>
participant instanceof RemoteParticipant ? [participant] : [],
),
})),
),
),
);
/**
* Displaynames for each member of the call. This will disambiguate
* any displaynames that clashes with another member. Only members