@furious.luke/argus-js 0.5.4 → 0.5.6
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 +75 -7
- package/dist/index.cjs +456 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -5
- package/dist/index.d.ts +154 -5
- package/dist/index.js +456 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ const publisher = new Publisher({
|
|
|
66
66
|
});
|
|
67
67
|
},
|
|
68
68
|
onConnectionStateChange: (state) => console.log("state:", state),
|
|
69
|
+
onConnectionQualityChange: ({ level }) => console.log("connection quality:", level),
|
|
69
70
|
onRecoveryStateChange: (event) => console.log("media recovery:", event),
|
|
70
71
|
// Show a button or prompt. From its click handler, call captureScreen()
|
|
71
72
|
// and then publisher.publish(newStream, "screen").
|
|
@@ -158,7 +159,10 @@ const publisher = new Publisher({
|
|
|
158
159
|
void audio.play();
|
|
159
160
|
},
|
|
160
161
|
onAssistantText({ utteranceId, text }) {
|
|
161
|
-
|
|
162
|
+
appendCaption(utteranceId, text); // APPEND to the bubble keyed by utteranceId
|
|
163
|
+
},
|
|
164
|
+
onAssistantTextFinished({ utteranceId }) {
|
|
165
|
+
finalizeCaption(utteranceId); // this utterance's caption is complete
|
|
162
166
|
},
|
|
163
167
|
onUserTextResult(result) {
|
|
164
168
|
console.log(result.messageId, result.accepted);
|
|
@@ -175,19 +179,32 @@ publisher.sendUserText(crypto.randomUUID(), "Stop and explain that again");
|
|
|
175
179
|
attached but silent between utterances. `sendUserText` is admitted only while
|
|
176
180
|
the customer server owns a live control-token notify subscription.
|
|
177
181
|
|
|
182
|
+
One assistant reply arrives as **many** `onAssistantText` chunks paced to the
|
|
183
|
+
audio, all sharing the **same `utteranceId`**. Group by that id and **append**
|
|
184
|
+
each chunk to the one caption element — creating a new element per callback is
|
|
185
|
+
what scatters a single reply across the UI. Chunks travel a reliable, ordered
|
|
186
|
+
data channel, so appending in arrival order reconstructs the text (there is no
|
|
187
|
+
sequence number). Treat `onAssistantTextFinished` (same `utteranceId`, emitted on
|
|
188
|
+
that same ordered channel after the last chunk) as the completion boundary —
|
|
189
|
+
not the `utterance_finished` lifecycle event, which can overtake the last chunk.
|
|
190
|
+
|
|
178
191
|
### `new Publisher(options)`
|
|
179
192
|
|
|
180
193
|
| Option | Type | Description |
|
|
181
194
|
| --- | --- | --- |
|
|
182
|
-
| `gatewayURLs` | `string[]` | **Required.** The `gateway_urls` from the join response. All are
|
|
195
|
+
| `gatewayURLs` | `string[]` | **Required.** The `gateway_urls` from the join response. All are opened at once; the region whose acknowledgement (`accepted`) returns first is selected on network path, and only that region places the stream. The rest are held as standbys to fail over to. |
|
|
183
196
|
| `token` | `string` | **Required.** The short-lived join token from the join response. |
|
|
184
197
|
| `iceServers` | `RTCIceServer[]` | Optional extra ICE servers (e.g. your own STUN). TURN is supplied automatically by the winning gateway. |
|
|
185
198
|
| `iceTransportPolicy` | `RTCIceTransportPolicy` | Passed to the underlying `RTCPeerConnection`. Defaults to `"all"`. Set `"relay"` to force media through TURN only (verifies the relay path end to end). |
|
|
186
199
|
| `turnTransportPolicy` | `"all" \| "udp" \| "tls"` | Restricts gateway TURN URLs. Defaults to `"all"`; use `"tls"` with relay-only ICE to verify TURN over TLS. Startup fails if the required transport was not advertised. |
|
|
187
|
-
| `gatewayHandshakeTimeoutMs` | `number` | Overall deadline for the
|
|
200
|
+
| `gatewayHandshakeTimeoutMs` | `number` | Overall deadline for the whole gateway race — selection, placement, and any failovers. Unaccepted sockets are replaced after 3 seconds so a blackholed TCP flow cannot consume the full deadline. Defaults to 20 seconds. |
|
|
201
|
+
| `gatewayFailoverTimeoutMs` | `number` | How long the selected region has to return `ready` before the publisher fails over to the next-fastest standby. A socket that closes or errors fails over immediately regardless; this is the backstop for a region that goes silent. Kept generous so a slow-but-good region is not abandoned for one that is merely closer to the control plane. Defaults to 8 seconds, capped at 20. |
|
|
188
202
|
| `peerConnectionTimeoutMs` | `number` | Deadline after the initial offer for WebRTC to reach `connected`. Defaults to 30 seconds. |
|
|
189
203
|
| `signalingReconnectTimeoutMs` | `number` | How long to retry a dropped signaling socket against the selected regional gateway. Defaults to 20 seconds. |
|
|
190
204
|
| `preferredVideoCodecs` | `string[]` | Preferred video codecs, most-preferred first, as RTP MIME types (e.g. `"video/VP9"`, `"video/H264"`). Each published video track offers these ahead of the rest, so the browser sends the first one the media server also accepts. Defaults to `["video/VP9"]`. Pass `[]` to leave the browser's native order untouched. Codecs the browser lacks (or `setCodecPreferences` support, e.g. older Safari) are ignored — negotiation always falls back cleanly. |
|
|
205
|
+
| `connectionStatsIntervalMs` | `number` | How often to poll `RTCPeerConnection.getStats()` for connection-quality assessment while connected. Defaults to 2000. Set to `0` to disable stats polling entirely (`onConnectionStats`/`onConnectionQualityChange` will not fire). |
|
|
206
|
+
| `connectionQualityThresholds` | `Partial<ConnectionQualityThresholds>` | Overrides for the quality classification thresholds. Packet loss is the primary axis; RTT and jitter can only push the level worse, never better. Any omitted field keeps its default (see below). |
|
|
207
|
+
| `connectionQualityDebounceSamples` | `number` | Consecutive worse-than-current samples required before a downgrade is committed, damping transient blips. Improvements are reported on the first better sample. Defaults to 2. |
|
|
191
208
|
| `callbacks` | `PublisherCallbacks` | Optional lifecycle callbacks (see below). |
|
|
192
209
|
|
|
193
210
|
### Methods & properties
|
|
@@ -219,15 +236,19 @@ the customer server owns a live control-token notify subscription.
|
|
|
219
236
|
| `onRecoveryStateChange(event)` | Argus detected stalled media and the publisher started, escalated, completed, or failed automatic recovery. |
|
|
220
237
|
| `onRecoveryRequired(event)` | Automatic recovery could not restore media, or capture ended and the host must ask the user for a new screen share. |
|
|
221
238
|
| `onSpeechTrack(track, streams)` | The inbound `speech` track arrived after `enableSpeech()` — attach it to an `<audio>` element to play text-to-speech. |
|
|
222
|
-
| `onAssistantText({ utteranceId, text })` |
|
|
239
|
+
| `onAssistantText({ utteranceId, text })` | One caption chunk arrived; paced with synthesized speech when speech is enabled, immediate in text-only mode. An utterance emits many chunks sharing one `utteranceId` — append them to the bubble keyed by that id rather than rendering each separately. |
|
|
240
|
+
| `onAssistantTextFinished({ utteranceId })` | The utterance's caption stream is complete. Emitted on the same ordered channel after its last `onAssistantText` chunk; finalize the visible caption here rather than on `utterance_finished`. |
|
|
223
241
|
| `onUserTextResult({ messageId, accepted, reason })` | The server accepted or rejected a `sendUserText` message. |
|
|
242
|
+
| `onConnectionStats(sample)` | A periodic `ConnectionStatsSample` (loss ratio, RTT, jitter, send/available bitrate, encoder limitation) — the raw feed behind `onConnectionQualityChange`. Fires every `connectionStatsIntervalMs` while connected. |
|
|
243
|
+
| `onConnectionQualityChange({ level, sample })` | The derived connection-quality level (`"good" \| "fair" \| "poor" \| "critical"`) changed, including the first assessment after connecting. Downgrades are debounced; upgrades fire immediately. React to a degrading uplink here — warn the user, or drop a secondary track. |
|
|
224
244
|
| `onError(error)` | A fatal error occurred (signaling error, WebRTC connection failure/timeout, or signaling resume timed out). |
|
|
225
245
|
|
|
226
246
|
## How `start()` works
|
|
227
247
|
|
|
228
|
-
1. **Gateway race.** Every URL in `gatewayURLs` is opened at once with the token in the query string.
|
|
229
|
-
2. **
|
|
230
|
-
3. **
|
|
248
|
+
1. **Gateway race.** Every URL in `gatewayURLs` is opened at once with the token in the query string. Selection and placement are two separate steps: the region whose `accepted` returns *first* is selected — purely on network path, without waiting on any placement work — and the browser sends `proceed` to that one only, holding the rest open as standbys. This picks the lowest-latency region without a separate probe, and keeps a region's distance to the control plane out of the choice.
|
|
249
|
+
2. **Placement, with failover.** The selected region does its placement work and returns `ready`. If its socket closes or errors, the browser fails over to the next-fastest standby immediately; if it just goes silent, it fails over after `gatewayFailoverTimeoutMs`. A region that answers `unavailable` (transiently unable to serve) is retried after a backoff rather than counted out. A region that reports the stream is already bound elsewhere sends a `placement_redirect`, and the browser reconnects to the region that holds it — so a mistimed failover self-heals.
|
|
250
|
+
3. **TURN + read token.** The selected gateway's `ready` message carries per-session TURN credentials (merged into the ICE configuration) and the read token exposed as `frameReadToken`.
|
|
251
|
+
4. **WebRTC.** A peer connection and the text data channel are created. Any
|
|
231
252
|
initial media track is added and labelled, then an offer is sent. Remote ICE
|
|
232
253
|
candidates that arrive before the SDP answer are buffered and flushed once
|
|
233
254
|
the answer is applied. Locally gathered candidates remain queued until their
|
|
@@ -272,6 +293,53 @@ pass it to `publish` (or `replaceStream`) for that track type. A browser cannot
|
|
|
272
293
|
silently reacquire a screen share after the user or operating system ends it, so
|
|
273
294
|
`capture_ended` always requires host UI and a fresh `captureScreen()` call.
|
|
274
295
|
|
|
296
|
+
## Connection quality
|
|
297
|
+
|
|
298
|
+
While connected, the publisher polls the peer connection's WebRTC stats every
|
|
299
|
+
`connectionStatsIntervalMs` (default 2s) and derives a coarse quality level so
|
|
300
|
+
you can react to a degrading uplink without parsing `getStats()` yourself. Each
|
|
301
|
+
poll delivers a raw `ConnectionStatsSample` to `onConnectionStats`; whenever the
|
|
302
|
+
derived level changes it delivers a `ConnectionQuality` to
|
|
303
|
+
`onConnectionQualityChange`.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
const pub = new Publisher({
|
|
307
|
+
gatewayURLs,
|
|
308
|
+
token,
|
|
309
|
+
callbacks: {
|
|
310
|
+
onConnectionQualityChange: ({ level, sample }) => {
|
|
311
|
+
if (level === "poor" || level === "critical") {
|
|
312
|
+
showBanner(`Weak connection — ${Math.round(sample.lossRatio * 100)}% packet loss`);
|
|
313
|
+
} else {
|
|
314
|
+
hideBanner();
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Packet loss is the primary signal; round-trip time, jitter, and a
|
|
322
|
+
bandwidth-limited encoder can only push the level worse, never better. To avoid
|
|
323
|
+
flapping on a momentary blip, **downgrades** are debounced — they require
|
|
324
|
+
`connectionQualityDebounceSamples` (default 2) consecutive worse samples before
|
|
325
|
+
committing — while **upgrades** are reported on the first improved sample so
|
|
326
|
+
recovery is reflected promptly. The first sample after connecting always emits
|
|
327
|
+
the baseline level.
|
|
328
|
+
|
|
329
|
+
The default `ConnectionQualityThresholds` (all overridable via
|
|
330
|
+
`connectionQualityThresholds`):
|
|
331
|
+
|
|
332
|
+
| Level | Packet loss | RTT | Jitter |
|
|
333
|
+
| --- | --- | --- | --- |
|
|
334
|
+
| `fair` | ≥ 2% | ≥ 300 ms | ≥ 50 ms |
|
|
335
|
+
| `poor` | ≥ 5% | ≥ 600 ms | ≥ 150 ms |
|
|
336
|
+
| `critical` | ≥ 12% | ≥ 1000 ms | — |
|
|
337
|
+
|
|
338
|
+
Loss ratio and send bitrate are windowed over each interval; RTT, jitter, and
|
|
339
|
+
available bitrate are point-in-time. A sample field is `null` when the browser
|
|
340
|
+
did not report the underlying stat. This is a detection surface only — the
|
|
341
|
+
publisher does not itself lower bitrate or resolution in response.
|
|
342
|
+
|
|
275
343
|
## Browser support
|
|
276
344
|
|
|
277
345
|
Requires a browser with WebRTC (`RTCPeerConnection`) and `WebSocket` — all current evergreen browsers. There is no Node.js runtime support; this is a browser-only package.
|