node-mac-recorder 2.1.3 → 2.4.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.
@@ -17,7 +17,12 @@
17
17
  "Bash(touch:*)",
18
18
  "Bash(git add:*)",
19
19
  "Bash(git commit:*)",
20
- "Bash(find:*)"
20
+ "Bash(find:*)",
21
+ "WebFetch(domain:developer.apple.com)",
22
+ "WebFetch(domain:github.com)",
23
+ "WebFetch(domain:nonstrict.eu)",
24
+ "Bash(cp:*)",
25
+ "Bash(git checkout:*)"
21
26
  ],
22
27
  "deny": []
23
28
  }
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # node-mac-recorder
2
2
 
3
- A powerful native macOS screen recording Node.js package with advanced window selection, multi-display support, and granular audio controls. Built with AVFoundation and ScreenCaptureKit for optimal performance.
3
+ A powerful native macOS screen recording Node.js package with advanced window selection, multi-display support, and granular audio controls. Built with AVFoundation for optimal performance.
4
4
 
5
5
  ## Features
6
6
 
@@ -12,7 +12,6 @@ A powerful native macOS screen recording Node.js package with advanced window se
12
12
  - 🖱️ **Multi-Display Support** - Automatic display detection and selection
13
13
  - 🎨 **Cursor Control** - Toggle cursor visibility in recordings
14
14
  - 🖱️ **Cursor Tracking** - Track mouse position, cursor types, and click events
15
- - 🚫 **Exclude Apps/Windows (macOS 15+)** - Use ScreenCaptureKit to exclude specific apps (bundle id/PID) and windows
16
15
 
17
16
  🎵 **Granular Audio Controls**
18
17
 
@@ -50,11 +49,6 @@ npm install node-mac-recorder
50
49
  - **Screen Recording Permission** (automatically requested)
51
50
  - **CPU Architecture**: Intel (x64) and Apple Silicon (ARM64) supported
52
51
 
53
- ScreenCaptureKit path and exclusion support:
54
-
55
- - Requires macOS 15.0+ for on-disk recording via `SCRecordingOutput`
56
- - On older systems (<=14), the library falls back to AVFoundation automatically (exclusions not available)
57
-
58
52
  ### Build Requirements
59
53
 
60
54
  ```bash
@@ -116,22 +110,9 @@ await recorder.startRecording("./recording.mov", {
116
110
  quality: "high", // 'low', 'medium', 'high'
117
111
  frameRate: 30, // FPS (15, 30, 60)
118
112
  captureCursor: false, // Show cursor (default: false)
119
-
120
- // ScreenCaptureKit (macOS 15+) - optional, backward compatible
121
- useScreenCaptureKit: false, // If true and available, prefers ScreenCaptureKit
122
- excludedAppBundleIds: ["com.apple.Safari"], // Exclude by bundle id
123
- excludedPIDs: [process.pid], // Exclude by PID
124
- excludedWindowIds: [12345, 67890], // Exclude specific window IDs
125
- // When running under Electron, autoExcludeSelf defaults to true
126
- autoExcludeSelf: true,
127
113
  });
128
114
  ```
129
115
 
130
- Notes
131
-
132
- - If any of `excludedAppBundleIds`, `excludedPIDs`, `excludedWindowIds` are provided, the library automatically switches to ScreenCaptureKit on supported macOS versions.
133
- - When running inside Electron, the current app PID is excluded by default (`autoExcludeSelf: true`).
134
-
135
116
  #### `stopRecording()`
136
117
 
137
118
  Stops the current recording.
@@ -324,29 +305,6 @@ await new Promise((resolve) => setTimeout(resolve, 10000)); // 10 seconds
324
305
  await recorder.stopRecording();
325
306
  ```
326
307
 
327
- ### Exclude Electron app and other windows (ScreenCaptureKit)
328
-
329
- ```javascript
330
- const recorder = new MacRecorder();
331
-
332
- // By default, when running inside Electron, your app is auto-excluded.
333
- // You can also exclude other apps/windows explicitly:
334
- await recorder.startRecording("./excluded.mov", {
335
- captureCursor: true,
336
- // Prefer SC explicitly (optional — auto-enabled when exclusions are set)
337
- useScreenCaptureKit: true,
338
- excludedAppBundleIds: ["com.apple.Safari"],
339
- excludedWindowIds: [
340
- /* CGWindowID list */
341
- ],
342
- // autoExcludeSelf is true by default on Electron; set false to include your app
343
- // autoExcludeSelf: false,
344
- });
345
-
346
- // ... later
347
- await recorder.stopRecording();
348
- ```
349
-
350
308
  ### Multi-Display Recording
351
309
 
352
310
  ```javascript
@@ -404,17 +362,16 @@ audioDevices.forEach((device, i) => {
404
362
  });
405
363
 
406
364
  // Find system audio device (like BlackHole, Soundflower, etc.)
407
- const systemAudioDevice = audioDevices.find(
408
- (device) =>
409
- device.name.toLowerCase().includes("blackhole") ||
410
- device.name.toLowerCase().includes("soundflower") ||
411
- device.name.toLowerCase().includes("loopback") ||
412
- device.name.toLowerCase().includes("aggregate")
365
+ const systemAudioDevice = audioDevices.find(device =>
366
+ device.name.toLowerCase().includes('blackhole') ||
367
+ device.name.toLowerCase().includes('soundflower') ||
368
+ device.name.toLowerCase().includes('loopback') ||
369
+ device.name.toLowerCase().includes('aggregate')
413
370
  );
414
371
 
415
372
  if (systemAudioDevice) {
416
373
  console.log(`Using system audio device: ${systemAudioDevice.name}`);
417
-
374
+
418
375
  // Record with specific system audio device
419
376
  await recorder.startRecording("./system-audio-specific.mov", {
420
377
  includeMicrophone: false,
@@ -423,10 +380,8 @@ if (systemAudioDevice) {
423
380
  captureArea: { x: 0, y: 0, width: 1, height: 1 }, // Minimal video
424
381
  });
425
382
  } else {
426
- console.log(
427
- "No system audio device found. Installing BlackHole or Soundflower recommended."
428
- );
429
-
383
+ console.log("No system audio device found. Installing BlackHole or Soundflower recommended.");
384
+
430
385
  // Record with default system audio capture (may not work without virtual audio device)
431
386
  await recorder.startRecording("./system-audio-default.mov", {
432
387
  includeMicrophone: false,
@@ -436,7 +391,7 @@ if (systemAudioDevice) {
436
391
  }
437
392
 
438
393
  // Record for 10 seconds
439
- await new Promise((resolve) => setTimeout(resolve, 10000));
394
+ await new Promise(resolve => setTimeout(resolve, 10000));
440
395
  await recorder.stopRecording();
441
396
  ```
442
397
 
@@ -445,7 +400,7 @@ await recorder.stopRecording();
445
400
  For reliable system audio capture, install a virtual audio device:
446
401
 
447
402
  1. **BlackHole** (Free): https://github.com/ExistentialAudio/BlackHole
448
- 2. **Soundflower** (Free): https://github.com/mattingalls/Soundflower
403
+ 2. **Soundflower** (Free): https://github.com/mattingalls/Soundflower
449
404
  3. **Loopback** (Paid): https://rogueamoeba.com/loopback/
450
405
 
451
406
  These create aggregate audio devices that the package can detect and use for system audio capture.
@@ -765,11 +720,6 @@ npm cache clean --force
765
720
  xcode-select --install
766
721
  ```
767
722
 
768
- ### ScreenCaptureKit availability
769
-
770
- - Exclusions require macOS 15+ (uses `SCRecordingOutput`).
771
- - On macOS 12.3–14, the module will fall back to AVFoundation (no exclusions). This is automatic and backward-compatible.
772
-
773
723
  ### Recording Issues
774
724
 
775
725
  1. **Empty/Black Video**: Check screen recording permissions
@@ -824,24 +774,12 @@ MIT License - see [LICENSE](LICENSE) file for details.
824
774
 
825
775
  ### Latest Updates
826
776
 
827
- - ✅ ScreenCaptureKit path with exclusions (apps by bundle id/PID and window IDs) on macOS 15+
828
- - ✅ Electron apps auto-excluded by default (can be disabled with `autoExcludeSelf: false`)
829
- - ✅ Prebuilt binaries for darwin-arm64 and darwin-x64; automatic loading via `node-gyp-build`
830
- - ✅ Cursor Tracking: Track mouse position, cursor types, and click events with JSON export
831
- - ✅ Window Recording: Automatic coordinate conversion for multi-display setups
832
- - ✅ Audio Controls: Separate microphone and system audio controls
833
- - ✅ Display Selection: Multi-monitor support with automatic detection
834
- - ✅ Smart Filtering: Improved window detection and filtering
835
- - ✅ Performance: Optimized native implementation
836
-
837
- ## Prebuilt binaries
838
-
839
- This package ships prebuilt native binaries for macOS:
840
-
841
- - darwin-arm64 (Apple Silicon)
842
- - darwin-x64 (Intel)
843
-
844
- At runtime, the correct binary is loaded automatically via `node-gyp-build`. If a prebuilt is not available for your environment, the module falls back to a local build.
777
+ - ✅ **Cursor Tracking**: Track mouse position, cursor types, and click events with JSON export
778
+ - ✅ **Window Recording**: Automatic coordinate conversion for multi-display setups
779
+ - ✅ **Audio Controls**: Separate microphone and system audio controls
780
+ - ✅ **Display Selection**: Multi-monitor support with automatic detection
781
+ - ✅ **Smart Filtering**: Improved window detection and filtering
782
+ - ✅ **Performance**: Optimized native implementation
845
783
 
846
784
  ---
847
785
 
@@ -0,0 +1,44 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "target_name": "mac_recorder",
5
+ "sources": [
6
+ "src/mac_recorder.mm",
7
+ "src/screen_capture.mm",
8
+ "src/audio_capture.mm",
9
+ "src/cursor_tracker.mm",
10
+ "src/window_selector.mm"
11
+ ],
12
+ "include_dirs": [
13
+ "<!@(node -p \"require('node-addon-api').include\")"
14
+ ],
15
+ "dependencies": [
16
+ "<!(node -p \"require('node-addon-api').gyp\")"
17
+ ],
18
+ "cflags!": [ "-fno-exceptions" ],
19
+ "cflags_cc!": [ "-fno-exceptions" ],
20
+ "xcode_settings": {
21
+ "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
22
+ "CLANG_CXX_LIBRARY": "libc++",
23
+ "MACOSX_DEPLOYMENT_TARGET": "10.15",
24
+ "OTHER_CFLAGS": [
25
+ "-ObjC++"
26
+ ]
27
+ },
28
+ "link_settings": {
29
+ "libraries": [
30
+ "-framework AVFoundation",
31
+ "-framework CoreMedia",
32
+ "-framework CoreVideo",
33
+ "-framework Foundation",
34
+ "-framework AppKit",
35
+ "-framework ScreenCaptureKit",
36
+ "-framework ApplicationServices",
37
+ "-framework Carbon",
38
+ "-framework Accessibility"
39
+ ]
40
+ },
41
+ "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,116 @@
1
+ #import <AVFoundation/AVFoundation.h>
2
+ #import <CoreAudio/CoreAudio.h>
3
+
4
+ @interface AudioCapture : NSObject
5
+
6
+ + (NSArray *)getAudioDevices;
7
+ + (BOOL)hasAudioPermission;
8
+ + (void)requestAudioPermission:(void(^)(BOOL granted))completion;
9
+
10
+ @end
11
+
12
+ @implementation AudioCapture
13
+
14
+ + (NSArray *)getAudioDevices {
15
+ NSMutableArray *devices = [NSMutableArray array];
16
+
17
+ // Get all audio devices
18
+ NSArray *audioDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
19
+
20
+ for (AVCaptureDevice *device in audioDevices) {
21
+ NSDictionary *deviceInfo = @{
22
+ @"id": device.uniqueID,
23
+ @"name": device.localizedName,
24
+ @"manufacturer": device.manufacturer ?: @"Unknown",
25
+ @"isDefault": @([device isEqual:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]])
26
+ };
27
+
28
+ [devices addObject:deviceInfo];
29
+ }
30
+
31
+ // Also get system audio devices using Core Audio
32
+ AudioObjectPropertyAddress propertyAddress = {
33
+ kAudioHardwarePropertyDevices,
34
+ kAudioObjectPropertyScopeGlobal,
35
+ kAudioObjectPropertyElementMaster
36
+ };
37
+
38
+ UInt32 dataSize = 0;
39
+ OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
40
+
41
+ if (status == kAudioHardwareNoError) {
42
+ UInt32 deviceCount = dataSize / sizeof(AudioDeviceID);
43
+ AudioDeviceID *audioDeviceIDs = (AudioDeviceID *)malloc(dataSize);
44
+
45
+ status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDeviceIDs);
46
+
47
+ if (status == kAudioHardwareNoError) {
48
+ for (UInt32 i = 0; i < deviceCount; i++) {
49
+ AudioDeviceID deviceID = audioDeviceIDs[i];
50
+
51
+ // Get device name
52
+ propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
53
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
54
+
55
+ CFStringRef deviceName = NULL;
56
+ dataSize = sizeof(deviceName);
57
+
58
+ status = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, &deviceName);
59
+
60
+ if (status == kAudioHardwareNoError && deviceName) {
61
+ // Check if it's an input device
62
+ propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
63
+ propertyAddress.mScope = kAudioDevicePropertyScopeInput;
64
+
65
+ AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &dataSize);
66
+
67
+ if (dataSize > 0) {
68
+ AudioBufferList *bufferList = (AudioBufferList *)malloc(dataSize);
69
+ AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, bufferList);
70
+
71
+ if (bufferList->mNumberBuffers > 0) {
72
+ NSDictionary *deviceInfo = @{
73
+ @"id": @(deviceID),
74
+ @"name": (__bridge NSString *)deviceName,
75
+ @"type": @"System Audio Input",
76
+ @"isSystemDevice": @YES
77
+ };
78
+
79
+ [devices addObject:deviceInfo];
80
+ }
81
+
82
+ free(bufferList);
83
+ }
84
+
85
+ CFRelease(deviceName);
86
+ }
87
+ }
88
+ }
89
+
90
+ free(audioDeviceIDs);
91
+ }
92
+
93
+ return [devices copy];
94
+ }
95
+
96
+ + (BOOL)hasAudioPermission {
97
+ if (@available(macOS 10.14, *)) {
98
+ AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
99
+ return status == AVAuthorizationStatusAuthorized;
100
+ }
101
+ return YES; // Older versions don't require explicit permission
102
+ }
103
+
104
+ + (void)requestAudioPermission:(void(^)(BOOL granted))completion {
105
+ if (@available(macOS 10.14, *)) {
106
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
107
+ dispatch_async(dispatch_get_main_queue(), ^{
108
+ completion(granted);
109
+ });
110
+ }];
111
+ } else {
112
+ completion(YES);
113
+ }
114
+ }
115
+
116
+ @end