@burdenoff/website-sdk 2026.1105.2 → 2026.1106.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.
- package/README.md +133 -0
- package/dist/avatar-live-view/index.d.mts +1 -1
- package/dist/avatar-live-view/index.d.ts +1 -1
- package/dist/avatar-live-view/index.js +1071 -205
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +1064 -198
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +181 -62
- package/dist/avatar-mic/index.d.ts +181 -62
- package/dist/avatar-mic/index.js +717 -5
- package/dist/avatar-mic/index.js.map +1 -1
- package/dist/avatar-mic/index.mjs +717 -6
- package/dist/avatar-mic/index.mjs.map +1 -1
- package/dist/{chunk-MVNN6LLO.mjs → chunk-BHEHF3Y5.mjs} +2 -2
- package/dist/chunk-BHEHF3Y5.mjs.map +1 -0
- package/dist/{chunk-XIZE37IB.js → chunk-Z2UP22GV.js} +2 -2
- package/dist/chunk-Z2UP22GV.js.map +1 -0
- package/dist/{explore-avatar-live-config-auLKxSzx.d.ts → explore-avatar-live-config-CJstiSAN.d.ts} +10 -3
- package/dist/{explore-avatar-live-config-BL8FTm0I.d.mts → explore-avatar-live-config-D2PGF8fG.d.mts} +10 -3
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-MVNN6LLO.mjs.map +0 -1
- package/dist/chunk-XIZE37IB.js.map +0 -1
package/dist/avatar-mic/index.js
CHANGED
|
@@ -2,6 +2,717 @@
|
|
|
2
2
|
|
|
3
3
|
var chunkZSTFX7FV_js = require('../chunk-ZSTFX7FV.js');
|
|
4
4
|
|
|
5
|
+
// src/avatar-mic/speech-words.ts
|
|
6
|
+
var FILLERS = /* @__PURE__ */ new Set([
|
|
7
|
+
"uh",
|
|
8
|
+
"um",
|
|
9
|
+
"uhm",
|
|
10
|
+
"umm",
|
|
11
|
+
"er",
|
|
12
|
+
"erm",
|
|
13
|
+
"eh",
|
|
14
|
+
"ah",
|
|
15
|
+
"hm",
|
|
16
|
+
"hmm",
|
|
17
|
+
"hmmm",
|
|
18
|
+
"mm",
|
|
19
|
+
"mmm",
|
|
20
|
+
"mhm",
|
|
21
|
+
"huh",
|
|
22
|
+
"oh",
|
|
23
|
+
"so",
|
|
24
|
+
"well",
|
|
25
|
+
"okay",
|
|
26
|
+
"ok",
|
|
27
|
+
"and",
|
|
28
|
+
"like",
|
|
29
|
+
"hey",
|
|
30
|
+
"hi",
|
|
31
|
+
"hello",
|
|
32
|
+
"actually"
|
|
33
|
+
]);
|
|
34
|
+
var OPEN_ENDINGS = /* @__PURE__ */ new Set([
|
|
35
|
+
"the",
|
|
36
|
+
"a",
|
|
37
|
+
"an",
|
|
38
|
+
"of",
|
|
39
|
+
"to",
|
|
40
|
+
"for",
|
|
41
|
+
"about",
|
|
42
|
+
"and",
|
|
43
|
+
"or",
|
|
44
|
+
"but",
|
|
45
|
+
"what",
|
|
46
|
+
"how",
|
|
47
|
+
"which",
|
|
48
|
+
"is",
|
|
49
|
+
"are",
|
|
50
|
+
"can"
|
|
51
|
+
]);
|
|
52
|
+
var PRONOUNS = /* @__PURE__ */ new Set([
|
|
53
|
+
"i",
|
|
54
|
+
"me",
|
|
55
|
+
"you",
|
|
56
|
+
"it",
|
|
57
|
+
"we",
|
|
58
|
+
"us",
|
|
59
|
+
"they",
|
|
60
|
+
"them",
|
|
61
|
+
"he",
|
|
62
|
+
"him",
|
|
63
|
+
"she",
|
|
64
|
+
"her",
|
|
65
|
+
"this",
|
|
66
|
+
"that",
|
|
67
|
+
"these",
|
|
68
|
+
"those"
|
|
69
|
+
]);
|
|
70
|
+
var ECHO_MIN_WORDS = 3;
|
|
71
|
+
var SELF_CONTAINED_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
72
|
+
function tidyWords(text) {
|
|
73
|
+
return text.replace(/\s+/g, " ").trim();
|
|
74
|
+
}
|
|
75
|
+
function speechWords(text) {
|
|
76
|
+
const flat = text.toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, " ").trim();
|
|
77
|
+
return flat === "" ? [] : flat.split(" ");
|
|
78
|
+
}
|
|
79
|
+
function startsWithWords(whole, head) {
|
|
80
|
+
return head.length <= whole.length && head.every((word, index) => whole[index] === word);
|
|
81
|
+
}
|
|
82
|
+
function endsWithWords(whole, tail) {
|
|
83
|
+
return tail.length <= whole.length && tail.every(
|
|
84
|
+
(word, index) => whole[whole.length - tail.length + index] === word
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function startsWithWordsOf(text, head) {
|
|
88
|
+
return startsWithWords(speechWords(text), speechWords(head));
|
|
89
|
+
}
|
|
90
|
+
function mergeTurnPhrase(turn, phrase) {
|
|
91
|
+
const heard = speechWords(turn);
|
|
92
|
+
const incoming = speechWords(phrase);
|
|
93
|
+
if (incoming.length === 0) return turn;
|
|
94
|
+
if (heard.length === 0) return tidyWords(phrase);
|
|
95
|
+
if (startsWithWords(incoming, heard)) {
|
|
96
|
+
return incoming.length === heard.length ? turn : tidyWords(phrase);
|
|
97
|
+
}
|
|
98
|
+
if (incoming.length >= 2 && endsWithWords(heard, incoming)) return turn;
|
|
99
|
+
return tidyWords(`${turn} ${phrase}`);
|
|
100
|
+
}
|
|
101
|
+
function mergeInterim(turn, interim) {
|
|
102
|
+
const said = tidyWords(interim);
|
|
103
|
+
if (said === "") return turn;
|
|
104
|
+
const head = speechWords(turn);
|
|
105
|
+
if (head.length === 0) return said;
|
|
106
|
+
const tokens = said.split(" ");
|
|
107
|
+
let matched = 0;
|
|
108
|
+
for (let at = 0; at < tokens.length; at += 1) {
|
|
109
|
+
const words = speechWords(tokens[at] ?? "");
|
|
110
|
+
for (const word of words) {
|
|
111
|
+
if (word !== head[matched]) return tidyWords(`${turn} ${said}`);
|
|
112
|
+
matched += 1;
|
|
113
|
+
if (matched === head.length) {
|
|
114
|
+
return tidyWords(`${turn} ${tokens.slice(at + 1).join(" ")}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return tidyWords(`${turn} ${said}`);
|
|
119
|
+
}
|
|
120
|
+
function saidWords(words) {
|
|
121
|
+
return words.filter((word) => !FILLERS.has(word));
|
|
122
|
+
}
|
|
123
|
+
function isGenuineSpeech(text) {
|
|
124
|
+
const said = saidWords(speechWords(text));
|
|
125
|
+
if (said.length === 1 && PRONOUNS.has(said[0] ?? "")) return false;
|
|
126
|
+
const content = said.filter((word) => !OPEN_ENDINGS.has(word));
|
|
127
|
+
if (content.length === 0) return false;
|
|
128
|
+
const letters = [...content.join("")];
|
|
129
|
+
if (letters.length >= 2) return true;
|
|
130
|
+
return letters.length === 1 && SELF_CONTAINED_SCRIPT.test(letters[0] ?? "");
|
|
131
|
+
}
|
|
132
|
+
function waitsLonger(text) {
|
|
133
|
+
const words = speechWords(text);
|
|
134
|
+
const last = words[words.length - 1];
|
|
135
|
+
if (last === void 0) return false;
|
|
136
|
+
return saidWords(words).length <= 1 || OPEN_ENDINGS.has(last) || FILLERS.has(last);
|
|
137
|
+
}
|
|
138
|
+
function isEchoOf(text, spoken) {
|
|
139
|
+
if (spoken === null) return false;
|
|
140
|
+
const heard = speechWords(text);
|
|
141
|
+
if (heard.length < ECHO_MIN_WORDS) return false;
|
|
142
|
+
return ` ${speechWords(spoken).join(" ")} `.includes(` ${heard.join(" ")} `);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/avatar-mic/browser-press.ts
|
|
146
|
+
var BROWSER_START_WAIT_MS = 15e3;
|
|
147
|
+
var BROWSER_FINAL_WAIT_MS = 3e3;
|
|
148
|
+
var BROWSER_RESTART_COOLDOWN_MS = 2e3;
|
|
149
|
+
var BROWSER_TOUCH_RESTART_MS = 120;
|
|
150
|
+
var BROWSER_MAX_QUICK_ENDS = 8;
|
|
151
|
+
var BROWSER_MAX_RESTARTS = 60;
|
|
152
|
+
var BROWSER_PRIME_SETTLE_MS = 250;
|
|
153
|
+
var BROWSER_GRANT_RETRY_MS = 350;
|
|
154
|
+
var BROWSER_PAUSE_MIN_MS = 2e3;
|
|
155
|
+
var BROWSER_PAUSE_MAX_MS = 2500;
|
|
156
|
+
var BROWSER_PAUSE_AFTER_START_MS = 1500;
|
|
157
|
+
var BROWSER_PAUSE_SHOWN_MS = 800;
|
|
158
|
+
var BROWSER_ECHO_WINDOW_MS = 2500;
|
|
159
|
+
var QUICK_END_MS = 1e3;
|
|
160
|
+
var NETWORK_FAILURES_FOR_GOOD = 2;
|
|
161
|
+
var IDLE = { name: "idle" };
|
|
162
|
+
function isRecognitionConstructor(value) {
|
|
163
|
+
return typeof value === "function";
|
|
164
|
+
}
|
|
165
|
+
function newBrowserRecognition() {
|
|
166
|
+
const found = Reflect.get(globalThis, "SpeechRecognition") ?? Reflect.get(globalThis, "webkitSpeechRecognition");
|
|
167
|
+
return isRecognitionConstructor(found) ? new found() : null;
|
|
168
|
+
}
|
|
169
|
+
function primeBrowserMicrophone() {
|
|
170
|
+
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
171
|
+
if (typeof media?.getUserMedia !== "function") return null;
|
|
172
|
+
return media.getUserMedia({ audio: true }).then((stream) => {
|
|
173
|
+
for (const track of stream.getTracks()) track.stop();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function watchBrowserMicPermission(onState) {
|
|
177
|
+
const permissions = typeof navigator === "undefined" ? void 0 : navigator.permissions;
|
|
178
|
+
if (typeof permissions?.query !== "function") return;
|
|
179
|
+
try {
|
|
180
|
+
void permissions.query({ name: "microphone" }).then(
|
|
181
|
+
(status) => {
|
|
182
|
+
onState(status.state);
|
|
183
|
+
status.addEventListener("change", () => onState(status.state));
|
|
184
|
+
},
|
|
185
|
+
() => void 0
|
|
186
|
+
);
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function hasCoarsePointer() {
|
|
191
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
return window.matchMedia("(pointer: coarse)").matches;
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
var BROWSER = {
|
|
201
|
+
createRecognition: newBrowserRecognition,
|
|
202
|
+
now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
|
|
203
|
+
primeMicrophone: primeBrowserMicrophone,
|
|
204
|
+
watchMicPermission: watchBrowserMicPermission,
|
|
205
|
+
touch: hasCoarsePointer
|
|
206
|
+
};
|
|
207
|
+
function primeFailure(error) {
|
|
208
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
209
|
+
if (name === "NotAllowedError" || name === "SecurityError") return "denied";
|
|
210
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
211
|
+
return "unsupported";
|
|
212
|
+
}
|
|
213
|
+
return "failed";
|
|
214
|
+
}
|
|
215
|
+
function tidy(text) {
|
|
216
|
+
return text.replace(/\s+/g, " ").trim();
|
|
217
|
+
}
|
|
218
|
+
function readRecognitionResults(results) {
|
|
219
|
+
const finals = [];
|
|
220
|
+
const interim = [];
|
|
221
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
222
|
+
const result = results[index];
|
|
223
|
+
const text = tidy(result?.[0]?.transcript ?? "");
|
|
224
|
+
if (result === void 0 || text === "") continue;
|
|
225
|
+
if (!result.isFinal) {
|
|
226
|
+
interim.push(text);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const last = finals.length > 0 ? finals[finals.length - 1] : void 0;
|
|
230
|
+
if (last !== void 0 && startsWithWordsOf(text, last)) {
|
|
231
|
+
finals[finals.length - 1] = text;
|
|
232
|
+
} else {
|
|
233
|
+
finals.push(text);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { finals, interim: interim.join(" ") };
|
|
237
|
+
}
|
|
238
|
+
function createBrowserAvatarMic(options, seams = {}) {
|
|
239
|
+
const { createRecognition, now, primeMicrophone, watchMicPermission, touch } = { ...BROWSER, ...seams };
|
|
240
|
+
const { endpoint } = options;
|
|
241
|
+
const pauseMs = endpoint === void 0 || !Number.isFinite(endpoint.pauseMs) ? BROWSER_PAUSE_MIN_MS : Math.min(
|
|
242
|
+
BROWSER_PAUSE_MAX_MS,
|
|
243
|
+
Math.max(BROWSER_PAUSE_MIN_MS, endpoint.pauseMs)
|
|
244
|
+
);
|
|
245
|
+
let press = null;
|
|
246
|
+
let epoch = 0;
|
|
247
|
+
let state = IDLE;
|
|
248
|
+
let networkFailures = 0;
|
|
249
|
+
let permission = null;
|
|
250
|
+
let hadMicrophone = false;
|
|
251
|
+
watchMicPermission((next) => {
|
|
252
|
+
permission = next;
|
|
253
|
+
});
|
|
254
|
+
const report = (next) => {
|
|
255
|
+
state = next;
|
|
256
|
+
options.onState(next);
|
|
257
|
+
};
|
|
258
|
+
const turnOf = (earlier, instance) => {
|
|
259
|
+
let turn = earlier;
|
|
260
|
+
for (const final of instance?.finals ?? []) {
|
|
261
|
+
turn = mergeTurnPhrase(turn, final);
|
|
262
|
+
}
|
|
263
|
+
return mergeInterim(turn, instance?.interim ?? "");
|
|
264
|
+
};
|
|
265
|
+
const heard = (p) => turnOf(p.earlier, p.instance);
|
|
266
|
+
const show = (p) => {
|
|
267
|
+
if (p.stage === "listening") {
|
|
268
|
+
const text = heard(p);
|
|
269
|
+
report({
|
|
270
|
+
name: "listening",
|
|
271
|
+
epoch: p.epoch,
|
|
272
|
+
startedAt: p.startedAt,
|
|
273
|
+
text,
|
|
274
|
+
...endpoint === void 0 ? {} : { genuine: isGenuineSpeech(text), pausing: p.pausing }
|
|
275
|
+
});
|
|
276
|
+
} else if (p.stage === "finishing") {
|
|
277
|
+
report({
|
|
278
|
+
name: "finishing",
|
|
279
|
+
epoch: p.epoch,
|
|
280
|
+
text: heard(p),
|
|
281
|
+
limit: p.limit
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
const release = (instance) => {
|
|
286
|
+
const { recognition } = instance;
|
|
287
|
+
recognition.onstart = null;
|
|
288
|
+
recognition.onresult = null;
|
|
289
|
+
recognition.onerror = null;
|
|
290
|
+
recognition.onend = null;
|
|
291
|
+
try {
|
|
292
|
+
recognition.abort();
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const clearTimers = (p) => {
|
|
297
|
+
for (const timer of [
|
|
298
|
+
p.startTimer,
|
|
299
|
+
p.limitTimer,
|
|
300
|
+
p.finalTimer,
|
|
301
|
+
p.launchTimer,
|
|
302
|
+
p.pauseTimer,
|
|
303
|
+
p.pausingTimer
|
|
304
|
+
]) {
|
|
305
|
+
if (timer !== null) clearTimeout(timer);
|
|
306
|
+
}
|
|
307
|
+
p.startTimer = null;
|
|
308
|
+
p.limitTimer = null;
|
|
309
|
+
p.finalTimer = null;
|
|
310
|
+
p.launchTimer = null;
|
|
311
|
+
p.pauseTimer = null;
|
|
312
|
+
p.pausingTimer = null;
|
|
313
|
+
};
|
|
314
|
+
const clearPause = (p) => {
|
|
315
|
+
if (p.pauseTimer !== null) clearTimeout(p.pauseTimer);
|
|
316
|
+
if (p.pausingTimer !== null) clearTimeout(p.pausingTimer);
|
|
317
|
+
p.pauseTimer = null;
|
|
318
|
+
p.pausingTimer = null;
|
|
319
|
+
};
|
|
320
|
+
const settle = (p, next) => {
|
|
321
|
+
if (p.stage === "ended") return;
|
|
322
|
+
p.stage = "ended";
|
|
323
|
+
clearTimers(p);
|
|
324
|
+
const instance = p.instance;
|
|
325
|
+
p.instance = null;
|
|
326
|
+
if (instance !== null) release(instance);
|
|
327
|
+
if (press === p) press = null;
|
|
328
|
+
report(next);
|
|
329
|
+
};
|
|
330
|
+
const complete = (p) => {
|
|
331
|
+
const text = heard(p);
|
|
332
|
+
if (text !== "") networkFailures = 0;
|
|
333
|
+
if (endpoint === void 0) {
|
|
334
|
+
settle(p, { name: "done", epoch: p.epoch, text, limit: p.limit });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const by = p.stage === "finishing" ? p.by ?? "tap" : p.stage === "starting" ? "silence" : "failed";
|
|
338
|
+
const echo = by === "pause" && p.speechAt !== null && p.speechAt - p.listenedAt <= BROWSER_ECHO_WINDOW_MS && isEchoOf(text, endpoint.echoOf());
|
|
339
|
+
settle(p, {
|
|
340
|
+
name: "done",
|
|
341
|
+
epoch: p.epoch,
|
|
342
|
+
text,
|
|
343
|
+
limit: p.limit,
|
|
344
|
+
by,
|
|
345
|
+
echo
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
const fail = (p, reason) => settle(p, { name: "failed", epoch: p.epoch, reason, text: heard(p) });
|
|
349
|
+
const current = (p, instance) => press === p && p.stage !== "ended" && p.instance === instance;
|
|
350
|
+
const armPause = (p, turn, speech) => {
|
|
351
|
+
clearPause(p);
|
|
352
|
+
if (!speech) return;
|
|
353
|
+
const at = now();
|
|
354
|
+
if (!p.pausing) {
|
|
355
|
+
p.pausingTimer = setTimeout(
|
|
356
|
+
() => {
|
|
357
|
+
p.pausingTimer = null;
|
|
358
|
+
if (press !== p || p.stage !== "listening") return;
|
|
359
|
+
p.pausing = true;
|
|
360
|
+
show(p);
|
|
361
|
+
},
|
|
362
|
+
Math.max(0, p.wordsAt + BROWSER_PAUSE_SHOWN_MS - at)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const instance = p.instance;
|
|
366
|
+
if (instance === null || !instance.started) return;
|
|
367
|
+
const wait = waitsLonger(turn) ? 2 * pauseMs : pauseMs;
|
|
368
|
+
const due = Math.max(
|
|
369
|
+
p.wordsAt + wait,
|
|
370
|
+
instance.startedAt + BROWSER_PAUSE_AFTER_START_MS
|
|
371
|
+
);
|
|
372
|
+
p.pauseTimer = setTimeout(
|
|
373
|
+
() => {
|
|
374
|
+
p.pauseTimer = null;
|
|
375
|
+
if (press === p && p.stage === "listening" && p.instance === instance) {
|
|
376
|
+
stopRecording(p, "pause");
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
Math.max(0, due - at)
|
|
380
|
+
);
|
|
381
|
+
};
|
|
382
|
+
const listenOn = (p) => {
|
|
383
|
+
if (endpoint === void 0 || p.stage !== "listening") return;
|
|
384
|
+
const turn = heard(p);
|
|
385
|
+
const words = speechWords(turn).join(" ");
|
|
386
|
+
const at = now();
|
|
387
|
+
if (words !== p.words) {
|
|
388
|
+
p.words = words;
|
|
389
|
+
p.wordsAt = at;
|
|
390
|
+
p.pausing = false;
|
|
391
|
+
}
|
|
392
|
+
const speech = isGenuineSpeech(turn);
|
|
393
|
+
if (speech && p.speechAt === null) {
|
|
394
|
+
p.speechAt = at;
|
|
395
|
+
if (p.limitTimer !== null) clearTimeout(p.limitTimer);
|
|
396
|
+
p.limitTimer = setTimeout(() => {
|
|
397
|
+
p.limitTimer = null;
|
|
398
|
+
if (press === p) stopRecording(p, "limit");
|
|
399
|
+
}, options.maxPressMs);
|
|
400
|
+
}
|
|
401
|
+
armPause(p, turn, speech);
|
|
402
|
+
};
|
|
403
|
+
const began = (p, instance) => {
|
|
404
|
+
instance.started = true;
|
|
405
|
+
instance.startedAt = now();
|
|
406
|
+
hadMicrophone = true;
|
|
407
|
+
if (p.stage === "starting") {
|
|
408
|
+
if (p.startTimer !== null) clearTimeout(p.startTimer);
|
|
409
|
+
p.startTimer = null;
|
|
410
|
+
p.stage = "listening";
|
|
411
|
+
p.startedAt = Date.now();
|
|
412
|
+
p.listenedAt = instance.startedAt;
|
|
413
|
+
p.limitTimer = setTimeout(() => {
|
|
414
|
+
p.limitTimer = null;
|
|
415
|
+
if (press !== p) return;
|
|
416
|
+
if (endpoint === void 0) {
|
|
417
|
+
stopRecording(p, "limit");
|
|
418
|
+
} else if (p.stage === "listening" && p.speechAt === null) {
|
|
419
|
+
settle(p, {
|
|
420
|
+
name: "done",
|
|
421
|
+
epoch: p.epoch,
|
|
422
|
+
text: "",
|
|
423
|
+
limit: false,
|
|
424
|
+
by: "silence",
|
|
425
|
+
echo: false
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}, options.maxPressMs);
|
|
429
|
+
}
|
|
430
|
+
listenOn(p);
|
|
431
|
+
};
|
|
432
|
+
const launchIn = (p, delay) => {
|
|
433
|
+
if (delay <= 0) {
|
|
434
|
+
launch(p);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const stage = p.stage;
|
|
438
|
+
p.launchTimer = setTimeout(() => {
|
|
439
|
+
p.launchTimer = null;
|
|
440
|
+
if (press === p && p.stage === stage) launch(p);
|
|
441
|
+
}, delay);
|
|
442
|
+
};
|
|
443
|
+
const restart = (p, end) => {
|
|
444
|
+
p.quickEnds = end === "listened" ? 0 : p.quickEnds + 1;
|
|
445
|
+
if (p.restarts >= BROWSER_MAX_RESTARTS || p.quickEnds >= BROWSER_MAX_QUICK_ENDS) {
|
|
446
|
+
complete(p);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
p.restarts += 1;
|
|
450
|
+
launchIn(
|
|
451
|
+
p,
|
|
452
|
+
end === "threw" || end === "quick" && !p.touch ? BROWSER_RESTART_COOLDOWN_MS : p.touch ? BROWSER_TOUCH_RESTART_MS : 0
|
|
453
|
+
);
|
|
454
|
+
};
|
|
455
|
+
const keep = (p, instance) => {
|
|
456
|
+
p.earlier = turnOf(p.earlier, instance);
|
|
457
|
+
};
|
|
458
|
+
const onError = (p, instance, code) => {
|
|
459
|
+
if (code === "no-speech") {
|
|
460
|
+
instance.noSpeech = true;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const going = p.stage === "starting" || p.stage === "listening";
|
|
464
|
+
if (code === "language-not-supported" && going && !p.langRetried) {
|
|
465
|
+
p.langRetried = true;
|
|
466
|
+
if (options.fallbackLang !== p.lang) {
|
|
467
|
+
p.lang = options.fallbackLang;
|
|
468
|
+
keep(p, instance);
|
|
469
|
+
p.instance = null;
|
|
470
|
+
release(instance);
|
|
471
|
+
launch(p);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const refused = code === "not-allowed" || code === "service-not-allowed";
|
|
476
|
+
if (refused && going && p.confirmed && !p.grantRetried) {
|
|
477
|
+
p.grantRetried = true;
|
|
478
|
+
keep(p, instance);
|
|
479
|
+
p.instance = null;
|
|
480
|
+
release(instance);
|
|
481
|
+
launchIn(p, BROWSER_GRANT_RETRY_MS);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (refused && !p.confirmed) hadMicrophone = false;
|
|
485
|
+
if (heard(p) !== "") {
|
|
486
|
+
complete(p);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
switch (code) {
|
|
490
|
+
case "not-allowed":
|
|
491
|
+
fail(p, p.confirmed ? "unsupported" : "denied");
|
|
492
|
+
return;
|
|
493
|
+
case "service-not-allowed":
|
|
494
|
+
case "audio-capture":
|
|
495
|
+
case "language-not-supported":
|
|
496
|
+
fail(p, "unsupported");
|
|
497
|
+
return;
|
|
498
|
+
case "network":
|
|
499
|
+
networkFailures += 1;
|
|
500
|
+
fail(
|
|
501
|
+
p,
|
|
502
|
+
networkFailures >= NETWORK_FAILURES_FOR_GOOD ? "unsupported" : "failed"
|
|
503
|
+
);
|
|
504
|
+
return;
|
|
505
|
+
default:
|
|
506
|
+
fail(p, "failed");
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
const onEnd = (p, instance) => {
|
|
510
|
+
keep(p, instance);
|
|
511
|
+
p.instance = null;
|
|
512
|
+
instance.recognition.onstart = null;
|
|
513
|
+
instance.recognition.onresult = null;
|
|
514
|
+
instance.recognition.onerror = null;
|
|
515
|
+
instance.recognition.onend = null;
|
|
516
|
+
switch (p.stage) {
|
|
517
|
+
case "starting":
|
|
518
|
+
if (instance.noSpeech) complete(p);
|
|
519
|
+
else fail(p, "failed");
|
|
520
|
+
return;
|
|
521
|
+
case "listening": {
|
|
522
|
+
if (p.pauseTimer !== null) clearTimeout(p.pauseTimer);
|
|
523
|
+
p.pauseTimer = null;
|
|
524
|
+
const quick = !instance.started || now() - instance.startedAt < QUICK_END_MS && instance.finals.length === 0 && instance.interim === "";
|
|
525
|
+
restart(p, quick ? "quick" : "listened");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
case "finishing":
|
|
529
|
+
complete(p);
|
|
530
|
+
return;
|
|
531
|
+
default:
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function launch(p) {
|
|
536
|
+
let recognition;
|
|
537
|
+
try {
|
|
538
|
+
recognition = createRecognition();
|
|
539
|
+
} catch {
|
|
540
|
+
recognition = null;
|
|
541
|
+
}
|
|
542
|
+
if (recognition === null) {
|
|
543
|
+
if (p.stage === "starting") fail(p, "unsupported");
|
|
544
|
+
else complete(p);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const instance = {
|
|
548
|
+
recognition,
|
|
549
|
+
started: false,
|
|
550
|
+
startedAt: 0,
|
|
551
|
+
finals: [],
|
|
552
|
+
interim: "",
|
|
553
|
+
noSpeech: false
|
|
554
|
+
};
|
|
555
|
+
recognition.lang = p.lang;
|
|
556
|
+
recognition.continuous = !p.touch;
|
|
557
|
+
recognition.interimResults = true;
|
|
558
|
+
recognition.maxAlternatives = 1;
|
|
559
|
+
recognition.onstart = () => {
|
|
560
|
+
if (!current(p, instance)) return;
|
|
561
|
+
began(p, instance);
|
|
562
|
+
show(p);
|
|
563
|
+
};
|
|
564
|
+
recognition.onresult = (event) => {
|
|
565
|
+
if (!current(p, instance)) return;
|
|
566
|
+
if (!instance.started) began(p, instance);
|
|
567
|
+
const read = readRecognitionResults(event.results);
|
|
568
|
+
instance.finals = read.finals;
|
|
569
|
+
instance.interim = read.interim;
|
|
570
|
+
listenOn(p);
|
|
571
|
+
show(p);
|
|
572
|
+
};
|
|
573
|
+
recognition.onerror = (event) => {
|
|
574
|
+
if (current(p, instance)) onError(p, instance, event.error);
|
|
575
|
+
};
|
|
576
|
+
recognition.onend = () => {
|
|
577
|
+
if (current(p, instance)) onEnd(p, instance);
|
|
578
|
+
};
|
|
579
|
+
p.instance = instance;
|
|
580
|
+
try {
|
|
581
|
+
recognition.start();
|
|
582
|
+
} catch {
|
|
583
|
+
p.instance = null;
|
|
584
|
+
release(instance);
|
|
585
|
+
if (p.stage === "starting") fail(p, "failed");
|
|
586
|
+
else if (p.stage === "listening") restart(p, "threw");
|
|
587
|
+
else complete(p);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function prime(p) {
|
|
591
|
+
let opening;
|
|
592
|
+
try {
|
|
593
|
+
opening = primeMicrophone();
|
|
594
|
+
} catch (error) {
|
|
595
|
+
fail(p, primeFailure(error));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (opening === null) {
|
|
599
|
+
launch(p);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
void opening.then(
|
|
603
|
+
() => {
|
|
604
|
+
hadMicrophone = true;
|
|
605
|
+
if (press !== p || p.stage !== "starting") return;
|
|
606
|
+
p.confirmed = true;
|
|
607
|
+
launchIn(p, BROWSER_PRIME_SETTLE_MS);
|
|
608
|
+
},
|
|
609
|
+
(error) => {
|
|
610
|
+
if (press === p && p.stage === "starting") fail(p, primeFailure(error));
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
function stopRecording(p, by) {
|
|
615
|
+
if (p.stage !== "listening") return;
|
|
616
|
+
if (p.limitTimer !== null) clearTimeout(p.limitTimer);
|
|
617
|
+
if (p.launchTimer !== null) clearTimeout(p.launchTimer);
|
|
618
|
+
p.limitTimer = null;
|
|
619
|
+
p.launchTimer = null;
|
|
620
|
+
clearPause(p);
|
|
621
|
+
p.stage = "finishing";
|
|
622
|
+
p.limit = by === "limit";
|
|
623
|
+
p.by = by;
|
|
624
|
+
const instance = p.instance;
|
|
625
|
+
if (instance === null || !instance.started) {
|
|
626
|
+
complete(p);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
instance.recognition.stop();
|
|
631
|
+
} catch {
|
|
632
|
+
complete(p);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
p.finalTimer = setTimeout(() => {
|
|
636
|
+
p.finalTimer = null;
|
|
637
|
+
if (press === p && p.stage === "finishing") complete(p);
|
|
638
|
+
}, BROWSER_FINAL_WAIT_MS);
|
|
639
|
+
show(p);
|
|
640
|
+
}
|
|
641
|
+
const cancel = () => {
|
|
642
|
+
const p = press;
|
|
643
|
+
press = null;
|
|
644
|
+
if (p !== null && p.stage !== "ended") {
|
|
645
|
+
p.stage = "ended";
|
|
646
|
+
clearTimers(p);
|
|
647
|
+
const instance = p.instance;
|
|
648
|
+
p.instance = null;
|
|
649
|
+
if (instance !== null) release(instance);
|
|
650
|
+
}
|
|
651
|
+
if (state.name !== "idle") report(IDLE);
|
|
652
|
+
};
|
|
653
|
+
const start = () => {
|
|
654
|
+
const held = press;
|
|
655
|
+
if (held !== null && (held.stage === "starting" || held.stage === "listening")) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (held !== null && held.stage === "finishing") {
|
|
659
|
+
held.stage = "ended";
|
|
660
|
+
clearTimers(held);
|
|
661
|
+
const instance = held.instance;
|
|
662
|
+
held.instance = null;
|
|
663
|
+
if (instance !== null) release(instance);
|
|
664
|
+
press = null;
|
|
665
|
+
}
|
|
666
|
+
epoch += 1;
|
|
667
|
+
const p = {
|
|
668
|
+
epoch,
|
|
669
|
+
stage: "starting",
|
|
670
|
+
instance: null,
|
|
671
|
+
earlier: "",
|
|
672
|
+
lang: options.lang,
|
|
673
|
+
langRetried: false,
|
|
674
|
+
restarts: 0,
|
|
675
|
+
quickEnds: 0,
|
|
676
|
+
confirmed: permission === "granted",
|
|
677
|
+
grantRetried: false,
|
|
678
|
+
touch: touch(),
|
|
679
|
+
startedAt: 0,
|
|
680
|
+
limit: false,
|
|
681
|
+
startTimer: null,
|
|
682
|
+
limitTimer: null,
|
|
683
|
+
finalTimer: null,
|
|
684
|
+
launchTimer: null,
|
|
685
|
+
by: null,
|
|
686
|
+
words: "",
|
|
687
|
+
wordsAt: 0,
|
|
688
|
+
speechAt: null,
|
|
689
|
+
listenedAt: 0,
|
|
690
|
+
pausing: false,
|
|
691
|
+
pauseTimer: null,
|
|
692
|
+
pausingTimer: null
|
|
693
|
+
};
|
|
694
|
+
press = p;
|
|
695
|
+
report({ name: "starting", epoch });
|
|
696
|
+
p.startTimer = setTimeout(() => {
|
|
697
|
+
p.startTimer = null;
|
|
698
|
+
if (press === p && p.stage === "starting") fail(p, "failed");
|
|
699
|
+
}, BROWSER_START_WAIT_MS);
|
|
700
|
+
if (p.confirmed || permission === null && hadMicrophone) launch(p);
|
|
701
|
+
else prime(p);
|
|
702
|
+
};
|
|
703
|
+
return {
|
|
704
|
+
start,
|
|
705
|
+
stop() {
|
|
706
|
+
const p = press;
|
|
707
|
+
if (p === null) return;
|
|
708
|
+
if (p.stage === "starting") cancel();
|
|
709
|
+
else stopRecording(p, "tap");
|
|
710
|
+
},
|
|
711
|
+
cancel,
|
|
712
|
+
state: () => state
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
5
716
|
// src/avatar-mic/capture-worklet.ts
|
|
6
717
|
var CAPTURE_FRAME_SAMPLES = 320;
|
|
7
718
|
var CAPTURE_SAMPLE_RATE = 16e3;
|
|
@@ -324,7 +1035,7 @@ var FINAL_WAIT_MS = 12e3;
|
|
|
324
1035
|
var OFFER_TTL_MS = 12e4;
|
|
325
1036
|
var OFFER_MARGIN_MS = 5e3;
|
|
326
1037
|
var CLOCK_TOLERANCE_MS = 5e3;
|
|
327
|
-
var
|
|
1038
|
+
var IDLE2 = { name: "idle" };
|
|
328
1039
|
function offerFreshUntil(offer) {
|
|
329
1040
|
const ahead = offer.expiresAt - offer.receivedAt;
|
|
330
1041
|
const life = ahead > 0 && ahead <= OFFER_TTL_MS + CLOCK_TOLERANCE_MS ? Math.min(ahead, OFFER_TTL_MS) : OFFER_TTL_MS;
|
|
@@ -357,20 +1068,20 @@ function randomUtteranceId() {
|
|
|
357
1068
|
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
358
1069
|
return `web-${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
359
1070
|
}
|
|
360
|
-
var
|
|
1071
|
+
var BROWSER2 = {
|
|
361
1072
|
createSocket: openBrowserSocket,
|
|
362
1073
|
startCapture: startMicCapture,
|
|
363
1074
|
newUtteranceId: randomUtteranceId
|
|
364
1075
|
};
|
|
365
1076
|
function createAvatarMic(options, seams = {}) {
|
|
366
1077
|
const { createSocket, startCapture, newUtteranceId } = {
|
|
367
|
-
...
|
|
1078
|
+
...BROWSER2,
|
|
368
1079
|
...seams
|
|
369
1080
|
};
|
|
370
1081
|
let press = null;
|
|
371
1082
|
let connection = null;
|
|
372
1083
|
let epoch = 0;
|
|
373
|
-
let state =
|
|
1084
|
+
let state = IDLE2;
|
|
374
1085
|
const report = (next) => {
|
|
375
1086
|
state = next;
|
|
376
1087
|
options.onState(next);
|
|
@@ -652,7 +1363,7 @@ function createAvatarMic(options, seams = {}) {
|
|
|
652
1363
|
clearTimers(p);
|
|
653
1364
|
}
|
|
654
1365
|
closeConnection();
|
|
655
|
-
if (state.name !== "idle") report(
|
|
1366
|
+
if (state.name !== "idle") report(IDLE2);
|
|
656
1367
|
};
|
|
657
1368
|
const start = () => {
|
|
658
1369
|
const current = press;
|
|
@@ -734,5 +1445,6 @@ function createAvatarMic(options, seams = {}) {
|
|
|
734
1445
|
}
|
|
735
1446
|
|
|
736
1447
|
exports.createAvatarMic = createAvatarMic;
|
|
1448
|
+
exports.createBrowserAvatarMic = createBrowserAvatarMic;
|
|
737
1449
|
//# sourceMappingURL=index.js.map
|
|
738
1450
|
//# sourceMappingURL=index.js.map
|