@streaming-cdn/rtc-web 1.3.13 → 1.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +15 -0
- package/NOTICE.md +8 -0
- package/README.md +36 -1
- package/dist/active-speaker.d.ts +25 -0
- package/dist/call-recorder.d.ts +48 -0
- package/dist/index.d.ts +345 -0
- package/dist/native-room.d.ts +104 -0
- package/dist/peer-quality.d.ts +80 -0
- package/dist/picture-in-picture.d.ts +40 -0
- package/dist/rtc-web.es.js +1996 -0
- package/dist/rtc-web.umd.js +1 -0
- package/dist/sfu-room.d.ts +96 -0
- package/dist/video-effects.d.ts +46 -0
- package/package.json +1 -1
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
STREAMING CDN SDK BINARY LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026. All rights reserved.
|
|
4
|
+
|
|
5
|
+
The SDK binaries, public API declarations, documentation, and example code are
|
|
6
|
+
licensed for use with an active Streaming CDN service account and are governed
|
|
7
|
+
by the applicable service agreement.
|
|
8
|
+
|
|
9
|
+
Except where third-party notices state otherwise, redistribution, modification,
|
|
10
|
+
reverse engineering, decompilation, and creation of derivative SDK products are
|
|
11
|
+
not permitted without prior written authorization.
|
|
12
|
+
|
|
13
|
+
Example applications may be modified and incorporated into an application that
|
|
14
|
+
uses the Streaming CDN service. No ownership rights in the SDK binaries or
|
|
15
|
+
service are transferred.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The SDK uses platform and media-runtime components distributed under their own
|
|
4
|
+
licenses. Those components remain subject to their respective license terms.
|
|
5
|
+
|
|
6
|
+
The binary SDK license applies only to the Streaming CDN integration layer.
|
|
7
|
+
Applications must also comply with Apple, Google, browser, notification-service,
|
|
8
|
+
and media-runtime requirements applicable to the selected integration modules.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# RTC Web SDK 1.3.
|
|
1
|
+
# RTC Web SDK 1.3.15
|
|
2
2
|
|
|
3
3
|
The customer package contains compiled ESM/UMD JavaScript and TypeScript declarations. It intentionally excludes implementation source and source maps. Example source remains available under `examples/`.
|
|
4
4
|
|
|
@@ -169,6 +169,41 @@ the room client (`getNativeClient()`); the publisher keeps sending every
|
|
|
169
169
|
layer. The React Native adapter has the same transport option and the same
|
|
170
170
|
`setRemoteQuality` on the client.
|
|
171
171
|
|
|
172
|
+
## Virtual backgrounds and effects
|
|
173
|
+
|
|
174
|
+
`createVideoEffectsPipeline(cameraTrack)` draws the camera through a canvas
|
|
175
|
+
and hands back a processed track for `setVideoTrack`, so both transports
|
|
176
|
+
publish it unchanged. Backgrounds: `blur`, animated procedural scenes
|
|
177
|
+
(`aurora`, `cybergrid`, `bokeh`, `sunset`), or your own `image`. Overlays:
|
|
178
|
+
`snow`, `confetti`, `vignette`. Person cut-out uses any `Segmenter` you plug
|
|
179
|
+
in (for example a self-hosted MediaPipe selfie model); without one the
|
|
180
|
+
pipeline degrades honestly — `blur` blurs the whole frame, scenes show the
|
|
181
|
+
camera as a floating card.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const effects = createVideoEffectsPipeline(cameraTrack, { segmenter });
|
|
185
|
+
effects.setBackground("aurora");
|
|
186
|
+
effects.setOverlay("confetti");
|
|
187
|
+
await media.setVideoTrack(effects.track);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Call recording
|
|
191
|
+
|
|
192
|
+
`createCallRecorder(media.getNativeClient(), { credential })` composites every
|
|
193
|
+
participant's video onto a canvas, mixes all audio, records with
|
|
194
|
+
MediaRecorder, and on `stop()` uploads the file with the room signaling token.
|
|
195
|
+
The platform stores it, bills the storage, lists it under
|
|
196
|
+
`GET /v1/rtc/recordings` (Server API), and serves it from
|
|
197
|
+
`GET /v1/rtc/recordings/{id}/download`. Recording happens on this client —
|
|
198
|
+
what is recorded is what this participant saw and heard.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const recorder = createCallRecorder(media.getNativeClient(), { credential });
|
|
202
|
+
await recorder.start();
|
|
203
|
+
// ... later
|
|
204
|
+
const { recordingId, durationSeconds } = await recorder.stop();
|
|
205
|
+
```
|
|
206
|
+
|
|
172
207
|
## Active speaker
|
|
173
208
|
|
|
174
209
|
The client samples WebRTC audio levels once a second on both transports and
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active speaker detection.
|
|
3
|
+
*
|
|
4
|
+
* A call UI wants to highlight whoever is talking without every application
|
|
5
|
+
* re-implementing audio-level plumbing. Levels come from WebRTC's own
|
|
6
|
+
* `inbound-rtp` audio stats — no AudioContext, no extra decoding — and the
|
|
7
|
+
* choice has hysteresis: the floor only changes hands when somebody is
|
|
8
|
+
* clearly louder than the current speaker, otherwise two people at similar
|
|
9
|
+
* volume would flap the highlight every second.
|
|
10
|
+
*/
|
|
11
|
+
export interface SpeakerLevel {
|
|
12
|
+
participantId: string;
|
|
13
|
+
/** WebRTC audioLevel, 0..1. */
|
|
14
|
+
level: number;
|
|
15
|
+
}
|
|
16
|
+
/** Below this a level is background noise, not speech. */
|
|
17
|
+
export declare const SPEAKER_NOISE_FLOOR = 0.02;
|
|
18
|
+
/** A challenger must be this much louder than the current speaker to take over. */
|
|
19
|
+
export declare const SPEAKER_TAKEOVER_RATIO = 1.4;
|
|
20
|
+
export declare function pickActiveSpeaker(levels: SpeakerLevel[], previous: string | null, noiseFloor?: number, takeoverRatio?: number): string | null;
|
|
21
|
+
/** Reads the audio level of each inbound audio RTP stream, keyed by mid. */
|
|
22
|
+
export declare function readAudioLevels(report: Iterable<Record<string, unknown>>): Array<{
|
|
23
|
+
mid: string | null;
|
|
24
|
+
level: number;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side call recording.
|
|
3
|
+
*
|
|
4
|
+
* The mesh has no server with access to the media, so the first recorder that
|
|
5
|
+
* can exist lives where the media already is: this client. It composites every
|
|
6
|
+
* video tile onto a canvas, mixes every audio track, records the result with
|
|
7
|
+
* MediaRecorder, and uploads the finished file with the room signaling token.
|
|
8
|
+
* The platform bills and purges the stored file through the same
|
|
9
|
+
* `rtc_recordings` rows a future server-side recorder will use.
|
|
10
|
+
*/
|
|
11
|
+
export interface RecordableRoomClient {
|
|
12
|
+
getRemoteStreams(): Array<{
|
|
13
|
+
participant: {
|
|
14
|
+
participantId: string;
|
|
15
|
+
displayName: string;
|
|
16
|
+
};
|
|
17
|
+
stream: MediaStream;
|
|
18
|
+
}>;
|
|
19
|
+
getLocalStream(): MediaStream | null;
|
|
20
|
+
}
|
|
21
|
+
export interface CallRecorderOptions {
|
|
22
|
+
credential: {
|
|
23
|
+
signalingToken: string;
|
|
24
|
+
signalingUrl: string;
|
|
25
|
+
};
|
|
26
|
+
/** Canvas output height; width follows 16:9. Default 720. */
|
|
27
|
+
height?: number;
|
|
28
|
+
mimeType?: string;
|
|
29
|
+
/** Test seams. */
|
|
30
|
+
document?: Document;
|
|
31
|
+
mediaRecorderFactory?: (stream: MediaStream, options: {
|
|
32
|
+
mimeType: string;
|
|
33
|
+
}) => MediaRecorder;
|
|
34
|
+
fetchImplementation?: typeof fetch;
|
|
35
|
+
}
|
|
36
|
+
export interface CallRecorderResult {
|
|
37
|
+
recordingId: string;
|
|
38
|
+
sizeBytes: number;
|
|
39
|
+
durationSeconds: number;
|
|
40
|
+
}
|
|
41
|
+
export interface CallRecorder {
|
|
42
|
+
readonly recording: boolean;
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
/** Stops, uploads, and marks the recording ready. */
|
|
45
|
+
stop(): Promise<CallRecorderResult>;
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
export declare function createCallRecorder(client: RecordableRoomClient, options: CallRecorderOptions): CallRecorder;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
export { createPictureInPictureController } from "./picture-in-picture";
|
|
2
|
+
export type { PictureInPictureController, PictureInPictureOptions } from "./picture-in-picture";
|
|
3
|
+
export { pickActiveSpeaker, SPEAKER_NOISE_FLOOR, SPEAKER_TAKEOVER_RATIO } from "./active-speaker";
|
|
4
|
+
export type { SpeakerLevel } from "./active-speaker";
|
|
5
|
+
export { createCallRecorder } from "./call-recorder";
|
|
6
|
+
export type { CallRecorder, CallRecorderOptions, CallRecorderResult } from "./call-recorder";
|
|
7
|
+
export { BACKGROUND_PRESETS, createVideoEffectsPipeline, OVERLAY_EFFECTS, scenePainters } from "./video-effects";
|
|
8
|
+
export type { BackgroundPreset, OverlayEffect, Segmenter, VideoEffectsOptions, VideoEffectsPipeline } from "./video-effects";
|
|
9
|
+
export type RtcMode = "voice" | "video" | "meeting";
|
|
10
|
+
/**
|
|
11
|
+
* How media travels. `mesh` (the default) opens one connection per pair and
|
|
12
|
+
* suits direct calls and small rooms; `sfu` sends media once to the platform
|
|
13
|
+
* SFU and pulls each remote participant from it, which is what a large room
|
|
14
|
+
* needs. `auto` picks by product: meetings — rooms that grow — take the SFU,
|
|
15
|
+
* voice and video calls stay on the mesh. The rest of the public contract is
|
|
16
|
+
* identical either way.
|
|
17
|
+
*/
|
|
18
|
+
export type RtcTransport = "mesh" | "sfu" | "auto";
|
|
19
|
+
export type RtcQuality = "auto" | "audio" | "low" | "medium" | "high";
|
|
20
|
+
export type RtcVideoCodec = "auto" | "vp8" | "h264";
|
|
21
|
+
export type RtcIceTransportPolicy = "all" | "relay";
|
|
22
|
+
export type RtcConnectionState = "initialized" | "joining" | "connected" | "reconnecting" | "disconnected" | "failed";
|
|
23
|
+
export interface RtcCredential {
|
|
24
|
+
signalingToken: string;
|
|
25
|
+
signalingUrl: string;
|
|
26
|
+
iceServers?: RTCIceServer[];
|
|
27
|
+
iceServersExpiresAt?: string;
|
|
28
|
+
roomId: string;
|
|
29
|
+
participantId?: string;
|
|
30
|
+
externalUserId?: string;
|
|
31
|
+
telemetryToken?: string;
|
|
32
|
+
telemetryEndpoint?: string;
|
|
33
|
+
telemetryExpiresAt?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RtcClientOptions {
|
|
36
|
+
credential: RtcCredential;
|
|
37
|
+
mode: RtcMode;
|
|
38
|
+
/** Reuses media acquired by a pre-join device room instead of requesting it again. */
|
|
39
|
+
localStream?: MediaStream | null;
|
|
40
|
+
/** Publishes microphone audio when true. */
|
|
41
|
+
audio?: boolean;
|
|
42
|
+
/** Publishes camera video when true. */
|
|
43
|
+
video?: boolean;
|
|
44
|
+
/** Creates an audio recvonly transceiver when local audio is not published. */
|
|
45
|
+
receiveAudio?: boolean;
|
|
46
|
+
/** Creates a video recvonly transceiver when local video is not published. */
|
|
47
|
+
receiveVideo?: boolean;
|
|
48
|
+
/** Initial outbound video quality. "auto" adapts between low, medium and high. */
|
|
49
|
+
quality?: RtcQuality;
|
|
50
|
+
/** Preferred video codec. Negotiation still retains compatible fallback codecs. */
|
|
51
|
+
preferredVideoCodec?: RtcVideoCodec;
|
|
52
|
+
/** Uses every ICE route by default. Set relay to diagnose or avoid a poor direct carrier route. */
|
|
53
|
+
iceTransportPolicy?: RtcIceTransportPolicy;
|
|
54
|
+
/** Media transport. Defaults to the peer mesh; "sfu" routes media through the platform SFU. */
|
|
55
|
+
transport?: RtcTransport;
|
|
56
|
+
/** Enables automatic quality changes when quality is "auto". */
|
|
57
|
+
adaptiveQuality?: boolean;
|
|
58
|
+
autoJoin?: boolean;
|
|
59
|
+
statsInterval?: number;
|
|
60
|
+
telemetryInterval?: number;
|
|
61
|
+
autoReportStats?: boolean;
|
|
62
|
+
telemetryEndpoint?: string;
|
|
63
|
+
preflightProbeUrl?: string;
|
|
64
|
+
peerConnection?: RTCPeerConnection | null;
|
|
65
|
+
statsProvider?: () => Promise<Partial<RtcQualitySample>>;
|
|
66
|
+
onEvent?: (event: RtcSdkEvent) => void;
|
|
67
|
+
}
|
|
68
|
+
export interface RtcSdkEvent<T = unknown> {
|
|
69
|
+
type: string;
|
|
70
|
+
timestamp: string;
|
|
71
|
+
detail: T;
|
|
72
|
+
}
|
|
73
|
+
export interface RtcQualitySample {
|
|
74
|
+
sampleType: "preflight" | "runtime" | "final";
|
|
75
|
+
sampledAt: string;
|
|
76
|
+
connectionState: string;
|
|
77
|
+
mediaScore: number | null;
|
|
78
|
+
networkScore: number;
|
|
79
|
+
estimatedMos: number;
|
|
80
|
+
rttMs: number | null;
|
|
81
|
+
jitterMs: number | null;
|
|
82
|
+
packetLossPct: number | null;
|
|
83
|
+
/** Packet loss since the previous sample. packetLossPct uses the same interval. */
|
|
84
|
+
packetLossCumulativePct: number | null;
|
|
85
|
+
packetsLost: number | null;
|
|
86
|
+
packetsReceived: number | null;
|
|
87
|
+
inboundBitrateBps: number | null;
|
|
88
|
+
outboundBitrateBps: number | null;
|
|
89
|
+
availableOutgoingBitrateBps: number | null;
|
|
90
|
+
framesPerSecond: number | null;
|
|
91
|
+
frameWidth: number | null;
|
|
92
|
+
frameHeight: number | null;
|
|
93
|
+
framesDropped: number | null;
|
|
94
|
+
freezeCount: number | null;
|
|
95
|
+
freezeDurationMs: number | null;
|
|
96
|
+
concealedSamples: number | null;
|
|
97
|
+
codec: string | null;
|
|
98
|
+
transportProtocol: string | null;
|
|
99
|
+
candidateType: string | null;
|
|
100
|
+
qualityLimitationReason: string | null;
|
|
101
|
+
deviceMemoryGb: number | null;
|
|
102
|
+
effectiveConnectionType: string | null;
|
|
103
|
+
}
|
|
104
|
+
export interface RtcStats {
|
|
105
|
+
state: RtcConnectionState;
|
|
106
|
+
participantCount: number;
|
|
107
|
+
audioEnabled: boolean;
|
|
108
|
+
videoEnabled: boolean;
|
|
109
|
+
screenShareEnabled: boolean;
|
|
110
|
+
latestMediaScore: number | null;
|
|
111
|
+
latestQuality: RtcQualitySample | null;
|
|
112
|
+
sampledAt: string;
|
|
113
|
+
}
|
|
114
|
+
export interface RtcPreflightResult {
|
|
115
|
+
passed: boolean;
|
|
116
|
+
sampledAt: string;
|
|
117
|
+
secureContext: boolean;
|
|
118
|
+
mediaPermission: "granted" | "denied" | "unavailable";
|
|
119
|
+
microphoneCount: number;
|
|
120
|
+
cameraCount: number;
|
|
121
|
+
probeLatencyMs: number | null;
|
|
122
|
+
effectiveConnectionType: string | null;
|
|
123
|
+
issues: string[];
|
|
124
|
+
}
|
|
125
|
+
export interface RtcParticipant {
|
|
126
|
+
id: string;
|
|
127
|
+
name: string;
|
|
128
|
+
picture?: string;
|
|
129
|
+
role: string;
|
|
130
|
+
}
|
|
131
|
+
export interface RtcRemoteStream {
|
|
132
|
+
participant: RtcParticipant;
|
|
133
|
+
stream: MediaStream;
|
|
134
|
+
}
|
|
135
|
+
export declare class RtcClient extends EventTarget {
|
|
136
|
+
readonly mode: RtcMode;
|
|
137
|
+
private readonly options;
|
|
138
|
+
private client;
|
|
139
|
+
private peerConnection;
|
|
140
|
+
private state;
|
|
141
|
+
private latestMediaScore;
|
|
142
|
+
private latestQuality;
|
|
143
|
+
private statsTimer;
|
|
144
|
+
private statsRunning;
|
|
145
|
+
private previousCounters;
|
|
146
|
+
private lastTelemetryAt;
|
|
147
|
+
private joinPromise;
|
|
148
|
+
private requestedQuality;
|
|
149
|
+
private effectiveQuality;
|
|
150
|
+
private poorQualitySamples;
|
|
151
|
+
private goodQualitySamples;
|
|
152
|
+
constructor(options: RtcClientOptions);
|
|
153
|
+
initialize(): Promise<this>;
|
|
154
|
+
join(): Promise<void>;
|
|
155
|
+
leave(): Promise<void>;
|
|
156
|
+
setMuted(muted: boolean): Promise<void>;
|
|
157
|
+
setCameraEnabled(enabled: boolean): Promise<void>;
|
|
158
|
+
setVideoTrack(track: MediaStreamTrack): Promise<void>;
|
|
159
|
+
setScreenShareEnabled(enabled: boolean): Promise<void>;
|
|
160
|
+
setDevice(device: MediaDeviceInfo): Promise<void>;
|
|
161
|
+
setQuality(quality: RtcQuality): Promise<void>;
|
|
162
|
+
setPreferredVideoCodec(codec: RtcVideoCodec): Promise<void>;
|
|
163
|
+
getDevices(): Promise<MediaDeviceInfo[]>;
|
|
164
|
+
getParticipants(): ReadonlyArray<RtcParticipant>;
|
|
165
|
+
getLocalStream(): MediaStream | null;
|
|
166
|
+
getRemoteStreams(): RtcRemoteStream[];
|
|
167
|
+
setPeerConnection(peerConnection: RTCPeerConnection | null): void;
|
|
168
|
+
sendChatMessage(message: string, participantIds?: string[]): Promise<void>;
|
|
169
|
+
sendControlMessage(action: string, detail?: Record<string, unknown>): Promise<void>;
|
|
170
|
+
createPoll(question: string, options: string[], config?: {
|
|
171
|
+
anonymous?: boolean;
|
|
172
|
+
hideVotes?: boolean;
|
|
173
|
+
}): Promise<void>;
|
|
174
|
+
votePoll(pollId: string, optionIndex: number): Promise<void>;
|
|
175
|
+
requestStageAccess(): Promise<void>;
|
|
176
|
+
cancelStageAccess(): Promise<void>;
|
|
177
|
+
joinStage(): Promise<void>;
|
|
178
|
+
leaveStage(): Promise<void>;
|
|
179
|
+
grantStageAccess(participantIds: string[]): Promise<void>;
|
|
180
|
+
denyStageAccess(participantIds: string[]): Promise<void>;
|
|
181
|
+
startRecording(allowMultiple?: boolean): Promise<void>;
|
|
182
|
+
stopRecording(recordingId?: string): Promise<void>;
|
|
183
|
+
pauseRecording(recordingId?: string): Promise<void>;
|
|
184
|
+
resumeRecording(recordingId?: string): Promise<void>;
|
|
185
|
+
runPreflight(options?: {
|
|
186
|
+
requestMedia?: boolean;
|
|
187
|
+
probeUrl?: string;
|
|
188
|
+
}): Promise<RtcPreflightResult>;
|
|
189
|
+
collectQualitySample(sampleType?: RtcQualitySample["sampleType"]): Promise<RtcQualitySample>;
|
|
190
|
+
reportQuality(sample: Partial<RtcQualitySample>): Promise<void>;
|
|
191
|
+
getStats(): RtcStats;
|
|
192
|
+
/** Escape hatch for advanced integrations. Prefer the stable SDK methods when possible. */
|
|
193
|
+
getNativeClient<T = unknown>(): T | null;
|
|
194
|
+
destroy(): void;
|
|
195
|
+
private handleNativeEvent;
|
|
196
|
+
private startStats;
|
|
197
|
+
private sampleAndReport;
|
|
198
|
+
private stopStats;
|
|
199
|
+
private adaptQuality;
|
|
200
|
+
private applyEffectiveQuality;
|
|
201
|
+
private requireClient;
|
|
202
|
+
private emit;
|
|
203
|
+
}
|
|
204
|
+
export declare function createRtcClient(options: RtcClientOptions): Promise<RtcClient>;
|
|
205
|
+
export interface RtcIncomingCall {
|
|
206
|
+
id: string;
|
|
207
|
+
mode: "voice" | "video";
|
|
208
|
+
status: string;
|
|
209
|
+
caller: {
|
|
210
|
+
id: string;
|
|
211
|
+
name: string;
|
|
212
|
+
};
|
|
213
|
+
metadata?: Record<string, unknown>;
|
|
214
|
+
createdAt?: string;
|
|
215
|
+
expiresAt?: string;
|
|
216
|
+
}
|
|
217
|
+
export interface RtcPresenceUser {
|
|
218
|
+
externalUserId: string;
|
|
219
|
+
displayName: string;
|
|
220
|
+
platform: "web" | "ios" | "android";
|
|
221
|
+
devices: number;
|
|
222
|
+
connectedAt: string;
|
|
223
|
+
}
|
|
224
|
+
export type RtcClientTokenReason = "initial" | "expiring" | "reconnect" | "unauthorized";
|
|
225
|
+
export interface RtcClientIdentityToken {
|
|
226
|
+
token: string;
|
|
227
|
+
expiresAt?: string;
|
|
228
|
+
}
|
|
229
|
+
export type RtcClientTokenProvider = (context: {
|
|
230
|
+
reason: RtcClientTokenReason;
|
|
231
|
+
previousExpiresAt?: string;
|
|
232
|
+
}) => Promise<string | RtcClientIdentityToken>;
|
|
233
|
+
export interface RtcIncomingClientOptions {
|
|
234
|
+
/** Fixed short-lived token. Prefer tokenProvider for production applications. */
|
|
235
|
+
token?: string;
|
|
236
|
+
/** Expiration timestamp for a fixed token, when known. */
|
|
237
|
+
tokenExpiresAt?: string;
|
|
238
|
+
/** Fetches a fresh short-lived token from the application's trusted backend. */
|
|
239
|
+
tokenProvider?: RtcClientTokenProvider;
|
|
240
|
+
/** Refresh lead time. Defaults to five minutes and scales down for short-lived tokens. */
|
|
241
|
+
tokenRefreshSkewMs?: number;
|
|
242
|
+
signalingUrl?: string;
|
|
243
|
+
apiBaseUrl?: string;
|
|
244
|
+
autoConnect?: boolean;
|
|
245
|
+
mount?: HTMLElement | string | null;
|
|
246
|
+
reconnect?: boolean;
|
|
247
|
+
reportSignalingDiagnostics?: boolean;
|
|
248
|
+
/** Enables call audio. Defaults to true. */
|
|
249
|
+
sounds?: boolean;
|
|
250
|
+
/** Custom incoming ringtone URL. A built-in tone is used when omitted. */
|
|
251
|
+
ringtoneUrl?: string;
|
|
252
|
+
/** Custom outgoing ringback URL. A built-in tone is used when omitted. */
|
|
253
|
+
ringbackUrl?: string;
|
|
254
|
+
/** How long terminal call states remain visible in the built-in UI. Defaults to 1600 ms. */
|
|
255
|
+
terminalStateDurationMs?: number;
|
|
256
|
+
onEvent?: (event: RtcSdkEvent) => void;
|
|
257
|
+
}
|
|
258
|
+
export interface RtcCallActionResult {
|
|
259
|
+
call: Record<string, unknown>;
|
|
260
|
+
credential?: RtcCredential;
|
|
261
|
+
callerCredential?: RtcCredential;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Unlock call audio from a click/tap handler before awaiting network work.
|
|
265
|
+
* Browsers may otherwise block incoming ringtones started by signaling events.
|
|
266
|
+
*/
|
|
267
|
+
export declare function unlockRtcCallAudio(): Promise<boolean>;
|
|
268
|
+
export declare class RtcIncomingClient extends EventTarget {
|
|
269
|
+
private readonly options;
|
|
270
|
+
private socket;
|
|
271
|
+
private stopped;
|
|
272
|
+
private reconnectAttempt;
|
|
273
|
+
private reconnectTimer;
|
|
274
|
+
private heartbeatTimer;
|
|
275
|
+
private heartbeatDeadline;
|
|
276
|
+
private currentCall;
|
|
277
|
+
private outgoingCallId;
|
|
278
|
+
private mount;
|
|
279
|
+
private terminalTimer;
|
|
280
|
+
private tokenRefreshTimer;
|
|
281
|
+
private tokenRequest;
|
|
282
|
+
private currentToken;
|
|
283
|
+
private currentTokenExpiresAt?;
|
|
284
|
+
private readonly callAudio;
|
|
285
|
+
private readonly mediaClients;
|
|
286
|
+
private readonly activeCallIds;
|
|
287
|
+
private readonly seenInvitationIds;
|
|
288
|
+
private startCallRequest;
|
|
289
|
+
private lastDisconnect;
|
|
290
|
+
constructor(options: RtcIncomingClientOptions);
|
|
291
|
+
connect(): this;
|
|
292
|
+
setMount(target: HTMLElement | string | null): void;
|
|
293
|
+
/** Binds media lifetime to a call. Terminal call states automatically leave and destroy it. */
|
|
294
|
+
attachMediaClient(callId: string, media: RtcClient): () => void;
|
|
295
|
+
/**
|
|
296
|
+
* Rings everyone in `targets` as a single call and connects whoever answers
|
|
297
|
+
* first; the rest stop ringing on their own. `target` still works and means a
|
|
298
|
+
* call with one recipient.
|
|
299
|
+
*/
|
|
300
|
+
startCall(input: {
|
|
301
|
+
mode: "voice" | "video";
|
|
302
|
+
target?: {
|
|
303
|
+
id: string;
|
|
304
|
+
name: string;
|
|
305
|
+
};
|
|
306
|
+
targets?: Array<{
|
|
307
|
+
id: string;
|
|
308
|
+
name: string;
|
|
309
|
+
}>;
|
|
310
|
+
metadata?: Record<string, unknown>;
|
|
311
|
+
idempotencyKey?: string;
|
|
312
|
+
}): Promise<RtcCallActionResult>;
|
|
313
|
+
accept(callId?: string | undefined): Promise<RtcCallActionResult>;
|
|
314
|
+
reject(callId?: string | undefined): Promise<RtcCallActionResult>;
|
|
315
|
+
busy(callId?: string | undefined): Promise<RtcCallActionResult>;
|
|
316
|
+
end(callId: string): Promise<RtcCallActionResult>;
|
|
317
|
+
cancel(callId: string): Promise<RtcCallActionResult>;
|
|
318
|
+
/** Injects an incoming call received through the application's own push or signaling channel. */
|
|
319
|
+
handleIncomingCall(call: RtcIncomingCall): void;
|
|
320
|
+
/** Applies a call state received through the application's own push or signaling channel. */
|
|
321
|
+
handleCallUpdate(call: {
|
|
322
|
+
id: string;
|
|
323
|
+
status: string;
|
|
324
|
+
}): void;
|
|
325
|
+
disconnect(): void;
|
|
326
|
+
destroy(): void;
|
|
327
|
+
private openSocket;
|
|
328
|
+
private handleSignal;
|
|
329
|
+
private callAction;
|
|
330
|
+
private request;
|
|
331
|
+
private renderIncoming;
|
|
332
|
+
private dismiss;
|
|
333
|
+
private showTerminal;
|
|
334
|
+
private clearUi;
|
|
335
|
+
private scheduleReconnect;
|
|
336
|
+
private resolveToken;
|
|
337
|
+
private tokenNeedsRefresh;
|
|
338
|
+
private refreshSkew;
|
|
339
|
+
private scheduleTokenRefresh;
|
|
340
|
+
private releaseMedia;
|
|
341
|
+
private reportSignalingDiagnostic;
|
|
342
|
+
private emit;
|
|
343
|
+
}
|
|
344
|
+
export declare function createRtcIncomingClient(options: RtcIncomingClientOptions): RtcIncomingClient;
|
|
345
|
+
export declare const version = "1.3.15";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export interface NativeRoomIdentity {
|
|
2
|
+
participantId: string;
|
|
3
|
+
externalUserId: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
platform: string;
|
|
6
|
+
}
|
|
7
|
+
export interface NativeRoomCredential {
|
|
8
|
+
signalingToken: string;
|
|
9
|
+
signalingUrl: string;
|
|
10
|
+
roomId: string;
|
|
11
|
+
participantId: string;
|
|
12
|
+
externalUserId?: string;
|
|
13
|
+
iceServers?: RTCIceServer[];
|
|
14
|
+
iceServersExpiresAt?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface NativeRoomOptions {
|
|
17
|
+
credential: NativeRoomCredential;
|
|
18
|
+
audio: boolean;
|
|
19
|
+
video: boolean;
|
|
20
|
+
receiveAudio: boolean;
|
|
21
|
+
receiveVideo: boolean;
|
|
22
|
+
quality?: NativeRoomQuality;
|
|
23
|
+
preferredVideoCodec?: NativeRoomVideoCodec;
|
|
24
|
+
iceTransportPolicy?: RTCIceTransportPolicy;
|
|
25
|
+
localStream?: MediaStream | null;
|
|
26
|
+
onEvent: (type: string, detail: unknown) => void;
|
|
27
|
+
}
|
|
28
|
+
export type NativeRoomQuality = "audio" | "low" | "medium" | "high";
|
|
29
|
+
export type NativeRoomVideoCodec = "auto" | "vp8" | "h264";
|
|
30
|
+
export declare class NativeRoomClient {
|
|
31
|
+
private readonly options;
|
|
32
|
+
private socket;
|
|
33
|
+
private localStream;
|
|
34
|
+
private screenStream;
|
|
35
|
+
private readonly peers;
|
|
36
|
+
private joinPromise;
|
|
37
|
+
private resolveJoin;
|
|
38
|
+
private rejectJoin;
|
|
39
|
+
private joined;
|
|
40
|
+
private stopped;
|
|
41
|
+
private reconnectAttempt;
|
|
42
|
+
private reconnectTimer;
|
|
43
|
+
private heartbeatTimer;
|
|
44
|
+
private qualityTimer;
|
|
45
|
+
private speakerTimer;
|
|
46
|
+
private activeSpeaker;
|
|
47
|
+
private heartbeatDeadline;
|
|
48
|
+
private quality;
|
|
49
|
+
private preferredVideoCodec;
|
|
50
|
+
private signalQueue;
|
|
51
|
+
private socketGeneration;
|
|
52
|
+
constructor(options: NativeRoomOptions);
|
|
53
|
+
initialize(): Promise<void>;
|
|
54
|
+
join(): Promise<void>;
|
|
55
|
+
leave(): Promise<void>;
|
|
56
|
+
setMuted(muted: boolean): void;
|
|
57
|
+
setCameraEnabled(enabled: boolean): Promise<void>;
|
|
58
|
+
setScreenShareEnabled(enabled: boolean): Promise<void>;
|
|
59
|
+
setDevice(device: MediaDeviceInfo): Promise<void>;
|
|
60
|
+
setQuality(quality: NativeRoomQuality): Promise<void>;
|
|
61
|
+
private static clampToCeiling;
|
|
62
|
+
setPreferredVideoCodec(codec: NativeRoomVideoCodec): Promise<void>;
|
|
63
|
+
getParticipants(): NativeRoomIdentity[];
|
|
64
|
+
getLocalStream(): MediaStream | null;
|
|
65
|
+
setVideoTrack(track: MediaStreamTrack): Promise<void>;
|
|
66
|
+
getRemoteStreams(): Array<{
|
|
67
|
+
participant: NativeRoomIdentity;
|
|
68
|
+
stream: MediaStream;
|
|
69
|
+
}>;
|
|
70
|
+
getPrimaryPeerConnection(): RTCPeerConnection | null;
|
|
71
|
+
sendChat(message: string, participantIds?: string[]): void;
|
|
72
|
+
sendControl(action: string, detail?: Record<string, unknown>): void;
|
|
73
|
+
private connect;
|
|
74
|
+
private openSocket;
|
|
75
|
+
private handleSignal;
|
|
76
|
+
private ensurePeer;
|
|
77
|
+
private createOffer;
|
|
78
|
+
private performOffer;
|
|
79
|
+
private shouldInitiateOffer;
|
|
80
|
+
private renegotiateAll;
|
|
81
|
+
private replaceVideoTrack;
|
|
82
|
+
private applyVideoQuality;
|
|
83
|
+
/**
|
|
84
|
+
* Applies this connection's own tier. The room-wide setting is a ceiling
|
|
85
|
+
* rather than an instruction: asking for audio keeps every link on audio, but
|
|
86
|
+
* asking for high lets each link settle wherever it can actually hold.
|
|
87
|
+
*/
|
|
88
|
+
private applyPeerVideoQuality;
|
|
89
|
+
private startQualitySampling;
|
|
90
|
+
private stopQualitySampling;
|
|
91
|
+
private sampleActiveSpeaker;
|
|
92
|
+
private samplePeerQuality;
|
|
93
|
+
/** A new link starts one step below the ceiling so it proves itself upward. */
|
|
94
|
+
private static startingQuality;
|
|
95
|
+
private applyCodecPreference;
|
|
96
|
+
private removePeer;
|
|
97
|
+
private resetPeers;
|
|
98
|
+
private startHeartbeat;
|
|
99
|
+
private stopHeartbeat;
|
|
100
|
+
private clearConnectionTimers;
|
|
101
|
+
private scheduleReconnect;
|
|
102
|
+
private send;
|
|
103
|
+
private stopStream;
|
|
104
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-connection quality for a peer mesh.
|
|
3
|
+
*
|
|
4
|
+
* A mesh has no forwarding server, so there is nothing to send several
|
|
5
|
+
* qualities to — simulcast solves a problem this topology does not have. What
|
|
6
|
+
* it does have is one connection per participant, each with its own link, and
|
|
7
|
+
* until now a single room-wide quality was applied to all of them. One
|
|
8
|
+
* participant on a poor connection therefore either dragged the whole room down
|
|
9
|
+
* or was left with a stream their link could not carry. Each connection is
|
|
10
|
+
* rated on its own evidence instead.
|
|
11
|
+
*
|
|
12
|
+
* The ladder matches `sdk/react-native/src/quality.ts`, including the parts
|
|
13
|
+
* that look wrong until you read its tests: `"none"` is a truthy string and has
|
|
14
|
+
* to be normalised, and the step-up gate compares the bandwidth estimate
|
|
15
|
+
* against the *current* tier's ceiling on purpose. A field report argued the
|
|
16
|
+
* latter suppresses upgrades; the same report's data showed the estimate
|
|
17
|
+
* reaching 1,029,490 bps while pinned to a 600,000 tier, so the encoder cap was
|
|
18
|
+
* not holding it down, and relaxing the gate made the ladder climb to a tier the
|
|
19
|
+
* link could not carry.
|
|
20
|
+
*/
|
|
21
|
+
export type PeerQuality = "audio" | "low" | "medium" | "high";
|
|
22
|
+
export interface PeerQualityTier {
|
|
23
|
+
active: boolean;
|
|
24
|
+
maxBitrate: number;
|
|
25
|
+
maxFramerate: number;
|
|
26
|
+
scaleResolutionDownBy: number;
|
|
27
|
+
}
|
|
28
|
+
export declare const peerQualityTiers: Record<PeerQuality, PeerQualityTier>;
|
|
29
|
+
export interface PeerQualityThresholds {
|
|
30
|
+
warmupSamples: number;
|
|
31
|
+
poorSamplesToStepDown: number;
|
|
32
|
+
goodSamplesToStepUp: number;
|
|
33
|
+
criticalLossPct: number;
|
|
34
|
+
criticalRttMs: number;
|
|
35
|
+
poorLossPct: number;
|
|
36
|
+
poorRttMs: number;
|
|
37
|
+
goodLossPct: number;
|
|
38
|
+
goodRttMs: number;
|
|
39
|
+
sustainRatio: number;
|
|
40
|
+
}
|
|
41
|
+
export declare const defaultPeerQualityThresholds: PeerQualityThresholds;
|
|
42
|
+
export interface PeerQualitySample {
|
|
43
|
+
packetLossPct: number | null;
|
|
44
|
+
rttMs: number | null;
|
|
45
|
+
availableOutgoingBitrateBps: number | null;
|
|
46
|
+
qualityLimitationReason: string | null;
|
|
47
|
+
}
|
|
48
|
+
export interface PeerQualityState {
|
|
49
|
+
quality: Exclude<PeerQuality, "audio">;
|
|
50
|
+
poorSamples: number;
|
|
51
|
+
goodSamples: number;
|
|
52
|
+
samplesSeen: number;
|
|
53
|
+
changed: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** `"none"` is what a healthy sender reports, and it is a truthy string. */
|
|
56
|
+
export declare function normalizeLimitation(reason: string | null | undefined): "" | "cpu" | "bandwidth" | "other";
|
|
57
|
+
/**
|
|
58
|
+
* Chooses this connection's tier. `ceiling` is what the application asked for:
|
|
59
|
+
* adaptation may move below it but never above, so an app that asked for audio
|
|
60
|
+
* stays on audio.
|
|
61
|
+
*/
|
|
62
|
+
export declare function adaptPeerQuality(current: Exclude<PeerQuality, "audio">, sample: PeerQualitySample, poorSamples: number, goodSamples: number, options?: {
|
|
63
|
+
samplesSeen?: number;
|
|
64
|
+
ceiling?: PeerQuality;
|
|
65
|
+
thresholds?: Partial<PeerQualityThresholds>;
|
|
66
|
+
}): PeerQualityState;
|
|
67
|
+
export interface PeerCounters {
|
|
68
|
+
packetsSent: number;
|
|
69
|
+
packetsLost: number;
|
|
70
|
+
timestamp: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Reads one connection's outbound video health. Loss is a delta against the
|
|
74
|
+
* previous read rather than the session total, so a connection that struggled
|
|
75
|
+
* early is not held down by history it has recovered from.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readPeerSample(report: Map<string, Record<string, unknown>> | Iterable<[string, Record<string, unknown>]>, previous: PeerCounters | null): {
|
|
78
|
+
sample: PeerQualitySample;
|
|
79
|
+
counters: PeerCounters | null;
|
|
80
|
+
};
|