@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,170 @@
|
|
|
1
|
+
import { SpeechAudioOutput, SpeechOutputRefusal } from '../../speech/types';
|
|
2
|
+
import { AvatarScheduler, AvatarUnsubscribe } from './avatarPorts';
|
|
3
|
+
import { AvatarBeforeAudioReason, AvatarTrackerConfig, AvatarUncertainReason } from './avatarPlaybackTracker';
|
|
4
|
+
import { AvatarClearOptions, AvatarClearOutcome, AvatarSegmentWriter, AvatarTransportEvent } from './avatarTransport';
|
|
5
|
+
export type AvatarPortEvent = AvatarTransportEvent | {
|
|
6
|
+
readonly type: 'meter';
|
|
7
|
+
readonly audible: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** What the outputs need from the live avatar session. Faked in tests. */
|
|
10
|
+
export interface AvatarOutputPort {
|
|
11
|
+
/** Avatar switched ON and ready. Evaluated when a reply starts writing, and before a retry. */
|
|
12
|
+
isActive(): boolean;
|
|
13
|
+
/** The browser currently blocks the avatar's audio (autoplay policy). */
|
|
14
|
+
isAudioBlocked(): boolean;
|
|
15
|
+
/** Identifies the current avatar connection. Only the first avatar reply of a connection can be hit by a first-stream race. */
|
|
16
|
+
connectionId(): number;
|
|
17
|
+
/**
|
|
18
|
+
* True while the level meter can really hear the avatar (it exists and its AudioContext is running). Without it, "nothing was
|
|
19
|
+
* heard" cannot be proven, so no-start detection (and with it the speakers fallback and the one-time retry) is switched off.
|
|
20
|
+
*/
|
|
21
|
+
isMeterHealthy(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The level meter's current reading: true while it hears audio on the avatar's received track. Provisional, and meaningful only while
|
|
24
|
+
* isMeterHealthy(). The meter reports changes only, so this is where a wait for the avatar's audio to drain starts from.
|
|
25
|
+
*/
|
|
26
|
+
isMeterAudible(): boolean;
|
|
27
|
+
openSegment(): Promise<AvatarSegmentWriter>;
|
|
28
|
+
/**
|
|
29
|
+
* Interrupt the avatar and wait (bounded) until it reports finished(interrupted); resolves with what the clear achieved. `options` says
|
|
30
|
+
* whether the avatar's report of the segment it ends is already overdue (AvatarClearOptions in avatarTransport.ts).
|
|
31
|
+
*/
|
|
32
|
+
clearBuffer(options?: AvatarClearOptions): Promise<AvatarClearOutcome>;
|
|
33
|
+
/** Instantly mute or unmute the avatar's audio element. */
|
|
34
|
+
setOutputMuted(muted: boolean): void;
|
|
35
|
+
subscribe(handler: (event: AvatarPortEvent) => void): AvatarUnsubscribe;
|
|
36
|
+
}
|
|
37
|
+
export type AvatarNotice = {
|
|
38
|
+
readonly kind: 'voice-fallback';
|
|
39
|
+
readonly reason: AvatarBeforeAudioReason;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: 'interrupted';
|
|
42
|
+
readonly reason: AvatarUncertainReason;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: 'audio-blocked';
|
|
45
|
+
}
|
|
46
|
+
/** The chosen voice is a device voice: it produces no audio data, so the avatar cannot speak it. The normal voice speaks instead. */
|
|
47
|
+
| {
|
|
48
|
+
readonly kind: 'needs-hosted-voice';
|
|
49
|
+
}
|
|
50
|
+
/** The first reply of a connection played nothing; it is being written to the avatar once more from the same audio. */
|
|
51
|
+
| {
|
|
52
|
+
readonly kind: 'first-reply-retry';
|
|
53
|
+
readonly attempt: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The avatar's earlier output could not be established as stopped (see `outputDrained`), so this reply was not started anywhere: nothing
|
|
57
|
+
* was written or unmuted, the speakers did not play it, and it is not retried. The hosted engine ends it with
|
|
58
|
+
* onError('output-not-cleared'), and the voice session shows its "reply not heard" notice (P1c); the reply's text stays where it is.
|
|
59
|
+
*/
|
|
60
|
+
| {
|
|
61
|
+
readonly kind: 'output-not-cleared';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Nothing could establish that the avatar's earlier output stopped (no meter could hear it, and the avatar did not confirm the clear), but
|
|
65
|
+
* everything written to the avatar must have played out by now (the time-based release, see `playedOutAt`): that output counts as played
|
|
66
|
+
* out, and avatar replies go ahead again. Sent once, when the release is decided, whether or not a reply is waiting for it.
|
|
67
|
+
* `sincePlayedOutMs`: how long ago that moment passed.
|
|
68
|
+
*/
|
|
69
|
+
| {
|
|
70
|
+
readonly kind: 'output-released';
|
|
71
|
+
readonly sincePlayedOutMs: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The avatar reported a reply played out (its own lk.playback_finished), but a meter that can hear still heard audio when the tail wait
|
|
75
|
+
* after that report ran out: the output was muted BEFORE the reply was reported ended, so the microphone never reopens on audible avatar
|
|
76
|
+
* audio, and the next reply waits for that audio to drain as after any clear (see `outputDrained`). The reply is still reported
|
|
77
|
+
* completed, which is what the avatar confirmed; nothing claims that its audio ended by itself.
|
|
78
|
+
*/
|
|
79
|
+
| {
|
|
80
|
+
readonly kind: 'output-muted-at-end';
|
|
81
|
+
};
|
|
82
|
+
/** One piece of a reply, decoded for the avatar: PCM16 little-endian, mono, 16 kHz. */
|
|
83
|
+
export interface AvatarDecodedPiece {
|
|
84
|
+
readonly pcm: Uint8Array;
|
|
85
|
+
readonly durationSec: number;
|
|
86
|
+
}
|
|
87
|
+
export interface AvatarReplyOutputsDeps {
|
|
88
|
+
readonly port: AvatarOutputPort;
|
|
89
|
+
readonly scheduler: AvatarScheduler;
|
|
90
|
+
/** The hosted proxy's MP3 bytes to PCM16 16 kHz mono (`decodeMp3ToPcm16k` in production). Must honour the signal. */
|
|
91
|
+
readonly decode: (bytes: Uint8Array, signal: AbortSignal) => Promise<AvatarDecodedPiece>;
|
|
92
|
+
readonly trackerConfig?: AvatarTrackerConfig;
|
|
93
|
+
/**
|
|
94
|
+
* The next piece takes longer than this to be ready while the avatar may need it: a stall. Counted from the moment the engine could hand
|
|
95
|
+
* the next piece over, which (writes resolve when a piece begins) is when the previous piece began; for a piece already handed over,
|
|
96
|
+
* from the moment its decode is awaited.
|
|
97
|
+
*/
|
|
98
|
+
readonly stallMs?: number;
|
|
99
|
+
/** Writes are cut into slices of this duration so a cancel between slices drops the rest. */
|
|
100
|
+
readonly writeSliceMs?: number;
|
|
101
|
+
/**
|
|
102
|
+
* After a clear, the next reply unmutes only once the avatar's audio on the received track has drained (see `outputDrained`). This caps
|
|
103
|
+
* that wait, counted from the clear's settlement (from its own start for a reply that measures again after a failed wait). It is a
|
|
104
|
+
* safety bound, not the expected wait: it ends a wait in which the meter never reads quiet (an avatar that ignores the clear, a meter
|
|
105
|
+
* stuck on noise), and the wait for a stopped reply's first words while no start latency has been measured on the connection. The real
|
|
106
|
+
* worker's audio went quiet 1149 ms after a Stop (contract test, 2026-10-09, the meter's own hangover included), so the default leaves
|
|
107
|
+
* well over twice that before a tail could be cut short. A meter that can hear when the bound runs out decides: audio, or quiet that has
|
|
108
|
+
* not yet held for the tracker's tail, means the earlier output was NOT established as stopped, and the next reply is then refused
|
|
109
|
+
* instead of unmuting (operator decision 7, 2026-10-09). Without a meter that can hear, only the avatar's confirmation of the clear plus
|
|
110
|
+
* this whole bound since it lets the next reply unmute, and a confirmation that a failed wait disproved no longer counts. Without that
|
|
111
|
+
* confirmation either, only the time-based release can (see `playedOutAt`).
|
|
112
|
+
*/
|
|
113
|
+
readonly maxDrainMs?: number;
|
|
114
|
+
readonly onNotice?: (notice: AvatarNotice) => void;
|
|
115
|
+
/** A reply starts using the avatar (true) or is over (false): the session's idle and time-limit rules read it. */
|
|
116
|
+
readonly onActivity?: (busy: boolean) => void;
|
|
117
|
+
}
|
|
118
|
+
export interface AvatarReplyOutputs {
|
|
119
|
+
/** A fresh output for ONE reply. It does nothing until the hosted engine writes or drains it. */
|
|
120
|
+
createOutput(): SpeechAudioOutput;
|
|
121
|
+
/**
|
|
122
|
+
* The avatar was switched off while a reply plays on it (its toggle, the compact view's stop, the end of the voice conversation, a
|
|
123
|
+
* session that ended): that reply LEAVES. Its sound stops at once, what may have been heard counts as begun, and the rest plays on the
|
|
124
|
+
* speakers from the same bytes (R4). Never the engine's `cancel()`, which would silence the reply.
|
|
125
|
+
*/
|
|
126
|
+
leave(): void;
|
|
127
|
+
/** A reply is using the avatar right now. */
|
|
128
|
+
isBusy(): boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The start latency the time-based release counts on a connection where none was measured (see `playedOutAt`): a stand-in, not a measured
|
|
132
|
+
* value, and never less than the tracker's own start allowance. The paid contract test (2026-10-09) measured 0.89–0.95 s from our write to
|
|
133
|
+
* audible avatar audio, so this is more than three times that. A test pins the floor set for the release: never below 2 s.
|
|
134
|
+
*/
|
|
135
|
+
export declare const UNMEASURED_START_LATENCY_MS = 3000;
|
|
136
|
+
/**
|
|
137
|
+
* Added to the time-based release's moment (see `playedOutAt`) for what the start latency and the pushed duration leave out: jitter in the
|
|
138
|
+
* avatar's start and in the received track's buffering. In the paid contract test (2026-10-09) the start latency varied by 0.06 s. The
|
|
139
|
+
* meter went quiet 0.32 s after the avatar's own lk.playback_finished, but that includes the meter's 400 ms hangover
|
|
140
|
+
* (DEFAULT_AVATAR_LEVEL_CONFIG), so the audio itself ended about when that finished came (some 0.08 s before it). 1 s is more than ten
|
|
141
|
+
* times either figure.
|
|
142
|
+
*/
|
|
143
|
+
export declare const RELEASE_MARGIN_MS = 1000;
|
|
144
|
+
/**
|
|
145
|
+
* When a reply may have been heard and stops early (uncertain, or a leave), a piece already handed to the avatar counts as begun when its
|
|
146
|
+
* place in the stream (the reply's observed start plus the pieces before it) is less than this far ahead. The observed start can lag the
|
|
147
|
+
* audible one by the meter's attack or the start signal's trip (tens of milliseconds); one second more than covers it, on the side that
|
|
148
|
+
* never repeats a word: a piece counted begun is never played again on the speakers. New with P8: the harness never replayed after audio
|
|
149
|
+
* may have been heard, and the speakers fallback (R4) needs the line drawn somewhere.
|
|
150
|
+
*/
|
|
151
|
+
export declare const MAY_HAVE_BEGUN_MARGIN_MS = 1000;
|
|
152
|
+
/** How a reply that stops before its end reaches the hosted engine (SpeechAudioOutput). Its reason is never read beyond these. */
|
|
153
|
+
export declare class AvatarOutputFailure extends Error {
|
|
154
|
+
readonly outcome: string;
|
|
155
|
+
readonly reason: "avatar-output-failed";
|
|
156
|
+
constructor(outcome: string);
|
|
157
|
+
}
|
|
158
|
+
/** The avatar was switched off while the reply goes on (SPEECH_OUTPUT_LEFT): the rest plays on the speakers. */
|
|
159
|
+
export declare class AvatarOutputLeft extends Error {
|
|
160
|
+
readonly reason = "left";
|
|
161
|
+
constructor();
|
|
162
|
+
}
|
|
163
|
+
/** The avatar's earlier output could not be shown stopped (SPEECH_OUTPUT_REFUSE_AUDIBLE): the reply is heard nowhere. */
|
|
164
|
+
export declare class AvatarOutputRefusal extends Error implements SpeechOutputRefusal {
|
|
165
|
+
readonly reason = "refuse-audible";
|
|
166
|
+
constructor();
|
|
167
|
+
}
|
|
168
|
+
/** Whole-sample slices of about `ms` of PCM16 16 kHz mono. */
|
|
169
|
+
export declare function sliceAvatarPcm(pcm: Uint8Array, ms: number): Uint8Array[];
|
|
170
|
+
export declare function createAvatarReplyOutputs(deps: AvatarReplyOutputsDeps): AvatarReplyOutputs;
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { SPEECH_OUTPUT_LEFT as e, SPEECH_OUTPUT_REFUSE_AUDIBLE as t } from "../../speech/types.js";
|
|
2
|
+
import { AVATAR_PCM_BYTES_PER_SECOND as n } from "./avatarProtocol.js";
|
|
3
|
+
import { AvatarSegmentEndedError as r } from "./avatarTransport.js";
|
|
4
|
+
import { DEFAULT_AVATAR_TRACKER_CONFIG as i, ReplyPlaybackTracker as a } from "./avatarPlaybackTracker.js";
|
|
5
|
+
//#region src/shared/assistant/avatar/live/avatarReplyOutputs.ts
|
|
6
|
+
var o = 2, s = .1, c = 3e3, l = 1e3, u = 1e3, d = class extends Error {
|
|
7
|
+
outcome;
|
|
8
|
+
reason = "avatar-output-failed";
|
|
9
|
+
constructor(e) {
|
|
10
|
+
super(`avatar output failed: ${e}`), this.outcome = e, this.name = "AvatarOutputFailure";
|
|
11
|
+
}
|
|
12
|
+
}, f = class extends Error {
|
|
13
|
+
reason = e;
|
|
14
|
+
constructor() {
|
|
15
|
+
super("avatar output left the reply"), this.name = "AvatarOutputLeft";
|
|
16
|
+
}
|
|
17
|
+
}, p = class extends Error {
|
|
18
|
+
reason = t;
|
|
19
|
+
constructor() {
|
|
20
|
+
super("avatar refused the reply: earlier audio may still be playing"), this.name = "AvatarOutputRefusal";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function m(e) {
|
|
24
|
+
return !e.synthetic || e.rpc === "ok" || e.rpc === "none";
|
|
25
|
+
}
|
|
26
|
+
function h(e, t) {
|
|
27
|
+
let r = Math.max(2, Math.floor(n * t / 1e3 / 2) * 2), i = [];
|
|
28
|
+
for (let t = 0; t < e.byteLength; t += r) i.push(e.subarray(t, Math.min(e.byteLength, t + r)));
|
|
29
|
+
return i;
|
|
30
|
+
}
|
|
31
|
+
function g(e) {
|
|
32
|
+
let { port: t, scheduler: n } = e, u = e.trackerConfig ?? i, g = e.stallMs ?? 6e3, _ = e.writeSliceMs ?? 200, v = e.maxDrainMs ?? 3e3, y = null, b = null, x = null, S = null, C = null, w = null, T = (e) => y === e && e.end === null, E = (e) => T(e.reply) && e.reply.attempt === e, D = () => u.meterSupported && t.isMeterHealthy();
|
|
33
|
+
function O(t) {
|
|
34
|
+
try {
|
|
35
|
+
e.onNotice?.(t);
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
function k(t) {
|
|
39
|
+
try {
|
|
40
|
+
e.onActivity?.(t);
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
function A(e) {
|
|
44
|
+
return C !== null && C.connection === e ? C.ms : u.startLatencyAllowanceMs;
|
|
45
|
+
}
|
|
46
|
+
function ee(e) {
|
|
47
|
+
let t = e.tracker.audibleAfterMs;
|
|
48
|
+
if (t === null) return;
|
|
49
|
+
let { connection: n } = e.reply;
|
|
50
|
+
(C === null || C.connection !== n || t > C.ms) && (C = {
|
|
51
|
+
connection: n,
|
|
52
|
+
ms: t
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function j(e) {
|
|
56
|
+
return C !== null && C.connection === e ? C.ms : Math.max(c, u.startLatencyAllowanceMs);
|
|
57
|
+
}
|
|
58
|
+
function M(e, t, n) {
|
|
59
|
+
let r = t + n * 1e3;
|
|
60
|
+
w === null || w.connection !== e ? w = {
|
|
61
|
+
connection: e,
|
|
62
|
+
writtenUntil: r,
|
|
63
|
+
heardAt: null
|
|
64
|
+
} : r > w.writtenUntil && (w = {
|
|
65
|
+
...w,
|
|
66
|
+
writtenUntil: r
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function N(e) {
|
|
70
|
+
w !== null && w.connection === e && (w = {
|
|
71
|
+
...w,
|
|
72
|
+
heardAt: n.now()
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function P(e) {
|
|
76
|
+
return w === null || w.connection !== e ? null : w.writtenUntil + j(e) + l;
|
|
77
|
+
}
|
|
78
|
+
function F(e) {
|
|
79
|
+
let t = P(e), r = w?.heardAt ?? null;
|
|
80
|
+
return t === null || n.now() < t || r !== null && r >= t ? !1 : (O({
|
|
81
|
+
kind: "output-released",
|
|
82
|
+
sincePlayedOutMs: n.now() - t
|
|
83
|
+
}), !0);
|
|
84
|
+
}
|
|
85
|
+
function I(e, r, i) {
|
|
86
|
+
let a = D(), o = a && t.isMeterAudible();
|
|
87
|
+
return o && N(e), !a && !i ? Promise.resolve(F(e)) : new Promise((s) => {
|
|
88
|
+
let c = !1, l = null, d = null, f = () => void 0, p = a && !t.isMeterAudible() ? n.now() : null, m = (e) => {
|
|
89
|
+
c || (c = !0, f(), l?.(), d?.(), s(e));
|
|
90
|
+
}, h = () => {
|
|
91
|
+
if (!a) return;
|
|
92
|
+
let e = Math.max(0, r - n.now()) + u.tailMs;
|
|
93
|
+
l ??= n.setTimeout(() => {
|
|
94
|
+
l = null, D() && m(!0);
|
|
95
|
+
}, e);
|
|
96
|
+
};
|
|
97
|
+
d = t.subscribe((t) => {
|
|
98
|
+
if (t.type === "meter") {
|
|
99
|
+
if ((o || t.audible && D()) && N(e), o = t.audible && D(), !t.audible) return p ??= n.now(), h();
|
|
100
|
+
p = null, l?.(), l = null;
|
|
101
|
+
}
|
|
102
|
+
}), p !== null && h();
|
|
103
|
+
let g = () => D() ? t.isMeterAudible() ? (N(e), !1) : p === null ? i : n.now() - p >= u.tailMs : i || F(e);
|
|
104
|
+
f = n.setTimeout(() => m(g()), v);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function L(e) {
|
|
108
|
+
b = e, e.then((t) => {
|
|
109
|
+
b === e && (b = null, x = t);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function R(e) {
|
|
113
|
+
if (b === null && x !== null) {
|
|
114
|
+
let t = x;
|
|
115
|
+
t.connection === e ? L(I(e, t.surfaceBy, !1).then((e) => e ? null : t)) : x = null;
|
|
116
|
+
}
|
|
117
|
+
let t = b;
|
|
118
|
+
return t === null ? null : t.then((t) => t === null || t.connection !== e);
|
|
119
|
+
}
|
|
120
|
+
function te(e, r) {
|
|
121
|
+
if (e.outputStopped) return;
|
|
122
|
+
e.outputStopped = !0, t.setOutputMuted(!0);
|
|
123
|
+
let i = e.segment;
|
|
124
|
+
if (i) if (i.pushedSec > 0) {
|
|
125
|
+
let a = t.clearBuffer({ finishOverdue: r }).catch(() => ({
|
|
126
|
+
positionSec: null,
|
|
127
|
+
synthetic: !0,
|
|
128
|
+
rpc: "error"
|
|
129
|
+
}));
|
|
130
|
+
e.clear = a;
|
|
131
|
+
let { connection: o } = e.reply, s = e.lastWriteAt ?? n.now();
|
|
132
|
+
M(o, s, i.pushedSec);
|
|
133
|
+
let c = s + A(o), l = {
|
|
134
|
+
connection: o,
|
|
135
|
+
surfaceBy: c
|
|
136
|
+
};
|
|
137
|
+
L((b ?? Promise.resolve(null)).then(() => a).then((e) => I(o, c, m(e))).then((e) => e ? null : l));
|
|
138
|
+
} else i.discard();
|
|
139
|
+
}
|
|
140
|
+
function z(e) {
|
|
141
|
+
for (let t of [...e.waiters]) t();
|
|
142
|
+
}
|
|
143
|
+
function B(e, t) {
|
|
144
|
+
if (!(t.begun || t.retracted)) {
|
|
145
|
+
t.begun = !0;
|
|
146
|
+
try {
|
|
147
|
+
t.began();
|
|
148
|
+
} catch {}
|
|
149
|
+
t.pending?.resolve(), t.pending = null, z(e);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function V(e) {
|
|
153
|
+
let t = e.attempt;
|
|
154
|
+
if (t === null || e.startedAt === null && t.tracker.nothingHeard) return;
|
|
155
|
+
let r = n.now(), i = 0;
|
|
156
|
+
for (let n = 0; n < e.pieces.length; n += 1) {
|
|
157
|
+
let a = e.pieces[n];
|
|
158
|
+
if (!(a === void 0 || a.retracted)) {
|
|
159
|
+
if (n > t.pushedThrough) return;
|
|
160
|
+
if (!a.begun) {
|
|
161
|
+
if (!(e.startedAt === null || e.startedAt + i - 1e3 <= r)) return;
|
|
162
|
+
B(e, a);
|
|
163
|
+
}
|
|
164
|
+
i += (a.durationSec ?? 0) * 1e3;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function H(e, t) {
|
|
169
|
+
if (e.end !== null) return;
|
|
170
|
+
e.end = t;
|
|
171
|
+
let n = y === e;
|
|
172
|
+
if (n && (y = null), e.abort.abort(), t.kind === "completed") for (let t of e.pieces) B(e, t);
|
|
173
|
+
for (let n of e.pieces) {
|
|
174
|
+
let e = n.pending;
|
|
175
|
+
n.pending = null, e !== null && (t.kind === "completed" || t.kind === "cancelled" ? e.resolve() : e.reject(t.error));
|
|
176
|
+
}
|
|
177
|
+
let r = e.pendingDrain;
|
|
178
|
+
e.pendingDrain = null, r !== null && (t.kind === "completed" || t.kind === "cancelled" ? r.resolve() : r.reject(t.error)), z(e), n && e.activated && k(!1);
|
|
179
|
+
}
|
|
180
|
+
function U(e, t) {
|
|
181
|
+
return e.kind === "cancelled" ? Promise.resolve() : e.kind === "completed" ? t === "drain" ? Promise.resolve() : Promise.reject(new d("written-after-end")) : Promise.reject(e.error);
|
|
182
|
+
}
|
|
183
|
+
function W(e) {
|
|
184
|
+
return e.firstOnConnection && e.attempts < o && t.isActive() && !t.isAudioBlocked();
|
|
185
|
+
}
|
|
186
|
+
function G(e, t) {
|
|
187
|
+
if (e.unsubscribe?.(), e.unsubscribe = null, !E(e)) return;
|
|
188
|
+
let n = e.reply;
|
|
189
|
+
if (t.kind === "failed-before-audio" && t.reason === "no-start" && W(n)) {
|
|
190
|
+
q(e);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
K(n, t);
|
|
194
|
+
}
|
|
195
|
+
function K(e, n) {
|
|
196
|
+
switch (n.kind) {
|
|
197
|
+
case "completed":
|
|
198
|
+
n.mutedAtEnd === !0 && O({ kind: "output-muted-at-end" }), H(e, { kind: "completed" });
|
|
199
|
+
return;
|
|
200
|
+
case "cancelled":
|
|
201
|
+
H(e, { kind: "cancelled" });
|
|
202
|
+
return;
|
|
203
|
+
case "failed-before-audio":
|
|
204
|
+
O({
|
|
205
|
+
kind: "voice-fallback",
|
|
206
|
+
reason: n.reason
|
|
207
|
+
}), H(e, n.reason === "not-ready" && !t.isActive() ? {
|
|
208
|
+
kind: "left",
|
|
209
|
+
error: new f()
|
|
210
|
+
} : {
|
|
211
|
+
kind: "failed",
|
|
212
|
+
error: new d(n.reason)
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
case "uncertain":
|
|
216
|
+
O({
|
|
217
|
+
kind: "interrupted",
|
|
218
|
+
reason: n.reason
|
|
219
|
+
}), V(e), H(e, {
|
|
220
|
+
kind: "failed",
|
|
221
|
+
error: new d(n.reason)
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
case "output-not-cleared":
|
|
225
|
+
O({ kind: "output-not-cleared" }), H(e, {
|
|
226
|
+
kind: "refused",
|
|
227
|
+
error: new p()
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function q(e) {
|
|
233
|
+
let n = e.reply, r = e.clear ? await e.clear : null;
|
|
234
|
+
if (E(e)) {
|
|
235
|
+
if (r !== null && !r.synthetic && r.positionSec !== null && r.positionSec > s) {
|
|
236
|
+
K(n, {
|
|
237
|
+
kind: "uncertain",
|
|
238
|
+
reason: "played-before-retry",
|
|
239
|
+
heardAudio: !0,
|
|
240
|
+
cause: "unknown"
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (t.isAudioBlocked()) {
|
|
245
|
+
O({ kind: "audio-blocked" }), K(n, {
|
|
246
|
+
kind: "failed-before-audio",
|
|
247
|
+
reason: "audio-blocked",
|
|
248
|
+
cause: "unknown"
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!t.isActive()) {
|
|
253
|
+
K(n, {
|
|
254
|
+
kind: "failed-before-audio",
|
|
255
|
+
reason: "not-ready",
|
|
256
|
+
cause: "unknown"
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (r !== null && (r.rpc === "rejected" || r.rpc === "unsupported")) {
|
|
261
|
+
K(n, {
|
|
262
|
+
kind: "failed-before-audio",
|
|
263
|
+
reason: "not-accepted",
|
|
264
|
+
cause: "unknown"
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
O({
|
|
269
|
+
kind: "first-reply-retry",
|
|
270
|
+
attempt: n.attempts + 1
|
|
271
|
+
}), Q(n);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function J(e, t) {
|
|
275
|
+
if (!E(e)) return;
|
|
276
|
+
let { tracker: n } = e;
|
|
277
|
+
switch (t.type) {
|
|
278
|
+
case "playbackStarted":
|
|
279
|
+
n.providerStarted();
|
|
280
|
+
return;
|
|
281
|
+
case "playbackFinished":
|
|
282
|
+
if (e.outputStopped) return;
|
|
283
|
+
n.providerFinished(t.positionSec, t.interrupted);
|
|
284
|
+
return;
|
|
285
|
+
case "avatarLeft":
|
|
286
|
+
n.avatarLeft();
|
|
287
|
+
return;
|
|
288
|
+
case "disconnected":
|
|
289
|
+
n.disconnected();
|
|
290
|
+
return;
|
|
291
|
+
case "audioPlayback":
|
|
292
|
+
t.canPlay || (O({ kind: "audio-blocked" }), n.audioBlocked());
|
|
293
|
+
return;
|
|
294
|
+
case "meter":
|
|
295
|
+
n.meter(t.audible), t.audible && ee(e);
|
|
296
|
+
return;
|
|
297
|
+
default: return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function Y(e, t) {
|
|
301
|
+
let r = e.reply;
|
|
302
|
+
return new Promise((i) => {
|
|
303
|
+
let a = null, o = !1, s = (e) => {
|
|
304
|
+
o || (o = !0, a?.(), r.waiters.delete(c), i(e));
|
|
305
|
+
};
|
|
306
|
+
function c() {
|
|
307
|
+
if (!E(e)) return s("gone");
|
|
308
|
+
let i = r.pieces[t];
|
|
309
|
+
if (i !== void 0) return s(i);
|
|
310
|
+
if (r.drained) return s("end");
|
|
311
|
+
let o = r.pieces[t - 1], c = o === void 0 || o.begun || o.retracted;
|
|
312
|
+
a === null && c && (a = n.setTimeout(() => {
|
|
313
|
+
a = () => void 0, e.tracker.stalled(), s("stalled");
|
|
314
|
+
}, g));
|
|
315
|
+
}
|
|
316
|
+
r.waiters.add(c), c();
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function X(e, t) {
|
|
320
|
+
return new Promise((r, i) => {
|
|
321
|
+
let a = n.setTimeout(() => {
|
|
322
|
+
t.tracker.stalled(), r(null);
|
|
323
|
+
}, g);
|
|
324
|
+
e.then((e) => {
|
|
325
|
+
a(), r(e);
|
|
326
|
+
}, (e) => {
|
|
327
|
+
a(), i(e);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async function Z(e) {
|
|
332
|
+
let { reply: i, tracker: a } = e, o = () => E(e) && !a.isDone;
|
|
333
|
+
try {
|
|
334
|
+
let r = R(i.connection), s = r === null || await r;
|
|
335
|
+
if (!E(e)) return;
|
|
336
|
+
if (!t.isActive()) {
|
|
337
|
+
a.failed("not-ready");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (t.isAudioBlocked()) {
|
|
341
|
+
O({ kind: "audio-blocked" }), a.audioBlocked();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (!s) {
|
|
345
|
+
a.outputNotCleared();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
t.setOutputMuted(!1), e.unsubscribe = t.subscribe((t) => J(e, t)), a.meter(t.isMeterAudible());
|
|
349
|
+
try {
|
|
350
|
+
e.segment = await t.openSegment();
|
|
351
|
+
} catch {
|
|
352
|
+
E(e) && a.failed("not-ready");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (!E(e)) {
|
|
356
|
+
e.segment.discard();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
let c = 0;
|
|
360
|
+
for (let t = 0;; t += 1) {
|
|
361
|
+
let r = await Y(e, t);
|
|
362
|
+
if (!o() || r === "stalled" || r === "gone") return;
|
|
363
|
+
if (r === "end") break;
|
|
364
|
+
if (r.retracted || r.decoded === null) continue;
|
|
365
|
+
let s = await X(r.decoded, e);
|
|
366
|
+
if (!o() || s === null) return;
|
|
367
|
+
if (!r.retracted) {
|
|
368
|
+
r.durationSec = s.durationSec, c += s.durationSec, a.chunkReady(r.index, s.durationSec);
|
|
369
|
+
for (let r of h(s.pcm, _)) {
|
|
370
|
+
if (!o()) return;
|
|
371
|
+
a.firstWrite(), e.lastWriteAt = n.now(), e.pushedThrough = t, await e.segment.write(r), a.delivered(), S = i.connection;
|
|
372
|
+
}
|
|
373
|
+
i.firstOnConnection || (r.decoded = null);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (!o()) return;
|
|
377
|
+
if (e.pushedThrough < 0) {
|
|
378
|
+
e.segment.discard(), e.unsubscribe?.(), e.unsubscribe = null, K(i, { kind: "completed" });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
a.streamEnding(c), await e.segment.close();
|
|
382
|
+
} catch (e) {
|
|
383
|
+
if (!o()) return;
|
|
384
|
+
if (e instanceof r) return a.failed("stream-error");
|
|
385
|
+
a.failed("synth-failed");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function Q(e) {
|
|
389
|
+
e.attempts += 1, e.startedAt = null;
|
|
390
|
+
let r = null, i = () => r !== null && E(r);
|
|
391
|
+
r = {
|
|
392
|
+
reply: e,
|
|
393
|
+
tracker: new a({
|
|
394
|
+
onStart: () => {
|
|
395
|
+
i() && e.startedAt === null && (e.startedAt = n.now());
|
|
396
|
+
},
|
|
397
|
+
onChunkStart: (t) => {
|
|
398
|
+
if (!i()) return;
|
|
399
|
+
e.startedAt === null && (e.startedAt = n.now());
|
|
400
|
+
let r = e.pieces.find((e) => e.index === t);
|
|
401
|
+
r !== void 0 && B(e, r);
|
|
402
|
+
},
|
|
403
|
+
onEnd: () => void 0,
|
|
404
|
+
stopOutput: (e) => {
|
|
405
|
+
r !== null && te(r, e);
|
|
406
|
+
},
|
|
407
|
+
onOutcome: (e) => {
|
|
408
|
+
r !== null && G(r, e);
|
|
409
|
+
}
|
|
410
|
+
}, n, u.meterSupported && !t.isMeterHealthy() ? {
|
|
411
|
+
...u,
|
|
412
|
+
meterSupported: !1
|
|
413
|
+
} : u),
|
|
414
|
+
segment: null,
|
|
415
|
+
unsubscribe: null,
|
|
416
|
+
outputStopped: !1,
|
|
417
|
+
clear: null,
|
|
418
|
+
lastWriteAt: null,
|
|
419
|
+
pushedThrough: -1
|
|
420
|
+
}, e.attempt = r, Z(r);
|
|
421
|
+
}
|
|
422
|
+
function $(e) {
|
|
423
|
+
let t = e.attempt;
|
|
424
|
+
t?.tracker.cancel(), t?.unsubscribe?.(), e.abort.abort();
|
|
425
|
+
}
|
|
426
|
+
function ne(e) {
|
|
427
|
+
if (e.activated) return;
|
|
428
|
+
e.activated = !0;
|
|
429
|
+
let n = y;
|
|
430
|
+
n !== null && n !== e && (H(n, { kind: "cancelled" }), $(n)), e.connection = t.connectionId(), e.firstOnConnection = e.connection !== S, y = e, k(!0);
|
|
431
|
+
}
|
|
432
|
+
function re(e) {
|
|
433
|
+
let n = R(t.connectionId());
|
|
434
|
+
return n === null ? Promise.resolve() : n.then((n) => {
|
|
435
|
+
if (e.end !== null) return U(e.end, "drain");
|
|
436
|
+
if (n || !t.isActive() || t.isAudioBlocked()) return;
|
|
437
|
+
O({ kind: "output-not-cleared" });
|
|
438
|
+
let r = {
|
|
439
|
+
kind: "refused",
|
|
440
|
+
error: new p()
|
|
441
|
+
};
|
|
442
|
+
throw e.end = r, r.error;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
function ie() {
|
|
446
|
+
let t = {
|
|
447
|
+
connection: -1,
|
|
448
|
+
firstOnConnection: !1,
|
|
449
|
+
activated: !1,
|
|
450
|
+
abort: new AbortController(),
|
|
451
|
+
pieces: [],
|
|
452
|
+
drained: !1,
|
|
453
|
+
pendingDrain: null,
|
|
454
|
+
attempts: 0,
|
|
455
|
+
attempt: null,
|
|
456
|
+
end: null,
|
|
457
|
+
startedAt: null,
|
|
458
|
+
waiters: /* @__PURE__ */ new Set()
|
|
459
|
+
};
|
|
460
|
+
return {
|
|
461
|
+
write(n, r, i) {
|
|
462
|
+
if (t.end !== null) return U(t.end, "write");
|
|
463
|
+
ne(t);
|
|
464
|
+
let a = e.decode(n, t.abort.signal);
|
|
465
|
+
a.catch(() => void 0);
|
|
466
|
+
let o = {
|
|
467
|
+
index: r,
|
|
468
|
+
began: i,
|
|
469
|
+
decoded: a,
|
|
470
|
+
durationSec: null,
|
|
471
|
+
begun: !1,
|
|
472
|
+
retracted: !1,
|
|
473
|
+
pending: null
|
|
474
|
+
}, s = new Promise((e, t) => {
|
|
475
|
+
o.pending = {
|
|
476
|
+
resolve: e,
|
|
477
|
+
reject: t
|
|
478
|
+
};
|
|
479
|
+
});
|
|
480
|
+
return t.pieces.push(o), t.attempt === null && Q(t), z(t), s;
|
|
481
|
+
},
|
|
482
|
+
drain() {
|
|
483
|
+
if (t.end !== null) return U(t.end, "drain");
|
|
484
|
+
if (!t.activated) return re(t);
|
|
485
|
+
t.drained = !0;
|
|
486
|
+
let e = new Promise((e, n) => {
|
|
487
|
+
t.pendingDrain = {
|
|
488
|
+
resolve: e,
|
|
489
|
+
reject: n
|
|
490
|
+
};
|
|
491
|
+
});
|
|
492
|
+
return z(t), e;
|
|
493
|
+
},
|
|
494
|
+
retract(e) {
|
|
495
|
+
if (t.end !== null) return;
|
|
496
|
+
let n = t.attempt?.pushedThrough ?? -1;
|
|
497
|
+
t.pieces.forEach((r, i) => {
|
|
498
|
+
if (!(r.index < e || r.begun || r.retracted)) {
|
|
499
|
+
if (i <= n) {
|
|
500
|
+
B(t, r);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
r.retracted = !0, r.pending?.resolve(), r.pending = null;
|
|
504
|
+
}
|
|
505
|
+
}), z(t);
|
|
506
|
+
},
|
|
507
|
+
cancel() {
|
|
508
|
+
t.end === null && (H(t, { kind: "cancelled" }), $(t));
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
createOutput: ie,
|
|
514
|
+
leave() {
|
|
515
|
+
let e = y;
|
|
516
|
+
e === null || e.end !== null || (t.setOutputMuted(!0), V(e), H(e, {
|
|
517
|
+
kind: "left",
|
|
518
|
+
error: new f()
|
|
519
|
+
}), $(e));
|
|
520
|
+
},
|
|
521
|
+
isBusy: () => y !== null
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
525
|
+
export { d as AvatarOutputFailure, f as AvatarOutputLeft, p as AvatarOutputRefusal, u as MAY_HAVE_BEGUN_MARGIN_MS, l as RELEASE_MARGIN_MS, c as UNMEASURED_START_LATENCY_MS, g as createAvatarReplyOutputs, h as sliceAvatarPcm };
|