@burdenoff/fe-libs 2026.1013.13 → 2026.1013.14
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/dist/shared/assistant/avatar/live/LiveAvatarStage.d.ts +15 -0
- package/dist/shared/assistant/avatar/live/LiveAvatarStage.js +139 -0
- package/dist/shared/assistant/avatar/live/avatarAudioDecode.d.ts +40 -0
- package/dist/shared/assistant/avatar/live/avatarAudioDecode.js +72 -0
- package/dist/shared/assistant/avatar/live/avatarAudioErrors.d.ts +25 -0
- package/dist/shared/assistant/avatar/live/avatarAudioErrors.js +27 -0
- package/dist/shared/assistant/avatar/live/avatarAudioMeter.d.ts +37 -0
- package/dist/shared/assistant/avatar/live/avatarAudioMeter.js +62 -0
- package/dist/shared/assistant/avatar/live/avatarMp3Inspect.d.ts +30 -0
- package/dist/shared/assistant/avatar/live/avatarMp3Inspect.js +168 -0
- package/dist/shared/assistant/avatar/live/avatarPcm16.d.ts +21 -0
- package/dist/shared/assistant/avatar/live/avatarPcm16.js +54 -0
- package/dist/shared/assistant/avatar/live/avatarPlaybackTracker.d.ts +181 -0
- package/dist/shared/assistant/avatar/live/avatarPlaybackTracker.js +221 -0
- package/dist/shared/assistant/avatar/live/avatarPorts.d.ts +66 -0
- package/dist/shared/assistant/avatar/live/avatarPorts.js +44 -0
- package/dist/shared/assistant/avatar/live/avatarProtocol.d.ts +22 -0
- package/dist/shared/assistant/avatar/live/avatarProtocol.js +27 -0
- package/dist/shared/assistant/avatar/live/avatarReplyOutputs.d.ts +170 -0
- package/dist/shared/assistant/avatar/live/avatarReplyOutputs.js +525 -0
- package/dist/shared/assistant/avatar/live/avatarSessionClient.d.ts +96 -0
- package/dist/shared/assistant/avatar/live/avatarSessionClient.js +261 -0
- package/dist/shared/assistant/avatar/live/avatarTransport.d.ts +270 -0
- package/dist/shared/assistant/avatar/live/avatarTransport.js +333 -0
- package/dist/shared/assistant/avatar/live/avatarTransportPort.d.ts +17 -0
- package/dist/shared/assistant/avatar/live/avatarTransportPort.js +27 -0
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +44 -0
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +124 -0
- package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +133 -0
- package/dist/shared/assistant/avatar/live/liveAvatarController.js +488 -0
- package/dist/shared/assistant/avatar/live/liveAvatarStatus.d.ts +4 -0
- package/dist/shared/assistant/avatar/live/liveAvatarStatus.js +14 -0
- package/dist/shared/assistant/avatar/live/liveKitAvatarRoom.d.ts +6 -0
- package/dist/shared/assistant/avatar/live/liveKitAvatarRoom.js +86 -0
- package/dist/shared/assistant/avatar/useAvatarCopy.d.ts +2 -0
- package/dist/shared/assistant/avatar/useAvatarCopy.js +1 -0
- package/dist/shared/assistant/i18nManifest.d.ts +1 -0
- package/dist/shared/assistant/i18nManifest.js +1 -0
- package/dist/shared/assistant/index.d.ts +17 -0
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.d.ts +37 -0
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.js +118 -63
- package/dist/shared/assistant/speech/types.d.ts +14 -2
- package/dist/shared-assistant.js +250 -233
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { AvatarRpcRejectedError as e } from "./avatarPorts.js";
|
|
2
|
+
//#region src/shared/assistant/avatar/live/liveKitAvatarRoom.ts
|
|
3
|
+
async function t() {
|
|
4
|
+
let t = await import("livekit-client"), n = new t.Room({
|
|
5
|
+
adaptiveStream: !0,
|
|
6
|
+
dynacast: !1,
|
|
7
|
+
disconnectOnPageLeave: !0
|
|
8
|
+
}), r = (e, t) => (n.on(e, t), () => {
|
|
9
|
+
n.off(e, t);
|
|
10
|
+
}), i = (e, n) => ({
|
|
11
|
+
identity: n,
|
|
12
|
+
kind: e.kind === t.Track.Kind.Video ? "video" : "audio",
|
|
13
|
+
attach: (t) => {
|
|
14
|
+
e.attach(t);
|
|
15
|
+
},
|
|
16
|
+
detach: () => {
|
|
17
|
+
e.detach();
|
|
18
|
+
},
|
|
19
|
+
mediaStreamTrack: e.mediaStreamTrack ?? null
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
async connect(e, t) {
|
|
23
|
+
await n.connect(e, t);
|
|
24
|
+
},
|
|
25
|
+
async disconnect() {
|
|
26
|
+
await n.disconnect();
|
|
27
|
+
},
|
|
28
|
+
isConnected: () => n.state === t.ConnectionState.Connected,
|
|
29
|
+
hasParticipant: (e) => n.remoteParticipants.has(e),
|
|
30
|
+
hasVideoTrack: (e) => {
|
|
31
|
+
let t = n.remoteParticipants.get(e);
|
|
32
|
+
if (!t) return !1;
|
|
33
|
+
for (let e of t.videoTrackPublications.values()) if (e.isSubscribed) return !0;
|
|
34
|
+
return !1;
|
|
35
|
+
},
|
|
36
|
+
onParticipantConnected: (e) => r(t.RoomEvent.ParticipantConnected, (t) => e(t.identity)),
|
|
37
|
+
onParticipantDisconnected: (e) => r(t.RoomEvent.ParticipantDisconnected, (t) => e(t.identity)),
|
|
38
|
+
onTrackSubscribed: (e) => r(t.RoomEvent.TrackSubscribed, (t, n, r) => e(i(t, r.identity))),
|
|
39
|
+
onDisconnected: (e) => r(t.RoomEvent.Disconnected, () => e()),
|
|
40
|
+
onReconnecting: (e) => r(t.RoomEvent.Reconnecting, () => e()),
|
|
41
|
+
onReconnected: (e) => r(t.RoomEvent.Reconnected, () => e()),
|
|
42
|
+
onAudioPlaybackChanged: (e) => r(t.RoomEvent.AudioPlaybackStatusChanged, (t) => e(t)),
|
|
43
|
+
registerRpc(r, i) {
|
|
44
|
+
try {
|
|
45
|
+
n.unregisterRpcMethod(r);
|
|
46
|
+
} catch {}
|
|
47
|
+
n.registerRpcMethod(r, async (n) => {
|
|
48
|
+
try {
|
|
49
|
+
return i(n.callerIdentity, n.payload);
|
|
50
|
+
} catch (n) {
|
|
51
|
+
throw n instanceof e ? new t.RpcError(t.RpcError.ErrorCode.APPLICATION_ERROR, "rejected") : n;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
unregisterRpc(e) {
|
|
56
|
+
try {
|
|
57
|
+
n.unregisterRpcMethod(e);
|
|
58
|
+
} catch {}
|
|
59
|
+
},
|
|
60
|
+
performRpc: (e, t, r, i) => n.localParticipant.performRpc({
|
|
61
|
+
destinationIdentity: e,
|
|
62
|
+
method: t,
|
|
63
|
+
payload: r,
|
|
64
|
+
responseTimeout: i
|
|
65
|
+
}),
|
|
66
|
+
async openByteStream({ topic: e, name: t, destinationIdentity: r, attributes: i }) {
|
|
67
|
+
let a = await n.localParticipant.streamBytes({
|
|
68
|
+
name: t,
|
|
69
|
+
topic: e,
|
|
70
|
+
attributes: i,
|
|
71
|
+
destinationIdentities: [r],
|
|
72
|
+
mimeType: "audio/pcm"
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
write: (e) => a.write(e),
|
|
76
|
+
close: () => a.close()
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
async startAudio() {
|
|
80
|
+
await n.startAudio();
|
|
81
|
+
},
|
|
82
|
+
canPlaybackAudio: () => n.canPlaybackAudio
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { t as openLiveKitAvatarRoom };
|
|
@@ -32,6 +32,8 @@ export interface AvatarCopy {
|
|
|
32
32
|
stateEnding: string;
|
|
33
33
|
stateInterrupted: string;
|
|
34
34
|
stateVoiceOnly: string;
|
|
35
|
+
/** The session reached its time limit and ended between replies (BOFF-8822 P8); the voice goes on. */
|
|
36
|
+
stateTimeLimit: string;
|
|
35
37
|
stageLabel: string;
|
|
36
38
|
stageWaiting: string;
|
|
37
39
|
chooserTitle: string;
|
|
@@ -17,6 +17,7 @@ function n() {
|
|
|
17
17
|
stateEnding: n("avatar.stateEnding", "Stopping the avatar…"),
|
|
18
18
|
stateInterrupted: n("avatar.stateInterrupted", "The avatar was interrupted. You can keep talking."),
|
|
19
19
|
stateVoiceOnly: n("avatar.stateVoiceOnly", "The avatar is unavailable right now. Continuing with voice only."),
|
|
20
|
+
stateTimeLimit: n("avatar.stateTimeLimit", "The avatar reached its time limit. Continuing with voice only."),
|
|
20
21
|
stageLabel: n("avatar.stageLabel", "Live avatar"),
|
|
21
22
|
stageWaiting: n("avatar.stageWaiting", "Waiting for the avatar…"),
|
|
22
23
|
chooserTitle: n("avatar.chooserTitle", "Choose an avatar"),
|
|
@@ -246,6 +246,7 @@ export declare const ASSISTANT_I18N_LITERAL_COPY: {
|
|
|
246
246
|
readonly "avatar.stateOn": "Avatar on";
|
|
247
247
|
readonly "avatar.stateReady": "Avatar ready";
|
|
248
248
|
readonly "avatar.stateStarting": "Starting the avatar…";
|
|
249
|
+
readonly "avatar.stateTimeLimit": "The avatar reached its time limit. Continuing with voice only.";
|
|
249
250
|
readonly "avatar.stateVoiceOnly": "The avatar is unavailable right now. Continuing with voice only.";
|
|
250
251
|
readonly "avatar.stop": "Turn off the avatar";
|
|
251
252
|
readonly "avatar.toggle": "Live avatar";
|
|
@@ -140,6 +140,7 @@ var m = {
|
|
|
140
140
|
"avatar.stateOn": "Avatar on",
|
|
141
141
|
"avatar.stateReady": "Avatar ready",
|
|
142
142
|
"avatar.stateStarting": "Starting the avatar…",
|
|
143
|
+
"avatar.stateTimeLimit": "The avatar reached its time limit. Continuing with voice only.",
|
|
143
144
|
"avatar.stateVoiceOnly": "The avatar is unavailable right now. Continuing with voice only.",
|
|
144
145
|
"avatar.stop": "Turn off the avatar",
|
|
145
146
|
"avatar.toggle": "Live avatar",
|
|
@@ -313,3 +313,20 @@ export * from './workflowApi';
|
|
|
313
313
|
export * from './ui/WorkflowAssistantPanel';
|
|
314
314
|
export * from './speech/notifyCancellationSpeechEngine';
|
|
315
315
|
export * from './avatar/avatarPreferences';
|
|
316
|
+
export * from './avatar/live/avatarProtocol';
|
|
317
|
+
export * from './avatar/live/avatarPorts';
|
|
318
|
+
export * from './avatar/live/avatarTransport';
|
|
319
|
+
export * from './avatar/live/avatarPlaybackTracker';
|
|
320
|
+
export * from './avatar/live/avatarAudioMeter';
|
|
321
|
+
export * from './avatar/live/avatarAudioErrors';
|
|
322
|
+
export * from './avatar/live/avatarMp3Inspect';
|
|
323
|
+
export * from './avatar/live/avatarPcm16';
|
|
324
|
+
export * from './avatar/live/avatarAudioDecode';
|
|
325
|
+
export * from './avatar/live/avatarTransportPort';
|
|
326
|
+
export * from './avatar/live/avatarReplyOutputs';
|
|
327
|
+
export * from './avatar/live/avatarSessionClient';
|
|
328
|
+
export * from './avatar/live/liveAvatarController';
|
|
329
|
+
export * from './avatar/live/liveKitAvatarRoom';
|
|
330
|
+
export * from './avatar/live/LiveAvatarStage';
|
|
331
|
+
export * from './avatar/live/liveAvatarStatus';
|
|
332
|
+
export * from './avatar/live/createLiveAvatarSeam';
|
|
@@ -7,6 +7,11 @@ export interface HostedTtsOptions {
|
|
|
7
7
|
fetchImpl?: typeof fetch;
|
|
8
8
|
/** Test seam: the clock the disable window is measured against. */
|
|
9
9
|
now?: () => number;
|
|
10
|
+
/**
|
|
11
|
+
* Test seam: the timer behind the audio output's no-progress watchdog
|
|
12
|
+
* (`hostedOutputStallMs`, BOFF-8822). Returns a function that cancels it.
|
|
13
|
+
*/
|
|
14
|
+
setTimer?: (callback: () => void, ms: number) => () => void;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
17
|
* The hosted engine, plus the one hook its owner needs beyond `SpeechEngine`.
|
|
@@ -23,4 +28,36 @@ export interface HostedSpeechEngine extends SpeechEngine {
|
|
|
23
28
|
}
|
|
24
29
|
/** `Retry-After` in seconds when the server sent a usable one, else backoff. */
|
|
25
30
|
export declare function retryDelayMs(retryAfter: string | null, attempt: number): number;
|
|
31
|
+
/**
|
|
32
|
+
* ★★ The backstop for an audio output that stops making progress (BOFF-8822,
|
|
33
|
+
* the voice owners' P1b note a, 2026-10-13).
|
|
34
|
+
*
|
|
35
|
+
* `speak()` waits for its output's `write()` and `drain()` before it fetches
|
|
36
|
+
* more or ends the reply, and the output contract says both settle in bounded
|
|
37
|
+
* time (`SpeechAudioOutput`). One that never did (a contract bug in an output)
|
|
38
|
+
* would leave the reply "speaking", and the voice conversation with it, until
|
|
39
|
+
* the user pressed Stop. So while the engine waits on its output, a watchdog
|
|
40
|
+
* runs, and every sign of progress starts it again: a piece beginning, on the
|
|
41
|
+
* output or replayed on the speakers, and every write or drain settling.
|
|
42
|
+
*
|
|
43
|
+
* ★ Not a fixed timeout. A write may resolve only when its piece BEGINS, so it
|
|
44
|
+
* can wait for every earlier piece to play out, and a drain waits for all the
|
|
45
|
+
* audio held: a fixed cap would end long, healthy replies. The window grows with
|
|
46
|
+
* the audio handed over, bounded from above by its bytes at the lowest MP3
|
|
47
|
+
* bitrate (8 kbit/s; the proxy sends 32), so it is never shorter than twice that
|
|
48
|
+
* audio plus a minute. The output's own bounds (the live avatar's start, stall
|
|
49
|
+
* and finish deadlines: its start allowance, 1.25 times the reply's audio and a
|
|
50
|
+
* few seconds of slack) run out well inside it, so the output always decides
|
|
51
|
+
* first, and this only catches an output that never does.
|
|
52
|
+
*
|
|
53
|
+
* On expiry the output and the speakers are silenced BEFORE the reply ends with
|
|
54
|
+
* `onError('unknown')`, which latches nothing; the output is not drained again.
|
|
55
|
+
*/
|
|
56
|
+
export declare const HOSTED_OUTPUT_STALL_BASE_MS = 60000;
|
|
57
|
+
/** How many times the audio handed over the window adds on top of the base. */
|
|
58
|
+
export declare const HOSTED_OUTPUT_STALL_AUDIO_RATIO = 2;
|
|
59
|
+
/** 8 kbit/s, the lowest MP3 bitrate: bytes over this are at least the audio's seconds. */
|
|
60
|
+
export declare const HOSTED_OUTPUT_MIN_AUDIO_BYTES_PER_SECOND = 1000;
|
|
61
|
+
/** The watchdog's window once `bytesHanded` bytes of audio went to the output. */
|
|
62
|
+
export declare function hostedOutputStallMs(bytesHanded: number): number;
|
|
26
63
|
export declare function createHostedTtsEngine(options: HostedTtsOptions): HostedSpeechEngine;
|
|
@@ -18,7 +18,53 @@ function _(e, t) {
|
|
|
18
18
|
let n = e === null ? NaN : Number(e);
|
|
19
19
|
return Math.min(Number.isFinite(n) && n >= 0 ? n * 1e3 : h * 2 ** (t - 1), g);
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
var v = 6e4, y = 2, b = 1e3;
|
|
22
|
+
function x(e) {
|
|
23
|
+
return v + e / b * 2 * 1e3;
|
|
24
|
+
}
|
|
25
|
+
var S = class extends Error {
|
|
26
|
+
reason = "unknown";
|
|
27
|
+
constructor() {
|
|
28
|
+
super("audio output made no progress"), this.name = "OutputStalledError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function C(e) {
|
|
32
|
+
let t = 0, n = !1, r = null, i = () => {
|
|
33
|
+
let n = r;
|
|
34
|
+
n !== null && (n.cancel(), n.cancel = e(() => {
|
|
35
|
+
r === n && (r = null, n.fail(new S()));
|
|
36
|
+
}, x(t)));
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
handed(e) {
|
|
40
|
+
t += e;
|
|
41
|
+
},
|
|
42
|
+
progress: i,
|
|
43
|
+
guard(e) {
|
|
44
|
+
return n ? e : new Promise((t, n) => {
|
|
45
|
+
let a = {
|
|
46
|
+
fail: n,
|
|
47
|
+
cancel: () => void 0
|
|
48
|
+
};
|
|
49
|
+
r = a, i();
|
|
50
|
+
let o = () => {
|
|
51
|
+
r === a && (r = null, a.cancel());
|
|
52
|
+
};
|
|
53
|
+
e.then((e) => {
|
|
54
|
+
o(), t(e);
|
|
55
|
+
}, (e) => {
|
|
56
|
+
o(), n(e);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
stop() {
|
|
61
|
+
n = !0;
|
|
62
|
+
let e = r;
|
|
63
|
+
r = null, e?.cancel();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function w(e, t) {
|
|
22
68
|
return new Promise((n) => {
|
|
23
69
|
if (t.aborted) {
|
|
24
70
|
n();
|
|
@@ -33,32 +79,35 @@ function v(e, t) {
|
|
|
33
79
|
t.addEventListener("abort", i, { once: !0 });
|
|
34
80
|
});
|
|
35
81
|
}
|
|
36
|
-
function
|
|
37
|
-
let g = h.fetchImpl ?? ((...e) => fetch(...e)),
|
|
82
|
+
function T(h) {
|
|
83
|
+
let g = h.fetchImpl ?? ((...e) => fetch(...e)), v = h.now ?? (() => Date.now()), y = h.setTimer ?? ((e, t) => {
|
|
84
|
+
let n = setTimeout(e, t);
|
|
85
|
+
return () => clearTimeout(n);
|
|
86
|
+
}), b = null, x = null, T = 0, E = null, D = null, O = null, k = 0, A = null, j = null, M = () => {
|
|
38
87
|
try {
|
|
39
88
|
return h.getHeaders?.() ?? {};
|
|
40
89
|
} catch {
|
|
41
90
|
return {};
|
|
42
91
|
}
|
|
43
|
-
},
|
|
44
|
-
for (let e of [...
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
let e =
|
|
92
|
+
}, N = () => v() >= k ? !1 : j === null ? !0 : f(M()) === j, P = /* @__PURE__ */ new Set(), F = () => {
|
|
93
|
+
for (let e of [...P]) e();
|
|
94
|
+
}, I = () => {
|
|
95
|
+
A !== null && clearTimeout(A), A = null;
|
|
96
|
+
}, L = (e) => {
|
|
97
|
+
I(), A = setTimeout(() => {
|
|
98
|
+
A = null;
|
|
99
|
+
let e = k - v();
|
|
51
100
|
if (e > 0) {
|
|
52
|
-
|
|
101
|
+
L(e);
|
|
53
102
|
return;
|
|
54
103
|
}
|
|
55
|
-
|
|
104
|
+
F();
|
|
56
105
|
}, e);
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
},
|
|
106
|
+
}, R = (e, t) => {
|
|
107
|
+
k = v() + e, j = t, L(e), F();
|
|
108
|
+
}, z = () => {
|
|
60
109
|
x &&= (URL.revokeObjectURL(x), null);
|
|
61
|
-
},
|
|
110
|
+
}, B = () => {
|
|
62
111
|
if (b) {
|
|
63
112
|
b.onended = null, b.onerror = null;
|
|
64
113
|
try {
|
|
@@ -66,9 +115,9 @@ function y(h) {
|
|
|
66
115
|
} catch {}
|
|
67
116
|
b = null;
|
|
68
117
|
}
|
|
69
|
-
|
|
118
|
+
z();
|
|
70
119
|
};
|
|
71
|
-
async function
|
|
120
|
+
async function V(e, n, s) {
|
|
72
121
|
let c = i(e, r(n.lang) ?? n.lang, t, n.voice?.name ?? null, n.rate), l = h.getHeaders?.() ?? {}, u;
|
|
73
122
|
for (let e = 1; u = await g(h.endpoint, {
|
|
74
123
|
method: "POST",
|
|
@@ -83,37 +132,37 @@ function y(h) {
|
|
|
83
132
|
let e = /* @__PURE__ */ Error(`hosted tts ${u.status}`);
|
|
84
133
|
throw e.reason = a(u.status), e.headers = f(l), e;
|
|
85
134
|
}
|
|
86
|
-
await
|
|
135
|
+
await w(_(u.headers.get("retry-after"), e), s);
|
|
87
136
|
}
|
|
88
|
-
let d = u.headers.get("content-type"),
|
|
89
|
-
if (!
|
|
137
|
+
let d = u.headers.get("content-type"), v = o(d, d?.includes("json") ? await u.json() : await u.arrayBuffer());
|
|
138
|
+
if (!v) {
|
|
90
139
|
let e = /* @__PURE__ */ Error("hosted tts returned no audio");
|
|
91
140
|
throw e.reason = "unknown", e;
|
|
92
141
|
}
|
|
93
|
-
return
|
|
142
|
+
return v;
|
|
94
143
|
}
|
|
95
|
-
function
|
|
144
|
+
function H(t, n, r) {
|
|
96
145
|
return new Promise((i, a) => {
|
|
97
|
-
if (n !==
|
|
146
|
+
if (n !== T) {
|
|
98
147
|
i();
|
|
99
148
|
return;
|
|
100
149
|
}
|
|
101
150
|
let o = new Blob([t.slice().buffer], { type: "audio/mpeg" });
|
|
102
|
-
|
|
151
|
+
z(), x = URL.createObjectURL(o);
|
|
103
152
|
let s = new Audio(x);
|
|
104
153
|
e(s), b = s, s.onended = () => {
|
|
105
|
-
n ===
|
|
154
|
+
n === T && i();
|
|
106
155
|
}, s.onerror = () => {
|
|
107
|
-
if (n !==
|
|
156
|
+
if (n !== T) {
|
|
108
157
|
i();
|
|
109
158
|
return;
|
|
110
159
|
}
|
|
111
160
|
let e = /* @__PURE__ */ Error("audio playback failed");
|
|
112
161
|
e.reason = "unknown", a(e);
|
|
113
162
|
}, s.play().then(() => {
|
|
114
|
-
n ===
|
|
163
|
+
n === T && r?.();
|
|
115
164
|
}, (e) => {
|
|
116
|
-
if (n !==
|
|
165
|
+
if (n !== T) {
|
|
117
166
|
i();
|
|
118
167
|
return;
|
|
119
168
|
}
|
|
@@ -125,14 +174,14 @@ function y(h) {
|
|
|
125
174
|
return {
|
|
126
175
|
id: "hosted-tts",
|
|
127
176
|
isSupported() {
|
|
128
|
-
return
|
|
177
|
+
return N() ? !1 : typeof window < "u" && typeof window.Audio == "function" && typeof fetch == "function";
|
|
129
178
|
},
|
|
130
179
|
listVoices() {
|
|
131
|
-
return
|
|
180
|
+
return N() ? [] : [...n];
|
|
132
181
|
},
|
|
133
182
|
subscribeVoices(e) {
|
|
134
|
-
return
|
|
135
|
-
|
|
183
|
+
return P.add(e), () => {
|
|
184
|
+
P.delete(e);
|
|
136
185
|
};
|
|
137
186
|
},
|
|
138
187
|
speak(e, t, n = {}) {
|
|
@@ -141,66 +190,72 @@ function y(h) {
|
|
|
141
190
|
n.onEnd?.();
|
|
142
191
|
return;
|
|
143
192
|
}
|
|
144
|
-
|
|
145
|
-
let i =
|
|
146
|
-
|
|
193
|
+
T += 1;
|
|
194
|
+
let i = T;
|
|
195
|
+
B(), D?.cancel(), O?.stop();
|
|
147
196
|
let a = new AbortController();
|
|
148
|
-
|
|
197
|
+
E = a;
|
|
149
198
|
let o = !1, f = () => {
|
|
150
199
|
o || (o = !0, n.onStart?.());
|
|
151
200
|
}, p = (e) => {
|
|
152
|
-
let n =
|
|
201
|
+
let n = V(e, t, a.signal);
|
|
153
202
|
return n.catch(() => {}), n;
|
|
154
|
-
}, m = t.audioOutput ? u(t.audioOutput, {
|
|
155
|
-
play: (e) =>
|
|
156
|
-
isCurrent: () => i ===
|
|
157
|
-
onChunkStart: (e) =>
|
|
203
|
+
}, m = t.audioOutput ? C(y) : null, h = t.audioOutput ? u(t.audioOutput, {
|
|
204
|
+
play: (e) => H(e, i, f),
|
|
205
|
+
isCurrent: () => i === T,
|
|
206
|
+
onChunkStart: (e) => {
|
|
207
|
+
m?.progress(), n.onChunkStart?.(e);
|
|
208
|
+
},
|
|
158
209
|
onStart: f
|
|
159
210
|
}) : null;
|
|
160
|
-
|
|
211
|
+
D = h, O = m;
|
|
212
|
+
let g = (e) => m ? m.guard(e) : e, _ = () => {
|
|
213
|
+
h && (m?.stop(), O === m && (O = null), D === h && (D = null));
|
|
214
|
+
};
|
|
215
|
+
(async () => {
|
|
161
216
|
try {
|
|
162
217
|
let e = p(r[0]);
|
|
163
218
|
for (let t = 0; t < r.length; t += 1) {
|
|
164
|
-
if (i !==
|
|
219
|
+
if (i !== T) return;
|
|
165
220
|
let a = await e;
|
|
166
|
-
if (i !==
|
|
221
|
+
if (i !== T) return;
|
|
167
222
|
let o = r[t + 1];
|
|
168
|
-
if (o !== void 0 && (e = p(o)),
|
|
169
|
-
await
|
|
223
|
+
if (o !== void 0 && (e = p(o)), h) {
|
|
224
|
+
m?.handed(a.byteLength), await g(h.write(a));
|
|
170
225
|
continue;
|
|
171
226
|
}
|
|
172
|
-
n.onChunkStart?.(t), await
|
|
227
|
+
n.onChunkStart?.(t), await H(a, i, f);
|
|
173
228
|
}
|
|
174
|
-
if (
|
|
175
|
-
let e = await
|
|
176
|
-
if (i !==
|
|
229
|
+
if (h) {
|
|
230
|
+
let e = await g(h.drain());
|
|
231
|
+
if (i !== T) return;
|
|
177
232
|
if (!e) throw Object.assign(/* @__PURE__ */ Error("audio output did not finish"), { reason: "unknown" });
|
|
178
233
|
}
|
|
179
|
-
i ===
|
|
234
|
+
i === T && (_(), n.onEnd?.());
|
|
180
235
|
} catch (e) {
|
|
181
|
-
if (a.abort(), i !==
|
|
236
|
+
if (a.abort(), i !== T) return;
|
|
182
237
|
let t = null;
|
|
183
|
-
if (
|
|
184
|
-
let n = await
|
|
185
|
-
if (i !==
|
|
186
|
-
|
|
238
|
+
if (h) {
|
|
239
|
+
let n = e instanceof S ? null : await g(h.drain()).then(() => null, (e) => e);
|
|
240
|
+
if (i !== T) return;
|
|
241
|
+
h.cancel(), d(e) ? t = e : d(n) && (t = n);
|
|
187
242
|
}
|
|
188
|
-
if (
|
|
243
|
+
if (T += 1, B(), _(), t) {
|
|
189
244
|
n.onError?.("output-not-cleared");
|
|
190
245
|
return;
|
|
191
246
|
}
|
|
192
247
|
let r = e?.reason;
|
|
193
|
-
c(r, e?.transient === !0) &&
|
|
248
|
+
c(r, e?.transient === !0) && R(s(r), r === "not-allowed" ? e.headers ?? null : null), n.onError?.(r === "not-allowed" || r === "unsupported" || r === "cancelled" ? r : "unknown");
|
|
194
249
|
}
|
|
195
250
|
})();
|
|
196
251
|
},
|
|
197
252
|
headersChanged() {
|
|
198
|
-
|
|
253
|
+
v() >= k || j === null || N() || (k = 0, j = null, I(), F());
|
|
199
254
|
},
|
|
200
255
|
cancel() {
|
|
201
|
-
|
|
256
|
+
T += 1, E?.abort(), E = null, B(), D?.cancel(), D = null, O?.stop(), O = null;
|
|
202
257
|
}
|
|
203
258
|
};
|
|
204
259
|
}
|
|
205
260
|
//#endregion
|
|
206
|
-
export { y as createHostedTtsEngine, _ as retryDelayMs };
|
|
261
|
+
export { b as HOSTED_OUTPUT_MIN_AUDIO_BYTES_PER_SECOND, y as HOSTED_OUTPUT_STALL_AUDIO_RATIO, v as HOSTED_OUTPUT_STALL_BASE_MS, T as createHostedTtsEngine, x as hostedOutputStallMs, _ as retryDelayMs };
|
|
@@ -61,6 +61,16 @@ export interface SpeechSpeakOptions {
|
|
|
61
61
|
*
|
|
62
62
|
* One object serves exactly one reply: attach a fresh one to each, so neither
|
|
63
63
|
* a leave, a refusal nor the hard stop carries over to the next reply.
|
|
64
|
+
*
|
|
65
|
+
* ★★ `write()` and `drain()` MUST settle in bounded time. The engine waits for
|
|
66
|
+
* each one before it fetches more, falls back, or ends the reply, so a call that
|
|
67
|
+
* never settles would leave the reply "speaking" until the user pressed Stop.
|
|
68
|
+
* Each output bounds its own waits (the live avatar: its start, stall and finish
|
|
69
|
+
* deadlines); an output that cannot tell what happened settles as uncertain, by
|
|
70
|
+
* the rules above, rather than wait. The engine keeps only a backstop: when its
|
|
71
|
+
* output makes no progress for far longer than any such bound
|
|
72
|
+
* (`hostedOutputStallMs`), it silences the output and ends the reply with
|
|
73
|
+
* `onError('unknown')`, which latches nothing.
|
|
64
74
|
*/
|
|
65
75
|
export interface SpeechAudioOutput {
|
|
66
76
|
/**
|
|
@@ -74,7 +84,8 @@ export interface SpeechAudioOutput {
|
|
|
74
84
|
* before rejecting, for any piece that MAY have been heard: the speakers
|
|
75
85
|
* resume after the last piece that began, so nothing is heard twice. A piece
|
|
76
86
|
* retracted before it began resolves its `write` without `began()`. Writing
|
|
77
|
-
* may go on after a `drain()`, until `cancel()` or the reply's end.
|
|
87
|
+
* may go on after a `drain()`, until `cancel()` or the reply's end. MUST
|
|
88
|
+
* settle in bounded time, whatever the output hears or does not hear.
|
|
78
89
|
*/
|
|
79
90
|
write(bytes: Uint8Array, index: number, began: () => void): Promise<void>;
|
|
80
91
|
/**
|
|
@@ -82,7 +93,8 @@ export interface SpeechAudioOutput {
|
|
|
82
93
|
* `write` does. Callable more than once: a queue may drain when its speech
|
|
83
94
|
* pauses and write again afterwards, even before that drain settles (a piece
|
|
84
95
|
* written meanwhile is not part of it). The reply's last drain follows its
|
|
85
|
-
* end and its last write.
|
|
96
|
+
* end and its last write. MUST settle in bounded time: when the end cannot be
|
|
97
|
+
* confirmed, reject, rather than wait for a signal that may never come.
|
|
86
98
|
*/
|
|
87
99
|
drain(): Promise<void>;
|
|
88
100
|
/**
|