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.
@@ -1,10 +1,12 @@
1
- import { Readable } from "stream";
2
1
  /**
3
- * SilenceFiller is a Readable stream that intersperses incoming audio data
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 extends Readable {
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.pushInterval = null;
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.pushInterval = setInterval(() => {
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.push(chunk);
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.pushInterval) {
108
- clearInterval(this.pushInterval);
109
- this.pushInterval = null;
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 (true) {
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.push(chunk);
198
+ this.destination.write(chunk);
123
199
  }
124
200
  }
125
- this.push(null); // Signal end of stream
126
- this.once("end", () => {
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 = Buffer.alloc(0);
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 = Buffer.concat([chunk, nextBuffer]);
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 = Buffer.alloc(alignedBytesNeeded - chunk.length, 0);
192
- chunk = Buffer.concat([chunk, silenceNeeded]);
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
- // SilenceFiller extends from Node.JS Readable -- this should not be exported in non-nodeJS environments. Otherwise the bundle will crash in the browser.
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hume",
3
- "version": "0.15.8",
3
+ "version": "0.15.9",
4
4
  "private": false,
5
5
  "repository": "github:humeai/hume-typescript-sdk",
6
6
  "type": "commonjs",