native-recorder-nodejs 1.0.7 → 1.1.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 +1 -0
- package/README.md +17 -3
- package/dist/bindings.js +129 -4
- package/native/mac/AVFEngine.mm +5 -5
- package/native/mac/SCKAudioCapture.mm +5 -5
- package/package.json +6 -2
- package/prebuilds/darwin-arm64/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v29/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v30/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v31/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v32/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v33/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v34/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v35/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v36/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v37/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v38/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v39/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-arm64/electron-v40/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v29/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v30/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v31/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v32/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v33/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v34/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v35/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v36/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v37/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v38/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v39/NativeAudioSDK.node +0 -0
- package/prebuilds/darwin-x64/electron-v40/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v29/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v30/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v31/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v32/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v33/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v34/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v35/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v36/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v37/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v38/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v39/NativeAudioSDK.node +0 -0
- package/prebuilds/win32-x64/electron-v40/NativeAudioSDK.node +0 -0
- package/src/bindings.ts +161 -5
package/CMakeLists.txt
CHANGED
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ High-performance, low-latency native audio recording SDK for Node.js. Supports b
|
|
|
21
21
|
| ------------- | ------------ | ----------- | ----------------------- |
|
|
22
22
|
| Windows 10/11 | x64 | Supported | Supported (per-device) |
|
|
23
23
|
| Windows 10/11 | ia32 | Supported | Supported (per-device) |
|
|
24
|
-
| macOS
|
|
25
|
-
| macOS
|
|
24
|
+
| macOS 13.0+ | arm64 | Supported | Supported (system-wide) |
|
|
25
|
+
| macOS 13.0+ | x64 | Supported | Supported (system-wide) |
|
|
26
26
|
|
|
27
27
|
### Platform Differences
|
|
28
28
|
|
|
@@ -31,7 +31,7 @@ High-performance, low-latency native audio recording SDK for Node.js. Supports b
|
|
|
31
31
|
| Input Devices | Multiple (WASAPI) | Multiple (AVFoundation) |
|
|
32
32
|
| Output Devices | Multiple (per-device) | Single "System Audio" device |
|
|
33
33
|
| Permissions | None required | Microphone + Screen Recording |
|
|
34
|
-
| Min OS Version | Windows 10+ | macOS
|
|
34
|
+
| Min OS Version | Windows 10+ | macOS 13.0+ (for system audio) |
|
|
35
35
|
|
|
36
36
|
## Installation
|
|
37
37
|
|
|
@@ -41,6 +41,20 @@ npm install native-recorder-nodejs
|
|
|
41
41
|
|
|
42
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
43
|
|
|
44
|
+
### Electron Support
|
|
45
|
+
|
|
46
|
+
This package includes prebuilt binaries for Electron 29-40. In most cases, it will work out of the box. If you encounter issues:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Option 1: Use electron-rebuild (recommended)
|
|
50
|
+
npx electron-rebuild
|
|
51
|
+
|
|
52
|
+
# Option 2: Rebuild using cmake-js with Electron runtime
|
|
53
|
+
npx cmake-js compile --runtime=electron --runtime-version=YOUR_ELECTRON_VERSION --arch=x64
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Note**: Make sure to rebuild native modules after updating Electron version.
|
|
57
|
+
|
|
44
58
|
## Quick Start
|
|
45
59
|
|
|
46
60
|
```typescript
|
package/dist/bindings.js
CHANGED
|
@@ -2,30 +2,155 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const path_1 = require("path");
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
|
+
/**
|
|
6
|
+
* Detect if running in Electron environment
|
|
7
|
+
*/
|
|
8
|
+
function isElectron() {
|
|
9
|
+
// Check for Electron-specific process properties
|
|
10
|
+
if (typeof process !== "undefined") {
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
if (process.versions && process.versions.electron) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
// Check for Electron renderer process
|
|
16
|
+
if (typeof globalThis !== "undefined" &&
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
globalThis.window &&
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
globalThis.window.process &&
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
globalThis.window.process.type === "renderer") {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get Electron major version number
|
|
30
|
+
*/
|
|
31
|
+
function getElectronMajorVersion() {
|
|
32
|
+
try {
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
const electronVersion = process.versions.electron;
|
|
35
|
+
if (!electronVersion)
|
|
36
|
+
return null;
|
|
37
|
+
return parseInt(electronVersion.split(".")[0], 10);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find available Electron prebuild versions in the prebuilds directory
|
|
45
|
+
*/
|
|
46
|
+
function findAvailableElectronVersions(platformDir) {
|
|
47
|
+
try {
|
|
48
|
+
if (!(0, fs_1.existsSync)(platformDir))
|
|
49
|
+
return [];
|
|
50
|
+
const dirs = (0, fs_1.readdirSync)(platformDir, { withFileTypes: true });
|
|
51
|
+
const versions = [];
|
|
52
|
+
for (const dir of dirs) {
|
|
53
|
+
if (dir.isDirectory() && dir.name.startsWith("electron-v")) {
|
|
54
|
+
const version = parseInt(dir.name.replace("electron-v", ""), 10);
|
|
55
|
+
if (!isNaN(version)) {
|
|
56
|
+
versions.push(version);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return versions.sort((a, b) => b - a); // Sort descending (newest first)
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Find the closest available Electron prebuild version
|
|
68
|
+
* Prefers newer versions that are closest to the target
|
|
69
|
+
*/
|
|
70
|
+
function findClosestElectronVersion(targetVersion, availableVersions) {
|
|
71
|
+
if (availableVersions.length === 0)
|
|
72
|
+
return null;
|
|
73
|
+
// First, try to find an exact match
|
|
74
|
+
if (availableVersions.includes(targetVersion)) {
|
|
75
|
+
return targetVersion;
|
|
76
|
+
}
|
|
77
|
+
// Find the closest version (prefer slightly older over much newer for stability)
|
|
78
|
+
let closest = null;
|
|
79
|
+
let minDiff = Infinity;
|
|
80
|
+
for (const version of availableVersions) {
|
|
81
|
+
const diff = Math.abs(version - targetVersion);
|
|
82
|
+
// Prefer older versions when diff is the same (more stable)
|
|
83
|
+
if (diff < minDiff || (diff === minDiff && version < targetVersion)) {
|
|
84
|
+
minDiff = diff;
|
|
85
|
+
closest = version;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return closest;
|
|
89
|
+
}
|
|
5
90
|
function getBinding() {
|
|
6
91
|
const moduleName = "NativeAudioSDK.node";
|
|
7
92
|
// Try prebuild first (installed via npm)
|
|
8
93
|
const prebuildsDir = (0, path_1.join)(__dirname, "..", "prebuilds");
|
|
9
94
|
const platform = process.platform;
|
|
10
95
|
const arch = process.arch;
|
|
96
|
+
const platformDir = (0, path_1.join)(prebuildsDir, `${platform}-${arch}`);
|
|
97
|
+
const paths = [];
|
|
98
|
+
// For Electron, try Electron-specific prebuild first
|
|
99
|
+
if (isElectron()) {
|
|
100
|
+
const electronMajor = getElectronMajorVersion();
|
|
101
|
+
if (electronMajor) {
|
|
102
|
+
// Try exact version match first
|
|
103
|
+
const exactPath = (0, path_1.join)(platformDir, `electron-v${electronMajor}`, moduleName);
|
|
104
|
+
paths.push(exactPath);
|
|
105
|
+
if ((0, fs_1.existsSync)(exactPath)) {
|
|
106
|
+
return require(exactPath);
|
|
107
|
+
}
|
|
108
|
+
// Fallback: find closest available Electron version
|
|
109
|
+
const availableVersions = findAvailableElectronVersions(platformDir);
|
|
110
|
+
const closestVersion = findClosestElectronVersion(electronMajor, availableVersions);
|
|
111
|
+
if (closestVersion && closestVersion !== electronMajor) {
|
|
112
|
+
const fallbackPath = (0, path_1.join)(platformDir, `electron-v${closestVersion}`, moduleName);
|
|
113
|
+
paths.push(fallbackPath);
|
|
114
|
+
if ((0, fs_1.existsSync)(fallbackPath)) {
|
|
115
|
+
console.warn(`[native-recorder-nodejs] No prebuild for Electron ${electronMajor}, ` +
|
|
116
|
+
`using Electron ${closestVersion} prebuild as fallback. ` +
|
|
117
|
+
`Consider running 'npx electron-rebuild' for best compatibility.`);
|
|
118
|
+
return require(fallbackPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Try N-API prebuild (works for both Node.js and Electron with N-API support)
|
|
124
|
+
// Since this module uses N-API, the Node.js prebuild should work in Electron too
|
|
11
125
|
// prebuild-install stores binaries in: prebuilds/{platform}-{arch}/
|
|
12
|
-
const prebuildPath = (0, path_1.join)(
|
|
126
|
+
const prebuildPath = (0, path_1.join)(platformDir, moduleName);
|
|
127
|
+
paths.push(prebuildPath);
|
|
13
128
|
if ((0, fs_1.existsSync)(prebuildPath)) {
|
|
14
129
|
return require(prebuildPath);
|
|
15
130
|
}
|
|
131
|
+
// Also try napi-v8 subfolder format
|
|
132
|
+
const napiPrebuildPath = (0, path_1.join)(platformDir, "napi-v8", moduleName);
|
|
133
|
+
paths.push(napiPrebuildPath);
|
|
134
|
+
if ((0, fs_1.existsSync)(napiPrebuildPath)) {
|
|
135
|
+
return require(napiPrebuildPath);
|
|
136
|
+
}
|
|
16
137
|
// Fallback to local build (development)
|
|
17
138
|
const localPath = (0, path_1.join)(__dirname, "..", "build", "Release", moduleName);
|
|
139
|
+
paths.push(localPath);
|
|
18
140
|
if ((0, fs_1.existsSync)(localPath)) {
|
|
19
141
|
return require(localPath);
|
|
20
142
|
}
|
|
21
143
|
// Final fallback for different build configurations
|
|
22
144
|
const debugPath = (0, path_1.join)(__dirname, "..", "build", "Debug", moduleName);
|
|
145
|
+
paths.push(debugPath);
|
|
23
146
|
if ((0, fs_1.existsSync)(debugPath)) {
|
|
24
147
|
return require(debugPath);
|
|
25
148
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`
|
|
149
|
+
const runtime = isElectron() ? "Electron" : "Node.js";
|
|
150
|
+
throw new Error(`Could not find native module ${moduleName} for ${runtime}. ` +
|
|
151
|
+
`Tried:\n - ${paths.join("\n - ")}\n` +
|
|
152
|
+
`Please run 'npm run build:native' or reinstall the package.\n` +
|
|
153
|
+
`For Electron users: try running 'npx electron-rebuild' in your project.`);
|
|
29
154
|
}
|
|
30
155
|
const bindings = getBinding();
|
|
31
156
|
exports.default = bindings;
|
package/native/mac/AVFEngine.mm
CHANGED
|
@@ -77,10 +77,10 @@ void AVFEngine::Start(const std::string &deviceType, const std::string &deviceId
|
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (@available(macOS
|
|
80
|
+
if (@available(macOS 13.0, *)) {
|
|
81
81
|
[impl->sckCapture startWithCallback:dataCb errorCallback:errorCb];
|
|
82
82
|
} else {
|
|
83
|
-
if (errorCb) errorCb("System audio recording requires macOS
|
|
83
|
+
if (errorCb) errorCb("System audio recording requires macOS 13.0 or later.");
|
|
84
84
|
}
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
@@ -166,7 +166,7 @@ std::vector<AudioDevice> AVFEngine::GetDevices() {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
// Add system audio output device (only one on macOS)
|
|
169
|
-
if (@available(macOS
|
|
169
|
+
if (@available(macOS 13.0, *)) {
|
|
170
170
|
AudioDevice systemDevice;
|
|
171
171
|
systemDevice.id = AudioEngine::SYSTEM_AUDIO_DEVICE_ID;
|
|
172
172
|
systemDevice.name = "System Audio";
|
|
@@ -217,7 +217,7 @@ PermissionStatus AVFEngine::CheckPermission() {
|
|
|
217
217
|
// Check screen capture permission (for system audio)
|
|
218
218
|
// ScreenCaptureKit doesn't have a direct permission check API,
|
|
219
219
|
// but we can check if we can get shareable content
|
|
220
|
-
if (@available(macOS
|
|
220
|
+
if (@available(macOS 13.0, *)) {
|
|
221
221
|
__block BOOL hasScreenPermission = NO;
|
|
222
222
|
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
|
223
223
|
|
|
@@ -255,7 +255,7 @@ bool AVFEngine::RequestPermission(PermissionType type) {
|
|
|
255
255
|
}
|
|
256
256
|
else if (type == PermissionType::System) {
|
|
257
257
|
// Request screen capture permission for system audio
|
|
258
|
-
if (@available(macOS
|
|
258
|
+
if (@available(macOS 13.0, *)) {
|
|
259
259
|
// Attempting to get shareable content will trigger the permission prompt
|
|
260
260
|
// if not already granted
|
|
261
261
|
[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent * _Nullable shareableContent, NSError * _Nullable error) {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
self.dataCallback = dataCb;
|
|
30
30
|
self.errorCallback = errorCb;
|
|
31
31
|
|
|
32
|
-
if (@available(macOS
|
|
32
|
+
if (@available(macOS 13.0, *)) {
|
|
33
33
|
[SCShareableContent getShareableContentExcludingDesktopWindows:YES
|
|
34
34
|
onScreenWindowsOnly:NO
|
|
35
35
|
completionHandler:^(SCShareableContent *content, NSError *error) {
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
});
|
|
77
77
|
}];
|
|
78
78
|
} else {
|
|
79
|
-
if (self.errorCallback) self.errorCallback("ScreenCaptureKit
|
|
79
|
+
if (self.errorCallback) self.errorCallback("ScreenCaptureKit audio capture requires macOS 13.0+");
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
- (void)stop {
|
|
84
|
-
if (@available(macOS
|
|
84
|
+
if (@available(macOS 13.0, *)) {
|
|
85
85
|
if (self.stream) {
|
|
86
86
|
[self.stream stopCaptureWithCompletionHandler:nil];
|
|
87
87
|
self.stream = nil;
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
|
|
93
93
|
if (type != SCStreamOutputTypeAudio || !self.dataCallback) return;
|
|
94
94
|
|
|
95
|
-
if (@available(macOS
|
|
95
|
+
if (@available(macOS 13.0, *)) {
|
|
96
96
|
CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
|
|
97
97
|
const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
|
|
98
98
|
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
|
|
204
204
|
// SCStreamDelegate method
|
|
205
205
|
- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
|
|
206
|
-
if (@available(macOS
|
|
206
|
+
if (@available(macOS 13.0, *)) {
|
|
207
207
|
if (error && self.errorCallback) {
|
|
208
208
|
self.errorCallback("Stream stopped with error: " + std::string(error.localizedDescription.UTF8String));
|
|
209
209
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-recorder-nodejs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Cross-platform (Win/Mac) Native Audio SDK for Node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
"build:native:debug": "cmake-js compile --debug",
|
|
12
12
|
"build:tests": "cmake -S . -B build -DBUILD_TESTS=ON && cmake --build build --config Release --target NativeTests",
|
|
13
13
|
"prebuild": "prebuild --backend cmake-js -r napi --strip",
|
|
14
|
+
"prebuild:electron": "prebuild --backend cmake-js -r electron -t 29.0.0 -t 30.0.0 -t 31.0.0 -t 32.0.0 -t 33.0.0 -t 34.0.0 -t 35.0.0 -t 36.0.0 -t 37.0.0 -t 38.0.0 -t 39.0.0 -t 40.0.0 --strip",
|
|
15
|
+
"prebuild:all": "npm run prebuild && npm run prebuild:electron",
|
|
14
16
|
"prebuild:upload": "prebuild --upload-all ${GITHUB_TOKEN}",
|
|
15
17
|
"test": "jest",
|
|
16
18
|
"test:native": "npm run build:tests && cd build && ctest -C Release --output-on-failure",
|
|
17
19
|
"test:cli": "node ./test/cli_test.js",
|
|
18
20
|
"clean": "rimraf dist build prebuilds",
|
|
21
|
+
"install": "prebuild-install || npm run build:native",
|
|
19
22
|
"prepublishOnly": "npm run build:ts",
|
|
20
23
|
"release": "npm version patch && git push && git push --tags",
|
|
21
24
|
"release:minor": "npm version minor && git push && git push --tags",
|
|
@@ -57,7 +60,8 @@
|
|
|
57
60
|
]
|
|
58
61
|
},
|
|
59
62
|
"dependencies": {
|
|
60
|
-
"node-addon-api": "^7.0.0"
|
|
63
|
+
"node-addon-api": "^7.0.0",
|
|
64
|
+
"prebuild-install": "^7.1.2"
|
|
61
65
|
},
|
|
62
66
|
"devDependencies": {
|
|
63
67
|
"@types/jest": "^29.5.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/bindings.ts
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
import { join, dirname } from "path";
|
|
2
|
-
import { existsSync } from "fs";
|
|
2
|
+
import { existsSync, readdirSync } from "fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Detect if running in Electron environment
|
|
6
|
+
*/
|
|
7
|
+
function isElectron(): boolean {
|
|
8
|
+
// Check for Electron-specific process properties
|
|
9
|
+
if (typeof process !== "undefined") {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
if (process.versions && process.versions.electron) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
// Check for Electron renderer process
|
|
15
|
+
if (
|
|
16
|
+
typeof globalThis !== "undefined" &&
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
globalThis.window &&
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
globalThis.window.process &&
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
globalThis.window.process.type === "renderer"
|
|
23
|
+
) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get Electron major version number
|
|
32
|
+
*/
|
|
33
|
+
function getElectronMajorVersion(): number | null {
|
|
34
|
+
try {
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
const electronVersion = process.versions.electron;
|
|
37
|
+
if (!electronVersion) return null;
|
|
38
|
+
|
|
39
|
+
return parseInt(electronVersion.split(".")[0], 10);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find available Electron prebuild versions in the prebuilds directory
|
|
47
|
+
*/
|
|
48
|
+
function findAvailableElectronVersions(platformDir: string): number[] {
|
|
49
|
+
try {
|
|
50
|
+
if (!existsSync(platformDir)) return [];
|
|
51
|
+
|
|
52
|
+
const dirs = readdirSync(platformDir, { withFileTypes: true });
|
|
53
|
+
const versions: number[] = [];
|
|
54
|
+
|
|
55
|
+
for (const dir of dirs) {
|
|
56
|
+
if (dir.isDirectory() && dir.name.startsWith("electron-v")) {
|
|
57
|
+
const version = parseInt(dir.name.replace("electron-v", ""), 10);
|
|
58
|
+
if (!isNaN(version)) {
|
|
59
|
+
versions.push(version);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return versions.sort((a, b) => b - a); // Sort descending (newest first)
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find the closest available Electron prebuild version
|
|
72
|
+
* Prefers newer versions that are closest to the target
|
|
73
|
+
*/
|
|
74
|
+
function findClosestElectronVersion(
|
|
75
|
+
targetVersion: number,
|
|
76
|
+
availableVersions: number[],
|
|
77
|
+
): number | null {
|
|
78
|
+
if (availableVersions.length === 0) return null;
|
|
79
|
+
|
|
80
|
+
// First, try to find an exact match
|
|
81
|
+
if (availableVersions.includes(targetVersion)) {
|
|
82
|
+
return targetVersion;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Find the closest version (prefer slightly older over much newer for stability)
|
|
86
|
+
let closest: number | null = null;
|
|
87
|
+
let minDiff = Infinity;
|
|
88
|
+
|
|
89
|
+
for (const version of availableVersions) {
|
|
90
|
+
const diff = Math.abs(version - targetVersion);
|
|
91
|
+
// Prefer older versions when diff is the same (more stable)
|
|
92
|
+
if (diff < minDiff || (diff === minDiff && version < targetVersion)) {
|
|
93
|
+
minDiff = diff;
|
|
94
|
+
closest = version;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return closest;
|
|
99
|
+
}
|
|
3
100
|
|
|
4
101
|
function getBinding() {
|
|
5
102
|
const moduleName = "NativeAudioSDK.node";
|
|
@@ -8,30 +105,89 @@ function getBinding() {
|
|
|
8
105
|
const prebuildsDir = join(__dirname, "..", "prebuilds");
|
|
9
106
|
const platform = process.platform;
|
|
10
107
|
const arch = process.arch;
|
|
108
|
+
const platformDir = join(prebuildsDir, `${platform}-${arch}`);
|
|
109
|
+
|
|
110
|
+
const paths: string[] = [];
|
|
11
111
|
|
|
112
|
+
// For Electron, try Electron-specific prebuild first
|
|
113
|
+
if (isElectron()) {
|
|
114
|
+
const electronMajor = getElectronMajorVersion();
|
|
115
|
+
if (electronMajor) {
|
|
116
|
+
// Try exact version match first
|
|
117
|
+
const exactPath = join(
|
|
118
|
+
platformDir,
|
|
119
|
+
`electron-v${electronMajor}`,
|
|
120
|
+
moduleName,
|
|
121
|
+
);
|
|
122
|
+
paths.push(exactPath);
|
|
123
|
+
if (existsSync(exactPath)) {
|
|
124
|
+
return require(exactPath);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback: find closest available Electron version
|
|
128
|
+
const availableVersions = findAvailableElectronVersions(platformDir);
|
|
129
|
+
const closestVersion = findClosestElectronVersion(
|
|
130
|
+
electronMajor,
|
|
131
|
+
availableVersions,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (closestVersion && closestVersion !== electronMajor) {
|
|
135
|
+
const fallbackPath = join(
|
|
136
|
+
platformDir,
|
|
137
|
+
`electron-v${closestVersion}`,
|
|
138
|
+
moduleName,
|
|
139
|
+
);
|
|
140
|
+
paths.push(fallbackPath);
|
|
141
|
+
if (existsSync(fallbackPath)) {
|
|
142
|
+
console.warn(
|
|
143
|
+
`[native-recorder-nodejs] No prebuild for Electron ${electronMajor}, ` +
|
|
144
|
+
`using Electron ${closestVersion} prebuild as fallback. ` +
|
|
145
|
+
`Consider running 'npx electron-rebuild' for best compatibility.`,
|
|
146
|
+
);
|
|
147
|
+
return require(fallbackPath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Try N-API prebuild (works for both Node.js and Electron with N-API support)
|
|
154
|
+
// Since this module uses N-API, the Node.js prebuild should work in Electron too
|
|
12
155
|
// prebuild-install stores binaries in: prebuilds/{platform}-{arch}/
|
|
13
|
-
const prebuildPath = join(
|
|
156
|
+
const prebuildPath = join(platformDir, moduleName);
|
|
157
|
+
paths.push(prebuildPath);
|
|
14
158
|
|
|
15
159
|
if (existsSync(prebuildPath)) {
|
|
16
160
|
return require(prebuildPath);
|
|
17
161
|
}
|
|
18
162
|
|
|
163
|
+
// Also try napi-v8 subfolder format
|
|
164
|
+
const napiPrebuildPath = join(platformDir, "napi-v8", moduleName);
|
|
165
|
+
paths.push(napiPrebuildPath);
|
|
166
|
+
|
|
167
|
+
if (existsSync(napiPrebuildPath)) {
|
|
168
|
+
return require(napiPrebuildPath);
|
|
169
|
+
}
|
|
170
|
+
|
|
19
171
|
// Fallback to local build (development)
|
|
20
172
|
const localPath = join(__dirname, "..", "build", "Release", moduleName);
|
|
173
|
+
paths.push(localPath);
|
|
21
174
|
if (existsSync(localPath)) {
|
|
22
175
|
return require(localPath);
|
|
23
176
|
}
|
|
24
177
|
|
|
25
178
|
// Final fallback for different build configurations
|
|
26
179
|
const debugPath = join(__dirname, "..", "build", "Debug", moduleName);
|
|
180
|
+
paths.push(debugPath);
|
|
27
181
|
if (existsSync(debugPath)) {
|
|
28
182
|
return require(debugPath);
|
|
29
183
|
}
|
|
30
184
|
|
|
185
|
+
const runtime = isElectron() ? "Electron" : "Node.js";
|
|
31
186
|
throw new Error(
|
|
32
|
-
`Could not find native module ${moduleName}. ` +
|
|
33
|
-
`Tried:\n - ${
|
|
34
|
-
`Please run 'npm run build:native' or reinstall the package
|
|
187
|
+
`Could not find native module ${moduleName} for ${runtime}. ` +
|
|
188
|
+
`Tried:\n - ${paths.join("\n - ")}\n` +
|
|
189
|
+
`Please run 'npm run build:native' or reinstall the package.\n` +
|
|
190
|
+
`For Electron users: try running 'npx electron-rebuild' in your project.`,
|
|
35
191
|
);
|
|
36
192
|
}
|
|
37
193
|
|