@voltkit-rtc/client 0.7.3 → 0.9.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 +86 -0
- package/README.md +346 -2
- package/dist/cjs/adaptive.d.ts +104 -0
- package/dist/cjs/adaptive.d.ts.map +1 -0
- package/dist/cjs/adaptive.js +263 -0
- package/dist/cjs/adaptive.js.map +1 -0
- package/dist/cjs/devices.d.ts +42 -0
- package/dist/cjs/devices.d.ts.map +1 -0
- package/dist/cjs/devices.js +56 -0
- package/dist/cjs/devices.js.map +1 -0
- package/dist/cjs/diagnostics.d.ts +65 -0
- package/dist/cjs/diagnostics.d.ts.map +1 -0
- package/dist/cjs/diagnostics.js +69 -0
- package/dist/cjs/diagnostics.js.map +1 -0
- package/dist/cjs/effects-core.d.ts +95 -0
- package/dist/cjs/effects-core.d.ts.map +1 -0
- package/dist/cjs/effects-core.js +154 -0
- package/dist/cjs/effects-core.js.map +1 -0
- package/dist/cjs/effects-gl.d.ts +79 -0
- package/dist/cjs/effects-gl.d.ts.map +1 -0
- package/dist/cjs/effects-gl.js +351 -0
- package/dist/cjs/effects-gl.js.map +1 -0
- package/dist/cjs/fallback.d.ts +81 -0
- package/dist/cjs/fallback.d.ts.map +1 -0
- package/dist/cjs/fallback.js +148 -0
- package/dist/cjs/fallback.js.map +1 -0
- package/dist/cjs/health.d.ts +70 -0
- package/dist/cjs/health.d.ts.map +1 -0
- package/dist/cjs/health.js +116 -0
- package/dist/cjs/health.js.map +1 -0
- package/dist/cjs/index.d.ts +396 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1367 -81
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/link.d.ts +99 -0
- package/dist/cjs/link.d.ts.map +1 -0
- package/dist/cjs/link.js +219 -0
- package/dist/cjs/link.js.map +1 -0
- package/dist/cjs/noise-suppression.d.ts +44 -0
- package/dist/cjs/noise-suppression.d.ts.map +1 -0
- package/dist/cjs/noise-suppression.js +103 -0
- package/dist/cjs/noise-suppression.js.map +1 -0
- package/dist/cjs/preflight.d.ts +79 -0
- package/dist/cjs/preflight.d.ts.map +1 -0
- package/dist/cjs/preflight.js +159 -0
- package/dist/cjs/preflight.js.map +1 -0
- package/dist/cjs/processors.d.ts +34 -0
- package/dist/cjs/processors.d.ts.map +1 -0
- package/dist/cjs/processors.js +10 -0
- package/dist/cjs/processors.js.map +1 -0
- package/dist/cjs/speakers.d.ts +34 -0
- package/dist/cjs/speakers.d.ts.map +1 -0
- package/dist/cjs/speakers.js +71 -0
- package/dist/cjs/speakers.js.map +1 -0
- package/dist/cjs/video-effects.d.ts +93 -0
- package/dist/cjs/video-effects.d.ts.map +1 -0
- package/dist/cjs/video-effects.js +493 -0
- package/dist/cjs/video-effects.js.map +1 -0
- package/dist/esm/adaptive.d.ts +104 -0
- package/dist/esm/adaptive.d.ts.map +1 -0
- package/dist/esm/adaptive.js +256 -0
- package/dist/esm/adaptive.js.map +1 -0
- package/dist/esm/devices.d.ts +42 -0
- package/dist/esm/devices.d.ts.map +1 -0
- package/dist/esm/devices.js +50 -0
- package/dist/esm/devices.js.map +1 -0
- package/dist/esm/diagnostics.d.ts +65 -0
- package/dist/esm/diagnostics.d.ts.map +1 -0
- package/dist/esm/diagnostics.js +65 -0
- package/dist/esm/diagnostics.js.map +1 -0
- package/dist/esm/effects-core.d.ts +95 -0
- package/dist/esm/effects-core.d.ts.map +1 -0
- package/dist/esm/effects-core.js +142 -0
- package/dist/esm/effects-core.js.map +1 -0
- package/dist/esm/effects-gl.d.ts +79 -0
- package/dist/esm/effects-gl.d.ts.map +1 -0
- package/dist/esm/effects-gl.js +347 -0
- package/dist/esm/effects-gl.js.map +1 -0
- package/dist/esm/fallback.d.ts +81 -0
- package/dist/esm/fallback.d.ts.map +1 -0
- package/dist/esm/fallback.js +142 -0
- package/dist/esm/fallback.js.map +1 -0
- package/dist/esm/health.d.ts +70 -0
- package/dist/esm/health.d.ts.map +1 -0
- package/dist/esm/health.js +111 -0
- package/dist/esm/health.js.map +1 -0
- package/dist/esm/index.d.ts +396 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1365 -81
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/link.d.ts +99 -0
- package/dist/esm/link.d.ts.map +1 -0
- package/dist/esm/link.js +214 -0
- package/dist/esm/link.js.map +1 -0
- package/dist/esm/noise-suppression.d.ts +44 -0
- package/dist/esm/noise-suppression.d.ts.map +1 -0
- package/dist/esm/noise-suppression.js +99 -0
- package/dist/esm/noise-suppression.js.map +1 -0
- package/dist/esm/preflight.d.ts +79 -0
- package/dist/esm/preflight.d.ts.map +1 -0
- package/dist/esm/preflight.js +153 -0
- package/dist/esm/preflight.js.map +1 -0
- package/dist/esm/processors.d.ts +34 -0
- package/dist/esm/processors.d.ts.map +1 -0
- package/dist/esm/processors.js +9 -0
- package/dist/esm/processors.js.map +1 -0
- package/dist/esm/speakers.d.ts +34 -0
- package/dist/esm/speakers.d.ts.map +1 -0
- package/dist/esm/speakers.js +67 -0
- package/dist/esm/speakers.js.map +1 -0
- package/dist/esm/video-effects.d.ts +93 -0
- package/dist/esm/video-effects.d.ts.map +1 -0
- package/dist/esm/video-effects.js +488 -0
- package/dist/esm/video-effects.js.map +1 -0
- package/package.json +31 -1
- package/src/adaptive.ts +295 -0
- package/src/devices.ts +70 -0
- package/src/diagnostics.ts +94 -0
- package/src/effects-core.ts +177 -0
- package/src/effects-gl.ts +384 -0
- package/src/fallback.ts +169 -0
- package/src/health.ts +127 -0
- package/src/index.ts +1364 -81
- package/src/link.ts +255 -0
- package/src/noise-suppression.ts +135 -0
- package/src/preflight.ts +191 -0
- package/src/processors.ts +35 -0
- package/src/speakers.ts +66 -0
- package/src/video-effects.ts +552 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
- **AI noise suppression: `@voltkit-rtc/client/noise-suppression`** (Agora AI Denoiser, LiveKit Krisp, ZEGOCLOUD
|
|
6
|
+
AI noise suppression). `createNoiseSuppressor({ workletUrl?, wasmUrl?, simdWasmUrl? })` is RNNoise in an
|
|
7
|
+
AudioWorklet (`@sapphi-red/web-noise-suppressor` 0.4.1, MIT, loaded only when used); files from jsDelivr at the
|
|
8
|
+
pinned version unless self-hosted. In Chromium, rumble-like noise went down by more than 60 dB.
|
|
9
|
+
- **`setAudioProcessor(processor | null)` and `audioProcessorInUse`** (LiveKit `setProcessor`): the microphone goes
|
|
10
|
+
through an `AudioTrackProcessor` (`{ name, process(track), destroy() }`) before it is sent. Swapped in place while
|
|
11
|
+
live; kept across device switches, a recovered microphone, reconnects and audio-quality changes. A failing
|
|
12
|
+
processor sends the microphone unprocessed and emits `error`. `mic-silent` is not checked while one is on.
|
|
13
|
+
- **Virtual background and beauty: `@voltkit-rtc/client/video-effects`** (LiveKit track processors, Agora/ZEGOCLOUD
|
|
14
|
+
virtual background and beauty, TRTC video effects). `createVideoEffects({ background: 'none' | 'blur' | { image },
|
|
15
|
+
blur, beauty, wasmPath?, modelUrl?, maxFrameRate?, onOverload? })`, `update()` live, `isVideoEffectsSupported()`.
|
|
16
|
+
WebGL2: mask-weighted blur (no halo), edges refined against the picture, cover-fitted images, beauty without a
|
|
17
|
+
face model. MediaPipe's selfie segmenter (landscape model for wide pictures, square for tall), from the optional
|
|
18
|
+
peer `@mediapipe/tasks-vision` at exactly 0.10.35 (later versions report usage to Google); nothing behind the
|
|
19
|
+
person shows until it is found. Insertable streams in Chrome/Edge, a hidden video on a worker timer elsewhere;
|
|
20
|
+
Chime-style adaptive segmentation on slow devices.
|
|
21
|
+
- **`setVideoProcessor(processor | null)` and `videoProcessorInUse`**: the camera goes through a `VideoTrackProcessor`
|
|
22
|
+
(`{ name, process(track), destroy() }`, same size out) before it is sent; the processed picture is also what `local`
|
|
23
|
+
shows. Swapped in place while live; kept across `switchDevice`, `replaceVideoTrack`, reconnects and quality changes,
|
|
24
|
+
without restarting the processor. The camera behind it is the one switched, constrained, watched and released.
|
|
25
|
+
A failing processor sends the camera unprocessed and emits `error`. `camera-fps-low` is not checked while one is on.
|
|
26
|
+
- **A faster first picture** (Agora/TRTC fast first frame). The join asks for the media transports and uses the ones
|
|
27
|
+
the server made with it (`join {transports}`, controllers from 2026-10-20; older ones are asked as before). The
|
|
28
|
+
DTLS parameters are sent without waiting for the reply, since mediasoup takes them after its handshake has begun:
|
|
29
|
+
ICE starts a round trip sooner, and a refusal rejoins. A video's first layers no longer wait before its resume.
|
|
30
|
+
Up to three round trips fewer before the first publish or the first video.
|
|
31
|
+
|
|
32
|
+
## 0.8.0
|
|
33
|
+
|
|
34
|
+
- **Media recovery:** send and receive transports restart ICE after failure or
|
|
35
|
+
3 s disconnected, before a full rejoin. The entire recovery is bounded to
|
|
36
|
+
10 s; unsupported servers rejoin. `mediaReconnecting` reports recovery.
|
|
37
|
+
- **Audio-only fallback and `streamFallback` (Agora AUDIO_ONLY, Chime received-audio health, Stream Video's
|
|
38
|
+
paused tracks).** When the received sound breaks up badly (25 % of the audio lost over ~15 s), remote cameras are
|
|
39
|
+
paused at the server so the audio keeps going (screen shares stay), and come back after 20 s of recovery (longer
|
|
40
|
+
after a relapse). The media server also reports videos it cut for this viewer's bandwidth. Both arrive as
|
|
41
|
+
`streamFallback {producerId, identity, source, fallback, reason: 'bandwidth' | 'audio-protection'}`. On by
|
|
42
|
+
default: `ClientOptions.audioOnlyFallback: false` or `setAudioOnlyFallback(false)`; `audioOnlyFallbackActive`.
|
|
43
|
+
- **`getDiagnostics()` and `SDK_VERSION`**: a JSON snapshot for a support ticket (session, transport paths, network,
|
|
44
|
+
published/received, the last 200 notable events); no token, no media.
|
|
45
|
+
- **`play(element)` and `autoplayFailed {element, resume}`** (Agora autoplay-failed, TRTC AUTOPLAY_FAILED): a refused
|
|
46
|
+
play is reported and retried on the viewer's next tap, click or key press.
|
|
47
|
+
- **`setPlayoutDelay(ms | null)`** (TRTC playoutDelay): `RTCRtpReceiver.jitterBufferTarget` on every remote track,
|
|
48
|
+
now and later; 300-500 ms for an audience watching a stream, null (default) for conversation.
|
|
49
|
+
- **Faster joins.** The producers already live are subscribed to together on join (as `newProducer` events and the
|
|
50
|
+
Flutter SDK already were), and `StreamClient.prepareConnection(wsUrl)` warms DNS and TLS before `connect()`.
|
|
51
|
+
- **`StreamClient.preflight(wsUrl, token, {media?, durationMs?})`** (Twilio runPreflight, LiveKit ConnectionCheck):
|
|
52
|
+
joins a test room, publishes a synthetic tone and picture (or the app's media, cloned) for a few seconds, leaves,
|
|
53
|
+
and reports steps, media path (UDP/TCP/relayed), round trip, loss, upload and warnings. Never rejects.
|
|
54
|
+
- **`mediaWarning {code, active}`** (Agora exception/recover pairs, LiveKit silence and system-mute handling):
|
|
55
|
+
`mic-silent`, `camera-fps-low` (under 15 fps when 24+ asked; usually low light), `camera-interrupted`,
|
|
56
|
+
`mic-interrupted`; each once when it starts and once when it clears; checked every 2 s while listened to.
|
|
57
|
+
- **`audioLevels` and `activeSpeakers`** (Agora volume-indicator, TRTC AUDIO_VOLUME, Amazon Chime's active speaker
|
|
58
|
+
ranking): levels every ~250 ms while listened to (remote audio from `getSynchronizationSources`, the own microphone
|
|
59
|
+
from its media-source stats; `speaking` above -35 dBov, `SPEAKING_LEVEL`), and the speaker order when it changes
|
|
60
|
+
(microphones only). Nothing is read while nobody listens.
|
|
61
|
+
- **Cameras and microphones.** `listDevices(kind?)`, `switchDevice('videoinput' | 'audioinput', deviceId)` (same
|
|
62
|
+
constraints, the replaced device released, a reconnect keeps the choice; refuses a canvas/processed video), and
|
|
63
|
+
`devicesChanged {devices, added, removed}` while listened to. A published device that stops from outside raises
|
|
64
|
+
`localTrackEnded {kind, deviceId, recovered}`: a microphone comes back from the system default, a camera is
|
|
65
|
+
switched off and never swapped on its own (Amazon Chime's and LiveKit's behaviour).
|
|
66
|
+
- **Publisher-side audio-only fallback (opt-in)**: `setPublishFallback(true)` pauses the camera while the server reports
|
|
67
|
+
this client's own sound breaking up (Agora's setLocalPublishFallbackOption), and resumes it after recovery;
|
|
68
|
+
`publishFallback {fallback}`. A camera switched off by the user or a moderator meanwhile stays off.
|
|
69
|
+
- **Socket liveness:** ping after 15 s idle, reconnect after 8 s without any
|
|
70
|
+
response (checked every 5 s). Browser `online` wakes a pending backoff.
|
|
71
|
+
Late restart/setup results cannot change a newer session; close cancels watches.
|
|
72
|
+
|
|
73
|
+
Performance on busy devices (owner's demo test, 2026-10-19: three cameras with effects and reactions hung phones
|
|
74
|
+
and made laptops stutter). Ideas from LiveKit (adaptive stream, dynacast, CpuConstrained), Tencent TRTC (small
|
|
75
|
+
stream, receiveWhenViewVisible) and Agora (automatic low stream). Every 0.7 call works as before.
|
|
76
|
+
|
|
77
|
+
- **Adaptive stream: `attachVideo(producerId, element)`.** A remote video is received at the size its element is
|
|
78
|
+
shown (smallest layer that fills it sharply, devicePixelRatio up to 2) and paused at the server while the element
|
|
79
|
+
cannot be seen (off screen, `display: none`, background tab). Bigger at once, smaller after ~1 s, paused after
|
|
80
|
+
1.5 s hidden. `setReceiveQuality`/`setPreferredLayers` stay a ceiling. Returns `detach()`. A server without
|
|
81
|
+
`pauseConsumer` gets the smallest layer instead of a pause.
|
|
82
|
+
- **Dynacast.** The server's new `producerLayers` event says which camera layers any viewer needs; the SDK
|
|
83
|
+
switches the unused encodings off (the smallest stays on). Nothing to call. Older servers never send it.
|
|
84
|
+
- **`cpuConstrained` event and `prioritizePerformance(on = true)`.** `cpuConstrained` reports when the camera is
|
|
85
|
+
CPU-limited (held ~4 s) and when that clears; `prioritizePerformance()` stops encoding the camera's largest
|
|
86
|
+
layer until `prioritizePerformance(false)`, kept across publishes and reconnects. `performancePrioritized`.
|
|
87
|
+
- New wire calls (additive): `pauseConsumer`; new wire event: `producerLayers`.
|
|
88
|
+
|
|
3
89
|
## 0.7.3
|
|
4
90
|
|
|
5
91
|
Bug fixes found by checking this SDK for the bug classes a frontend tester
|
package/README.md
CHANGED
|
@@ -122,6 +122,34 @@ modified: the client keeps one of its own (`client.local`, announced by
|
|
|
122
122
|
track you replaced is yours again: it is not stopped, and if pausing the camera
|
|
123
123
|
had switched it off, it is switched back on.
|
|
124
124
|
|
|
125
|
+
### Cameras and microphones (0.8.0)
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const cameras = await client.listDevices('videoinput'); // also 'audioinput', 'audiooutput'
|
|
129
|
+
await client.switchDevice('videoinput', cameras[1].deviceId);
|
|
130
|
+
await client.switchDevice('audioinput', headsetId);
|
|
131
|
+
|
|
132
|
+
client.on('devicesChanged', ({ added, removed }) => refreshDevicePicker());
|
|
133
|
+
client.on('localTrackEnded', ({ kind, recovered }) => {
|
|
134
|
+
if (kind === 'video') showCameraPicker(); // the camera was switched off: offer another, then setCamEnabled(true)
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`switchDevice` captures the chosen device with the constraints `publish` was
|
|
139
|
+
given (size, frame rate, echo cancellation), swaps it in (viewers see no pause
|
|
140
|
+
for a camera of the same size) and stops the one it replaces. A reconnect
|
|
141
|
+
captures the chosen device again. If you publish a canvas or another processed
|
|
142
|
+
track, capture the new camera yourself and pass your track to
|
|
143
|
+
`replaceVideoTrack`; `switchDevice` refuses rather than replace your picture
|
|
144
|
+
with the raw camera.
|
|
145
|
+
|
|
146
|
+
When a published device stops from outside (unplugged, a Bluetooth headset
|
|
147
|
+
switched off, the permission revoked), as Amazon Chime and LiveKit do: the
|
|
148
|
+
**microphone** is captured again from the system default and keeps publishing
|
|
149
|
+
(`recovered: true`), or is switched off if there is none; the **camera** is
|
|
150
|
+
switched off, and never swapped for another camera on its own. Labels in
|
|
151
|
+
`listDevices` are empty until the page has camera or microphone access.
|
|
152
|
+
|
|
125
153
|
### Video quality
|
|
126
154
|
|
|
127
155
|
Choose 1080p, 720p or 480p when publishing, or change it while live:
|
|
@@ -217,6 +245,102 @@ off only for headphones or line-in music, or listeners hear their own echo.
|
|
|
217
245
|
Music (`publishMusic`) is a separate 128 kbps stereo track and is not affected:
|
|
218
246
|
it is HD and free whatever the microphone's quality (see [Music](#music)).
|
|
219
247
|
|
|
248
|
+
### Noise suppression (0.9.0)
|
|
249
|
+
|
|
250
|
+
AI noise suppression for the microphone, as Agora's AI Denoiser, LiveKit's
|
|
251
|
+
Krisp filter and ZEGOCLOUD's AI noise suppression do: RNNoise, a small neural
|
|
252
|
+
network, runs in the browser's audio thread and takes fans, traffic, air
|
|
253
|
+
conditioning and room rumble out of the voice. It is a separate import, so apps
|
|
254
|
+
that do not use it never download it:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { createNoiseSuppressor } from '@voltkit-rtc/client/noise-suppression';
|
|
258
|
+
|
|
259
|
+
await client.setAudioProcessor(createNoiseSuppressor()); // before or after publish
|
|
260
|
+
client.audioProcessorInUse; // the processor, or null
|
|
261
|
+
await client.setAudioProcessor(null); // send the microphone as it is again
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- It applies to the microphone published now (swapped in place: listeners hear
|
|
265
|
+
no gap) and to every one after it: `switchDevice('audioinput', …)`, a
|
|
266
|
+
microphone recovered after unplugging, a reconnect, `setAudioQuality`.
|
|
267
|
+
Music and screen-share audio are not processed.
|
|
268
|
+
- The worklet (64 kB) and the model (150 kB) load the first time it runs, from
|
|
269
|
+
jsDelivr at the exact version this SDK was tested with. Under a strict
|
|
270
|
+
Content-Security-Policy, copy `rnnoise/workletProcessor.js`, `rnnoise.wasm`
|
|
271
|
+
and `rnnoise_simd.wasm` from `node_modules/@sapphi-red/web-noise-suppressor/dist`
|
|
272
|
+
to your site and pass their URLs:
|
|
273
|
+
`createNoiseSuppressor({ workletUrl, wasmUrl, simdWasmUrl })`.
|
|
274
|
+
- If it cannot start (the files are blocked, an old browser), the microphone
|
|
275
|
+
still goes out, unprocessed, and `error` says why. The next microphone tries
|
|
276
|
+
again.
|
|
277
|
+
- Turn it on from a click (a toggle, the join button): browsers start audio
|
|
278
|
+
processing only after the page has been touched, and the SDK resumes it on
|
|
279
|
+
the first tap or key press.
|
|
280
|
+
- `mic-silent` is not reported while a processor is on: a quiet room is
|
|
281
|
+
near-silence after denoising. Your own level in `audioLevels` is what
|
|
282
|
+
listeners hear.
|
|
283
|
+
- Measured in Chromium: rumble-like noise (fans, traffic, AC) went down by more
|
|
284
|
+
than 60 dB; steady white hiss, which it was not trained on, by 7-12 dB. Keep
|
|
285
|
+
the browser's own `echoCancellation`; its `noiseSuppression` can stay on too.
|
|
286
|
+
|
|
287
|
+
Your own processor works the same way: anything with `name`,
|
|
288
|
+
`process(track) → Promise<MediaStreamTrack>` and `destroy()`
|
|
289
|
+
(`AudioTrackProcessor`), such as a licensed Krisp build.
|
|
290
|
+
|
|
291
|
+
### Virtual background and beauty (0.9.0)
|
|
292
|
+
|
|
293
|
+
A blurred or replaced background and beauty for the camera, as LiveKit's track
|
|
294
|
+
processors, Agora's and ZEGOCLOUD's virtual background and beauty, and TRTC's
|
|
295
|
+
video effects do: in the browser, on the GPU. A separate import, so apps that do
|
|
296
|
+
not use it never download it:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { createVideoEffects, isVideoEffectsSupported } from '@voltkit-rtc/client/video-effects';
|
|
300
|
+
|
|
301
|
+
if (isVideoEffectsSupported()) {
|
|
302
|
+
const effects = createVideoEffects({ background: 'blur', beauty: true });
|
|
303
|
+
await client.setVideoProcessor(effects); // before or after publish
|
|
304
|
+
client.videoProcessorInUse; // the processor, or null
|
|
305
|
+
|
|
306
|
+
await effects.update({ background: { image: '/office.jpg' } }); // live: no pause for viewers
|
|
307
|
+
await effects.update({ blur: 8, beauty: { smooth: 0.7, brighten: 0.4 } });
|
|
308
|
+
await client.setVideoProcessor(null); // the camera as it is again
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
- `background`: `'none'`, `'blur'` (`blur` 1-10, default 5) or `{ image }` (a
|
|
313
|
+
URL, Blob, image, canvas or ImageBitmap), fitted like `object-fit: cover`.
|
|
314
|
+
- `beauty`: `true`, or `smooth`, `brighten`, `redness`, `sharpen` (0-1;
|
|
315
|
+
defaults 0.5, 0.3, 0.1, 0.3). Skin is smoothed with an edge-preserving filter
|
|
316
|
+
(eyes, lips and teeth stay sharp) and lightly brightened.
|
|
317
|
+
- What you see in `local` is what viewers get. Switching cameras
|
|
318
|
+
(`switchDevice`), `replaceVideoTrack`, reconnects and quality changes keep the
|
|
319
|
+
effects without restarting them. A paused camera costs no processing.
|
|
320
|
+
- **Background effects need MediaPipe**: `npm install @mediapipe/tasks-vision@0.10.35`
|
|
321
|
+
(exactly that version: later ones report usage to Google). Its wasm (3 MB
|
|
322
|
+
compressed) and the person model (250 kB) load the first time a background is
|
|
323
|
+
used, from jsDelivr and Google. To serve them yourself (a strict CSP), copy
|
|
324
|
+
`node_modules/@mediapipe/tasks-vision/wasm` to your site and pass `wasmPath`,
|
|
325
|
+
and `modelUrl` for the model. Beauty alone needs none of this.
|
|
326
|
+
- Nothing behind you shows while the model loads: the background is fully
|
|
327
|
+
blurred or replaced until you are found. If the effects cannot start, the
|
|
328
|
+
camera goes out as it is and `error` says why, so watch `error` when privacy
|
|
329
|
+
matters.
|
|
330
|
+
- Webcams (wide pictures) use MediaPipe's landscape model and phones (tall
|
|
331
|
+
pictures) the square one, which is what each traces best.
|
|
332
|
+
- A slow device finds you only every few frames and reuses the outline (the
|
|
333
|
+
picture keeps the camera's frame rate). `onOverload` tells you when even that
|
|
334
|
+
is not enough: turn the background off or lower the camera's size.
|
|
335
|
+
- Chrome and Edge process each frame as it arrives and keep going in a
|
|
336
|
+
background tab. Firefox and Safari draw from a hidden video on a timer, which
|
|
337
|
+
a background tab can slow down.
|
|
338
|
+
|
|
339
|
+
Your own processor works the same way: anything with `name`,
|
|
340
|
+
`process(track) → Promise<MediaStreamTrack>` (same size and frame rate) and
|
|
341
|
+
`destroy()` (`VideoTrackProcessor`). The SDK stops a picture it no longer sends;
|
|
342
|
+
stop drawing it once its `readyState` is `'ended'`.
|
|
343
|
+
|
|
220
344
|
### Project media settings
|
|
221
345
|
|
|
222
346
|
A project's owner chooses what it may publish: audio and video on or off, the
|
|
@@ -297,11 +421,61 @@ for their own connection; if every viewer is, look at the publisher's
|
|
|
297
421
|
The [troubleshooting guide](https://voltkit.in/docs/troubleshooting) lists the
|
|
298
422
|
usual causes and what to do about each.
|
|
299
423
|
|
|
424
|
+
### Smooth on busy devices (0.8.0)
|
|
425
|
+
|
|
426
|
+
A room with several cameras, effects and reactions can overload a phone or a
|
|
427
|
+
modest laptop: every viewer decoding every co-host's full-size picture, every
|
|
428
|
+
publisher encoding layers nobody watches. Three things keep it smooth (the
|
|
429
|
+
ideas behind LiveKit's adaptive stream and dynacast, Tencent TRTC's small
|
|
430
|
+
stream with `receiveWhenViewVisible`, and Agora's automatic low stream):
|
|
431
|
+
|
|
432
|
+
**Adaptive stream: each video at the size it is shown.** Attach the element
|
|
433
|
+
that shows a remote video:
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
client.on('track', ({ kind, producerId, track }) => {
|
|
437
|
+
if (kind !== 'video') return;
|
|
438
|
+
const video = showVideo(producerId, track); // your <video> element
|
|
439
|
+
const detach = client.attachVideo(producerId, video); // call detach() when the element goes away
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The SDK then receives the smallest layer that fills the element sharply (its
|
|
444
|
+
size on screen × devicePixelRatio, at most 2) and pauses the video at the
|
|
445
|
+
server while the element cannot be seen (scrolled away, `display: none`, the tab
|
|
446
|
+
in the background): no bandwidth, no decoding. Bigger is applied at once,
|
|
447
|
+
smaller after about a second, and a hidden video pauses after 1.5 s, so
|
|
448
|
+
scrolling past a tile does not stop it. `setReceiveQuality` and
|
|
449
|
+
`setPreferredLayers` still apply as a ceiling.
|
|
450
|
+
|
|
451
|
+
**Dynacast: publishers stop encoding layers nobody receives.** Nothing to
|
|
452
|
+
call: the server tells the publishing client which of its camera layers any
|
|
453
|
+
viewer needs, and the SDK switches the others off (the smallest always stays
|
|
454
|
+
on, so a new viewer starts at once). With adaptive stream on the viewers, a host
|
|
455
|
+
shown in small tiles stops spending its CPU on its largest layer.
|
|
456
|
+
|
|
457
|
+
**When the publisher's device itself is overloaded:**
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
client.on('cpuConstrained', ({ constrained }) => {
|
|
461
|
+
if (constrained) {
|
|
462
|
+
void client.prioritizePerformance(); // the camera's largest layer stops being encoded
|
|
463
|
+
turnOffEffects(); // and your own savings: effects off, smaller remote videos
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
await client.prioritizePerformance(false); // later, when the user wants full quality back
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
`cpuConstrained` fires when the browser reports the camera limited by the CPU
|
|
470
|
+
for about 4 seconds (`videoLimited` with `cpu`), and again when that clears.
|
|
471
|
+
`performancePrioritized` tells whether the lighter mode is on.
|
|
472
|
+
|
|
300
473
|
### Small tiles
|
|
301
474
|
|
|
302
475
|
Each viewer gets the biggest layer their connection carries, which a thumbnail
|
|
303
|
-
or a row of co-host tiles does not need.
|
|
304
|
-
`producerId` from the `track`
|
|
476
|
+
or a row of co-host tiles does not need. `attachVideo` (above) chooses for you;
|
|
477
|
+
to choose yourself, ask for a smaller one with the `producerId` from the `track`
|
|
478
|
+
event:
|
|
305
479
|
|
|
306
480
|
```ts
|
|
307
481
|
client.on('track', ({ kind, producerId, track }) => {
|
|
@@ -526,6 +700,176 @@ apps. The room closes with `roomClosed { reason: 'quota-exhausted' }` when the
|
|
|
526
700
|
funds reach zero unless the wallet is recharged first; a recharge sends
|
|
527
701
|
`walletLow { cleared: true }`, which also cancels the `usageLimit` countdown.
|
|
528
702
|
|
|
703
|
+
### Media and socket recovery (0.8.0)
|
|
704
|
+
|
|
705
|
+
Recovery runs automatically. A send or receive transport that fails, or stays
|
|
706
|
+
disconnected for 3 seconds, requests new ICE credentials and restarts on the
|
|
707
|
+
same transport. A recovery still unfinished after 10 seconds (including the
|
|
708
|
+
request and browser call), or an older server without `restartIce`, uses the
|
|
709
|
+
normal reconnect and republish path.
|
|
710
|
+
|
|
711
|
+
`mediaReconnecting` carries `{ transportId, reconnecting }`: `true` when ICE
|
|
712
|
+
recovery starts and `false` when that recovery ends or its transport is discarded.
|
|
713
|
+
Use `connectionStateChanged` to distinguish recovery from a full reconnect.
|
|
714
|
+
|
|
715
|
+
After 15 seconds without any socket frame the SDK sends `ping`. If no frame
|
|
716
|
+
arrives for 8 seconds after it, the next 5-second check reconnects. Any reply
|
|
717
|
+
counts, including an older server's `unknown-method`. Background tabs may
|
|
718
|
+
delay timers. A browser `online` event wakes a pending reconnect immediately;
|
|
719
|
+
while connected it probes the socket. Closing releases the timers and listener.
|
|
720
|
+
|
|
721
|
+
### Diagnostics for support (0.8.0)
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
const report = await client.getDiagnostics();
|
|
725
|
+
sendToSupport(JSON.stringify(report));
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
What Agora's log upload and Twilio's Insights are used for, kept on the device:
|
|
729
|
+
the SDK version (`SDK_VERSION`) and browser, the room, identity, role and
|
|
730
|
+
connection state, how each media transport is connected (UDP, TCP or relayed),
|
|
731
|
+
the round trip and upload now, what is published and received, and the last
|
|
732
|
+
200 notable events (state changes, errors, recoveries, fallbacks, warnings) with
|
|
733
|
+
their times. It holds no token and no media.
|
|
734
|
+
|
|
735
|
+
### Sound that browsers block until a tap (0.8.0)
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
client.on('track', ({ kind, track }) => {
|
|
739
|
+
const el = kind === 'audio' ? new Audio() : videoFor(track);
|
|
740
|
+
el.srcObject = new MediaStream([track]);
|
|
741
|
+
void client.play(el); // instead of el.play()
|
|
742
|
+
});
|
|
743
|
+
client.on('autoplayFailed', ({ resume }) => showTapToUnmute(resume)); // optional
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Browsers often refuse sound until the viewer interacts with the page. `play()`
|
|
747
|
+
reports that (`autoplayFailed`, like Agora's and Tencent TRTC's), and plays
|
|
748
|
+
every refused element by itself on the viewer's next tap, click or key press,
|
|
749
|
+
so a viewer who touches anything hears the room.
|
|
750
|
+
|
|
751
|
+
### Smoother watching on shaky networks (0.8.0)
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
client.setPlayoutDelay(400); // an audience watching a stream: a little later, far fewer freezes
|
|
755
|
+
client.setPlayoutDelay(null); // people talking with each other: as little delay as the browser can keep
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
Like Tencent TRTC's `playoutDelay`: every remote track is buffered about that
|
|
759
|
+
long before it plays, which rides out a mobile network's jitter. It applies to
|
|
760
|
+
what is received now and later; 0 to 4000 ms. Browsers before Chrome 114 keep
|
|
761
|
+
their own buffer.
|
|
762
|
+
|
|
763
|
+
### Faster joins (0.8.0)
|
|
764
|
+
|
|
765
|
+
Everything already live in the room is subscribed to at once when the client
|
|
766
|
+
joins (it used to be one after another), so a room with six cameras shows them
|
|
767
|
+
together. Warm the connection while the user is still on the way in, as
|
|
768
|
+
LiveKit's `prepareConnection` does:
|
|
769
|
+
|
|
770
|
+
```ts
|
|
771
|
+
StreamClient.prepareConnection(wsUrl); // on the room screen; never rejects
|
|
772
|
+
// ... later
|
|
773
|
+
await client.connect();
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
From 0.9.0 the first picture comes sooner, with nothing to change in your app:
|
|
777
|
+
the join brings the media transports with it (controllers from 2026-10-20), and
|
|
778
|
+
neither the DTLS handshake nor a video's first layer waits for an
|
|
779
|
+
acknowledgement any more. Each is a round trip saved, 50-150 ms on a mobile
|
|
780
|
+
network, for the first publish and the first video alike.
|
|
781
|
+
|
|
782
|
+
### Check before going live (0.8.0)
|
|
783
|
+
|
|
784
|
+
```ts
|
|
785
|
+
// Your backend mints a host token for a test room nobody else is in.
|
|
786
|
+
const { wsUrl, token } = await fetch('/api/precall-token').then((r) => r.json());
|
|
787
|
+
const report = await StreamClient.preflight(wsUrl, token); // or { media: myStream, durationMs: 5000 }
|
|
788
|
+
if (!report.ok) showProblem(report.steps.find((s) => !s.ok)?.error);
|
|
789
|
+
else if (report.path?.relayed || report.path?.protocol === 'tcp') showHint('A firewall slows your connection');
|
|
790
|
+
else if (report.warnings.includes('mic-silent')) showHint('Your microphone sends no sound');
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
Like Twilio's `runPreflight` and LiveKit's `ConnectionCheck`, it joins the
|
|
794
|
+
room, publishes for a few seconds (a tone and a moving picture it makes, or your
|
|
795
|
+
`media`, cloned so your tracks keep running) and leaves. The report has each
|
|
796
|
+
step (`join`, `media`) with its time or error, how media travelled (`path`: UDP
|
|
797
|
+
is best; TCP or a relay means a firewall in the way), `rttMs`, `lossPct`,
|
|
798
|
+
`uploadKbps`, `level`, and any camera or microphone `warnings`. It never
|
|
799
|
+
rejects. The session is real and billed like any other for its few seconds.
|
|
800
|
+
|
|
801
|
+
### Camera and microphone warnings (0.8.0)
|
|
802
|
+
|
|
803
|
+
```ts
|
|
804
|
+
client.on('mediaWarning', ({ code, active }) => {
|
|
805
|
+
if (code === 'mic-silent') showBanner(active, 'Your microphone sends no sound: check its mute switch');
|
|
806
|
+
if (code === 'camera-fps-low') showBanner(active, 'Your camera is dark: add light in front of you');
|
|
807
|
+
});
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Like Agora's exception events, each warning comes once when it starts
|
|
811
|
+
(`active: true`) and once when it clears:
|
|
812
|
+
|
|
813
|
+
| `code` | When |
|
|
814
|
+
| --- | --- |
|
|
815
|
+
| `mic-silent` | the microphone is on but sends exact silence for about 6 s: a mute switch, a broken or wrong device (a quiet room still has a noise floor) |
|
|
816
|
+
| `camera-fps-low` | the camera delivers under 15 fps for about 6 s when 24 or more were asked: usually a dark room, where cameras expose each frame longer. 20 fps is not a warning |
|
|
817
|
+
| `camera-interrupted`, `mic-interrupted` | the system took the device for more than 2 s: a phone call, another app |
|
|
818
|
+
|
|
819
|
+
Checked every 2 s while listened to.
|
|
820
|
+
|
|
821
|
+
### Who is speaking (0.8.0)
|
|
822
|
+
|
|
823
|
+
```ts
|
|
824
|
+
client.on('audioLevels', (levels) => {
|
|
825
|
+
for (const { identity, speaking } of levels) tileFor(identity).ring(speaking);
|
|
826
|
+
});
|
|
827
|
+
client.on('activeSpeakers', ({ speakers }) => showLarge(speakers[0])); // most active first
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
`audioLevels` comes about four times a second while listened to, for every
|
|
831
|
+
remote microphone, music and shared audio this client receives and its own
|
|
832
|
+
microphone while on: `{ identity, producerId, source, level, speaking, local }`.
|
|
833
|
+
`level` is WebRTC's audio level (0 silence, 1 full scale); `speaking` is above
|
|
834
|
+
-35 dBov (`SPEAKING_LEVEL`). `activeSpeakers` ranks microphones the way Amazon
|
|
835
|
+
Chime does, so a short cough does not steal the large tile and the person who
|
|
836
|
+
keeps talking does; music never counts. Both are read in the browser from what
|
|
837
|
+
it receives, and nothing is read while nobody listens.
|
|
838
|
+
|
|
839
|
+
### Poor connections: audio keeps going (0.8.0)
|
|
840
|
+
|
|
841
|
+
When a viewer's connection cannot carry video, the audio comes first, as in
|
|
842
|
+
Agora's audio-only stream fallback:
|
|
843
|
+
|
|
844
|
+
- **The media server cuts a video** this viewer's bandwidth cannot carry even
|
|
845
|
+
at its smallest size, while the sound keeps flowing, and says so.
|
|
846
|
+
- **The SDK pauses remote cameras** when the received sound itself breaks up
|
|
847
|
+
badly (a quarter of it lost over about 15 seconds), so what bandwidth there is
|
|
848
|
+
goes to the audio. Screen shares stay. The cameras come back once the
|
|
849
|
+
connection has recovered for 20 seconds, longer if it falls back again soon.
|
|
850
|
+
|
|
851
|
+
Either way the app is told, per video, so it can show the person's name instead
|
|
852
|
+
of a frozen picture:
|
|
853
|
+
|
|
854
|
+
```ts
|
|
855
|
+
client.on('streamFallback', ({ producerId, identity, fallback, reason }) => {
|
|
856
|
+
tileFor(producerId).showPoorConnection(fallback); // reason: 'bandwidth' or 'audio-protection'
|
|
857
|
+
});
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
The pause is on by default. Turn it off with `new StreamClient(url, token,
|
|
861
|
+
{ audioOnlyFallback: false })` or `client.setAudioOnlyFallback(false)`, which
|
|
862
|
+
also brings back any camera held at that moment.
|
|
863
|
+
|
|
864
|
+
**The host's side (opt-in).** `client.setPublishFallback(true)` does the same
|
|
865
|
+
for what this client sends (Agora's `setLocalPublishFallbackOption`): when the
|
|
866
|
+
server reports this client's own sound breaking up on the way in, its camera is
|
|
867
|
+
paused (viewers see it paused) so the upload carries the voice, and resumed once
|
|
868
|
+
it recovers. `publishFallback { fallback }` says when. A camera the user or a
|
|
869
|
+
moderator switches off meanwhile stays off. `audioOnlyFallbackActive`
|
|
870
|
+
says whether it is holding cameras now. The server's bandwidth reports need the
|
|
871
|
+
controller and media node from 2026-10-20; older ones never send them.
|
|
872
|
+
|
|
529
873
|
### Network quality
|
|
530
874
|
|
|
531
875
|
```ts
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive stream: receive each remote video at the size it is shown, and not
|
|
3
|
+
* at all while nobody can see it.
|
|
4
|
+
*
|
|
5
|
+
* The app attaches the element that shows a video (`attachVideo`). From that
|
|
6
|
+
* element's size on screen (CSS pixels × devicePixelRatio, at most 2) the
|
|
7
|
+
* smallest simulcast layer that fills it sharply is chosen; while no attached
|
|
8
|
+
* element is visible (scrolled away, `display: none`, the tab in the
|
|
9
|
+
* background) the video is paused at the media server, so it costs no
|
|
10
|
+
* bandwidth and no decoding. This is what LiveKit calls adaptiveStream and
|
|
11
|
+
* Tencent TRTC "small stream + receiveWhenViewVisible": the single biggest
|
|
12
|
+
* saving on a viewer with several tiles, where every tile otherwise decodes
|
|
13
|
+
* the publisher's full-size layer.
|
|
14
|
+
*
|
|
15
|
+
* Pure decisions are exported for tests; the browser parts (observers,
|
|
16
|
+
* timers) are injected.
|
|
17
|
+
*/
|
|
18
|
+
import type { VideoLayer } from './quality.js';
|
|
19
|
+
/** Where a video is drawn, in CSS pixels, and how it is fitted into that box. */
|
|
20
|
+
export interface ViewBox {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
fit: 'cover' | 'contain';
|
|
24
|
+
}
|
|
25
|
+
/** Never ask for more than twice the CSS size: a 3× phone screen gains nothing visible from it. */
|
|
26
|
+
export declare const MAX_PIXEL_RATIO = 2;
|
|
27
|
+
/** A layer shown at most this much bigger than it is still looks sharp: avoids a whole layer for a few pixels. */
|
|
28
|
+
export declare const MAX_UPSCALE = 1.15;
|
|
29
|
+
/**
|
|
30
|
+
* The smallest layer that fills `box` sharply, or null when the layers are
|
|
31
|
+
* not known (an older publisher): then the server chooses, as before.
|
|
32
|
+
*
|
|
33
|
+
* With `cover` the picture is scaled until it fills the box (a part is
|
|
34
|
+
* cropped), with `contain` until it fits. Either way the scale is measured
|
|
35
|
+
* against each layer's own size, so a portrait layer list works too.
|
|
36
|
+
*/
|
|
37
|
+
export declare function layerForBox(layers: readonly VideoLayer[], box: ViewBox, pixelRatio?: number): number | null;
|
|
38
|
+
/** What one video should get now: paused, or a layer (null = let the server choose). */
|
|
39
|
+
export interface AdaptiveWant {
|
|
40
|
+
visible: boolean;
|
|
41
|
+
spatialLayer: number | null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The largest view among the visible ones decides; any visible view keeps the video flowing. A view not measured
|
|
45
|
+
* yet (just attached, before layout) counts as shown at the server's choice, so attaching never pauses a video
|
|
46
|
+
* for the moment before the browser has sized the element.
|
|
47
|
+
*/
|
|
48
|
+
export declare function wantFor(views: readonly {
|
|
49
|
+
box: ViewBox;
|
|
50
|
+
visible: boolean;
|
|
51
|
+
measured?: boolean;
|
|
52
|
+
}[], layers: readonly VideoLayer[], pixelRatio: number, pageVisible: boolean): AdaptiveWant;
|
|
53
|
+
/** Pause a hidden video only after this long, so scrolling past or a quick tab switch does not stop it. */
|
|
54
|
+
export declare const PAUSE_AFTER_MS = 1500;
|
|
55
|
+
/** Go down a layer only after this long (going up is immediate): a tile shrinking for a moment keeps its picture. */
|
|
56
|
+
export declare const LOWER_AFTER_MS = 1200;
|
|
57
|
+
export interface AdaptiveHost {
|
|
58
|
+
/** The layers this producer's publisher declared (smallest first or any order); [] when unknown. */
|
|
59
|
+
layers(producerId: string): readonly VideoLayer[];
|
|
60
|
+
/** Apply a decision for one video. */
|
|
61
|
+
apply(producerId: string, want: AdaptiveWant): void;
|
|
62
|
+
/** For tests: the environment. */
|
|
63
|
+
pixelRatio?(): number;
|
|
64
|
+
pageVisible?(): boolean;
|
|
65
|
+
now?(): number;
|
|
66
|
+
setTimer?(fn: () => void, ms: number): unknown;
|
|
67
|
+
clearTimer?(handle: unknown): void;
|
|
68
|
+
/** For tests: stand-ins for ResizeObserver / IntersectionObserver / document visibility. */
|
|
69
|
+
observe?(element: Element, onChange: (box: ViewBox | null, visible: boolean | null) => void): () => void;
|
|
70
|
+
onPageVisibility?(listener: () => void): () => void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The videos being shown, by producer id. Decisions are applied through the
|
|
74
|
+
* host; going up (or becoming visible) is applied at once, going down (or
|
|
75
|
+
* hiding) after a short delay.
|
|
76
|
+
*/
|
|
77
|
+
export declare class AdaptiveVideos {
|
|
78
|
+
private readonly host;
|
|
79
|
+
private readonly entries;
|
|
80
|
+
private stopPage;
|
|
81
|
+
constructor(host: AdaptiveHost);
|
|
82
|
+
/** Show `producerId` in `element`; returns the function that stops. */
|
|
83
|
+
attach(producerId: string, element: Element): () => void;
|
|
84
|
+
detach(producerId: string, element: Element): void;
|
|
85
|
+
/** Whether the app shows this video through attachVideo (its decisions then apply to new consumers too). */
|
|
86
|
+
has(producerId: string): boolean;
|
|
87
|
+
/** The decision in force, for a consumer made (or made again) after it: null when the app does not attach it. */
|
|
88
|
+
current(producerId: string): AdaptiveWant | null;
|
|
89
|
+
/**
|
|
90
|
+
* A new consumer was made with `current()` applied (its layers, and paused when hidden): record that as applied,
|
|
91
|
+
* so nothing is sent twice, and drop a pending change made for the old one.
|
|
92
|
+
*/
|
|
93
|
+
consumed(producerId: string): void;
|
|
94
|
+
/** Stop everything (the session closed). */
|
|
95
|
+
dispose(): void;
|
|
96
|
+
private decide;
|
|
97
|
+
private update;
|
|
98
|
+
private clear;
|
|
99
|
+
private observe;
|
|
100
|
+
private watchPage;
|
|
101
|
+
}
|
|
102
|
+
/** The page is visible (not a background tab). */
|
|
103
|
+
export declare function pageIsVisible(): boolean;
|
|
104
|
+
//# sourceMappingURL=adaptive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive.d.ts","sourceRoot":"","sources":["../../src/adaptive.ts"],"names":[],"mappings":"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"}
|