dsh-live-voice 0.2.0 → 0.2.2
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/CHANGELOG.md +41 -1
- package/README.md +43 -17
- package/lib/client.js +297 -72
- package/lib/server.js +3 -0
- package/package.json +19 -12
- package/src/client/components.ts +58 -19
- package/src/client/index.ts +87 -41
- package/src/core/coordinator.ts +66 -13
- package/src/core/settings.ts +17 -0
- package/src/engines/recognition/browser.ts +38 -0
- package/src/engines/recognition/whisper-http.ts +80 -29
|
@@ -39,12 +39,18 @@ export function encodeMonoPcm16Wav(samples, inputRate) {
|
|
|
39
39
|
return buffer;
|
|
40
40
|
}
|
|
41
41
|
export class WhisperHttpRecognitionEngine {
|
|
42
|
-
constructor({
|
|
42
|
+
constructor({
|
|
43
|
+
globals = globalThis,
|
|
44
|
+
meter,
|
|
45
|
+
voiceDetectionPreset = 'natural',
|
|
46
|
+
maxUtteranceSeconds = 60,
|
|
47
|
+
} = {}) {
|
|
43
48
|
this.g = globals;
|
|
44
49
|
this.meter = meter;
|
|
45
50
|
this.session = null;
|
|
46
51
|
this.lang = 'pt-BR';
|
|
47
52
|
this.voiceDetectionPreset = voiceDetectionPreset;
|
|
53
|
+
this.maxUtteranceSeconds = maxUtteranceSeconds;
|
|
48
54
|
this.route = ROUTE;
|
|
49
55
|
}
|
|
50
56
|
get segmentation() {
|
|
@@ -73,7 +79,14 @@ export class WhisperHttpRecognitionEngine {
|
|
|
73
79
|
};
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
|
-
async start({
|
|
82
|
+
async start({
|
|
83
|
+
lang = this.lang,
|
|
84
|
+
signal,
|
|
85
|
+
onResult,
|
|
86
|
+
onActivity,
|
|
87
|
+
onError,
|
|
88
|
+
onProcessingChange,
|
|
89
|
+
} = {}) {
|
|
77
90
|
await this.stop();
|
|
78
91
|
if (signal?.aborted) throw abortError();
|
|
79
92
|
const context = this.meter?.context,
|
|
@@ -95,16 +108,25 @@ export class WhisperHttpRecognitionEngine {
|
|
|
95
108
|
samples: 0,
|
|
96
109
|
voiced: false,
|
|
97
110
|
silence: 0,
|
|
98
|
-
|
|
111
|
+
transcriptionQueue: [],
|
|
112
|
+
activeRequest: null,
|
|
113
|
+
draining: false,
|
|
99
114
|
onResult,
|
|
100
115
|
onActivity,
|
|
101
116
|
onError,
|
|
117
|
+
onProcessingChange,
|
|
102
118
|
lang,
|
|
103
119
|
signal,
|
|
104
120
|
};
|
|
105
121
|
this.session = session;
|
|
106
122
|
const valid = () => this.session === session && !signal?.aborted;
|
|
107
|
-
const
|
|
123
|
+
const notifyProcessing = () =>
|
|
124
|
+
session.onProcessingChange?.({
|
|
125
|
+
queued: session.transcriptionQueue.length,
|
|
126
|
+
active: !!session.activeRequest,
|
|
127
|
+
pending: session.transcriptionQueue.length + (session.activeRequest ? 1 : 0),
|
|
128
|
+
});
|
|
129
|
+
const enqueue = () => {
|
|
108
130
|
if (!session.voiced || session.samples < context.sampleRate * 0.25) {
|
|
109
131
|
session.chunks = [];
|
|
110
132
|
session.samples = 0;
|
|
@@ -122,29 +144,46 @@ export class WhisperHttpRecognitionEngine {
|
|
|
122
144
|
session.samples = 0;
|
|
123
145
|
session.voiced = false;
|
|
124
146
|
session.silence = 0;
|
|
125
|
-
|
|
126
|
-
|
|
147
|
+
session.transcriptionQueue.push(samples);
|
|
148
|
+
notifyProcessing();
|
|
149
|
+
void drain();
|
|
150
|
+
};
|
|
151
|
+
const drain = async () => {
|
|
152
|
+
if (session.draining || !valid()) return;
|
|
153
|
+
session.draining = true;
|
|
127
154
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
while (valid() && session.transcriptionQueue.length) {
|
|
156
|
+
const samples = session.transcriptionQueue.shift();
|
|
157
|
+
const request = new AbortController();
|
|
158
|
+
session.activeRequest = request;
|
|
159
|
+
notifyProcessing();
|
|
160
|
+
try {
|
|
161
|
+
const response = await this.g.fetch(this.route + '/transcribe', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
credentials: 'same-origin',
|
|
164
|
+
headers: {
|
|
165
|
+
'content-type': 'audio/wav',
|
|
166
|
+
'x-dlv-client-id': session.operation,
|
|
167
|
+
'x-dlv-operation-id': id(),
|
|
168
|
+
'x-dlv-language': lang,
|
|
169
|
+
},
|
|
170
|
+
body: encodeMonoPcm16Wav(samples, context.sampleRate),
|
|
171
|
+
signal: request.signal,
|
|
172
|
+
});
|
|
173
|
+
const json = await response.json();
|
|
174
|
+
if (!response.ok || !json?.ok)
|
|
175
|
+
throw new Error(json?.error?.message || 'HTTP transcription failed.');
|
|
176
|
+
if (valid() && json.value.text) onResult?.({ final: json.value.text, interim: '' });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (error.name !== 'AbortError' && valid()) onError?.(error);
|
|
179
|
+
} finally {
|
|
180
|
+
if (session.activeRequest === request) session.activeRequest = null;
|
|
181
|
+
notifyProcessing();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
146
184
|
} finally {
|
|
147
|
-
session.
|
|
185
|
+
session.draining = false;
|
|
186
|
+
notifyProcessing();
|
|
148
187
|
}
|
|
149
188
|
};
|
|
150
189
|
processor.onaudioprocess = (event) => {
|
|
@@ -164,14 +203,24 @@ export class WhisperHttpRecognitionEngine {
|
|
|
164
203
|
if (
|
|
165
204
|
(session.voiced &&
|
|
166
205
|
session.silence > context.sampleRate * (this.segmentation.silenceMs / 1000)) ||
|
|
167
|
-
session.samples > context.sampleRate *
|
|
206
|
+
session.samples > context.sampleRate * this.maxUtteranceSeconds
|
|
168
207
|
)
|
|
169
|
-
|
|
208
|
+
enqueue();
|
|
170
209
|
};
|
|
171
210
|
source.connect(processor);
|
|
211
|
+
session.finish = enqueue;
|
|
172
212
|
session.abort = () => this.stop();
|
|
173
213
|
signal?.addEventListener('abort', session.abort, { once: true });
|
|
174
214
|
}
|
|
215
|
+
finish() {
|
|
216
|
+
const session = this.session;
|
|
217
|
+
if (!session) return;
|
|
218
|
+
session.processor.onaudioprocess = null;
|
|
219
|
+
try {
|
|
220
|
+
this.meter?.source?.disconnect(session.processor);
|
|
221
|
+
} catch {}
|
|
222
|
+
session.finish?.();
|
|
223
|
+
}
|
|
175
224
|
async stop() {
|
|
176
225
|
const session = this.session;
|
|
177
226
|
if (!session) return;
|
|
@@ -185,7 +234,9 @@ export class WhisperHttpRecognitionEngine {
|
|
|
185
234
|
session.processor.disconnect();
|
|
186
235
|
session.gain?.disconnect();
|
|
187
236
|
} catch {}
|
|
188
|
-
|
|
189
|
-
session.
|
|
237
|
+
session.transcriptionQueue = [];
|
|
238
|
+
session.activeRequest?.abort();
|
|
239
|
+
session.activeRequest = null;
|
|
240
|
+
session.onProcessingChange?.({ queued: 0, active: false, pending: 0 });
|
|
190
241
|
}
|
|
191
242
|
}
|