wireweave 0.3.46 → 0.3.48
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 +25 -0
- package/package.json +1 -1
- package/src/relay-pool.js +23 -3
package/README.md
CHANGED
|
@@ -151,6 +151,31 @@ voice.addEventListener('participants', e => console.log(e.detail.list));
|
|
|
151
151
|
|
|
152
152
|
Voice carries every empirically-discovered reliability pattern: perfect negotiation (RFC 8840), ICE restart on disconnect, track-stall detection, SFU hub election (mesh→star at 3+ peers), exponential-backoff reconnect.
|
|
153
153
|
|
|
154
|
+
### voice quality/input settings
|
|
155
|
+
|
|
156
|
+
`VoiceSession` (and `ensureVoice`) accepts real, constructor-configurable options for input mode, mic sensitivity, echo/noise handling, and Opus quality — every one of them threaded into an actual `getUserMedia`/`RTCRtpSender`/SDP call site, not just stored:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const voice = ww.ensureVoice({
|
|
160
|
+
serverId: 'abc:xyz',
|
|
161
|
+
displayName: 'you',
|
|
162
|
+
pttMode: true, // push-to-talk (default) vs. open-mic/VAD mode
|
|
163
|
+
micSensitivity: 0.045, // RMS threshold the speaker-activity detector uses
|
|
164
|
+
noiseSuppression: true, // getUserMedia audio constraints — real MediaTrackConstraints
|
|
165
|
+
echoCancellation: true,
|
|
166
|
+
autoGainControl: true,
|
|
167
|
+
audioQuality: 'high', // Opus bitrate tier: 'low' (16kbps) | 'medium' (32kbps) | 'high' (48kbps) | 'max' (64kbps)
|
|
168
|
+
dtx: true // discontinuous transmission (silence suppression), SDP fmtp usedtx=1
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- **`pttMode`** (push-to-talk vs. voice-activity/open-mic): `true` (default) starts the session muted; the caller opens the mic via `setMuted(false)`/`requestTransmit()`. `false` starts unmuted — the mic stays live and the speaker-activity detector (RMS threshold) drives the `isSpeaking` UI flag instead of gating transmission. Live-togglable: `voice.setPttMode(false)`.
|
|
173
|
+
- **`micSensitivity`** — the RMS level (0–1) above which a stream counts as "speaking" in the speaker-activity poller. Live-togglable: `voice.setMicSensitivity(0.09)`.
|
|
174
|
+
- **`noiseSuppression` / `echoCancellation` / `autoGainControl`** — real [`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) passed straight into the `getUserMedia({ audio: {...} })` call `connect()` makes.
|
|
175
|
+
- **`audioQuality`** — an Opus bitrate ladder tier (`low`/`medium`/`high`/`max`, mapping to 16/32/48/64 kbps) applied via the real `RTCRtpSender.setParameters()` call on every connected peer's audio sender. Live-togglable and re-applies immediately to every open connection: `voice.setAudioQuality('low')`.
|
|
176
|
+
- **`dtx`** — discontinuous transmission (silence suppression), negotiated per the real WebRTC spec via the Opus `a=fmtp` SDP line (`usedtx=1`), not an `RTCRtpEncodingParameters` field (that isn't where DTX lives in the real spec). Applied at every offer/answer/ICE-restart. Live-togglable: `voice.setDtx(false)` (takes effect on the next SDP exchange for already-open peers).
|
|
177
|
+
- **Per-room bandwidth shaping for large rooms** — this module is audio-only (no video sender exists anywhere in it), so real per-layer RTP simulcast (which is video-only in every browser implementation) doesn't apply. The honest, reachable analog is what's already built: SFU hub election (`_sfuElect`/`_sfuRankCandidates`) picks the highest-uplink participant as the hub and fans out audio to everyone via zero-copy `replaceTrack`, so bandwidth concentrates on whoever the room is actually routing through, combined with the per-connection Opus bitrate ladder above.
|
|
178
|
+
|
|
154
179
|
## node usage (relay + auth only)
|
|
155
180
|
|
|
156
181
|
```js
|
package/package.json
CHANGED
package/src/relay-pool.js
CHANGED
|
@@ -64,7 +64,15 @@ class RelayHealth {
|
|
|
64
64
|
this.rank = 50;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
// Recomputes rank on every attempt, not just on a successful signal —
|
|
68
|
+
// otherwise a relay that NEVER once connects (rank frozen at the ctor's
|
|
69
|
+
// neutral 50 forever, since recordConnectLatency/recordSustainedConnection/
|
|
70
|
+
// recordEoseLatency — the only other rank-recomputing call sites — never
|
|
71
|
+
// fire for it) reads as perpetually "average" no matter how many failed
|
|
72
|
+
// attempts pile up, defeating both healthReport() ranking and
|
|
73
|
+
// _maybeRotate's rank-gap rotation trigger for the exact "never connects
|
|
74
|
+
// at all" relay auto-rotation exists to route around.
|
|
75
|
+
recordConnectAttempt() { this.attempts++; this.rank = computeRank(this); }
|
|
68
76
|
|
|
69
77
|
recordConnectLatency(ms) {
|
|
70
78
|
this.connectLatencyMs = ewma(this.connectLatencyMs, ms);
|
|
@@ -250,7 +258,13 @@ export class RelayPool extends EventTarget {
|
|
|
250
258
|
// Swap the worst currently-active relay for the best-ranked unused
|
|
251
259
|
// candidate (fallback pool, or a previously-tried relay we disconnected
|
|
252
260
|
// from) when the gap is large enough to be worth the churn of a new
|
|
253
|
-
// connection. Never rotates below MIN_ACTIVE_RELAYS active URLs.
|
|
261
|
+
// connection. Never rotates below MIN_ACTIVE_RELAYS active URLs. Called
|
|
262
|
+
// from every ws.onclose (both the sustained-then-dropped branch AND the
|
|
263
|
+
// never-connected/failed-fast branch) — a relay that never manages to
|
|
264
|
+
// connect at all still needs evaluating here, since that IS the
|
|
265
|
+
// "consistently unhealthy" case rotation exists to route around, and its
|
|
266
|
+
// rank already reflects the failures via recordConnectAttempt's own
|
|
267
|
+
// computeRank call once attempts >= 2 (the sample floor enforced below).
|
|
254
268
|
_maybeRotate() {
|
|
255
269
|
if (!this.autoRotate || this._closed) return;
|
|
256
270
|
const MIN_ACTIVE_RELAYS = 2;
|
|
@@ -378,11 +392,17 @@ export class RelayPool extends EventTarget {
|
|
|
378
392
|
relay.reconnectDelay = 1000;
|
|
379
393
|
health.recordSustainedConnection();
|
|
380
394
|
this._scheduleSaveHealth();
|
|
381
|
-
this._maybeRotate();
|
|
382
395
|
} else {
|
|
383
396
|
relay.failCount++;
|
|
384
397
|
relay.reconnectDelay = Math.min(relay.reconnectDelay * 2, 30000);
|
|
385
398
|
}
|
|
399
|
+
// Evaluate rotation on EVERY close, not just a sustained-then-dropped
|
|
400
|
+
// connection — a relay that never manages to connect at all (the
|
|
401
|
+
// `else` branch above) is exactly the "consistently unhealthy"
|
|
402
|
+
// relay auto-rotation exists to route around, and its rank already
|
|
403
|
+
// reflects that via computeRank's uptime/latency components once
|
|
404
|
+
// `attempts >= 2` (the sample floor _maybeRotate itself enforces).
|
|
405
|
+
this._maybeRotate();
|
|
386
406
|
relay._openedAt = null;
|
|
387
407
|
if (this._closed) return;
|
|
388
408
|
const t = setTimeout(() => this._open(url), jitter(relay.reconnectDelay));
|