@kstonekuan/audio-capture 0.0.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.
Files changed (4) hide show
  1. package/README.md +146 -0
  2. package/index.d.ts +23 -0
  3. package/index.js +579 -0
  4. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @kstonekuan/audio-capture
2
+
3
+ Native Node.js addon for cross-platform microphone capture. Built with Rust ([cpal](https://github.com/RustAudio/cpal)) and exposed to JavaScript via [NAPI-RS](https://napi.rs/).
4
+
5
+ Audio is captured from the system microphone, normalized to **16kHz 16-bit PCM mono**, and delivered to a JavaScript callback in real time. The architecture uses a lock-free ring buffer to decouple the OS audio thread from Node.js, so the real-time callback never blocks on JS execution.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @kstonekuan/audio-capture
11
+ ```
12
+
13
+ Pre-built native binaries are included for:
14
+
15
+ | Platform | Architecture |
16
+ | -------------- | ------------ |
17
+ | macOS | arm64, x64 |
18
+ | Linux (glibc) | x64 |
19
+ | Windows (MSVC) | x64 |
20
+
21
+ No Rust toolchain is needed for end users.
22
+
23
+ ## Usage
24
+
25
+ ### Capture audio
26
+
27
+ ```js
28
+ import { createRequire } from "node:module";
29
+ const require = createRequire(import.meta.url);
30
+ const { Recorder } = require("@kstonekuan/audio-capture");
31
+
32
+ const recorder = new Recorder();
33
+
34
+ recorder.start((error, samples) => {
35
+ if (error) {
36
+ console.error("Capture error:", error.message);
37
+ return;
38
+ }
39
+ // `samples` is an Int16Array of 16kHz 16-bit PCM mono audio
40
+ console.log(`Received ${samples.length} samples at ${recorder.sampleRate}Hz`);
41
+ });
42
+
43
+ // Stop capturing when done
44
+ setTimeout(() => {
45
+ recorder.stop();
46
+ }, 5000);
47
+ ```
48
+
49
+ ### List audio devices
50
+
51
+ ```js
52
+ const devices = Recorder.getAudioDevices();
53
+ for (const device of devices) {
54
+ console.log(`${device.index}: ${device.name}`);
55
+ }
56
+ ```
57
+
58
+ ### Select a specific device
59
+
60
+ ```js
61
+ const recorder = new Recorder(1); // use device at index 1
62
+ ```
63
+
64
+ ## API
65
+
66
+ ### `new Recorder(deviceIndex?: number)`
67
+
68
+ Creates a new recorder instance. Pass a device index to capture from a specific input device, or omit it to use the system default.
69
+
70
+ ### `recorder.start(callback: (error: Error | null, samples: Int16Array) => void): void`
71
+
72
+ Start capturing audio. The callback is invoked on a dedicated drain thread each time new samples are available from the microphone. Samples are 16kHz 16-bit signed PCM, mono.
73
+
74
+ ### `recorder.stop(): void`
75
+
76
+ Stop capturing and release the audio stream.
77
+
78
+ ### `recorder.sampleRate: number`
79
+
80
+ The output sample rate in Hz (always `16000`).
81
+
82
+ ### `Recorder.getAudioDevices(): AudioDevice[]`
83
+
84
+ Returns a list of available audio input devices.
85
+
86
+ ### `AudioDevice`
87
+
88
+ ```ts
89
+ interface AudioDevice {
90
+ index: number;
91
+ name: string;
92
+ }
93
+ ```
94
+
95
+ ## Architecture
96
+
97
+ ```
98
+ Microphone
99
+ -> cpal (OS audio thread)
100
+ -> normalize to 16kHz mono
101
+ -> lock-free SPSC ring buffer
102
+ -> drain thread
103
+ -> NAPI ThreadsafeFunction
104
+ -> JavaScript callback (Int16Array)
105
+ ```
106
+
107
+ Three threads are involved:
108
+
109
+ 1. **Audio thread** -- owns the cpal stream lifecycle (start/stop/shutdown)
110
+ 2. **cpal callback** (OS real-time thread) -- normalizes samples (channel downmix + resample) and pushes `i16` into the ring buffer. Never blocks.
111
+ 3. **Drain thread** -- waits on a condvar, reads all available samples from the ring buffer, and invokes the JavaScript callback via a NAPI ThreadsafeFunction (non-blocking)
112
+
113
+ The ring buffer decouples the real-time cpal callback from the drain thread, so the callback never contends with JS-bound callback invocation.
114
+
115
+ ## Usage example
116
+
117
+ [gemini-cli-voice-extension](https://github.com/kstonekuan/gemini-cli-voice-extension) uses `@kstonekuan/audio-capture` to stream microphone audio to the Gemini Live API for real-time speech transcription:
118
+
119
+ ```ts
120
+ const recorder = new Recorder(deviceIndex ?? null);
121
+
122
+ recorder.start((error, samples) => {
123
+ if (error) return;
124
+
125
+ // Base64-encode the PCM samples and stream to Gemini Live API
126
+ const base64Audio = int16ArrayToBase64(samples);
127
+ session.sendRealtimeInput({
128
+ audio: { data: base64Audio, mimeType: "audio/pcm;rate=16000" },
129
+ });
130
+ });
131
+ ```
132
+
133
+ The 16kHz PCM output matches what the Gemini Live API expects, so no additional conversion is needed.
134
+
135
+ ## Building from source
136
+
137
+ Requires a [Rust toolchain](https://rustup.rs/) and system audio libraries (ALSA dev headers on Linux).
138
+
139
+ ```bash
140
+ pnpm install
141
+ pnpm build # runs: napi build --platform --release
142
+ ```
143
+
144
+ ## License
145
+
146
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /* auto-generated by NAPI-RS */
2
+ /* eslint-disable */
3
+ export declare class Recorder {
4
+ constructor(deviceIndex?: number | undefined | null)
5
+ /**
6
+ * Start capturing audio. The callback is invoked on a dedicated drain
7
+ * thread each time new samples are available from the microphone.
8
+ */
9
+ start(callback: (err: Error | null, samples: Int16Array) => void): void
10
+ stop(): void
11
+ get sampleRate(): number
12
+ static getAudioDevices(): Array<AudioDevice>
13
+ }
14
+
15
+ /**
16
+ * Exposed to JavaScript as a plain object.
17
+ * `index` is `i32` because JavaScript has no unsigned integer type.
18
+ * The value is always non-negative.
19
+ */
20
+ export interface AudioDevice {
21
+ index: number
22
+ name: string
23
+ }
package/index.js ADDED
@@ -0,0 +1,579 @@
1
+ // prettier-ignore
2
+ /* eslint-disable */
3
+ // @ts-nocheck
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ const { readFileSync } = require('node:fs')
7
+ let nativeBinding = null
8
+ const loadErrors = []
9
+
10
+ const isMusl = () => {
11
+ let musl = false
12
+ if (process.platform === 'linux') {
13
+ musl = isMuslFromFilesystem()
14
+ if (musl === null) {
15
+ musl = isMuslFromReport()
16
+ }
17
+ if (musl === null) {
18
+ musl = isMuslFromChildProcess()
19
+ }
20
+ }
21
+ return musl
22
+ }
23
+
24
+ const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
25
+
26
+ const isMuslFromFilesystem = () => {
27
+ try {
28
+ return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
29
+ } catch {
30
+ return null
31
+ }
32
+ }
33
+
34
+ const isMuslFromReport = () => {
35
+ let report = null
36
+ if (typeof process.report?.getReport === 'function') {
37
+ process.report.excludeNetwork = true
38
+ report = process.report.getReport()
39
+ }
40
+ if (!report) {
41
+ return null
42
+ }
43
+ if (report.header && report.header.glibcVersionRuntime) {
44
+ return false
45
+ }
46
+ if (Array.isArray(report.sharedObjects)) {
47
+ if (report.sharedObjects.some(isFileMusl)) {
48
+ return true
49
+ }
50
+ }
51
+ return false
52
+ }
53
+
54
+ const isMuslFromChildProcess = () => {
55
+ try {
56
+ return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
57
+ } catch (e) {
58
+ // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
59
+ return false
60
+ }
61
+ }
62
+
63
+ function requireNative() {
64
+ if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
65
+ try {
66
+ return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
67
+ } catch (err) {
68
+ loadErrors.push(err)
69
+ }
70
+ } else if (process.platform === 'android') {
71
+ if (process.arch === 'arm64') {
72
+ try {
73
+ return require('./audio-capture.android-arm64.node')
74
+ } catch (e) {
75
+ loadErrors.push(e)
76
+ }
77
+ try {
78
+ const binding = require('@kstonekuan/audio-capture-android-arm64')
79
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-android-arm64/package.json').version
80
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
81
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
82
+ }
83
+ return binding
84
+ } catch (e) {
85
+ loadErrors.push(e)
86
+ }
87
+ } else if (process.arch === 'arm') {
88
+ try {
89
+ return require('./audio-capture.android-arm-eabi.node')
90
+ } catch (e) {
91
+ loadErrors.push(e)
92
+ }
93
+ try {
94
+ const binding = require('@kstonekuan/audio-capture-android-arm-eabi')
95
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-android-arm-eabi/package.json').version
96
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
97
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
98
+ }
99
+ return binding
100
+ } catch (e) {
101
+ loadErrors.push(e)
102
+ }
103
+ } else {
104
+ loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
105
+ }
106
+ } else if (process.platform === 'win32') {
107
+ if (process.arch === 'x64') {
108
+ if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') {
109
+ try {
110
+ return require('./audio-capture.win32-x64-gnu.node')
111
+ } catch (e) {
112
+ loadErrors.push(e)
113
+ }
114
+ try {
115
+ const binding = require('@kstonekuan/audio-capture-win32-x64-gnu')
116
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-win32-x64-gnu/package.json').version
117
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
118
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
119
+ }
120
+ return binding
121
+ } catch (e) {
122
+ loadErrors.push(e)
123
+ }
124
+ } else {
125
+ try {
126
+ return require('./audio-capture.win32-x64-msvc.node')
127
+ } catch (e) {
128
+ loadErrors.push(e)
129
+ }
130
+ try {
131
+ const binding = require('@kstonekuan/audio-capture-win32-x64-msvc')
132
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-win32-x64-msvc/package.json').version
133
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
134
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
135
+ }
136
+ return binding
137
+ } catch (e) {
138
+ loadErrors.push(e)
139
+ }
140
+ }
141
+ } else if (process.arch === 'ia32') {
142
+ try {
143
+ return require('./audio-capture.win32-ia32-msvc.node')
144
+ } catch (e) {
145
+ loadErrors.push(e)
146
+ }
147
+ try {
148
+ const binding = require('@kstonekuan/audio-capture-win32-ia32-msvc')
149
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-win32-ia32-msvc/package.json').version
150
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
151
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
152
+ }
153
+ return binding
154
+ } catch (e) {
155
+ loadErrors.push(e)
156
+ }
157
+ } else if (process.arch === 'arm64') {
158
+ try {
159
+ return require('./audio-capture.win32-arm64-msvc.node')
160
+ } catch (e) {
161
+ loadErrors.push(e)
162
+ }
163
+ try {
164
+ const binding = require('@kstonekuan/audio-capture-win32-arm64-msvc')
165
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-win32-arm64-msvc/package.json').version
166
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
167
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
168
+ }
169
+ return binding
170
+ } catch (e) {
171
+ loadErrors.push(e)
172
+ }
173
+ } else {
174
+ loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
175
+ }
176
+ } else if (process.platform === 'darwin') {
177
+ try {
178
+ return require('./audio-capture.darwin-universal.node')
179
+ } catch (e) {
180
+ loadErrors.push(e)
181
+ }
182
+ try {
183
+ const binding = require('@kstonekuan/audio-capture-darwin-universal')
184
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-darwin-universal/package.json').version
185
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
186
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
187
+ }
188
+ return binding
189
+ } catch (e) {
190
+ loadErrors.push(e)
191
+ }
192
+ if (process.arch === 'x64') {
193
+ try {
194
+ return require('./audio-capture.darwin-x64.node')
195
+ } catch (e) {
196
+ loadErrors.push(e)
197
+ }
198
+ try {
199
+ const binding = require('@kstonekuan/audio-capture-darwin-x64')
200
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-darwin-x64/package.json').version
201
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
202
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
203
+ }
204
+ return binding
205
+ } catch (e) {
206
+ loadErrors.push(e)
207
+ }
208
+ } else if (process.arch === 'arm64') {
209
+ try {
210
+ return require('./audio-capture.darwin-arm64.node')
211
+ } catch (e) {
212
+ loadErrors.push(e)
213
+ }
214
+ try {
215
+ const binding = require('@kstonekuan/audio-capture-darwin-arm64')
216
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-darwin-arm64/package.json').version
217
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
218
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
219
+ }
220
+ return binding
221
+ } catch (e) {
222
+ loadErrors.push(e)
223
+ }
224
+ } else {
225
+ loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
226
+ }
227
+ } else if (process.platform === 'freebsd') {
228
+ if (process.arch === 'x64') {
229
+ try {
230
+ return require('./audio-capture.freebsd-x64.node')
231
+ } catch (e) {
232
+ loadErrors.push(e)
233
+ }
234
+ try {
235
+ const binding = require('@kstonekuan/audio-capture-freebsd-x64')
236
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-freebsd-x64/package.json').version
237
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
238
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
239
+ }
240
+ return binding
241
+ } catch (e) {
242
+ loadErrors.push(e)
243
+ }
244
+ } else if (process.arch === 'arm64') {
245
+ try {
246
+ return require('./audio-capture.freebsd-arm64.node')
247
+ } catch (e) {
248
+ loadErrors.push(e)
249
+ }
250
+ try {
251
+ const binding = require('@kstonekuan/audio-capture-freebsd-arm64')
252
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-freebsd-arm64/package.json').version
253
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
254
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
255
+ }
256
+ return binding
257
+ } catch (e) {
258
+ loadErrors.push(e)
259
+ }
260
+ } else {
261
+ loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
262
+ }
263
+ } else if (process.platform === 'linux') {
264
+ if (process.arch === 'x64') {
265
+ if (isMusl()) {
266
+ try {
267
+ return require('./audio-capture.linux-x64-musl.node')
268
+ } catch (e) {
269
+ loadErrors.push(e)
270
+ }
271
+ try {
272
+ const binding = require('@kstonekuan/audio-capture-linux-x64-musl')
273
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-x64-musl/package.json').version
274
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
275
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
276
+ }
277
+ return binding
278
+ } catch (e) {
279
+ loadErrors.push(e)
280
+ }
281
+ } else {
282
+ try {
283
+ return require('./audio-capture.linux-x64-gnu.node')
284
+ } catch (e) {
285
+ loadErrors.push(e)
286
+ }
287
+ try {
288
+ const binding = require('@kstonekuan/audio-capture-linux-x64-gnu')
289
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-x64-gnu/package.json').version
290
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
291
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
292
+ }
293
+ return binding
294
+ } catch (e) {
295
+ loadErrors.push(e)
296
+ }
297
+ }
298
+ } else if (process.arch === 'arm64') {
299
+ if (isMusl()) {
300
+ try {
301
+ return require('./audio-capture.linux-arm64-musl.node')
302
+ } catch (e) {
303
+ loadErrors.push(e)
304
+ }
305
+ try {
306
+ const binding = require('@kstonekuan/audio-capture-linux-arm64-musl')
307
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-arm64-musl/package.json').version
308
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
309
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
310
+ }
311
+ return binding
312
+ } catch (e) {
313
+ loadErrors.push(e)
314
+ }
315
+ } else {
316
+ try {
317
+ return require('./audio-capture.linux-arm64-gnu.node')
318
+ } catch (e) {
319
+ loadErrors.push(e)
320
+ }
321
+ try {
322
+ const binding = require('@kstonekuan/audio-capture-linux-arm64-gnu')
323
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-arm64-gnu/package.json').version
324
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
325
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
326
+ }
327
+ return binding
328
+ } catch (e) {
329
+ loadErrors.push(e)
330
+ }
331
+ }
332
+ } else if (process.arch === 'arm') {
333
+ if (isMusl()) {
334
+ try {
335
+ return require('./audio-capture.linux-arm-musleabihf.node')
336
+ } catch (e) {
337
+ loadErrors.push(e)
338
+ }
339
+ try {
340
+ const binding = require('@kstonekuan/audio-capture-linux-arm-musleabihf')
341
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-arm-musleabihf/package.json').version
342
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
343
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
344
+ }
345
+ return binding
346
+ } catch (e) {
347
+ loadErrors.push(e)
348
+ }
349
+ } else {
350
+ try {
351
+ return require('./audio-capture.linux-arm-gnueabihf.node')
352
+ } catch (e) {
353
+ loadErrors.push(e)
354
+ }
355
+ try {
356
+ const binding = require('@kstonekuan/audio-capture-linux-arm-gnueabihf')
357
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-arm-gnueabihf/package.json').version
358
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
359
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
360
+ }
361
+ return binding
362
+ } catch (e) {
363
+ loadErrors.push(e)
364
+ }
365
+ }
366
+ } else if (process.arch === 'loong64') {
367
+ if (isMusl()) {
368
+ try {
369
+ return require('./audio-capture.linux-loong64-musl.node')
370
+ } catch (e) {
371
+ loadErrors.push(e)
372
+ }
373
+ try {
374
+ const binding = require('@kstonekuan/audio-capture-linux-loong64-musl')
375
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-loong64-musl/package.json').version
376
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
377
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
378
+ }
379
+ return binding
380
+ } catch (e) {
381
+ loadErrors.push(e)
382
+ }
383
+ } else {
384
+ try {
385
+ return require('./audio-capture.linux-loong64-gnu.node')
386
+ } catch (e) {
387
+ loadErrors.push(e)
388
+ }
389
+ try {
390
+ const binding = require('@kstonekuan/audio-capture-linux-loong64-gnu')
391
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-loong64-gnu/package.json').version
392
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
393
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
394
+ }
395
+ return binding
396
+ } catch (e) {
397
+ loadErrors.push(e)
398
+ }
399
+ }
400
+ } else if (process.arch === 'riscv64') {
401
+ if (isMusl()) {
402
+ try {
403
+ return require('./audio-capture.linux-riscv64-musl.node')
404
+ } catch (e) {
405
+ loadErrors.push(e)
406
+ }
407
+ try {
408
+ const binding = require('@kstonekuan/audio-capture-linux-riscv64-musl')
409
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-riscv64-musl/package.json').version
410
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
411
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
412
+ }
413
+ return binding
414
+ } catch (e) {
415
+ loadErrors.push(e)
416
+ }
417
+ } else {
418
+ try {
419
+ return require('./audio-capture.linux-riscv64-gnu.node')
420
+ } catch (e) {
421
+ loadErrors.push(e)
422
+ }
423
+ try {
424
+ const binding = require('@kstonekuan/audio-capture-linux-riscv64-gnu')
425
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-riscv64-gnu/package.json').version
426
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
427
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
428
+ }
429
+ return binding
430
+ } catch (e) {
431
+ loadErrors.push(e)
432
+ }
433
+ }
434
+ } else if (process.arch === 'ppc64') {
435
+ try {
436
+ return require('./audio-capture.linux-ppc64-gnu.node')
437
+ } catch (e) {
438
+ loadErrors.push(e)
439
+ }
440
+ try {
441
+ const binding = require('@kstonekuan/audio-capture-linux-ppc64-gnu')
442
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-ppc64-gnu/package.json').version
443
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
444
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
445
+ }
446
+ return binding
447
+ } catch (e) {
448
+ loadErrors.push(e)
449
+ }
450
+ } else if (process.arch === 's390x') {
451
+ try {
452
+ return require('./audio-capture.linux-s390x-gnu.node')
453
+ } catch (e) {
454
+ loadErrors.push(e)
455
+ }
456
+ try {
457
+ const binding = require('@kstonekuan/audio-capture-linux-s390x-gnu')
458
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-linux-s390x-gnu/package.json').version
459
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
460
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
461
+ }
462
+ return binding
463
+ } catch (e) {
464
+ loadErrors.push(e)
465
+ }
466
+ } else {
467
+ loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
468
+ }
469
+ } else if (process.platform === 'openharmony') {
470
+ if (process.arch === 'arm64') {
471
+ try {
472
+ return require('./audio-capture.openharmony-arm64.node')
473
+ } catch (e) {
474
+ loadErrors.push(e)
475
+ }
476
+ try {
477
+ const binding = require('@kstonekuan/audio-capture-openharmony-arm64')
478
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-openharmony-arm64/package.json').version
479
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
480
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
481
+ }
482
+ return binding
483
+ } catch (e) {
484
+ loadErrors.push(e)
485
+ }
486
+ } else if (process.arch === 'x64') {
487
+ try {
488
+ return require('./audio-capture.openharmony-x64.node')
489
+ } catch (e) {
490
+ loadErrors.push(e)
491
+ }
492
+ try {
493
+ const binding = require('@kstonekuan/audio-capture-openharmony-x64')
494
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-openharmony-x64/package.json').version
495
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
496
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
497
+ }
498
+ return binding
499
+ } catch (e) {
500
+ loadErrors.push(e)
501
+ }
502
+ } else if (process.arch === 'arm') {
503
+ try {
504
+ return require('./audio-capture.openharmony-arm.node')
505
+ } catch (e) {
506
+ loadErrors.push(e)
507
+ }
508
+ try {
509
+ const binding = require('@kstonekuan/audio-capture-openharmony-arm')
510
+ const bindingPackageVersion = require('@kstonekuan/audio-capture-openharmony-arm/package.json').version
511
+ if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
512
+ throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
513
+ }
514
+ return binding
515
+ } catch (e) {
516
+ loadErrors.push(e)
517
+ }
518
+ } else {
519
+ loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))
520
+ }
521
+ } else {
522
+ loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
523
+ }
524
+ }
525
+
526
+ nativeBinding = requireNative()
527
+
528
+ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
529
+ let wasiBinding = null
530
+ let wasiBindingError = null
531
+ try {
532
+ wasiBinding = require('./audio-capture.wasi.cjs')
533
+ nativeBinding = wasiBinding
534
+ } catch (err) {
535
+ if (process.env.NAPI_RS_FORCE_WASI) {
536
+ wasiBindingError = err
537
+ }
538
+ }
539
+ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
540
+ try {
541
+ wasiBinding = require('@kstonekuan/audio-capture-wasm32-wasi')
542
+ nativeBinding = wasiBinding
543
+ } catch (err) {
544
+ if (process.env.NAPI_RS_FORCE_WASI) {
545
+ if (!wasiBindingError) {
546
+ wasiBindingError = err
547
+ } else {
548
+ wasiBindingError.cause = err
549
+ }
550
+ loadErrors.push(err)
551
+ }
552
+ }
553
+ }
554
+ if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {
555
+ const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')
556
+ error.cause = wasiBindingError
557
+ throw error
558
+ }
559
+ }
560
+
561
+ if (!nativeBinding) {
562
+ if (loadErrors.length > 0) {
563
+ throw new Error(
564
+ `Cannot find native binding. ` +
565
+ `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +
566
+ 'Please try `npm i` again after removing both package-lock.json and node_modules directory.',
567
+ {
568
+ cause: loadErrors.reduce((err, cur) => {
569
+ cur.cause = err
570
+ return cur
571
+ }),
572
+ },
573
+ )
574
+ }
575
+ throw new Error(`Failed to load native binding`)
576
+ }
577
+
578
+ module.exports = nativeBinding
579
+ module.exports.Recorder = nativeBinding.Recorder
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@kstonekuan/audio-capture",
3
+ "version": "0.0.1",
4
+ "type": "commonjs",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts"
10
+ ],
11
+ "napi": {
12
+ "binaryName": "audio-capture",
13
+ "targets": [
14
+ "aarch64-apple-darwin",
15
+ "x86_64-apple-darwin",
16
+ "x86_64-unknown-linux-gnu",
17
+ "x86_64-pc-windows-msvc"
18
+ ],
19
+ "packageName": "@kstonekuan/audio-capture"
20
+ },
21
+ "optionalDependencies": {
22
+ "@kstonekuan/audio-capture-darwin-arm64": "0.0.1",
23
+ "@kstonekuan/audio-capture-darwin-x64": "0.0.1",
24
+ "@kstonekuan/audio-capture-linux-x64-gnu": "0.0.1",
25
+ "@kstonekuan/audio-capture-win32-x64-msvc": "0.0.1"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/kstonekuan/gemini-cli-voice-extension"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "devDependencies": {
35
+ "@napi-rs/cli": "^3.5.1"
36
+ },
37
+ "scripts": {
38
+ "build": "napi build --platform --release",
39
+ "artifacts": "napi artifacts",
40
+ "prepublishOnly": "napi prepublish -t npm --no-gh-release",
41
+ "version": "napi version"
42
+ }
43
+ }