The source of the local participant is the createLocalMembership$ and
not the MatrixLivekitMembers! Co-authored-by: Valere <bill.carson@valrsoft.com>
This commit is contained in:
@@ -74,16 +74,16 @@
|
|||||||
"matrix_id": "Matrix ID: {{id}}",
|
"matrix_id": "Matrix ID: {{id}}",
|
||||||
"matrixRTCMode": {
|
"matrixRTCMode": {
|
||||||
"Comptibility": {
|
"Comptibility": {
|
||||||
"label": "Compatibility: state events & multi SFU",
|
"description": "Compatible with homeservers that do not support sticky events (but all other EC clients are v0.17.0 or later)",
|
||||||
"description": "Compatible with homeservers that do not support sticky events (but all other EC clients are v0.17.0 or later)"
|
"label": "Compatibility: state events & multi SFU"
|
||||||
},
|
},
|
||||||
"Legacy": {
|
"Legacy": {
|
||||||
"label": "Legacy: state events & oldest membership SFU",
|
"description": "Compatible with old versions of EC that do not support multi SFU",
|
||||||
"description": "Compatible with old versions of EC that do not support multi SFU"
|
"label": "Legacy: state events & oldest membership SFU"
|
||||||
},
|
},
|
||||||
"Matrix_2_0": {
|
"Matrix_2_0": {
|
||||||
"label": "Matrix 2.0: sticky events & multi SFU",
|
"description": "Compatible only with homservers supporting sticky events and all EC clients v0.17.0 or later",
|
||||||
"description": "Compatible only with homservers supporting sticky events and all EC clients v0.17.0 or later"
|
"label": "Matrix 2.0: sticky events & multi SFU"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute_all_audio": "Mute all audio (participants, reactions, join sounds)",
|
"mute_all_audio": "Mute all audio (participants, reactions, join sounds)",
|
||||||
|
|||||||
@@ -103,7 +103,10 @@ import {
|
|||||||
} from "../SessionBehaviors.ts";
|
} from "../SessionBehaviors.ts";
|
||||||
import { ECConnectionFactory } from "./remoteMembers/ConnectionFactory.ts";
|
import { ECConnectionFactory } from "./remoteMembers/ConnectionFactory.ts";
|
||||||
import { createConnectionManager$ } from "./remoteMembers/ConnectionManager.ts";
|
import { createConnectionManager$ } from "./remoteMembers/ConnectionManager.ts";
|
||||||
import { createMatrixLivekitMembers$ } from "./remoteMembers/MatrixLivekitMembers.ts";
|
import {
|
||||||
|
createMatrixLivekitMembers$,
|
||||||
|
type MatrixLivekitMember,
|
||||||
|
} from "./remoteMembers/MatrixLivekitMembers.ts";
|
||||||
import {
|
import {
|
||||||
createCallNotificationLifecycle$,
|
createCallNotificationLifecycle$,
|
||||||
createReceivedDecline$,
|
createReceivedDecline$,
|
||||||
@@ -269,6 +272,38 @@ export class CallViewModel {
|
|||||||
options: this.connectOptions$,
|
options: this.connectOptions$,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private localRtcMembership$ = this.scope.behavior(
|
||||||
|
this.memberships$.pipe(
|
||||||
|
map(
|
||||||
|
(memberships) =>
|
||||||
|
memberships.value.find(
|
||||||
|
(membership) =>
|
||||||
|
membership.userId === this.userId &&
|
||||||
|
membership.deviceId === this.deviceId,
|
||||||
|
) ?? null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
private localMatrixLivekitMemberUninitialized = {
|
||||||
|
membership$: this.localRtcMembership$,
|
||||||
|
participant$: this.localMembership.participant$,
|
||||||
|
connection$: this.localMembership.connection$,
|
||||||
|
userId: this.userId,
|
||||||
|
};
|
||||||
|
|
||||||
|
private localMatrixLivekitMember$: Behavior<MatrixLivekitMember | null> =
|
||||||
|
this.scope.behavior(
|
||||||
|
this.localRtcMembership$.pipe(
|
||||||
|
switchMap((membership) => {
|
||||||
|
if (!membership) return of(null);
|
||||||
|
return of(
|
||||||
|
// casting is save here since we know that localRtcMembership$ is !== null since we reached this case.
|
||||||
|
this.localMatrixLivekitMemberUninitialized as MatrixLivekitMember,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
private callLifecycle = createCallNotificationLifecycle$({
|
private callLifecycle = createCallNotificationLifecycle$({
|
||||||
@@ -283,6 +318,7 @@ export class CallViewModel {
|
|||||||
localUser: { userId: this.userId, deviceId: this.deviceId },
|
localUser: { userId: this.userId, deviceId: this.deviceId },
|
||||||
});
|
});
|
||||||
public autoLeave$ = this.callLifecycle.autoLeave$;
|
public autoLeave$ = this.callLifecycle.autoLeave$;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -377,12 +413,10 @@ export class CallViewModel {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
private roomMembers$ = createRoomMembers$(this.scope, this.matrixRoom);
|
|
||||||
|
|
||||||
private matrixMemberMetadataStore = createMatrixMemberMetadata$(
|
private matrixMemberMetadataStore = createMatrixMemberMetadata$(
|
||||||
this.scope,
|
this.scope,
|
||||||
this.scope.behavior(this.memberships$.pipe(map((mems) => mems.value))),
|
this.scope.behavior(this.memberships$.pipe(map((mems) => mems.value))),
|
||||||
this.roomMembers$,
|
createRoomMembers$(this.scope, this.matrixRoom),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -390,22 +424,55 @@ export class CallViewModel {
|
|||||||
*/
|
*/
|
||||||
// TODO this also needs the local participant to be added.
|
// TODO this also needs the local participant to be added.
|
||||||
private readonly userMedia$ = this.scope.behavior<UserMedia[]>(
|
private readonly userMedia$ = this.scope.behavior<UserMedia[]>(
|
||||||
combineLatest([this.matrixLivekitMembers$, duplicateTiles.value$]).pipe(
|
combineLatest([
|
||||||
|
this.localMatrixLivekitMember$,
|
||||||
|
this.matrixLivekitMembers$,
|
||||||
|
duplicateTiles.value$,
|
||||||
|
]).pipe(
|
||||||
// Generate a collection of MediaItems from the list of expected (whether
|
// Generate a collection of MediaItems from the list of expected (whether
|
||||||
// present or missing) LiveKit participants.
|
// present or missing) LiveKit participants.
|
||||||
generateItems(
|
generateItems(
|
||||||
function* ([{ value: matrixLivekitMembers }, duplicateTiles]) {
|
function* ([
|
||||||
|
localMatrixLivekitMember,
|
||||||
|
{ value: matrixLivekitMembers },
|
||||||
|
duplicateTiles,
|
||||||
|
]) {
|
||||||
|
// add local member if available
|
||||||
|
if (localMatrixLivekitMember) {
|
||||||
|
const {
|
||||||
|
userId,
|
||||||
|
participant$,
|
||||||
|
connection$,
|
||||||
|
// membership$,
|
||||||
|
} = localMatrixLivekitMember;
|
||||||
|
const participantId = participant$.value?.identity; // should be membership$.value.membershipID which is not optional
|
||||||
|
// const participantId = membership$.value.membershipID;
|
||||||
|
if (participantId) {
|
||||||
|
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
||||||
|
yield {
|
||||||
|
keys: [dup, participantId, userId, participant$, connection$],
|
||||||
|
data: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add remote members that are available
|
||||||
for (const {
|
for (const {
|
||||||
participantId,
|
|
||||||
userId,
|
userId,
|
||||||
participant$,
|
participant$,
|
||||||
connection$,
|
connection$,
|
||||||
} of matrixLivekitMembers)
|
// membership$
|
||||||
for (let dup = 0; dup < 1 + duplicateTiles; dup++)
|
} of matrixLivekitMembers) {
|
||||||
|
const participantId = participant$.value?.identity;
|
||||||
|
// const participantId = membership$.value?.identity;
|
||||||
|
if (!participantId) continue;
|
||||||
|
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
||||||
yield {
|
yield {
|
||||||
keys: [dup, participantId, userId, participant$, connection$],
|
keys: [dup, participantId, userId, participant$, connection$],
|
||||||
data: undefined,
|
data: undefined,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
scope,
|
scope,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
type E2EEOptions,
|
type E2EEOptions,
|
||||||
type Participant,
|
type Participant,
|
||||||
ParticipantEvent,
|
ParticipantEvent,
|
||||||
|
type LocalParticipant,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { observeParticipantEvents } from "@livekit/components-core";
|
import { observeParticipantEvents } from "@livekit/components-core";
|
||||||
import {
|
import {
|
||||||
@@ -54,6 +55,7 @@ import { getUrlParams } from "../../../UrlParams.ts";
|
|||||||
import { PosthogAnalytics } from "../../../analytics/PosthogAnalytics.ts";
|
import { PosthogAnalytics } from "../../../analytics/PosthogAnalytics.ts";
|
||||||
import { MatrixRTCMode } from "../../../settings/settings.ts";
|
import { MatrixRTCMode } from "../../../settings/settings.ts";
|
||||||
import { Config } from "../../../config/Config.ts";
|
import { Config } from "../../../config/Config.ts";
|
||||||
|
import { type Connection } from "../remoteMembers/Connection.ts";
|
||||||
|
|
||||||
export enum LivekitState {
|
export enum LivekitState {
|
||||||
Uninitialized = "uninitialized",
|
Uninitialized = "uninitialized",
|
||||||
@@ -82,8 +84,8 @@ type LocalMemberMatrixState =
|
|||||||
| { state: MatrixState.Disconnected };
|
| { state: MatrixState.Disconnected };
|
||||||
|
|
||||||
export interface LocalMemberConnectionState {
|
export interface LocalMemberConnectionState {
|
||||||
livekit$: BehaviorSubject<LocalMemberLivekitState>;
|
livekit$: Behavior<LocalMemberLivekitState>;
|
||||||
matrix$: BehaviorSubject<LocalMemberMatrixState>;
|
matrix$: Behavior<LocalMemberMatrixState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -145,7 +147,8 @@ export const createLocalMembership$ = ({
|
|||||||
// Use null here since behavior cannot be initialised with undefined.
|
// Use null here since behavior cannot be initialised with undefined.
|
||||||
sharingScreen$: Behavior<boolean | null>;
|
sharingScreen$: Behavior<boolean | null>;
|
||||||
toggleScreenSharing: (() => void) | null;
|
toggleScreenSharing: (() => void) | null;
|
||||||
|
participant$: Behavior<LocalParticipant | null>;
|
||||||
|
connection$: Behavior<Connection | null>;
|
||||||
// deprecated fields
|
// deprecated fields
|
||||||
/** @deprecated use state instead*/
|
/** @deprecated use state instead*/
|
||||||
homeserverConnected$: Behavior<boolean>;
|
homeserverConnected$: Behavior<boolean>;
|
||||||
@@ -317,6 +320,7 @@ export const createLocalMembership$ = ({
|
|||||||
state.livekit$.next({ state: LivekitState.Error, error });
|
state.livekit$.next({ state: LivekitState.Error, error });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
combineLatest([localTransport$, connectRequested$]).subscribe(
|
combineLatest([localTransport$, connectRequested$]).subscribe(
|
||||||
([transport, connectRequested]) => {
|
([transport, connectRequested]) => {
|
||||||
if (
|
if (
|
||||||
@@ -515,6 +519,9 @@ export const createLocalMembership$ = ({
|
|||||||
alternativeScreenshareToggle,
|
alternativeScreenshareToggle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const participant$ = scope.behavior(
|
||||||
|
connection$.pipe(map((c) => c?.livekitRoom.localParticipant ?? null)),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
startTracks,
|
startTracks,
|
||||||
requestConnect,
|
requestConnect,
|
||||||
@@ -526,6 +533,8 @@ export const createLocalMembership$ = ({
|
|||||||
configError$,
|
configError$,
|
||||||
sharingScreen$,
|
sharingScreen$,
|
||||||
toggleScreenSharing,
|
toggleScreenSharing,
|
||||||
|
participant$,
|
||||||
|
connection$,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -389,15 +389,17 @@ 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: PublishingParticipant[][] = [];
|
const observedPublishers: PublishingParticipant[][] = [];
|
||||||
const s = connection.participants$.subscribe((publishers) => {
|
const s = connection.remoteParticipantsWithTracks$.subscribe(
|
||||||
observedPublishers.push(publishers);
|
(publishers) => {
|
||||||
if (publishers.some((p) => p.identity === "@bob:example.org:DEV111")) {
|
observedPublishers.push(publishers);
|
||||||
bobIsAPublisher.resolve();
|
if (publishers.some((p) => p.identity === "@bob:example.org:DEV111")) {
|
||||||
}
|
bobIsAPublisher.resolve();
|
||||||
if (publishers.some((p) => p.identity === "@dan:example.org:DEV333")) {
|
}
|
||||||
danIsAPublisher.resolve();
|
if (publishers.some((p) => p.identity === "@dan:example.org:DEV333")) {
|
||||||
}
|
danIsAPublisher.resolve();
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
onTestFinished(() => s.unsubscribe());
|
onTestFinished(() => s.unsubscribe());
|
||||||
// The publishingParticipants$ observable is derived from the current members of the
|
// The publishingParticipants$ observable is derived from the current members of the
|
||||||
// livekitRoom and the rtc membership in order to publish the members that are publishing
|
// livekitRoom and the rtc membership in order to publish the members that are publishing
|
||||||
@@ -513,9 +515,11 @@ describe("Publishing participants observations", () => {
|
|||||||
const connection = setupRemoteConnection();
|
const connection = setupRemoteConnection();
|
||||||
|
|
||||||
let observedPublishers: PublishingParticipant[][] = [];
|
let observedPublishers: PublishingParticipant[][] = [];
|
||||||
const s = connection.participants$.subscribe((publishers) => {
|
const s = connection.remoteParticipantsWithTracks$.subscribe(
|
||||||
observedPublishers.push(publishers);
|
(publishers) => {
|
||||||
});
|
observedPublishers.push(publishers);
|
||||||
|
},
|
||||||
|
);
|
||||||
onTestFinished(() => s.unsubscribe());
|
onTestFinished(() => s.unsubscribe());
|
||||||
|
|
||||||
let participants: RemoteParticipant[] = [
|
let participants: RemoteParticipant[] = [
|
||||||
|
|||||||
@@ -179,12 +179,13 @@ export class Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An observable of the participants that are publishing on this connection.
|
* An observable of the participants that are publishing on this connection. (Excluding our local participant)
|
||||||
* 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 remoteParticipantsWithTracks$: Behavior<
|
||||||
public readonly participants$: Behavior<PublishingParticipant[]>;
|
PublishingParticipant[]
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The media transport to connect to.
|
* The media transport to connect to.
|
||||||
@@ -211,7 +212,9 @@ export class Connection {
|
|||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
this.participants$ = scope.behavior(
|
// REMOTE participants with track!!!
|
||||||
|
// this.remoteParticipantsWithTracks$
|
||||||
|
this.remoteParticipantsWithTracks$ = scope.behavior(
|
||||||
// only tracks remote participants
|
// only tracks remote participants
|
||||||
connectedParticipantsObserver(this.livekitRoom, {
|
connectedParticipantsObserver(this.livekitRoom, {
|
||||||
additionalRoomEvents: [
|
additionalRoomEvents: [
|
||||||
@@ -219,10 +222,11 @@ export class Connection {
|
|||||||
RoomEvent.TrackUnpublished,
|
RoomEvent.TrackUnpublished,
|
||||||
],
|
],
|
||||||
}).pipe(
|
}).pipe(
|
||||||
map((participants) => [
|
map((participants) => {
|
||||||
this.livekitRoom.localParticipant,
|
return participants.filter(
|
||||||
...participants,
|
(participant) => participant.getTrackPublications().length > 0,
|
||||||
]),
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export class ConnectionManagerData {
|
|||||||
|
|
||||||
public getConnectionForTransport(
|
public getConnectionForTransport(
|
||||||
transport: LivekitTransport,
|
transport: LivekitTransport,
|
||||||
): Connection | undefined {
|
): Connection | null {
|
||||||
return this.store.get(this.getKey(transport))?.[0];
|
return this.store.get(this.getKey(transport))?.[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getParticipantForTransport(
|
public getParticipantForTransport(
|
||||||
@@ -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.participants$.pipe(
|
return connection.remoteParticipantsWithTracks$.pipe(
|
||||||
map((participants) => ({
|
map((participants) => ({
|
||||||
connection,
|
connection,
|
||||||
participants,
|
participants,
|
||||||
|
|||||||
@@ -30,13 +30,14 @@ const logger = rootLogger.getChild("MatrixLivekitMembers");
|
|||||||
* or if it has no livekit transport at all.
|
* or if it has no livekit transport at all.
|
||||||
*/
|
*/
|
||||||
export interface MatrixLivekitMember {
|
export interface MatrixLivekitMember {
|
||||||
participantId: string;
|
|
||||||
userId: string;
|
|
||||||
membership$: Behavior<CallMembership>;
|
membership$: Behavior<CallMembership>;
|
||||||
participant$: Behavior<
|
participant$: Behavior<
|
||||||
LocalLivekitParticipant | RemoteLivekitParticipant | null
|
LocalLivekitParticipant | RemoteLivekitParticipant | null
|
||||||
>;
|
>;
|
||||||
connection$: Behavior<Connection | undefined>;
|
connection$: Behavior<Connection | null>;
|
||||||
|
// participantId: string; We do not want a participantId here since it will be generated by the jwt
|
||||||
|
// TODO decide if we can also drop the userId. Its in the matrix membership anyways.
|
||||||
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -96,7 +97,7 @@ export function createMatrixLivekitMembers$({
|
|||||||
participants.find((p) => p.identity == participantId) ?? null;
|
participants.find((p) => p.identity == participantId) ?? null;
|
||||||
const connection = transport
|
const connection = transport
|
||||||
? managerData.getConnectionForTransport(transport)
|
? managerData.getConnectionForTransport(transport)
|
||||||
: undefined;
|
: null;
|
||||||
|
|
||||||
yield {
|
yield {
|
||||||
keys: [participantId, membership.userId],
|
keys: [participantId, membership.userId],
|
||||||
|
|||||||
Reference in New Issue
Block a user