native-recorder-nodejs 1.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.
- package/CMakeLists.txt +110 -0
- package/README.md +380 -0
- package/dist/bindings.d.ts +2 -0
- package/dist/bindings.js +31 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder.d.ts +56 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +343 -0
- package/dist/recorder.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +49 -0
- package/dist/types.js.map +1 -0
- package/native/AudioController.cpp +233 -0
- package/native/AudioController.h +26 -0
- package/native/AudioEngine.h +75 -0
- package/native/Factory.cpp +18 -0
- package/native/mac/AVFEngine.h +24 -0
- package/native/mac/AVFEngine.mm +274 -0
- package/native/mac/SCKAudioCapture.h +13 -0
- package/native/mac/SCKAudioCapture.mm +213 -0
- package/native/main.cpp +9 -0
- package/native/win/WASAPIEngine.cpp +449 -0
- package/native/win/WASAPIEngine.h +44 -0
- package/package.json +74 -0
- package/prebuilds/native-audio-recorder-v0.1.0-napi-v8-darwin-arm64.tar.gz +0 -0
- package/prebuilds/native-recorder-nodejs-v1.0.0-napi-v8-darwin-arm64.tar.gz +0 -0
- package/src/bindings.ts +39 -0
- package/src/index.ts +206 -0
package/CMakeLists.txt
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.15)
|
|
2
|
+
project(NativeAudioSDK)
|
|
3
|
+
|
|
4
|
+
# --- Configuration ---
|
|
5
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
6
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
7
|
+
|
|
8
|
+
# --- Dependencies ---
|
|
9
|
+
include_directories(${CMAKE_JS_INC})
|
|
10
|
+
|
|
11
|
+
# node-addon-api
|
|
12
|
+
# execute_process(COMMAND node -p "require('node-addon-api').include"
|
|
13
|
+
# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
14
|
+
# OUTPUT_VARIABLE NODE_ADDON_API_DIR
|
|
15
|
+
# OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
16
|
+
|
|
17
|
+
set(NODE_ADDON_API_DIR "${CMAKE_SOURCE_DIR}/node_modules/node-addon-api")
|
|
18
|
+
include_directories(${NODE_ADDON_API_DIR})
|
|
19
|
+
|
|
20
|
+
# --- Source Files ---
|
|
21
|
+
set(ENGINE_SOURCES
|
|
22
|
+
native/Factory.cpp
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Platform specific sources
|
|
26
|
+
if(WIN32)
|
|
27
|
+
list(APPEND ENGINE_SOURCES
|
|
28
|
+
native/win/WASAPIEngine.cpp
|
|
29
|
+
)
|
|
30
|
+
add_definitions(-DNAPI_CPP_EXCEPTIONS -DWIN32_LEAN_AND_MEAN)
|
|
31
|
+
elseif(APPLE)
|
|
32
|
+
set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} -fobjc-arc")
|
|
33
|
+
list(APPEND ENGINE_SOURCES
|
|
34
|
+
native/mac/AVFEngine.mm
|
|
35
|
+
native/mac/SCKAudioCapture.mm
|
|
36
|
+
)
|
|
37
|
+
add_definitions(-DNAPI_CPP_EXCEPTIONS)
|
|
38
|
+
endif()
|
|
39
|
+
|
|
40
|
+
set(SOURCE_FILES
|
|
41
|
+
native/main.cpp
|
|
42
|
+
native/AudioController.cpp
|
|
43
|
+
${ENGINE_SOURCES}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# --- Main Target (Node Addon) ---
|
|
47
|
+
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
|
|
48
|
+
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
|
|
49
|
+
|
|
50
|
+
if(APPLE)
|
|
51
|
+
target_compile_options(${PROJECT_NAME} PRIVATE -fobjc-arc)
|
|
52
|
+
endif()
|
|
53
|
+
|
|
54
|
+
# Link libraries
|
|
55
|
+
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_LIB})
|
|
56
|
+
|
|
57
|
+
if(WIN32)
|
|
58
|
+
target_link_libraries(${PROJECT_NAME} PRIVATE Ole32) # Example for Windows
|
|
59
|
+
elseif(APPLE)
|
|
60
|
+
find_library(AVFOUNDATION_FRAMEWORK AVFoundation)
|
|
61
|
+
find_library(COREAUDIO_FRAMEWORK CoreAudio)
|
|
62
|
+
find_library(COREMEDIA_FRAMEWORK CoreMedia)
|
|
63
|
+
find_library(SCREENCAPTUREKIT_FRAMEWORK ScreenCaptureKit)
|
|
64
|
+
find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox)
|
|
65
|
+
find_library(FOUNDATION_FRAMEWORK Foundation)
|
|
66
|
+
target_link_libraries(${PROJECT_NAME} PRIVATE ${AVFOUNDATION_FRAMEWORK} ${COREAUDIO_FRAMEWORK} ${COREMEDIA_FRAMEWORK} ${SCREENCAPTUREKIT_FRAMEWORK} ${AUDIOTOOLBOX_FRAMEWORK} ${FOUNDATION_FRAMEWORK})
|
|
67
|
+
endif()
|
|
68
|
+
|
|
69
|
+
# --- Testing (Catch2) ---
|
|
70
|
+
option(BUILD_TESTS "Build unit tests" OFF)
|
|
71
|
+
|
|
72
|
+
if(BUILD_TESTS)
|
|
73
|
+
include(FetchContent)
|
|
74
|
+
FetchContent_Declare(
|
|
75
|
+
Catch2
|
|
76
|
+
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
|
77
|
+
GIT_TAG v3.4.0
|
|
78
|
+
)
|
|
79
|
+
FetchContent_MakeAvailable(Catch2)
|
|
80
|
+
|
|
81
|
+
# Define test sources (exclude main.cpp which has N-API exports)
|
|
82
|
+
set(TEST_SOURCES
|
|
83
|
+
test/native/test_factory.cpp
|
|
84
|
+
${ENGINE_SOURCES}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if(WIN32)
|
|
88
|
+
list(APPEND TEST_SOURCES test/native/test_wasapi.cpp)
|
|
89
|
+
elseif(APPLE)
|
|
90
|
+
list(APPEND TEST_SOURCES test/native/test_avf.cpp)
|
|
91
|
+
endif()
|
|
92
|
+
|
|
93
|
+
add_executable(NativeTests ${TEST_SOURCES})
|
|
94
|
+
if(APPLE)
|
|
95
|
+
target_compile_options(NativeTests PRIVATE -fobjc-arc)
|
|
96
|
+
endif()
|
|
97
|
+
target_link_libraries(NativeTests PRIVATE Catch2::Catch2WithMain)
|
|
98
|
+
|
|
99
|
+
# Link platform libs to tests as well
|
|
100
|
+
if(WIN32)
|
|
101
|
+
target_link_libraries(NativeTests PRIVATE Ole32)
|
|
102
|
+
elseif(APPLE)
|
|
103
|
+
target_link_libraries(NativeTests PRIVATE ${AVFOUNDATION_FRAMEWORK} ${COREAUDIO_FRAMEWORK} ${COREMEDIA_FRAMEWORK} ${SCREENCAPTUREKIT_FRAMEWORK} ${AUDIOTOOLBOX_FRAMEWORK} ${FOUNDATION_FRAMEWORK})
|
|
104
|
+
endif()
|
|
105
|
+
|
|
106
|
+
enable_testing()
|
|
107
|
+
include(CTest)
|
|
108
|
+
include(Catch)
|
|
109
|
+
catch_discover_tests(NativeTests)
|
|
110
|
+
endif()
|
package/README.md
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Native Recorder for Node.js
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Yidadaa/Native-Recorder-NodeJS/actions/workflows/build.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/native-recorder-nodejs)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
High-performance, low-latency native audio recording SDK for Node.js. Supports both **microphone input** and **system audio capture (loopback)** on Windows and macOS.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Microphone Recording** - Capture audio from any input device
|
|
12
|
+
- **System Audio Capture** - Record what's playing on your computer (loopback)
|
|
13
|
+
- **Cross-Platform** - Windows (WASAPI) and macOS (AVFoundation + ScreenCaptureKit)
|
|
14
|
+
- **High Performance** - Native C++ implementation with minimal latency
|
|
15
|
+
- **Prebuilt Binaries** - No compilation required for most platforms
|
|
16
|
+
- **Type Safe** - Full TypeScript support
|
|
17
|
+
|
|
18
|
+
## Platform Support
|
|
19
|
+
|
|
20
|
+
| Platform | Architecture | Input (Mic) | Output (System Audio) |
|
|
21
|
+
| ------------- | ------------ | ----------- | ----------------------- |
|
|
22
|
+
| Windows 10/11 | x64 | Supported | Supported (per-device) |
|
|
23
|
+
| Windows 10/11 | ia32 | Supported | Supported (per-device) |
|
|
24
|
+
| macOS 12.3+ | arm64 | Supported | Supported (system-wide) |
|
|
25
|
+
| macOS 12.3+ | x64 | Supported | Supported (system-wide) |
|
|
26
|
+
|
|
27
|
+
### Platform Differences
|
|
28
|
+
|
|
29
|
+
| Feature | Windows | macOS |
|
|
30
|
+
| -------------- | --------------------- | ------------------------------ |
|
|
31
|
+
| Input Devices | Multiple (WASAPI) | Multiple (AVFoundation) |
|
|
32
|
+
| Output Devices | Multiple (per-device) | Single "System Audio" device |
|
|
33
|
+
| Permissions | None required | Microphone + Screen Recording |
|
|
34
|
+
| Min OS Version | Windows 10+ | macOS 12.3+ (for system audio) |
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install native-recorder-nodejs
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Prebuilt binaries are available for most platforms. If a prebuild is not available, the package will compile from source (requires CMake and a C++ compiler).
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { AudioRecorder, SYSTEM_AUDIO_DEVICE_ID } from 'native-recorder-nodejs';
|
|
48
|
+
import * as fs from 'fs';
|
|
49
|
+
|
|
50
|
+
const recorder = new AudioRecorder();
|
|
51
|
+
|
|
52
|
+
// List available devices
|
|
53
|
+
const inputs = AudioRecorder.getDevices('input');
|
|
54
|
+
const outputs = AudioRecorder.getDevices('output');
|
|
55
|
+
|
|
56
|
+
console.log('Microphones:', inputs);
|
|
57
|
+
console.log('Output devices:', outputs);
|
|
58
|
+
|
|
59
|
+
// Record from default microphone
|
|
60
|
+
const mic = inputs.find(d => d.isDefault);
|
|
61
|
+
if (mic) {
|
|
62
|
+
const output = fs.createWriteStream('recording.raw');
|
|
63
|
+
|
|
64
|
+
recorder.on('data', (buffer) => {
|
|
65
|
+
output.write(buffer);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
recorder.on('error', (error) => {
|
|
69
|
+
console.error('Recording error:', error);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await recorder.start({
|
|
73
|
+
deviceType: 'input',
|
|
74
|
+
deviceId: mic.id
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Record for 5 seconds
|
|
78
|
+
setTimeout(async () => {
|
|
79
|
+
await recorder.stop();
|
|
80
|
+
output.end();
|
|
81
|
+
console.log('Recording saved!');
|
|
82
|
+
}, 5000);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Record System Audio
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Get system audio device
|
|
90
|
+
const outputs = AudioRecorder.getDevices('output');
|
|
91
|
+
const systemAudio = outputs.find(d => d.id === SYSTEM_AUDIO_DEVICE_ID)
|
|
92
|
+
|| outputs.find(d => d.isDefault);
|
|
93
|
+
|
|
94
|
+
if (systemAudio) {
|
|
95
|
+
await recorder.start({
|
|
96
|
+
deviceType: 'output',
|
|
97
|
+
deviceId: systemAudio.id
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## API Reference
|
|
103
|
+
|
|
104
|
+
### `AudioRecorder`
|
|
105
|
+
|
|
106
|
+
The main class for controlling audio recording.
|
|
107
|
+
|
|
108
|
+
#### Constructor
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const recorder = new AudioRecorder();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Instance Methods
|
|
115
|
+
|
|
116
|
+
##### `start(config: RecordingConfig): Promise<void>`
|
|
117
|
+
|
|
118
|
+
Starts recording from the specified device.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
interface RecordingConfig {
|
|
122
|
+
deviceType: 'input' | 'output'; // Required
|
|
123
|
+
deviceId: string; // Required
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await recorder.start({
|
|
127
|
+
deviceType: 'input',
|
|
128
|
+
deviceId: 'device-uuid'
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
##### `stop(): Promise<void>`
|
|
133
|
+
|
|
134
|
+
Stops the recording session.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
await recorder.stop();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Static Methods
|
|
141
|
+
|
|
142
|
+
##### `getDevices(type?: DeviceType): AudioDevice[]`
|
|
143
|
+
|
|
144
|
+
Lists available audio devices.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface AudioDevice {
|
|
148
|
+
id: string; // Unique identifier
|
|
149
|
+
name: string; // Human-readable name
|
|
150
|
+
type: 'input' | 'output';
|
|
151
|
+
isDefault: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const allDevices = AudioRecorder.getDevices();
|
|
155
|
+
const microphones = AudioRecorder.getDevices('input');
|
|
156
|
+
const outputs = AudioRecorder.getDevices('output');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
##### `getDeviceFormat(deviceId: string): AudioFormat`
|
|
160
|
+
|
|
161
|
+
Gets the audio format of a device.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
interface AudioFormat {
|
|
165
|
+
sampleRate: number; // e.g., 48000
|
|
166
|
+
channels: number; // 1 (mono) or 2 (stereo)
|
|
167
|
+
bitDepth: number; // Output bit depth (16)
|
|
168
|
+
rawBitDepth: number; // Native device bit depth
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const format = AudioRecorder.getDeviceFormat(device.id);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
##### `checkPermission(): PermissionStatus`
|
|
175
|
+
|
|
176
|
+
Checks current permission status.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
interface PermissionStatus {
|
|
180
|
+
mic: boolean; // Microphone permission
|
|
181
|
+
system: boolean; // System audio permission
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const status = AudioRecorder.checkPermission();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
##### `requestPermission(type: 'mic' | 'system'): boolean`
|
|
188
|
+
|
|
189
|
+
Requests permission for recording.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const granted = AudioRecorder.requestPermission('mic');
|
|
193
|
+
const systemGranted = AudioRecorder.requestPermission('system');
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Events
|
|
197
|
+
|
|
198
|
+
##### `'data'`
|
|
199
|
+
|
|
200
|
+
Emitted when audio data is available.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
recorder.on('data', (buffer: Buffer) => {
|
|
204
|
+
// Raw PCM 16-bit LE audio data
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
##### `'error'`
|
|
209
|
+
|
|
210
|
+
Emitted when an error occurs.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
recorder.on('error', (error: Error) => {
|
|
214
|
+
console.error(error.message);
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Constants
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// Special device ID for system-wide audio capture on macOS
|
|
222
|
+
export const SYSTEM_AUDIO_DEVICE_ID = 'system';
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Audio Format
|
|
226
|
+
|
|
227
|
+
The output is always:
|
|
228
|
+
- **Format**: Raw PCM
|
|
229
|
+
- **Bit Depth**: 16-bit signed integer
|
|
230
|
+
- **Endianness**: Little Endian
|
|
231
|
+
- **Sample Rate**: 48kHz on macOS (fixed), native device rate on Windows (commonly 44.1kHz or 48kHz)
|
|
232
|
+
- **Channels**: Stereo on macOS (fixed), preserved from source on Windows
|
|
233
|
+
|
|
234
|
+
### Playing Raw Audio
|
|
235
|
+
|
|
236
|
+
You can play the recorded raw audio using FFplay:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
ffplay -f s16le -ar 48000 -ch_layout stereo recording.raw
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Or convert to WAV using FFmpeg:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
ffmpeg -f s16le -ar 48000 -ac 2 -i recording.raw output.wav
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Permissions (macOS)
|
|
249
|
+
|
|
250
|
+
On macOS, you need to grant permissions:
|
|
251
|
+
|
|
252
|
+
1. **Microphone** - Required for input device recording
|
|
253
|
+
2. **Screen Recording** - Required for system audio capture
|
|
254
|
+
|
|
255
|
+
The SDK will automatically prompt for permissions when needed, or you can request them programmatically:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const permissions = AudioRecorder.checkPermission();
|
|
259
|
+
|
|
260
|
+
if (!permissions.mic) {
|
|
261
|
+
AudioRecorder.requestPermission('mic');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!permissions.system) {
|
|
265
|
+
AudioRecorder.requestPermission('system');
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Error Handling
|
|
270
|
+
|
|
271
|
+
| Error Code | Description |
|
|
272
|
+
| ---------------------- | ------------------------------------------ |
|
|
273
|
+
| `DEVICE_NOT_FOUND` | Specified device ID does not exist |
|
|
274
|
+
| `DEVICE_TYPE_MISMATCH` | Device ID doesn't match the specified type |
|
|
275
|
+
| `PERMISSION_DENIED` | Missing required system permission |
|
|
276
|
+
| `ALREADY_RECORDING` | Recording session already active |
|
|
277
|
+
| `DEVICE_DISCONNECTED` | Device was disconnected during recording |
|
|
278
|
+
|
|
279
|
+
## Building from Source
|
|
280
|
+
|
|
281
|
+
### Prerequisites
|
|
282
|
+
|
|
283
|
+
- Node.js 16+
|
|
284
|
+
- CMake 3.15+
|
|
285
|
+
- C++17 compiler
|
|
286
|
+
- Windows: Visual Studio 2019+ or MSVC Build Tools
|
|
287
|
+
- macOS: Xcode Command Line Tools
|
|
288
|
+
|
|
289
|
+
### Build Commands
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Install dependencies
|
|
293
|
+
npm install
|
|
294
|
+
|
|
295
|
+
# Build native module
|
|
296
|
+
npm run build:native
|
|
297
|
+
|
|
298
|
+
# Build TypeScript
|
|
299
|
+
npm run build:ts
|
|
300
|
+
|
|
301
|
+
# Build everything
|
|
302
|
+
npm run build
|
|
303
|
+
|
|
304
|
+
# Run tests
|
|
305
|
+
npm test
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Publishing
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Build prebuilt binaries for current platform
|
|
312
|
+
npm run prebuild
|
|
313
|
+
|
|
314
|
+
# Upload prebuilts to GitHub Release (requires GITHUB_TOKEN)
|
|
315
|
+
npm run prebuild:upload
|
|
316
|
+
|
|
317
|
+
# Publish to npm (prebuilts should be uploaded first)
|
|
318
|
+
npm publish
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### CI/CD with GitHub Actions
|
|
322
|
+
|
|
323
|
+
The project uses GitHub Actions for automated builds and releases. The workflow is triggered by:
|
|
324
|
+
|
|
325
|
+
| Trigger | Action |
|
|
326
|
+
| ------------------------------ | ------------------------------------------------------ |
|
|
327
|
+
| Push to `main` | Build and test on all platforms |
|
|
328
|
+
| Pull Request to `main` | Build and test on all platforms |
|
|
329
|
+
| Push tag `v*` (e.g., `v1.0.0`) | Build, test, publish to npm, and create GitHub Release |
|
|
330
|
+
|
|
331
|
+
**To release a new version:**
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# 1. Update version in package.json
|
|
335
|
+
npm version patch # or minor, major
|
|
336
|
+
|
|
337
|
+
# 2. Push the tag to trigger the release workflow
|
|
338
|
+
git push origin main --tags
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The CI will automatically:
|
|
342
|
+
1. Build native modules for all platforms (macOS arm64/x64, Windows x64/ia32)
|
|
343
|
+
2. Run tests on each platform
|
|
344
|
+
3. Publish the package to npm with prebuilt binaries
|
|
345
|
+
4. Create a GitHub Release with the native binaries attached
|
|
346
|
+
|
|
347
|
+
> **Note**: Ensure `NPM_TOKEN` is configured in repository secrets for npm publishing.
|
|
348
|
+
|
|
349
|
+
## Architecture
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
┌─────────────────────────────────────────────────┐
|
|
353
|
+
│ Node.js Application │
|
|
354
|
+
├─────────────────────────────────────────────────┤
|
|
355
|
+
│ TypeScript Wrapper │
|
|
356
|
+
├─────────────────────────────────────────────────┤
|
|
357
|
+
│ N-API Binding Layer │
|
|
358
|
+
├────────────────────┬────────────────────────────┤
|
|
359
|
+
│ Windows │ macOS │
|
|
360
|
+
│ (WASAPI) │ (AVFoundation + │
|
|
361
|
+
│ │ ScreenCaptureKit) │
|
|
362
|
+
└────────────────────┴────────────────────────────┘
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
For detailed architecture documentation, see [docs/architecture.md](docs/architecture.md).
|
|
366
|
+
|
|
367
|
+
For complete API documentation, see [docs/api.md](docs/api.md).
|
|
368
|
+
|
|
369
|
+
## Contributing
|
|
370
|
+
|
|
371
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
372
|
+
|
|
373
|
+
## License
|
|
374
|
+
|
|
375
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
376
|
+
|
|
377
|
+
## Acknowledgments
|
|
378
|
+
|
|
379
|
+
- [node-addon-api](https://github.com/nodejs/node-addon-api) - N-API C++ wrapper
|
|
380
|
+
- [cmake-js](https://github.com/nicknisi/cmake-js) - CMake build system for Node.js addons
|
package/dist/bindings.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path_1 = require("path");
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
function getBinding() {
|
|
6
|
+
const moduleName = "NativeAudioSDK.node";
|
|
7
|
+
// Try prebuild first (installed via npm)
|
|
8
|
+
const prebuildsDir = (0, path_1.join)(__dirname, "..", "prebuilds");
|
|
9
|
+
const platform = process.platform;
|
|
10
|
+
const arch = process.arch;
|
|
11
|
+
// prebuild-install stores binaries in: prebuilds/{platform}-{arch}/
|
|
12
|
+
const prebuildPath = (0, path_1.join)(prebuildsDir, `${platform}-${arch}`, moduleName);
|
|
13
|
+
if ((0, fs_1.existsSync)(prebuildPath)) {
|
|
14
|
+
return require(prebuildPath);
|
|
15
|
+
}
|
|
16
|
+
// Fallback to local build (development)
|
|
17
|
+
const localPath = (0, path_1.join)(__dirname, "..", "build", "Release", moduleName);
|
|
18
|
+
if ((0, fs_1.existsSync)(localPath)) {
|
|
19
|
+
return require(localPath);
|
|
20
|
+
}
|
|
21
|
+
// Final fallback for different build configurations
|
|
22
|
+
const debugPath = (0, path_1.join)(__dirname, "..", "build", "Debug", moduleName);
|
|
23
|
+
if ((0, fs_1.existsSync)(debugPath)) {
|
|
24
|
+
return require(debugPath);
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`Could not find native module ${moduleName}. ` +
|
|
27
|
+
`Tried:\n - ${prebuildPath}\n - ${localPath}\n - ${debugPath}\n` +
|
|
28
|
+
`Please run 'npm run build:native' or reinstall the package.`);
|
|
29
|
+
}
|
|
30
|
+
const bindings = getBinding();
|
|
31
|
+
exports.default = bindings;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
/**
|
|
3
|
+
* Special device ID for system-wide audio capture (macOS)
|
|
4
|
+
*/
|
|
5
|
+
export declare const SYSTEM_AUDIO_DEVICE_ID = "system";
|
|
6
|
+
/**
|
|
7
|
+
* Device type classification
|
|
8
|
+
*/
|
|
9
|
+
export type DeviceType = "input" | "output";
|
|
10
|
+
/**
|
|
11
|
+
* Permission type for requesting access
|
|
12
|
+
*/
|
|
13
|
+
export type PermissionType = "mic" | "system";
|
|
14
|
+
/**
|
|
15
|
+
* Permission status for audio recording
|
|
16
|
+
*/
|
|
17
|
+
export interface PermissionStatus {
|
|
18
|
+
/** Microphone permission granted */
|
|
19
|
+
mic: boolean;
|
|
20
|
+
/** System audio permission granted (screen recording permission on macOS) */
|
|
21
|
+
system: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Represents an audio device
|
|
25
|
+
*/
|
|
26
|
+
export interface AudioDevice {
|
|
27
|
+
/** Unique device identifier (always has a value) */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Human-readable device name */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Device type: 'input' for microphones, 'output' for system audio */
|
|
32
|
+
type: DeviceType;
|
|
33
|
+
/** Whether this is the default device for its type */
|
|
34
|
+
isDefault: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Audio format information
|
|
38
|
+
*/
|
|
39
|
+
export interface AudioFormat {
|
|
40
|
+
/** Sample rate in Hz (e.g., 44100, 48000) */
|
|
41
|
+
sampleRate: number;
|
|
42
|
+
/** Number of channels (1 = Mono, 2 = Stereo) */
|
|
43
|
+
channels: number;
|
|
44
|
+
/** Output bit depth (currently fixed at 16) */
|
|
45
|
+
bitDepth: number;
|
|
46
|
+
/** Native device bit depth */
|
|
47
|
+
rawBitDepth: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Recording configuration
|
|
51
|
+
* Both deviceType and deviceId are required for consistent cross-platform behavior
|
|
52
|
+
*/
|
|
53
|
+
export interface RecordingConfig {
|
|
54
|
+
/**
|
|
55
|
+
* Type of device to record from.
|
|
56
|
+
* - 'input': Record from microphone
|
|
57
|
+
* - 'output': Record system audio (loopback)
|
|
58
|
+
*/
|
|
59
|
+
deviceType: DeviceType;
|
|
60
|
+
/**
|
|
61
|
+
* Device ID to record from (obtained from getDevices()).
|
|
62
|
+
* Every device has a valid ID - use the ID from the device list.
|
|
63
|
+
*/
|
|
64
|
+
deviceId: string;
|
|
65
|
+
}
|
|
66
|
+
export declare class AudioRecorder extends EventEmitter {
|
|
67
|
+
private controller;
|
|
68
|
+
private isRecording;
|
|
69
|
+
constructor();
|
|
70
|
+
/**
|
|
71
|
+
* Starts the recording session.
|
|
72
|
+
* @param config Configuration object with deviceType and deviceId (both required)
|
|
73
|
+
*/
|
|
74
|
+
start(config: RecordingConfig): Promise<void>;
|
|
75
|
+
stop(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Lists available audio devices.
|
|
78
|
+
* @param type Optional filter by device type
|
|
79
|
+
* @returns Array of AudioDevice objects (all with valid id values)
|
|
80
|
+
*/
|
|
81
|
+
static getDevices(type?: DeviceType): AudioDevice[];
|
|
82
|
+
/**
|
|
83
|
+
* Gets the audio format of a specific device.
|
|
84
|
+
* @param deviceId The device ID to query
|
|
85
|
+
* @returns AudioFormat object
|
|
86
|
+
*/
|
|
87
|
+
static getDeviceFormat(deviceId: string): AudioFormat;
|
|
88
|
+
/**
|
|
89
|
+
* Checks the current permission status for audio recording.
|
|
90
|
+
* On Windows, always returns { mic: true, system: true } as no explicit permissions are required.
|
|
91
|
+
* On macOS, checks actual permission status for microphone and screen recording.
|
|
92
|
+
* @returns PermissionStatus object with mic and system boolean fields
|
|
93
|
+
*/
|
|
94
|
+
static checkPermission(): PermissionStatus;
|
|
95
|
+
/**
|
|
96
|
+
* Requests permission for the specified type.
|
|
97
|
+
* On Windows, always returns true as no explicit permissions are required.
|
|
98
|
+
* On macOS, prompts the user to grant the requested permission.
|
|
99
|
+
* @param type The permission type to request: 'mic' for microphone, 'system' for system audio
|
|
100
|
+
* @returns true if permission was granted, false otherwise
|
|
101
|
+
*/
|
|
102
|
+
static requestPermission(type: PermissionType): boolean;
|
|
103
|
+
}
|
|
104
|
+
export declare const nativeBindings: any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/typescript/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC"}
|