@rapidmx/meet-plugin 0.14.1 → 0.16.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/README.md +35 -55
- package/apps/meet/[token].tsx +341 -341
- package/apps/meet/_CallControls.tsx +508 -463
- package/apps/meet/_CallView.tsx +1060 -891
- package/apps/meet/_ChatPanel.tsx +194 -0
- package/apps/meet/_ColorWheel.tsx +136 -0
- package/apps/meet/_DiagnosticsPanel.tsx +65 -7
- package/apps/meet/_MeetLobby.tsx +224 -224
- package/apps/meet/_ParticipantTile.tsx +243 -169
- package/apps/meet/_ParticipantsDrawer.tsx +10 -7
- package/apps/meet/_Whiteboard.tsx +577 -0
- package/apps/meet/_icons.tsx +135 -80
- package/apps/meet/_meetApi.ts +213 -212
- package/apps/shared/collab/CollabSession.ts +675 -0
- package/apps/shared/collab/WhiteboardModel.ts +216 -0
- package/apps/shared/collab/chatMarkdown.tsx +375 -0
- package/apps/shared/collab/color.ts +67 -0
- package/apps/shared/collab/protocol.ts +321 -0
- package/apps/shared/collab/whiteboardRender.ts +311 -0
- package/apps/shared/media/filters/VideoFilterProcessor.ts +408 -408
- package/apps/shared/media/filters/mlModels.ts +174 -174
- package/apps/shared/relay/AudioPlayer.ts +29 -0
- package/apps/shared/relay/AudioSender.ts +23 -0
- package/apps/shared/relay/RelayClient.ts +54 -2
- package/apps/shared/relay/RelayReceiver.ts +43 -2
- package/apps/shared/relay/RelaySender.ts +23 -1
- package/apps/shared/relay/RelayTransport.ts +21 -1
- package/apps/shared/relay/SilenceMeter.ts +59 -0
- package/apps/shared/relay/relayEnv.ts +1 -1
- package/apps/shared/signaling/SignalingClient.ts +335 -0
- package/apps/shared/webrtc/MeshConnectionManager.ts +971 -949
- package/apps/shared/webrtc/types.ts +335 -247
- package/dist/apps/meet/[token].js +1 -1
- package/dist/apps/meet/_CallControls.d.ts +21 -6
- package/dist/apps/meet/_CallControls.js +14 -8
- package/dist/apps/meet/_CallView.d.ts +36 -7
- package/dist/apps/meet/_CallView.js +132 -16
- package/dist/apps/meet/_ChatPanel.d.ts +24 -0
- package/dist/apps/meet/_ChatPanel.js +116 -0
- package/dist/apps/meet/_ColorWheel.d.ts +14 -0
- package/dist/apps/meet/_ColorWheel.js +82 -0
- package/dist/apps/meet/_DiagnosticsPanel.d.ts +5 -3
- package/dist/apps/meet/_DiagnosticsPanel.js +17 -1
- package/dist/apps/meet/_ParticipantTile.d.ts +11 -3
- package/dist/apps/meet/_ParticipantTile.js +36 -5
- package/dist/apps/meet/_ParticipantsDrawer.js +3 -3
- package/dist/apps/meet/_Whiteboard.d.ts +64 -0
- package/dist/apps/meet/_Whiteboard.js +312 -0
- package/dist/apps/meet/_icons.d.ts +16 -0
- package/dist/apps/meet/_icons.js +16 -0
- package/dist/apps/meet/_meetApi.d.ts +8 -7
- package/dist/apps/meet/_meetApi.js +2 -2
- package/dist/apps/shared/collab/CollabSession.d.ts +208 -0
- package/dist/apps/shared/collab/CollabSession.js +559 -0
- package/dist/apps/shared/collab/WhiteboardModel.d.ts +54 -0
- package/dist/apps/shared/collab/WhiteboardModel.js +197 -0
- package/dist/apps/shared/collab/chatMarkdown.d.ts +65 -0
- package/dist/apps/shared/collab/chatMarkdown.js +311 -0
- package/dist/apps/shared/collab/color.d.ts +18 -0
- package/dist/apps/shared/collab/color.js +59 -0
- package/dist/apps/shared/collab/protocol.d.ts +157 -0
- package/dist/apps/shared/collab/protocol.js +222 -0
- package/dist/apps/shared/collab/whiteboardRender.d.ts +31 -0
- package/dist/apps/shared/collab/whiteboardRender.js +268 -0
- package/dist/apps/shared/relay/AudioPlayer.d.ts +9 -0
- package/dist/apps/shared/relay/AudioPlayer.js +24 -4
- package/dist/apps/shared/relay/AudioSender.d.ts +8 -0
- package/dist/apps/shared/relay/AudioSender.js +20 -4
- package/dist/apps/shared/relay/RelayClient.d.ts +16 -0
- package/dist/apps/shared/relay/RelayClient.js +46 -2
- package/dist/apps/shared/relay/RelayReceiver.d.ts +5 -0
- package/dist/apps/shared/relay/RelayReceiver.js +38 -6
- package/dist/apps/shared/relay/RelaySender.d.ts +7 -0
- package/dist/apps/shared/relay/RelaySender.js +20 -5
- package/dist/apps/shared/relay/RelayTransport.d.ts +6 -1
- package/dist/apps/shared/relay/RelayTransport.js +16 -1
- package/dist/apps/shared/relay/SilenceMeter.d.ts +25 -0
- package/dist/apps/shared/relay/SilenceMeter.js +55 -0
- package/dist/apps/shared/relay/relayEnv.d.ts +1 -1
- package/dist/apps/shared/signaling/SignalingClient.d.ts +95 -0
- package/dist/apps/shared/signaling/SignalingClient.js +278 -0
- package/dist/apps/shared/webrtc/MeshConnectionManager.d.ts +7 -7
- package/dist/apps/shared/webrtc/MeshConnectionManager.js +28 -9
- package/dist/apps/shared/webrtc/types.d.ts +104 -26
- package/dist/apps/shared/webrtc/types.js +3 -4
- package/dist/lib/routes/BaseVideoMeetingRoute.js +171 -69
- package/dist/lib/routes/BaseVideoMeetingRoute.js.map +1 -1
- package/dist/lib/util/RelayHub.js +15 -2
- package/dist/lib/util/RelayHub.js.map +1 -1
- package/dist/lib/util/SignalingHub.js +339 -0
- package/dist/lib/util/SignalingHub.js.map +1 -0
- package/dist/types/routes/BaseVideoMeetingRoute.d.ts +68 -46
- package/dist/types/util/RelayHub.d.ts +5 -2
- package/dist/types/util/SignalingHub.d.ts +112 -0
- package/package.json +208 -208
- package/apps/shared/push/GuestSignalingClient.ts +0 -359
- package/dist/apps/shared/push/GuestSignalingClient.d.ts +0 -98
- package/dist/apps/shared/push/GuestSignalingClient.js +0 -312
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
/**
|
|
6
6
|
* Full-mesh WebRTC connection management: one direct `RTCPeerConnection` per other participant, signaled entirely
|
|
7
|
-
* over the meeting's own
|
|
7
|
+
* over the meeting's own signaling channel (`SignalingChannel` - see `types.ts`) as this plugin's Phase 2
|
|
8
8
|
* `.claude/NOTES.md` entry lays out. Pure state machine, no JSX/DOM rendering - `apps/meet/_CallView.tsx` owns one
|
|
9
9
|
* instance per call and mirrors its events into React state.
|
|
10
10
|
*
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
*
|
|
32
32
|
* ## Join announcement and roster discovery
|
|
33
33
|
*
|
|
34
|
-
* The channel has no history (
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* subscribed *and* who joined before them - never the other way around. Every participant who learns of a
|
|
34
|
+
* The channel has no history (the server delivers a message only to the sockets connected at that moment, and never
|
|
35
|
+
* replays one to a socket that connects later - see the backend's `SignalingHub`), so a newcomer's `hello` would only
|
|
36
|
+
* reach participants who happened to already be connected *and* who joined before them - never the other way around. Every participant who learns of a
|
|
38
37
|
* genuinely new peer (from any message, not just `hello`) therefore echoes its own `hello` right back, once, the
|
|
39
38
|
* first time it becomes aware of that peer - so within one extra round trip everyone converges on the same full
|
|
40
39
|
* roster regardless of join order. This is naturally bounded (each participant echoes at most once per peer it
|
|
41
40
|
* ever discovers) rather than a risk of runaway flooding.
|
|
42
41
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* Nothing in `SignalingChannel` itself promises that messages arrive in the order they were sent (one socket keeps
|
|
43
|
+
* one sender's messages in order today, but the transport this replaced sent each message as its own `POST`, and
|
|
44
|
+
* this manager does not depend on which transport it has). Two consequences are handled here: an `offer` that beats its sender's `hello` creates the peer under a placeholder name that the
|
|
45
45
|
* `hello` then replaces (`participant-updated`), and an ICE candidate that beats its offer is held until the peer
|
|
46
46
|
* exists rather than dropped.
|
|
47
47
|
*
|
|
@@ -558,7 +558,11 @@ export class MeshConnectionManager {
|
|
|
558
558
|
async pollDiagnostics(peer) {
|
|
559
559
|
let diagnostics;
|
|
560
560
|
try {
|
|
561
|
-
|
|
561
|
+
const relay = this.options.relay;
|
|
562
|
+
diagnostics =
|
|
563
|
+
peer.transport === "websocket" && relay?.diagnostics
|
|
564
|
+
? fromRelayDiagnostics(relay.diagnostics(peer.uid))
|
|
565
|
+
: await peer.pc.collectDiagnostics();
|
|
562
566
|
}
|
|
563
567
|
catch {
|
|
564
568
|
// Leaves whatever was last polled in place rather than blanking a momentary failure.
|
|
@@ -636,6 +640,9 @@ export class MeshConnectionManager {
|
|
|
636
640
|
});
|
|
637
641
|
this.syncRelaySending();
|
|
638
642
|
this.emit({ type: "participant-updated", participant: toParticipant(peer) });
|
|
643
|
+
if (relay.diagnostics) {
|
|
644
|
+
this.startDiagnosticsPolling(peer);
|
|
645
|
+
}
|
|
639
646
|
if (notify) {
|
|
640
647
|
this.send({ kind: "relay-fallback", to: peer.uid });
|
|
641
648
|
}
|
|
@@ -739,7 +746,7 @@ export class MeshConnectionManager {
|
|
|
739
746
|
}
|
|
740
747
|
const peer = this.peers.get(message.from);
|
|
741
748
|
if (!peer) {
|
|
742
|
-
// A candidate that raced ahead of its own offer (
|
|
749
|
+
// A candidate that raced ahead of its own offer (see this module's doc comment on ordering) - hold it for
|
|
743
750
|
// the peer to claim when it is created. Bounded, see `MAX_ORPHAN_PEERS`.
|
|
744
751
|
const held = this.orphanCandidates.get(message.from);
|
|
745
752
|
if (held) {
|
|
@@ -806,6 +813,18 @@ function toParticipant(peer) {
|
|
|
806
813
|
diagnostics: peer.diagnostics,
|
|
807
814
|
};
|
|
808
815
|
}
|
|
816
|
+
/** A relayed pair's diagnostics: the relay's own counters, with the part that maps onto RTP stats (round trip, frames
|
|
817
|
+
* lost, bytes each way) also filled into the ordinary fields, so the panel's ordinary rows show it too. The round trip
|
|
818
|
+
* is to the relay server, not to the peer - see `RelayDiagnostics.roundTripMs`. */
|
|
819
|
+
function fromRelayDiagnostics(relay) {
|
|
820
|
+
const { send, receive } = relay;
|
|
821
|
+
return {
|
|
822
|
+
roundTripTimeSeconds: relay.roundTripMs === undefined ? undefined : relay.roundTripMs / 1000,
|
|
823
|
+
audio: { packetsLost: receive?.audio.framesLost, bytesSent: send.audio.bytesSent, bytesReceived: receive?.audio.bytesReceived },
|
|
824
|
+
video: { packetsLost: receive?.video.framesLost, bytesSent: send.video.bytesSent, bytesReceived: receive?.video.bytesReceived },
|
|
825
|
+
relay,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
809
828
|
function sameState(a, b) {
|
|
810
829
|
return a.audioOn === b.audioOn && a.videoOn === b.videoOn && a.handRaised === b.handRaised;
|
|
811
830
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/** Shared types for `MeshConnectionManager.ts` and its signaling transport (`apps/shared/
|
|
2
|
-
* kept in their own module so a test can depend on the shapes alone. `
|
|
3
|
-
*
|
|
4
|
-
* its own doc comment. */
|
|
1
|
+
/** Shared types for `MeshConnectionManager.ts` and its signaling transport (`apps/shared/signaling/SignalingClient.ts`) -
|
|
2
|
+
* kept in their own module so a test can depend on the shapes alone. `SignalingClient` serves both a guest and an
|
|
3
|
+
* already-authenticated real caller (`VideoMeetingJoinResult.authenticated`) - see its own doc comment. */
|
|
5
4
|
/** What a participant is sending, and whether their hand is up. Announced in `hello` and re-sent as a `state`
|
|
6
5
|
* message on every change, so a tile can show a muted microphone, an avatar for a camera that is off, and a raised
|
|
7
6
|
* hand without inspecting the media itself (the far end of a `replaceTrack(null)` gives no dependable signal that
|
|
@@ -29,16 +28,19 @@ export declare const REACTION_EMOJIS: readonly ["👍", "👏", "❤️", "🎉"
|
|
|
29
28
|
* every other participant has chosen for their own tab. */
|
|
30
29
|
export type TransportMode = "auto" | "p2p" | "relay" | "websocket";
|
|
31
30
|
/**
|
|
32
|
-
* One WebRTC signaling message,
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* One WebRTC signaling message, sent as the `data` of a `{"op":"signal"}` frame over the meeting's own signaling
|
|
32
|
+
* WebSocket (`apps/shared/signaling/SignalingClient.ts`, served by `BaseVideoMeetingRoute.signal()` and the backend's
|
|
33
|
+
* `SignalingHub`) and delivered to the other participants the same way. `type` is a fixed literal so a listener can
|
|
34
|
+
* cheaply recognize and skip anything that isn't one of these.
|
|
35
|
+
*
|
|
36
|
+
* `chat`/`whiteboard` (see `../collab/CollabSession.ts`) are broadcast like `reaction`.
|
|
37
37
|
*
|
|
38
38
|
* `hello`/`bye`/`presenter-claim`/`presenter-release`/`state`/`reaction`/`talking-stick` are broadcast (`to` left
|
|
39
|
-
* unset - every participant
|
|
40
|
-
* point-to-point (`to` is the intended peer's
|
|
41
|
-
*
|
|
39
|
+
* unset - every other participant receives it); `offer`/`answer`/`ice-candidate`/`mute-request`/`kicked` are
|
|
40
|
+
* point-to-point (`to` is the intended peer's id) - the server delivers one only to the socket of that peer, so
|
|
41
|
+
* nobody else ever sees it. The sender never receives its own messages back either. (A recipient still ignores a
|
|
42
|
+
* message whose `to` isn't itself, and its own messages - harmless with this transport, and kept so
|
|
43
|
+
* `MeshConnectionManager` does not depend on how its transport routes.)
|
|
42
44
|
*
|
|
43
45
|
* `mute-request`/`kicked`/`talking-stick` are host moderation signals (see `MeshConnectionManager`'s doc comment) -
|
|
44
46
|
* purely cooperative, like every other message here: the recipient's own client decides whether to honor one, and
|
|
@@ -56,16 +58,17 @@ export type TransportMode = "auto" | "p2p" | "relay" | "websocket";
|
|
|
56
58
|
*/
|
|
57
59
|
export interface SignalMessage {
|
|
58
60
|
type: "video-meeting-signal";
|
|
59
|
-
kind: "hello" | "bye" | "offer" | "answer" | "ice-candidate" | "presenter-claim" | "presenter-release" | "state" | "reaction" | "relay-fallback" | "mute-request" | "kicked" | "talking-stick" | "restart-connection";
|
|
61
|
+
kind: "hello" | "bye" | "offer" | "answer" | "ice-candidate" | "presenter-claim" | "presenter-release" | "state" | "reaction" | "relay-fallback" | "mute-request" | "kicked" | "talking-stick" | "restart-connection" | "chat" | "whiteboard" | "collab-sync-request" | "collab-sync" | "whiteboard-cursor";
|
|
60
62
|
/** The sender's authenticated uid - the real caller's own uid when `join()` returned `authenticated: true`, else
|
|
61
|
-
* the `guest:<random>` uid `BaseVideoMeetingRoute.join()` minted. The server
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* the `guest:<random>` uid `BaseVideoMeetingRoute.join()` minted. The server overwrites whatever the sender put
|
|
64
|
+
* here with the uid its signaling socket authenticated as (it stops one participant speaking as another), so a
|
|
65
|
+
* received message's `from` is always that uid. */
|
|
64
66
|
from: string;
|
|
65
67
|
/** Which of the sender's tabs or devices this is: `<from>~<random>`. The same account joining from two devices is
|
|
66
|
-
* two participants, not one that ignores its own messages.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
68
|
+
* two participants, not one that ignores its own messages. The server overwrites it with the peer id the sender's
|
|
69
|
+
* socket announced in its `hello` (which must be `<from>` or start with `<from>~`), so every received message
|
|
70
|
+
* carries one; a received `peer` that is neither is still ignored, so it can't name someone else's tab, and one
|
|
71
|
+
* that is absent identifies the participant by `from`. */
|
|
69
72
|
peer?: string;
|
|
70
73
|
/** Set only on a point-to-point message (`offer`/`answer`/`ice-candidate`/`relay-fallback`). */
|
|
71
74
|
to?: string;
|
|
@@ -84,15 +87,26 @@ export interface SignalMessage {
|
|
|
84
87
|
sdp?: RTCSessionDescriptionInit;
|
|
85
88
|
/** `ice-candidate` only. */
|
|
86
89
|
candidate?: RTCIceCandidateInit;
|
|
90
|
+
/** `chat` only - a `ChatPayload` (`../collab/protocol.ts`), validated by `parseChat()` before use. Typed `unknown`
|
|
91
|
+
* since it is another participant's input until then; the same goes for the next two. */
|
|
92
|
+
chat?: unknown;
|
|
93
|
+
/** `whiteboard` only - a `WhiteboardOp`, validated by `parseWhiteboardOp()`. */
|
|
94
|
+
whiteboard?: unknown;
|
|
95
|
+
/** `collab-sync` only - one `SyncPayload` part of a newcomer's catch-up, validated by `parseSync()`. `collab-sync`
|
|
96
|
+
* and `collab-sync-request` are point-to-point (`to` set) - see `CollabSession`'s doc comment. */
|
|
97
|
+
sync?: unknown;
|
|
98
|
+
/** `whiteboard-cursor` only - a `CursorPayload`, validated by `parseCursor()`; broadcast, never stored. */
|
|
99
|
+
cursor?: unknown;
|
|
87
100
|
}
|
|
88
101
|
/** What `MeshConnectionManager` needs from a signaling transport - implemented by
|
|
89
|
-
* `apps/shared/
|
|
90
|
-
* tests (see `test/webrtc/MeshConnectionManager.test.ts`), which never opens a real network connection. */
|
|
102
|
+
* `apps/shared/signaling/SignalingClient.ts` for a real call, and by an in-memory fake for `MeshConnectionManager`'s own
|
|
103
|
+
* tests (see `test/apps/shared/webrtc/MeshConnectionManager.test.ts`), which never opens a real network connection. */
|
|
91
104
|
export interface SignalingChannel {
|
|
92
105
|
send(message: SignalMessage): void;
|
|
93
|
-
/** Calls `handler` with every message received on the channel
|
|
94
|
-
*
|
|
95
|
-
* rather than relying on the transport to). Returns the function
|
|
106
|
+
/** Calls `handler` with every message received on the channel. `SignalingClient` never delivers this client's own
|
|
107
|
+
* sends, nor a `to`-addressed message meant for another peer, but `MeshConnectionManager` filters both out itself
|
|
108
|
+
* rather than relying on the transport to (its tests' fake echoes everything to everyone). Returns the function
|
|
109
|
+
* that stops listening. */
|
|
96
110
|
onMessage(handler: (message: SignalMessage) => void): () => void;
|
|
97
111
|
}
|
|
98
112
|
/** One kind's RTP stream stats, as of one sample - cumulative counters (`bytesSent`/`bytesReceived`), not a rate,
|
|
@@ -114,6 +128,66 @@ export interface ConnectionDiagnostics {
|
|
|
114
128
|
roundTripTimeSeconds?: number;
|
|
115
129
|
audio: RtpStreamDiagnostics;
|
|
116
130
|
video: RtpStreamDiagnostics;
|
|
131
|
+
/** The WebSocket relay's own counters, present only for a `"websocket"` pair - `audio`/`video` then carry the
|
|
132
|
+
* subset of them that maps onto RTP stats (frames lost, bytes each way). */
|
|
133
|
+
relay?: RelayDiagnostics;
|
|
134
|
+
}
|
|
135
|
+
/** What the relay's sending half did with one kind of frame, cumulative since the relay transport was created. */
|
|
136
|
+
export interface RelaySendStats {
|
|
137
|
+
framesSent: number;
|
|
138
|
+
/** Frames encoded but not sent: the socket was not ready, backed up, or refused a fragment. */
|
|
139
|
+
framesDropped: number;
|
|
140
|
+
bytesSent: number;
|
|
141
|
+
}
|
|
142
|
+
/** What the relay's sending half has done, cumulative since the relay transport was created. It is per browser, not
|
|
143
|
+
* per peer: every relayed peer receives the same stream. */
|
|
144
|
+
export interface RelaySendDiagnostics {
|
|
145
|
+
/** Milliseconds of microphone audio the capture path delivered, against `audioCaptureWallMs` - the wall-clock time
|
|
146
|
+
* it was capturing for. Well short of it means capture is losing whole blocks. */
|
|
147
|
+
audioCapturedMs: number;
|
|
148
|
+
audioCaptureWallMs: number;
|
|
149
|
+
/** How much of `audioCapturedMs` was digital silence (see `SilenceMeter`) - a live microphone always has some
|
|
150
|
+
* noise, so a large share means the capture path is handing the encoder zero-filled blocks. */
|
|
151
|
+
audioSilentMs: number;
|
|
152
|
+
/** Milliseconds of captured audio not encoded because the encoder was too far behind. */
|
|
153
|
+
audioEncoderSkippedMs: number;
|
|
154
|
+
audio: RelaySendStats;
|
|
155
|
+
video: RelaySendStats;
|
|
156
|
+
}
|
|
157
|
+
/** What the relay's receiving half got of one kind of frame from one peer, cumulative. */
|
|
158
|
+
export interface RelayReceiveStats {
|
|
159
|
+
framesReceived: number;
|
|
160
|
+
/** Frames the sequence numbers say never arrived complete - dropped by the sender, the server or in transit. */
|
|
161
|
+
framesLost: number;
|
|
162
|
+
bytesReceived: number;
|
|
163
|
+
}
|
|
164
|
+
/** What the relay's receiving half has done with one peer's media, cumulative since it started receiving from them. */
|
|
165
|
+
export interface RelayReceiveDiagnostics {
|
|
166
|
+
audio: RelayReceiveStats & {
|
|
167
|
+
/** Milliseconds of decoded audio scheduled for playback. */
|
|
168
|
+
playedMs: number;
|
|
169
|
+
/** How much of `playedMs` was digital silence - silence that arrived inside the packets, i.e. the sender sent
|
|
170
|
+
* it, as opposed to `gapMs`. */
|
|
171
|
+
silentMs: number;
|
|
172
|
+
/** Times playback ran dry and restarted, and the total silence that inserted. */
|
|
173
|
+
gaps: number;
|
|
174
|
+
gapMs: number;
|
|
175
|
+
/** Packets dropped on arrival because the decoder was backed up or playback was already too far ahead. */
|
|
176
|
+
droppedLate: number;
|
|
177
|
+
/** How far ahead of the clock audio is scheduled right now - the receive buffer's current depth. */
|
|
178
|
+
bufferedMs: number;
|
|
179
|
+
};
|
|
180
|
+
video: RelayReceiveStats;
|
|
181
|
+
}
|
|
182
|
+
/** One sample of the relay's counters for one peer. */
|
|
183
|
+
export interface RelayDiagnostics {
|
|
184
|
+
/** The latest round trip from this browser to the relay server, in milliseconds - not to the peer: the relay has
|
|
185
|
+
* no direct path between the two, and each end only knows its own leg. `undefined` before the first pong, or for
|
|
186
|
+
* a server too old to answer pings. */
|
|
187
|
+
roundTripMs?: number;
|
|
188
|
+
send: RelaySendDiagnostics;
|
|
189
|
+
/** `undefined` when nothing is being received from this peer. */
|
|
190
|
+
receive?: RelayReceiveDiagnostics;
|
|
117
191
|
}
|
|
118
192
|
/** The subset of `RTCRtpSender` `MeshConnectionManager.setLocalTrack()` uses. Every connection carries one audio and
|
|
119
193
|
* one video sender for its whole life (see `MeshConnectionManager`'s doc comment), so turning a camera on or off,
|
|
@@ -183,6 +257,9 @@ export interface RelayTransportLike {
|
|
|
183
257
|
setSending(active: boolean): void;
|
|
184
258
|
/** Same as `MeshConnectionManager.setLocalTrack()` - `null` sends nothing for that kind. */
|
|
185
259
|
setLocalTrack(kind: "audio" | "video", track: MediaStreamTrack | null): void;
|
|
260
|
+
/** The relay's counters as they stand for `peerId`, for the diagnostics panel. Optional so a test's fake relay
|
|
261
|
+
* needn't provide it; the mesh then shows nothing for a relayed pair. */
|
|
262
|
+
diagnostics?(peerId: string): RelayDiagnostics;
|
|
186
263
|
close(): void;
|
|
187
264
|
}
|
|
188
265
|
/** `iceTransportPolicy` mirrors the real `RTCConfiguration` field of the same name - `"relay"` forces ICE to use
|
|
@@ -200,8 +277,9 @@ export interface MeshParticipant extends ParticipantState {
|
|
|
200
277
|
/** How this participant's media currently reaches the local one - see `MediaTransport`. */
|
|
201
278
|
transport: MediaTransport;
|
|
202
279
|
/** This connection's most recently polled quality stats, for the diagnostics panel - `undefined` until the
|
|
203
|
-
* first poll completes (shortly after `transport` first leaves `"connecting"
|
|
204
|
-
* a `"websocket"`-relayed participant
|
|
280
|
+
* first poll completes (shortly after `transport` first leaves `"connecting"`, or the pair moves to the relay).
|
|
281
|
+
* For a `"websocket"`-relayed participant they come from the relay's own counters (`relay`) rather than an
|
|
282
|
+
* `RTCPeerConnection`. */
|
|
205
283
|
diagnostics?: ConnectionDiagnostics;
|
|
206
284
|
}
|
|
207
285
|
export type MeshEvent = {
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
/** Shared types for `MeshConnectionManager.ts` and its signaling transport (`apps/shared/
|
|
6
|
-
* kept in their own module so a test can depend on the shapes alone. `
|
|
7
|
-
*
|
|
8
|
-
* its own doc comment. */
|
|
5
|
+
/** Shared types for `MeshConnectionManager.ts` and its signaling transport (`apps/shared/signaling/SignalingClient.ts`) -
|
|
6
|
+
* kept in their own module so a test can depend on the shapes alone. `SignalingClient` serves both a guest and an
|
|
7
|
+
* already-authenticated real caller (`VideoMeetingJoinResult.authenticated`) - see its own doc comment. */
|
|
9
8
|
/** The emoji a participant can send during a call. A received `reaction` outside this list is ignored, so an
|
|
10
9
|
* arbitrary string from another participant is never rendered. */
|
|
11
10
|
export const REACTION_EMOJIS = ["👍", "👏", "❤️", "🎉", "😂", "😮", "😢", "👋"];
|