@rapidmx/meet-plugin 0.13.1 → 0.14.1
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/README.md +55 -55
- package/apps/meet/[token].tsx +341 -341
- package/apps/meet/_CallControls.tsx +463 -394
- package/apps/meet/_CallView.tsx +891 -848
- package/apps/meet/_DiagnosticsWindow.tsx +80 -0
- package/apps/meet/_MeetLobby.tsx +224 -224
- package/apps/meet/_ParticipantTile.tsx +169 -169
- package/apps/meet/_ParticipantsDrawer.tsx +10 -104
- package/apps/meet/_SettingsModal.tsx +152 -0
- package/apps/meet/_icons.tsx +80 -82
- package/apps/meet/_meetApi.ts +212 -212
- package/apps/shared/media/filters/VideoFilterProcessor.ts +408 -408
- package/apps/shared/media/filters/mlModels.ts +174 -174
- package/apps/shared/push/GuestSignalingClient.ts +359 -359
- package/apps/shared/relay/RelayClient.ts +7 -2
- package/apps/shared/webrtc/MeshConnectionManager.ts +949 -795
- package/apps/shared/webrtc/types.ts +247 -222
- package/dist/apps/meet/_CallControls.d.ts +19 -9
- package/dist/apps/meet/_CallControls.js +28 -10
- package/dist/apps/meet/_CallView.d.ts +14 -2
- package/dist/apps/meet/_CallView.js +27 -2
- package/dist/apps/meet/_DiagnosticsWindow.d.ts +25 -0
- package/dist/apps/meet/_DiagnosticsWindow.js +45 -0
- package/dist/apps/meet/_ParticipantsDrawer.d.ts +10 -21
- package/dist/apps/meet/_ParticipantsDrawer.js +9 -39
- package/dist/apps/meet/_SettingsModal.d.ts +28 -0
- package/dist/apps/meet/_SettingsModal.js +59 -0
- package/dist/apps/meet/_icons.d.ts +1 -1
- package/dist/apps/meet/_icons.js +1 -1
- package/dist/apps/shared/relay/RelayClient.d.ts +6 -1
- package/dist/apps/shared/relay/RelayClient.js +7 -2
- package/dist/apps/shared/webrtc/MeshConnectionManager.d.ts +55 -2
- package/dist/apps/shared/webrtc/MeshConnectionManager.js +153 -9
- package/dist/apps/shared/webrtc/types.d.ts +25 -1
- package/dist/lib/util/RedisRelayBus.js +61 -5
- package/dist/lib/util/RedisRelayBus.js.map +1 -1
- package/dist/lib/util/RelayHub.js +10 -3
- package/dist/lib/util/RelayHub.js.map +1 -1
- package/dist/types/util/RedisRelayBus.d.ts +45 -6
- package/dist/types/util/RelayHub.d.ts +9 -2
- package/package.json +208 -208
|
@@ -99,9 +99,37 @@
|
|
|
99
99
|
* unavailable (turned off by the operator, or a browser without WebCodecs) the pair is marked `"failed"` and stays
|
|
100
100
|
* in the roster, rather than the participant vanishing with no explanation as it used to.
|
|
101
101
|
*
|
|
102
|
-
* A pair never moves back up: once on the relay it stays there for the rest of the call
|
|
102
|
+
* A pair never moves back up: once on the relay it stays there for the rest of the call - except that the whole
|
|
103
|
+
* waterfall is itself only what `"auto"` mode does; see the next section.
|
|
104
|
+
*
|
|
105
|
+
* ## Forcing a transport (`setTransportMode()`)
|
|
106
|
+
*
|
|
107
|
+
* `TransportMode` (`types.ts`) lets a participant override the waterfall above for their own tab: `"p2p"` offers
|
|
108
|
+
* ICE no TURN servers at all and never falls back past a failed direct attempt; `"relay"` sets
|
|
109
|
+
* `RTCPeerConnectionLike`'s `iceTransportPolicy: "relay"` (still real WebRTC, just never direct) and likewise never
|
|
110
|
+
* falls back further; `"websocket"` skips WebRTC negotiation entirely and jumps straight to the server relay for
|
|
111
|
+
* every peer, via the very same `fallBack()` the `"auto"` waterfall uses once it gives up (same cooperative
|
|
112
|
+
* `relay-fallback` notification, same "no relay available -> `failed`" handling - `"websocket"` mode just calls it
|
|
113
|
+
* immediately instead of after a timeout). `"p2p"`/`"relay"` skip the waterfall in the other direction:
|
|
114
|
+
* `giveUpOnWebRTC()` is what `createPeer()`'s connect timeout and `handleConnectionState()`'s `disconnected`/
|
|
115
|
+
* `failed` branches call instead of `fallBack()` directly, and it marks the pair `"failed"` outright in forced
|
|
116
|
+
* `"p2p"`/`"relay"` mode rather than degrading to the relay - a participant who explicitly chose "peer-to-peer
|
|
117
|
+
* only" or "TURN relay only" to troubleshoot a connection does not want it quietly becoming something else.
|
|
118
|
+
*
|
|
119
|
+
* Switching modes mid-call (not just at join) rebuilds every current peer connection under the new policy
|
|
120
|
+
* (`restartPeer()`): the old `RTCPeerConnection` is closed and a fresh one created in its place, exactly as
|
|
121
|
+
* `createPeer()` builds one for a brand-new peer, but keeping the existing roster entry (name/audioOn/videoOn/
|
|
122
|
+
* handRaised) and emitting `participant-updated` rather than a `participant-left`/`participant-joined` pair - from
|
|
123
|
+
* the UI's perspective this looks like the existing `"connecting"` state any reconnect already uses, not someone
|
|
124
|
+
* leaving and rejoining. This can't be done unilaterally: a participant cannot rebuild their own half of a pair
|
|
125
|
+
* without the other side rebuilding its matching half at (almost) the same moment, or the two ends negotiate
|
|
126
|
+
* against two different objects that have no idea about each other. `setTransportMode()` therefore also broadcasts
|
|
127
|
+
* `restart-connection` (no payload beyond the envelope - `types.ts`'s `SignalMessage` doc comment), and every
|
|
128
|
+
* recipient calls `restartPeer()` for just that one sender's connection in response - each side always rebuilds
|
|
129
|
+
* using whatever mode *it* currently has configured, never the sender's, since this is a personal, per-tab choice,
|
|
130
|
+
* not something a meeting agrees on together.
|
|
103
131
|
*/
|
|
104
|
-
import { type MeshEvent, type MeshParticipant, type ParticipantState, type RTCPeerConnectionFactory, type RelayTransportLike, type SignalingChannel } from "./types.js";
|
|
132
|
+
import { type MeshEvent, type MeshParticipant, type ParticipantState, type RTCPeerConnectionFactory, type RelayTransportLike, type SignalingChannel, type TransportMode } from "./types.js";
|
|
105
133
|
/** The lexicographically smaller uid is always the offerer for that pair - see this module's doc comment. */
|
|
106
134
|
export declare function isOfferer(selfId: string, peerId: string): boolean;
|
|
107
135
|
/** How long a connection may stay in `connecting` before the pair gives up on WebRTC. ICE with an unreachable TURN
|
|
@@ -143,6 +171,8 @@ export interface MeshConnectionManagerOptions {
|
|
|
143
171
|
disconnectedGraceMs?: number;
|
|
144
172
|
/** Defaults to `DEFAULT_DIAGNOSTICS_POLL_MS`. */
|
|
145
173
|
diagnosticsPollMs?: number;
|
|
174
|
+
/** Defaults to `"auto"` - see this module's doc comment on `setTransportMode()`. */
|
|
175
|
+
transportMode?: TransportMode;
|
|
146
176
|
}
|
|
147
177
|
export declare class MeshConnectionManager {
|
|
148
178
|
private readonly options;
|
|
@@ -155,10 +185,12 @@ export declare class MeshConnectionManager {
|
|
|
155
185
|
private started;
|
|
156
186
|
private stopped;
|
|
157
187
|
private currentPresenterUid;
|
|
188
|
+
private currentTransportMode;
|
|
158
189
|
private readonly selfId;
|
|
159
190
|
constructor(options: MeshConnectionManagerOptions);
|
|
160
191
|
get participants(): MeshParticipant[];
|
|
161
192
|
get presenterUid(): string | undefined;
|
|
193
|
+
get transportMode(): TransportMode;
|
|
162
194
|
onEvent(listener: (event: MeshEvent) => void): () => void;
|
|
163
195
|
/** Subscribes to the channel and announces this participant. Idempotent - a second call is a no-op. */
|
|
164
196
|
start(): void;
|
|
@@ -195,6 +227,10 @@ export declare class MeshConnectionManager {
|
|
|
195
227
|
* there is only ever one sender; the caller (`_CallView.tsx`) is responsible for only offering this to the
|
|
196
228
|
* host. */
|
|
197
229
|
setTalkingStick(active: boolean, holder?: string): void;
|
|
230
|
+
/** Switches this tab's own transport policy - see this module's doc comment on `TransportMode`. A no-op for the
|
|
231
|
+
* mode it already has. Rebuilds every current peer connection under the new policy and, once `start()` has
|
|
232
|
+
* run, tells every peer to rebuild their matching half too (`restart-connection`) - see `restartPeer()`. */
|
|
233
|
+
setTransportMode(mode: TransportMode): void;
|
|
198
234
|
private sendHello;
|
|
199
235
|
private send;
|
|
200
236
|
private emit;
|
|
@@ -207,6 +243,11 @@ export declare class MeshConnectionManager {
|
|
|
207
243
|
private updatePeer;
|
|
208
244
|
private handleBye;
|
|
209
245
|
private createPeer;
|
|
246
|
+
/** The `RTCPeerConnection` config for a peer created right now - `this.options.iceServers` as given for `"auto"`
|
|
247
|
+
* and `"websocket"` (the latter never actually uses them - see `handleHello()`/`handleOffer()`), STUN-only (no
|
|
248
|
+
* TURN offered to ICE at all) for `"p2p"`, and the same servers with `iceTransportPolicy: "relay"` for
|
|
249
|
+
* `"relay"` - see this module's doc comment on `TransportMode`. */
|
|
250
|
+
private pcConfig;
|
|
210
251
|
/** Reacts to the peer connection's state: notes which path a connected pair got, and gives up on WebRTC for a
|
|
211
252
|
* pair that failed or stays disconnected - see `fallBack()`. Ignores a pair that already left WebRTC. */
|
|
212
253
|
private handleConnectionState;
|
|
@@ -220,6 +261,12 @@ export declare class MeshConnectionManager {
|
|
|
220
261
|
private pollDiagnostics;
|
|
221
262
|
private setTransport;
|
|
222
263
|
private clearTimers;
|
|
264
|
+
/** What a connect timeout or a `"disconnected"`/`"failed"` connection state means for `peer`: `fallBack()` to
|
|
265
|
+
* the relay for `"auto"` mode (unchanged from before `TransportMode` existed), but `"failed"` outright for a
|
|
266
|
+
* forced `"p2p"`/`"relay"` mode - see this module's doc comment on why those two never degrade further.
|
|
267
|
+
* `"websocket"` mode never reaches here at all: `handleHello()`/`handleOffer()` call `fallBack()` immediately
|
|
268
|
+
* on peer creation, well before a connect timeout could ever fire. */
|
|
269
|
+
private giveUpOnWebRTC;
|
|
223
270
|
/**
|
|
224
271
|
* Gives up on WebRTC for `peer` and moves the pair to the WebSocket relay (the third path - see this module's doc
|
|
225
272
|
* comment), or marks it `"failed"` when there is no relay to move to. `notify` tells the other side, which may not
|
|
@@ -229,6 +276,12 @@ export declare class MeshConnectionManager {
|
|
|
229
276
|
private handleRelayFallback;
|
|
230
277
|
/** The relay only encodes and uploads the local media while at least one pair is actually using it. */
|
|
231
278
|
private syncRelaySending;
|
|
279
|
+
/** Rebuilds `uid`'s connection from scratch under the current `TransportMode` - see this module's doc comment
|
|
280
|
+
* on `setTransportMode()`. A no-op if `uid` isn't a known peer (already left). Keeps the roster entry (name and
|
|
281
|
+
* announced state) and emits `participant-updated`, not a `participant-left`/`participant-joined` pair - from
|
|
282
|
+
* the UI's perspective this is the same `"connecting"` state any other reconnect already shows, not someone
|
|
283
|
+
* leaving and rejoining. */
|
|
284
|
+
private restartPeer;
|
|
232
285
|
private initiateOffer;
|
|
233
286
|
private handleOffer;
|
|
234
287
|
private handleAnswer;
|
|
@@ -103,7 +103,35 @@
|
|
|
103
103
|
* unavailable (turned off by the operator, or a browser without WebCodecs) the pair is marked `"failed"` and stays
|
|
104
104
|
* in the roster, rather than the participant vanishing with no explanation as it used to.
|
|
105
105
|
*
|
|
106
|
-
* A pair never moves back up: once on the relay it stays there for the rest of the call
|
|
106
|
+
* A pair never moves back up: once on the relay it stays there for the rest of the call - except that the whole
|
|
107
|
+
* waterfall is itself only what `"auto"` mode does; see the next section.
|
|
108
|
+
*
|
|
109
|
+
* ## Forcing a transport (`setTransportMode()`)
|
|
110
|
+
*
|
|
111
|
+
* `TransportMode` (`types.ts`) lets a participant override the waterfall above for their own tab: `"p2p"` offers
|
|
112
|
+
* ICE no TURN servers at all and never falls back past a failed direct attempt; `"relay"` sets
|
|
113
|
+
* `RTCPeerConnectionLike`'s `iceTransportPolicy: "relay"` (still real WebRTC, just never direct) and likewise never
|
|
114
|
+
* falls back further; `"websocket"` skips WebRTC negotiation entirely and jumps straight to the server relay for
|
|
115
|
+
* every peer, via the very same `fallBack()` the `"auto"` waterfall uses once it gives up (same cooperative
|
|
116
|
+
* `relay-fallback` notification, same "no relay available -> `failed`" handling - `"websocket"` mode just calls it
|
|
117
|
+
* immediately instead of after a timeout). `"p2p"`/`"relay"` skip the waterfall in the other direction:
|
|
118
|
+
* `giveUpOnWebRTC()` is what `createPeer()`'s connect timeout and `handleConnectionState()`'s `disconnected`/
|
|
119
|
+
* `failed` branches call instead of `fallBack()` directly, and it marks the pair `"failed"` outright in forced
|
|
120
|
+
* `"p2p"`/`"relay"` mode rather than degrading to the relay - a participant who explicitly chose "peer-to-peer
|
|
121
|
+
* only" or "TURN relay only" to troubleshoot a connection does not want it quietly becoming something else.
|
|
122
|
+
*
|
|
123
|
+
* Switching modes mid-call (not just at join) rebuilds every current peer connection under the new policy
|
|
124
|
+
* (`restartPeer()`): the old `RTCPeerConnection` is closed and a fresh one created in its place, exactly as
|
|
125
|
+
* `createPeer()` builds one for a brand-new peer, but keeping the existing roster entry (name/audioOn/videoOn/
|
|
126
|
+
* handRaised) and emitting `participant-updated` rather than a `participant-left`/`participant-joined` pair - from
|
|
127
|
+
* the UI's perspective this looks like the existing `"connecting"` state any reconnect already uses, not someone
|
|
128
|
+
* leaving and rejoining. This can't be done unilaterally: a participant cannot rebuild their own half of a pair
|
|
129
|
+
* without the other side rebuilding its matching half at (almost) the same moment, or the two ends negotiate
|
|
130
|
+
* against two different objects that have no idea about each other. `setTransportMode()` therefore also broadcasts
|
|
131
|
+
* `restart-connection` (no payload beyond the envelope - `types.ts`'s `SignalMessage` doc comment), and every
|
|
132
|
+
* recipient calls `restartPeer()` for just that one sender's connection in response - each side always rebuilds
|
|
133
|
+
* using whatever mode *it* currently has configured, never the sender's, since this is a personal, per-tab choice,
|
|
134
|
+
* not something a meeting agrees on together.
|
|
107
135
|
*/
|
|
108
136
|
import { REACTION_EMOJIS, } from "./types.js";
|
|
109
137
|
/** The lexicographically smaller uid is always the offerer for that pair - see this module's doc comment. */
|
|
@@ -133,6 +161,7 @@ export class MeshConnectionManager {
|
|
|
133
161
|
this.started = false;
|
|
134
162
|
this.stopped = false;
|
|
135
163
|
this.selfId = options.peerId ?? options.selfUid;
|
|
164
|
+
this.currentTransportMode = options.transportMode ?? "auto";
|
|
136
165
|
this.localTracks = { audio: options.localAudioTrack ?? null, video: options.localVideoTrack ?? null };
|
|
137
166
|
this.localState = {
|
|
138
167
|
audioOn: !!options.localAudioTrack,
|
|
@@ -149,6 +178,9 @@ export class MeshConnectionManager {
|
|
|
149
178
|
get presenterUid() {
|
|
150
179
|
return this.currentPresenterUid;
|
|
151
180
|
}
|
|
181
|
+
get transportMode() {
|
|
182
|
+
return this.currentTransportMode;
|
|
183
|
+
}
|
|
152
184
|
onEvent(listener) {
|
|
153
185
|
this.listeners.add(listener);
|
|
154
186
|
return () => this.listeners.delete(listener);
|
|
@@ -257,6 +289,21 @@ export class MeshConnectionManager {
|
|
|
257
289
|
this.send({ kind: "talking-stick", active, holder });
|
|
258
290
|
this.emit({ type: "talking-stick-changed", active, holder });
|
|
259
291
|
}
|
|
292
|
+
/** Switches this tab's own transport policy - see this module's doc comment on `TransportMode`. A no-op for the
|
|
293
|
+
* mode it already has. Rebuilds every current peer connection under the new policy and, once `start()` has
|
|
294
|
+
* run, tells every peer to rebuild their matching half too (`restart-connection`) - see `restartPeer()`. */
|
|
295
|
+
setTransportMode(mode) {
|
|
296
|
+
if (this.currentTransportMode === mode) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.currentTransportMode = mode;
|
|
300
|
+
if (this.started && !this.stopped) {
|
|
301
|
+
this.send({ kind: "restart-connection" });
|
|
302
|
+
}
|
|
303
|
+
for (const uid of [...this.peers.keys()]) {
|
|
304
|
+
this.restartPeer(uid);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
260
307
|
sendHello() {
|
|
261
308
|
this.send({ kind: "hello", name: this.options.selfName, state: this.localState });
|
|
262
309
|
}
|
|
@@ -323,6 +370,9 @@ export class MeshConnectionManager {
|
|
|
323
370
|
case "talking-stick":
|
|
324
371
|
this.emit({ type: "talking-stick-changed", active: message.active === true, holder: typeof message.holder === "string" ? message.holder : undefined });
|
|
325
372
|
return;
|
|
373
|
+
case "restart-connection":
|
|
374
|
+
this.restartPeer(message.from);
|
|
375
|
+
return;
|
|
326
376
|
}
|
|
327
377
|
}
|
|
328
378
|
handleHello(message) {
|
|
@@ -338,6 +388,11 @@ export class MeshConnectionManager {
|
|
|
338
388
|
// Let a newcomer who couldn't have seen our own original `hello` learn about us too - see this module's
|
|
339
389
|
// doc comment on roster discovery.
|
|
340
390
|
this.sendHello();
|
|
391
|
+
if (this.currentTransportMode === "websocket") {
|
|
392
|
+
// Forced relay-only: don't negotiate WebRTC at all - see this module's doc comment on `setTransportMode()`.
|
|
393
|
+
this.fallBack(peer, true);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
341
396
|
if (offerer) {
|
|
342
397
|
void this.initiateOffer(peer);
|
|
343
398
|
}
|
|
@@ -395,7 +450,7 @@ export class MeshConnectionManager {
|
|
|
395
450
|
}
|
|
396
451
|
}
|
|
397
452
|
createPeer(uid, name, state, offerer) {
|
|
398
|
-
const pc = this.options.createPeerConnection(
|
|
453
|
+
const pc = this.options.createPeerConnection(this.pcConfig());
|
|
399
454
|
const peer = {
|
|
400
455
|
uid,
|
|
401
456
|
name,
|
|
@@ -430,9 +485,23 @@ export class MeshConnectionManager {
|
|
|
430
485
|
};
|
|
431
486
|
pc.onconnectionstatechange = () => this.handleConnectionState(peer);
|
|
432
487
|
// Cleared the moment the connection comes up, so this only ever fires for a pair still waiting on WebRTC.
|
|
433
|
-
peer.connectTimer = setTimeout(() => this.
|
|
488
|
+
peer.connectTimer = setTimeout(() => this.giveUpOnWebRTC(peer), this.options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS);
|
|
434
489
|
return peer;
|
|
435
490
|
}
|
|
491
|
+
/** The `RTCPeerConnection` config for a peer created right now - `this.options.iceServers` as given for `"auto"`
|
|
492
|
+
* and `"websocket"` (the latter never actually uses them - see `handleHello()`/`handleOffer()`), STUN-only (no
|
|
493
|
+
* TURN offered to ICE at all) for `"p2p"`, and the same servers with `iceTransportPolicy: "relay"` for
|
|
494
|
+
* `"relay"` - see this module's doc comment on `TransportMode`. */
|
|
495
|
+
pcConfig() {
|
|
496
|
+
switch (this.currentTransportMode) {
|
|
497
|
+
case "p2p":
|
|
498
|
+
return { iceServers: stunOnlyServers(this.options.iceServers) };
|
|
499
|
+
case "relay":
|
|
500
|
+
return { iceServers: this.options.iceServers, iceTransportPolicy: "relay" };
|
|
501
|
+
default:
|
|
502
|
+
return { iceServers: this.options.iceServers };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
436
505
|
/** Reacts to the peer connection's state: notes which path a connected pair got, and gives up on WebRTC for a
|
|
437
506
|
* pair that failed or stays disconnected - see `fallBack()`. Ignores a pair that already left WebRTC. */
|
|
438
507
|
handleConnectionState(peer) {
|
|
@@ -446,10 +515,10 @@ export class MeshConnectionManager {
|
|
|
446
515
|
return;
|
|
447
516
|
case "disconnected":
|
|
448
517
|
// ICE often recovers from this on its own, so it is only given up on after a grace period.
|
|
449
|
-
peer.disconnectTimer ?? (peer.disconnectTimer = setTimeout(() => this.
|
|
518
|
+
peer.disconnectTimer ?? (peer.disconnectTimer = setTimeout(() => this.giveUpOnWebRTC(peer), this.options.disconnectedGraceMs ?? DEFAULT_DISCONNECTED_GRACE_MS));
|
|
450
519
|
return;
|
|
451
520
|
case "failed":
|
|
452
|
-
this.
|
|
521
|
+
this.giveUpOnWebRTC(peer);
|
|
453
522
|
return;
|
|
454
523
|
case "closed":
|
|
455
524
|
this.handleBye(peer.uid);
|
|
@@ -513,6 +582,31 @@ export class MeshConnectionManager {
|
|
|
513
582
|
peer.connectTimer = undefined;
|
|
514
583
|
peer.disconnectTimer = undefined;
|
|
515
584
|
}
|
|
585
|
+
/** What a connect timeout or a `"disconnected"`/`"failed"` connection state means for `peer`: `fallBack()` to
|
|
586
|
+
* the relay for `"auto"` mode (unchanged from before `TransportMode` existed), but `"failed"` outright for a
|
|
587
|
+
* forced `"p2p"`/`"relay"` mode - see this module's doc comment on why those two never degrade further.
|
|
588
|
+
* `"websocket"` mode never reaches here at all: `handleHello()`/`handleOffer()` call `fallBack()` immediately
|
|
589
|
+
* on peer creation, well before a connect timeout could ever fire. */
|
|
590
|
+
giveUpOnWebRTC(peer) {
|
|
591
|
+
if (this.currentTransportMode !== "p2p" && this.currentTransportMode !== "relay") {
|
|
592
|
+
this.fallBack(peer);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
/* v8 ignore if -- unreachable via real usage: unlike `fallBack()` (also callable directly from
|
|
596
|
+
`handleRelayFallback()`, which is how its own identical-looking guard is actually exercised),
|
|
597
|
+
`giveUpOnWebRTC()`'s only callers are `createPeer()`'s connect timer and `handleConnectionState()`'s
|
|
598
|
+
disconnect timer/`"failed"` case - and every transition that could make either side of this true
|
|
599
|
+
(`fallBack()` moving a peer to `"websocket"`, `restartPeer()` replacing the map entry) clears this
|
|
600
|
+
peer's pending timers first, so a stale timer referencing this exact peer object never fires. Kept for
|
|
601
|
+
the same reason `fallBack()`'s guard is - a future caller that doesn't pre-clear timers should fail
|
|
602
|
+
safe, not corrupt an already-superseded peer. */
|
|
603
|
+
if (peer.transport === "websocket" || this.peers.get(peer.uid) !== peer) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
this.clearTimers(peer);
|
|
607
|
+
this.stopDiagnosticsPolling(peer);
|
|
608
|
+
this.setTransport(peer, "failed");
|
|
609
|
+
}
|
|
516
610
|
/**
|
|
517
611
|
* Gives up on WebRTC for `peer` and moves the pair to the WebSocket relay (the third path - see this module's doc
|
|
518
612
|
* comment), or marks it `"failed"` when there is no relay to move to. `notify` tells the other side, which may not
|
|
@@ -556,6 +650,40 @@ export class MeshConnectionManager {
|
|
|
556
650
|
syncRelaySending() {
|
|
557
651
|
this.options.relay?.setSending([...this.peers.values()].some((peer) => peer.transport === "websocket"));
|
|
558
652
|
}
|
|
653
|
+
/** Rebuilds `uid`'s connection from scratch under the current `TransportMode` - see this module's doc comment
|
|
654
|
+
* on `setTransportMode()`. A no-op if `uid` isn't a known peer (already left). Keeps the roster entry (name and
|
|
655
|
+
* announced state) and emits `participant-updated`, not a `participant-left`/`participant-joined` pair - from
|
|
656
|
+
* the UI's perspective this is the same `"connecting"` state any other reconnect already shows, not someone
|
|
657
|
+
* leaving and rejoining. */
|
|
658
|
+
restartPeer(uid) {
|
|
659
|
+
const old = this.peers.get(uid);
|
|
660
|
+
if (!old) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
this.clearTimers(old);
|
|
664
|
+
this.stopDiagnosticsPolling(old);
|
|
665
|
+
// Stop listening to the dying connection before closing it - same ordering `fallBack()` uses, and for the
|
|
666
|
+
// same reason: closing it must never be mistaken for the peer leaving.
|
|
667
|
+
old.pc.onconnectionstatechange = null;
|
|
668
|
+
old.pc.onicecandidate = null;
|
|
669
|
+
old.pc.ontrack = null;
|
|
670
|
+
old.pc.close();
|
|
671
|
+
if (old.transport === "websocket") {
|
|
672
|
+
this.options.relay?.stopReceivingFrom(uid);
|
|
673
|
+
}
|
|
674
|
+
const offerer = isOfferer(this.selfId, uid);
|
|
675
|
+
const fresh = this.createPeer(uid, old.name, { audioOn: old.audioOn, videoOn: old.videoOn, handRaised: old.handRaised }, offerer);
|
|
676
|
+
if (this.currentTransportMode === "websocket") {
|
|
677
|
+
this.fallBack(fresh, true);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
// In case `old` was the relay's only remaining user - `fresh` starts on WebRTC, not the relay.
|
|
681
|
+
this.syncRelaySending();
|
|
682
|
+
this.emit({ type: "participant-updated", participant: toParticipant(fresh) });
|
|
683
|
+
if (offerer) {
|
|
684
|
+
void this.initiateOffer(fresh);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
559
687
|
async initiateOffer(peer) {
|
|
560
688
|
const offer = await peer.pc.createOffer();
|
|
561
689
|
await peer.pc.setLocalDescription(offer);
|
|
@@ -570,6 +698,17 @@ export class MeshConnectionManager {
|
|
|
570
698
|
if (!peer) {
|
|
571
699
|
peer = this.createPeer(message.from, message.from, undefined, false);
|
|
572
700
|
}
|
|
701
|
+
if (isNew) {
|
|
702
|
+
this.emit({ type: "participant-joined", participant: toParticipant(peer) });
|
|
703
|
+
this.sendHello();
|
|
704
|
+
}
|
|
705
|
+
if (this.currentTransportMode === "websocket") {
|
|
706
|
+
// Forced relay-only: don't bother negotiating this offer at all - see this module's doc comment on
|
|
707
|
+
// `setTransportMode()`. Also tells the offerer (`relay-fallback`), so they don't wait out their own
|
|
708
|
+
// connect timeout for an answer that was never coming.
|
|
709
|
+
this.fallBack(peer, true);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
573
712
|
await peer.pc.setRemoteDescription(message.sdp);
|
|
574
713
|
this.flushPendingCandidates(peer);
|
|
575
714
|
if (!peer.senders.audio && !peer.senders.video) {
|
|
@@ -582,10 +721,6 @@ export class MeshConnectionManager {
|
|
|
582
721
|
}
|
|
583
722
|
}
|
|
584
723
|
}
|
|
585
|
-
if (isNew) {
|
|
586
|
-
this.emit({ type: "participant-joined", participant: toParticipant(peer) });
|
|
587
|
-
this.sendHello();
|
|
588
|
-
}
|
|
589
724
|
const answer = await peer.pc.createAnswer();
|
|
590
725
|
await peer.pc.setLocalDescription(answer);
|
|
591
726
|
this.send({ kind: "answer", to: peer.uid, sdp: answer });
|
|
@@ -681,3 +816,12 @@ function sanitizeState(state) {
|
|
|
681
816
|
function isReactionEmoji(emoji) {
|
|
682
817
|
return REACTION_EMOJIS.includes(emoji);
|
|
683
818
|
}
|
|
819
|
+
/** `servers` with every entry that names so much as one `turn:`/`turns:` url dropped - what `"p2p"` mode gives ICE,
|
|
820
|
+
* so it has no TURN relay candidate to ever produce. A `stun:`/`stuns:`-only entry (the normal shape this app's own
|
|
821
|
+
* `buildIceServers()` produces - one STUN entry, one TURN entry) is kept as-is. */
|
|
822
|
+
function stunOnlyServers(servers) {
|
|
823
|
+
return servers.filter((server) => {
|
|
824
|
+
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
825
|
+
return urls.every((url) => url.startsWith("stun:") || url.startsWith("stuns:"));
|
|
826
|
+
});
|
|
827
|
+
}
|
|
@@ -14,6 +14,20 @@ export interface ParticipantState {
|
|
|
14
14
|
/** The emoji a participant can send during a call. A received `reaction` outside this list is ignored, so an
|
|
15
15
|
* arbitrary string from another participant is never rendered. */
|
|
16
16
|
export declare const REACTION_EMOJIS: readonly ["👍", "👏", "❤️", "🎉", "😂", "😮", "😢", "👋"];
|
|
17
|
+
/** Which media path this participant's own tab currently insists on, for every peer - see
|
|
18
|
+
* `MeshConnectionManager.setTransportMode()`'s doc comment for what each one actually does:
|
|
19
|
+
*
|
|
20
|
+
* - `"auto"`: the default waterfall (direct, then TURN, then the server's own WebSocket relay) - unchanged behavior.
|
|
21
|
+
* - `"p2p"`: direct connections only - no TURN server offered to ICE, and a pair that can't connect directly stays
|
|
22
|
+
* `"failed"` rather than falling back to the WebSocket relay.
|
|
23
|
+
* - `"relay"`: TURN-relayed WebRTC only (`RTCPeerConnection`'s own `iceTransportPolicy: "relay"`) - still real
|
|
24
|
+
* WebRTC, just never direct; a pair with no reachable TURN server stays `"failed"`, same "no further fallback" rule
|
|
25
|
+
* as `"p2p"`.
|
|
26
|
+
* - `"websocket"`: skips WebRTC negotiation entirely for every pair and goes straight to the server's own relay.
|
|
27
|
+
*
|
|
28
|
+
* A purely personal, per-tab troubleshooting choice - never transmitted as a setting, and independent of whatever
|
|
29
|
+
* every other participant has chosen for their own tab. */
|
|
30
|
+
export type TransportMode = "auto" | "p2p" | "relay" | "websocket";
|
|
17
31
|
/**
|
|
18
32
|
* One WebRTC signaling message, published as the JSON body of `POST /push/:meetingUid` (`BasePushRoute.send()`)
|
|
19
33
|
* and received back over `/push`'s WebSocket, exactly as `BaseVideoMeetingRoute`'s doc comment describes ("SDP
|
|
@@ -33,10 +47,16 @@ export declare const REACTION_EMOJIS: readonly ["👍", "👏", "❤️", "🎉"
|
|
|
33
47
|
* revoking the recipient's server-side channel grant (`BaseVideoMeetingRoute.revokeChannelGrant()`), which is
|
|
34
48
|
* enforced and does not depend on the recipient's client cooperating; `talking-stick` has no server-side
|
|
35
49
|
* counterpart at all (see this module's doc comment on why it is purely an in-call runtime signal).
|
|
50
|
+
*
|
|
51
|
+
* `restart-connection` is broadcast whenever a participant changes their own `TransportMode`
|
|
52
|
+
* (`MeshConnectionManager.setTransportMode()`): every connection of theirs needs rebuilding under the new policy,
|
|
53
|
+
* and a recipient can't rebuild its own half of a pair unilaterally without the other side doing the same at
|
|
54
|
+
* (almost) the same moment, or the two sides end up negotiating against two different `RTCPeerConnection`s. No
|
|
55
|
+
* payload beyond the envelope - `from` already says whose connection to rebuild.
|
|
36
56
|
*/
|
|
37
57
|
export interface SignalMessage {
|
|
38
58
|
type: "video-meeting-signal";
|
|
39
|
-
kind: "hello" | "bye" | "offer" | "answer" | "ice-candidate" | "presenter-claim" | "presenter-release" | "state" | "reaction" | "relay-fallback" | "mute-request" | "kicked" | "talking-stick";
|
|
59
|
+
kind: "hello" | "bye" | "offer" | "answer" | "ice-candidate" | "presenter-claim" | "presenter-release" | "state" | "reaction" | "relay-fallback" | "mute-request" | "kicked" | "talking-stick" | "restart-connection";
|
|
40
60
|
/** The sender's authenticated uid - the real caller's own uid when `join()` returned `authenticated: true`, else
|
|
41
61
|
* the `guest:<random>` uid `BaseVideoMeetingRoute.join()` minted. The server refuses a published message whose
|
|
42
62
|
* `from` isn't the authenticated caller's uid (it stops one participant speaking as another), so this must be
|
|
@@ -165,8 +185,12 @@ export interface RelayTransportLike {
|
|
|
165
185
|
setLocalTrack(kind: "audio" | "video", track: MediaStreamTrack | null): void;
|
|
166
186
|
close(): void;
|
|
167
187
|
}
|
|
188
|
+
/** `iceTransportPolicy` mirrors the real `RTCConfiguration` field of the same name - `"relay"` forces ICE to use
|
|
189
|
+
* only a TURN relay candidate, never a direct one (`TransportMode.relay`); omitted (or `"all"`) is the ordinary,
|
|
190
|
+
* unconstrained default. */
|
|
168
191
|
export type RTCPeerConnectionFactory = (config: {
|
|
169
192
|
iceServers: RTCIceServer[];
|
|
193
|
+
iceTransportPolicy?: "all" | "relay";
|
|
170
194
|
}) => RTCPeerConnectionLike;
|
|
171
195
|
/** One other participant currently known to be in the call - `MeshConnectionManager.participants` never includes
|
|
172
196
|
* the local participant themselves (the UI already knows its own name/uid without asking the manager). */
|
|
@@ -11,6 +11,9 @@ export const REDIS_RELAY_ORIGIN_BYTES = 16;
|
|
|
11
11
|
/** The least time between two `warn` messages about failing Redis commands; the failures in between are logged at
|
|
12
12
|
* `debug`, so a Redis outage cannot flood the log at the rate media frames arrive. */
|
|
13
13
|
export const REDIS_RELAY_WARN_INTERVAL_MS = 30000;
|
|
14
|
+
/** How often the bus re-checks, via one batched `PUBSUB NUMSUB`, whether any other replica still has a listener for
|
|
15
|
+
* each of its active rooms - see the class doc comment's "Skipping Redis for a single-replica room" section. */
|
|
16
|
+
export const REDIS_RELAY_NUMSUB_POLL_MS = 2000;
|
|
14
17
|
/** The channel a room's frames are published to. */
|
|
15
18
|
export function redisRelayChannel(roomId) {
|
|
16
19
|
return REDIS_RELAY_CHANNEL_PREFIX + roomId;
|
|
@@ -66,13 +69,33 @@ export function redisRelayChannel(roomId) {
|
|
|
66
69
|
* server's client-output-buffer limit for pub/sub, after which Redis drops that connection (the client then
|
|
67
70
|
* reconnects and resubscribes).
|
|
68
71
|
*
|
|
72
|
+
* ## Skipping Redis for a single-replica room
|
|
73
|
+
*
|
|
74
|
+
* Every room still SUBSCRIBEs the moment it has its first local listener (so a remote replica's room gets this
|
|
75
|
+
* replica's frames the instant it starts caring, with no detection delay), but whether `publish()` also bothers
|
|
76
|
+
* PUBLISHing a frame to Redis in the first place is a separate decision, re-checked on a timer
|
|
77
|
+
* (`pollLocalOnly()`, every `numSubPollMs`/`REDIS_RELAY_NUMSUB_POLL_MS`): one batched `PUBSUB NUMSUB` across every
|
|
78
|
+
* active room's channel tells this bus, authoritatively, how many subscribers across the *whole* Redis deployment
|
|
79
|
+
* each channel has right now. A channel with at most one (this bus's own subscription, and only this bus's own)
|
|
80
|
+
* has no listener on any other replica - there is nobody `publishToRedis()` could possibly be publishing *for* -
|
|
81
|
+
* so a room's `localOnly` flag is set and every frame for it skips the Redis publish entirely until the next poll
|
|
82
|
+
* says otherwise, which is by far the common case for a small/single-instance deployment and for most rooms even in
|
|
83
|
+
* a larger one (most calls never span replicas). A room `localOnly` has not yet been determined for (just created,
|
|
84
|
+
* or the last poll failed) keeps publishing - the safe default, since under-publishing (an unnoticed remote
|
|
85
|
+
* listener goes briefly silent) is a real glitch and over-publishing (a few redundant PUBLISHes with no
|
|
86
|
+
* subscriber) costs only a little Redis traffic. The gap between a remote replica's listener actually arriving and
|
|
87
|
+
* this bus's next poll noticing is therefore bounded by `numSubPollMs` - frames to that listener may be briefly
|
|
88
|
+
* missing right at that boundary, which is the same order of magnitude as, and no worse than, media this transport
|
|
89
|
+
* already tolerates losing under ordinary backpressure (see `RelayHub.RELAY_WS_MAX_BACKPRESSURE_BYTES`'s own doc
|
|
90
|
+
* comment).
|
|
91
|
+
*
|
|
69
92
|
* @author Jean-Philippe Steinmetz
|
|
70
93
|
*/
|
|
71
94
|
export class RedisRelayBus {
|
|
72
95
|
constructor(options) {
|
|
73
96
|
this.options = options;
|
|
74
97
|
/** What the bus has done so far. */
|
|
75
|
-
this.stats = { published: 0, publishSkipped: 0, failures: 0, received: 0, receivedDropped: 0 };
|
|
98
|
+
this.stats = { published: 0, publishSkipped: 0, publishSkippedLocalOnly: 0, failures: 0, received: 0, receivedDropped: 0 };
|
|
76
99
|
this.id = crypto.randomBytes(REDIS_RELAY_ORIGIN_BYTES);
|
|
77
100
|
this.rooms = new Map();
|
|
78
101
|
this.lastWarn = Number.NEGATIVE_INFINITY;
|
|
@@ -108,17 +131,20 @@ export class RedisRelayBus {
|
|
|
108
131
|
this.subscriber = subscriber;
|
|
109
132
|
this.run("connect the publisher", () => publisher.connect());
|
|
110
133
|
this.run("connect the subscriber", () => subscriber.connect());
|
|
134
|
+
this.pollTimer = setInterval(() => void this.pollLocalOnly(), this.options.numSubPollMs ?? REDIS_RELAY_NUMSUB_POLL_MS);
|
|
111
135
|
}
|
|
112
136
|
/**
|
|
113
|
-
* Shuts the bus down: destroys both clients (which ends their connections, and with them every subscription)
|
|
114
|
-
* stops publishing. Local delivery keeps working, as after a failed
|
|
115
|
-
* Nothing is unsubscribed one by one: the connection going away does that
|
|
116
|
-
* only stall behind a Redis that is down.
|
|
137
|
+
* Shuts the bus down: destroys both clients (which ends their connections, and with them every subscription),
|
|
138
|
+
* stops the `pollLocalOnly()` timer and stops publishing. Local delivery keeps working, as after a failed
|
|
139
|
+
* `connect()`. Safe to call repeatedly. Nothing is unsubscribed one by one: the connection going away does that
|
|
140
|
+
* server-side, and an UNSUBSCRIBE could only stall behind a Redis that is down.
|
|
117
141
|
*/
|
|
118
142
|
close() {
|
|
119
143
|
const clients = [this.publisher, this.subscriber];
|
|
120
144
|
this.publisher = undefined;
|
|
121
145
|
this.subscriber = undefined;
|
|
146
|
+
clearInterval(this.pollTimer);
|
|
147
|
+
this.pollTimer = undefined;
|
|
122
148
|
for (const client of clients) {
|
|
123
149
|
try {
|
|
124
150
|
client?.destroy();
|
|
@@ -152,6 +178,10 @@ export class RedisRelayBus {
|
|
|
152
178
|
}
|
|
153
179
|
}
|
|
154
180
|
publishToRedis(roomId, sender, frame) {
|
|
181
|
+
if (this.rooms.get(roomId)?.localOnly) {
|
|
182
|
+
this.stats.publishSkippedLocalOnly++;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
155
185
|
const publisher = this.publisher;
|
|
156
186
|
const senderBytes = Buffer.from(sender, "utf8");
|
|
157
187
|
if (!publisher?.isReady || senderBytes.length === 0 || senderBytes.length > 255) {
|
|
@@ -166,6 +196,32 @@ export class RedisRelayBus {
|
|
|
166
196
|
this.stats.published++;
|
|
167
197
|
this.run("publish a frame", () => publisher.publish(redisRelayChannel(roomId), message));
|
|
168
198
|
}
|
|
199
|
+
/** Refreshes every active room's `localOnly` flag with one batched `PUBSUB NUMSUB`, on the publisher client (the
|
|
200
|
+
* subscriber is reserved for buffer-mode subscribe/unsubscribe) - see the class doc comment. Does nothing while
|
|
201
|
+
* there are no rooms or the publisher isn't ready; a failed command is counted and logged, and leaves every
|
|
202
|
+
* room's existing flag untouched, so a transient failure never flips a room's safe-default `undefined`/`false`
|
|
203
|
+
* to a stale `true` it didn't earn. Applies results by walking `this.rooms` fresh *after* the command resolves,
|
|
204
|
+
* not the snapshot queried - a room that left while this was in flight is simply no longer in the map to visit,
|
|
205
|
+
* and one that arrived gets no count back (`undefined`, falling back to the safe default below) and waits for
|
|
206
|
+
* the next poll, with no race to special-case either way. */
|
|
207
|
+
async pollLocalOnly() {
|
|
208
|
+
const publisher = this.publisher;
|
|
209
|
+
const roomIds = [...this.rooms.keys()];
|
|
210
|
+
if (!publisher?.isReady || roomIds.length === 0) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
let counts;
|
|
214
|
+
try {
|
|
215
|
+
counts = await publisher.pubSubNumSub(roomIds.map(redisRelayChannel));
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
this.report("check room subscriber counts", err);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
for (const [roomId, room] of this.rooms) {
|
|
222
|
+
room.localOnly = (counts[redisRelayChannel(roomId)] ?? Number.POSITIVE_INFINITY) <= 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
169
225
|
redisSubscribe(roomId, room) {
|
|
170
226
|
const subscriber = this.subscriber;
|
|
171
227
|
if (!subscriber) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedisRelayBus.js","sourceRoot":"","sources":["../../../src/util/RedisRelayBus.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG/E,qGAAqG;AACrG,MAAM,CAAC,MAAM,0BAA0B,GAAG,kBAAkB,CAAC;AAE7D,gGAAgG;AAChG,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C;sFACsF;AACtF,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAM,CAAC;AAEnD,oDAAoD;AACpD,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC5C,OAAO,0BAA0B,GAAG,MAAM,CAAC;AAC/C,CAAC;
|
|
1
|
+
{"version":3,"file":"RedisRelayBus.js","sourceRoot":"","sources":["../../../src/util/RedisRelayBus.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,9 +15,16 @@ export const RELAY_MAX_PAYLOAD_BYTES = 16 * 1024;
|
|
|
15
15
|
export const RELAY_LARGE_PAYLOAD_BYTES = 64 * 1024;
|
|
16
16
|
/** How many bytes the framework may hold unsent for one relay socket before it drops further messages to it, when the
|
|
17
17
|
* installed `@rapidrest/service-core` lets a route set that (its default, 64 KiB, is smaller than one key frame's worth
|
|
18
|
-
* of headroom and made a briefly slow receiver lose frames constantly).
|
|
19
|
-
*
|
|
20
|
-
|
|
18
|
+
* of headroom and made a briefly slow receiver lose frames constantly). 128 KiB is roughly 2.7 seconds of
|
|
19
|
+
* `VideoSender.VIDEO_BITRATE` (350 kbps) plus `AudioSender.AUDIO_BITRATE` (24 kbps) combined - (350_000 + 24_000) / 8
|
|
20
|
+
* bytes/s ≈ 46.75 KB/s - comfortably more than one key frame but not so much that a receiver whose downlink is
|
|
21
|
+
* briefly slower than the stream is left playing several seconds stale: this was previously 1 MiB (≈22 seconds at
|
|
22
|
+
* this bitrate, not the "roughly two seconds" the comment claimed - a bits-vs-bytes mix-up), which let a receiver's
|
|
23
|
+
* backlog grow essentially without bound before anything was ever dropped, rather than the "briefly slow" case this
|
|
24
|
+
* budget is meant to cover. `apps/shared/relay/`'s own senders can't import this constant (`tsconfig.apps.json`
|
|
25
|
+
* builds `apps/` as its own program - see its own doc comments), so this is a cross-reference, not a shared import:
|
|
26
|
+
* changing either bitrate should revisit this value too. */
|
|
27
|
+
export const RELAY_WS_MAX_BACKPRESSURE_BYTES = 128 * 1024;
|
|
21
28
|
/** The most sockets (registered or still awaiting their `hello`) one room holds at once. */
|
|
22
29
|
export const RELAY_MAX_SOCKETS_PER_ROOM = 16;
|
|
23
30
|
/** The most sockets one authenticated uid may hold in one room at once (several tabs or devices of one account). */
|