mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
[SDK] split the clint object into -> slot and participation
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MatrixClient, type Room } from "matrix-js-sdk";
|
||||
import { EventType } from "matrix-js-sdk/lib/@types/event";
|
||||
import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
type MatrixRTCSession as JsSdkRTCSession,
|
||||
MatrixRTCSessionManager,
|
||||
type SlotDescription,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/lib/models/event";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/lib/models/room-state";
|
||||
import { BehaviorSubject, filter, fromEvent, map, of, switchMap } from "rxjs";
|
||||
|
||||
import { ObservableScope } from "./reactive/ObservableScope";
|
||||
import { generateItems } from "./reactive/observable";
|
||||
import {
|
||||
type RTCMember,
|
||||
type RTCParticipation,
|
||||
type RTCParticipationOptions,
|
||||
type RTCSlot,
|
||||
type RTCSlotOptions,
|
||||
} from "./api";
|
||||
import { MatrixRTCError } from "./errors";
|
||||
import {
|
||||
createRTCParticipation,
|
||||
type ParticipationInternals,
|
||||
} from "./RTCParticipation";
|
||||
import { createMemberships$ } from "./matrixrtc/JsRtcSessionBehaviors";
|
||||
import {
|
||||
createMatrixMemberMetadata$,
|
||||
createRoomMembers$,
|
||||
} from "./matrixrtc/MatrixMemberMetadata";
|
||||
import {
|
||||
createRTCMember,
|
||||
type MemberContext,
|
||||
membershipKeys,
|
||||
} from "./matrixrtc/Members";
|
||||
import { createTransportRegistry } from "./matrixrtc/Transports";
|
||||
|
||||
/**
|
||||
* Takes the whole `MatrixClient` rather than a slice of it, and finds the
|
||||
* MatrixRTC session itself. The js-sdk is the MatrixRTC implementation today;
|
||||
* when the rust-rtc crate replaces it, the SDK has to bridge the crate to the
|
||||
* js-sdk client, and only the SDK knows what that bridge needs. Holding the
|
||||
* client keeps that change inside the SDK.
|
||||
*/
|
||||
export function createRTCSlot(
|
||||
scope: ObservableScope,
|
||||
client: MatrixClient,
|
||||
room: Room,
|
||||
options: RTCSlotOptions,
|
||||
): RTCSlot {
|
||||
const logger = rootLogger.getChild("[RTCSlot]");
|
||||
const userId = client.getUserId();
|
||||
const deviceId = client.getDeviceId();
|
||||
if (!(userId && deviceId))
|
||||
throw new MatrixRTCError("The client has to be logged in");
|
||||
const { application = "m.call", id = "ROOM" } = options;
|
||||
|
||||
const jsSdkSession = sessionManager(
|
||||
scope,
|
||||
client,
|
||||
{ application, id },
|
||||
logger,
|
||||
).getRoomSession(room);
|
||||
const memberships$ = createMemberships$(scope, jsSdkSession);
|
||||
|
||||
const active$ = new BehaviorSubject<ParticipationInternals | null>(null);
|
||||
const participationScopes = new Set<ObservableScope>();
|
||||
scope.onEnd(() => {
|
||||
for (const participationScope of participationScopes)
|
||||
participationScope.end();
|
||||
});
|
||||
const transports = createTransportRegistry(
|
||||
scope,
|
||||
scope.behavior(
|
||||
active$.pipe(switchMap((active) => active?.connections$ ?? of([]))),
|
||||
),
|
||||
);
|
||||
const context: MemberContext = {
|
||||
metadata: createMatrixMemberMetadata$(
|
||||
scope,
|
||||
scope.behavior(memberships$.pipe(map(({ value }) => value))),
|
||||
createRoomMembers$(scope, room),
|
||||
),
|
||||
transports,
|
||||
own: { userId, deviceId },
|
||||
};
|
||||
|
||||
const members$ = scope.behavior<RTCMember[]>(
|
||||
memberships$.pipe(
|
||||
map(({ value }) => value),
|
||||
generateItems(
|
||||
"RTCSlot members",
|
||||
function* (memberships) {
|
||||
for (const membership of memberships)
|
||||
yield { keys: membershipKeys(membership), data: membership };
|
||||
},
|
||||
(memberScope, membership$) =>
|
||||
createRTCMember(memberScope, membership$, context),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const status$ = scope.behavior(
|
||||
fromEvent(room, RoomStateEvent.Events, (event: MatrixEvent) => event).pipe(
|
||||
filter((event) => event.getType() === EventType.RTCSlot),
|
||||
map(() => slotStatus(jsSdkSession)),
|
||||
),
|
||||
slotStatus(jsSdkSession),
|
||||
);
|
||||
|
||||
const slot: RTCSlot = {
|
||||
roomId: room.roomId,
|
||||
application,
|
||||
id,
|
||||
status$,
|
||||
members$,
|
||||
participation$: scope.behavior(
|
||||
active$.pipe(map((active) => active?.participation ?? null)),
|
||||
),
|
||||
join: (participationOptions: RTCParticipationOptions): RTCParticipation => {
|
||||
if (active$.value !== null)
|
||||
throw new MatrixRTCError(
|
||||
"Already in this slot: leave the participation before joining again",
|
||||
);
|
||||
const participationScope = new ObservableScope();
|
||||
participationScopes.add(participationScope);
|
||||
const internals = createRTCParticipation(
|
||||
participationScope,
|
||||
{
|
||||
client,
|
||||
room,
|
||||
slot,
|
||||
jsSdkSession,
|
||||
members$,
|
||||
transports,
|
||||
slotOptions: options,
|
||||
logger,
|
||||
},
|
||||
participationOptions,
|
||||
);
|
||||
participationScope.onEnd(() => {
|
||||
participationScopes.delete(participationScope);
|
||||
if (active$.value === internals) active$.next(null);
|
||||
});
|
||||
active$.next(internals);
|
||||
return internals.participation;
|
||||
},
|
||||
};
|
||||
return slot;
|
||||
}
|
||||
|
||||
function slotStatus(
|
||||
session: Pick<JsSdkRTCSession, "getRtcSlot">,
|
||||
): "open" | "closed" | undefined {
|
||||
return session.getRtcSlot()?.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* The js-sdk keeps one session manager per slot. The client comes with the
|
||||
* manager for the default slot; any other slot gets a manager of its own,
|
||||
* which lives as long as the scope.
|
||||
*/
|
||||
function sessionManager(
|
||||
scope: ObservableScope,
|
||||
client: MatrixClient,
|
||||
{ application, id }: SlotDescription,
|
||||
logger: Logger,
|
||||
): MatrixRTCSessionManager {
|
||||
if (application === "m.call" && id === "ROOM") return client.matrixRTC;
|
||||
const manager = new MatrixRTCSessionManager(logger, client, {
|
||||
application,
|
||||
id,
|
||||
});
|
||||
manager.start();
|
||||
scope.onEnd(() => manager.stop());
|
||||
return manager;
|
||||
}
|
||||
Reference in New Issue
Block a user