@thattobi/narrator-avatar 1.1.10

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.
@@ -0,0 +1,734 @@
1
+ import { jsx as z } from "react/jsx-runtime";
2
+ import { forwardRef as Gt, useState as Vt, useRef as p, useEffect as et, useCallback as K, useImperativeHandle as qt, lazy as zt, Suspense as Yt } from "react";
3
+ function Tt(t, r) {
4
+ if (t) return !0;
5
+ if (typeof r != "string" || !r.trim()) return !1;
6
+ if (r.startsWith("/") && !r.startsWith("//")) return !0;
7
+ if (typeof window > "u") return !1;
8
+ try {
9
+ const o = new URL(r, window.location.href);
10
+ return o.origin === window.location.origin && /^https?:$/.test(o.protocol);
11
+ } catch {
12
+ return !1;
13
+ }
14
+ }
15
+ const S = {}, Ct = "https://api.deepgram.com/v1/speak", Jt = "encoding=linear16&container=wav&sample_rate=24000", At = 44, Ht = 3, Zt = 280, mt = 560;
16
+ function kt(t) {
17
+ return typeof t == "string" ? t.trim() : "";
18
+ }
19
+ function Pt(t) {
20
+ return t === "deepgram";
21
+ }
22
+ const rt = (t) => new Promise((r) => setTimeout(r, t));
23
+ function Qt(t) {
24
+ const r = t.trim();
25
+ return r ? r.split(new RegExp("(?<=[.!?])\\s+")).map((o) => o.trim()).filter(Boolean) : [];
26
+ }
27
+ function Xt(t, {
28
+ tinyMaxWords: r = 4,
29
+ tinyMaxChars: o = 48,
30
+ maxChars: e = 280
31
+ } = {}) {
32
+ if (!(t != null && t.length)) return [];
33
+ const n = [];
34
+ let i = "";
35
+ const s = (c) => c.trim().split(/\s+/).filter(Boolean).length, a = (c) => {
36
+ const l = (c || "").trim();
37
+ return !!l && s(l) <= r && l.length <= o;
38
+ }, y = (c) => /[.!?…]["')\]]*\s*$/.test((c || "").trim());
39
+ for (const c of t) {
40
+ if (!i) {
41
+ i = c;
42
+ continue;
43
+ }
44
+ const l = `${i} ${c}`, f = (a(i) || a(c)) && l.length <= e, m = !y(i) && l.length <= e;
45
+ f || m ? i = l : (n.push(i), i = c);
46
+ }
47
+ return i && n.push(i), n;
48
+ }
49
+ function Et(t) {
50
+ return Xt(Qt(t));
51
+ }
52
+ function Rt(t, r = mt) {
53
+ const o = (t || "").trim();
54
+ let e = Math.max(320, Number(r) || mt);
55
+ /\?\s*$/.test(o) ? e += 220 : /!\s*$/.test(o) ? e += 160 : /(?:\.\.\.|…)\s*$/.test(o) ? e += 240 : /\.\s*$/.test(o) && (e += 120);
56
+ const n = o.split(/\s+/).filter(Boolean).length;
57
+ return n >= 24 ? e += 120 : n >= 14 && (e += 60), e += Math.floor(Math.random() * 100) - 30, Math.max(380, Math.min(1100, Math.round(e)));
58
+ }
59
+ async function te(t, r, o) {
60
+ const e = Date.now() + Math.max(0, t);
61
+ for (; Date.now() < e; ) {
62
+ if (!r()) return;
63
+ for (; o != null && o.current && r(); ) await rt(80);
64
+ if (!r()) return;
65
+ await rt(Math.min(60, e - Date.now()));
66
+ }
67
+ }
68
+ function ee(t) {
69
+ return t == null ? "" : Array.isArray(t) ? t.join("").replace(/\s+/g, " ").trim() : typeof t == "string" ? t.replace(/\s+/g, " ").trim() : String(t).replace(/\s+/g, " ").trim();
70
+ }
71
+ function re(t, r = "", o = 0) {
72
+ let e = (r || "").trim();
73
+ const n = () => {
74
+ if (!t) return;
75
+ const i = Number(o) || 0;
76
+ if (i <= 0) {
77
+ t(e);
78
+ return;
79
+ }
80
+ const s = e.split(/\s+/).filter(Boolean);
81
+ t(s.length <= i ? e : s.slice(-i).join(" "));
82
+ };
83
+ return e && n(), (i) => {
84
+ const s = ee(i);
85
+ s && (e ? s.startsWith(e) && s.length > e.length ? e = s : e === s || e.endsWith(` ${s}`) || e.endsWith(s) || (e = `${e} ${s}`) : e = s, n());
86
+ };
87
+ }
88
+ function ne(t, r) {
89
+ var e;
90
+ if (!((e = t == null ? void 0 : t.words) != null && e.length) || r <= 0) return "";
91
+ const o = [];
92
+ for (let n = 0; n < t.words.length; n++) {
93
+ const i = t.wtimes[n];
94
+ if (i + t.wdurations[n] <= r + 1 || i < r) o.push(t.words[n]);
95
+ else break;
96
+ }
97
+ return o.join(" ");
98
+ }
99
+ function oe(t, r) {
100
+ if (r.byteLength < At) return null;
101
+ const o = new DataView(r), e = o.getUint32(24, !0), n = Math.max(1, o.getUint16(22, !0)), i = r.byteLength - At, s = i / (2 * n), a = t.createBuffer(n, s, e), y = new Int16Array(r, At, i / 2 | 0);
102
+ for (let c = 0; c < n; c++) {
103
+ const l = a.getChannelData(c);
104
+ for (let f = 0; f < s; f++)
105
+ l[f] = y[f * n + c] / 32768;
106
+ }
107
+ return a;
108
+ }
109
+ function ie(t) {
110
+ const r = t.replace(/[^\p{L}\p{N}'’-]/gu, ""), o = r.replace(/[^\p{L}]/gu, ""), e = (o.match(/[aeiouy]/gi) || []).length;
111
+ return Math.max(1, o.length + e * 0.65 + (r.length - o.length) * 0.45);
112
+ }
113
+ function se(t) {
114
+ return /[.?!…]["')\]}]*$/.test(t) ? 175 : /[;:]["')\]}]*$/.test(t) ? 115 : /[,—-]["')\]}]*$/.test(t) ? 75 : 0;
115
+ }
116
+ function ue(t, r) {
117
+ const o = t.trim().split(/\s+/).filter(Boolean), e = Math.max(0, Number(r) || 0), n = o.map(se), i = n.reduce((M, C) => M + C, 0), s = i > 0 ? Math.min(1, e * 0.28 / i) : 0, a = i * s, y = Math.max(0, e - a), c = o.map(ie), l = c.reduce((M, C) => M + C, 0) || 1, f = [], m = [];
118
+ let E = 0;
119
+ for (let M = 0; M < o.length; M++) {
120
+ f.push(E);
121
+ const C = c[M] / l * y;
122
+ m.push(C), E += C + n[M] * s;
123
+ }
124
+ return f.length || (o.push(t || " "), f.push(0), m.push(e)), { words: o, wtimes: f, wdurations: m };
125
+ }
126
+ async function ae(t, r, o, e, n = Ct, i = 1) {
127
+ const s = n.includes("?") ? "&" : "?", a = Math.max(0.7, Math.min(1.5, Number(i) || 1)), y = `${n}${s}model=${encodeURIComponent(r)}&speed=${a}&${Jt}`;
128
+ let c;
129
+ for (let l = 0; l < Ht; l++)
130
+ try {
131
+ const f = { "Content-Type": "application/json", Accept: "audio/wav" };
132
+ t && (f.Authorization = `Token ${t}`);
133
+ const m = await fetch(y, {
134
+ method: "POST",
135
+ headers: f,
136
+ body: JSON.stringify({ text: o }),
137
+ signal: e
138
+ });
139
+ if (!m.ok) {
140
+ const M = (await m.text()).slice(0, 500);
141
+ throw new Error(
142
+ `Deepgram TTS error: ${m.status} ${m.statusText}${M ? ` — ${M}` : ""}`
143
+ );
144
+ }
145
+ const E = await m.arrayBuffer();
146
+ if (!(E != null && E.byteLength)) throw new Error("Deepgram TTS returned empty audio");
147
+ return E;
148
+ } catch (f) {
149
+ if (c = f, (f == null ? void 0 : f.name) === "AbortError" || e != null && e.aborted) throw f;
150
+ l < Ht - 1 && await rt(Zt * (l + 1));
151
+ }
152
+ throw c instanceof TypeError && /failed to fetch/i.test(c.message) ? new Error(
153
+ "Deepgram TTS request could not reach the API. Check the configured same-origin proxy/service and browser network policy."
154
+ ) : c || new Error("Deepgram TTS failed");
155
+ }
156
+ async function ce(t, r, o, e, n, i = 1, s) {
157
+ const a = await ae(r, o, e, n, s, i);
158
+ let y = oe(t, a);
159
+ if (!y) throw new Error("Failed to prepare audio");
160
+ const c = y.duration * 1e3, { words: l, wtimes: f, wdurations: m } = ue(e, c);
161
+ return { phrase: e, audioBuffer: y, durationMs: c, words: l, wtimes: f, wdurations: m };
162
+ }
163
+ function Wt(t, r, o, e = "") {
164
+ const n = Math.max(0.7, Math.min(1.5, Number(r) || 1));
165
+ return `deepgram|${e}|${t}|${n}|${o}`;
166
+ }
167
+ function ht(t, r, o, e, n, i, s = 1, a) {
168
+ const y = Wt(e, s, n, a), c = t.get(y);
169
+ if (c)
170
+ return Promise.resolve(c).then(
171
+ (f) => f,
172
+ () => (t.delete(y), ht(t, r, o, e, n, null, s, a))
173
+ );
174
+ const l = ce(r, o, e, n, null, s, a).catch((f) => {
175
+ throw t.delete(y), f;
176
+ });
177
+ return t.set(y, l), l;
178
+ }
179
+ function Ft(t, r) {
180
+ if (!t || t.size <= r) return;
181
+ const o = [...t.keys()];
182
+ for (const e of o.slice(0, t.size - r)) t.delete(e);
183
+ }
184
+ function le(t, r, o) {
185
+ if (!r || !t) return null;
186
+ const e = r.sampleRate;
187
+ let n = Math.floor(Math.max(0, o) / 1e3 * e);
188
+ if (n < 0 && (n = 0), n >= r.length - 32) return null;
189
+ const i = r.length - n, s = t.createBuffer(r.numberOfChannels, i, e);
190
+ for (let a = 0; a < r.numberOfChannels; a++)
191
+ s.getChannelData(a).set(r.getChannelData(a).subarray(n));
192
+ return s;
193
+ }
194
+ function fe(t, r, o, e) {
195
+ const n = [], i = [], s = [];
196
+ for (let a = 0; a < t.length; a++) {
197
+ const y = r[a], c = o[a], l = y + c;
198
+ l <= e + 1 || (y < e ? (n.push(t[a]), i.push(0), s.push(l - e)) : (n.push(t[a]), i.push(y - e), s.push(c)));
199
+ }
200
+ return n.length || (n.push(" "), i.push(0), s.push(120)), { words: n, wtimes: i, wdurations: s };
201
+ }
202
+ function de(t, r, o) {
203
+ const e = le(t, r.audioBuffer, o);
204
+ if (!e) return null;
205
+ const { words: n, wtimes: i, wdurations: s } = fe(
206
+ r.words,
207
+ r.wtimes,
208
+ r.wdurations,
209
+ o
210
+ );
211
+ return { audio: e, words: n, wtimes: i, wdurations: s, durationMs: e.duration * 1e3 };
212
+ }
213
+ async function Y(t) {
214
+ if (!t || t.state === "closed") return !1;
215
+ if (t.state === "running") return !0;
216
+ try {
217
+ await Promise.race([
218
+ t.resume(),
219
+ new Promise((r, o) => setTimeout(() => o(new Error("timeout")), 1500))
220
+ ]);
221
+ } catch {
222
+ try {
223
+ await t.resume();
224
+ } catch {
225
+ return !1;
226
+ }
227
+ }
228
+ return t.state === "running";
229
+ }
230
+ function he(t, r, {
231
+ onWord: o,
232
+ abortedRef: e,
233
+ pausedRef: n,
234
+ sessionRef: i,
235
+ sessionId: s,
236
+ playbackState: a
237
+ }) {
238
+ return new Promise((y) => {
239
+ const c = t.audioCtx;
240
+ let l = null, f = !1;
241
+ const m = (E) => {
242
+ if (!f) {
243
+ f = !0, l != null && (clearInterval(l), l = null);
244
+ for (const M of t.wordTimers || []) clearTimeout(M);
245
+ if (t.wordTimers = [], t.isAudioPlaying = !1, t.source)
246
+ try {
247
+ t.source.onended = null;
248
+ } catch {
249
+ }
250
+ E !== "paused" && (t.isSpeaking = !1), y(E);
251
+ }
252
+ };
253
+ (async () => {
254
+ var C, nt, H;
255
+ if (!c || !(r != null && r.audio)) {
256
+ m("aborted");
257
+ return;
258
+ }
259
+ if (!await Y(c)) {
260
+ m("blocked");
261
+ return;
262
+ }
263
+ if (e.current || i.current !== s) {
264
+ m("aborted");
265
+ return;
266
+ }
267
+ if (n.current) {
268
+ m("paused");
269
+ return;
270
+ }
271
+ try {
272
+ (nt = (C = t.source) == null ? void 0 : C.stop) == null || nt.call(C);
273
+ } catch {
274
+ }
275
+ t.source = null;
276
+ for (const b of t.wordTimers || []) clearTimeout(b);
277
+ t.wordTimers = [];
278
+ const M = c.createBufferSource();
279
+ if (M.buffer = r.audio, M.connect(t.gain), t.source = M, t.isSpeaking = !0, t.isAudioPlaying = !0, a.segmentStartedCtxTime = c.currentTime, typeof o == "function" && ((H = r.words) != null && H.length))
280
+ for (let b = 0; b < r.words.length; b++) {
281
+ const U = Math.max(0, r.wtimes[b]), ot = r.words[b], P = setTimeout(() => {
282
+ e.current || n.current || i.current !== s || o(ot);
283
+ }, U);
284
+ t.wordTimers.push(P);
285
+ }
286
+ M.onended = () => {
287
+ if (n.current) {
288
+ m("paused");
289
+ return;
290
+ }
291
+ e.current || i.current !== s ? m("aborted") : m("done");
292
+ }, l = setInterval(() => {
293
+ if (!f) {
294
+ if (e.current || i.current !== s) {
295
+ try {
296
+ M.stop();
297
+ } catch {
298
+ }
299
+ m("aborted");
300
+ return;
301
+ }
302
+ if (n.current) {
303
+ try {
304
+ const b = (c.currentTime - a.segmentStartedCtxTime) * 1e3;
305
+ a.resumeOffsetMs = (a.segmentOffsetMs || 0) + Math.max(0, b), M.stop();
306
+ } catch {
307
+ }
308
+ m("paused");
309
+ }
310
+ }
311
+ }, 40);
312
+ try {
313
+ M.start(0);
314
+ } catch {
315
+ m("aborted");
316
+ }
317
+ })();
318
+ });
319
+ }
320
+ const _t = Gt(function({
321
+ ttsApiKey: r = null,
322
+ deepgramEndpoint: o = Ct,
323
+ ttsVoice: e = "aura-2-thalia-en",
324
+ ttsService: n = "deepgram",
325
+ speechRate: i = 1,
326
+ phraseGapMs: s = mt,
327
+ preloadText: a = null,
328
+ onPreloadReady: y = null,
329
+ pauseWhenHidden: c = !0,
330
+ subtitleMaxWords: l = 16,
331
+ onReady: f = () => {
332
+ },
333
+ onError: m = () => {
334
+ },
335
+ onSpeechStart: E = () => {
336
+ },
337
+ onSpeechEnd: M = () => {
338
+ },
339
+ onSubtitle: C = null,
340
+ className: nt = "",
341
+ style: H = {}
342
+ }, b) {
343
+ const [U, ot] = Vt(!1), P = p(null), F = p(/* @__PURE__ */ new Map()), J = p(0), Z = p(!1), O = p(!1), j = p(!1), G = p({
344
+ segmentOffsetMs: 0,
345
+ segmentStartedCtxTime: null,
346
+ durationMs: 0,
347
+ resumeOffsetMs: 0
348
+ }), wt = p(0), Q = p(!1), V = p(r), I = p(o), it = p(e), st = p(i), bt = p(s), Kt = p(a), ut = p(y), W = p(f), D = p(m), at = p(E), L = p(M), $ = p(C), xt = p(l), Nt = p(c), ct = p(() => {
349
+ }), lt = p(async () => {
350
+ }), ft = p(() => {
351
+ });
352
+ et(() => {
353
+ V.current = r, I.current = o || Ct, it.current = e, st.current = Math.max(0.6, Math.min(1.2, Number(i) || 1));
354
+ const u = Number(s);
355
+ bt.current = Number.isFinite(u) && u >= 0 ? u : mt, Kt.current = a, ut.current = y, W.current = f, D.current = m, at.current = E, L.current = M, $.current = C, xt.current = Number(l) > 0 ? Number(l) : 16, Nt.current = c;
356
+ }), et(() => {
357
+ var T, A;
358
+ if (!Pt(n)) {
359
+ ot(!0), (T = W.current) == null || T.call(W);
360
+ return;
361
+ }
362
+ const u = window.AudioContext || window.webkitAudioContext, d = new u(), w = d.createGain();
363
+ return w.connect(d.destination), P.current = {
364
+ audioCtx: d,
365
+ gain: w,
366
+ source: null,
367
+ wordTimers: [],
368
+ isSpeaking: !1,
369
+ isAudioPlaying: !1
370
+ }, ot(!0), (A = W.current) == null || A.call(W), () => {
371
+ var k, g, v, h;
372
+ (k = ft.current) == null || k.call(ft, !0);
373
+ try {
374
+ (h = (v = (g = P.current) == null ? void 0 : g.source) == null ? void 0 : v.stop) == null || h.call(v);
375
+ } catch {
376
+ }
377
+ try {
378
+ d.close();
379
+ } catch {
380
+ }
381
+ P.current = null, F.current.clear();
382
+ };
383
+ }, [n]), et(() => {
384
+ const u = () => {
385
+ var v, h;
386
+ if (!Nt.current) return;
387
+ const g = P.current;
388
+ g && (g.isSpeaking || g.isAudioPlaying) && !O.current && (j.current = !0, (v = ct.current) == null || v.call(ct));
389
+ try {
390
+ ((h = g == null ? void 0 : g.audioCtx) == null ? void 0 : h.state) === "running" && g.audioCtx.suspend();
391
+ } catch {
392
+ }
393
+ }, d = async () => {
394
+ var h;
395
+ const g = P.current;
396
+ !g || !await Y(g.audioCtx) || j.current && (j.current = !1, await ((h = lt.current) == null ? void 0 : h.call(lt)));
397
+ }, w = () => {
398
+ document.hidden ? u() : d();
399
+ }, T = () => u(), A = () => {
400
+ var g;
401
+ j.current ? d() : Y((g = P.current) == null ? void 0 : g.audioCtx);
402
+ }, k = () => {
403
+ d();
404
+ };
405
+ return document.addEventListener("visibilitychange", w), window.addEventListener("pagehide", T), window.addEventListener("pageshow", k), window.addEventListener("pointerdown", A, { passive: !0 }), window.addEventListener("touchstart", A, { passive: !0 }), () => {
406
+ document.removeEventListener("visibilitychange", w), window.removeEventListener("pagehide", T), window.removeEventListener("pageshow", k), window.removeEventListener("pointerdown", A), window.removeEventListener("touchstart", A);
407
+ };
408
+ }, []);
409
+ const X = K((u = !0) => {
410
+ var w, T, A;
411
+ Z.current = !0, O.current = !1, Q.current = !1, u && (J.current += 1);
412
+ const d = P.current;
413
+ if (d) {
414
+ for (const k of d.wordTimers || []) clearTimeout(k);
415
+ d.wordTimers = [];
416
+ try {
417
+ (T = (w = d.source) == null ? void 0 : w.stop) == null || T.call(w);
418
+ } catch {
419
+ }
420
+ d.source = null, d.isSpeaking = !1, d.isAudioPlaying = !1;
421
+ }
422
+ try {
423
+ (A = $.current) == null || A.call($, "");
424
+ } catch {
425
+ }
426
+ }, []);
427
+ ft.current = X;
428
+ const dt = K((u) => {
429
+ const d = P.current;
430
+ if (!(d != null && d.audioCtx) || !(u != null && u.trim()) || !Pt(n)) return;
431
+ const w = kt(
432
+ n === "deepgram" ? V.current || (S == null ? void 0 : S.VITE_DEEPGRAM_API_KEY) : V.current
433
+ );
434
+ if (n === "deepgram" && !Tt(w, I.current)) return;
435
+ const T = it.current || "aura-2-thalia-en", A = st.current, k = Et(u), g = k.length ? k : [u.trim()], v = ++wt.current, h = (x) => ht(
436
+ F.current,
437
+ d.audioCtx,
438
+ w,
439
+ T,
440
+ g[x],
441
+ null,
442
+ A,
443
+ I.current
444
+ );
445
+ (async () => {
446
+ var x;
447
+ try {
448
+ if (await h(0), v !== wt.current) return;
449
+ (x = ut.current) == null || x.call(ut);
450
+ } catch (q) {
451
+ console.warn("[VoiceOnlyNarrator] Preload failed:", q);
452
+ return;
453
+ }
454
+ v === wt.current && (g[1] && h(1), Ft(F.current, 8));
455
+ })();
456
+ }, [n]);
457
+ et(() => {
458
+ !U || !a || dt(a);
459
+ }, [U, a, dt]);
460
+ const $t = K(async (u, d) => {
461
+ var Lt, St, It;
462
+ const w = P.current;
463
+ if (!(w != null && w.audioCtx)) return;
464
+ const T = kt(
465
+ n === "deepgram" ? V.current || (S == null ? void 0 : S.VITE_DEEPGRAM_API_KEY) : V.current
466
+ );
467
+ if (n === "deepgram" && !Tt(T, I.current))
468
+ throw new Error("Deepgram requires an API key or a same-origin proxy endpoint");
469
+ const A = it.current || "aura-2-thalia-en", k = st.current, g = Et(u), v = g.length ? g : [u.trim() || " "], h = () => !Z.current && J.current === d, x = /* @__PURE__ */ new Map(), q = (N) => {
470
+ N < 0 || N >= v.length || x.has(N) || x.set(
471
+ N,
472
+ ht(
473
+ F.current,
474
+ w.audioCtx,
475
+ T,
476
+ A,
477
+ v[N],
478
+ null,
479
+ k,
480
+ I.current
481
+ )
482
+ );
483
+ };
484
+ if (q(0), !await Y(w.audioCtx))
485
+ throw new Error("AudioContext blocked — tap Speak / Continue Learning to unlock audio");
486
+ if (await x.get(0), !h()) return;
487
+ q(1);
488
+ let yt = !1, Mt = !1;
489
+ for (let N = 0; N < v.length && h(); N++) {
490
+ for (; O.current && h(); ) await rt(80);
491
+ if (!h()) break;
492
+ q(N), q(N + 1);
493
+ let B;
494
+ try {
495
+ B = await x.get(N);
496
+ } catch (tt) {
497
+ (Lt = D.current) == null || Lt.call(D, tt), x.delete(N);
498
+ continue;
499
+ }
500
+ if (x.delete(N), !B || !h()) break;
501
+ (St = $.current) == null || St.call($, "");
502
+ let _ = 0, R = !1;
503
+ for (; !R && h(); ) {
504
+ for (; O.current && h(); ) await rt(80);
505
+ if (!h()) break;
506
+ if (_ >= B.durationMs - 60) {
507
+ R = !0;
508
+ break;
509
+ }
510
+ const tt = de(w.audioCtx, B, _);
511
+ if (!tt) {
512
+ R = !0;
513
+ break;
514
+ }
515
+ G.current = {
516
+ segmentOffsetMs: _,
517
+ segmentStartedCtxTime: null,
518
+ durationMs: B.durationMs,
519
+ resumeOffsetMs: _
520
+ };
521
+ const Ut = re(
522
+ (jt) => {
523
+ var Bt;
524
+ return (Bt = $.current) == null ? void 0 : Bt.call($, jt);
525
+ },
526
+ ne(B, _),
527
+ xt.current
528
+ );
529
+ Q.current = !0;
530
+ const vt = await he(w, tt, {
531
+ onWord: Ut,
532
+ abortedRef: Z,
533
+ pausedRef: O,
534
+ sessionRef: J,
535
+ sessionId: d,
536
+ playbackState: G.current
537
+ });
538
+ if (vt === "blocked") {
539
+ Mt = !0;
540
+ break;
541
+ }
542
+ if (vt === "paused") {
543
+ _ = G.current.resumeOffsetMs || _;
544
+ continue;
545
+ }
546
+ if (vt === "aborted") break;
547
+ yt = !0, R = !0;
548
+ }
549
+ if (Mt) break;
550
+ try {
551
+ F.current.delete(Wt(
552
+ A,
553
+ k,
554
+ B.phrase,
555
+ I.current
556
+ ));
557
+ } catch {
558
+ }
559
+ Ft(F.current, 8), h() && R && N < v.length - 1 && await te(
560
+ Rt(B.phrase, bt.current),
561
+ h,
562
+ O
563
+ );
564
+ }
565
+ if (Q.current = !1, Mt && h())
566
+ throw new Error("AudioContext blocked — tap Speak / Continue Learning to unlock audio");
567
+ if (h() && yt) (It = L.current) == null || It.call(L);
568
+ else if (h() && !yt)
569
+ throw new Error("No audio could be played");
570
+ }, []), Ot = K((u) => {
571
+ var T, A, k;
572
+ if (!Pt(n)) {
573
+ (T = D.current) == null || T.call(D, new Error('Voice-only mode requires ttsService="deepgram"'));
574
+ return;
575
+ }
576
+ if (!P.current || !(u != null && u.trim())) return;
577
+ const d = kt(
578
+ n === "deepgram" ? V.current || (S == null ? void 0 : S.VITE_DEEPGRAM_API_KEY) : V.current
579
+ );
580
+ if (n !== "deepgram" || Tt(d, I.current)) {
581
+ const v = Et(u)[0] || u.trim();
582
+ v && ht(
583
+ F.current,
584
+ P.current.audioCtx,
585
+ d,
586
+ it.current || "aura-2-thalia-en",
587
+ v,
588
+ null,
589
+ st.current,
590
+ I.current
591
+ );
592
+ }
593
+ X(!0), j.current = !1, Z.current = !1, O.current = !1;
594
+ const w = J.current;
595
+ Y(P.current.audioCtx), (A = at.current) == null || A.call(at, u), (k = $.current) == null || k.call($, ""), $t(u, w).catch((g) => {
596
+ var v, h;
597
+ J.current !== w || Z.current || (console.error("[VoiceOnlyNarrator] Speech failed:", g), Q.current = !1, (v = L.current) == null || v.call(L), (h = D.current) == null || h.call(D, g));
598
+ });
599
+ }, [X, $t, n]), gt = K(() => {
600
+ var d, w;
601
+ const u = P.current;
602
+ if (u) {
603
+ if (u.isAudioPlaying && G.current.segmentStartedCtxTime != null) {
604
+ const T = (u.audioCtx.currentTime - G.current.segmentStartedCtxTime) * 1e3;
605
+ G.current.resumeOffsetMs = (G.current.segmentOffsetMs || 0) + Math.max(0, T);
606
+ }
607
+ O.current = !0;
608
+ try {
609
+ (w = (d = u.source) == null ? void 0 : d.stop) == null || w.call(d);
610
+ } catch {
611
+ }
612
+ u.isAudioPlaying = !1;
613
+ }
614
+ }, []), pt = K(async () => {
615
+ var u;
616
+ O.current && (await Y((u = P.current) == null ? void 0 : u.audioCtx), O.current = !1);
617
+ }, []), Dt = K(() => {
618
+ var u;
619
+ j.current = !1, X(!0), (u = L.current) == null || u.call(L);
620
+ }, [X]);
621
+ return ct.current = gt, lt.current = pt, qt(b, () => ({
622
+ speakText: Ot,
623
+ preloadSpeech: dt,
624
+ pauseSpeaking: gt,
625
+ resumeSpeaking: pt,
626
+ stopSpeaking: Dt,
627
+ makeEyeContact: () => {
628
+ },
629
+ setMood: () => {
630
+ },
631
+ setLighting: () => {
632
+ },
633
+ setView: () => {
634
+ },
635
+ playGesture: () => {
636
+ },
637
+ playTeachingGesture: () => !1,
638
+ playAnimation: () => {
639
+ },
640
+ playPose: () => {
641
+ },
642
+ stopAnimation: () => {
643
+ },
644
+ stopPose: () => {
645
+ },
646
+ setMixerGain: () => {
647
+ },
648
+ playBackgroundAudio: () => {
649
+ },
650
+ stopBackgroundAudio: () => {
651
+ },
652
+ isReady: U,
653
+ get isSpeaking() {
654
+ var u;
655
+ return Q.current || !!((u = P.current) != null && u.isSpeaking);
656
+ },
657
+ get talkingHead() {
658
+ return null;
659
+ },
660
+ get avatarMode() {
661
+ return "voice";
662
+ }
663
+ }), [Ot, dt, gt, pt, Dt, U]), /* @__PURE__ */ z(
664
+ "div",
665
+ {
666
+ className: `narrator-avatar-container narrator-avatar-voice-only ${nt}`,
667
+ style: {
668
+ position: "relative",
669
+ minHeight: 0,
670
+ height: 0,
671
+ overflow: "hidden",
672
+ pointerEvents: "none",
673
+ ...H,
674
+ // Force zero footprint — host should not reserve avatar layout on mobile
675
+ width: (H == null ? void 0 : H.width) ?? "100%",
676
+ maxHeight: 0
677
+ },
678
+ "data-avatar-mode": "voice",
679
+ "aria-hidden": "true"
680
+ }
681
+ );
682
+ });
683
+ _t.displayName = "VoiceOnlyNarrator";
684
+ const me = zt(() => import("./NarratorAvatarFull-DhBLV6kE.js"));
685
+ function we() {
686
+ var r, o, e, n;
687
+ if (typeof window > "u") return !1;
688
+ const t = typeof navigator < "u" && navigator.userAgent || "";
689
+ return !!(/iPhone|iPod|Android.*Mobile|webOS|BlackBerry|IEMobile|Opera Mini/i.test(t) || /iPad/i.test(t) || /Android/i.test(t) && !/Mobile/i.test(t) || /Macintosh/i.test(t) && (navigator.maxTouchPoints || 0) > 1 && ((o = (r = window.matchMedia) == null ? void 0 : r.call(window, "(pointer: coarse)")) != null && o.matches) || (n = (e = window.matchMedia) == null ? void 0 : e.call(window, "(max-width: 768px)")) != null && n.matches || window.innerWidth <= 768);
690
+ }
691
+ function ge(t, r) {
692
+ return t === "voice" || t === !0 ? "voice" : t === "full" || t === !1 ? "full" : we() && r === "deepgram" ? "voice" : "full";
693
+ }
694
+ const pe = Gt(function(r, o) {
695
+ var a;
696
+ const { avatarMode: e = "auto", ...n } = r, [i, s] = Vt(() => e === "voice" || e === !0 ? "voice" : e === "full" || e === !1 ? "full" : null);
697
+ return et(() => {
698
+ s(ge(e, n.ttsService || "google"));
699
+ }, [e, n.ttsService]), i === null ? /* @__PURE__ */ z(
700
+ "div",
701
+ {
702
+ className: `narrator-avatar-container ${n.className || ""}`,
703
+ style: { position: "relative", minHeight: 0, ...n.style },
704
+ "data-avatar-mode": "resolving",
705
+ "aria-hidden": "true"
706
+ }
707
+ ) : i === "voice" ? /* @__PURE__ */ z(_t, { ref: o, ...n }) : /* @__PURE__ */ z(
708
+ Yt,
709
+ {
710
+ fallback: /* @__PURE__ */ z(
711
+ "div",
712
+ {
713
+ className: `narrator-avatar-container ${n.className || ""}`,
714
+ style: {
715
+ position: "relative",
716
+ display: "flex",
717
+ alignItems: "center",
718
+ justifyContent: "center",
719
+ minHeight: ((a = n.style) == null ? void 0 : a.minHeight) || 400,
720
+ ...n.style
721
+ },
722
+ children: "Loading 3D avatar…"
723
+ }
724
+ ),
725
+ children: /* @__PURE__ */ z(me, { ref: o, ...n })
726
+ }
727
+ );
728
+ });
729
+ pe.displayName = "NarratorAvatar";
730
+ export {
731
+ pe as N,
732
+ Tt as c
733
+ };
734
+ //# sourceMappingURL=index-xRyAk0sW.js.map