electron-native-screenshare 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CilginSinek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # electron-native-screenshare
2
+
3
+ Cross-platform native audio capture for Electron screen sharing with **process-level audio isolation**.
4
+
5
+ Uses low-level OS APIs instead of web APIs for **lower latency, lower CPU usage, and true per-process audio control**.
6
+
7
+ | Platform | API | Min Version | Include Mode | Exclude Mode |
8
+ |----------|-----|-------------|:------------:|:------------:|
9
+ | Windows | WASAPI Process Loopback | Windows 10 2004 | ✅ Per-process | ✅ Per-process |
10
+ | macOS | ScreenCaptureKit | macOS 13 Ventura | ✅ Per-app | ✅ Per-app |
11
+ | Linux | PipeWire | 0.3.26+ | ✅ Per-process | ⚠️ System audio* |
12
+
13
+ > \* Linux exclude mode captures all system audio from the default output. Per-process exclusion is an OS-level limitation of PipeWire.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install electron-native-screenshare
19
+ ```
20
+
21
+ ### Platform Prerequisites
22
+
23
+ **Windows**: Visual Studio Build Tools with "Desktop development with C++" workload.
24
+
25
+ **macOS**: Xcode Command Line Tools. macOS 13 (Ventura) or later required.
26
+
27
+ **Linux**:
28
+ ```bash
29
+ sudo apt install libpipewire-0.3-dev libx11-dev
30
+ ```
31
+
32
+ > ⚠️ If PipeWire is not installed on Linux, the module will load but audio functions will throw a descriptive error at runtime. It will **not** crash your application.
33
+
34
+ ## Quick Start
35
+
36
+ ```javascript
37
+ const {
38
+ startCapture,
39
+ stopCapture,
40
+ getPidFromWindowHandle,
41
+ isAvailable
42
+ } = require('electron-native-screenshare');
43
+
44
+ // Check if native module loaded successfully
45
+ if (!isAvailable()) {
46
+ console.warn('Native audio capture is not available on this platform');
47
+ }
48
+ ```
49
+
50
+ ## API
51
+
52
+ ### `startCapture(processId?, isIncludeMode?, onData?)`
53
+
54
+ Starts audio capture with process-level isolation.
55
+
56
+ **Parameters:**
57
+ | Name | Type | Default | Description |
58
+ |------|------|---------|-------------|
59
+ | `processId` | `number` | `process.pid` | Target process ID |
60
+ | `isIncludeMode` | `boolean` | `false` | `true` = capture only target, `false` = exclude target |
61
+ | `onData` | `function` | `() => {}` | Callback `(data: Buffer, meta: AudioMetadata) => void` |
62
+
63
+ **Returns:** `boolean` — `true` if started successfully.
64
+
65
+ **Throws:** `Error` if native module unavailable or initialization fails.
66
+
67
+ #### AudioMetadata
68
+
69
+ ```typescript
70
+ interface AudioMetadata {
71
+ sampleRate: number; // e.g., 48000
72
+ channels: number; // e.g., 2 (stereo)
73
+ bitsPerSample: number; // e.g., 32
74
+ isFloat: boolean; // true = IEEE float, false = integer PCM
75
+ }
76
+ ```
77
+
78
+ ### `stopCapture()`
79
+
80
+ Stops the active capture session. Safe to call even if nothing is capturing.
81
+
82
+ **Returns:** `boolean`
83
+
84
+ ### `getPidFromWindowHandle(windowHandle)`
85
+
86
+ Resolves a native window handle to its owning process ID.
87
+
88
+ | Platform | Handle Type | Notes |
89
+ |----------|-------------|-------|
90
+ | Windows | `HWND` | Auto-resolves UWP ApplicationFrameWindow → child process |
91
+ | macOS | `CGWindowID` | Uses CGWindowListCopyWindowInfo |
92
+ | Linux | X11 Window ID | Uses `_NET_WM_PID` atom |
93
+
94
+ **Parameters:**
95
+ | Name | Type | Description |
96
+ |------|------|-------------|
97
+ | `windowHandle` | `number` | Native window handle from Electron's `desktopCapturer` |
98
+
99
+ **Returns:** `number` — Process ID, or `0` if not found.
100
+
101
+ ### `isAvailable()`
102
+
103
+ Returns `true` if the native module loaded successfully.
104
+
105
+ ### `getPlatform()`
106
+
107
+ Returns the current platform: `'win32'`, `'darwin'`, or `'linux'`.
108
+
109
+ ### `getLoadError()`
110
+
111
+ Returns the load error message if the native module failed, or `null` on success.
112
+
113
+ ## Usage Examples
114
+
115
+ ### Screen Sharing (Exclude Mode)
116
+
117
+ Capture all system audio **except** your Electron app:
118
+
119
+ ```javascript
120
+ const { startCapture, stopCapture } = require('electron-native-screenshare');
121
+
122
+ // Your app's audio will NOT go into the stream
123
+ startCapture(process.pid, false, (audioData, meta) => {
124
+ // audioData: Buffer of raw PCM float32 samples
125
+ // meta: { sampleRate: 48000, channels: 2, bitsPerSample: 32, isFloat: true }
126
+
127
+ // Send to WebRTC, write to file, or process as needed
128
+ webrtcTrack.write(audioData);
129
+ });
130
+
131
+ // Later...
132
+ stopCapture();
133
+ ```
134
+
135
+ ### Window Sharing (Include Mode)
136
+
137
+ Capture **only** a specific window's audio:
138
+
139
+ ```javascript
140
+ const {
141
+ startCapture,
142
+ stopCapture,
143
+ getPidFromWindowHandle
144
+ } = require('electron-native-screenshare');
145
+
146
+ // Get sources from Electron's desktopCapturer
147
+ const sources = await desktopCapturer.getSources({ types: ['window'] });
148
+ const target = sources.find(s => s.name === 'Spotify');
149
+
150
+ // Extract window handle and resolve PID
151
+ const hwnd = parseInt(target.id.split(':')[1]);
152
+ const pid = getPidFromWindowHandle(hwnd);
153
+
154
+ // Capture only Spotify's audio
155
+ startCapture(pid, true, (audioData, meta) => {
156
+ // Only Spotify's audio is in the buffer
157
+ webrtcTrack.write(audioData);
158
+ });
159
+ ```
160
+
161
+ ### Graceful Degradation
162
+
163
+ ```javascript
164
+ const capture = require('electron-native-screenshare');
165
+
166
+ if (!capture.isAvailable()) {
167
+ const error = capture.getLoadError();
168
+ console.warn(`Audio capture unavailable: ${error}`);
169
+ // Fall back to Electron's built-in audio capture
170
+ // or disable audio in your sharing feature
171
+ } else {
172
+ capture.startCapture(process.pid, false, onAudioData);
173
+ }
174
+ ```
175
+
176
+ ## How It Works
177
+
178
+ ### Windows — WASAPI Process Loopback
179
+ Uses the Windows Audio Session API (WASAPI) with `AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK` (Windows 10 2004+). This provides true kernel-level process audio isolation with zero mixing overhead.
180
+
181
+ ### macOS — ScreenCaptureKit
182
+ Uses Apple's `SCStream` with `SCContentFilter` (macOS 13+). Filters by `SCRunningApplication` to include/exclude specific apps. Audio is delivered via `SCStreamOutput` delegate as `CMSampleBuffer`.
183
+
184
+ ### Linux — PipeWire
185
+ Uses PipeWire's `pw_stream` API for audio capture. Include mode connects directly to the target process's audio node via `PW_KEY_TARGET_OBJECT`. Exclude mode captures from the default sink monitor.
186
+
187
+ ## CI/CD
188
+
189
+ | Workflow | Trigger | Description |
190
+ |----------|---------|-------------|
191
+ | `test.yml` | Push to `main`, PRs | Builds and tests on Windows, macOS, Linux × Node 18/20/22 |
192
+ | `publish-npm.yml` | GitHub Release | Publishes to npm (requires passing tests) |
193
+ | `publish-github.yml` | GitHub Release | Publishes to GitHub Packages as `@CilginSinek/electron-native-screenshare` |
194
+
195
+ > Tests **must pass** before any publish. If the test pipeline fails, the package will not be published.
196
+
197
+ ## License
198
+
199
+ [MIT](LICENSE)
package/binding.gyp ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "target_name": "topluyo_capture",
5
+ "include_dirs": [
6
+ "<!@(node -p \"require('node-addon-api').include\")"
7
+ ],
8
+ "dependencies": [
9
+ "<!(node -p \"require('node-addon-api').gyp\")"
10
+ ],
11
+ "defines": [
12
+ "NAPI_DISABLE_CPP_EXCEPTIONS"
13
+ ],
14
+ "conditions": [
15
+ ["OS==\"win\"", {
16
+ "sources": [
17
+ "src/win/addon.cpp",
18
+ "src/win/wasapi_capture.cpp"
19
+ ],
20
+ "defines": [
21
+ "_WIN32_WINNT=0x0A00",
22
+ "NTDDI_VERSION=0x0A00000A"
23
+ ],
24
+ "libraries": [
25
+ "-lMmdevapi.lib",
26
+ "-lAvrt.lib"
27
+ ]
28
+ }],
29
+ ["OS==\"mac\"", {
30
+ "sources": [
31
+ "src/mac/addon.cpp",
32
+ "src/mac/coreaudio_capture.mm"
33
+ ],
34
+ "xcode_settings": {
35
+ "CLANG_ENABLE_OBJC_ARC": "YES",
36
+ "OTHER_CPLUSPLUSFLAGS": ["-std=c++17", "-ObjC++"],
37
+ "MACOSX_DEPLOYMENT_TARGET": "13.0"
38
+ },
39
+ "link_settings": {
40
+ "libraries": [
41
+ "-framework CoreAudio",
42
+ "-framework CoreMedia",
43
+ "-framework CoreGraphics",
44
+ "-framework ScreenCaptureKit",
45
+ "-framework Foundation"
46
+ ]
47
+ }
48
+ }],
49
+ ["OS==\"linux\"", {
50
+ "sources": [
51
+ "src/linux/addon.cpp",
52
+ "src/linux/pipewire_capture.cpp"
53
+ ],
54
+ "cflags_cc": [
55
+ "-std=c++17",
56
+ "<!@(pkg-config --cflags libpipewire-0.3 2>/dev/null || echo '')",
57
+ "<!@(pkg-config --cflags x11 2>/dev/null || echo '')"
58
+ ],
59
+ "libraries": [
60
+ "<!@(pkg-config --libs libpipewire-0.3 2>/dev/null || echo '-lpipewire-0.3')",
61
+ "<!@(pkg-config --libs x11 2>/dev/null || echo '-lX11')"
62
+ ]
63
+ }]
64
+ ]
65
+ }
66
+ ]
67
+ }
package/lib/index.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * electron-native-screenshare
3
+ *
4
+ * Cross-platform native audio capture for Electron screen sharing
5
+ * with process-level audio isolation.
6
+ *
7
+ * Unified API — platform detection is automatic. The consumer never
8
+ * needs to import OS-specific modules.
9
+ *
10
+ * Supported platforms:
11
+ * - Windows 10 2004+ (WASAPI Process Loopback)
12
+ * - macOS 13 Ventura+ (ScreenCaptureKit)
13
+ * - Linux (PipeWire 0.3.26+)
14
+ *
15
+ * @module electron-native-screenshare
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ const os = require('os');
21
+ const path = require('path');
22
+
23
+ const platform = os.platform();
24
+ const SUPPORTED_PLATFORMS = ['win32', 'darwin', 'linux'];
25
+
26
+ /** @type {import('./types').NativeCapture | null} */
27
+ let capture = null;
28
+
29
+ /** @type {string | null} */
30
+ let loadError = null;
31
+
32
+ // --- Native module loading with graceful degradation ---
33
+
34
+ if (!SUPPORTED_PLATFORMS.includes(platform)) {
35
+ loadError = `[electron-native-screenshare] Unsupported platform: "${platform}". ` +
36
+ `Supported: ${SUPPORTED_PLATFORMS.join(', ')}. ` +
37
+ `Audio capture functions will throw when called.`;
38
+ console.warn(loadError);
39
+ } else {
40
+ try {
41
+ capture = require('../build/Release/topluyo_capture.node');
42
+ } catch (e) {
43
+ const platformHints = {
44
+ win32: 'Ensure Visual Studio Build Tools and Windows 10 SDK are installed.',
45
+ darwin: 'Ensure Xcode Command Line Tools are installed. Requires macOS 13+.',
46
+ linux: 'Ensure libpipewire-0.3-dev and libx11-dev are installed. ' +
47
+ 'Run: sudo apt install libpipewire-0.3-dev libx11-dev'
48
+ };
49
+
50
+ loadError = `[electron-native-screenshare] Failed to load native module on ${platform}.\n` +
51
+ ` Error: ${e.message}\n` +
52
+ ` Hint: ${platformHints[platform] || 'Check native build dependencies.'}`;
53
+ console.warn(loadError);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Throws a descriptive error if the native module isn't loaded.
59
+ * @private
60
+ */
61
+ function ensureLoaded() {
62
+ if (!capture) {
63
+ throw new Error(
64
+ loadError ||
65
+ `[electron-native-screenshare] Native module is not available on ${platform}.`
66
+ );
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Starts audio capture with process-level isolation.
72
+ *
73
+ * Behavior depends on `isIncludeMode`:
74
+ * - `false` (default): Captures ALL system audio EXCEPT the specified process.
75
+ * Use for screen sharing — your app's audio won't leak into the stream.
76
+ * - `true`: Captures ONLY the specified process's audio.
77
+ * Use for window sharing — only that window's audio goes to the stream.
78
+ *
79
+ * @param {number} [processId] - Target process ID. Defaults to `process.pid` (current process).
80
+ * @param {boolean} [isIncludeMode=false] - `true` to include only the target, `false` to exclude it.
81
+ * @param {function(Buffer, AudioMetadata): void} [onData] - Callback receiving raw PCM audio chunks.
82
+ * - `data` {Buffer} — Raw PCM audio (float32, stereo, 48kHz by default)
83
+ * - `meta` {AudioMetadata} — `{ sampleRate, channels, bitsPerSample, isFloat }`
84
+ * @returns {boolean} `true` if capture started successfully.
85
+ * @throws {Error} If the native module failed to load or initialization fails.
86
+ *
87
+ * @example
88
+ * const { startCapture, stopCapture } = require('electron-native-screenshare');
89
+ *
90
+ * // Screen share: exclude your app's audio
91
+ * startCapture(process.pid, false, (data, meta) => {
92
+ * console.log(`Got ${data.length} bytes, ${meta.sampleRate}Hz ${meta.channels}ch`);
93
+ * });
94
+ *
95
+ * // Window share: include only a specific window's audio
96
+ * startCapture(targetPid, true, (data, meta) => { ... });
97
+ */
98
+ function startCapture(processId, isIncludeMode, onData) {
99
+ ensureLoaded();
100
+
101
+ if (processId === undefined || processId === null) {
102
+ processId = process.pid;
103
+ }
104
+ if (typeof processId !== 'number' || !Number.isInteger(processId) || processId < 0) {
105
+ throw new Error('[electron-native-screenshare] processId must be a non-negative integer.');
106
+ }
107
+ if (typeof isIncludeMode !== 'boolean') {
108
+ isIncludeMode = false;
109
+ }
110
+ if (typeof onData !== 'function') {
111
+ onData = () => {};
112
+ }
113
+
114
+ return capture.startCapture(processId, isIncludeMode, (data, meta) => onData(data, meta));
115
+ }
116
+
117
+ /**
118
+ * Stops the active audio capture session.
119
+ *
120
+ * Safe to call even if no capture is running.
121
+ *
122
+ * @returns {boolean} `true` if stopped successfully.
123
+ * @throws {Error} If the native module failed to load.
124
+ */
125
+ function stopCapture() {
126
+ ensureLoaded();
127
+ return capture.stopCapture();
128
+ }
129
+
130
+ /**
131
+ * Resolves a native window handle to its owning process ID.
132
+ *
133
+ * Platform-specific handle types:
134
+ * - Windows: HWND (from Electron's `desktopCapturer` source ID)
135
+ * - macOS: CGWindowID
136
+ * - Linux: X11 Window ID
137
+ *
138
+ * On Windows, this also handles UWP ApplicationFrameWindow → actual child process resolution.
139
+ *
140
+ * @param {number} windowHandle - The native window handle (HWND / CGWindowID / X11 Window).
141
+ * @returns {number} Process ID owning the window, or `0` if not found.
142
+ * @throws {Error} If the native module failed to load or argument is invalid.
143
+ *
144
+ * @example
145
+ * const { getPidFromWindowHandle } = require('electron-native-screenshare');
146
+ *
147
+ * // From Electron desktopCapturer source:
148
+ * // source.id = "window:12345:0" → extract 12345
149
+ * const hwnd = parseInt(source.id.split(':')[1]);
150
+ * const pid = getPidFromWindowHandle(hwnd);
151
+ */
152
+ function getPidFromWindowHandle(windowHandle) {
153
+ ensureLoaded();
154
+
155
+ if (typeof windowHandle !== 'number') {
156
+ throw new Error('[electron-native-screenshare] windowHandle must be a number.');
157
+ }
158
+
159
+ return capture.getPidFromHwnd(windowHandle);
160
+ }
161
+
162
+ /**
163
+ * Returns whether the native module loaded successfully on this platform.
164
+ *
165
+ * Use this to gracefully check availability before calling capture functions,
166
+ * instead of wrapping everything in try/catch.
167
+ *
168
+ * @returns {boolean} `true` if capture functions are available.
169
+ */
170
+ function isAvailable() {
171
+ return capture !== null;
172
+ }
173
+
174
+ /**
175
+ * Returns the current platform name.
176
+ * @returns {string} 'win32', 'darwin', 'linux', or the raw os.platform() string.
177
+ */
178
+ function getPlatform() {
179
+ return platform;
180
+ }
181
+
182
+ /**
183
+ * If the native module failed to load, returns the error message.
184
+ * Returns `null` if the module loaded successfully.
185
+ * @returns {string | null}
186
+ */
187
+ function getLoadError() {
188
+ return loadError;
189
+ }
190
+
191
+ module.exports = {
192
+ startCapture,
193
+ stopCapture,
194
+ getPidFromWindowHandle,
195
+ isAvailable,
196
+ getPlatform,
197
+ getLoadError,
198
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "electron-native-screenshare",
3
+ "version": "1.0.0",
4
+ "description": "Cross-platform native audio capture for Electron screen sharing with process-level isolation. WASAPI (Windows), ScreenCaptureKit (macOS), PipeWire (Linux).",
5
+ "main": "lib/index.js",
6
+ "files": [
7
+ "lib/",
8
+ "src/",
9
+ "binding.gyp",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "os": [
14
+ "win32",
15
+ "linux",
16
+ "darwin"
17
+ ],
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "gypfile": true,
22
+ "scripts": {
23
+ "build": "node-gyp rebuild",
24
+ "clean": "node-gyp clean",
25
+ "test": "jest --verbose --forceExit",
26
+ "prepublishOnly": "npm test"
27
+ },
28
+ "keywords": [
29
+ "electron",
30
+ "screen-share",
31
+ "audio-capture",
32
+ "wasapi",
33
+ "coreaudio",
34
+ "screencapturekit",
35
+ "pipewire",
36
+ "native",
37
+ "process-isolation"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/CilginSinek/electron-native-screenshare.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/CilginSinek/electron-native-screenshare/issues"
45
+ },
46
+ "homepage": "https://github.com/CilginSinek/electron-native-screenshare#readme",
47
+ "author": "CilginSinek",
48
+ "license": "MIT",
49
+ "dependencies": {
50
+ "node-addon-api": "^7.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "jest": "^29.7.0"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Linux N-API addon — mirrors the Windows addon.cpp interface exactly.
3
+ *
4
+ * Exports:
5
+ * startCapture(processId, isIncludeMode, callback) → boolean
6
+ * stopCapture() → boolean
7
+ * getPidFromHwnd(windowId) → number
8
+ */
9
+
10
+ #include <napi.h>
11
+ #include "pipewire_capture.h"
12
+
13
+ static PipewireCapture capture;
14
+ static Napi::ThreadSafeFunction tsfn;
15
+
16
+ Napi::Value StartCapture(const Napi::CallbackInfo& info) {
17
+ Napi::Env env = info.Env();
18
+
19
+ uint32_t processId = 0;
20
+ if (info.Length() > 0 && info[0].IsNumber()) {
21
+ processId = info[0].As<Napi::Number>().Uint32Value();
22
+ }
23
+
24
+ bool isIncludeMode = false;
25
+ if (info.Length() > 1 && info[1].IsBoolean()) {
26
+ isIncludeMode = info[1].As<Napi::Boolean>().Value();
27
+ }
28
+
29
+ if (info.Length() < 3 || !info[2].IsFunction()) {
30
+ Napi::TypeError::New(env, "Callback function expected as third argument").ThrowAsJavaScriptException();
31
+ return env.Null();
32
+ }
33
+
34
+ std::string errorMsg;
35
+ int result = capture.Initialize(processId, isIncludeMode, errorMsg);
36
+ if (result != 0 || !errorMsg.empty()) {
37
+ char buf[512];
38
+ snprintf(buf, sizeof(buf), "PipeWire Init Failed: %s (code: %d)", errorMsg.c_str(), result);
39
+ Napi::TypeError::New(env, buf).ThrowAsJavaScriptException();
40
+ return env.Null();
41
+ }
42
+
43
+ tsfn = Napi::ThreadSafeFunction::New(
44
+ env,
45
+ info[2].As<Napi::Function>(),
46
+ "PipeWireCaptureCallback",
47
+ 0,
48
+ 1
49
+ );
50
+
51
+ auto callback = [](const uint8_t* data, size_t length, PipewireCapture::AudioMetadata metadata) {
52
+ if (!tsfn) return;
53
+
54
+ struct Payload {
55
+ std::vector<uint8_t> buffer;
56
+ PipewireCapture::AudioMetadata meta;
57
+ };
58
+ auto* payload = new Payload{ std::vector<uint8_t>(data, data + length), metadata };
59
+
60
+ auto napiCallback = [](Napi::Env env, Napi::Function jsCallback, Payload* p) {
61
+ Napi::Object metaObj = Napi::Object::New(env);
62
+ metaObj.Set("sampleRate", p->meta.sampleRate);
63
+ metaObj.Set("channels", p->meta.channels);
64
+ metaObj.Set("bitsPerSample", p->meta.bitsPerSample);
65
+ metaObj.Set("isFloat", p->meta.isFloat);
66
+
67
+ Napi::Buffer<uint8_t> buffer = Napi::Buffer<uint8_t>::Copy(env, p->buffer.data(), p->buffer.size());
68
+ jsCallback.Call({ buffer, metaObj });
69
+ delete p;
70
+ };
71
+
72
+ tsfn.NonBlockingCall(payload, napiCallback);
73
+ };
74
+
75
+ capture.Start(callback);
76
+ return Napi::Boolean::New(env, true);
77
+ }
78
+
79
+ Napi::Value StopCapture(const Napi::CallbackInfo& info) {
80
+ Napi::Env env = info.Env();
81
+ capture.Stop();
82
+ if (tsfn) {
83
+ tsfn.Release();
84
+ tsfn = nullptr;
85
+ }
86
+ return Napi::Boolean::New(env, true);
87
+ }
88
+
89
+ Napi::Value GetPidFromHwnd(const Napi::CallbackInfo& info) {
90
+ Napi::Env env = info.Env();
91
+ if (info.Length() < 1 || !info[0].IsNumber()) {
92
+ Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
93
+ return env.Null();
94
+ }
95
+ uint32_t windowId = info[0].As<Napi::Number>().Uint32Value();
96
+ uint32_t pid = getPidFromWindowId(windowId);
97
+ return Napi::Number::New(env, pid);
98
+ }
99
+
100
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
101
+ exports.Set(Napi::String::New(env, "startCapture"), Napi::Function::New(env, StartCapture));
102
+ exports.Set(Napi::String::New(env, "stopCapture"), Napi::Function::New(env, StopCapture));
103
+ exports.Set(Napi::String::New(env, "getPidFromHwnd"), Napi::Function::New(env, GetPidFromHwnd));
104
+ return exports;
105
+ }
106
+
107
+ NODE_API_MODULE(topluyo_capture, Init)