speechflow 1.6.4 → 1.6.6
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 +22 -0
- package/README.md +28 -3
- package/etc/speechflow.yaml +15 -13
- package/etc/stx.conf +5 -0
- package/package.json +5 -5
- package/speechflow-cli/dst/speechflow-main-api.js +3 -7
- package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-graph.js +1 -1
- package/speechflow-cli/dst/speechflow-main.js +6 -0
- package/speechflow-cli/dst/speechflow-main.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +1 -21
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +1 -21
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +4 -10
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +70 -60
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +58 -42
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js +149 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch2-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch2.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js +202 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch2.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +13 -11
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +26 -25
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -2
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -21
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -2
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -15
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -2
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -18
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +0 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -10
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +8 -8
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +6 -4
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.js +6 -6
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-util-audio.js +22 -1
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-error.js +7 -1
- package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.d.ts +2 -1
- package/speechflow-cli/dst/speechflow-util-stream.js +23 -3
- package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/etc/tsconfig.json +1 -0
- package/speechflow-cli/package.json +20 -20
- package/speechflow-cli/src/speechflow-main-api.ts +6 -13
- package/speechflow-cli/src/speechflow-main-graph.ts +1 -1
- package/speechflow-cli/src/speechflow-main.ts +4 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +1 -29
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +1 -29
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +4 -10
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +83 -72
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +66 -46
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
- package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +14 -12
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +26 -25
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
- package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
- package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -21
- package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -15
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -18
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -10
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +12 -16
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
- package/speechflow-cli/src/speechflow-node-xio-device.ts +7 -5
- package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +11 -11
- package/speechflow-cli/src/speechflow-node.ts +6 -6
- package/speechflow-cli/src/speechflow-util-audio.ts +31 -1
- package/speechflow-cli/src/speechflow-util-error.ts +9 -3
- package/speechflow-cli/src/speechflow-util-stream.ts +31 -6
- package/speechflow-ui-db/dst/index.js +25 -25
- package/speechflow-ui-db/package.json +11 -11
- package/speechflow-ui-db/src/app.vue +14 -5
- package/speechflow-ui-st/dst/index.js +460 -25
- package/speechflow-ui-st/package.json +13 -13
- package/speechflow-ui-st/src/app.vue +8 -3
- package/speechflow-cli/dst/speechflow-util-webaudio-wt.js +0 -124
- package/speechflow-cli/dst/speechflow-util-webaudio-wt.js.map +0 -1
- package/speechflow-cli/dst/speechflow-util-webaudio.d.ts +0 -13
- package/speechflow-cli/dst/speechflow-util-webaudio.js +0 -137
- package/speechflow-cli/dst/speechflow-util-webaudio.js.map +0 -1
- /package/speechflow-cli/dst/{speechflow-util-webaudio-wt.d.ts → speechflow-node-a2a-pitch2-wt.d.ts} +0 -0
|
@@ -40,7 +40,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
40
40
|
private queueRecv = this.queue.pointerUse("recv")
|
|
41
41
|
private queueVAD = this.queue.pointerUse("vad")
|
|
42
42
|
private queueSend = this.queue.pointerUse("send")
|
|
43
|
-
private
|
|
43
|
+
private closing = false
|
|
44
44
|
private tailTimer: ReturnType<typeof setTimeout> | null = null
|
|
45
45
|
private activeEventListeners = new Set<() => void>()
|
|
46
46
|
|
|
@@ -71,7 +71,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
71
71
|
throw new Error("VAD node currently supports PCM-S16LE audio only")
|
|
72
72
|
|
|
73
73
|
/* clear destruction flag */
|
|
74
|
-
this.
|
|
74
|
+
this.closing = false
|
|
75
75
|
|
|
76
76
|
/* internal processing constants */
|
|
77
77
|
const vadSampleRateTarget = 16000 /* internal target of VAD */
|
|
@@ -98,7 +98,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
98
98
|
redemptionFrames: this.params.redemptionFrames,
|
|
99
99
|
preSpeechPadFrames: this.params.preSpeechPadFrames,
|
|
100
100
|
onSpeechStart: () => {
|
|
101
|
-
if (this.
|
|
101
|
+
if (this.closing)
|
|
102
102
|
return
|
|
103
103
|
this.log("info", "VAD: speech start")
|
|
104
104
|
if (this.params.mode === "unplugged") {
|
|
@@ -107,7 +107,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
onSpeechEnd: (audio) => {
|
|
110
|
-
if (this.
|
|
110
|
+
if (this.closing)
|
|
111
111
|
return
|
|
112
112
|
const duration = util.audioArrayDuration(audio, vadSampleRateTarget)
|
|
113
113
|
this.log("info", `VAD: speech end (duration: ${duration.toFixed(2)}s)`)
|
|
@@ -115,7 +115,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
115
115
|
tail = true
|
|
116
116
|
clearTailTimer()
|
|
117
117
|
this.tailTimer = setTimeout(() => {
|
|
118
|
-
if (this.
|
|
118
|
+
if (this.closing || this.tailTimer === null)
|
|
119
119
|
return
|
|
120
120
|
tail = false
|
|
121
121
|
this.tailTimer = null
|
|
@@ -123,14 +123,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
123
123
|
}
|
|
124
124
|
},
|
|
125
125
|
onVADMisfire: () => {
|
|
126
|
-
if (this.
|
|
126
|
+
if (this.closing)
|
|
127
127
|
return
|
|
128
128
|
this.log("info", "VAD: speech end (segment too short)")
|
|
129
129
|
if (this.params.mode === "unplugged") {
|
|
130
130
|
tail = true
|
|
131
131
|
clearTailTimer()
|
|
132
132
|
this.tailTimer = setTimeout(() => {
|
|
133
|
-
if (this.
|
|
133
|
+
if (this.closing || this.tailTimer === null)
|
|
134
134
|
return
|
|
135
135
|
tail = false
|
|
136
136
|
this.tailTimer = null
|
|
@@ -138,7 +138,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
onFrameProcessed: (audio) => {
|
|
141
|
-
if (this.
|
|
141
|
+
if (this.closing)
|
|
142
142
|
return
|
|
143
143
|
try {
|
|
144
144
|
/* annotate the current audio segment */
|
|
@@ -158,14 +158,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
catch (error) {
|
|
161
|
-
this.log("error", `VAD frame processing error: ${error}
|
|
161
|
+
this.log("error", `VAD frame processing error: ${error}`, { cause: error })
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
})
|
|
165
165
|
this.vad.start()
|
|
166
166
|
}
|
|
167
167
|
catch (error) {
|
|
168
|
-
throw new Error(`failed to initialize VAD: ${error}
|
|
168
|
+
throw new Error(`failed to initialize VAD: ${error}`, { cause: error })
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/* provide Duplex stream and internally attach to VAD */
|
|
@@ -178,7 +178,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
178
178
|
|
|
179
179
|
/* receive audio chunk (writable side of stream) */
|
|
180
180
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
181
|
-
if (self.
|
|
181
|
+
if (self.closing) {
|
|
182
182
|
callback(new Error("stream already destroyed"))
|
|
183
183
|
return
|
|
184
184
|
}
|
|
@@ -217,7 +217,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
217
217
|
})
|
|
218
218
|
|
|
219
219
|
/* push segments through Voice Activity Detection (VAD) */
|
|
220
|
-
if (self.vad && !self.
|
|
220
|
+
if (self.vad && !self.closing) {
|
|
221
221
|
try {
|
|
222
222
|
for (const segment of segmentData)
|
|
223
223
|
self.vad.processAudio(segment.data)
|
|
@@ -230,14 +230,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
230
230
|
callback()
|
|
231
231
|
}
|
|
232
232
|
catch (error) {
|
|
233
|
-
callback(error
|
|
233
|
+
callback(util.ensureError(error, "VAD processing failed"))
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
},
|
|
237
237
|
|
|
238
238
|
/* receive no more audio chunks (writable side of stream) */
|
|
239
239
|
final (callback) {
|
|
240
|
-
if (self.
|
|
240
|
+
if (self.closing) {
|
|
241
241
|
callback()
|
|
242
242
|
return
|
|
243
243
|
}
|
|
@@ -249,14 +249,14 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
249
249
|
|
|
250
250
|
/* send audio chunk(s) (readable side of stream) */
|
|
251
251
|
read (_size) {
|
|
252
|
-
if (self.
|
|
252
|
+
if (self.closing) {
|
|
253
253
|
this.push(null)
|
|
254
254
|
return
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/* try to perform read operation from scratch */
|
|
258
258
|
const tryToRead = () => {
|
|
259
|
-
if (self.
|
|
259
|
+
if (self.closing) {
|
|
260
260
|
this.push(null)
|
|
261
261
|
return
|
|
262
262
|
}
|
|
@@ -265,7 +265,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
265
265
|
const flushPendingChunks = () => {
|
|
266
266
|
let pushed = 0
|
|
267
267
|
while (true) {
|
|
268
|
-
if (self.
|
|
268
|
+
if (self.closing) {
|
|
269
269
|
this.push(null)
|
|
270
270
|
return
|
|
271
271
|
}
|
|
@@ -297,7 +297,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
297
297
|
mode we else would be never called again until
|
|
298
298
|
we at least once push a new chunk as the result */
|
|
299
299
|
setTimeout(() => {
|
|
300
|
-
if (self.
|
|
300
|
+
if (self.closing || self.queue === null)
|
|
301
301
|
return
|
|
302
302
|
tryToRead()
|
|
303
303
|
}, 0)
|
|
@@ -308,14 +308,15 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
308
308
|
|
|
309
309
|
/* await forthcoming audio chunks */
|
|
310
310
|
const awaitForthcomingChunks = () => {
|
|
311
|
-
|
|
311
|
+
self.activeEventListeners.delete(awaitForthcomingChunks)
|
|
312
|
+
if (self.closing)
|
|
312
313
|
return
|
|
313
314
|
const element = self.queueSend.peek()
|
|
314
315
|
if (element !== undefined
|
|
315
316
|
&& element.type === "audio-frame"
|
|
316
317
|
&& element.isSpeech !== undefined)
|
|
317
318
|
flushPendingChunks()
|
|
318
|
-
else if (!self.
|
|
319
|
+
else if (!self.closing && !self.activeEventListeners.has(awaitForthcomingChunks)) {
|
|
319
320
|
self.queue.once("write", awaitForthcomingChunks)
|
|
320
321
|
self.activeEventListeners.add(awaitForthcomingChunks)
|
|
321
322
|
}
|
|
@@ -328,7 +329,7 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
328
329
|
&& element.type === "audio-frame"
|
|
329
330
|
&& element.isSpeech !== undefined)
|
|
330
331
|
flushPendingChunks()
|
|
331
|
-
else if (!self.
|
|
332
|
+
else if (!self.closing && !self.activeEventListeners.has(awaitForthcomingChunks)) {
|
|
332
333
|
self.queue.once("write", awaitForthcomingChunks)
|
|
333
334
|
self.activeEventListeners.add(awaitForthcomingChunks)
|
|
334
335
|
}
|
|
@@ -340,8 +341,8 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
340
341
|
|
|
341
342
|
/* close node */
|
|
342
343
|
async close () {
|
|
343
|
-
/* indicate
|
|
344
|
-
this.
|
|
344
|
+
/* indicate closing */
|
|
345
|
+
this.closing = true
|
|
345
346
|
|
|
346
347
|
/* cleanup tail timer */
|
|
347
348
|
if (this.tailTimer !== null) {
|
|
@@ -355,9 +356,9 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
355
356
|
})
|
|
356
357
|
this.activeEventListeners.clear()
|
|
357
358
|
|
|
358
|
-
/*
|
|
359
|
+
/* shutdown stream */
|
|
359
360
|
if (this.stream !== null) {
|
|
360
|
-
this.stream
|
|
361
|
+
await util.destroyStream(this.stream)
|
|
361
362
|
this.stream = null
|
|
362
363
|
}
|
|
363
364
|
|
|
@@ -9,6 +9,7 @@ import Stream from "node:stream"
|
|
|
9
9
|
|
|
10
10
|
/* internal dependencies */
|
|
11
11
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
12
|
+
import * as util from "./speechflow-util"
|
|
12
13
|
|
|
13
14
|
/* write WAV header */
|
|
14
15
|
const writeWavHeader = (
|
|
@@ -190,13 +191,7 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
|
|
|
190
191
|
async close () {
|
|
191
192
|
/* shutdown stream */
|
|
192
193
|
if (this.stream !== null) {
|
|
193
|
-
await
|
|
194
|
-
if (this.stream instanceof Stream.Duplex)
|
|
195
|
-
this.stream.end(() => { resolve() })
|
|
196
|
-
else
|
|
197
|
-
resolve()
|
|
198
|
-
})
|
|
199
|
-
this.stream.destroy()
|
|
194
|
+
await util.destroyStream(this.stream)
|
|
200
195
|
this.stream = null
|
|
201
196
|
}
|
|
202
197
|
}
|
|
@@ -70,7 +70,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
70
70
|
/* internal state */
|
|
71
71
|
private client: TranscribeStreamingClient | null = null
|
|
72
72
|
private clientStream: AsyncIterable<TranscriptResultStream> | null = null
|
|
73
|
-
private
|
|
73
|
+
private closing = false
|
|
74
74
|
private initTimeout: ReturnType<typeof setTimeout> | null = null
|
|
75
75
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
76
76
|
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
@@ -111,7 +111,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
111
111
|
throw new Error("Amazon Transcribe node currently supports PCM-S16LE audio only")
|
|
112
112
|
|
|
113
113
|
/* clear destruction flag */
|
|
114
|
-
this.
|
|
114
|
+
this.closing = false
|
|
115
115
|
|
|
116
116
|
/* create queue for results */
|
|
117
117
|
this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
|
|
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
130
130
|
if (this.client === null)
|
|
131
131
|
throw new Error("failed to establish Amazon Transcribe client")
|
|
132
132
|
|
|
133
|
-
/*
|
|
133
|
+
/* create an AudioStream for Amazon Transcribe */
|
|
134
134
|
const audioQueue = new AsyncQueue<Uint8Array>()
|
|
135
135
|
const audioStream = (async function *(q: AsyncQueue<Uint8Array>): AsyncIterable<AudioStream> {
|
|
136
136
|
for await (const chunk of q) {
|
|
@@ -140,7 +140,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
140
140
|
|
|
141
141
|
/* start streaming */
|
|
142
142
|
const ensureAudioStreamActive = async () => {
|
|
143
|
-
if (this.clientStream !== null || this.
|
|
143
|
+
if (this.clientStream !== null || this.closing)
|
|
144
144
|
return
|
|
145
145
|
const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
|
|
146
146
|
const command = new StartStreamTranscriptionCommand({
|
|
@@ -172,7 +172,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
172
172
|
const tsStart = Duration.fromMillis((result.StartTime ?? 0) * 1000).plus(this.timeZeroOffset)
|
|
173
173
|
const tsEnd = Duration.fromMillis((result.EndTime ?? 0) * 1000).plus(this.timeZeroOffset)
|
|
174
174
|
const metas = metastore.fetch(tsStart, tsEnd)
|
|
175
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
175
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
176
176
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
177
177
|
return prev
|
|
178
178
|
}, new Map<string, any>())
|
|
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
210
210
|
decodeStrings: false,
|
|
211
211
|
highWaterMark: 1,
|
|
212
212
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
213
|
-
if (self.
|
|
213
|
+
if (self.closing || self.client === null) {
|
|
214
214
|
callback(new Error("stream already destroyed"))
|
|
215
215
|
return
|
|
216
216
|
}
|
|
@@ -223,7 +223,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
223
223
|
self.log("debug", `send data (${chunk.payload.byteLength} bytes)`)
|
|
224
224
|
if (chunk.meta.size > 0)
|
|
225
225
|
metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
|
|
226
|
-
audioQueue.push(new Uint8Array(chunk.payload)) /*
|
|
226
|
+
audioQueue.push(new Uint8Array(chunk.payload)) /* intentionally discard all time information */
|
|
227
227
|
ensureAudioStreamActive().catch((error: unknown) => {
|
|
228
228
|
self.log("error", `failed to start audio stream: ${util.ensureError(error).message}`)
|
|
229
229
|
})
|
|
@@ -232,12 +232,12 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
232
232
|
}
|
|
233
233
|
},
|
|
234
234
|
read (size) {
|
|
235
|
-
if (self.
|
|
235
|
+
if (self.closing || self.queue === null) {
|
|
236
236
|
this.push(null)
|
|
237
237
|
return
|
|
238
238
|
}
|
|
239
239
|
self.queue.read().then((chunk) => {
|
|
240
|
-
if (self.
|
|
240
|
+
if (self.closing || self.queue === null) {
|
|
241
241
|
this.push(null)
|
|
242
242
|
return
|
|
243
243
|
}
|
|
@@ -250,12 +250,12 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
250
250
|
this.push(chunk)
|
|
251
251
|
}
|
|
252
252
|
}).catch((error: unknown) => {
|
|
253
|
-
if (!self.
|
|
253
|
+
if (!self.closing && self.queue !== null)
|
|
254
254
|
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
255
255
|
})
|
|
256
256
|
},
|
|
257
257
|
final (callback) {
|
|
258
|
-
if (self.
|
|
258
|
+
if (self.closing || self.client === null) {
|
|
259
259
|
callback()
|
|
260
260
|
return
|
|
261
261
|
}
|
|
@@ -263,7 +263,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
263
263
|
() => self.client!.destroy(),
|
|
264
264
|
(error: Error) => self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
|
|
265
265
|
)
|
|
266
|
-
audioQueue.push(null) /*
|
|
266
|
+
audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
|
|
267
267
|
audioQueue.destroy()
|
|
268
268
|
callback()
|
|
269
269
|
}
|
|
@@ -272,8 +272,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
272
272
|
|
|
273
273
|
/* close node */
|
|
274
274
|
async close () {
|
|
275
|
-
/* indicate
|
|
276
|
-
this.
|
|
275
|
+
/* indicate closing first to stop all async operations */
|
|
276
|
+
this.closing = true
|
|
277
277
|
|
|
278
278
|
/* cleanup all timers */
|
|
279
279
|
if (this.initTimeout !== null) {
|
|
@@ -297,9 +297,9 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
297
297
|
this.client = null
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
/*
|
|
300
|
+
/* shutdown stream */
|
|
301
301
|
if (this.stream !== null) {
|
|
302
|
-
this.stream
|
|
302
|
+
await util.destroyStream(this.stream)
|
|
303
303
|
this.stream = null
|
|
304
304
|
}
|
|
305
305
|
}
|
|
@@ -22,7 +22,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
22
22
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private dg: Deepgram.LiveClient | null = null
|
|
25
|
-
private
|
|
25
|
+
private closing = false
|
|
26
26
|
private initTimeout: ReturnType<typeof setTimeout> | null = null
|
|
27
27
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
28
28
|
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
@@ -75,7 +75,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
75
75
|
throw new Error("Deepgram node currently supports PCM-S16LE audio only")
|
|
76
76
|
|
|
77
77
|
/* clear destruction flag */
|
|
78
|
-
this.
|
|
78
|
+
this.closing = false
|
|
79
79
|
|
|
80
80
|
/* create queue for results */
|
|
81
81
|
this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
|
|
@@ -114,7 +114,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
114
114
|
|
|
115
115
|
/* hook onto Deepgram API events */
|
|
116
116
|
this.dg.on(Deepgram.LiveTranscriptionEvents.Transcript, async (data) => {
|
|
117
|
-
if (this.
|
|
117
|
+
if (this.closing || this.queue === null)
|
|
118
118
|
return
|
|
119
119
|
const text = (data.channel?.alternatives[0]?.transcript ?? "") as string
|
|
120
120
|
const words = (data.channel?.alternatives[0]?.words ?? []) as
|
|
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
130
130
|
const start = Duration.fromMillis(data.start * 1000).plus(this.timeZeroOffset)
|
|
131
131
|
const end = start.plus({ seconds: data.duration })
|
|
132
132
|
const metas = metastore.fetch(start, end)
|
|
133
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
133
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
134
134
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
135
135
|
return prev
|
|
136
136
|
}, new Map<string, any>())
|
|
@@ -156,12 +156,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
156
156
|
})
|
|
157
157
|
this.dg.on(Deepgram.LiveTranscriptionEvents.Close, () => {
|
|
158
158
|
this.log("info", "connection close")
|
|
159
|
-
if (!this.
|
|
159
|
+
if (!this.closing && this.queue !== null)
|
|
160
160
|
this.queue.write(null)
|
|
161
161
|
})
|
|
162
162
|
this.dg.on(Deepgram.LiveTranscriptionEvents.Error, (error: Error) => {
|
|
163
163
|
this.log("error", `error: ${error.message}`)
|
|
164
|
-
if (!this.
|
|
164
|
+
if (!this.closing && this.queue !== null)
|
|
165
165
|
this.queue.write(null)
|
|
166
166
|
this.emit("error")
|
|
167
167
|
})
|
|
@@ -193,7 +193,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
193
193
|
decodeStrings: false,
|
|
194
194
|
highWaterMark: 1,
|
|
195
195
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
196
|
-
if (self.
|
|
196
|
+
if (self.closing || self.dg === null) {
|
|
197
197
|
callback(new Error("stream already destroyed"))
|
|
198
198
|
return
|
|
199
199
|
}
|
|
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
210
210
|
self.dg.send(chunk.payload.buffer) /* intentionally discard all time information */
|
|
211
211
|
}
|
|
212
212
|
catch (error) {
|
|
213
|
-
callback(error
|
|
213
|
+
callback(util.ensureError(error, "failed to send to Deepgram"))
|
|
214
214
|
return
|
|
215
215
|
}
|
|
216
216
|
}
|
|
@@ -218,12 +218,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
218
218
|
}
|
|
219
219
|
},
|
|
220
220
|
read (size) {
|
|
221
|
-
if (self.
|
|
221
|
+
if (self.closing || self.queue === null) {
|
|
222
222
|
this.push(null)
|
|
223
223
|
return
|
|
224
224
|
}
|
|
225
225
|
self.queue.read().then((chunk) => {
|
|
226
|
-
if (self.
|
|
226
|
+
if (self.closing || self.queue === null) {
|
|
227
227
|
this.push(null)
|
|
228
228
|
return
|
|
229
229
|
}
|
|
@@ -236,12 +236,12 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
236
236
|
this.push(chunk)
|
|
237
237
|
}
|
|
238
238
|
}).catch((error: unknown) => {
|
|
239
|
-
if (!self.
|
|
239
|
+
if (!self.closing && self.queue !== null)
|
|
240
240
|
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
241
241
|
})
|
|
242
242
|
},
|
|
243
243
|
final (callback) {
|
|
244
|
-
if (self.
|
|
244
|
+
if (self.closing || self.dg === null) {
|
|
245
245
|
callback()
|
|
246
246
|
return
|
|
247
247
|
}
|
|
@@ -259,8 +259,8 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
259
259
|
|
|
260
260
|
/* close node */
|
|
261
261
|
async close () {
|
|
262
|
-
/* indicate
|
|
263
|
-
this.
|
|
262
|
+
/* indicate closing first to stop all async operations */
|
|
263
|
+
this.closing = true
|
|
264
264
|
|
|
265
265
|
/* cleanup all timers */
|
|
266
266
|
if (this.initTimeout !== null) {
|
|
@@ -272,9 +272,9 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
272
272
|
this.connectionTimeout = null
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
/*
|
|
275
|
+
/* shutdown stream */
|
|
276
276
|
if (this.stream !== null) {
|
|
277
|
-
this.stream
|
|
277
|
+
await util.destroyStream(this.stream)
|
|
278
278
|
this.stream = null
|
|
279
279
|
}
|
|
280
280
|
|
|
@@ -23,12 +23,11 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
23
23
|
public static name = "a2t-openai"
|
|
24
24
|
|
|
25
25
|
/* internal state */
|
|
26
|
-
private static speexInitialized = false
|
|
27
26
|
private openai: OpenAI | null = null
|
|
28
27
|
private ws: ws.WebSocket | null = null
|
|
29
28
|
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
30
29
|
private resampler: SpeexResampler | null = null
|
|
31
|
-
private
|
|
30
|
+
private closing = false
|
|
32
31
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
33
32
|
|
|
34
33
|
/* construct node */
|
|
@@ -61,7 +60,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
61
60
|
throw new Error("OpenAI transcribe node currently supports PCM-S16LE audio only")
|
|
62
61
|
|
|
63
62
|
/* clear destruction flag */
|
|
64
|
-
this.
|
|
63
|
+
this.closing = false
|
|
65
64
|
|
|
66
65
|
/* create queue for results */
|
|
67
66
|
this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
|
|
@@ -71,11 +70,6 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
71
70
|
|
|
72
71
|
/* establish resampler from our standard audio sample rate (48Khz)
|
|
73
72
|
to OpenAI's maximum 24Khz input sample rate */
|
|
74
|
-
if (!SpeechFlowNodeA2TOpenAI.speexInitialized) {
|
|
75
|
-
/* at least once initialize resampler */
|
|
76
|
-
await SpeexResampler.initPromise
|
|
77
|
-
SpeechFlowNodeA2TOpenAI.speexInitialized = true
|
|
78
|
-
}
|
|
79
73
|
this.resampler = new SpeexResampler(1, this.config.audioSampleRate, 24000, 7)
|
|
80
74
|
|
|
81
75
|
/* instantiate OpenAI API */
|
|
@@ -178,7 +172,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
178
172
|
const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
|
|
179
173
|
const end = start // FIXME: OpenAI does not provide timestamps
|
|
180
174
|
const metas = metastore.fetch(start, end)
|
|
181
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
175
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
182
176
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
183
177
|
return prev
|
|
184
178
|
}, new Map<string, any>())
|
|
@@ -193,7 +187,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
193
187
|
const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
|
|
194
188
|
const end = start // FIXME: OpenAI does not provide timestamps
|
|
195
189
|
const metas = metastore.fetch(start, end)
|
|
196
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
190
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
197
191
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
198
192
|
return prev
|
|
199
193
|
}, new Map<string, any>())
|
|
@@ -232,7 +226,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
232
226
|
decodeStrings: false,
|
|
233
227
|
highWaterMark: 1,
|
|
234
228
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
235
|
-
if (self.
|
|
229
|
+
if (self.closing || self.ws === null) {
|
|
236
230
|
callback(new Error("stream already destroyed"))
|
|
237
231
|
return
|
|
238
232
|
}
|
|
@@ -254,7 +248,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
254
248
|
})
|
|
255
249
|
}
|
|
256
250
|
catch (error) {
|
|
257
|
-
callback(error
|
|
251
|
+
callback(util.ensureError(error, "failed to send to OpenAI transcribe"))
|
|
258
252
|
return
|
|
259
253
|
}
|
|
260
254
|
}
|
|
@@ -262,12 +256,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
262
256
|
}
|
|
263
257
|
},
|
|
264
258
|
read (size) {
|
|
265
|
-
if (self.
|
|
259
|
+
if (self.closing || self.queue === null) {
|
|
266
260
|
this.push(null)
|
|
267
261
|
return
|
|
268
262
|
}
|
|
269
263
|
self.queue.read().then((chunk) => {
|
|
270
|
-
if (self.
|
|
264
|
+
if (self.closing || self.queue === null) {
|
|
271
265
|
this.push(null)
|
|
272
266
|
return
|
|
273
267
|
}
|
|
@@ -280,12 +274,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
280
274
|
this.push(chunk)
|
|
281
275
|
}
|
|
282
276
|
}).catch((error: unknown) => {
|
|
283
|
-
if (!self.
|
|
277
|
+
if (!self.closing && self.queue !== null)
|
|
284
278
|
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
285
279
|
})
|
|
286
280
|
},
|
|
287
281
|
final (callback) {
|
|
288
|
-
if (self.
|
|
282
|
+
if (self.closing || self.ws === null) {
|
|
289
283
|
callback()
|
|
290
284
|
return
|
|
291
285
|
}
|
|
@@ -297,7 +291,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
297
291
|
}
|
|
298
292
|
catch (error) {
|
|
299
293
|
self.log("warning", `error closing OpenAI connection: ${error}`)
|
|
300
|
-
callback(error
|
|
294
|
+
callback(util.ensureError(error, "failed to close OpenAI connection"))
|
|
301
295
|
}
|
|
302
296
|
}
|
|
303
297
|
})
|
|
@@ -305,8 +299,8 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
305
299
|
|
|
306
300
|
/* close node */
|
|
307
301
|
async close () {
|
|
308
|
-
/* indicate
|
|
309
|
-
this.
|
|
302
|
+
/* indicate closing first to stop all async operations */
|
|
303
|
+
this.closing = true
|
|
310
304
|
|
|
311
305
|
/* clear connection timeout */
|
|
312
306
|
if (this.connectionTimeout !== null) {
|
|
@@ -328,9 +322,9 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
328
322
|
if (this.openai !== null)
|
|
329
323
|
this.openai = null
|
|
330
324
|
|
|
331
|
-
/*
|
|
325
|
+
/* shutdown stream */
|
|
332
326
|
if (this.stream !== null) {
|
|
333
|
-
this.stream
|
|
327
|
+
await util.destroyStream(this.stream)
|
|
334
328
|
this.stream = null
|
|
335
329
|
}
|
|
336
330
|
}
|