hume 0.15.8 → 0.15.9
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/cjs/Client.d.ts +2 -2
- package/dist/cjs/Client.js +6 -6
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/wrapper/SilenceFiller.d.ts +64 -10
- package/dist/cjs/wrapper/SilenceFiller.js +109 -26
- package/dist/cjs/wrapper/index.d.ts +5 -0
- package/dist/cjs/wrapper/index.js +6 -6
- package/dist/esm/Client.d.mts +2 -2
- package/dist/esm/Client.mjs +6 -6
- package/dist/esm/version.d.mts +1 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/esm/wrapper/SilenceFiller.d.mts +64 -10
- package/dist/esm/wrapper/SilenceFiller.mjs +109 -26
- package/dist/esm/wrapper/index.d.mts +5 -0
- package/dist/esm/wrapper/index.mjs +4 -5
- package/package.json +1 -1
- package/reference.md +709 -709
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { Readable } from "stream";
|
|
2
1
|
/**
|
|
3
|
-
* SilenceFiller is a
|
|
2
|
+
* SilenceFiller is a pipeable stream that intersperses incoming audio data
|
|
4
3
|
* with bytes of silence. This is important in some cases to keep an audio
|
|
5
4
|
* stream "alive". Audio players, such as ffmpeg, can interpret inactivity as
|
|
6
5
|
* meaning the stream is ended, or disconnected.
|
|
7
6
|
*
|
|
7
|
+
* This implementation does not depend on Node.js built-ins and can work in
|
|
8
|
+
* any JavaScript environment, while still being pipeable to Node.js streams.
|
|
9
|
+
*
|
|
8
10
|
* @example
|
|
9
11
|
* ```typescript
|
|
10
12
|
* import { SilenceFiller } from 'hume';
|
|
@@ -29,7 +31,7 @@ import { Readable } from "stream";
|
|
|
29
31
|
* await silenceFiller.endStream();
|
|
30
32
|
* ```
|
|
31
33
|
*/
|
|
32
|
-
export class SilenceFiller
|
|
34
|
+
export class SilenceFiller {
|
|
33
35
|
/**
|
|
34
36
|
* Creates a new SilenceFiller instance.
|
|
35
37
|
*
|
|
@@ -41,17 +43,90 @@ export class SilenceFiller extends Readable {
|
|
|
41
43
|
* playback will take longer to start.
|
|
42
44
|
*/
|
|
43
45
|
constructor(pushIntervalMs = 5, sampleRate = 48000, bytesPerSample = 2, bufferSize = 9600) {
|
|
44
|
-
super({ objectMode: false });
|
|
45
46
|
this.isStarted = false;
|
|
46
|
-
this.
|
|
47
|
+
this.pushIntervalId = null;
|
|
48
|
+
this.destination = null;
|
|
49
|
+
this.eventListeners = new Map();
|
|
50
|
+
this.ended = false;
|
|
47
51
|
this.unclockedSilenceFiller = new UnclockedSilenceFiller(bufferSize, sampleRate, bytesPerSample);
|
|
48
52
|
this.bytesPerSample = bytesPerSample;
|
|
49
53
|
this.pushIntervalMs = pushIntervalMs;
|
|
50
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Pipes the output of this SilenceFiller to a writable destination.
|
|
57
|
+
*
|
|
58
|
+
* @param destination - The destination to pipe to (e.g., a Node.js Writable stream).
|
|
59
|
+
* @returns The destination, for chaining.
|
|
60
|
+
*/
|
|
61
|
+
pipe(destination) {
|
|
62
|
+
this.destination = destination;
|
|
63
|
+
return destination;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Registers an event listener.
|
|
67
|
+
*
|
|
68
|
+
* @param event - The event name ('error', 'end').
|
|
69
|
+
* @param listener - The listener function.
|
|
70
|
+
* @returns This instance, for chaining.
|
|
71
|
+
*/
|
|
72
|
+
on(event, listener) {
|
|
73
|
+
if (!this.eventListeners.has(event)) {
|
|
74
|
+
this.eventListeners.set(event, new Set());
|
|
75
|
+
}
|
|
76
|
+
this.eventListeners.get(event).add(listener);
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Registers a one-time event listener.
|
|
81
|
+
*
|
|
82
|
+
* @param event - The event name ('error', 'end').
|
|
83
|
+
* @param listener - The listener function.
|
|
84
|
+
* @returns This instance, for chaining.
|
|
85
|
+
*/
|
|
86
|
+
once(event, listener) {
|
|
87
|
+
const onceWrapper = (...args) => {
|
|
88
|
+
this.off(event, onceWrapper);
|
|
89
|
+
listener(...args);
|
|
90
|
+
};
|
|
91
|
+
return this.on(event, onceWrapper);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Removes an event listener.
|
|
95
|
+
*
|
|
96
|
+
* @param event - The event name.
|
|
97
|
+
* @param listener - The listener function to remove.
|
|
98
|
+
* @returns This instance, for chaining.
|
|
99
|
+
*/
|
|
100
|
+
off(event, listener) {
|
|
101
|
+
const listeners = this.eventListeners.get(event);
|
|
102
|
+
if (listeners) {
|
|
103
|
+
listeners.delete(listener);
|
|
104
|
+
}
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Emits an event to all registered listeners.
|
|
109
|
+
*
|
|
110
|
+
* @param event - The event name.
|
|
111
|
+
* @param args - Arguments to pass to listeners.
|
|
112
|
+
*/
|
|
113
|
+
emit(event, ...args) {
|
|
114
|
+
const listeners = this.eventListeners.get(event);
|
|
115
|
+
if (listeners) {
|
|
116
|
+
for (const listener of listeners) {
|
|
117
|
+
try {
|
|
118
|
+
listener(...args);
|
|
119
|
+
}
|
|
120
|
+
catch (_a) {
|
|
121
|
+
// Ignore errors in listeners
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
51
126
|
/**
|
|
52
127
|
* Writes audio data to the silence filler.
|
|
53
128
|
*
|
|
54
|
-
* @param audioBuffer - The audio buffer to write.
|
|
129
|
+
* @param audioBuffer - The audio buffer to write (Uint8Array or Buffer).
|
|
55
130
|
*/
|
|
56
131
|
writeAudio(audioBuffer) {
|
|
57
132
|
const now = Date.now();
|
|
@@ -68,12 +143,12 @@ export class SilenceFiller extends Readable {
|
|
|
68
143
|
}
|
|
69
144
|
}
|
|
70
145
|
startPushInterval() {
|
|
71
|
-
this.
|
|
146
|
+
this.pushIntervalId = setInterval(() => {
|
|
72
147
|
this.pushData();
|
|
73
148
|
}, this.pushIntervalMs);
|
|
74
149
|
}
|
|
75
150
|
pushData() {
|
|
76
|
-
if (!this.isStarted)
|
|
151
|
+
if (!this.isStarted || !this.destination)
|
|
77
152
|
return;
|
|
78
153
|
try {
|
|
79
154
|
const now = Date.now();
|
|
@@ -83,7 +158,7 @@ export class SilenceFiller extends Readable {
|
|
|
83
158
|
const alignedChunkSize = Math.floor(audioChunk.length / this.bytesPerSample) * this.bytesPerSample;
|
|
84
159
|
if (alignedChunkSize > 0) {
|
|
85
160
|
const chunk = audioChunk.subarray(0, alignedChunkSize);
|
|
86
|
-
this.
|
|
161
|
+
this.destination.write(chunk);
|
|
87
162
|
}
|
|
88
163
|
}
|
|
89
164
|
}
|
|
@@ -92,10 +167,6 @@ export class SilenceFiller extends Readable {
|
|
|
92
167
|
this.emit("error", error);
|
|
93
168
|
}
|
|
94
169
|
}
|
|
95
|
-
_read() { }
|
|
96
|
-
_destroy(error, callback) {
|
|
97
|
-
super._destroy(error, callback);
|
|
98
|
-
}
|
|
99
170
|
/**
|
|
100
171
|
* Ends the stream and drains all remaining audio data.
|
|
101
172
|
*
|
|
@@ -103,15 +174,20 @@ export class SilenceFiller extends Readable {
|
|
|
103
174
|
*/
|
|
104
175
|
endStream() {
|
|
105
176
|
return new Promise((resolve) => {
|
|
177
|
+
if (this.ended) {
|
|
178
|
+
resolve();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.ended = true;
|
|
106
182
|
// Stop pushing data
|
|
107
|
-
if (this.
|
|
108
|
-
clearInterval(this.
|
|
109
|
-
this.
|
|
183
|
+
if (this.pushIntervalId) {
|
|
184
|
+
clearInterval(this.pushIntervalId);
|
|
185
|
+
this.pushIntervalId = null;
|
|
110
186
|
}
|
|
111
187
|
// Drain all remaining audio from SilenceFiller
|
|
112
188
|
const now = Date.now();
|
|
113
189
|
// Keep reading until no more audio is available
|
|
114
|
-
while (
|
|
190
|
+
while (this.destination) {
|
|
115
191
|
const remainingChunk = this.unclockedSilenceFiller.readAudio(now);
|
|
116
192
|
if (!remainingChunk || remainingChunk.length === 0) {
|
|
117
193
|
break;
|
|
@@ -119,13 +195,11 @@ export class SilenceFiller extends Readable {
|
|
|
119
195
|
const alignedChunkSize = Math.floor(remainingChunk.length / this.bytesPerSample) * this.bytesPerSample;
|
|
120
196
|
if (alignedChunkSize > 0) {
|
|
121
197
|
const chunk = remainingChunk.subarray(0, alignedChunkSize);
|
|
122
|
-
this.
|
|
198
|
+
this.destination.write(chunk);
|
|
123
199
|
}
|
|
124
200
|
}
|
|
125
|
-
this.
|
|
126
|
-
|
|
127
|
-
resolve();
|
|
128
|
-
});
|
|
201
|
+
this.emit("end");
|
|
202
|
+
resolve();
|
|
129
203
|
});
|
|
130
204
|
}
|
|
131
205
|
}
|
|
@@ -172,11 +246,11 @@ export class UnclockedSilenceFiller {
|
|
|
172
246
|
if (alignedBytesNeeded <= 0) {
|
|
173
247
|
return null;
|
|
174
248
|
}
|
|
175
|
-
let chunk =
|
|
249
|
+
let chunk = new Uint8Array(0);
|
|
176
250
|
// Drain from queue until we have enough bytes
|
|
177
251
|
while (chunk.length < alignedBytesNeeded && this.audioQueue.length > 0) {
|
|
178
252
|
const nextBuffer = this.audioQueue.shift();
|
|
179
|
-
chunk =
|
|
253
|
+
chunk = concatUint8Arrays(chunk, nextBuffer);
|
|
180
254
|
this.totalBufferedBytes -= nextBuffer.length;
|
|
181
255
|
}
|
|
182
256
|
// If we have more than needed, put the excess back
|
|
@@ -188,11 +262,20 @@ export class UnclockedSilenceFiller {
|
|
|
188
262
|
}
|
|
189
263
|
// Fill remaining with silence if needed
|
|
190
264
|
if (chunk.length < alignedBytesNeeded) {
|
|
191
|
-
const silenceNeeded =
|
|
192
|
-
chunk =
|
|
265
|
+
const silenceNeeded = new Uint8Array(alignedBytesNeeded - chunk.length); // Uint8Array is zero-filled by default
|
|
266
|
+
chunk = concatUint8Arrays(chunk, silenceNeeded);
|
|
193
267
|
}
|
|
194
268
|
// Update total bytes sent
|
|
195
269
|
this.totalBytesSent += chunk.length;
|
|
196
270
|
return chunk;
|
|
197
271
|
}
|
|
198
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Concatenates two Uint8Arrays into a new Uint8Array.
|
|
275
|
+
*/
|
|
276
|
+
function concatUint8Arrays(a, b) {
|
|
277
|
+
const result = new Uint8Array(a.length + b.length);
|
|
278
|
+
result.set(a, 0);
|
|
279
|
+
result.set(b, a.length);
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
@@ -12,4 +12,9 @@ export { ExpressionMeasurement } from "./expressionMeasurement/ExpressionMeasure
|
|
|
12
12
|
export { EVIWebAudioPlayer } from "./EVIWebAudioPlayer.mjs";
|
|
13
13
|
export type { EVIWebAudioPlayerFFTOptions, EVIWebAudioPlayerOptions } from "./EVIWebAudioPlayer.mjs";
|
|
14
14
|
export { collate } from "./collate.mjs";
|
|
15
|
+
export { SilenceFiller } from "./SilenceFiller.mjs";
|
|
16
|
+
export type { PipeDestination } from "./SilenceFiller.mjs";
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated SilenceFiller no longer requires dynamic import. Use `import { SilenceFiller } from 'hume'` directly.
|
|
19
|
+
*/
|
|
15
20
|
export declare const createSilenceFiller: () => Promise<typeof import("./SilenceFiller.mjs").SilenceFiller>;
|
|
@@ -20,12 +20,11 @@ export { HumeClient } from "./HumeClient.mjs";
|
|
|
20
20
|
export { ExpressionMeasurement } from "./expressionMeasurement/ExpressionMeasurementClient.mjs";
|
|
21
21
|
export { EVIWebAudioPlayer } from "./EVIWebAudioPlayer.mjs";
|
|
22
22
|
export { collate } from "./collate.mjs";
|
|
23
|
-
|
|
23
|
+
export { SilenceFiller } from "./SilenceFiller.mjs";
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated SilenceFiller no longer requires dynamic import. Use `import { SilenceFiller } from 'hume'` directly.
|
|
26
|
+
*/
|
|
24
27
|
export const createSilenceFiller = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
-
var _a;
|
|
26
|
-
if (typeof process === "undefined" || !((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node)) {
|
|
27
|
-
throw new Error("SilenceFiller is only available in Node.js environments");
|
|
28
|
-
}
|
|
29
28
|
const { SilenceFiller } = yield import("./SilenceFiller.mjs");
|
|
30
29
|
return SilenceFiller;
|
|
31
30
|
});
|