@voltkit-rtc/client 0.6.1 → 0.7.0
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/CHANGELOG.md +68 -2
- package/README.md +222 -11
- package/dist/cjs/chat.d.ts +0 -9
- package/dist/cjs/chat.d.ts.map +1 -1
- package/dist/cjs/chat.js +16 -14
- package/dist/cjs/chat.js.map +1 -1
- package/dist/cjs/errors.d.ts +571 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +155 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.ts +298 -16
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +654 -118
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/media-permissions.d.ts +101 -0
- package/dist/cjs/media-permissions.d.ts.map +1 -0
- package/dist/cjs/media-permissions.js +166 -0
- package/dist/cjs/media-permissions.js.map +1 -0
- package/dist/cjs/network.d.ts +80 -0
- package/dist/cjs/network.d.ts.map +1 -0
- package/dist/cjs/network.js +190 -0
- package/dist/cjs/network.js.map +1 -0
- package/dist/cjs/token.d.ts +17 -0
- package/dist/cjs/token.d.ts.map +1 -0
- package/dist/cjs/token.js +36 -0
- package/dist/cjs/token.js.map +1 -0
- package/dist/esm/chat.d.ts +0 -9
- package/dist/esm/chat.d.ts.map +1 -1
- package/dist/esm/chat.js +16 -14
- package/dist/esm/chat.js.map +1 -1
- package/dist/esm/errors.d.ts +571 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +149 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +298 -16
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +633 -109
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/media-permissions.d.ts +101 -0
- package/dist/esm/media-permissions.d.ts.map +1 -0
- package/dist/esm/media-permissions.js +160 -0
- package/dist/esm/media-permissions.js.map +1 -0
- package/dist/esm/network.d.ts +80 -0
- package/dist/esm/network.d.ts.map +1 -0
- package/dist/esm/network.js +183 -0
- package/dist/esm/network.js.map +1 -0
- package/dist/esm/token.d.ts +17 -0
- package/dist/esm/token.d.ts.map +1 -0
- package/dist/esm/token.js +31 -0
- package/dist/esm/token.js.map +1 -0
- package/package.json +1 -1
- package/src/chat.ts +7 -14
- package/src/errors.ts +162 -0
- package/src/index.ts +775 -109
- package/src/media-permissions.ts +220 -0
- package/src/network.ts +212 -0
- package/src/token.ts +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
Additive: every 0.6 call works as before. Pairs with the controller's wallet
|
|
6
|
+
billing (join result `media` and `billing`, `walletLow`, `refreshToken`) and
|
|
7
|
+
works with older controllers too.
|
|
8
|
+
|
|
9
|
+
- **Audio quality.** `PublishOptions.audioQuality: 'std' | 'hd'`, `setAudioQuality()`
|
|
10
|
+
live and the `audioQuality` getter. Standard (the default) is mono Opus with
|
|
11
|
+
`maxaveragebitrate` 64000 and the sender held at 60 kbps (a little under the
|
|
12
|
+
declared rate: Opus VBR measured 60-65 kbps per 5 s window with a 64 kbps
|
|
13
|
+
cap, and standard must never measure above 64), so it stays standard on a
|
|
14
|
+
server that bills what it measures; HD is stereo Opus at up to 128 kbps.
|
|
15
|
+
`AUDIO_SETTINGS` carries both numbers (`maxBitrate`, `senderMaxBitrate`). The microphone's producer declares its `maxaveragebitrate` and
|
|
16
|
+
`appData.audioQuality` to the server. Switching re-creates the microphone's
|
|
17
|
+
producer and keeps it muted if it was. Music is unchanged (128 kbps stereo).
|
|
18
|
+
The microphone was stereo-flagged with no cap in 0.6.
|
|
19
|
+
- **Music is HD and free.** `publishMusic` stays a 128 kbps stereo track in
|
|
20
|
+
every project, also one limited to standard audio (the server allows it there
|
|
21
|
+
and never bills music: the microphone and video alone decide the tier).
|
|
22
|
+
- **Project media settings.** `mediaPermissions` (from the join result's
|
|
23
|
+
`media`, else the `video` policy) and `billingMode`. A kind the project
|
|
24
|
+
switched off is refused before anything is produced (`media-disabled`);
|
|
25
|
+
`setAudioQuality('hd')` without HD is refused (`audio-quality-not-allowed`);
|
|
26
|
+
HD in `publish()`, 60 fps without 60 fps modes and a 60 fps size that is not
|
|
27
|
+
enabled are capped with a warning carrying the server's code. Media profiles
|
|
28
|
+
narrow wallet sessions only. A wallet session waits for a video slot at
|
|
29
|
+
`media.fanout.maxVideoSubscriptions` instead of asking for a refusal.
|
|
30
|
+
- **Changed:** 60 fps refused by the project now warns `frame-rate-not-allowed`
|
|
31
|
+
(the server's code); `frame-rate-fallback` now means only that the camera,
|
|
32
|
+
device or upload cannot provide 60. `setVideoFrameRate(60)` in such a project
|
|
33
|
+
no longer re-creates the camera's producer.
|
|
34
|
+
- **Network quality.** `networkQuality { level, rttMs, lossPct, availableKbps }`
|
|
35
|
+
about every 3 s while listened to, from the transports' statistics, with the
|
|
36
|
+
thresholds in the README.
|
|
37
|
+
- **Tokens.** `updateToken(token)` sends a newer token on the live connection
|
|
38
|
+
(`refreshToken`; older servers keep it for the next reconnect), and
|
|
39
|
+
`tokenExpiring { expiresAt, secondsLeft }` fires a minute before the held
|
|
40
|
+
token expires when nobody refreshed it. Server-pushed tokens carry `expiresAt`.
|
|
41
|
+
- **Wallet.** Typed `walletLow { remainingSeconds, cleared? }`.
|
|
42
|
+
- **Connection state.** `connectionState` and `connectionStateChanged`
|
|
43
|
+
(`idle`, `connecting`, `connected`, `reconnecting`, `closed`).
|
|
44
|
+
- **Parity with the wire:** `claim`, `release`, `blockSlot`, `unblockSlot`,
|
|
45
|
+
`setMetadata`, `setRoomMetadata` and the `claimChanged`, `participantUpdated`
|
|
46
|
+
and `roomMetadataChanged` events, as the Flutter, Android and Swift SDKs
|
|
47
|
+
have. `info` keeps the roster, slots and room metadata current;
|
|
48
|
+
`participants` and `remoteTracks` getters.
|
|
49
|
+
- **Errors.** One registry shared by every SDK (`ERROR_CODES`, `errorInfo`,
|
|
50
|
+
`isTerminalError`, `ErrorCode`); every error the SDK raises is an `RpcError`
|
|
51
|
+
with a registry code (`not-connected`, `timeout`, `connect-failed`,
|
|
52
|
+
`replaced`, `reconnect-gave-up`, `devices-gone`, `unpublished`,
|
|
53
|
+
`codec-fallback`, ...); `RpcError.data` carries what the server attached.
|
|
54
|
+
`kicked` and `billing-unavailable` now end a reconnect at once, like the other
|
|
55
|
+
terminal codes. `CLIENT_EVENTS` and `CONNECTION_STATES` list the names.
|
|
56
|
+
- **Constructor options** (added 2026-10-14, before 0.7.0's first publish):
|
|
57
|
+
`new StreamClient(wsUrl, token, options?)` with `ClientOptions.handlerName`
|
|
58
|
+
(`DeviceHandlerName`), the mediasoup-client handler to build the Device with
|
|
59
|
+
instead of detecting the browser. Reserved for future native hosts; unset,
|
|
60
|
+
nothing changes.
|
|
61
|
+
- **Socket URL** (same date): the `token` query parameter is appended by
|
|
62
|
+
string (`?token=` or `&token=` after an existing query, URL-encoded) instead
|
|
63
|
+
of through `URL.searchParams`, which some non-browser `URL` polyfills do not
|
|
64
|
+
implement. Identical URLs in browsers.
|
|
65
|
+
- No package is published by this change: the owner publishes.
|
|
66
|
+
|
|
67
|
+
## 0.6.1
|
|
68
|
+
|
|
69
|
+
(Published as RTC-REF-001.)
|
|
4
70
|
|
|
5
71
|
- Ignore hidden-page and in-flight pre-switch samples in `videoLimited` and the optional 60→30 fps fallback. Resume with warm-up and fresh counters; sustained foreground limitation still triggers fallback.
|
|
6
72
|
- FPS changes/fallback preserve existing capture constraints, including dimensions, facing mode and native-resize preference.
|
|
@@ -8,7 +74,7 @@
|
|
|
8
74
|
- Optional `VideoSendStats.source` (sender input dimensions/FPS), `encoderImplementation` and `powerEfficientEncoder` diagnostics. Unsupported browser fields remain absent; H264 alone does not establish hardware encoding.
|
|
9
75
|
- Codec selection, quality/bitrate ladders and publishing APIs are unchanged. Requires a new browser SDK publication and website dependency update to reach the deployed demo. No package published by this change.
|
|
10
76
|
|
|
11
|
-
## 0.6.0
|
|
77
|
+
## 0.6.0
|
|
12
78
|
|
|
13
79
|
- Paid camera recording: `StreamClient.startRecording` / `stopRecording`, `CameraRecording` and `RecordingUploader`. Admin-controlled capture resolution/bitrate/FPS, default 360p; independent live-call quality.
|
|
14
80
|
- Direct device-to-R2 multipart PUTs, 5/10 MiB chunks, bounded buffering, limited parallel uploads, retry URL renewal and recording-only backpressure. Short independently playable files; validation overlaps the next capture after upload buffers drain.
|
package/README.md
CHANGED
|
@@ -9,6 +9,9 @@ rooms, chat, and live broadcast publishing.
|
|
|
9
9
|
| **Chat** | on the room's own connection, or `ChatClient` for chat with no media |
|
|
10
10
|
| **Broadcast** | `BroadcastPublisher` — go live to an audience of any size over WHIP |
|
|
11
11
|
|
|
12
|
+
Using React or Next.js? [`@voltkit-rtc/react`](https://voltkit.in/docs/react-sdk)
|
|
13
|
+
wraps this client in a provider, hooks and track components.
|
|
14
|
+
|
|
12
15
|
Your backend mints a short-lived token with
|
|
13
16
|
[`@voltkit-rtc/server`](https://voltkit.in/docs/server-sdk) and
|
|
14
17
|
hands the page `{ token, wsUrl }`. This package turns that into a live session.
|
|
@@ -55,6 +58,13 @@ if (joined.role === 'host' || joined.role === 'cohost') {
|
|
|
55
58
|
Always call `client.close()` when leaving. A client that is dropped without
|
|
56
59
|
closing keeps its reconnect loop running.
|
|
57
60
|
|
|
61
|
+
`new StreamClient(wsUrl, token, options?)` takes `ClientOptions`, all optional
|
|
62
|
+
and none needed in a browser: `handlerName` names the mediasoup-client handler
|
|
63
|
+
to build the WebRTC side with (`'Chrome111'`, `'Chrome74'`, `'Firefox120'`,
|
|
64
|
+
`'Safari12'` or `'ReactNative106'`) instead of detecting the browser. It is
|
|
65
|
+
reserved for future native hosts, which have no browser to detect; leave it
|
|
66
|
+
unset.
|
|
67
|
+
|
|
58
68
|
### Publishing
|
|
59
69
|
|
|
60
70
|
```ts
|
|
@@ -74,6 +84,7 @@ phones and computers alike; they are kept for a reconnect's republish.
|
|
|
74
84
|
|
|
75
85
|
| Option | Default | What it does |
|
|
76
86
|
| --- | --- | --- |
|
|
87
|
+
| `audioQuality` | `'std'` | the microphone: `'std'` (mono, up to 64 kbps) or `'hd'` (stereo, up to 128 kbps); see [Audio quality](#audio-quality) |
|
|
77
88
|
| `quality` | the highest the project allows | `'1080p'`, `'720p'` or `'480p'`; see [Video quality](#video-quality) |
|
|
78
89
|
| `frameRate` | `30` | `30` or `60`; only the top layer runs at 60 fps; capture at 60 too |
|
|
79
90
|
| `layers` | `2` on phones and tablets, `3` elsewhere | the most simulcast layers to send. When a phone is too busy, the browser shrinks the picture and may then send fewer layers, dropping the top one: asked for two, it keeps the full picture; asked for three, viewers were left with a quarter of it |
|
|
@@ -112,8 +123,9 @@ const media = await navigator.mediaDevices.getUserMedia({ audio: true, video: {
|
|
|
112
123
|
await client.publish(media, undefined, { quality: '1080p' });
|
|
113
124
|
await client.setVideoQuality('480p'); // resolves to the quality actually used
|
|
114
125
|
|
|
115
|
-
client.videoPolicy;
|
|
116
|
-
client.videoQuality;
|
|
126
|
+
client.videoPolicy; // { maxQuality: '480p' | '720p' | '1080p', maxFrameRate, maxBitrate } for this project
|
|
127
|
+
client.videoQuality; // what is being sent now
|
|
128
|
+
client.videoFrameRate; // 30 or 60, what the camera is being sent at
|
|
117
129
|
```
|
|
118
130
|
|
|
119
131
|
| Quality | Layers from a computer at 30 fps | From a phone or tablet | Video upload |
|
|
@@ -140,14 +152,87 @@ const effectiveFps = await client.setVideoFrameRate(30); // camera only, other t
|
|
|
140
152
|
Only the top layer uses 60 fps: 1080p is **9 Mbps**, 720p **4.5 Mbps**, and
|
|
141
153
|
480p **1.8 Mbps**; smaller layers stay at 30. This means roughly 1.5× the top
|
|
142
154
|
layer's data and twice the encoding work. Phones can heat up and use more
|
|
143
|
-
battery, and many front cameras capture only 30.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`
|
|
155
|
+
battery, and many front cameras capture only 30. When 60 cannot be used the SDK
|
|
156
|
+
sends 30 and says why on `error`, as a warning that stops nothing:
|
|
157
|
+
|
|
158
|
+
| `code` | Why |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| `frame-rate-not-allowed` | the project does not allow 60 fps (its settings, or no 60 fps mode is enabled) |
|
|
161
|
+
| `video-quality-not-allowed` | 60 fps is allowed, but not at the size you asked for: the largest size that is goes out at 60 |
|
|
162
|
+
| `frame-rate-fallback` | the camera captures below 50 fps, or the device or upload cannot keep 60 up |
|
|
163
|
+
|
|
164
|
+
`setVideoFrameRate(60)` retries the preference while preserving a paused
|
|
165
|
+
camera; in a project without 60 fps it changes nothing and warns again.
|
|
166
|
+
(Before 0.7.0 the project case was also reported as `frame-rate-fallback`.)
|
|
147
167
|
|
|
148
168
|
The full guide, with data use:
|
|
149
169
|
[voltkit.in/docs/video-quality](https://voltkit.in/docs/video-quality).
|
|
150
170
|
|
|
171
|
+
### Audio quality
|
|
172
|
+
|
|
173
|
+
The microphone goes out as **standard** audio unless you ask for HD:
|
|
174
|
+
|
|
175
|
+
| `audioQuality` | Sent as | Billed as (wallet billing) |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `'std'` (default) | mono Opus, `maxaveragebitrate` 64000, the sender held at 60 kbps, DTX and FEC | standard audio |
|
|
178
|
+
| `'hd'` | stereo Opus, `maxaveragebitrate` 128000, DTX and FEC | HD audio |
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
await client.publish(media, undefined, { audioQuality: 'hd' });
|
|
182
|
+
await client.setAudioQuality('std'); // while live; resolves to the quality in use
|
|
183
|
+
client.audioQuality; // 'std' | 'hd'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The server bills what it can verify, never what a client claims: HD only when
|
|
187
|
+
the microphone declares more than 64 kbps or is measured above 72 kbps, so a
|
|
188
|
+
standard microphone really stays at or under 64 kbps: its sender is held at
|
|
189
|
+
60 kbps, a little under the declared rate, because Opus VBR around a 64 kbps
|
|
190
|
+
target measured up to 65 kbps in a 5 s window. Switching re-creates the
|
|
191
|
+
microphone's producer (its codec settings are fixed per producer): listeners
|
|
192
|
+
hear a gap of a fraction of a second, and a muted microphone stays muted.
|
|
193
|
+
|
|
194
|
+
For HD, capture with `{ audio: { channelCount: { ideal: 2 }, echoCancellation:
|
|
195
|
+
true, noiseSuppression: true, autoGainControl: true } }`; turn the processing
|
|
196
|
+
off only for headphones or line-in music, or listeners hear their own echo.
|
|
197
|
+
Music (`publishMusic`) is a separate 128 kbps stereo track and is not affected:
|
|
198
|
+
it is HD and free whatever the microphone's quality (see [Music](#music)).
|
|
199
|
+
|
|
200
|
+
### Project media settings
|
|
201
|
+
|
|
202
|
+
A project's owner chooses what it may publish: audio and video on or off, the
|
|
203
|
+
largest video quality, 30 or 60 fps, standard or HD audio. Allowing a mode
|
|
204
|
+
allows every mode below it (1080p at 60 fps opens 720p and 480p at 60 and every
|
|
205
|
+
30 fps mode). The join result carries them, and the client follows them:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
client.mediaPermissions;
|
|
209
|
+
// { audio: true, video: true, maxVideoQuality: '1080p', maxFrameRate: 60,
|
|
210
|
+
// maxVideoQuality60: '720p', audioQualityMax: 'hd', maxVideoSubscriptions: 25, source: 'media' }
|
|
211
|
+
client.billingMode; // 'wallet' | 'legacy' | null (older servers)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Offer only these in your UI. The server enforces them whatever a client sends;
|
|
215
|
+
the SDK never sends what it would refuse, and tells you with the server's own
|
|
216
|
+
codes:
|
|
217
|
+
|
|
218
|
+
- **Refused** (the call throws, nothing is produced): a kind the project
|
|
219
|
+
switched off -- `publish()` with such a track, `publishScreen()` without
|
|
220
|
+
video, `publishMusic()` without audio -- with `media-disabled`; and
|
|
221
|
+
`setAudioQuality('hd')` without HD audio, with `audio-quality-not-allowed`.
|
|
222
|
+
- **Capped** (the call goes on with the most the project allows, and `error`
|
|
223
|
+
carries a warning): HD audio asked for in `publish()` goes out standard
|
|
224
|
+
(`audio-quality-not-allowed`); 60 fps without 60 fps modes goes out at 30
|
|
225
|
+
(`frame-rate-not-allowed`); a 60 fps size that is not enabled goes out at the
|
|
226
|
+
largest that is (`video-quality-not-allowed`). A 30 fps quality above the
|
|
227
|
+
maximum is capped silently, as the server does.
|
|
228
|
+
- **Fan-out:** a session billed from the wallet receives at most
|
|
229
|
+
`maxVideoSubscriptions` videos at once (25 by default). Further videos wait,
|
|
230
|
+
with one `video-subscription-limit` warning each, and are received as others
|
|
231
|
+
end.
|
|
232
|
+
|
|
233
|
+
With an older server (no `media` in the join result) the `videoPolicy` alone
|
|
234
|
+
applies, as in 0.6.
|
|
235
|
+
|
|
151
236
|
### When the picture is soft
|
|
152
237
|
|
|
153
238
|
A soft or stuttering picture usually comes from the publisher's device (too
|
|
@@ -289,6 +374,12 @@ await client.stopMusic();
|
|
|
289
374
|
|
|
290
375
|
`setMicEnabled` and `setMusicEnabled` are independent, in both directions.
|
|
291
376
|
`publishMusic` also takes a `MediaStream` or a `MediaStreamTrack`.
|
|
377
|
+
`client.musicPublishing` is whether music is published at all, and
|
|
378
|
+
`client.musicEnabled` whether the audience hears it now.
|
|
379
|
+
|
|
380
|
+
Music is HD and free: it always goes out at 128 kbps stereo, also in a project
|
|
381
|
+
limited to standard audio, and the service never bills it. Sending or hearing
|
|
382
|
+
music never changes anyone's rate; the microphone and video alone decide it.
|
|
292
383
|
|
|
293
384
|
The host keeps hearing the music (`monitor`, on by default for an element). A
|
|
294
385
|
file from another origin must be served with CORS headers and loaded with
|
|
@@ -340,18 +431,123 @@ await client.getRoomState();
|
|
|
340
431
|
| `localMediaChanged` / `localStreamChanged` | your own mic, camera or stream changed |
|
|
341
432
|
| `connected` / `disconnected` | the socket came up or dropped; reconnection is automatic |
|
|
342
433
|
| `kicked` / `roomClosed` | you were removed, or the host ended the room |
|
|
434
|
+
| `mustReconnect` | the server moved this session (a promotion, a node lost); the client reconnects by itself, with the new token when one is given |
|
|
343
435
|
| `error` | a failure or recoverable warning such as `frame-rate-fallback`; inspect its code |
|
|
344
436
|
| `usageLimit` | your project ran out of minutes or hit a usage limit; the session ends at `endsAt` unless it is topped up |
|
|
345
437
|
| `videoLimited` | your camera video is held back by this device (`cpu`) or the upload (`bandwidth`), or that cleared (`none`) |
|
|
346
438
|
| `closed` | terminal — `replaced`, `kicked`, `room-closed` or `gave-up`, or `left` when your own `close()` ends the session |
|
|
439
|
+
| `connectionStateChanged` | `{ state, previous }`: `idle` → `connecting` → `connected` ⇄ `reconnecting` → `closed` |
|
|
440
|
+
| `participantUpdated` | someone changed their display metadata (`setMetadata`) |
|
|
441
|
+
| `claimChanged` | a slot was taken, went away, came back, was locked or freed: `{ key, identity, state, reason }` |
|
|
442
|
+
| `roomMetadataChanged` | a moderator changed the room metadata: `{ metadata, by }` |
|
|
443
|
+
| `walletLow` | wallet billing: what the project may spend covers less than the grace period (`{ remainingSeconds }`), or that is over (`{ remainingSeconds, cleared: true }`) |
|
|
444
|
+
| `tokenExpiring` | the token this client would reconnect with expires in about a minute and nobody refreshed it: `{ expiresAt, secondsLeft }` |
|
|
445
|
+
| `networkQuality` | about every 3 s while listened to: `{ level, rttMs, lossPct, availableKbps }` |
|
|
446
|
+
|
|
447
|
+
`on()` returns a function that removes the listener. `CLIENT_EVENTS` lists
|
|
448
|
+
every event name.
|
|
449
|
+
|
|
450
|
+
### Connection state
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
client.connectionState; // 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closed' (CONNECTION_STATES)
|
|
454
|
+
client.on('connectionStateChanged', ({ state, previous }) => render(state));
|
|
455
|
+
client.participants; // who is in the room, kept current from the roster events
|
|
456
|
+
client.remoteTracks; // [{ producerId, identity, kind, source, track, paused }] being received
|
|
457
|
+
client.info; // the join result, kept current: roster, slots, room metadata
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Tokens
|
|
461
|
+
|
|
462
|
+
A room token authorises the connection, not the session: a live session stays
|
|
463
|
+
up past its expiry, and the server pushes a fresh token before the one the
|
|
464
|
+
client holds lapses. Your backend can refresh it as well:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
client.on('tokenExpiring', async ({ secondsLeft }) => {
|
|
468
|
+
const { token } = await fetch('/api/rtc/token', { method: 'POST' }).then((r) => r.json());
|
|
469
|
+
await client.updateToken(token); // { expiresAt } (ms)
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
`updateToken` sends the token on the live connection (`refreshToken`), where the
|
|
474
|
+
server checks it like a join token: `bad-token` for one it would refuse,
|
|
475
|
+
`token-mismatch` for another room or identity; the session then keeps its
|
|
476
|
+
token. While reconnecting, or before `connect()`, the token is kept for the
|
|
477
|
+
next join. An older server without `refreshToken` keeps it for the next
|
|
478
|
+
reconnect. `tokenExpiring` fires only when nobody refreshed the token: the
|
|
479
|
+
server stops when the account may no longer be used, and a dropped socket
|
|
480
|
+
cannot receive one.
|
|
347
481
|
|
|
348
|
-
|
|
482
|
+
### Wallet and usage notices
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
client.on('walletLow', ({ remainingSeconds, cleared }) => {
|
|
486
|
+
if (cleared) hideBanner();
|
|
487
|
+
else showBanner(`Credits run out in about ${remainingSeconds} s. Recharge to keep the call going.`);
|
|
488
|
+
});
|
|
489
|
+
client.on('usageLimit', ({ reason, endsAt }) => showCountdown(endsAt));
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
With wallet billing, `walletLow` warns while the funds the project may spend
|
|
493
|
+
(its own allocation, then the RTC allocation, then unallocated) cover less than
|
|
494
|
+
the grace period at the current burn; `usageLimit` is sent beside it for older
|
|
495
|
+
apps. The room closes with `roomClosed { reason: 'quota-exhausted' }` when the
|
|
496
|
+
funds reach zero unless the wallet is recharged first; a recharge sends
|
|
497
|
+
`walletLow { cleared: true }`, which also cancels the `usageLimit` countdown.
|
|
498
|
+
|
|
499
|
+
### Network quality
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
client.on('networkQuality', ({ level, rttMs, lossPct, availableKbps }) => showBars(level));
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Sampled from the media transports' statistics about every 3 s, only while
|
|
506
|
+
something listens and the session is connected. `rttMs` is the round trip to
|
|
507
|
+
the media server, `lossPct` the packets lost over the last interval in the
|
|
508
|
+
worse direction (sending or receiving), `availableKbps` the browser's bandwidth
|
|
509
|
+
estimate (upload while publishing). Each is `null` until known. The level:
|
|
510
|
+
|
|
511
|
+
| `level` | Round trip | and loss |
|
|
512
|
+
| --- | --- | --- |
|
|
513
|
+
| `excellent` | under 150 ms | under 1 % |
|
|
514
|
+
| `good` | under 300 ms | under 3 % |
|
|
515
|
+
| `poor` | under 500 ms | under 8 % |
|
|
516
|
+
| `bad` | anything worse | |
|
|
517
|
+
|
|
518
|
+
An unknown value does not lower the level. Bandwidth is reported, not graded:
|
|
519
|
+
`videoLimited` says when the camera is held back by the device or the upload.
|
|
520
|
+
|
|
521
|
+
### Slots and room metadata
|
|
522
|
+
|
|
523
|
+
A slot is a named thing in the room that one participant holds at a time: a
|
|
524
|
+
seat on a board, a guest spot. Keys are yours.
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
await client.claim('seat-3'); // take it; claim('seat-3', identity) grants it (moderators)
|
|
528
|
+
await client.release('seat-3');
|
|
529
|
+
await client.blockSlot('seat-9'); // moderators: lock it; refused with slot-occupied while held
|
|
530
|
+
await client.unblockSlot('seat-9');
|
|
531
|
+
await client.setMetadata({ name: 'Ana' }); // your display metadata; the room hears participantUpdated
|
|
532
|
+
await client.setRoomMetadata({ seats: 10 }, { requireFree: ['seat-11', 'seat-12'] }); // moderators; persisted
|
|
533
|
+
|
|
534
|
+
client.info.claims; // [{ key, identity, state: 'held' | 'away' }]
|
|
535
|
+
client.info.blockedSlots; // ['seat-9']
|
|
536
|
+
client.info.metadata; // the room metadata
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Draw `away` as taken (greyed), never as free:** the holder's connection
|
|
540
|
+
dropped and the slot is kept for them for about a minute. A refused
|
|
541
|
+
`setRoomMetadata` names the slots in the way in `error.data.slots`.
|
|
349
542
|
|
|
350
543
|
### Reconnection
|
|
351
544
|
|
|
352
545
|
Handled for you. A dropped socket is retried with backoff, the session is
|
|
353
|
-
rejoined, and if you were publishing, your media is published again
|
|
354
|
-
with `gave-up` means it
|
|
546
|
+
rejoined, and if you were publishing, your media is published again (with the
|
|
547
|
+
same quality, frame rate and audio quality). `closed` with `gave-up` means it
|
|
548
|
+
stopped trying: after eight attempts, or at once for a join refused with a code
|
|
549
|
+
retrying cannot fix (`isTerminalError`: the room ended, the token is wrong, you
|
|
550
|
+
were removed, the account ran out).
|
|
355
551
|
|
|
356
552
|
`replaced` means the same identity joined the same room somewhere else, and
|
|
357
553
|
this connection lost. Do not reconnect on it: the two would take turns evicting
|
|
@@ -487,8 +683,23 @@ if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
|
487
683
|
## Errors
|
|
488
684
|
|
|
489
685
|
Server refusals reject with `RpcError` (rooms) or `ChatRpcError` (`ChatClient`),
|
|
490
|
-
both carrying a machine-readable `code
|
|
491
|
-
|
|
686
|
+
both carrying a machine-readable `code`; the SDK's own failures and warnings
|
|
687
|
+
are `RpcError`s too. Switch on `code`, never on the message. `RpcError.data`
|
|
688
|
+
is what the server attached to act on a refusal (`{ slots }` for
|
|
689
|
+
`slot-occupied`).
|
|
690
|
+
|
|
691
|
+
Every code is in one registry shared by all VoltKit SDKs:
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
import { ERROR_CODES, errorInfo, isTerminalError } from '@voltkit-rtc/client';
|
|
695
|
+
|
|
696
|
+
errorInfo('quota-exhausted'); // { origin: 'server', terminal: true, retryable: false }
|
|
697
|
+
isTerminalError(code); // a join refused with it is not retried
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
A newer server may send a code this release does not know: it is passed through
|
|
701
|
+
unchanged, and treated as not terminal. The registry with every meaning is in
|
|
702
|
+
the repository's `docs/SDK-CONTRACT.md`, the wire codes in the
|
|
492
703
|
[API reference](https://voltkit.in/docs/api-reference).
|
|
493
704
|
|
|
494
705
|
## Help
|
package/dist/cjs/chat.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat for the orchestrator's signaling protocol.
|
|
3
|
-
*
|
|
4
|
-
* A channel shares its name with the room of the same name, so there are two
|
|
5
|
-
* ways into one conversation: `StreamClient` carries chat over the socket it
|
|
6
|
-
* already holds for media, and `ChatClient` here opens a socket that carries
|
|
7
|
-
* nothing else -- for an audience that is watching a broadcast over a CDN and
|
|
8
|
-
* has no reason to be on an SFU.
|
|
9
|
-
*/
|
|
10
1
|
import type { Role } from './index.js';
|
|
11
2
|
export interface ChatMessage {
|
|
12
3
|
/** Monotonic within a channel, so the oldest id held is a paging cursor. */
|
package/dist/cjs/chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/chat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/chat.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ChatClient = exports.ChatRpcError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Chat for the orchestrator's signaling protocol.
|
|
6
|
+
*
|
|
7
|
+
* A channel shares its name with the room of the same name, so there are two
|
|
8
|
+
* ways into one conversation: `StreamClient` carries chat over the socket it
|
|
9
|
+
* already holds for media, and `ChatClient` here opens a socket that carries
|
|
10
|
+
* nothing else -- for an audience that is watching a broadcast over a CDN and
|
|
11
|
+
* has no reason to be on an SFU.
|
|
12
|
+
*/
|
|
13
|
+
const errors_js_1 = require("./errors.js");
|
|
14
|
+
const token_js_1 = require("./token.js");
|
|
4
15
|
/** An error returned by the server, carrying its machine-readable code. */
|
|
5
16
|
class ChatRpcError extends Error {
|
|
6
17
|
code;
|
|
@@ -13,16 +24,6 @@ class ChatRpcError extends Error {
|
|
|
13
24
|
exports.ChatRpcError = ChatRpcError;
|
|
14
25
|
const RPC_TIMEOUT_MS = 15_000;
|
|
15
26
|
const MAX_RECONNECT_ATTEMPTS = 8;
|
|
16
|
-
/** A token that is wrong will be wrong next time too; retrying only adds noise. */
|
|
17
|
-
const TERMINAL_JOIN_ERRORS = new Set([
|
|
18
|
-
'bad-token',
|
|
19
|
-
'no-token',
|
|
20
|
-
// The account cannot use the service until it is topped up or restored.
|
|
21
|
-
'quota-exhausted',
|
|
22
|
-
'daily-limit-reached',
|
|
23
|
-
'monthly-limit-reached',
|
|
24
|
-
'tenant-suspended',
|
|
25
|
-
]);
|
|
26
27
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
27
28
|
/**
|
|
28
29
|
* A chat-only connection. Takes the `wsUrl` and `token` returned by
|
|
@@ -94,9 +95,7 @@ class ChatClient {
|
|
|
94
95
|
openSocket() {
|
|
95
96
|
this.detachSocket();
|
|
96
97
|
return new Promise((resolve, reject) => {
|
|
97
|
-
const
|
|
98
|
-
url.searchParams.set('token', this.token);
|
|
99
|
-
const ws = new WebSocket(url.toString());
|
|
98
|
+
const ws = new WebSocket((0, token_js_1.socketUrl)(this.wsUrl, this.token));
|
|
100
99
|
this.ws = ws;
|
|
101
100
|
const onError = () => reject(new Error('websocket failed to open'));
|
|
102
101
|
ws.addEventListener('open', () => {
|
|
@@ -165,7 +164,10 @@ class ChatClient {
|
|
|
165
164
|
}
|
|
166
165
|
catch (err) {
|
|
167
166
|
this.emit('error', err);
|
|
168
|
-
|
|
167
|
+
// A token that is wrong will be wrong next time too, and an account
|
|
168
|
+
// that ran out stays out until topped up: the registry's terminal
|
|
169
|
+
// codes, shared with StreamClient. Retrying only adds noise.
|
|
170
|
+
if (err instanceof ChatRpcError && (0, errors_js_1.isTerminalError)(err.code)) {
|
|
169
171
|
this.closedByUs = true;
|
|
170
172
|
this.emit('closed', 'gave-up');
|
|
171
173
|
return;
|
package/dist/cjs/chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/chat.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/chat.ts"],"names":[],"mappings":"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"}
|