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:
+118
-90
@@ -6,13 +6,12 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The public types of the SDK: the client, its members and transports. What a
|
||||
* member sends is in `media-api.ts`. `createMatrixRTCClient` in
|
||||
* `MatrixRTCClient.ts` is the only way to obtain an implementation of them.
|
||||
* The public types of the SDK: the slot, the participation, their members and
|
||||
* transports. What a member sends is in `media-api.ts`. `createRTCSlot` in
|
||||
* `RTCSlot.ts` is the only way to obtain an implementation of them.
|
||||
*/
|
||||
|
||||
import {
|
||||
type CallMembership,
|
||||
type RTCNotificationType,
|
||||
type Transport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
@@ -20,12 +19,12 @@ import { type Observable } from "rxjs";
|
||||
|
||||
import { type Behavior } from "./reactive/Behavior";
|
||||
import {
|
||||
type AudioMediaTrack,
|
||||
type EncryptionError,
|
||||
type LocalAudioMediaTrack,
|
||||
type LocalMemberMedia,
|
||||
type LocalVideoMediaTrack,
|
||||
type MediaSource,
|
||||
type MemberMedia,
|
||||
type PublishRequest,
|
||||
type VideoMediaTrack,
|
||||
} from "./media-api";
|
||||
import { type MediaBackendFactory } from "./media-backend/api";
|
||||
import { type EncryptionSystem } from "./encryption";
|
||||
@@ -34,26 +33,72 @@ import {
|
||||
type MediaQuality,
|
||||
type SessionTimings,
|
||||
} from "./config";
|
||||
import { type MatrixRTCError } from "./errors";
|
||||
|
||||
export { MatrixRTCError } from "./errors";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MatrixRTCClient
|
||||
// Slot
|
||||
|
||||
export interface MatrixRTCClientOptions {
|
||||
encryptionSystem: EncryptionSystem;
|
||||
/** Resolved by the host; the SDK reads neither config.json nor settings. */
|
||||
export interface RTCSlotOptions {
|
||||
/** The application the slot belongs to, as named in the membership. Default `m.call`. */
|
||||
application?: string;
|
||||
/** The application's slot in the room. Default `ROOM`. */
|
||||
id?: string;
|
||||
/** How memberships and keys in this slot are written. Resolved by the host. */
|
||||
matrixRTCMode: MatrixRTCMode;
|
||||
/** Published at the join; `publish` on the local member adds to it from then on. */
|
||||
encryptionSystem: EncryptionSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* The namespace in a room that memberships join into. It exists while the
|
||||
* room does, with nobody in it too. The room has to be the one the client's
|
||||
* sync loop maintains (`client.getRoom(roomId)`, once the join has synced):
|
||||
* the detached copy `joinRoom` returns for a room joined just now never
|
||||
* receives the state the members are read from.
|
||||
*/
|
||||
export interface RTCSlot {
|
||||
readonly roomId: string;
|
||||
readonly application: string;
|
||||
readonly id: string;
|
||||
/** From the slot state event; undefined while the room has none. */
|
||||
status$: Behavior<"open" | "closed" | undefined>;
|
||||
/**
|
||||
* Everyone in the slot, one object per membership, kept for as long as the
|
||||
* membership is. Our own membership is in here too, with `local: true`,
|
||||
* once the room has seen it. No media: that is the participation's.
|
||||
*/
|
||||
members$: Behavior<RTCMember[]>;
|
||||
/** The participation we are in, null between `leave()` and the next `join()`. */
|
||||
participation$: Behavior<RTCParticipation | null>;
|
||||
/**
|
||||
* Joins the slot and returns the participation that lasts until its
|
||||
* `leave()`. Throws while `participation$` is not null: a slot has one local
|
||||
* member.
|
||||
*/
|
||||
join(options: RTCParticipationOptions): RTCParticipation; // Maybe MediaSession,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Participation
|
||||
|
||||
export interface RTCParticipationOptions {
|
||||
/**
|
||||
* Published at the join; `publish` on the participation adds to it from then
|
||||
* on. Kept as an option, rather than only the method, because the LiveKit
|
||||
* backend reads the initial requests to build the room's default capture
|
||||
* options before the first connection exists.
|
||||
*/
|
||||
publish: PublishRequest[];
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* Conside already removing it. just skips code in js-sdk. But rust sdk will not have this feature.
|
||||
* MSC4075 notification sent with the join. A parameter of the MatrixRTC
|
||||
* join itself, so it is here even though it is named after calls; reacting
|
||||
* to a notification (ringing, timeouts, declines) is the application's job.
|
||||
*/
|
||||
sendNotificationType?: RTCNotificationType;
|
||||
/** The application the session belongs to, as named in the membership. Default `m.call`. */
|
||||
application?: string;
|
||||
/** The application's slot in the room. Default `ROOM`. */
|
||||
slot?: string;
|
||||
/**
|
||||
* Whatever the application wants to say about itself in the membership,
|
||||
* under namespaced keys. Opaque to the SDK, apart from `m.call.intent`
|
||||
@@ -61,7 +106,7 @@ export interface MatrixRTCClientOptions {
|
||||
* field of its own.
|
||||
*/
|
||||
applicationData?: Record<string, unknown>;
|
||||
/** Session timings the host has configured; the defaults otherwise. */
|
||||
/** Timings the host has configured; the defaults otherwise. */
|
||||
timings?: Partial<SessionTimings>;
|
||||
/** Limits on what is published; LiveKit's defaults otherwise. */
|
||||
mediaQuality?: MediaQuality;
|
||||
@@ -76,51 +121,58 @@ export interface MatrixRTCClientOptions {
|
||||
backend?: MediaBackendFactory;
|
||||
}
|
||||
|
||||
export type ConnectionStatus =
|
||||
export type ParticipationStatus =
|
||||
| "waitingForTransport"
|
||||
| "connecting"
|
||||
| "connected"
|
||||
| "reconnecting"
|
||||
| "disconnected";
|
||||
| "disconnected"
|
||||
/** `leave()` was called; the participation is over and stays so. */
|
||||
| "left";
|
||||
|
||||
/** Why the client is not connected: the first failing of its three links. */
|
||||
/** Why the participation is not connected: the first failing of its three links. */
|
||||
export type DisconnectReason = "sync" | "membership" | "probablyLeft" | "media";
|
||||
|
||||
import { type MatrixRTCError } from "./errors";
|
||||
|
||||
export { MatrixRTCError } from "./errors";
|
||||
|
||||
/**
|
||||
* A session in one room. The room has to be the one the client's sync loop
|
||||
* maintains (`client.getRoom(roomId)`, once the join has synced): the
|
||||
* detached copy `joinRoom` returns for a room joined just now never receives
|
||||
* the state the members are read from.
|
||||
*/
|
||||
export interface MatrixRTCClient {
|
||||
join(): void;
|
||||
/** The span between our `join()` of a slot and our `leave()`. */
|
||||
export interface RTCParticipation {
|
||||
readonly slot: RTCSlot;
|
||||
/** Sends the leave, tears the connections down and ends the participation. */
|
||||
leave(): void;
|
||||
/** Collapsed view of the local member's state machine. */
|
||||
status$: Behavior<ConnectionStatus>;
|
||||
status$: Behavior<ParticipationStatus>;
|
||||
connected$: Behavior<boolean>;
|
||||
reconnecting$: Behavior<boolean>;
|
||||
/** Null while connected, the first failing link otherwise. */
|
||||
disconnectReason$: Behavior<DisconnectReason | null>;
|
||||
|
||||
/** A transport, Matrix or connection error that stops the session. */
|
||||
/** A transport, Matrix or connection error that stops the participation. */
|
||||
fatalError$: Behavior<MatrixRTCError | null>;
|
||||
|
||||
/** Null until our own membership has been seen in the room. */
|
||||
localMember$: Behavior<LocalRTCMember | null>;
|
||||
/**
|
||||
* The slot's other members, each with the media this participation carries
|
||||
* for it. A call view needs nothing but this and `localMember$` to render.
|
||||
*/
|
||||
remoteMembers$: Behavior<RemoteRTCMember[]>;
|
||||
/** `remoteMembers.length`, plus one for the local member once it exists. */
|
||||
memberCount$: Behavior<number>;
|
||||
|
||||
/**
|
||||
* Whether the session has grown large enough that MatrixRTC has stopped
|
||||
* Publishes a source and resolves with its track, which also shows up in
|
||||
* `localMember$.tracks$`. Works from the moment `join()` returns: before the
|
||||
* transport is connected.
|
||||
*/
|
||||
publish(
|
||||
request: PublishRequest,
|
||||
): Promise<LocalAudioMediaTrack | LocalVideoMediaTrack>;
|
||||
/** Removes one of our publications; a screen share takes its audio with it. */
|
||||
unpublish(source: MediaSource): Promise<void>;
|
||||
|
||||
/**
|
||||
* Whether the slot has grown large enough that MatrixRTC has stopped
|
||||
* rotating the media encryption key.
|
||||
*/
|
||||
keyRotationSuppressed$: Behavior<boolean>;
|
||||
|
||||
/** Transports the session currently holds a live connection to. */
|
||||
/** Transports the participation currently holds a live connection to. */
|
||||
connectedTransports$: Behavior<TransportMetadata[]>;
|
||||
|
||||
/**
|
||||
@@ -139,9 +191,9 @@ export interface MatrixRTCClient {
|
||||
*/
|
||||
sendData(topic: string, text: string): Promise<void>;
|
||||
/**
|
||||
* What remote members sent with `sendData`, on every transport the client
|
||||
* is connected to. A message from an identity that is not a member is
|
||||
* dropped, so a host only ever hears from attested members.
|
||||
* What remote members sent with `sendData`, on every transport the
|
||||
* participation is connected to. A message from an identity that is not a
|
||||
* member is dropped, so a host only ever hears from attested members.
|
||||
*/
|
||||
data$: Observable<DataMessage>;
|
||||
}
|
||||
@@ -164,7 +216,7 @@ export interface DataMessage {
|
||||
export interface TransportMetadata {
|
||||
/** `"livekit"` today. */
|
||||
type: string;
|
||||
/** Stable key, unique per transport in the session: the raw transport, serialised with sorted keys. */
|
||||
/** Stable key, unique per transport in the slot: the raw transport, serialised with sorted keys. */
|
||||
id: string;
|
||||
/** The transport object as it appears in the membership. */
|
||||
raw: Transport;
|
||||
@@ -190,63 +242,39 @@ export type ResolvedTransport =
|
||||
// ---------------------------------------------------------------------------
|
||||
// Members
|
||||
|
||||
/**
|
||||
* What a membership says, as hosts read it. The js-sdk `CallMembership` is
|
||||
* behind it; anything beyond this is the js-sdk's API, not the SDK's.
|
||||
*/
|
||||
export type RTCMembership = Pick<
|
||||
CallMembership,
|
||||
| "userId"
|
||||
| "deviceId"
|
||||
| "memberId"
|
||||
| "rtcBackendIdentity"
|
||||
| "application"
|
||||
| "applicationData"
|
||||
| "getTransport"
|
||||
| "transports"
|
||||
| "createdTs"
|
||||
| "getAbsoluteExpiry"
|
||||
>;
|
||||
|
||||
/** A membership in the slot, with what the SDK derives from it. No media. */
|
||||
export interface RTCMember {
|
||||
local: boolean;
|
||||
/** The identity the media backend knows this member by. */
|
||||
id: string;
|
||||
/**
|
||||
* The identity this member has on the media backend's transport: the
|
||||
* participant identity the SFU sees, and the sender of its data packets. The
|
||||
* SDK uses it to match what arrives on the transport back to the member; a
|
||||
* host needs it only as a stable key per membership or as a debug label.
|
||||
*
|
||||
* Today it is read off the membership, where the js-sdk computes it:
|
||||
* `${userId}:${deviceId}` before sticky events, a hash of `memberId` in
|
||||
* Matrix 2.0 mode. Eventually the backend will derive it itself, so treat it
|
||||
* as opaque.
|
||||
*/
|
||||
rtcBackendIdentity: string;
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
membership$: Behavior<RTCMembership>;
|
||||
displayName$: Behavior<string>;
|
||||
avatarUrl$: Behavior<string | undefined>;
|
||||
/** The membership's own id; what the Matrix 2.0 identity is derived from. */
|
||||
memberId: string;
|
||||
/** Which transport this member is on; undefined when the membership has none. */
|
||||
transport$: Behavior<TransportMetadata | undefined>;
|
||||
/**
|
||||
* The member's tracks, in publication order, once it has shown up on its
|
||||
* transport; null until then ("waiting for media"). An entry stays the same
|
||||
* object for as long as the same publication is behind it. Which track is
|
||||
* which is in its `source`; `trackBySource$` picks one out.
|
||||
*/
|
||||
tracks$: Behavior<(AudioMediaTrack | VideoMediaTrack)[] | null>;
|
||||
/** Emits when the SFU reports a key problem for this member. */
|
||||
encryptionError$: Observable<EncryptionError>;
|
||||
/** What the member's application says about itself, e.g. `m.call.intent`. */
|
||||
applicationData$: Behavior<Record<string, unknown>>;
|
||||
|
||||
displayName$: Behavior<string>;
|
||||
avatarUrl$: Behavior<string | undefined>;
|
||||
}
|
||||
|
||||
export interface RemoteRTCMember extends RTCMember {
|
||||
export interface RemoteRTCMember extends RTCMember, MemberMedia {
|
||||
local: false;
|
||||
}
|
||||
|
||||
export interface LocalRTCMember extends RTCMember {
|
||||
/** Our member: the same as a remote one, with tracks that carry the controls. */
|
||||
export interface LocalRTCMember extends RTCMember, LocalMemberMedia {
|
||||
local: true;
|
||||
tracks$: Behavior<(LocalAudioMediaTrack | LocalVideoMediaTrack)[] | null>;
|
||||
/**
|
||||
* Publishes a source and resolves with its track once it is in `tracks$`.
|
||||
* Before the transport is connected the request is remembered and applied
|
||||
* once it is. Rejects where the device could not be used, including the
|
||||
* user closing the picker. One publication per source: publishing a source
|
||||
* again unmutes it.
|
||||
*/
|
||||
publish(
|
||||
request: PublishRequest,
|
||||
): Promise<LocalAudioMediaTrack | LocalVideoMediaTrack>;
|
||||
/** Removes one of our tracks; a screen share takes its audio with it. */
|
||||
unpublish(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user