review: count as publishing even if not yet connected to LK

This commit is contained in:
Valere
2025-10-14 17:47:38 +02:00
parent b030d304df
commit a6c4fb4148
2 changed files with 43 additions and 19 deletions

View File

@@ -35,6 +35,7 @@ import type {
import { import {
type ConnectionOpts, type ConnectionOpts,
type FocusConnectionState, type FocusConnectionState,
type PublishingParticipant,
RemoteConnection, RemoteConnection,
} from "./Connection.ts"; } from "./Connection.ts";
import { ObservableScope } from "./ObservableScope.ts"; import { ObservableScope } from "./ObservableScope.ts";
@@ -454,22 +455,19 @@ describe("Publishing participants observations", () => {
const bobIsAPublisher = Promise.withResolvers<void>(); const bobIsAPublisher = Promise.withResolvers<void>();
const danIsAPublisher = Promise.withResolvers<void>(); const danIsAPublisher = Promise.withResolvers<void>();
const observedPublishers: { const observedPublishers: PublishingParticipant[][] = [];
participant: RemoteParticipant;
membership: CallMembership;
}[][] = [];
const s = connection.publishingParticipants$.subscribe((publishers) => { const s = connection.publishingParticipants$.subscribe((publishers) => {
observedPublishers.push(publishers); observedPublishers.push(publishers);
if ( if (
publishers.some( publishers.some(
(p) => p.participant.identity === "@bob:example.org:DEV111", (p) => p.participant?.identity === "@bob:example.org:DEV111",
) )
) { ) {
bobIsAPublisher.resolve(); bobIsAPublisher.resolve();
} }
if ( if (
publishers.some( publishers.some(
(p) => p.participant.identity === "@dan:example.org:DEV333", (p) => p.participant?.identity === "@dan:example.org:DEV333",
) )
) { ) {
danIsAPublisher.resolve(); danIsAPublisher.resolve();
@@ -529,7 +527,7 @@ describe("Publishing participants observations", () => {
await bobIsAPublisher.promise; await bobIsAPublisher.promise;
const publishers = observedPublishers.pop(); const publishers = observedPublishers.pop();
expect(publishers?.length).toEqual(1); expect(publishers?.length).toEqual(1);
expect(publishers?.[0].participant.identity).toEqual( expect(publishers?.[0].participant?.identity).toEqual(
"@bob:example.org:DEV111", "@bob:example.org:DEV111",
); );
@@ -546,12 +544,12 @@ describe("Publishing participants observations", () => {
expect(twoPublishers?.length).toEqual(2); expect(twoPublishers?.length).toEqual(2);
expect( expect(
twoPublishers?.some( twoPublishers?.some(
(p) => p.participant.identity === "@bob:example.org:DEV111", (p) => p.participant?.identity === "@bob:example.org:DEV111",
), ),
).toBeTruthy(); ).toBeTruthy();
expect( expect(
twoPublishers?.some( twoPublishers?.some(
(p) => p.participant.identity === "@dan:example.org:DEV333", (p) => p.participant?.identity === "@dan:example.org:DEV333",
), ),
).toBeTruthy(); ).toBeTruthy();
@@ -568,12 +566,25 @@ describe("Publishing participants observations", () => {
); );
const updatedPublishers = observedPublishers.pop(); const updatedPublishers = observedPublishers.pop();
expect(updatedPublishers?.length).toEqual(1); // Bob is not connected to the room but he is still in the rtc memberships declaring that
// he is using that focus to publish, so he should still appear as a publisher
expect(updatedPublishers?.length).toEqual(2);
const pp = updatedPublishers?.find(
(p) => p.membership.sender == "@bob:example.org",
);
expect(pp).toBeDefined();
expect(pp!.participant).not.toBeDefined();
expect( expect(
updatedPublishers?.some( updatedPublishers?.some(
(p) => p.participant.identity === "@dan:example.org:DEV333", (p) => p.participant?.identity === "@dan:example.org:DEV333",
), ),
).toBeTruthy(); ).toBeTruthy();
// Now if bob is not in the rtc memberships, he should disappear
const noBob = rtcMemberships.filter(
({ membership }) => membership.sender !== "@bob:example.org",
);
fakeMembershipsFocusMap$.next(noBob);
expect(observedPublishers.pop()?.length).toEqual(1);
}); });
it("should be scoped to parent scope", (): void => { it("should be scoped to parent scope", (): void => {
@@ -581,10 +592,7 @@ describe("Publishing participants observations", () => {
const connection = setupRemoteConnection(); const connection = setupRemoteConnection();
let observedPublishers: { let observedPublishers: PublishingParticipant[][] = [];
participant: RemoteParticipant;
membership: CallMembership;
}[][] = [];
const s = connection.publishingParticipants$.subscribe((publishers) => { const s = connection.publishingParticipants$.subscribe((publishers) => {
observedPublishers.push(publishers); observedPublishers.push(publishers);
}); });
@@ -619,7 +627,7 @@ describe("Publishing participants observations", () => {
// We should have bob has a publisher now // We should have bob has a publisher now
const publishers = observedPublishers.pop(); const publishers = observedPublishers.pop();
expect(publishers?.length).toEqual(1); expect(publishers?.length).toEqual(1);
expect(publishers?.[0].participant.identity).toEqual( expect(publishers?.[0].participant?.identity).toEqual(
"@bob:example.org:DEV111", "@bob:example.org:DEV111",
); );

View File

@@ -13,6 +13,7 @@ import {
ConnectionError, ConnectionError,
type ConnectionState, type ConnectionState,
type E2EEOptions, type E2EEOptions,
type RemoteParticipant,
Room as LivekitRoom, Room as LivekitRoom,
type RoomOptions, type RoomOptions,
} from "livekit-client"; } from "livekit-client";
@@ -64,6 +65,21 @@ export type FocusConnectionState =
} }
| { state: "Stopped"; focus: LivekitTransport }; | { state: "Stopped"; focus: LivekitTransport };
/**
* Represents participant publishing or expected to publish on the connection.
* It is paired with its associated rtc membership.
*/
export type PublishingParticipant = {
/**
* The LiveKit participant publishing on this connection, or undefined if the participant is not currently (yet) connected to the livekit room.
*/
participant: RemoteParticipant | undefined;
/**
* The rtc call membership associated with this participant.
*/
membership: CallMembership;
};
/** /**
* A connection to a Matrix RTC LiveKit backend. * A connection to a Matrix RTC LiveKit backend.
* *
@@ -183,7 +199,7 @@ export class Connection {
* This is derived from `participantsIncludingSubscribers$` and `remoteTransports$`. * This is derived from `participantsIncludingSubscribers$` and `remoteTransports$`.
* It filters the participants to only those that are associated with a membership that claims to publish on this connection. * It filters the participants to only those that are associated with a membership that claims to publish on this connection.
*/ */
public readonly publishingParticipants$; public readonly publishingParticipants$: Behavior<PublishingParticipant[]>;
/** /**
* The focus server to connect to. * The focus server to connect to.
@@ -226,10 +242,10 @@ export class Connection {
) )
// Pair with their associated LiveKit participant (if any) // Pair with their associated LiveKit participant (if any)
// Uses flatMap to filter out memberships with no associated rtc participant ([]) // Uses flatMap to filter out memberships with no associated rtc participant ([])
.flatMap((membership) => { .map((membership) => {
const id = `${membership.sender}:${membership.deviceId}`; const id = `${membership.sender}:${membership.deviceId}`;
const participant = participants.find((p) => p.identity === id); const participant = participants.find((p) => p.identity === id);
return participant ? [{ participant, membership }] : []; return { participant, membership };
}), }),
), ),
[], [],