@burdenoff/website-sdk 2026.1104.3 → 2026.1105.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -6
- 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 +482 -64
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +476 -58
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +193 -0
- package/dist/avatar-mic/index.d.ts +193 -0
- package/dist/avatar-mic/index.js +738 -0
- package/dist/avatar-mic/index.js.map +1 -0
- package/dist/avatar-mic/index.mjs +736 -0
- package/dist/avatar-mic/index.mjs.map +1 -0
- package/dist/{chunk-5JQGE3XI.mjs → chunk-7HE33NBS.mjs} +4 -111
- package/dist/chunk-7HE33NBS.mjs.map +1 -0
- package/dist/chunk-JZS6FQT6.mjs +127 -0
- package/dist/chunk-JZS6FQT6.mjs.map +1 -0
- package/dist/{chunk-22OT6E26.js → chunk-MBVXDFVC.js} +18 -127
- package/dist/chunk-MBVXDFVC.js.map +1 -0
- package/dist/{chunk-7MSB3EUI.mjs → chunk-MVNN6LLO.mjs} +4 -3
- package/dist/chunk-MVNN6LLO.mjs.map +1 -0
- package/dist/chunk-N7E6Z2GF.js +132 -0
- package/dist/chunk-N7E6Z2GF.js.map +1 -0
- package/dist/{chunk-WSEQ2I6I.js → chunk-XIZE37IB.js} +4 -3
- package/dist/chunk-XIZE37IB.js.map +1 -0
- package/dist/components/explore.d.mts +1 -1
- package/dist/components/explore.d.ts +1 -1
- package/dist/components/explore.js +20 -20
- package/dist/components/explore.mjs +2 -2
- package/dist/{explore-BV4dRwoC.d.ts → explore-Dfru4j4m.d.ts} +45 -31
- package/dist/{explore-avatar-live-config-DRpS-mqQ.d.ts → explore-avatar-live-config-C-OwXNje.d.ts} +8 -0
- package/dist/{explore-avatar-live-config-B6_p0q1c.d.mts → explore-avatar-live-config-OO0hvQCK.d.mts} +8 -0
- package/dist/{explore-BgEeGPvY.d.mts → explore-ei8uoko9.d.mts} +45 -31
- package/dist/index.d.mts +11 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +70 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-22OT6E26.js.map +0 -1
- package/dist/chunk-5JQGE3XI.mjs.map +0 -1
- package/dist/chunk-7MSB3EUI.mjs.map +0 -1
- package/dist/chunk-O4GS3XPZ.js +0 -10
- package/dist/chunk-O4GS3XPZ.js.map +0 -1
- package/dist/chunk-TT3MHN42.mjs +0 -8
- package/dist/chunk-TT3MHN42.mjs.map +0 -1
- package/dist/chunk-WSEQ2I6I.js.map +0 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/** What the worklet posts for each frame. */
|
|
2
|
+
interface CaptureFrameMessage {
|
|
3
|
+
/** s16le mono 16 kHz, `CAPTURE_FRAME_SAMPLES` samples. */
|
|
4
|
+
pcm: ArrayBuffer;
|
|
5
|
+
/** Microphone level over the frame, dBFS. */
|
|
6
|
+
rmsDb: number;
|
|
7
|
+
/** Reference-bus level over the same samples, dBFS (about -90 when silent, as it always is here). */
|
|
8
|
+
refDb: number;
|
|
9
|
+
/** Zero crossings per sample. */
|
|
10
|
+
zcr: number;
|
|
11
|
+
/** AudioContext time at the end of the frame, seconds. */
|
|
12
|
+
ctxTime: number;
|
|
13
|
+
seq: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* BOFF-9797 (Stage C). The Avatar tab's own microphone capture: `getUserMedia`, through the capture worklet
|
|
18
|
+
* (`capture-worklet.ts`), into 16 kHz PCM frames.
|
|
19
|
+
*
|
|
20
|
+
* **Ported, not shared.** From `src/shared/assistant/voice/handsfree/micCapture.ts` in Algoshred/fe-libs at the
|
|
21
|
+
* commit `capture-worklet.ts` names (`CAPTURE_WORKLET_PORTED_FROM`; blob `8486c630`), with one difference: fe-libs
|
|
22
|
+
* routes everything its assistant plays into the worklet's second input (its playback reference, for barge-in), and the
|
|
23
|
+
* website plays nothing through this context (the avatar's voice is the room's own audio element, and the mic is
|
|
24
|
+
* offered only while no session plays), so no reference bus is made and the worklet has the microphone as its only
|
|
25
|
+
* input. `looksLikeHeadset`, which only fe-libs' barge-in reads, is not ported. `capture-worklet.test.ts` holds the
|
|
26
|
+
* parity tests.
|
|
27
|
+
*
|
|
28
|
+
* It opens `getUserMedia` with echo cancellation, noise suppression and auto gain on. Constraints are plain `true`,
|
|
29
|
+
* never `{ exact }`: `exact` throws OverconstrainedError on handsets that cannot honour one. The settings the browser
|
|
30
|
+
* actually applied are read back (`track.getSettings()`), because some Android handsets report `true` and apply nothing.
|
|
31
|
+
*
|
|
32
|
+
* `stop()` stops the track (the browser's recording indicator goes), disconnects the graph and closes the context. The
|
|
33
|
+
* caller does that the moment a press ends, whichever way it ends; `pagehide` does it too, whatever the caller does.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
interface MicCaptureHandle {
|
|
37
|
+
context: AudioContext;
|
|
38
|
+
micSource: MediaStreamAudioSourceNode;
|
|
39
|
+
track: MediaStreamTrack;
|
|
40
|
+
/** What the browser reports it applied. */
|
|
41
|
+
settings: MediaTrackSettings;
|
|
42
|
+
/** Stop everything and release the microphone. Safe to call more than once. */
|
|
43
|
+
stop(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What the mic needs of a WebSocket; {@link openBrowserSocket} adapts the browser's. (fe-libs' seam.) */
|
|
47
|
+
interface SocketLike {
|
|
48
|
+
readonly readyState: number;
|
|
49
|
+
send(data: string | ArrayBufferLike | ArrayBufferView): void;
|
|
50
|
+
close(code?: number): void;
|
|
51
|
+
listen(handlers: SocketHandlers): void;
|
|
52
|
+
}
|
|
53
|
+
interface SocketHandlers {
|
|
54
|
+
open(): void;
|
|
55
|
+
message(data: unknown): void;
|
|
56
|
+
close(code: number): void;
|
|
57
|
+
error(): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* BOFF-9797 (Stage C). The shapes the Avatar tab's mic shares with the live avatar's view.
|
|
62
|
+
*
|
|
63
|
+
* Types only, on purpose: the view (`explore-avatar-mic.tsx`, in the view's chunk) imports this file for its types and
|
|
64
|
+
* nothing else, so the view never carries the mic's code. It reaches the mic's chunk (`dist/avatar-mic/`) only through
|
|
65
|
+
* a dynamic `import()`, the first time a visitor uses the mic.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* What an availability answer offered the mic (`stt`, while the gateway's website speech-to-text is on): the token for
|
|
69
|
+
* one stream, when the gateway says it expires, and when this browser received it, on its own clock.
|
|
70
|
+
*/
|
|
71
|
+
interface AvatarMicOffer {
|
|
72
|
+
token: string;
|
|
73
|
+
/** Epoch milliseconds, on the gateway's clock. */
|
|
74
|
+
expiresAt: number;
|
|
75
|
+
/** Epoch milliseconds, on this browser's clock. */
|
|
76
|
+
receivedAt: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Why a press did not give text:
|
|
80
|
+
*
|
|
81
|
+
* - `denied`: the microphone was refused (the visitor, the browser or the page's permissions);
|
|
82
|
+
* - `unsupported`: this browser cannot capture here, or has no microphone to use;
|
|
83
|
+
* - `unavailable`: the server will not take a stream from this page now: no offer, an offer it refuses twice, its mic
|
|
84
|
+
* notice is another version than this SDK's, or the website avatar is paused;
|
|
85
|
+
* - `limit`: a limit on voice questions is reached (the visitor's, the network's, or every visitor's together);
|
|
86
|
+
* - `busy`: the stream is busy, or the server's counters cannot be read: try again in a moment;
|
|
87
|
+
* - `failed`: anything else (the connection lost, availability or the speech service not answering, no answer in
|
|
88
|
+
* time).
|
|
89
|
+
*/
|
|
90
|
+
type AvatarMicFailure = "denied" | "unsupported" | "unavailable" | "limit" | "busy" | "failed";
|
|
91
|
+
/**
|
|
92
|
+
* One press of the mic, as the view shows it. `epoch` names the press: a new press has a new one, and what an older one
|
|
93
|
+
* reports is never shown for a newer one.
|
|
94
|
+
*
|
|
95
|
+
* - `starting`: the microphone opening (the browser's permission prompt included) and the stream connecting;
|
|
96
|
+
* - `listening`: recording, since `startedAt` (this browser's clock), with what has been heard so far (the final text
|
|
97
|
+
* and, after it, the words still being recognised);
|
|
98
|
+
* - `finishing`: the recording has stopped (a tap, or the limit: `limit`), and the last of the text is on its way;
|
|
99
|
+
* - `done`: the text, final (empty when no words were heard);
|
|
100
|
+
* - `failed`: no text, and why (with any final text heard before it failed, which the view does not use).
|
|
101
|
+
*/
|
|
102
|
+
type AvatarMicState = {
|
|
103
|
+
name: "idle";
|
|
104
|
+
} | {
|
|
105
|
+
name: "starting";
|
|
106
|
+
epoch: number;
|
|
107
|
+
} | {
|
|
108
|
+
name: "listening";
|
|
109
|
+
epoch: number;
|
|
110
|
+
startedAt: number;
|
|
111
|
+
text: string;
|
|
112
|
+
} | {
|
|
113
|
+
name: "finishing";
|
|
114
|
+
epoch: number;
|
|
115
|
+
text: string;
|
|
116
|
+
limit: boolean;
|
|
117
|
+
} | {
|
|
118
|
+
name: "done";
|
|
119
|
+
epoch: number;
|
|
120
|
+
text: string;
|
|
121
|
+
limit: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
name: "failed";
|
|
124
|
+
epoch: number;
|
|
125
|
+
reason: AvatarMicFailure;
|
|
126
|
+
text: string;
|
|
127
|
+
};
|
|
128
|
+
interface AvatarMicOptions {
|
|
129
|
+
/** `avatarApiBase` of the SDK's gateway: the stream is `<base>/stt/stream`. */
|
|
130
|
+
base: string;
|
|
131
|
+
/** The mic notice version the visitor saw: every press names it, and the server takes only its own. */
|
|
132
|
+
notice: string;
|
|
133
|
+
/** The latest offer the view holds, or null. */
|
|
134
|
+
offer: () => AvatarMicOffer | null;
|
|
135
|
+
/**
|
|
136
|
+
* Ask availability again for a new offer: null when the answer carries none (the press is `unavailable`); rejects when
|
|
137
|
+
* there is no answer at all (the press is `failed`, and the mic is still offered).
|
|
138
|
+
*/
|
|
139
|
+
refreshOffer: () => Promise<AvatarMicOffer | null>;
|
|
140
|
+
/** Every change of the press's state. */
|
|
141
|
+
onState: (state: AvatarMicState) => void;
|
|
142
|
+
}
|
|
143
|
+
/** One view's mic: one press at a time. */
|
|
144
|
+
interface AvatarMic {
|
|
145
|
+
/** A new press. One still finishing is cancelled first (superseded); one starting or listening is left alone. */
|
|
146
|
+
start(): void;
|
|
147
|
+
/** Stop recording and wait for the final text (the visitor's tap). A press still starting is cancelled instead. */
|
|
148
|
+
stop(): void;
|
|
149
|
+
/** Call off whatever is under way, release the microphone, and report nothing more of it: back to `idle`. */
|
|
150
|
+
cancel(): void;
|
|
151
|
+
/** The press as it stands. */
|
|
152
|
+
state(): AvatarMicState;
|
|
153
|
+
}
|
|
154
|
+
/** What the mic's chunk (`dist/avatar-mic/`) exports. */
|
|
155
|
+
interface AvatarMicModule {
|
|
156
|
+
createAvatarMic: (options: AvatarMicOptions) => AvatarMic;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* BOFF-9797 (Stage C). The Avatar tab's mic, one press at a time: tap to start, tap to stop, thirty seconds at most.
|
|
161
|
+
*
|
|
162
|
+
* A press, in order:
|
|
163
|
+
*
|
|
164
|
+
* 1. **The microphone opens** (`startMicCapture`: the browser's permission prompt the first time). Meanwhile the offer
|
|
165
|
+
* is made ready: the token the last availability answer carried while it is fresh, else a new one, asked for now.
|
|
166
|
+
* 2. **The stream connects**, once the microphone is live (never while a permission prompt is up, so no stream is held
|
|
167
|
+
* open for a visitor who has not decided): `boff.web-stt.v1` and `offer.<token>`. A refused upgrade (4401) asks
|
|
168
|
+
* availability again and tries once more, never twice.
|
|
169
|
+
* 3. **The server says it is ready**: the press's `config` (its utterance id, its epoch, `en-IN`, and the mic notice the
|
|
170
|
+
* visitor saw), then up to {@link PRE_ROLL_MS} of what the microphone heard while the stream connected, then every
|
|
171
|
+
* frame as it comes. Listening, from here: what has been heard so far is reported as it is recognised.
|
|
172
|
+
* 4. **The recording stops**: the visitor's tap, or {@link MAX_PRESS_MS} (counted both on the clock and in audio sent),
|
|
173
|
+
* or the speech service's own limit. The microphone is released at once; `end`, and the last of the text.
|
|
174
|
+
* 5. **The text is final** (`done`): reported once, for the view to put in the box. Nothing is ever sent for the
|
|
175
|
+
* visitor: the text goes nowhere else.
|
|
176
|
+
*
|
|
177
|
+
* **Released on every way out.** The microphone's track is stopped the moment the recording stops, and also when the
|
|
178
|
+
* press fails, is cancelled (`cancel()`: the view going, the page hidden, the Chat tab) or superseded (a new press while
|
|
179
|
+
* one is still finishing: `cancel` for the old one, so it stops being paid for, and its text is never reported). A
|
|
180
|
+
* microphone that opens after its press has gone is closed as it opens. The stream is closed whenever no press needs
|
|
181
|
+
* it: one connection serves a press and the press that supersedes it, and no other.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
/** What a press needs from the browser. The browser's own by default; a test hands in fakes. */
|
|
185
|
+
interface AvatarMicSeams {
|
|
186
|
+
createSocket: (url: string, protocols: string[]) => SocketLike;
|
|
187
|
+
startCapture: (onFrame: (frame: CaptureFrameMessage) => void) => Promise<Pick<MicCaptureHandle, "stop">>;
|
|
188
|
+
newUtteranceId: () => string | null;
|
|
189
|
+
}
|
|
190
|
+
/** The mic of one Avatar tab. See the file's comment for what a press does. */
|
|
191
|
+
declare function createAvatarMic(options: AvatarMicOptions, seams?: Partial<AvatarMicSeams>): AvatarMic;
|
|
192
|
+
|
|
193
|
+
export { type AvatarMic, type AvatarMicFailure, type AvatarMicModule, type AvatarMicOffer, type AvatarMicOptions, type AvatarMicState, createAvatarMic };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/** What the worklet posts for each frame. */
|
|
2
|
+
interface CaptureFrameMessage {
|
|
3
|
+
/** s16le mono 16 kHz, `CAPTURE_FRAME_SAMPLES` samples. */
|
|
4
|
+
pcm: ArrayBuffer;
|
|
5
|
+
/** Microphone level over the frame, dBFS. */
|
|
6
|
+
rmsDb: number;
|
|
7
|
+
/** Reference-bus level over the same samples, dBFS (about -90 when silent, as it always is here). */
|
|
8
|
+
refDb: number;
|
|
9
|
+
/** Zero crossings per sample. */
|
|
10
|
+
zcr: number;
|
|
11
|
+
/** AudioContext time at the end of the frame, seconds. */
|
|
12
|
+
ctxTime: number;
|
|
13
|
+
seq: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* BOFF-9797 (Stage C). The Avatar tab's own microphone capture: `getUserMedia`, through the capture worklet
|
|
18
|
+
* (`capture-worklet.ts`), into 16 kHz PCM frames.
|
|
19
|
+
*
|
|
20
|
+
* **Ported, not shared.** From `src/shared/assistant/voice/handsfree/micCapture.ts` in Algoshred/fe-libs at the
|
|
21
|
+
* commit `capture-worklet.ts` names (`CAPTURE_WORKLET_PORTED_FROM`; blob `8486c630`), with one difference: fe-libs
|
|
22
|
+
* routes everything its assistant plays into the worklet's second input (its playback reference, for barge-in), and the
|
|
23
|
+
* website plays nothing through this context (the avatar's voice is the room's own audio element, and the mic is
|
|
24
|
+
* offered only while no session plays), so no reference bus is made and the worklet has the microphone as its only
|
|
25
|
+
* input. `looksLikeHeadset`, which only fe-libs' barge-in reads, is not ported. `capture-worklet.test.ts` holds the
|
|
26
|
+
* parity tests.
|
|
27
|
+
*
|
|
28
|
+
* It opens `getUserMedia` with echo cancellation, noise suppression and auto gain on. Constraints are plain `true`,
|
|
29
|
+
* never `{ exact }`: `exact` throws OverconstrainedError on handsets that cannot honour one. The settings the browser
|
|
30
|
+
* actually applied are read back (`track.getSettings()`), because some Android handsets report `true` and apply nothing.
|
|
31
|
+
*
|
|
32
|
+
* `stop()` stops the track (the browser's recording indicator goes), disconnects the graph and closes the context. The
|
|
33
|
+
* caller does that the moment a press ends, whichever way it ends; `pagehide` does it too, whatever the caller does.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
interface MicCaptureHandle {
|
|
37
|
+
context: AudioContext;
|
|
38
|
+
micSource: MediaStreamAudioSourceNode;
|
|
39
|
+
track: MediaStreamTrack;
|
|
40
|
+
/** What the browser reports it applied. */
|
|
41
|
+
settings: MediaTrackSettings;
|
|
42
|
+
/** Stop everything and release the microphone. Safe to call more than once. */
|
|
43
|
+
stop(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What the mic needs of a WebSocket; {@link openBrowserSocket} adapts the browser's. (fe-libs' seam.) */
|
|
47
|
+
interface SocketLike {
|
|
48
|
+
readonly readyState: number;
|
|
49
|
+
send(data: string | ArrayBufferLike | ArrayBufferView): void;
|
|
50
|
+
close(code?: number): void;
|
|
51
|
+
listen(handlers: SocketHandlers): void;
|
|
52
|
+
}
|
|
53
|
+
interface SocketHandlers {
|
|
54
|
+
open(): void;
|
|
55
|
+
message(data: unknown): void;
|
|
56
|
+
close(code: number): void;
|
|
57
|
+
error(): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* BOFF-9797 (Stage C). The shapes the Avatar tab's mic shares with the live avatar's view.
|
|
62
|
+
*
|
|
63
|
+
* Types only, on purpose: the view (`explore-avatar-mic.tsx`, in the view's chunk) imports this file for its types and
|
|
64
|
+
* nothing else, so the view never carries the mic's code. It reaches the mic's chunk (`dist/avatar-mic/`) only through
|
|
65
|
+
* a dynamic `import()`, the first time a visitor uses the mic.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* What an availability answer offered the mic (`stt`, while the gateway's website speech-to-text is on): the token for
|
|
69
|
+
* one stream, when the gateway says it expires, and when this browser received it, on its own clock.
|
|
70
|
+
*/
|
|
71
|
+
interface AvatarMicOffer {
|
|
72
|
+
token: string;
|
|
73
|
+
/** Epoch milliseconds, on the gateway's clock. */
|
|
74
|
+
expiresAt: number;
|
|
75
|
+
/** Epoch milliseconds, on this browser's clock. */
|
|
76
|
+
receivedAt: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Why a press did not give text:
|
|
80
|
+
*
|
|
81
|
+
* - `denied`: the microphone was refused (the visitor, the browser or the page's permissions);
|
|
82
|
+
* - `unsupported`: this browser cannot capture here, or has no microphone to use;
|
|
83
|
+
* - `unavailable`: the server will not take a stream from this page now: no offer, an offer it refuses twice, its mic
|
|
84
|
+
* notice is another version than this SDK's, or the website avatar is paused;
|
|
85
|
+
* - `limit`: a limit on voice questions is reached (the visitor's, the network's, or every visitor's together);
|
|
86
|
+
* - `busy`: the stream is busy, or the server's counters cannot be read: try again in a moment;
|
|
87
|
+
* - `failed`: anything else (the connection lost, availability or the speech service not answering, no answer in
|
|
88
|
+
* time).
|
|
89
|
+
*/
|
|
90
|
+
type AvatarMicFailure = "denied" | "unsupported" | "unavailable" | "limit" | "busy" | "failed";
|
|
91
|
+
/**
|
|
92
|
+
* One press of the mic, as the view shows it. `epoch` names the press: a new press has a new one, and what an older one
|
|
93
|
+
* reports is never shown for a newer one.
|
|
94
|
+
*
|
|
95
|
+
* - `starting`: the microphone opening (the browser's permission prompt included) and the stream connecting;
|
|
96
|
+
* - `listening`: recording, since `startedAt` (this browser's clock), with what has been heard so far (the final text
|
|
97
|
+
* and, after it, the words still being recognised);
|
|
98
|
+
* - `finishing`: the recording has stopped (a tap, or the limit: `limit`), and the last of the text is on its way;
|
|
99
|
+
* - `done`: the text, final (empty when no words were heard);
|
|
100
|
+
* - `failed`: no text, and why (with any final text heard before it failed, which the view does not use).
|
|
101
|
+
*/
|
|
102
|
+
type AvatarMicState = {
|
|
103
|
+
name: "idle";
|
|
104
|
+
} | {
|
|
105
|
+
name: "starting";
|
|
106
|
+
epoch: number;
|
|
107
|
+
} | {
|
|
108
|
+
name: "listening";
|
|
109
|
+
epoch: number;
|
|
110
|
+
startedAt: number;
|
|
111
|
+
text: string;
|
|
112
|
+
} | {
|
|
113
|
+
name: "finishing";
|
|
114
|
+
epoch: number;
|
|
115
|
+
text: string;
|
|
116
|
+
limit: boolean;
|
|
117
|
+
} | {
|
|
118
|
+
name: "done";
|
|
119
|
+
epoch: number;
|
|
120
|
+
text: string;
|
|
121
|
+
limit: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
name: "failed";
|
|
124
|
+
epoch: number;
|
|
125
|
+
reason: AvatarMicFailure;
|
|
126
|
+
text: string;
|
|
127
|
+
};
|
|
128
|
+
interface AvatarMicOptions {
|
|
129
|
+
/** `avatarApiBase` of the SDK's gateway: the stream is `<base>/stt/stream`. */
|
|
130
|
+
base: string;
|
|
131
|
+
/** The mic notice version the visitor saw: every press names it, and the server takes only its own. */
|
|
132
|
+
notice: string;
|
|
133
|
+
/** The latest offer the view holds, or null. */
|
|
134
|
+
offer: () => AvatarMicOffer | null;
|
|
135
|
+
/**
|
|
136
|
+
* Ask availability again for a new offer: null when the answer carries none (the press is `unavailable`); rejects when
|
|
137
|
+
* there is no answer at all (the press is `failed`, and the mic is still offered).
|
|
138
|
+
*/
|
|
139
|
+
refreshOffer: () => Promise<AvatarMicOffer | null>;
|
|
140
|
+
/** Every change of the press's state. */
|
|
141
|
+
onState: (state: AvatarMicState) => void;
|
|
142
|
+
}
|
|
143
|
+
/** One view's mic: one press at a time. */
|
|
144
|
+
interface AvatarMic {
|
|
145
|
+
/** A new press. One still finishing is cancelled first (superseded); one starting or listening is left alone. */
|
|
146
|
+
start(): void;
|
|
147
|
+
/** Stop recording and wait for the final text (the visitor's tap). A press still starting is cancelled instead. */
|
|
148
|
+
stop(): void;
|
|
149
|
+
/** Call off whatever is under way, release the microphone, and report nothing more of it: back to `idle`. */
|
|
150
|
+
cancel(): void;
|
|
151
|
+
/** The press as it stands. */
|
|
152
|
+
state(): AvatarMicState;
|
|
153
|
+
}
|
|
154
|
+
/** What the mic's chunk (`dist/avatar-mic/`) exports. */
|
|
155
|
+
interface AvatarMicModule {
|
|
156
|
+
createAvatarMic: (options: AvatarMicOptions) => AvatarMic;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* BOFF-9797 (Stage C). The Avatar tab's mic, one press at a time: tap to start, tap to stop, thirty seconds at most.
|
|
161
|
+
*
|
|
162
|
+
* A press, in order:
|
|
163
|
+
*
|
|
164
|
+
* 1. **The microphone opens** (`startMicCapture`: the browser's permission prompt the first time). Meanwhile the offer
|
|
165
|
+
* is made ready: the token the last availability answer carried while it is fresh, else a new one, asked for now.
|
|
166
|
+
* 2. **The stream connects**, once the microphone is live (never while a permission prompt is up, so no stream is held
|
|
167
|
+
* open for a visitor who has not decided): `boff.web-stt.v1` and `offer.<token>`. A refused upgrade (4401) asks
|
|
168
|
+
* availability again and tries once more, never twice.
|
|
169
|
+
* 3. **The server says it is ready**: the press's `config` (its utterance id, its epoch, `en-IN`, and the mic notice the
|
|
170
|
+
* visitor saw), then up to {@link PRE_ROLL_MS} of what the microphone heard while the stream connected, then every
|
|
171
|
+
* frame as it comes. Listening, from here: what has been heard so far is reported as it is recognised.
|
|
172
|
+
* 4. **The recording stops**: the visitor's tap, or {@link MAX_PRESS_MS} (counted both on the clock and in audio sent),
|
|
173
|
+
* or the speech service's own limit. The microphone is released at once; `end`, and the last of the text.
|
|
174
|
+
* 5. **The text is final** (`done`): reported once, for the view to put in the box. Nothing is ever sent for the
|
|
175
|
+
* visitor: the text goes nowhere else.
|
|
176
|
+
*
|
|
177
|
+
* **Released on every way out.** The microphone's track is stopped the moment the recording stops, and also when the
|
|
178
|
+
* press fails, is cancelled (`cancel()`: the view going, the page hidden, the Chat tab) or superseded (a new press while
|
|
179
|
+
* one is still finishing: `cancel` for the old one, so it stops being paid for, and its text is never reported). A
|
|
180
|
+
* microphone that opens after its press has gone is closed as it opens. The stream is closed whenever no press needs
|
|
181
|
+
* it: one connection serves a press and the press that supersedes it, and no other.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
/** What a press needs from the browser. The browser's own by default; a test hands in fakes. */
|
|
185
|
+
interface AvatarMicSeams {
|
|
186
|
+
createSocket: (url: string, protocols: string[]) => SocketLike;
|
|
187
|
+
startCapture: (onFrame: (frame: CaptureFrameMessage) => void) => Promise<Pick<MicCaptureHandle, "stop">>;
|
|
188
|
+
newUtteranceId: () => string | null;
|
|
189
|
+
}
|
|
190
|
+
/** The mic of one Avatar tab. See the file's comment for what a press does. */
|
|
191
|
+
declare function createAvatarMic(options: AvatarMicOptions, seams?: Partial<AvatarMicSeams>): AvatarMic;
|
|
192
|
+
|
|
193
|
+
export { type AvatarMic, type AvatarMicFailure, type AvatarMicModule, type AvatarMicOffer, type AvatarMicOptions, type AvatarMicState, createAvatarMic };
|