Refactor Matrix/LiveKit session merging
- Replace MatrixLivekitItem with MatrixLivekitMember, add displayName$ and participantId, and use explicit LiveKit participant types - Make sessionBehaviors$ accept a props object and return a typed RxRtcSession - Update CallViewModel to use the new session behaviors, rebuild media items from matrixLivekitMembers, handle missing connections and use participantId-based keys - Change localMembership/localTransport to accept Behavior-based options, read options.value for enterRTCSession, and fix advertised transport selection order - Update tests and minor UI adjustments (settings modal livekitRooms stubbed) and fix JSON formatting in locales
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"
|
"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)"
|
||||||
},
|
},
|
||||||
"Legacy": {
|
"Legacy": {
|
||||||
"label": "Legacy: state events & oldest membership SFU"
|
"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"
|
||||||
},
|
},
|
||||||
"Matrix_2_0": {
|
"Matrix_2_0": {
|
||||||
"label": "Matrix 2.0: sticky events & multi SFU"
|
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute_all_audio": "Mute all audio (participants, reactions, join sounds)",
|
"mute_all_audio": "Mute all audio (participants, reactions, join sounds)",
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
},
|
},
|
||||||
reactionsReader.raisedHands$,
|
reactionsReader.raisedHands$,
|
||||||
reactionsReader.reactions$,
|
reactionsReader.reactions$,
|
||||||
trackProcessorState$,
|
scope.behavior(trackProcessorState$),
|
||||||
);
|
);
|
||||||
setVm(vm);
|
setVm(vm);
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
() => void toggleRaisedHand(),
|
() => void toggleRaisedHand(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const allLivekitRooms = useBehavior(vm.allLivekitRooms$);
|
// const allLivekitRooms = useBehavior(vm.allLivekitRooms$);
|
||||||
const audioParticipants = useBehavior(vm.audioParticipants$);
|
const audioParticipants = useBehavior(vm.audioParticipants$);
|
||||||
const participantCount = useBehavior(vm.participantCount$);
|
const participantCount = useBehavior(vm.participantCount$);
|
||||||
const reconnecting = useBehavior(vm.reconnecting$);
|
const reconnecting = useBehavior(vm.reconnecting$);
|
||||||
@@ -841,7 +841,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
onDismiss={closeSettings}
|
onDismiss={closeSettings}
|
||||||
tab={settingsTab}
|
tab={settingsTab}
|
||||||
onTabChange={setSettingsTab}
|
onTabChange={setSettingsTab}
|
||||||
livekitRooms={allLivekitRooms}
|
// TODO expose correct data to setttings modal
|
||||||
|
livekitRooms={[]}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import EventEmitter from "events";
|
|||||||
|
|
||||||
import { enterRTCSession } from "../src/rtcSessionHelpers";
|
import { enterRTCSession } from "../src/rtcSessionHelpers";
|
||||||
import { mockConfig } from "./utils/test";
|
import { mockConfig } from "./utils/test";
|
||||||
|
import { MatrixRTCMode } from "./settings/settings";
|
||||||
|
|
||||||
const USE_MUTI_SFU = false;
|
const MATRIX_RTC_MODE = MatrixRTCMode.Legacy;
|
||||||
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
|
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
|
||||||
vi.mock("./UrlParams", () => ({ getUrlParams }));
|
vi.mock("./UrlParams", () => ({ getUrlParams }));
|
||||||
|
|
||||||
@@ -94,8 +95,7 @@ test("It joins the correct Session", async () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
encryptMedia: true,
|
encryptMedia: true,
|
||||||
useMultiSfu: USE_MUTI_SFU,
|
matrixRTCMode: MATRIX_RTC_MODE,
|
||||||
preferStickyEvents: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -153,8 +153,7 @@ test("It should not fail with configuration error if homeserver config has livek
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
encryptMedia: true,
|
encryptMedia: true,
|
||||||
useMultiSfu: USE_MUTI_SFU,
|
matrixRTCMode: MATRIX_RTC_MODE,
|
||||||
preferStickyEvents: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
RoomEvent,
|
RoomEvent,
|
||||||
} from "matrix-js-sdk";
|
} from "matrix-js-sdk";
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
|
||||||
combineLatest,
|
combineLatest,
|
||||||
concat,
|
concat,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
@@ -38,7 +37,6 @@ import {
|
|||||||
of,
|
of,
|
||||||
pairwise,
|
pairwise,
|
||||||
race,
|
race,
|
||||||
repeat,
|
|
||||||
scan,
|
scan,
|
||||||
skip,
|
skip,
|
||||||
skipWhile,
|
skipWhile,
|
||||||
@@ -93,7 +91,6 @@ import {
|
|||||||
import { shallowEquals } from "../utils/array";
|
import { shallowEquals } from "../utils/array";
|
||||||
import { type MediaDevices } from "./MediaDevices";
|
import { type MediaDevices } from "./MediaDevices";
|
||||||
import { type Behavior, constant } from "./Behavior";
|
import { type Behavior, constant } from "./Behavior";
|
||||||
import { enterRTCSession } from "../rtcSessionHelpers";
|
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||||
import { type MuteStates } from "./MuteStates";
|
import { type MuteStates } from "./MuteStates";
|
||||||
@@ -112,12 +109,12 @@ import {
|
|||||||
type SpotlightPortraitLayoutMedia,
|
type SpotlightPortraitLayoutMedia,
|
||||||
} from "./layout-types.ts";
|
} from "./layout-types.ts";
|
||||||
import { type ElementCallError } from "../utils/errors.ts";
|
import { type ElementCallError } from "../utils/errors.ts";
|
||||||
import { ObservableScope } from "./ObservableScope.ts";
|
import { type ObservableScope } from "./ObservableScope.ts";
|
||||||
import { ConnectionManager } from "./remoteMembers/ConnectionManager.ts";
|
import { ConnectionManager } from "./remoteMembers/ConnectionManager.ts";
|
||||||
import { MatrixLivekitMerger } from "./remoteMembers/matrixLivekitMerger.ts";
|
import { MatrixLivekitMerger } from "./remoteMembers/matrixLivekitMerger.ts";
|
||||||
import {
|
import {
|
||||||
localMembership$,
|
localMembership$,
|
||||||
LocalMemberState,
|
type LocalMemberState,
|
||||||
} from "./localMember/LocalMembership.ts";
|
} from "./localMember/LocalMembership.ts";
|
||||||
import { localTransport$ as computeLocalTransport$ } from "./localMember/LocalTransport.ts";
|
import { localTransport$ as computeLocalTransport$ } from "./localMember/LocalTransport.ts";
|
||||||
import { sessionBehaviors$ } from "./SessionBehaviors.ts";
|
import { sessionBehaviors$ } from "./SessionBehaviors.ts";
|
||||||
@@ -195,10 +192,10 @@ export class CallViewModel {
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
private sessionBehaviors = sessionBehaviors$(
|
private sessionBehaviors = sessionBehaviors$({
|
||||||
this.scope,
|
scope: this.scope,
|
||||||
this.matrixRTCSession,
|
matrixRTCSession: this.matrixRTCSession,
|
||||||
);
|
});
|
||||||
private memberships$ = this.sessionBehaviors.memberships$;
|
private memberships$ = this.sessionBehaviors.memberships$;
|
||||||
|
|
||||||
private localTransport$ = computeLocalTransport$({
|
private localTransport$ = computeLocalTransport$({
|
||||||
@@ -211,6 +208,8 @@ export class CallViewModel {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
private connectionFactory = new ECConnectionFactory(
|
private connectionFactory = new ECConnectionFactory(
|
||||||
this.matrixRoom.client,
|
this.matrixRoom.client,
|
||||||
this.mediaDevices,
|
this.mediaDevices,
|
||||||
@@ -219,10 +218,14 @@ export class CallViewModel {
|
|||||||
getUrlParams().controlledAudioDevices,
|
getUrlParams().controlledAudioDevices,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Can contain duplicates. The connection manager will take care of this.
|
||||||
private allTransports$ = this.scope.behavior(
|
private allTransports$ = this.scope.behavior(
|
||||||
combineLatest(
|
combineLatest(
|
||||||
[this.localTransport$, this.sessionBehaviors.transports$],
|
[this.localTransport$, this.sessionBehaviors.transports$],
|
||||||
(l, t) => [...(l ? [l] : []), ...t],
|
(localTransport, transports) => {
|
||||||
|
const localTransportAsArray = localTransport ? [localTransport] : [];
|
||||||
|
return [...localTransportAsArray, ...transports];
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -232,6 +235,8 @@ export class CallViewModel {
|
|||||||
this.allTransports$,
|
this.allTransports$,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
private matrixLivekitMerger = new MatrixLivekitMerger(
|
private matrixLivekitMerger = new MatrixLivekitMerger(
|
||||||
this.scope,
|
this.scope,
|
||||||
this.sessionBehaviors.membershipsWithTransport$,
|
this.sessionBehaviors.membershipsWithTransport$,
|
||||||
@@ -240,7 +245,7 @@ export class CallViewModel {
|
|||||||
this.userId,
|
this.userId,
|
||||||
this.deviceId,
|
this.deviceId,
|
||||||
);
|
);
|
||||||
private matrixLivekitItems$ = this.matrixLivekitMerger.matrixLivekitItems$;
|
private matrixLivekitMembers$ = this.matrixLivekitMerger.matrixLivekitMember$;
|
||||||
|
|
||||||
private localMembership = localMembership$({
|
private localMembership = localMembership$({
|
||||||
scope: this.scope,
|
scope: this.scope,
|
||||||
@@ -297,12 +302,12 @@ export class CallViewModel {
|
|||||||
// down, for example, and we want to avoid making people worry that the app is
|
// down, for example, and we want to avoid making people worry that the app is
|
||||||
// in a split-brained state.
|
// in a split-brained state.
|
||||||
// DISCUSSION own membership manager ALSO this probably can be simplifis
|
// DISCUSSION own membership manager ALSO this probably can be simplifis
|
||||||
private readonly pretendToBeDisconnected$ =
|
public reconnecting$ = this.localMembership.reconnecting$;
|
||||||
this.localMembership.reconnecting$;
|
private readonly pretendToBeDisconnected$ = this.reconnecting$;
|
||||||
|
|
||||||
public readonly audioParticipants$ = this.scope.behavior(
|
public readonly audioParticipants$ = this.scope.behavior(
|
||||||
this.matrixLivekitItems$.pipe(
|
this.matrixLivekitMembers$.pipe(
|
||||||
map((items) => items.map((item) => item.participant)),
|
map((members) => members.map((m) => m.participant)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -330,72 +335,82 @@ export class CallViewModel {
|
|||||||
// TODO KEEP THIS!! and adapt it to what our membershipManger returns
|
// TODO KEEP THIS!! and adapt it to what our membershipManger returns
|
||||||
private readonly mediaItems$ = this.scope.behavior<MediaItem[]>(
|
private readonly mediaItems$ = this.scope.behavior<MediaItem[]>(
|
||||||
generateKeyed$<
|
generateKeyed$<
|
||||||
[typeof this.participantsByRoom$.value, number],
|
[typeof this.matrixLivekitMembers$.value, number],
|
||||||
MediaItem,
|
MediaItem,
|
||||||
MediaItem[]
|
MediaItem[]
|
||||||
>(
|
>(
|
||||||
// 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.
|
||||||
combineLatest([this.participantsByRoom$, duplicateTiles.value$]),
|
combineLatest([this.matrixLivekitMembers$, duplicateTiles.value$]),
|
||||||
([participantsByRoom, duplicateTiles], createOrGet) => {
|
([matrixLivekitMembers, duplicateTiles], createOrGet) => {
|
||||||
const items: MediaItem[] = [];
|
const items: MediaItem[] = [];
|
||||||
|
|
||||||
for (const { livekitRoom, participants, url } of participantsByRoom) {
|
for (const {
|
||||||
for (const { id, participant, member } of participants) {
|
connection,
|
||||||
for (let i = 0; i < 1 + duplicateTiles; i++) {
|
participant,
|
||||||
const mediaId = `${id}:${i}`;
|
member,
|
||||||
const item = createOrGet(
|
displayName$,
|
||||||
mediaId,
|
participantId,
|
||||||
(scope) =>
|
} of matrixLivekitMembers) {
|
||||||
// We create UserMedia with or without a participant.
|
if (connection === undefined) {
|
||||||
// This will be the initial value of a BehaviourSubject.
|
logger.warn("connection is not yet initialised.");
|
||||||
// Once a participant appears we will update the BehaviourSubject. (see below)
|
continue;
|
||||||
new UserMedia(
|
}
|
||||||
scope,
|
for (let i = 0; i < 1 + duplicateTiles; i++) {
|
||||||
mediaId,
|
const mediaId = `${participantId}:${i}`;
|
||||||
member,
|
const lkRoom = connection?.livekitRoom;
|
||||||
participant,
|
const url = connection?.transport.livekit_service_url;
|
||||||
this.options.encryptionSystem,
|
const dpName$ = displayName$.pipe(map((n) => n ?? "[👻]"));
|
||||||
livekitRoom,
|
const item = createOrGet(
|
||||||
url,
|
mediaId,
|
||||||
this.mediaDevices,
|
(scope) =>
|
||||||
this.pretendToBeDisconnected$,
|
// We create UserMedia with or without a participant.
|
||||||
this.memberDisplaynames$.pipe(
|
// This will be the initial value of a BehaviourSubject.
|
||||||
map((m) => m.get(id) ?? "[👻]"),
|
// Once a participant appears we will update the BehaviourSubject. (see below)
|
||||||
),
|
new UserMedia(
|
||||||
this.handsRaised$.pipe(map((v) => v[id]?.time ?? null)),
|
scope,
|
||||||
this.reactions$.pipe(map((v) => v[id] ?? undefined)),
|
mediaId,
|
||||||
|
member,
|
||||||
|
participant,
|
||||||
|
this.options.encryptionSystem,
|
||||||
|
lkRoom,
|
||||||
|
url,
|
||||||
|
this.mediaDevices,
|
||||||
|
this.pretendToBeDisconnected$,
|
||||||
|
dpName$,
|
||||||
|
this.handsRaised$.pipe(
|
||||||
|
map((v) => v[participantId]?.time ?? null),
|
||||||
),
|
),
|
||||||
);
|
this.reactions$.pipe(
|
||||||
items.push(item);
|
map((v) => v[participantId] ?? undefined),
|
||||||
(item as UserMedia).updateParticipant(participant);
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
items.push(item);
|
||||||
|
(item as UserMedia).updateParticipant(participant);
|
||||||
|
|
||||||
if (participant?.isScreenShareEnabled) {
|
if (participant?.isScreenShareEnabled) {
|
||||||
const screenShareId = `${mediaId}:screen-share`;
|
const screenShareId = `${mediaId}:screen-share`;
|
||||||
items.push(
|
items.push(
|
||||||
createOrGet(
|
createOrGet(
|
||||||
screenShareId,
|
screenShareId,
|
||||||
(scope) =>
|
(scope) =>
|
||||||
new ScreenShare(
|
new ScreenShare(
|
||||||
scope,
|
scope,
|
||||||
screenShareId,
|
screenShareId,
|
||||||
member,
|
member,
|
||||||
participant,
|
participant,
|
||||||
this.options.encryptionSystem,
|
this.options.encryptionSystem,
|
||||||
livekitRoom,
|
lkRoom,
|
||||||
url,
|
url,
|
||||||
this.pretendToBeDisconnected$,
|
this.pretendToBeDisconnected$,
|
||||||
this.memberDisplaynames$.pipe(
|
dpName$,
|
||||||
map((m) => m.get(id) ?? "[👻]"),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -17,16 +17,29 @@ import { fromEvent, map } from "rxjs";
|
|||||||
import { type ObservableScope } from "./ObservableScope";
|
import { type ObservableScope } from "./ObservableScope";
|
||||||
import { type Behavior } from "./Behavior";
|
import { type Behavior } from "./Behavior";
|
||||||
|
|
||||||
export const sessionBehaviors$ = (
|
interface Props {
|
||||||
scope: ObservableScope,
|
scope: ObservableScope;
|
||||||
matrixRTCSession: MatrixRTCSession,
|
matrixRTCSession: MatrixRTCSession;
|
||||||
): {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps behaviors that we extract from an matrixRTCSession.
|
||||||
|
*/
|
||||||
|
interface RxRtcSession {
|
||||||
|
/**
|
||||||
|
* some prop
|
||||||
|
*/
|
||||||
memberships$: Behavior<CallMembership[]>;
|
memberships$: Behavior<CallMembership[]>;
|
||||||
membershipsWithTransport$: Behavior<
|
membershipsWithTransport$: Behavior<
|
||||||
{ membership: CallMembership; transport?: LivekitTransport }[]
|
{ membership: CallMembership; transport?: LivekitTransport }[]
|
||||||
>;
|
>;
|
||||||
transports$: Behavior<LivekitTransport[]>;
|
transports$: Behavior<LivekitTransport[]>;
|
||||||
} => {
|
}
|
||||||
|
|
||||||
|
export const sessionBehaviors$ = ({
|
||||||
|
scope,
|
||||||
|
matrixRTCSession,
|
||||||
|
}: Props): RxRtcSession => {
|
||||||
const memberships$ = scope.behavior(
|
const memberships$ = scope.behavior(
|
||||||
fromEvent(
|
fromEvent(
|
||||||
matrixRTCSession,
|
matrixRTCSession,
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ import {
|
|||||||
enterRTCSession,
|
enterRTCSession,
|
||||||
type EnterRTCSessionOptions,
|
type EnterRTCSessionOptions,
|
||||||
} from "../../rtcSessionHelpers";
|
} from "../../rtcSessionHelpers";
|
||||||
import { ElementCallError } from "../../utils/errors";
|
import { type ElementCallError } from "../../utils/errors";
|
||||||
import { Widget } from "matrix-widget-api";
|
import { ElementWidgetActions, type WidgetHelpers } from "../../widget";
|
||||||
import { ElementWidgetActions, WidgetHelpers } from "../../widget";
|
|
||||||
|
|
||||||
enum LivekitState {
|
enum LivekitState {
|
||||||
UNINITIALIZED = "uninitialized",
|
UNINITIALIZED = "uninitialized",
|
||||||
@@ -87,6 +86,7 @@ export interface LocalMemberState {
|
|||||||
* - send join state/sticky event
|
* - send join state/sticky event
|
||||||
*/
|
*/
|
||||||
interface Props {
|
interface Props {
|
||||||
|
options: Behavior<EnterRTCSessionOptions>;
|
||||||
scope: ObservableScope;
|
scope: ObservableScope;
|
||||||
mediaDevices: MediaDevices;
|
mediaDevices: MediaDevices;
|
||||||
muteStates: MuteStates;
|
muteStates: MuteStates;
|
||||||
@@ -113,6 +113,7 @@ interface Props {
|
|||||||
*/
|
*/
|
||||||
export const localMembership$ = ({
|
export const localMembership$ = ({
|
||||||
scope,
|
scope,
|
||||||
|
options,
|
||||||
muteStates,
|
muteStates,
|
||||||
mediaDevices,
|
mediaDevices,
|
||||||
connectionManager,
|
connectionManager,
|
||||||
@@ -124,7 +125,7 @@ export const localMembership$ = ({
|
|||||||
widget,
|
widget,
|
||||||
}: Props): {
|
}: Props): {
|
||||||
// publisher: Publisher
|
// publisher: Publisher
|
||||||
requestConnect: (options: EnterRTCSessionOptions) => LocalMemberState;
|
requestConnect: () => LocalMemberState;
|
||||||
startTracks: () => Behavior<LocalTrack[]>;
|
startTracks: () => Behavior<LocalTrack[]>;
|
||||||
requestDisconnect: () => Observable<LocalMemberLivekitState> | null;
|
requestDisconnect: () => Observable<LocalMemberLivekitState> | null;
|
||||||
state: LocalMemberState; // TODO this is probably superseeded by joinState$
|
state: LocalMemberState; // TODO this is probably superseeded by joinState$
|
||||||
@@ -268,9 +269,7 @@ export const localMembership$ = ({
|
|||||||
return tracks$;
|
return tracks$;
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestConnect = (
|
const requestConnect = (): LocalMemberState => {
|
||||||
options: EnterRTCSessionOptions,
|
|
||||||
): LocalMemberState => {
|
|
||||||
if (state.livekit$.value === null) {
|
if (state.livekit$.value === null) {
|
||||||
startTracks();
|
startTracks();
|
||||||
state.livekit$.next({ state: LivekitState.CONNECTING });
|
state.livekit$.next({ state: LivekitState.CONNECTING });
|
||||||
@@ -290,7 +289,7 @@ export const localMembership$ = ({
|
|||||||
localTransport$.pipe(
|
localTransport$.pipe(
|
||||||
tap((transport) => {
|
tap((transport) => {
|
||||||
if (transport !== undefined) {
|
if (transport !== undefined) {
|
||||||
enterRTCSession(matrixRTCSession, transport, options).catch(
|
enterRTCSession(matrixRTCSession, transport, options.value).catch(
|
||||||
(error) => {
|
(error) => {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
},
|
},
|
||||||
@@ -379,7 +378,7 @@ export const localMembership$ = ({
|
|||||||
if (advertised !== null && advertised !== undefined) {
|
if (advertised !== null && advertised !== undefined) {
|
||||||
try {
|
try {
|
||||||
configError$.next(null);
|
configError$.next(null);
|
||||||
await enterRTCSession(matrixRTCSession, advertised, options);
|
await enterRTCSession(matrixRTCSession, advertised, options.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Error entering RTC session", e);
|
logger.error("Error entering RTC session", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,13 +77,12 @@ export const localTransport$ = ({
|
|||||||
scope.behavior(from(makeTransport(client, roomId)), undefined);
|
scope.behavior(from(makeTransport(client, roomId)), undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transport we should advertise in our MatrixRTC membership (plus whether
|
* The transport we should advertise in our MatrixRTC membership.
|
||||||
* it is a multi-SFU transport and whether we should use sticky events).
|
|
||||||
*/
|
*/
|
||||||
const advertisedTransport$ = scope.behavior(
|
const advertisedTransport$ = scope.behavior(
|
||||||
combineLatest(
|
combineLatest(
|
||||||
[useOldestMember$, preferredTransport$, oldestMemberTransport$],
|
[useOldestMember$, oldestMemberTransport$, preferredTransport$],
|
||||||
(useOldestMember, preferredTransport, oldestMemberTransport) =>
|
(useOldestMember, oldestMemberTransport, preferredTransport) =>
|
||||||
useOldestMember ? oldestMemberTransport : preferredTransport,
|
useOldestMember ? oldestMemberTransport : preferredTransport,
|
||||||
).pipe<LivekitTransport>(distinctUntilChanged(deepCompare)),
|
).pipe<LivekitTransport>(distinctUntilChanged(deepCompare)),
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export class ConnectionManager {
|
|||||||
private readonly connectionFactory: ConnectionFactory,
|
private readonly connectionFactory: ConnectionFactory,
|
||||||
private readonly inputTransports$: Behavior<LivekitTransport[]>,
|
private readonly inputTransports$: Behavior<LivekitTransport[]>,
|
||||||
) {
|
) {
|
||||||
// TODO logger: only construct one logger from the client and make it compatible via a EC specific singleton.
|
// TODO logger: only construct one logger from the client and make it compatible via a EC specific sing
|
||||||
this.logger = logger.getChild("ConnectionManager");
|
this.logger = logger.getChild("ConnectionManager");
|
||||||
scope.onEnd(() => this.running$.next(false));
|
scope.onEnd(() => this.running$.next(false));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { type Room as MatrixRoom } from "matrix-js-sdk";
|
|||||||
import { getParticipantId } from "matrix-js-sdk/lib/matrixrtc/utils";
|
import { getParticipantId } from "matrix-js-sdk/lib/matrixrtc/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type MatrixLivekitItem,
|
type MatrixLivekitMember,
|
||||||
MatrixLivekitMerger,
|
MatrixLivekitMerger,
|
||||||
} from "./matrixLivekitMerger";
|
} from "./matrixLivekitMerger";
|
||||||
import { ObservableScope } from "../ObservableScope";
|
import { ObservableScope } from "../ObservableScope";
|
||||||
@@ -79,10 +79,12 @@ afterEach(() => {
|
|||||||
test("should signal participant not yet connected to livekit", () => {
|
test("should signal participant not yet connected to livekit", () => {
|
||||||
fakeMemberships$.next([aliceRtcMember]);
|
fakeMemberships$.next([aliceRtcMember]);
|
||||||
|
|
||||||
let items: MatrixLivekitItem[] = [];
|
let items: MatrixLivekitMember[] = [];
|
||||||
matrixLivekitMerger.matrixLivekitItems$.pipe(take(1)).subscribe((emitted) => {
|
matrixLivekitMerger.matrixLivekitMember$
|
||||||
items = emitted;
|
.pipe(take(1))
|
||||||
});
|
.subscribe((emitted) => {
|
||||||
|
items = emitted;
|
||||||
|
});
|
||||||
|
|
||||||
expect(items).toHaveLength(1);
|
expect(items).toHaveLength(1);
|
||||||
const item = items[0];
|
const item = items[0];
|
||||||
@@ -112,10 +114,12 @@ test("should signal participant on a connection that is publishing", () => {
|
|||||||
]);
|
]);
|
||||||
fakeManagerData$.next(managerData);
|
fakeManagerData$.next(managerData);
|
||||||
|
|
||||||
let items: MatrixLivekitItem[] = [];
|
let items: MatrixLivekitMember[] = [];
|
||||||
matrixLivekitMerger.matrixLivekitItems$.pipe(take(1)).subscribe((emitted) => {
|
matrixLivekitMerger.matrixLivekitMember$
|
||||||
items = emitted;
|
.pipe(take(1))
|
||||||
});
|
.subscribe((emitted) => {
|
||||||
|
items = emitted;
|
||||||
|
});
|
||||||
expect(items).toHaveLength(1);
|
expect(items).toHaveLength(1);
|
||||||
const item = items[0];
|
const item = items[0];
|
||||||
|
|
||||||
@@ -136,7 +140,7 @@ test("should signal participant on a connection that is not publishing", () => {
|
|||||||
managerData.add(fakeConnection, []);
|
managerData.add(fakeConnection, []);
|
||||||
fakeManagerData$.next(managerData);
|
fakeManagerData$.next(managerData);
|
||||||
|
|
||||||
matrixLivekitMerger.matrixLivekitItems$.pipe(take(1)).subscribe((items) => {
|
matrixLivekitMerger.matrixLivekitMember$.pipe(take(1)).subscribe((items) => {
|
||||||
expect(items).toHaveLength(1);
|
expect(items).toHaveLength(1);
|
||||||
const item = items[0];
|
const item = items[0];
|
||||||
|
|
||||||
@@ -177,8 +181,8 @@ describe("Publication edge case", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
test("bob is publishing in several connections", () => {
|
test("bob is publishing in several connections", () => {
|
||||||
let lastMatrixLkItems: MatrixLivekitItem[] = [];
|
let lastMatrixLkItems: MatrixLivekitMember[] = [];
|
||||||
matrixLivekitMerger.matrixLivekitItems$.subscribe((items) => {
|
matrixLivekitMerger.matrixLivekitMember$.subscribe((items) => {
|
||||||
lastMatrixLkItems = items;
|
lastMatrixLkItems = items;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -218,8 +222,8 @@ describe("Publication edge case", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("bob is publishing in the wrong connection", () => {
|
test("bob is publishing in the wrong connection", () => {
|
||||||
let lastMatrixLkItems: MatrixLivekitItem[] = [];
|
let lastMatrixLkItems: MatrixLivekitMember[] = [];
|
||||||
matrixLivekitMerger.matrixLivekitItems$.subscribe((items) => {
|
matrixLivekitMerger.matrixLivekitMember$.subscribe((items) => {
|
||||||
lastMatrixLkItems = items;
|
lastMatrixLkItems = items;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Participant as LivekitParticipant } from "livekit-client";
|
import {
|
||||||
|
type LocalParticipant as LocalLivekitParticipant,
|
||||||
|
type RemoteParticipant as RemoteLivekitParticipant,
|
||||||
|
} from "livekit-client";
|
||||||
import {
|
import {
|
||||||
type LivekitTransport,
|
type LivekitTransport,
|
||||||
type CallMembership,
|
type CallMembership,
|
||||||
@@ -27,22 +30,23 @@ import { type Connection } from "./Connection";
|
|||||||
* `livekitParticipant` can be undefined if the member is not yet connected to the livekit room
|
* `livekitParticipant` can be undefined if the member is not yet connected to the livekit room
|
||||||
* or if it has no livekit transport at all.
|
* or if it has no livekit transport at all.
|
||||||
*/
|
*/
|
||||||
export interface MatrixLivekitItem {
|
export interface MatrixLivekitMember {
|
||||||
membership: CallMembership;
|
membership: CallMembership;
|
||||||
displayName: string;
|
displayName$: Behavior<string>;
|
||||||
participant?: LivekitParticipant;
|
participant?: LocalLivekitParticipant | RemoteLivekitParticipant;
|
||||||
connection?: Connection;
|
connection?: Connection;
|
||||||
/**
|
/**
|
||||||
* TODO Try to remove this! Its waaay to much information.
|
* TODO Try to remove this! Its waaay to much information.
|
||||||
* Just get the member's avatar
|
* Just get the member's avatar
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
member?: RoomMember;
|
member: RoomMember;
|
||||||
mxcAvatarUrl?: string;
|
mxcAvatarUrl?: string;
|
||||||
|
participantId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternative structure idea:
|
// Alternative structure idea:
|
||||||
// const livekitMatrixItems$ = (callMemberships$,connectionManager,scope): Observable<MatrixLivekitItem[]> => {
|
// const livekitMatrixMember$ = (callMemberships$,connectionManager,scope): Observable<MatrixLivekitMember[]> => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines MatrixRtc and Livekit worlds.
|
* Combines MatrixRtc and Livekit worlds.
|
||||||
@@ -52,13 +56,13 @@ export interface MatrixLivekitItem {
|
|||||||
* - an observable of CallMembership[] to track the call members (The matrix side)
|
* - an observable of CallMembership[] to track the call members (The matrix side)
|
||||||
* - a `ConnectionManager` for the lk rooms (The livekit side)
|
* - a `ConnectionManager` for the lk rooms (The livekit side)
|
||||||
* - out (via public Observable):
|
* - out (via public Observable):
|
||||||
* - `remoteMatrixLivekitItems` an observable of MatrixLivekitItem[] to track the remote members and associated livekit data.
|
* - `remoteMatrixLivekitMember` an observable of MatrixLivekitMember[] to track the remote members and associated livekit data.
|
||||||
*/
|
*/
|
||||||
export class MatrixLivekitMerger {
|
export class MatrixLivekitMerger {
|
||||||
/**
|
/**
|
||||||
* Stream of all the call members and their associated livekit data (if available).
|
* Stream of all the call members and their associated livekit data (if available).
|
||||||
*/
|
*/
|
||||||
public matrixLivekitItems$: Behavior<MatrixLivekitItem[]>;
|
public matrixLivekitMember$: Behavior<MatrixLivekitMember[]>;
|
||||||
|
|
||||||
// private readonly logger: Logger;
|
// private readonly logger: Logger;
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ export class MatrixLivekitMerger {
|
|||||||
) {
|
) {
|
||||||
// this.logger = parentLogger.getChild("MatrixLivekitMerger");
|
// this.logger = parentLogger.getChild("MatrixLivekitMerger");
|
||||||
|
|
||||||
this.matrixLivekitItems$ = this.scope.behavior(
|
this.matrixLivekitMember$ = this.scope.behavior(
|
||||||
this.start$().pipe(startWith([])),
|
this.start$().pipe(startWith([])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -87,7 +91,7 @@ export class MatrixLivekitMerger {
|
|||||||
// =======================================
|
// =======================================
|
||||||
/// PRIVATES
|
/// PRIVATES
|
||||||
// =======================================
|
// =======================================
|
||||||
private start$(): Observable<MatrixLivekitItem[]> {
|
private start$(): Observable<MatrixLivekitMember[]> {
|
||||||
const displaynameMap$ = memberDisplaynames$(
|
const displaynameMap$ = memberDisplaynames$(
|
||||||
this.scope,
|
this.scope,
|
||||||
this.matrixRoom,
|
this.matrixRoom,
|
||||||
@@ -102,10 +106,9 @@ export class MatrixLivekitMerger {
|
|||||||
return combineLatest([
|
return combineLatest([
|
||||||
membershipsWithTransport$,
|
membershipsWithTransport$,
|
||||||
this.connectionManager.connectionManagerData$,
|
this.connectionManager.connectionManagerData$,
|
||||||
displaynameMap$,
|
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([memberships, managerData, displayNameMap]) => {
|
map(([memberships, managerData]) => {
|
||||||
const items: MatrixLivekitItem[] = memberships.map(
|
const items: MatrixLivekitMember[] = memberships.map(
|
||||||
({ membership, transport }) => {
|
({ membership, transport }) => {
|
||||||
// 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
|
||||||
const participantId = /*membership.membershipID*/ `${membership.userId}:${membership.deviceId}`;
|
const participantId = /*membership.membershipID*/ `${membership.userId}:${membership.deviceId}`;
|
||||||
@@ -123,14 +126,23 @@ export class MatrixLivekitMerger {
|
|||||||
const connection = transport
|
const connection = transport
|
||||||
? managerData.getConnectionForTransport(transport)
|
? managerData.getConnectionForTransport(transport)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const displayName$ = this.scope.behavior(
|
||||||
|
displaynameMap$.pipe(
|
||||||
|
map(
|
||||||
|
(displayNameMap) =>
|
||||||
|
displayNameMap.get(membership.membershipID) ?? "---",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
participant,
|
participant,
|
||||||
membership,
|
membership,
|
||||||
connection,
|
connection,
|
||||||
// This makes sense to add the the js-sdk callMembership (we only need the avatar so probably the call memberhsip just should aquire the avatar)
|
// This makes sense to add the the js-sdk callMembership (we only need the avatar so probably the call memberhsip just should aquire the avatar)
|
||||||
member,
|
member,
|
||||||
displayName: displayNameMap.get(membership.membershipID) ?? "---",
|
displayName$,
|
||||||
mxcAvatarUrl: member?.getMxcAvatarUrl(),
|
mxcAvatarUrl: member?.getMxcAvatarUrl(),
|
||||||
|
participantId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user