@rapidmx/meet-plugin 0.13.1 → 0.14.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.
Files changed (41) hide show
  1. package/README.md +55 -55
  2. package/apps/meet/[token].tsx +341 -341
  3. package/apps/meet/_CallControls.tsx +366 -394
  4. package/apps/meet/_CallView.tsx +987 -848
  5. package/apps/meet/_DiagnosticsWindow.tsx +80 -0
  6. package/apps/meet/_MeetLobby.tsx +224 -224
  7. package/apps/meet/_ParticipantTile.tsx +169 -169
  8. package/apps/meet/_ParticipantsDrawer.tsx +10 -104
  9. package/apps/meet/_SettingsModal.tsx +152 -0
  10. package/apps/meet/_icons.tsx +80 -82
  11. package/apps/meet/_meetApi.ts +212 -212
  12. package/apps/shared/media/filters/VideoFilterProcessor.ts +408 -408
  13. package/apps/shared/media/filters/mlModels.ts +174 -174
  14. package/apps/shared/push/GuestSignalingClient.ts +359 -359
  15. package/apps/shared/relay/RelayClient.ts +7 -2
  16. package/apps/shared/webrtc/MeshConnectionManager.ts +949 -795
  17. package/apps/shared/webrtc/types.ts +247 -222
  18. package/dist/apps/meet/_CallControls.d.ts +3 -8
  19. package/dist/apps/meet/_CallControls.js +3 -10
  20. package/dist/apps/meet/_CallView.d.ts +15 -2
  21. package/dist/apps/meet/_CallView.js +70 -4
  22. package/dist/apps/meet/_DiagnosticsWindow.d.ts +25 -0
  23. package/dist/apps/meet/_DiagnosticsWindow.js +45 -0
  24. package/dist/apps/meet/_ParticipantsDrawer.d.ts +10 -21
  25. package/dist/apps/meet/_ParticipantsDrawer.js +9 -39
  26. package/dist/apps/meet/_SettingsModal.d.ts +28 -0
  27. package/dist/apps/meet/_SettingsModal.js +59 -0
  28. package/dist/apps/meet/_icons.d.ts +1 -1
  29. package/dist/apps/meet/_icons.js +1 -1
  30. package/dist/apps/shared/relay/RelayClient.d.ts +6 -1
  31. package/dist/apps/shared/relay/RelayClient.js +7 -2
  32. package/dist/apps/shared/webrtc/MeshConnectionManager.d.ts +55 -2
  33. package/dist/apps/shared/webrtc/MeshConnectionManager.js +153 -9
  34. package/dist/apps/shared/webrtc/types.d.ts +25 -1
  35. package/dist/lib/util/RedisRelayBus.js +61 -5
  36. package/dist/lib/util/RedisRelayBus.js.map +1 -1
  37. package/dist/lib/util/RelayHub.js +10 -3
  38. package/dist/lib/util/RelayHub.js.map +1 -1
  39. package/dist/types/util/RedisRelayBus.d.ts +45 -6
  40. package/dist/types/util/RelayHub.d.ts +9 -2
  41. package/package.json +208 -208
@@ -9,7 +9,12 @@ export declare const MAX_NEGOTIATED_MESSAGE_BYTES: number;
9
9
  /** The most senders the server lets one socket `want`. */
10
10
  export declare const MAX_WANTED_PEERS = 32;
11
11
  /** A media send is dropped, not queued, while more than this many bytes are still waiting to leave - the
12
- * connection is slower than the media, and queueing would only add latency to the frames that are still fresh. */
12
+ * connection is slower than the media, and queueing would only add latency to the frames that are still fresh.
13
+ * 64 KiB is a little under 1.4 seconds of `VideoSender.VIDEO_BITRATE` (350 kbps) plus `AudioSender.AUDIO_BITRATE`
14
+ * (24 kbps) combined (≈46.75 KB/s) - tighter than the server's own `RelayHub.RELAY_WS_MAX_BACKPRESSURE_BYTES`
15
+ * (128 KiB) on purpose, so a sender that is itself the slow end of the connection starts dropping its own stale
16
+ * frames before the server's larger budget would. This was previously 256 KiB (≈5.6 seconds), which defeated the
17
+ * "drop, don't queue" intent on exactly the constrained uplink that routes a call to this fallback tier at all. */
13
18
  export declare const MAX_BUFFERED_BYTES: number;
14
19
  /** Reconnect delay: about a second after the first failure, doubling to a ceiling, jittered so a server restart
15
20
  * doesn't bring every client back in the same instant. */
@@ -32,8 +32,13 @@ export const MAX_NEGOTIATED_MESSAGE_BYTES = 1024 * 1024;
32
32
  /** The most senders the server lets one socket `want`. */
33
33
  export const MAX_WANTED_PEERS = 32;
34
34
  /** A media send is dropped, not queued, while more than this many bytes are still waiting to leave - the
35
- * connection is slower than the media, and queueing would only add latency to the frames that are still fresh. */
36
- export const MAX_BUFFERED_BYTES = 256 * 1024;
35
+ * connection is slower than the media, and queueing would only add latency to the frames that are still fresh.
36
+ * 64 KiB is a little under 1.4 seconds of `VideoSender.VIDEO_BITRATE` (350 kbps) plus `AudioSender.AUDIO_BITRATE`
37
+ * (24 kbps) combined (≈46.75 KB/s) - tighter than the server's own `RelayHub.RELAY_WS_MAX_BACKPRESSURE_BYTES`
38
+ * (128 KiB) on purpose, so a sender that is itself the slow end of the connection starts dropping its own stale
39
+ * frames before the server's larger budget would. This was previously 256 KiB (≈5.6 seconds), which defeated the
40
+ * "drop, don't queue" intent on exactly the constrained uplink that routes a call to this fallback tier at all. */
41
+ export const MAX_BUFFERED_BYTES = 64 * 1024;
37
42
  /** Reconnect delay: about a second after the first failure, doubling to a ceiling, jittered so a server restart
38
43
  * doesn't bring every client back in the same instant. */
39
44
  export const RELAY_BACKOFF_BASE_MS = 1000;
@@ -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({ iceServers: this.options.iceServers });
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.fallBack(peer), this.options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS);
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.fallBack(peer), this.options.disconnectedGraceMs ?? DEFAULT_DISCONNECTED_GRACE_MS));
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.fallBack(peer);
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) and
114
- * stops publishing. Local delivery keeps working, as after a failed `connect()`. Safe to call repeatedly.
115
- * Nothing is unsubscribed one by one: the connection going away does that server-side, and an UNSUBSCRIBE could
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":"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"}
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). One MiB is roughly two seconds of a 350 kbps
19
- * video stream plus audio. */
20
- export const RELAY_WS_MAX_BACKPRESSURE_BYTES = 1024 * 1024;
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). */