node-mac-recorder 2.20.17 → 2.21.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/AUDIO_CAPTURE.md +84 -0
- package/CAMERA_CAPTURE.md +77 -0
- package/MACOS_PERMISSIONS.md +56 -0
- package/README.md +76 -3
- package/binding.gyp +3 -2
- package/index.js +278 -23
- package/package.json +1 -1
- package/scripts/test-multi-capture.js +103 -0
- package/src/audio_recorder.mm +321 -0
- package/src/camera_recorder.mm +677 -0
- package/src/mac_recorder.mm +310 -67
- package/src/screen_capture_kit.h +2 -2
- package/src/screen_capture_kit.mm +351 -92
- package/src/audio_capture.mm +0 -41
package/AUDIO_CAPTURE.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Audio Capture Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to record microphone and/or system audio alongside the silent screen recording output.
|
|
4
|
+
|
|
5
|
+
## 1. Enumerate audio devices
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const recorder = new MacRecorder();
|
|
9
|
+
const devices = await recorder.getAudioDevices();
|
|
10
|
+
/*
|
|
11
|
+
[
|
|
12
|
+
{
|
|
13
|
+
id: "BuiltInMicDeviceID",
|
|
14
|
+
name: "MacBook Pro Microphone",
|
|
15
|
+
manufacturer: "Apple Inc.",
|
|
16
|
+
isDefault: true,
|
|
17
|
+
transportType: 0
|
|
18
|
+
},
|
|
19
|
+
...
|
|
20
|
+
]
|
|
21
|
+
*/
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Pick the `id` you want to capture. For system audio you typically need to select a loopback device (e.g. BlackHole, Loopback, VB-Cable).
|
|
25
|
+
|
|
26
|
+
## 2. Configure the capture
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
recorder.setAudioSettings({
|
|
30
|
+
microphone: true,
|
|
31
|
+
systemAudio: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
recorder.setSystemAudioDevice("LoopbackDeviceID"); // optional
|
|
35
|
+
recorder.setAudioDevice("BuiltInMicDeviceID"); // microphone device – use setOptions or setAudioSettings
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you only want system audio, disable the microphone flag. When both flags are true on macOS 15+, ScreenCaptureKit mixes the feeds automatically. On older macOS versions (where the module falls back to AVFoundation) you should provide a loopback device that already mixes both sources.
|
|
39
|
+
|
|
40
|
+
## 3. Start recording
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
await recorder.startRecording("./output.mov", {
|
|
44
|
+
includeMicrophone: true,
|
|
45
|
+
includeSystemAudio: true,
|
|
46
|
+
systemAudioDeviceId: "LoopbackDeviceID",
|
|
47
|
+
audioDeviceId: "BuiltInMicDeviceID",
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The recorder automatically creates `temp_audio_<timestamp>.webm` next to the main video file. The timestamp matches the cursor and camera companion files so you can synchronise them later.
|
|
52
|
+
|
|
53
|
+
### File format fallback
|
|
54
|
+
|
|
55
|
+
- macOS 15+ → Opus audio inside a WebM container.
|
|
56
|
+
- macOS 13–14 → Apple does not expose a WebM audio writer, so the clip is stored in a QuickTime container while keeping the `.webm` extension (transcode if you require a strict WebM file).
|
|
57
|
+
|
|
58
|
+
## 4. Stop recording
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const result = await recorder.stopRecording();
|
|
62
|
+
console.log(result.audioOutputPath); // temp_audio_<timestamp>.webm
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The `audioCaptureStarted` / `audioCaptureStopped` events fire with the resolved path and shared session timestamp, letting you update your Electron UI instantly.
|
|
66
|
+
|
|
67
|
+
## 5. Status helpers
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const status = recorder.getAudioCaptureStatus();
|
|
71
|
+
// {
|
|
72
|
+
// isCapturing: true,
|
|
73
|
+
// outputFile: "/tmp/temp_audio_1720000000000.webm",
|
|
74
|
+
// deviceIds: { microphone: "...", system: "..." },
|
|
75
|
+
// includeMicrophone: true,
|
|
76
|
+
// includeSystemAudio: true,
|
|
77
|
+
// sessionTimestamp: 1720000000000
|
|
78
|
+
// }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 6. Limitations
|
|
82
|
+
|
|
83
|
+
- On macOS versions that fall back to AVFoundation (13/14), system audio capture requires a virtual loopback device. The module records whichever device you select as `systemAudioDeviceId`.
|
|
84
|
+
- ScreenCaptureKit automatically mixes microphone + system audio when both flags are enabled. If you need isolated stems, run separate recordings with different device selections.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Camera Capture Guide
|
|
2
|
+
|
|
3
|
+
This guide shows how to capture an external or built‑in camera feed alongside the standard screen recording workflow.
|
|
4
|
+
|
|
5
|
+
## 1. Discover available cameras
|
|
6
|
+
|
|
7
|
+
Use the new helper to list all video-capable devices. Each entry includes the best resolution Apple reports for that device.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const cameras = await recorder.getCameraDevices();
|
|
11
|
+
/* Example entry:
|
|
12
|
+
{
|
|
13
|
+
id: "BuiltInCameraID",
|
|
14
|
+
name: "FaceTime HD Camera",
|
|
15
|
+
position: "front",
|
|
16
|
+
maxResolution: { width: 1920, height: 1080, maxFrameRate: 60 }
|
|
17
|
+
}
|
|
18
|
+
*/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Pick an `id` and store it for future sessions (it is stable across restarts).
|
|
22
|
+
|
|
23
|
+
## 2. Enable camera capture
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
recorder.setCameraDevice(selectedCameraId); // optional – default camera is used otherwise
|
|
27
|
+
recorder.setCameraEnabled(true);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Camera recording is completely independent from system audio and microphone settings; only video frames are captured.
|
|
31
|
+
|
|
32
|
+
The companion file is always silent—no audio tracks are written.
|
|
33
|
+
|
|
34
|
+
## 3. Start recording
|
|
35
|
+
|
|
36
|
+
When you call `startRecording`, the module automatically creates a file named `temp_camera_<timestamp>.webm` next to the main screen recording output:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
await recorder.startRecording("/tmp/output.mov", {
|
|
40
|
+
includeSystemAudio: true,
|
|
41
|
+
captureCamera: true, // shorthand for enabling camera inside options
|
|
42
|
+
cameraDeviceId: selectedCameraId,
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Internally this mirrors the cursor workflow: the camera file is placed in the same directory as `/tmp/output.mov`, sharing the same timestamp that the cursor JSON uses. No extra cleanup is required.
|
|
47
|
+
|
|
48
|
+
### File format fallback
|
|
49
|
+
|
|
50
|
+
- macOS 15+ → VP9 video inside a real WebM container.
|
|
51
|
+
- macOS 13–14 → Apple does not expose a WebM writer, so the clip is stored in a QuickTime container even though the filename remains `.webm`. Transcode to another format if you need a strict WebM file.
|
|
52
|
+
|
|
53
|
+
The `cameraCaptureStarted` and `cameraCaptureStopped` events include the resolved path and the shared `sessionTimestamp` so the UI can react immediately.
|
|
54
|
+
|
|
55
|
+
## 4. Stop recording
|
|
56
|
+
|
|
57
|
+
`stopRecording()` stops the screen capture and the camera recorder together:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
const result = await recorder.stopRecording();
|
|
61
|
+
console.log(result.outputPath); // screen video
|
|
62
|
+
console.log(result.cameraOutputPath); // companion camera clip (or null if disabled)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 5. Integrating with Electron live previews
|
|
66
|
+
|
|
67
|
+
Use the same `cameraDeviceId` with `navigator.mediaDevices.getUserMedia({ video: { deviceId } })` to show a live preview while the native module records in the background. The native pipeline does not consume the camera stream, so sharing the device with Electron is supported as long as the hardware allows concurrent access.
|
|
68
|
+
|
|
69
|
+
## 6. API quick reference
|
|
70
|
+
|
|
71
|
+
- `getCameraDevices()`
|
|
72
|
+
- `setCameraEnabled(enabled)` / `isCameraEnabled()`
|
|
73
|
+
- `setCameraDevice(deviceId)`
|
|
74
|
+
- `getCameraCaptureStatus()` – returns `{ isCapturing, outputFile, deviceId, sessionTimestamp }`
|
|
75
|
+
- Events: `cameraCaptureStarted`, `cameraCaptureStopped`
|
|
76
|
+
|
|
77
|
+
With these helpers you can drive the camera UI inside an Electron app while preserving the high-resolution screen capture handled by ScreenCaptureKit or AVFoundation.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# macOS Permission Checklist
|
|
2
|
+
|
|
3
|
+
This project relies on system-level frameworks (ScreenCaptureKit, AVFoundation) that require explicit permissions. When integrating the native module into an app (Electron, standalone macOS app, etc.), make sure the host application's `Info.plist` defines the usage descriptions below.
|
|
4
|
+
|
|
5
|
+
## Required `Info.plist` keys
|
|
6
|
+
|
|
7
|
+
```xml
|
|
8
|
+
<key>NSCameraUsageDescription</key>
|
|
9
|
+
<string>Allow camera access for screen recording companion video.</string>
|
|
10
|
+
|
|
11
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
12
|
+
<string>Allow microphone access for audio capture.</string>
|
|
13
|
+
|
|
14
|
+
<key>NSCameraUseContinuityCameraDeviceType</key>
|
|
15
|
+
<true/>
|
|
16
|
+
|
|
17
|
+
<key>com.apple.security.device.audio-input</key>
|
|
18
|
+
<true/>
|
|
19
|
+
|
|
20
|
+
<key>com.apple.security.device.camera</key>
|
|
21
|
+
<true/>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> `NSCameraUseContinuityCameraDeviceType` removes the runtime warning when Continuity Camera devices are detected. macOS 14+ expects this key whenever Continuity Camera APIs are used.
|
|
25
|
+
> The `com.apple.security.*` entitlements are only required for sandboxed / hardened runtime builds. Omit them if your distribution does not use the macOS sandbox.
|
|
26
|
+
|
|
27
|
+
### Screen recording
|
|
28
|
+
|
|
29
|
+
Screen recording permissions are granted by the user via the OS **Screen Recording** privacy panel. There is no `Info.plist` key to request it, but your app should guide the user to approve it.
|
|
30
|
+
|
|
31
|
+
## Electron apps
|
|
32
|
+
|
|
33
|
+
Add the keys above to `Info.plist` (located under `electron-builder` config or the generated app bundle). For example, with `electron-builder`, use the `mac.plist` option:
|
|
34
|
+
|
|
35
|
+
```jsonc
|
|
36
|
+
// package.json
|
|
37
|
+
{
|
|
38
|
+
"build": {
|
|
39
|
+
"mac": {
|
|
40
|
+
"extendInfo": {
|
|
41
|
+
"NSCameraUsageDescription": "Allow camera access...",
|
|
42
|
+
"NSMicrophoneUsageDescription": "Allow microphone access...",
|
|
43
|
+
"NSCameraUseContinuityCameraDeviceType": true,
|
|
44
|
+
"com.apple.security.device.audio-input": true,
|
|
45
|
+
"com.apple.security.device.camera": true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Native permission prompts
|
|
53
|
+
|
|
54
|
+
The module proactively requests camera and microphone permissions when recording starts (`AVCaptureDevice requestAccess`). If access is denied, the native start call fails with an explanatory error.
|
|
55
|
+
|
|
56
|
+
Make sure to run your app once, respond to the permission prompts, and instruct testers to do the same.
|
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ A powerful native macOS screen recording Node.js package with advanced window se
|
|
|
14
14
|
- 🖱️ **Multi-Display Support** - Automatic display detection and selection
|
|
15
15
|
- 🎨 **Cursor Control** - Toggle cursor visibility in recordings
|
|
16
16
|
- 🖱️ **Cursor Tracking** - Track mouse position, cursor types, and click events
|
|
17
|
+
- 📷 **Camera Recording** - Capture silent camera video alongside screen recordings
|
|
18
|
+
- 🔊 **Audio Capture** - Record microphone/system audio into synchronized companion files
|
|
17
19
|
- 🚫 **Automatic Overlay Exclusion** - Overlay windows automatically excluded from recordings
|
|
18
20
|
- ⚡ **Electron Compatible** - Enhanced crash protection for Electron applications
|
|
19
21
|
|
|
@@ -39,6 +41,8 @@ A powerful native macOS screen recording Node.js package with advanced window se
|
|
|
39
41
|
- 📁 **Flexible Output** - Custom output paths and formats
|
|
40
42
|
- 🔐 **Permission Management** - Built-in permission checking
|
|
41
43
|
|
|
44
|
+
> **Note**: The screen recording container remains silent. Audio is saved separately as `temp_audio_<timestamp>.webm` so you can remix microphone and system sound in post-processing.
|
|
45
|
+
|
|
42
46
|
## ScreenCaptureKit Technology
|
|
43
47
|
|
|
44
48
|
This package leverages Apple's modern **ScreenCaptureKit** framework (macOS 12.3+) for superior recording capabilities:
|
|
@@ -126,6 +130,10 @@ await recorder.startRecording("./recording.mov", {
|
|
|
126
130
|
quality: "high", // 'low', 'medium', 'high'
|
|
127
131
|
frameRate: 30, // FPS (15, 30, 60)
|
|
128
132
|
captureCursor: false, // Show cursor (default: false)
|
|
133
|
+
|
|
134
|
+
// Camera Capture
|
|
135
|
+
captureCamera: true, // Enable simultaneous camera recording (video-only WebM)
|
|
136
|
+
cameraDeviceId: "built-in-camera-id", // Use specific camera (optional)
|
|
129
137
|
});
|
|
130
138
|
```
|
|
131
139
|
|
|
@@ -136,7 +144,11 @@ Stops the current recording.
|
|
|
136
144
|
```javascript
|
|
137
145
|
const result = await recorder.stopRecording();
|
|
138
146
|
console.log("Recording saved to:", result.outputPath);
|
|
147
|
+
console.log("Camera clip saved to:", result.cameraOutputPath);
|
|
148
|
+
console.log("Audio clip saved to:", result.audioOutputPath);
|
|
149
|
+
console.log("Shared session timestamp:", result.sessionTimestamp);
|
|
139
150
|
```
|
|
151
|
+
The result object always contains `cameraOutputPath` and `audioOutputPath`. If either feature is disabled the corresponding value is `null`. The shared `sessionTimestamp` matches every automatically generated temp file name (`temp_cursor_*`, `temp_camera_*`, and `temp_audio_*`).
|
|
140
152
|
|
|
141
153
|
#### `getWindows()`
|
|
142
154
|
|
|
@@ -184,15 +196,70 @@ const devices = await recorder.getAudioDevices();
|
|
|
184
196
|
console.log(devices);
|
|
185
197
|
// [
|
|
186
198
|
// {
|
|
187
|
-
// id: "
|
|
188
|
-
// name: "
|
|
199
|
+
// id: "BuiltInMicDeviceID",
|
|
200
|
+
// name: "MacBook Pro Microphone",
|
|
189
201
|
// manufacturer: "Apple Inc.",
|
|
190
|
-
// isDefault: true
|
|
202
|
+
// isDefault: true,
|
|
203
|
+
// transportType: 0
|
|
204
|
+
// },
|
|
205
|
+
// ...
|
|
206
|
+
// ]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `getCameraDevices()`
|
|
210
|
+
|
|
211
|
+
Lists available camera devices with resolution metadata.
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const cameras = await recorder.getCameraDevices();
|
|
215
|
+
console.log(cameras);
|
|
216
|
+
// [
|
|
217
|
+
// {
|
|
218
|
+
// id: "FaceTime HD Camera",
|
|
219
|
+
// name: "FaceTime HD Camera",
|
|
220
|
+
// position: "front",
|
|
221
|
+
// maxResolution: { width: 1920, height: 1080, maxFrameRate: 60 }
|
|
191
222
|
// },
|
|
192
223
|
// ...
|
|
193
224
|
// ]
|
|
194
225
|
```
|
|
195
226
|
|
|
227
|
+
### Camera Capture Helpers
|
|
228
|
+
|
|
229
|
+
- `setCameraEnabled(enabled)` – toggles simultaneous camera recording (video-only)
|
|
230
|
+
- `setCameraDevice(deviceId)` – selects the camera by unique macOS identifier
|
|
231
|
+
- `isCameraEnabled()` – returns current camera toggle state
|
|
232
|
+
- `getCameraCaptureStatus()` – returns `{ isCapturing, outputFile, deviceId, sessionTimestamp }`
|
|
233
|
+
|
|
234
|
+
A typical workflow looks like this:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const cameras = await recorder.getCameraDevices();
|
|
238
|
+
const selectedCamera = cameras.find((camera) => camera.position === "front") || cameras[0];
|
|
239
|
+
|
|
240
|
+
recorder.setCameraDevice(selectedCamera?.id);
|
|
241
|
+
recorder.setCameraEnabled(true);
|
|
242
|
+
|
|
243
|
+
await recorder.startRecording("./output.mov");
|
|
244
|
+
// ...
|
|
245
|
+
const status = recorder.getCameraCaptureStatus();
|
|
246
|
+
console.log("Camera stream:", status.outputFile); // temp_camera_<timestamp>.webm
|
|
247
|
+
await recorder.stopRecording();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
> The camera clip is saved alongside the screen recording as `temp_camera_<timestamp>.webm`. The file contains video frames only (no audio). Use the same device IDs in Electron to power live previews (`navigator.mediaDevices.getUserMedia({ video: { deviceId } })`). On macOS versions prior to 15, Apple does not expose a WebM encoder; the module falls back to a QuickTime container while keeping the same filename, so transcode or rename if an actual WebM container is required.
|
|
251
|
+
|
|
252
|
+
See `CAMERA_CAPTURE.md` for a deeper walkthrough and Electron integration tips.
|
|
253
|
+
|
|
254
|
+
### Audio Capture Helpers
|
|
255
|
+
|
|
256
|
+
- `setAudioDevice(deviceId)` – select the microphone input
|
|
257
|
+
- `setSystemAudioEnabled(enabled)` / `isSystemAudioEnabled()`
|
|
258
|
+
- `setSystemAudioDevice(deviceId)` – prefer loopback devices when you need system audio only
|
|
259
|
+
- `getAudioCaptureStatus()` – returns `{ isCapturing, outputFile, deviceIds, includeMicrophone, includeSystemAudio, sessionTimestamp }`
|
|
260
|
+
|
|
261
|
+
See `AUDIO_CAPTURE.md` for a step-by-step guide on enumerating devices, enabling microphone/system capture, and consuming the audio companion files.
|
|
262
|
+
|
|
196
263
|
#### `checkPermissions()`
|
|
197
264
|
|
|
198
265
|
Checks macOS recording permissions.
|
|
@@ -217,6 +284,11 @@ console.log(status);
|
|
|
217
284
|
// {
|
|
218
285
|
// isRecording: true,
|
|
219
286
|
// outputPath: "./recording.mov",
|
|
287
|
+
// cameraOutputPath: "./temp_camera_1720000000000.webm",
|
|
288
|
+
// audioOutputPath: "./temp_audio_1720000000000.webm",
|
|
289
|
+
// cameraCapturing: true,
|
|
290
|
+
// audioCapturing: true,
|
|
291
|
+
// sessionTimestamp: 1720000000000,
|
|
220
292
|
// options: { ... },
|
|
221
293
|
// recordingTime: 15
|
|
222
294
|
// }
|
|
@@ -800,3 +872,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
800
872
|
---
|
|
801
873
|
|
|
802
874
|
**Made for macOS** 🍎 | **Built with AVFoundation** 📹 | **Node.js Ready** 🚀
|
|
875
|
+
> 🛡️ **Permissions:** Ensure your host application's `Info.plist` declares camera and microphone usage descriptions (see `MACOS_PERMISSIONS.md`).
|
package/binding.gyp
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"src/mac_recorder.mm",
|
|
7
7
|
"src/screen_capture_kit.mm",
|
|
8
8
|
"src/avfoundation_recorder.mm",
|
|
9
|
-
"src/
|
|
9
|
+
"src/camera_recorder.mm",
|
|
10
|
+
"src/audio_recorder.mm",
|
|
10
11
|
"src/cursor_tracker.mm",
|
|
11
12
|
"src/window_selector.mm"
|
|
12
13
|
],
|
|
@@ -44,4 +45,4 @@
|
|
|
44
45
|
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
|
|
45
46
|
}
|
|
46
47
|
]
|
|
47
|
-
}
|
|
48
|
+
}
|