/* 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. */ /** * EXPERIMENTAL * * MatrixRTC sessions with LiveKit media, without Element Call's UI: the call * model Element Call's own view model is built on, for hosts that want to * build a different one. * * Only the interface exists so far. `createRtcSession` returns an object that * does nothing, so that the development harness in `sdk/dev` and its e2e test * can be written against the contract before the implementation lands behind * it. The design and the migration from Element Call's `CallViewModel` are in * `sdk-plan.md` at the repository root. */ import { type MatrixClient, type Room } from "matrix-js-sdk"; import { type CallMembership, type RTCCallIntent, type RTCNotificationType, type Transport, } from "matrix-js-sdk/lib/matrixrtc"; import { type Track, type TrackProcessor } from "livekit-client"; import { type Observable } from "rxjs"; import { type Behavior } from "../src/state/Behavior"; import { type ObservableScope } from "../src/state/ObservableScope"; import { type EncryptionSystem } from "../src/e2ee/sharedKeyManagement"; import { type MatrixRTCMode } from "../src/config/ConfigOptions"; // Shared with Element Call. They live in `src` until the SDK has an // implementation to move them with; a consumer gets them from here either way. export { type Behavior, constant } from "../src/state/Behavior"; export { ObservableScope } from "../src/state/ObservableScope"; export { E2eeType } from "../src/e2ee/e2eeType"; export { type EncryptionSystem } from "../src/e2ee/sharedKeyManagement"; export { MatrixRTCMode } from "../src/config/ConfigOptions"; // --------------------------------------------------------------------------- // Session export interface RtcSessionOptions { encryptionSystem: EncryptionSystem; /** Resolved by the host; the SDK reads neither config.json nor settings. */ matrixRTCMode: MatrixRTCMode; /** * MSC4075 notification sent with the join. Parameters of the MatrixRTC join * itself, so they are here even though they are named after calls; reacting * to a notification (ringing, timeouts, declines) is the application's job. */ sendNotificationType?: RTCNotificationType; callIntent?: RTCCallIntent; } /** What the local member publishes. */ export interface LocalMediaInputs { microphoneEnabled$: Behavior; cameraEnabled$: Behavior; audioInputDeviceId$: Behavior; videoInputDeviceId$: Behavior; /** Background blur and the like. */ videoProcessor$: Behavior | undefined>; } export type SessionConnectionStatus = | "waitingForTransport" | "connecting" | "connected" | "reconnecting" | "disconnected"; export class RtcSessionError extends Error { public constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = "RtcSessionError"; } } export interface RtcSession { join(): void; leave(): void; /** Collapsed view of the local member's state machine. */ status$: Behavior; connected$: Behavior; reconnecting$: Behavior; /** A transport, Matrix or connection error that stops the session. */ fatalError$: Behavior; localMember$: Behavior; remoteMembers$: Behavior; /** `remoteMembers.length`, plus one for the local member once it exists. */ memberCount$: Behavior; /** * Whether the session has grown large enough that MatrixRTC has stopped * rotating the media encryption key. */ keyRotationSuppressed$: Behavior; /** Transports the session currently holds a live connection to. */ connectedTransports$: Behavior; } // --------------------------------------------------------------------------- // Transports /** * One transport advertised in a membership. Transport independent: `type` and * `id` are all the SDK needs; `raw` and `resolved$` are there for a * backend-specific developer panel and for connection diagnostics. */ export interface TransportMetadata { /** `"livekit"` today. */ type: string; /** Stable key, unique per transport in the session. For LiveKit, the service url. */ id: string; /** The transport object as it appears in the membership. */ raw: Transport; /** * What the backend had to fetch before it could connect. Undefined until the * connection has resolved it, and again after the connection stops. */ resolved$: Behavior; } export type ResolvedTransport = | { type: "livekit"; /** The SFU websocket url, as opposed to the JWT service url in `raw`. */ url: string; /** A secret: fit for a developer panel, never for a log line. */ token: string; roomAlias: string; identity: string; } | { type: string; [key: string]: unknown }; // --------------------------------------------------------------------------- // Members export interface RtcMember { local: boolean; /** `${userId}:${deviceId}` before sticky events, a uuid in Matrix 2.0 mode. The backend identity. */ id: string; userId: string; deviceId: string; membership$: Behavior; displayName$: Behavior; avatarUrl$: Behavior; /** Which transport this member is on; undefined when the membership has none. */ transport$: Behavior; /** * Null while the member has a transport but no media has arrived on it yet * ("waiting for media"). */ media$: Behavior; } export interface RemoteRtcMember extends RtcMember { local: false; } export interface LocalRtcMember extends RtcMember { local: true; media$: Behavior; sharingScreen$: Behavior; /** Null when the platform cannot share a screen. */ toggleScreenSharing: (() => void) | null; screenShareError$: Behavior; dismissScreenShareError(): void; } // --------------------------------------------------------------------------- // Media export type MediaSource = | "microphone" | "camera" | "screenShare" | "screenShareAudio"; export type MediaStreamStats = | RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats | undefined; /** One published track of a member. */ export interface MediaTrack { source: MediaSource; kind: "audio" | "video"; /** Stable for the life of the track. */ id: string; muted$: Behavior; /** False when the SFU reports the track as unencrypted. */ encrypted$: Behavior; /** Polled while subscribed. */ stats$: Behavior; /** * Rendering. The view hands its `