@burdenoff/website-sdk 2026.1104.3 → 2026.1105.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.
Files changed (48) hide show
  1. package/README.md +51 -6
  2. package/dist/avatar-live-view/index.d.mts +1 -1
  3. package/dist/avatar-live-view/index.d.ts +1 -1
  4. package/dist/avatar-live-view/index.js +482 -64
  5. package/dist/avatar-live-view/index.js.map +1 -1
  6. package/dist/avatar-live-view/index.mjs +476 -58
  7. package/dist/avatar-live-view/index.mjs.map +1 -1
  8. package/dist/avatar-mic/index.d.mts +193 -0
  9. package/dist/avatar-mic/index.d.ts +193 -0
  10. package/dist/avatar-mic/index.js +738 -0
  11. package/dist/avatar-mic/index.js.map +1 -0
  12. package/dist/avatar-mic/index.mjs +736 -0
  13. package/dist/avatar-mic/index.mjs.map +1 -0
  14. package/dist/{chunk-5JQGE3XI.mjs → chunk-7HE33NBS.mjs} +4 -111
  15. package/dist/chunk-7HE33NBS.mjs.map +1 -0
  16. package/dist/chunk-JZS6FQT6.mjs +127 -0
  17. package/dist/chunk-JZS6FQT6.mjs.map +1 -0
  18. package/dist/{chunk-22OT6E26.js → chunk-MBVXDFVC.js} +18 -127
  19. package/dist/chunk-MBVXDFVC.js.map +1 -0
  20. package/dist/{chunk-7MSB3EUI.mjs → chunk-MVNN6LLO.mjs} +4 -3
  21. package/dist/chunk-MVNN6LLO.mjs.map +1 -0
  22. package/dist/chunk-N7E6Z2GF.js +132 -0
  23. package/dist/chunk-N7E6Z2GF.js.map +1 -0
  24. package/dist/{chunk-WSEQ2I6I.js → chunk-XIZE37IB.js} +4 -3
  25. package/dist/chunk-XIZE37IB.js.map +1 -0
  26. package/dist/components/explore.d.mts +1 -1
  27. package/dist/components/explore.d.ts +1 -1
  28. package/dist/components/explore.js +20 -20
  29. package/dist/components/explore.mjs +2 -2
  30. package/dist/{explore-BV4dRwoC.d.ts → explore-Dfru4j4m.d.ts} +45 -31
  31. package/dist/{explore-avatar-live-config-DRpS-mqQ.d.ts → explore-avatar-live-config-C-OwXNje.d.ts} +8 -0
  32. package/dist/{explore-avatar-live-config-B6_p0q1c.d.mts → explore-avatar-live-config-OO0hvQCK.d.mts} +8 -0
  33. package/dist/{explore-BgEeGPvY.d.mts → explore-ei8uoko9.d.mts} +45 -31
  34. package/dist/index.d.mts +11 -6
  35. package/dist/index.d.ts +11 -6
  36. package/dist/index.js +70 -68
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.mjs +9 -6
  39. package/dist/index.mjs.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunk-22OT6E26.js.map +0 -1
  42. package/dist/chunk-5JQGE3XI.mjs.map +0 -1
  43. package/dist/chunk-7MSB3EUI.mjs.map +0 -1
  44. package/dist/chunk-O4GS3XPZ.js +0 -10
  45. package/dist/chunk-O4GS3XPZ.js.map +0 -1
  46. package/dist/chunk-TT3MHN42.mjs +0 -8
  47. package/dist/chunk-TT3MHN42.mjs.map +0 -1
  48. package/dist/chunk-WSEQ2I6I.js.map +0 -1
@@ -0,0 +1,736 @@
1
+ import { __publicField } from '../chunk-VNS5WDWO.mjs';
2
+
3
+ // src/avatar-mic/capture-worklet.ts
4
+ var CAPTURE_FRAME_SAMPLES = 320;
5
+ var CAPTURE_SAMPLE_RATE = 16e3;
6
+ var CAPTURE_PROCESSOR_NAME = "boff-handsfree-capture";
7
+ var CAPTURE_WORKLET_SOURCE = `
8
+ "use strict";
9
+ const FRAME = ${CAPTURE_FRAME_SAMPLES};
10
+ class BoffHandsfreeCapture extends AudioWorkletProcessor {
11
+ constructor() {
12
+ super();
13
+ this.step = sampleRate / ${CAPTURE_SAMPLE_RATE};
14
+ this.half = 6 * this.step;
15
+ this.fc = 7200 / sampleRate;
16
+ this.buf = new Float32Array(Math.ceil(this.half * 2) + 4096);
17
+ this.len = 0;
18
+ this.pos = 0;
19
+ this.frame = new Float32Array(FRAME);
20
+ this.n = 0;
21
+ this.seq = 0;
22
+ this.refEnergy = 0;
23
+ this.refCount = 0;
24
+ this.stopped = false;
25
+ this.port.onmessage = (e) => { if (e.data && e.data.type === "stop") this.stopped = true; };
26
+ }
27
+ tap(d) {
28
+ const x = 2 * this.fc * d;
29
+ const s = x === 0 ? 1 : Math.sin(Math.PI * x) / (Math.PI * x);
30
+ return 2 * this.fc * s * 0.5 * (1 + Math.cos(Math.PI * d / this.half));
31
+ }
32
+ emit() {
33
+ const pcm = new Int16Array(FRAME);
34
+ let energy = 0;
35
+ let crossings = 0;
36
+ for (let i = 0; i < FRAME; i++) {
37
+ const v = Math.max(-1, Math.min(1, this.frame[i]));
38
+ pcm[i] = Math.round(v * 32767);
39
+ energy += v * v;
40
+ if (i > 0 && (v >= 0) !== (this.frame[i - 1] >= 0)) crossings++;
41
+ }
42
+ const refMean = this.refCount > 0 ? this.refEnergy / this.refCount : 0;
43
+ this.port.postMessage({
44
+ pcm: pcm.buffer,
45
+ rmsDb: 10 * Math.log10(energy / FRAME + 1e-9),
46
+ refDb: 10 * Math.log10(refMean + 1e-9),
47
+ zcr: crossings / FRAME,
48
+ ctxTime: currentTime,
49
+ seq: this.seq++,
50
+ }, [pcm.buffer]);
51
+ this.n = 0;
52
+ this.refEnergy = 0;
53
+ this.refCount = 0;
54
+ }
55
+ process(inputs) {
56
+ if (this.stopped) return false;
57
+ const mic = inputs[0] && inputs[0][0];
58
+ if (!mic) return true;
59
+ const ref = inputs[1] && inputs[1][0];
60
+ if (ref) { for (let i = 0; i < ref.length; i++) this.refEnergy += ref[i] * ref[i]; }
61
+ this.refCount += mic.length;
62
+ if (this.len + mic.length > this.buf.length) {
63
+ const grown = new Float32Array((this.len + mic.length) * 2);
64
+ grown.set(this.buf.subarray(0, this.len));
65
+ this.buf = grown;
66
+ }
67
+ this.buf.set(mic, this.len);
68
+ this.len += mic.length;
69
+ while (this.pos + this.half < this.len) {
70
+ const from = Math.max(0, Math.ceil(this.pos - this.half));
71
+ const to = Math.floor(this.pos + this.half);
72
+ let acc = 0;
73
+ for (let i = from; i <= to; i++) acc += this.buf[i] * this.tap(i - this.pos);
74
+ this.frame[this.n++] = acc;
75
+ this.pos += this.step;
76
+ if (this.n === FRAME) this.emit();
77
+ }
78
+ const drop = Math.max(0, Math.floor(this.pos - this.half));
79
+ if (drop > 0) {
80
+ this.buf.copyWithin(0, drop, this.len);
81
+ this.len -= drop;
82
+ this.pos -= drop;
83
+ }
84
+ return true;
85
+ }
86
+ }
87
+ registerProcessor("${CAPTURE_PROCESSOR_NAME}", BoffHandsfreeCapture);
88
+ `;
89
+
90
+ // src/avatar-mic/mic-capture.ts
91
+ var MicCaptureError = class extends Error {
92
+ constructor(reason) {
93
+ super(`mic capture failed: ${reason}`);
94
+ __publicField(this, "reason", reason);
95
+ this.name = "MicCaptureError";
96
+ }
97
+ };
98
+ var CAPTURE_CONSTRAINTS = {
99
+ audio: {
100
+ echoCancellation: true,
101
+ noiseSuppression: true,
102
+ autoGainControl: true,
103
+ channelCount: 1
104
+ }
105
+ };
106
+ function isMicCaptureSupported() {
107
+ return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined";
108
+ }
109
+ async function startMicCapture(onFrame) {
110
+ if (!isMicCaptureSupported()) throw new MicCaptureError("unsupported");
111
+ let stream;
112
+ try {
113
+ stream = await navigator.mediaDevices.getUserMedia(CAPTURE_CONSTRAINTS);
114
+ } catch (cause) {
115
+ const name = cause instanceof DOMException ? cause.name : "";
116
+ throw new MicCaptureError(
117
+ name === "NotAllowedError" || name === "SecurityError" ? "not-allowed" : "unavailable"
118
+ );
119
+ }
120
+ const track = stream.getAudioTracks()[0];
121
+ if (!track) {
122
+ stream.getTracks().forEach((t) => t.stop());
123
+ throw new MicCaptureError("unavailable");
124
+ }
125
+ const context = new AudioContext({ latencyHint: "interactive" });
126
+ const release = () => {
127
+ stream.getTracks().forEach((t) => t.stop());
128
+ };
129
+ try {
130
+ await context.resume();
131
+ const url = URL.createObjectURL(
132
+ new Blob([CAPTURE_WORKLET_SOURCE], { type: "text/javascript" })
133
+ );
134
+ try {
135
+ await context.audioWorklet.addModule(url);
136
+ } finally {
137
+ URL.revokeObjectURL(url);
138
+ }
139
+ const node = new AudioWorkletNode(context, CAPTURE_PROCESSOR_NAME, {
140
+ numberOfInputs: 1,
141
+ numberOfOutputs: 0
142
+ });
143
+ node.port.onmessage = (event) => onFrame(event.data);
144
+ const micSource = context.createMediaStreamSource(stream);
145
+ micSource.connect(node, 0, 0);
146
+ let stopped = false;
147
+ const onPageHide = () => handle.stop();
148
+ const handle = {
149
+ context,
150
+ micSource,
151
+ track,
152
+ settings: track.getSettings(),
153
+ stop() {
154
+ if (stopped) return;
155
+ stopped = true;
156
+ window.removeEventListener("pagehide", onPageHide);
157
+ node.port.onmessage = null;
158
+ try {
159
+ node.port.postMessage({ type: "stop" });
160
+ micSource.disconnect();
161
+ } catch {
162
+ }
163
+ release();
164
+ void context.close().catch(() => void 0);
165
+ }
166
+ };
167
+ window.addEventListener("pagehide", onPageHide);
168
+ return handle;
169
+ } catch (cause) {
170
+ release();
171
+ void context.close().catch(() => void 0);
172
+ throw cause instanceof MicCaptureError ? cause : new MicCaptureError("unavailable");
173
+ }
174
+ }
175
+
176
+ // src/avatar-mic/stt-stream.ts
177
+ var WEB_STT_SUBPROTOCOL = "boff.web-stt.v1";
178
+ var OFFER_PREFIX = "offer.";
179
+ var STREAM_PATH = "/stt/stream";
180
+ var WEB_STT_LANGUAGES = ["en-IN"];
181
+ var TOKEN_CHARS = /^[A-Za-z0-9._~-]{1,4096}$/;
182
+ var UTTERANCE_ID = /^[A-Za-z0-9._-]{1,64}$/;
183
+ var NOTICE_VERSION = /^[A-Za-z0-9._-]{1,32}$/;
184
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
185
+ var MAX_SEND_BYTES = 6400;
186
+ var SOCKET_OPEN = 1;
187
+ var CLOSE_CODE_ERRORS = {
188
+ 4400: { code: "upstream", retryable: false },
189
+ 4401: { code: "auth", retryable: false },
190
+ 4403: { code: "auth", retryable: false },
191
+ 4408: { code: "closed", retryable: true },
192
+ 4413: { code: "upstream", retryable: false },
193
+ 4429: { code: "quota", retryable: false },
194
+ 4500: { code: "upstream", retryable: true },
195
+ 4503: { code: "upstream", retryable: true }
196
+ };
197
+ var CLOSE_AUTH = 4401;
198
+ function webSttUrl(base) {
199
+ let url;
200
+ try {
201
+ url = new URL(`${base}${STREAM_PATH}`);
202
+ } catch {
203
+ return null;
204
+ }
205
+ if (url.protocol === "https:") url.protocol = "wss:";
206
+ else if (url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase())) {
207
+ url.protocol = "ws:";
208
+ } else {
209
+ return null;
210
+ }
211
+ url.search = "";
212
+ url.hash = "";
213
+ return url.toString();
214
+ }
215
+ function isOfferToken(token) {
216
+ return TOKEN_CHARS.test(token);
217
+ }
218
+ function webSttSubprotocols(token) {
219
+ return [WEB_STT_SUBPROTOCOL, `${OFFER_PREFIX}${token}`];
220
+ }
221
+ function isUtteranceId(value) {
222
+ return UTTERANCE_ID.test(value);
223
+ }
224
+ function isNoticeVersion(value) {
225
+ return NOTICE_VERSION.test(value);
226
+ }
227
+ function configFrame(press) {
228
+ return JSON.stringify({
229
+ t: "config",
230
+ v: 1,
231
+ utteranceId: press.utteranceId,
232
+ epoch: press.epoch,
233
+ langs: [...WEB_STT_LANGUAGES],
234
+ sampleRate: 16e3,
235
+ encoding: "s16le",
236
+ notice: press.notice
237
+ });
238
+ }
239
+ function endFrame(utteranceId) {
240
+ return JSON.stringify({ t: "end", utteranceId });
241
+ }
242
+ function cancelFrame(utteranceId) {
243
+ return JSON.stringify({ t: "cancel", utteranceId });
244
+ }
245
+ function chunkFrames(frames) {
246
+ const perChunk = Math.floor(MAX_SEND_BYTES / 2);
247
+ const out = [];
248
+ let current = [];
249
+ const flush = () => {
250
+ if (current.length > 0) out.push(Int16Array.from(current));
251
+ current = [];
252
+ };
253
+ for (const frame of frames) {
254
+ for (const sample of frame) {
255
+ current.push(sample);
256
+ if (current.length === perChunk) flush();
257
+ }
258
+ }
259
+ flush();
260
+ return out;
261
+ }
262
+ function isRecord(value) {
263
+ return value !== null && typeof value === "object" && !Array.isArray(value);
264
+ }
265
+ function readServerMessage(data) {
266
+ if (typeof data !== "string") return null;
267
+ let parsed;
268
+ try {
269
+ parsed = JSON.parse(data);
270
+ } catch {
271
+ return null;
272
+ }
273
+ if (!isRecord(parsed)) return null;
274
+ const { t, utteranceId, epoch, text } = parsed;
275
+ if (t === "ready") return { t };
276
+ if (t === "partial" || t === "final") {
277
+ if (typeof text !== "string" || typeof utteranceId !== "string" || typeof epoch !== "number") {
278
+ return null;
279
+ }
280
+ return { t, utteranceId, epoch, text };
281
+ }
282
+ if (t === "done") {
283
+ if (typeof utteranceId !== "string" || typeof epoch !== "number") {
284
+ return null;
285
+ }
286
+ return { t, utteranceId, epoch };
287
+ }
288
+ if (t === "error") {
289
+ return {
290
+ t,
291
+ code: typeof parsed.code === "string" ? parsed.code : "upstream",
292
+ retryable: parsed.retryable === true,
293
+ utteranceId: typeof utteranceId === "string" ? utteranceId : null,
294
+ detail: typeof parsed.detail === "string" ? parsed.detail : null
295
+ };
296
+ }
297
+ return null;
298
+ }
299
+ function openBrowserSocket(url, protocols) {
300
+ const socket = new WebSocket(url, protocols);
301
+ socket.binaryType = "arraybuffer";
302
+ return {
303
+ get readyState() {
304
+ return socket.readyState;
305
+ },
306
+ send: (data) => socket.send(data),
307
+ close: (code) => socket.close(code),
308
+ listen(handlers) {
309
+ socket.onopen = () => handlers.open();
310
+ socket.onmessage = (event) => handlers.message(event.data);
311
+ socket.onclose = (event) => handlers.close(event.code);
312
+ socket.onerror = () => handlers.error();
313
+ }
314
+ };
315
+ }
316
+
317
+ // src/avatar-mic/press.ts
318
+ var MAX_PRESS_MS = 3e4;
319
+ var PRE_ROLL_MS = 1e3;
320
+ var READY_TIMEOUT_MS = 1e4;
321
+ var FINAL_WAIT_MS = 12e3;
322
+ var OFFER_TTL_MS = 12e4;
323
+ var OFFER_MARGIN_MS = 5e3;
324
+ var CLOCK_TOLERANCE_MS = 5e3;
325
+ var IDLE = { name: "idle" };
326
+ function offerFreshUntil(offer) {
327
+ const ahead = offer.expiresAt - offer.receivedAt;
328
+ const life = ahead > 0 && ahead <= OFFER_TTL_MS + CLOCK_TOLERANCE_MS ? Math.min(ahead, OFFER_TTL_MS) : OFFER_TTL_MS;
329
+ return offer.receivedAt + life - OFFER_MARGIN_MS;
330
+ }
331
+ function failureOf(code, retryable, detail) {
332
+ if (detail === "notice-version") return "unavailable";
333
+ switch (code) {
334
+ case "quota":
335
+ return "limit";
336
+ case "auth":
337
+ return "unavailable";
338
+ case "busy":
339
+ return retryable ? "busy" : "unavailable";
340
+ default:
341
+ return "failed";
342
+ }
343
+ }
344
+ function failureOfClose(code) {
345
+ const known = CLOSE_CODE_ERRORS[code];
346
+ if (known === void 0) return "failed";
347
+ return failureOf(known.code, known.retryable, null);
348
+ }
349
+ function randomUtteranceId() {
350
+ const crypto = globalThis.crypto;
351
+ if (typeof crypto?.randomUUID === "function") {
352
+ return `web-${crypto.randomUUID()}`;
353
+ }
354
+ if (typeof crypto?.getRandomValues !== "function") return null;
355
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
356
+ return `web-${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
357
+ }
358
+ var BROWSER = {
359
+ createSocket: openBrowserSocket,
360
+ startCapture: startMicCapture,
361
+ newUtteranceId: randomUtteranceId
362
+ };
363
+ function createAvatarMic(options, seams = {}) {
364
+ const { createSocket, startCapture, newUtteranceId } = {
365
+ ...BROWSER,
366
+ ...seams
367
+ };
368
+ let press = null;
369
+ let connection = null;
370
+ let epoch = 0;
371
+ let state = IDLE;
372
+ const report = (next) => {
373
+ state = next;
374
+ options.onState(next);
375
+ };
376
+ const heard = (p) => [...p.finals, p.partial].filter((part) => part !== "").join(" ");
377
+ const finalText = (p) => p.finals.join(" ");
378
+ const show = (p) => {
379
+ if (p.stage === "listening") {
380
+ report({
381
+ name: "listening",
382
+ epoch: p.epoch,
383
+ startedAt: p.startedAt,
384
+ text: heard(p)
385
+ });
386
+ } else if (p.stage === "finishing") {
387
+ report({
388
+ name: "finishing",
389
+ epoch: p.epoch,
390
+ text: heard(p),
391
+ limit: p.limit
392
+ });
393
+ }
394
+ };
395
+ const send = (data) => {
396
+ const socket = connection?.socket;
397
+ if (socket && socket.readyState === SOCKET_OPEN) socket.send(data);
398
+ };
399
+ const closeConnection = () => {
400
+ const closing = connection;
401
+ connection = null;
402
+ try {
403
+ closing?.socket.close(1e3);
404
+ } catch {
405
+ }
406
+ };
407
+ const clearTimers = (p) => {
408
+ for (const timer of [p.readyTimer, p.limitTimer, p.finalTimer]) {
409
+ if (timer !== null) clearTimeout(timer);
410
+ }
411
+ p.readyTimer = null;
412
+ p.limitTimer = null;
413
+ p.finalTimer = null;
414
+ };
415
+ const release = (p) => {
416
+ const capture = p.capture;
417
+ p.capture = null;
418
+ p.preRoll = [];
419
+ p.preRollMs = 0;
420
+ try {
421
+ capture?.stop();
422
+ } catch {
423
+ }
424
+ };
425
+ const settle = (p, next) => {
426
+ if (p.stage === "ended") return;
427
+ p.stage = "ended";
428
+ release(p);
429
+ clearTimers(p);
430
+ if (press === p) press = null;
431
+ closeConnection();
432
+ report(next);
433
+ };
434
+ const fail = (p, reason) => settle(p, { name: "failed", epoch: p.epoch, reason, text: finalText(p) });
435
+ const complete = (p) => settle(p, {
436
+ name: "done",
437
+ epoch: p.epoch,
438
+ text: finalText(p),
439
+ limit: p.limit
440
+ });
441
+ const stopRecording = (p, limit, withEnd) => {
442
+ if (p.stage !== "listening") return;
443
+ release(p);
444
+ if (p.limitTimer !== null) clearTimeout(p.limitTimer);
445
+ p.limitTimer = null;
446
+ p.stage = "finishing";
447
+ p.limit = limit;
448
+ if (withEnd) send(endFrame(p.utteranceId));
449
+ p.finalTimer = setTimeout(() => {
450
+ p.finalTimer = null;
451
+ if (press !== p || p.stage !== "finishing") return;
452
+ if (p.finals.length > 0) complete(p);
453
+ else fail(p, "failed");
454
+ }, FINAL_WAIT_MS);
455
+ show(p);
456
+ };
457
+ const begin = (p) => {
458
+ if (p.stage !== "starting" || p.capture === null) return;
459
+ if (p.readyTimer !== null) clearTimeout(p.readyTimer);
460
+ p.readyTimer = null;
461
+ send(
462
+ configFrame({
463
+ utteranceId: p.utteranceId,
464
+ epoch: p.epoch,
465
+ notice: options.notice
466
+ })
467
+ );
468
+ for (const chunk of chunkFrames(p.preRoll)) send(chunk);
469
+ p.sentMs += p.preRollMs;
470
+ p.preRoll = [];
471
+ p.preRollMs = 0;
472
+ p.stage = "listening";
473
+ p.startedAt = Date.now();
474
+ p.limitTimer = setTimeout(() => {
475
+ p.limitTimer = null;
476
+ if (press === p) stopRecording(p, true, true);
477
+ }, MAX_PRESS_MS);
478
+ show(p);
479
+ };
480
+ const onFrame = (p, frame) => {
481
+ if (press !== p) return;
482
+ const pcm = new Int16Array(frame.pcm);
483
+ const ms = pcm.length * 1e3 / CAPTURE_SAMPLE_RATE;
484
+ if (p.stage === "starting") {
485
+ p.preRoll.push(pcm);
486
+ p.preRollMs += ms;
487
+ while (p.preRollMs > PRE_ROLL_MS) {
488
+ const oldest = p.preRoll.shift();
489
+ if (oldest === void 0) break;
490
+ p.preRollMs -= oldest.length * 1e3 / CAPTURE_SAMPLE_RATE;
491
+ }
492
+ return;
493
+ }
494
+ if (p.stage !== "listening") return;
495
+ if (p.sentMs + ms > MAX_PRESS_MS) {
496
+ stopRecording(p, true, true);
497
+ return;
498
+ }
499
+ send(pcm);
500
+ p.sentMs += ms;
501
+ };
502
+ const onPressError = (p, error) => {
503
+ if (error.code === "bad-frame" && error.detail === "utterance-too-long") {
504
+ stopRecording(p, true, false);
505
+ return;
506
+ }
507
+ if (error.code === "timeout") {
508
+ if (p.finals.length > 0) complete(p);
509
+ else fail(p, "failed");
510
+ return;
511
+ }
512
+ fail(p, failureOf(error.code, error.retryable, error.detail));
513
+ };
514
+ const onMessage = (data) => {
515
+ const message = readServerMessage(data);
516
+ if (message === null || connection === null) return;
517
+ const p = press;
518
+ if (message.t === "ready") {
519
+ connection.ready = true;
520
+ if (p !== null) begin(p);
521
+ return;
522
+ }
523
+ if (message.t === "error" && message.utteranceId === null) {
524
+ connection.refusal = {
525
+ code: message.code,
526
+ retryable: message.retryable,
527
+ detail: message.detail
528
+ };
529
+ return;
530
+ }
531
+ if (p === null || p.stage === "ended") return;
532
+ if (message.utteranceId !== p.utteranceId) return;
533
+ switch (message.t) {
534
+ case "partial":
535
+ if (message.epoch !== p.epoch) return;
536
+ p.partial = message.text.trim();
537
+ show(p);
538
+ return;
539
+ case "final": {
540
+ if (message.epoch !== p.epoch) return;
541
+ const text = message.text.trim();
542
+ if (text !== "") p.finals.push(text);
543
+ p.partial = "";
544
+ show(p);
545
+ return;
546
+ }
547
+ case "done":
548
+ if (message.epoch !== p.epoch) return;
549
+ complete(p);
550
+ return;
551
+ case "error":
552
+ onPressError(p, message);
553
+ return;
554
+ }
555
+ };
556
+ const onClose = (closed, code) => {
557
+ connection = null;
558
+ const p = press;
559
+ if (p === null || p.stage === "ended") return;
560
+ if (p.stage === "starting") {
561
+ if (closed.ready && !p.ownConnection) {
562
+ if (p.capture !== null) connect(p);
563
+ return;
564
+ }
565
+ if (!closed.ready && !p.retried && code === CLOSE_AUTH) {
566
+ p.retried = true;
567
+ connect(p);
568
+ return;
569
+ }
570
+ }
571
+ if (p.stage === "finishing" && p.finals.length > 0) {
572
+ complete(p);
573
+ return;
574
+ }
575
+ const refusal = closed.refusal;
576
+ fail(
577
+ p,
578
+ refusal === null ? failureOfClose(code) : failureOf(refusal.code, refusal.retryable, refusal.detail)
579
+ );
580
+ };
581
+ const offerFor = async (p) => {
582
+ const held = options.offer();
583
+ if (!p.retried && held !== null && Date.now() < offerFreshUntil(held)) {
584
+ return held;
585
+ }
586
+ return await options.refreshOffer();
587
+ };
588
+ const open = (p, token) => {
589
+ const url = webSttUrl(options.base);
590
+ if (url === null || !isOfferToken(token)) {
591
+ fail(p, "unavailable");
592
+ return;
593
+ }
594
+ let socket;
595
+ try {
596
+ socket = createSocket(url, webSttSubprotocols(token));
597
+ } catch {
598
+ fail(p, "failed");
599
+ return;
600
+ }
601
+ const opened = { socket, ready: false, refusal: null };
602
+ connection = opened;
603
+ p.ownConnection = true;
604
+ socket.listen({
605
+ // The server's `ready` is what begins the press; a close always follows an error.
606
+ open: () => void 0,
607
+ error: () => void 0,
608
+ message: (data) => {
609
+ if (connection === opened) onMessage(data);
610
+ },
611
+ close: (code) => {
612
+ if (connection === opened) onClose(opened, code);
613
+ }
614
+ });
615
+ };
616
+ function connect(p) {
617
+ if (connection !== null) {
618
+ if (connection.ready) begin(p);
619
+ return;
620
+ }
621
+ if (p.readyTimer !== null) clearTimeout(p.readyTimer);
622
+ p.readyTimer = setTimeout(() => {
623
+ p.readyTimer = null;
624
+ if (press === p && p.stage === "starting") fail(p, "failed");
625
+ }, READY_TIMEOUT_MS);
626
+ const waiting = () => press === p && p.stage === "starting" && connection === null;
627
+ void offerFor(p).then(
628
+ (offer) => {
629
+ if (!waiting()) return;
630
+ if (offer === null) {
631
+ fail(p, "unavailable");
632
+ return;
633
+ }
634
+ open(p, offer.token);
635
+ },
636
+ () => {
637
+ if (waiting()) fail(p, "failed");
638
+ }
639
+ );
640
+ }
641
+ const cancel = () => {
642
+ const p = press;
643
+ press = null;
644
+ if (p !== null && p.stage !== "ended") {
645
+ if (p.stage === "listening" || p.stage === "finishing") {
646
+ send(cancelFrame(p.utteranceId));
647
+ }
648
+ p.stage = "ended";
649
+ release(p);
650
+ clearTimers(p);
651
+ }
652
+ closeConnection();
653
+ if (state.name !== "idle") report(IDLE);
654
+ };
655
+ const start = () => {
656
+ const current = press;
657
+ if (current !== null && (current.stage === "starting" || current.stage === "listening")) {
658
+ return;
659
+ }
660
+ if (current !== null && current.stage === "finishing") {
661
+ send(cancelFrame(current.utteranceId));
662
+ current.stage = "ended";
663
+ release(current);
664
+ clearTimers(current);
665
+ press = null;
666
+ }
667
+ epoch += 1;
668
+ const utteranceId = newUtteranceId();
669
+ if (utteranceId === null || !isUtteranceId(utteranceId) || !isNoticeVersion(options.notice)) {
670
+ closeConnection();
671
+ report({ name: "failed", epoch, reason: "unsupported", text: "" });
672
+ return;
673
+ }
674
+ const p = {
675
+ epoch,
676
+ utteranceId,
677
+ stage: "starting",
678
+ capture: null,
679
+ preRoll: [],
680
+ preRollMs: 0,
681
+ sentMs: 0,
682
+ startedAt: 0,
683
+ finals: [],
684
+ partial: "",
685
+ limit: false,
686
+ ownConnection: false,
687
+ retried: false,
688
+ readyTimer: null,
689
+ limitTimer: null,
690
+ finalTimer: null
691
+ };
692
+ press = p;
693
+ report({ name: "starting", epoch });
694
+ let capturing;
695
+ try {
696
+ capturing = startCapture((frame) => onFrame(p, frame));
697
+ } catch (error) {
698
+ capturing = Promise.reject(error);
699
+ }
700
+ capturing.then(
701
+ (capture) => {
702
+ if (press !== p || p.stage !== "starting") {
703
+ try {
704
+ capture.stop();
705
+ } catch {
706
+ }
707
+ return;
708
+ }
709
+ p.capture = capture;
710
+ connect(p);
711
+ },
712
+ (error) => {
713
+ if (press !== p || p.stage !== "starting") return;
714
+ fail(
715
+ p,
716
+ error instanceof MicCaptureError && error.reason === "not-allowed" ? "denied" : "unsupported"
717
+ );
718
+ }
719
+ );
720
+ };
721
+ return {
722
+ start,
723
+ stop() {
724
+ const p = press;
725
+ if (p === null) return;
726
+ if (p.stage === "starting") cancel();
727
+ else stopRecording(p, false, true);
728
+ },
729
+ cancel,
730
+ state: () => state
731
+ };
732
+ }
733
+
734
+ export { createAvatarMic };
735
+ //# sourceMappingURL=index.mjs.map
736
+ //# sourceMappingURL=index.mjs.map