make it run

This commit is contained in:
Timo K
2025-11-10 15:55:01 +01:00
parent 93659931ca
commit 93c4dc5beb
6 changed files with 80 additions and 34 deletions

View File

@@ -268,19 +268,15 @@ export class CallViewModel {
}); });
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// CallNotificationLifecycle
// consider inlining these!!!
private sentCallNotification$ = createSentCallNotification$(
this.scope,
this.matrixRTCSession,
);
private receivedDecline$ = createReceivedDecline$(this.matrixRoom);
private callLifecycle = createCallNotificationLifecycle$({ private callLifecycle = createCallNotificationLifecycle$({
scope: this.scope, scope: this.scope,
memberships$: this.memberships$, memberships$: this.memberships$,
sentCallNotification$: this.sentCallNotification$, sentCallNotification$: createSentCallNotification$(
receivedDecline$: this.receivedDecline$, this.scope,
this.matrixRTCSession,
),
receivedDecline$: createReceivedDecline$(this.matrixRoom),
options: this.options, options: this.options,
localUser: { userId: this.userId, deviceId: this.deviceId }, localUser: { userId: this.userId, deviceId: this.deviceId },
}); });
@@ -331,24 +327,44 @@ export class CallViewModel {
public readonly audioParticipants$ = this.scope.behavior( public readonly audioParticipants$ = this.scope.behavior(
this.matrixLivekitMembers$.pipe( this.matrixLivekitMembers$.pipe(
switchMap((membersWithEpoch) => {
const members = membersWithEpoch.value;
const a$ = combineLatest(
members.map((member) =>
combineLatest([member.connection$, member.participant$]).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 { url, livekitRoom, participant: participant.identity };
}),
),
),
);
return a$;
}),
map((members) => map((members) =>
members.value.reduce<AudioLivekitItem[]>((acc, curr) => { members.reduce<AudioLivekitItem[]>((acc, curr) => {
const url = curr.connection?.transport.livekit_service_url; if (!curr) return acc;
const livekitRoom = curr.connection?.livekitRoom;
const participant = curr.participant?.identity;
if (!url || !livekitRoom || !participant) return acc; const existing = acc.find((item) => item.url === curr.url);
const existing = acc.find((item) => item.url === url);
if (existing) { if (existing) {
existing.participants.push(participant); existing.participants.push(curr.participant);
} else { } else {
acc.push({ livekitRoom, participants: [participant], url }); acc.push({
livekitRoom: curr.livekitRoom,
participants: [curr.participant],
url: curr.url,
});
} }
return acc; return acc;
}, []), }, []),
), ),
), ),
[],
); );
public readonly handsRaised$ = this.scope.behavior( public readonly handsRaised$ = this.scope.behavior(

View File

@@ -16,14 +16,9 @@ import { type MatrixClient } from "matrix-js-sdk";
import { combineLatest, distinctUntilChanged, first, from, map } from "rxjs"; import { combineLatest, distinctUntilChanged, first, from, map } from "rxjs";
import { logger } from "matrix-js-sdk/lib/logger"; import { logger } from "matrix-js-sdk/lib/logger";
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
import { deepCompare } from "matrix-js-sdk/lib/utils";
import { type Behavior } from "../../Behavior.ts"; import { type Behavior } from "../../Behavior.ts";
import { import { type Epoch, type ObservableScope } from "../../ObservableScope.ts";
Epoch,
mapEpoch,
type ObservableScope,
} from "../../ObservableScope.ts";
import { Config } from "../../../config/Config.ts"; import { Config } from "../../../config/Config.ts";
import { MatrixRTCTransportMissingError } from "../../../utils/errors.ts"; import { MatrixRTCTransportMissingError } from "../../../utils/errors.ts";
import { getSFUConfigWithOpenID } from "../../../livekit/openIDSFU.ts"; import { getSFUConfigWithOpenID } from "../../../livekit/openIDSFU.ts";

View File

@@ -18,7 +18,7 @@ import {
RoomEvent, RoomEvent,
} from "livekit-client"; } from "livekit-client";
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
import { BehaviorSubject, type Observable } from "rxjs"; import { BehaviorSubject, map, type Observable } from "rxjs";
import { type Logger } from "matrix-js-sdk/lib/logger"; import { type Logger } from "matrix-js-sdk/lib/logger";
import { import {
@@ -184,7 +184,7 @@ export class Connection {
* 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 participantsWithTrack$: Behavior<PublishingParticipant[]>; public readonly participants$: Behavior<PublishingParticipant[]>;
/** /**
* The media transport to connect to. * The media transport to connect to.
@@ -211,13 +211,19 @@ export class Connection {
this.transport = transport; this.transport = transport;
this.client = client; this.client = client;
this.participantsWithTrack$ = scope.behavior( this.participants$ = scope.behavior(
// only tracks remote participants
connectedParticipantsObserver(this.livekitRoom, { connectedParticipantsObserver(this.livekitRoom, {
additionalRoomEvents: [ additionalRoomEvents: [
RoomEvent.TrackPublished, RoomEvent.TrackPublished,
RoomEvent.TrackUnpublished, RoomEvent.TrackUnpublished,
], ],
}), }).pipe(
map((participants) => [
this.livekitRoom.localParticipant,
...participants,
]),
),
[], [],
); );

View File

@@ -181,7 +181,7 @@ export function createConnectionManager$({
// Map the connections to list of {connection, participants}[] // Map the connections to list of {connection, participants}[]
const listOfConnectionsWithPublishingParticipants = const listOfConnectionsWithPublishingParticipants =
connections.value.map((connection) => { connections.value.map((connection) => {
return connection.participantsWithTrack$.pipe( return connection.participants$.pipe(
map((participants) => ({ map((participants) => ({
connection, connection,
participants, participants,

View File

@@ -105,6 +105,9 @@ export function createMatrixLivekitMembers$({
new Epoch([membershipsWithTransports, managerData] as const, epoch), new Epoch([membershipsWithTransports, managerData] as const, epoch),
), ),
generateItemsWithEpoch( generateItemsWithEpoch(
// Generator function.
// creates an array of `{key, data}[]`
// Each change in the keys (new key, missing key) will result in a call to the factory function.
function* ([membershipsWithTransports, managerData]) { function* ([membershipsWithTransports, managerData]) {
for (const { membership, transport } of membershipsWithTransports) { for (const { membership, transport } of membershipsWithTransports) {
// TODO! cannot use membership.membershipID yet, Currently its hardcoded by the jwt service to // TODO! cannot use membership.membershipID yet, Currently its hardcoded by the jwt service to
@@ -125,8 +128,11 @@ export function createMatrixLivekitMembers$({
}; };
} }
}, },
// Each update where the key of the generator array do not change will result in updates to the `data$` observable in the factory.
(scope, data$, participantId, userId) => { (scope, data$, participantId, userId) => {
const member = matrixRoom.getMember(userId); const member = matrixRoom.getMember(userId);
// will only get called once per `participantId, userId` pair.
// updates to data$ and as a result to displayName$ and mxcAvatarUrl$ are more frequent.
return { return {
participantId, participantId,
userId, userId,
@@ -134,11 +140,9 @@ export function createMatrixLivekitMembers$({
displayName$: scope.behavior( displayName$: scope.behavior(
displaynameMap$.pipe( displaynameMap$.pipe(
map((displayNames) => { map((displayNames) => {
const name = displayNames.get(userId); const name = displayNames.get(userId) ?? "";
if (name === undefined) { if (name === "")
logger.warn(`No display name for user ${userId}`); logger.warn(`No display name for user ${userId}`);
return "";
}
return name; return name;
}), }),
), ),

View File

@@ -6,7 +6,8 @@ Please see LICENSE in the repository root for full details.
*/ */
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { BehaviorSubject, timer } from "rxjs"; import { BehaviorSubject, combineLatest, timer } from "rxjs";
import { logger } from "matrix-js-sdk/lib/logger";
import { import {
Epoch, Epoch,
@@ -72,4 +73,28 @@ describe("Epoch", () => {
scope.behavior(a$, undefined); scope.behavior(a$, undefined);
}); });
it("diamonds emits in a predictable order", () => {
const sb$ = new BehaviorSubject("initial");
const root$ = sb$.pipe(trackEpoch());
const derivedA$ = root$.pipe(mapEpoch((e) => e + "-A"));
const derivedB$ = root$.pipe(mapEpoch((e) => e + "-B"));
combineLatest([root$, derivedB$, derivedA$]).subscribe(
([root, derivedA, derivedB]) => {
logger.log(
"combined" +
root.epoch +
root.value +
"\n" +
derivedA.epoch +
derivedA.value +
"\n" +
derivedB.epoch +
derivedB.value,
);
},
);
sb$.next("updated");
sb$.next("ANOTERUPDATE");
});
}); });