mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Group livekitRoomItems$ by connection
The old reducer keyed items by service URL and mutated the accumulator's entries in place, so a transport without a service URL could not be represented and items were not safe to compare. Group members by their Connection object instead and carry the whole transport on the item, which the audio renderer now receives in place of a URL.
This commit is contained in:
+1
-1
@@ -231,7 +231,7 @@ export async function createMatrixRTCSdk(
|
|||||||
complete: () => {
|
complete: () => {
|
||||||
logger.info("Livekit room items subscription completed");
|
logger.info("Livekit room items subscription completed");
|
||||||
for (const item of callViewModel.livekitRoomItems$.value) {
|
for (const item of callViewModel.livekitRoomItems$.value) {
|
||||||
logger.info("unregistering room item from room", item.url);
|
logger.info("unregistering room item from room", item.transport);
|
||||||
item.livekitRoom.unregisterTextStreamHandler(TEXT_LK_TOPIC);
|
item.livekitRoom.unregisterTextStreamHandler(TEXT_LK_TOPIC);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { testAudioContext } from "../useAudioContext.test";
|
|||||||
import * as MediaDevicesContext from "../MediaDevicesContext";
|
import * as MediaDevicesContext from "../MediaDevicesContext";
|
||||||
import { LivekitRoomAudioRenderer } from "./MatrixAudioRenderer";
|
import { LivekitRoomAudioRenderer } from "./MatrixAudioRenderer";
|
||||||
import {
|
import {
|
||||||
|
exampleTransport,
|
||||||
mockLocalParticipant,
|
mockLocalParticipant,
|
||||||
mockMediaDevices,
|
mockMediaDevices,
|
||||||
mockRemoteParticipant,
|
mockRemoteParticipant,
|
||||||
@@ -118,7 +119,7 @@ function renderTestComponent(
|
|||||||
<LivekitRoomAudioRenderer
|
<LivekitRoomAudioRenderer
|
||||||
validIdentities={participants.map((p) => p.identity)}
|
validIdentities={participants.map((p) => p.identity)}
|
||||||
livekitRoom={livekitRoom}
|
livekitRoom={livekitRoom}
|
||||||
url={""}
|
transport={exampleTransport}
|
||||||
/>
|
/>
|
||||||
</MediaDevicesProvider>,
|
</MediaDevicesProvider>,
|
||||||
);
|
);
|
||||||
@@ -150,7 +151,7 @@ it("should not render or warn for the local participant", () => {
|
|||||||
<LivekitRoomAudioRenderer
|
<LivekitRoomAudioRenderer
|
||||||
validIdentities={[]}
|
validIdentities={[]}
|
||||||
livekitRoom={{ remoteParticipants: new Map() } as unknown as Room}
|
livekitRoom={{ remoteParticipants: new Map() } as unknown as Room}
|
||||||
url={""}
|
transport={exampleTransport}
|
||||||
/>
|
/>
|
||||||
</MediaDevicesProvider>,
|
</MediaDevicesProvider>,
|
||||||
);
|
);
|
||||||
@@ -177,7 +178,7 @@ it("should warn only once per unexpected participant", () => {
|
|||||||
<LivekitRoomAudioRenderer
|
<LivekitRoomAudioRenderer
|
||||||
validIdentities={[]}
|
validIdentities={[]}
|
||||||
livekitRoom={{ remoteParticipants: new Map() } as unknown as Room}
|
livekitRoom={{ remoteParticipants: new Map() } as unknown as Room}
|
||||||
url={""}
|
transport={exampleTransport}
|
||||||
/>
|
/>
|
||||||
</MediaDevicesProvider>,
|
</MediaDevicesProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
|||||||
import { useEarpieceAudioConfig } from "../MediaDevicesContext";
|
import { useEarpieceAudioConfig } from "../MediaDevicesContext";
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
import * as controls from "../controls";
|
import * as controls from "../controls";
|
||||||
|
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
|
||||||
export interface MatrixAudioRendererProps {
|
export interface MatrixAudioRendererProps {
|
||||||
/**
|
transport: LivekitTransport;
|
||||||
* The service URL of the LiveKit room.
|
|
||||||
*/
|
|
||||||
url: string;
|
|
||||||
livekitRoom: LivekitRoom;
|
livekitRoom: LivekitRoom;
|
||||||
/**
|
/**
|
||||||
* The list of participant identities to render audio for.
|
* The list of participant identities to render audio for.
|
||||||
@@ -54,7 +52,7 @@ export interface MatrixAudioRendererProps {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function LivekitRoomAudioRenderer({
|
export function LivekitRoomAudioRenderer({
|
||||||
url,
|
transport,
|
||||||
livekitRoom,
|
livekitRoom,
|
||||||
validIdentities,
|
validIdentities,
|
||||||
muted,
|
muted,
|
||||||
@@ -88,7 +86,7 @@ export function LivekitRoomAudioRenderer({
|
|||||||
warnedIdentities.current.add(identity);
|
warnedIdentities.current.add(identity);
|
||||||
// Log that there is an invalid identity, that means that someone is publishing audio that is not expected to be in the call.
|
// Log that there is an invalid identity, that means that someone is publishing audio that is not expected to be in the call.
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Audio track ${identity} from ${url} has no matching matrix call member`,
|
`Audio track ${identity} from ${JSON.stringify(transport)} has no matching matrix call member`,
|
||||||
`current members: ${validIdentities.join()}`,
|
`current members: ${validIdentities.join()}`,
|
||||||
`track will not get rendered`,
|
`track will not get rendered`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -665,10 +665,10 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
onPointerOut={onPointerOut}
|
onPointerOut={onPointerOut}
|
||||||
>
|
>
|
||||||
{header}
|
{header}
|
||||||
{audioParticipants.map(({ livekitRoom, url, participants }) => (
|
{audioParticipants.map(({ livekitRoom, transport, participants }) => (
|
||||||
<LivekitRoomAudioRenderer
|
<LivekitRoomAudioRenderer
|
||||||
key={url}
|
key={JSON.stringify(transport)}
|
||||||
url={url}
|
transport={transport}
|
||||||
livekitRoom={livekitRoom}
|
livekitRoom={livekitRoom}
|
||||||
validIdentities={participants}
|
validIdentities={participants}
|
||||||
muted={muteAllAudio}
|
muted={muteAllAudio}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ExternalE2EEKeyProvider,
|
ExternalE2EEKeyProvider,
|
||||||
type Room as LivekitRoom,
|
type Room as LivekitRoom,
|
||||||
type RoomOptions,
|
type RoomOptions,
|
||||||
|
type RemoteParticipant,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { type Room as MatrixRoom } from "matrix-js-sdk";
|
import { type Room as MatrixRoom } from "matrix-js-sdk";
|
||||||
import {
|
import {
|
||||||
@@ -276,7 +277,7 @@ interface LayoutScanState {
|
|||||||
export type LivekitRoomItem = {
|
export type LivekitRoomItem = {
|
||||||
livekitRoom: LivekitRoom;
|
livekitRoom: LivekitRoom;
|
||||||
participants: string[];
|
participants: string[];
|
||||||
url: string;
|
transport: LivekitTransport;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -728,49 +729,42 @@ export function createCallViewModel$(
|
|||||||
const allConnections$ = scope.behavior(
|
const allConnections$ = scope.behavior(
|
||||||
connectionManager.connectionManagerData$.pipe(map((d) => d.value)),
|
connectionManager.connectionManagerData$.pipe(map((d) => d.value)),
|
||||||
);
|
);
|
||||||
const livekitRoomItems$ = scope.behavior(
|
|
||||||
remoteMatrixLivekitMembers$.pipe(
|
|
||||||
switchMap((members) => {
|
|
||||||
const a$ = combineLatest(
|
|
||||||
members.value.map((member) =>
|
|
||||||
combineLatest([member.connection$, member.participant.value$]).pipe(
|
|
||||||
map(([connection, participant]) => {
|
|
||||||
// do not render audio for local participant
|
|
||||||
if (!connection || !participant || participant.isLocal)
|
|
||||||
return null;
|
|
||||||
const livekitRoom = connection.livekitRoom;
|
|
||||||
const url = connection.transport.livekit_service_url;
|
|
||||||
|
|
||||||
return {
|
const livekitRoomItems$ = scope.behavior<LivekitRoomItem[]>(
|
||||||
url,
|
remoteMatrixLivekitMembers$.pipe(
|
||||||
livekitRoom,
|
// Flatten the members to a snapshot of their current connections and
|
||||||
participant: participant.identity,
|
// participant objects
|
||||||
};
|
switchMap(({ value: members }) => {
|
||||||
}),
|
if (members.length === 0) return of([]);
|
||||||
|
return combineLatest(
|
||||||
|
members.map((m) =>
|
||||||
|
combineLatest(
|
||||||
|
[m.connection$, m.participant.value$],
|
||||||
|
(connection, participant) => ({ connection, participant }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return a$;
|
|
||||||
}),
|
}),
|
||||||
|
// Keep only those with a defined connection and participant
|
||||||
map((members) =>
|
map((members) =>
|
||||||
members.reduce<LivekitRoomItem[]>((acc, curr) => {
|
members.filter(
|
||||||
if (!curr) return acc;
|
(
|
||||||
|
m,
|
||||||
const existing = acc.find((item) => item.url === curr.url);
|
): m is { connection: Connection; participant: RemoteParticipant } =>
|
||||||
if (existing) {
|
m.connection !== null && m.participant !== null,
|
||||||
existing.participants.push(curr.participant);
|
),
|
||||||
} else {
|
),
|
||||||
acc.push({
|
// Group members by connection
|
||||||
livekitRoom: curr.livekitRoom,
|
map((members) =>
|
||||||
participants: [curr.participant],
|
[...Map.groupBy(members, (m) => m.connection)].map(
|
||||||
url: curr.url,
|
([connection, members]) => ({
|
||||||
});
|
transport: connection.transport,
|
||||||
}
|
livekitRoom: connection.livekitRoom,
|
||||||
return acc;
|
participants: members.map((m) => m.participant.identity),
|
||||||
}, []),
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handsRaised$ = scope.behavior(
|
const handsRaised$ = scope.behavior(
|
||||||
|
|||||||
Reference in New Issue
Block a user