split api.ts into api.ts and media-api.ts

This commit is contained in:
Timo K.
2026-10-08 13:53:57 +02:00
parent f1ea4cc5a2
commit 6ffab92f0f
16 changed files with 192 additions and 169 deletions
+2 -1
View File
@@ -707,7 +707,8 @@ Layout:
``` ```
sdk/ sdk/
index.ts the entry point: re-exports the API and the primitives index.ts the entry point: re-exports the API and the primitives
src/api.ts the public types above src/api.ts the public types above: client, members, transports
src/media-api.ts the public media types: publish requests, tracks, MemberMedia
src/errors.ts MatrixRTCError and its codes src/errors.ts MatrixRTCError and its codes
src/config.ts MatrixRTCMode, session timings, media quality src/config.ts MatrixRTCMode, session timings, media quality
src/encryption.ts E2eeType, EncryptionSystem src/encryption.ts E2eeType, EncryptionSystem
+1
View File
@@ -53,6 +53,7 @@ export {
} from "./src/errors"; } from "./src/errors";
export * from "./src/api"; export * from "./src/api";
export * from "./src/media-api";
export * from "./src/media-backend/api"; export * from "./src/media-backend/api";
export { trackBySource$, type TrackOfSource } from "./src/utils/tracks"; export { trackBySource$, type TrackOfSource } from "./src/utils/tracks";
export { createMatrixRTCClient } from "./src/MatrixRTCClient"; export { createMatrixRTCClient } from "./src/MatrixRTCClient";
+10 -150
View File
@@ -6,8 +6,9 @@ Please see LICENSE in the repository root for full details.
*/ */
/** /**
* The public types of the SDK. `createMatrixRTCClient` in `MatrixRTCClient.ts` * The public types of the SDK: the client, its members and transports. What a
* is the only way to obtain an implementation of them. * member sends is in `media-api.ts`. `createMatrixRTCClient` in
* `MatrixRTCClient.ts` is the only way to obtain an implementation of them.
*/ */
import { import {
@@ -15,14 +16,16 @@ import {
type RTCNotificationType, type RTCNotificationType,
type Transport, type Transport,
} from "matrix-js-sdk/lib/matrixrtc"; } from "matrix-js-sdk/lib/matrixrtc";
import {
type Track,
type TrackProcessor,
type VideoCodec,
} from "livekit-client";
import { type Observable } from "rxjs"; import { type Observable } from "rxjs";
import { type Behavior } from "./reactive/Behavior"; import { type Behavior } from "./reactive/Behavior";
import {
type LocalAudioMediaTrack,
type LocalMemberMedia,
type LocalVideoMediaTrack,
type MemberMedia,
type PublishRequest,
} from "./media-api";
import { type MediaBackendFactory } from "./media-backend/api"; import { type MediaBackendFactory } from "./media-backend/api";
import { type EncryptionSystem } from "./encryption"; import { type EncryptionSystem } from "./encryption";
import { import {
@@ -72,40 +75,6 @@ export interface MatrixRTCClientOptions {
backend?: MediaBackendFactory; backend?: MediaBackendFactory;
} }
export interface AudioCaptureSettings {
echoCancellation?: boolean;
noiseSuppression?: boolean;
autoGainControl?: boolean;
}
export interface VideoCaptureSettings {
resolution?: { width: number; height: number; frameRate?: number };
maxBitrate?: number;
maxFramerate?: number;
codec?: VideoCodec;
}
/**
* One thing to publish: a source, where to capture it from and how to encode
* it. Where the device and the settings are left out, the browser's and
* LiveKit's defaults apply.
*/
export type PublishRequest =
| { source: "microphone"; deviceId?: string; capture?: AudioCaptureSettings }
| {
source: "camera";
deviceId?: string;
/** Background blur and the like. */
processor?: TrackProcessor<Track.Kind.Video>;
capture?: VideoCaptureSettings;
}
| {
source: "screenShare";
/** Whether to capture the screen's audio too. Default true. */
audio?: boolean;
capture?: VideoCaptureSettings;
};
export type ConnectionStatus = export type ConnectionStatus =
| "waitingForTransport" | "waitingForTransport"
| "connecting" | "connecting"
@@ -276,112 +245,3 @@ export interface LocalRTCMember extends RTCMember {
/** Removes one of our tracks; a screen share takes its audio with it. */ /** Removes one of our tracks; a screen share takes its audio with it. */
unpublish(id: string): Promise<void>; unpublish(id: string): Promise<void>;
} }
// ---------------------------------------------------------------------------
// Media
export type MediaSource =
| "microphone"
| "camera"
| "screenShare"
| "screenShareAudio"
/** Published without a source; the application knows what it is. */
| "unknown";
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<boolean>;
/** False when the SFU reports the track as unencrypted. */
encrypted$: Behavior<boolean>;
/** Polled while subscribed. */
stats$: Behavior<MediaStreamStats>;
/**
* Rendering. The view hands its `<video>` or `<audio>` element over; the SDK
* sets its stream and, for video, registers the size and on-screen observers
* that pick a simulcast layer and pause the subscription while the element is
* hidden. `attach` is idempotent per element; `detach` has to be called
* before the element leaves the DOM so those observers are released.
*/
attach(element: HTMLMediaElement): void;
detach(element: HTMLMediaElement): void;
}
export interface AudioMediaTrack extends MediaTrack {
kind: "audio";
/**
* Whether the track carries sound right now, as the backend measures it.
* False while muted. A call reads this on the microphone track and calls it
* "speaking". LiveKit measures it per member, so every audio track of a
* member reports the same value.
*/
isActive$: Behavior<boolean>;
/** Route playback through Web Audio, for earpiece pan and gain. Undefined resets. */
setAudioContext(ctx: AudioContext | undefined, plugins?: AudioNode[]): void;
setVolume(volume: number): void;
}
export interface VideoMediaTrack extends MediaTrack {
kind: "video";
}
/** The controls a member has over a track it publishes itself. */
export interface LocalMediaTrack {
/**
* Mutes or unmutes. Resolves with the state that resulted, which differs
* from the request where the device could not be used.
*/
setEnabled(enabled: boolean): Promise<boolean>;
/** Captures from another device. Rejects for a screen share, which has none. */
setDevice(deviceId: string): Promise<void>;
}
export interface LocalAudioMediaTrack
extends AudioMediaTrack, LocalMediaTrack {}
export interface LocalVideoMediaTrack extends VideoMediaTrack, LocalMediaTrack {
/** For mirroring; undefined where the camera does not say which way it faces. */
facingMode$: Behavior<"user" | "environment" | undefined>;
/**
* Restarts the camera facing the other way, on devices with a front and a
* back camera, and resolves with the id of the device now in use. Does
* nothing where the facing mode is unknown.
*/
switchFacingMode(): Promise<string | undefined>;
/** Background blur and the like; undefined removes the processor. */
setProcessor(
processor: TrackProcessor<Track.Kind.Video> | undefined,
): Promise<void>;
}
export type EncryptionError = "MissingKey" | "InvalidKey";
/**
* The media of one member, backed by a LiveKit participant inside the SDK.
* There is no identity field: the LiveKit identity is the member's `id`.
*/
export interface MemberMedia {
local: boolean;
/**
* One entry per published track, in publication order. 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)[]>;
/** Emits when the SFU reports a key problem for this member. */
encryptionError$: Observable<EncryptionError>;
}
export interface LocalMemberMedia extends MemberMedia {
local: true;
tracks$: Behavior<(LocalAudioMediaTrack | LocalVideoMediaTrack)[]>;
}
+1 -1
View File
@@ -13,10 +13,10 @@ import { type ObservableScope } from "../reactive/ObservableScope";
import { type createMatrixMemberMetadata$ } from "./MatrixMemberMetadata"; import { type createMatrixMemberMetadata$ } from "./MatrixMemberMetadata";
import { import {
type LocalRTCMember, type LocalRTCMember,
type MemberMedia,
type RemoteRTCMember, type RemoteRTCMember,
type RTCMember, type RTCMember,
} from "../api"; } from "../api";
import { type MemberMedia } from "../media-api";
import { type LocalMediaBackend } from "../media-backend/api"; import { type LocalMediaBackend } from "../media-backend/api";
import { type TransportRegistry } from "./Transports"; import { type TransportRegistry } from "./Transports";
+161
View File
@@ -0,0 +1,161 @@
/*
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.
*/
/**
* The public media types of the SDK: what a member publishes, the tracks it
* arrives as and how a host renders them. The client and member types are in
* `api.ts`.
*/
import {
type Track,
type TrackProcessor,
type VideoCodec,
} from "livekit-client";
import { type Observable } from "rxjs";
import { type Behavior } from "./reactive/Behavior";
export interface AudioCaptureSettings {
echoCancellation?: boolean;
noiseSuppression?: boolean;
autoGainControl?: boolean;
}
export interface VideoCaptureSettings {
resolution?: { width: number; height: number; frameRate?: number };
maxBitrate?: number;
maxFramerate?: number;
codec?: VideoCodec;
}
/**
* One thing to publish: a source, where to capture it from and how to encode
* it. Where the device and the settings are left out, the browser's and
* LiveKit's defaults apply.
*/
export type PublishRequest =
| { source: "microphone"; deviceId?: string; capture?: AudioCaptureSettings }
| {
source: "camera";
deviceId?: string;
/** Background blur and the like. */
processor?: TrackProcessor<Track.Kind.Video>;
capture?: VideoCaptureSettings;
}
| {
source: "screenShare";
/** Whether to capture the screen's audio too. Default true. */
audio?: boolean;
capture?: VideoCaptureSettings;
};
export type MediaSource =
| "microphone"
| "camera"
| "screenShare"
| "screenShareAudio"
/** Published without a source; the application knows what it is. */
| "unknown";
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<boolean>;
/** False when the SFU reports the track as unencrypted. */
encrypted$: Behavior<boolean>;
/** Polled while subscribed. */
stats$: Behavior<MediaStreamStats>;
/**
* Rendering. The view hands its `<video>` or `<audio>` element over; the SDK
* sets its stream and, for video, registers the size and on-screen observers
* that pick a simulcast layer and pause the subscription while the element is
* hidden. `attach` is idempotent per element; `detach` has to be called
* before the element leaves the DOM so those observers are released.
*/
attach(element: HTMLMediaElement): void;
detach(element: HTMLMediaElement): void;
}
export interface AudioMediaTrack extends MediaTrack {
kind: "audio";
/**
* Whether the track carries sound right now, as the backend measures it.
* False while muted. A call reads this on the microphone track and calls it
* "speaking". LiveKit measures it per member, so every audio track of a
* member reports the same value.
*/
isActive$: Behavior<boolean>;
/** Route playback through Web Audio, for earpiece pan and gain. Undefined resets. */
setAudioContext(ctx: AudioContext | undefined, plugins?: AudioNode[]): void;
setVolume(volume: number): void;
}
export interface VideoMediaTrack extends MediaTrack {
kind: "video";
}
/** The controls a member has over a track it publishes itself. */
export interface LocalMediaTrack {
/**
* Mutes or unmutes. Resolves with the state that resulted, which differs
* from the request where the device could not be used.
*/
setEnabled(enabled: boolean): Promise<boolean>;
/** Captures from another device. Rejects for a screen share, which has none. */
setDevice(deviceId: string): Promise<void>;
}
export interface LocalAudioMediaTrack
extends AudioMediaTrack, LocalMediaTrack {}
export interface LocalVideoMediaTrack extends VideoMediaTrack, LocalMediaTrack {
/** For mirroring; undefined where the camera does not say which way it faces. */
facingMode$: Behavior<"user" | "environment" | undefined>;
/**
* Restarts the camera facing the other way, on devices with a front and a
* back camera, and resolves with the id of the device now in use. Does
* nothing where the facing mode is unknown.
*/
switchFacingMode(): Promise<string | undefined>;
/** Background blur and the like; undefined removes the processor. */
setProcessor(
processor: TrackProcessor<Track.Kind.Video> | undefined,
): Promise<void>;
}
export type EncryptionError = "MissingKey" | "InvalidKey";
/**
* The media of one member, backed by a LiveKit participant inside the SDK.
* There is no identity field: the LiveKit identity is the member's `id`.
*/
export interface MemberMedia {
local: boolean;
/**
* One entry per published track, in publication order. 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)[]>;
/** Emits when the SFU reports a key problem for this member. */
encryptionError$: Observable<EncryptionError>;
}
export interface LocalMemberMedia extends MemberMedia {
local: true;
tracks$: Behavior<(LocalAudioMediaTrack | LocalVideoMediaTrack)[]>;
}
+2 -3
View File
@@ -17,15 +17,14 @@ import { type Transport } from "matrix-js-sdk/lib/matrixrtc";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
import { type Observable } from "rxjs"; import { type Observable } from "rxjs";
import { type ResolvedTransport, type RTCMembership } from "../api";
import { import {
type LocalAudioMediaTrack, type LocalAudioMediaTrack,
type LocalMemberMedia, type LocalMemberMedia,
type LocalVideoMediaTrack, type LocalVideoMediaTrack,
type MemberMedia, type MemberMedia,
type PublishRequest, type PublishRequest,
type ResolvedTransport, } from "../media-api";
type RTCMembership,
} from "../api";
import { type SessionTimings } from "../config"; import { type SessionTimings } from "../config";
import { type EncryptionSystem } from "../encryption"; import { type EncryptionSystem } from "../encryption";
import { type Behavior } from "../reactive/Behavior"; import { type Behavior } from "../reactive/Behavior";
@@ -23,7 +23,8 @@ import { type OpenIDClientParts, type SFUConfig } from "./openIDSFU";
import { type Behavior } from "../../reactive/Behavior"; import { type Behavior } from "../../reactive/Behavior";
import { Connection, type ConnectionOpts, ConnectionState } from "./Connection"; import { Connection, type ConnectionOpts, ConnectionState } from "./Connection";
import { type ObservableScope } from "../../reactive/ObservableScope"; import { type ObservableScope } from "../../reactive/ObservableScope";
import { type PublishRequest, type ResolvedTransport } from "../../api"; import { type ResolvedTransport } from "../../api";
import { type PublishRequest } from "../../media-api";
import { type MediaQuality } from "../../config"; import { type MediaQuality } from "../../config";
import { import {
audioCaptureOptions, audioCaptureOptions,
@@ -23,11 +23,8 @@ import {
switchMap, switchMap,
} from "rxjs"; } from "rxjs";
import { import { type ResolvedTransport, type RTCMembership } from "../../api";
type MemberMedia, import { type MemberMedia } from "../../media-api";
type ResolvedTransport,
type RTCMembership,
} from "../../api";
import { type MediaQuality } from "../../config"; import { type MediaQuality } from "../../config";
import { MatrixRTCError } from "../../errors"; import { MatrixRTCError } from "../../errors";
import { type Behavior } from "../../reactive/Behavior"; import { type Behavior } from "../../reactive/Behavior";
@@ -19,7 +19,7 @@ import {
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ObservableScope } from "../../reactive/ObservableScope"; import { ObservableScope } from "../../reactive/ObservableScope";
import { type AudioMediaTrack } from "../../api"; import { type AudioMediaTrack } from "../../media-api";
import { import {
createLivekitMediaTrack, createLivekitMediaTrack,
createLocalLivekitMediaTrack, createLocalLivekitMediaTrack,
@@ -48,7 +48,7 @@ import {
type MediaStreamStats, type MediaStreamStats,
type MediaTrack, type MediaTrack,
type VideoMediaTrack, type VideoMediaTrack,
} from "../../api"; } from "../../media-api";
import { MatrixRTCError } from "../../errors"; import { MatrixRTCError } from "../../errors";
import { LazyBehavior } from "../../utils/LazyBehavior"; import { LazyBehavior } from "../../utils/LazyBehavior";
@@ -28,7 +28,7 @@ import {
type LocalMemberMedia, type LocalMemberMedia,
type MediaSource, type MediaSource,
type MemberMedia, type MemberMedia,
} from "../../api"; } from "../../media-api";
import { import {
createLivekitMediaTrack, createLivekitMediaTrack,
createLocalLivekitMediaTrack, createLocalLivekitMediaTrack,
+1 -1
View File
@@ -32,7 +32,7 @@ import {
type LocalVideoMediaTrack, type LocalVideoMediaTrack,
type MediaSource, type MediaSource,
type PublishRequest, type PublishRequest,
} from "../../api"; } from "../../media-api";
import { type EncryptionSystem } from "../../encryption"; import { type EncryptionSystem } from "../../encryption";
import { FailToStartLivekitConnection, toMatrixRTCError } from "../../errors"; import { FailToStartLivekitConnection, toMatrixRTCError } from "../../errors";
import { type Behavior } from "../../reactive/Behavior"; import { type Behavior } from "../../reactive/Behavior";
+1 -1
View File
@@ -14,7 +14,7 @@ import {
} from "livekit-client"; } from "livekit-client";
import { type Logger } from "matrix-js-sdk/lib/logger"; import { type Logger } from "matrix-js-sdk/lib/logger";
import { type MediaSource, type PublishRequest } from "../../api"; import { type MediaSource, type PublishRequest } from "../../media-api";
import { livekitSources, mediaSources } from "./LivekitMediaTrack"; import { livekitSources, mediaSources } from "./LivekitMediaTrack";
import { import {
audioCaptureOptions, audioCaptureOptions,
@@ -12,7 +12,10 @@ import {
type VideoCaptureOptions, type VideoCaptureOptions,
} from "livekit-client"; } from "livekit-client";
import { type PublishRequest, type VideoCaptureSettings } from "../../api"; import {
type PublishRequest,
type VideoCaptureSettings,
} from "../../media-api";
export type PublishRequestFor<S extends PublishRequest["source"]> = Extract< export type PublishRequestFor<S extends PublishRequest["source"]> = Extract<
PublishRequest, PublishRequest,
+1 -1
View File
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { type MediaTrack } from "../api"; import { type MediaTrack } from "../media-api";
import { testScope } from "./test"; import { testScope } from "./test";
import { trackBySource$ } from "./tracks"; import { trackBySource$ } from "./tracks";
+1 -1
View File
@@ -14,7 +14,7 @@ import {
type MediaSource, type MediaSource,
type MediaTrack, type MediaTrack,
type VideoMediaTrack, type VideoMediaTrack,
} from "../api"; } from "../media-api";
export type TrackOfSource<S extends MediaSource> = S extends export type TrackOfSource<S extends MediaSource> = S extends
| "microphone" | "microphone"