node-mac-recorder 2.16.10 โ 2.16.12
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/.claude/settings.local.json +1 -1
- package/CLAUDE.md +51 -3
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +85 -55
- package/src/mac_recorder.mm +49 -57
- package/bring-to-front-test.js +0 -159
- package/capture-test.js +0 -87
- package/cursor-test.js +0 -22
- package/debug-test.js +0 -95
- package/default-auto-front-test.js +0 -86
- package/final-multi-display-test.js +0 -74
- package/list-test.js +0 -46
- package/quick-cursor-test.js +0 -20
- package/quick-screen-test.js +0 -34
- package/quick-test.js +0 -35
- package/system-sound-test.js +0 -46
- package/test-audio-controls.js +0 -104
- package/test-both.js +0 -46
- package/test-console-logs.js +0 -81
- package/test-coordinate-debug.js +0 -25
- package/test-cursor-visible.js +0 -41
- package/test-electron-detection.js +0 -44
- package/test-final-coordinate-fix.js +0 -38
- package/test-hybrid.js +0 -53
- package/test-macos14-forced.js +0 -107
- package/test-macos14.js +0 -56
- package/test-multi-display-overlay.js +0 -185
- package/test-multi-screen-selection.js +0 -171
- package/test-multidisplay-fix.js +0 -120
- package/test-native-cursor.js +0 -31
- package/test-overlay-tracking.js +0 -175
- package/test-primary-live.js +0 -17
- package/test-primary-window-debug.js +0 -33
- package/test-quick.js +0 -55
- package/test-real-screen-ids.js +0 -32
- package/test-screencapture-only.js +0 -50
- package/test-screencapture-pure.js +0 -69
- package/test-screencapture.js +0 -52
- package/test-system-audio.js +0 -104
- package/test-window-selector-fix.js +0 -88
- package/window-selector-test.js +0 -160
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"
|
|
4
|
+
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing fixed AVFoundation...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nrecorder.startRecording(''/tmp/electron-test2.mov'')\n .then(success => {\n console.log(''Start:'', success ? ''โ
SUCCESS'' : ''โ FAILED'');\n if (success) {\n setTimeout(() => {\n console.log(''โน๏ธ Stopping...'');\n recorder.stopRecording().then(() => {\n console.log(''โ
Stop completed'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/electron-test2.mov'')) {\n console.log(''๐น File:'', Math.round(fs.statSync(''/tmp/electron-test2.mov'').size/1024) + ''KB'');\n console.log(''๐ Fix successful!'');\n }\n });\n }, 2000);\n }\n })\n .catch(console.error);\n\")"
|
|
5
5
|
],
|
|
6
6
|
"deny": [],
|
|
7
7
|
"ask": []
|
package/CLAUDE.md
CHANGED
|
@@ -4,11 +4,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
node-mac-recorder is a Node.js native addon
|
|
7
|
+
**ELECTRON-FIRST PRIORITY**: node-mac-recorder is a Node.js native addon specifically designed and optimized for Electron.js applications. It provides macOS screen recording capabilities using ScreenCaptureKit (macOS 15+) and AVFoundation (macOS 13+). The package allows recording of full screens, specific windows, or custom areas, with support for multi-display setups, audio capture, and cursor tracking.
|
|
8
|
+
|
|
9
|
+
### **๐ CRITICAL: Electron.js Support**
|
|
10
|
+
This module is **PRIMARILY BUILT FOR ELECTRON.JS**. All features work seamlessly in Electron applications without any restrictions. Both ScreenCaptureKit and AVFoundation are fully supported in Electron environments.
|
|
8
11
|
|
|
9
12
|
## Build System & Commands
|
|
10
13
|
|
|
11
14
|
### Building the Native Module
|
|
15
|
+
|
|
12
16
|
```bash
|
|
13
17
|
npm run build # Build the native module using node-gyp
|
|
14
18
|
npm run rebuild # Clean rebuild of the native module
|
|
@@ -17,6 +21,7 @@ npm install # Runs install.js which builds the module automatically
|
|
|
17
21
|
```
|
|
18
22
|
|
|
19
23
|
### Testing
|
|
24
|
+
|
|
20
25
|
```bash
|
|
21
26
|
npm test # Run the main test suite (test.js)
|
|
22
27
|
node cursor-test.js # Test cursor tracking functionality only
|
|
@@ -24,7 +29,9 @@ node test.js # Run comprehensive API tests
|
|
|
24
29
|
```
|
|
25
30
|
|
|
26
31
|
### Development
|
|
32
|
+
|
|
27
33
|
The package uses node-gyp for building the native C++/Objective-C module. Requires:
|
|
34
|
+
|
|
28
35
|
- macOS 10.15+ (Catalina or later)
|
|
29
36
|
- Xcode Command Line Tools
|
|
30
37
|
- Node.js 14+
|
|
@@ -34,16 +41,19 @@ The package uses node-gyp for building the native C++/Objective-C module. Requir
|
|
|
34
41
|
### Core Components
|
|
35
42
|
|
|
36
43
|
**Main Entry Point**
|
|
44
|
+
|
|
37
45
|
- `index.js` - Main MacRecorder class (EventEmitter-based)
|
|
38
46
|
- Handles all high-level recording operations and coordinate transformations
|
|
39
47
|
|
|
40
48
|
**Native Module** (`src/`)
|
|
49
|
+
|
|
41
50
|
- `mac_recorder.mm` - Main native module entry point and N-API bindings
|
|
42
51
|
- `screen_capture.mm` - AVFoundation-based screen/window recording
|
|
43
52
|
- `audio_capture.mm` - Audio device enumeration and capture
|
|
44
53
|
- `cursor_tracker.mm` - Real-time cursor position and event tracking
|
|
45
54
|
|
|
46
55
|
**Build Configuration**
|
|
56
|
+
|
|
47
57
|
- `binding.gyp` - Native module build configuration
|
|
48
58
|
- Links against AVFoundation, ScreenCaptureKit, AppKit, and other macOS frameworks
|
|
49
59
|
|
|
@@ -58,6 +68,7 @@ The package uses node-gyp for building the native C++/Objective-C module. Requir
|
|
|
58
68
|
### Coordinate System Handling
|
|
59
69
|
|
|
60
70
|
The package handles complex multi-display coordinate transformations:
|
|
71
|
+
|
|
61
72
|
- Global macOS coordinates (can be negative for secondary displays)
|
|
62
73
|
- Display-relative coordinates (always positive, 0-based)
|
|
63
74
|
- Automatic window-to-display mapping for recording
|
|
@@ -65,6 +76,7 @@ The package handles complex multi-display coordinate transformations:
|
|
|
65
76
|
## API Structure
|
|
66
77
|
|
|
67
78
|
### Main Class Methods
|
|
79
|
+
|
|
68
80
|
- `startRecording(outputPath, options)` - Begin screen/window recording
|
|
69
81
|
- `stopRecording()` - Stop recording and finalize video file
|
|
70
82
|
- `getWindows()` - List all recordable application windows
|
|
@@ -73,6 +85,7 @@ The package handles complex multi-display coordinate transformations:
|
|
|
73
85
|
- `checkPermissions()` - Verify macOS recording permissions
|
|
74
86
|
|
|
75
87
|
### Cursor Tracking
|
|
88
|
+
|
|
76
89
|
- `startCursorCapture(filepath, options)` - Begin real-time cursor tracking to JSON
|
|
77
90
|
- `options.windowInfo` - Window information for window-relative coordinates
|
|
78
91
|
- `options.windowRelative` - Set to true for window-relative coordinates
|
|
@@ -80,7 +93,9 @@ The package handles complex multi-display coordinate transformations:
|
|
|
80
93
|
- `getCursorPosition()` - Get current cursor position and state
|
|
81
94
|
|
|
82
95
|
### Events
|
|
96
|
+
|
|
83
97
|
The MacRecorder class emits the following events:
|
|
98
|
+
|
|
84
99
|
- `recordingStarted` - Emitted immediately when recording starts with recording details
|
|
85
100
|
- `started` - Emitted when recording is confirmed started (legacy event)
|
|
86
101
|
- `stopped` - Emitted when recording stops
|
|
@@ -90,17 +105,20 @@ The MacRecorder class emits the following events:
|
|
|
90
105
|
- `cursorCaptureStopped` - Emitted when cursor capture ends
|
|
91
106
|
|
|
92
107
|
### Thumbnails
|
|
108
|
+
|
|
93
109
|
- `getWindowThumbnail(windowId, options)` - Capture window preview image
|
|
94
110
|
- `getDisplayThumbnail(displayId, options)` - Capture display preview image
|
|
95
111
|
|
|
96
112
|
## Development Notes
|
|
97
113
|
|
|
98
114
|
### Testing Strategy
|
|
115
|
+
|
|
99
116
|
- Use `npm test` for full API validation
|
|
100
117
|
- `cursor-test.js` for testing cursor tracking specifically
|
|
101
118
|
- Test files create output in `test-output/` directory
|
|
102
119
|
|
|
103
120
|
### Common Development Patterns
|
|
121
|
+
|
|
104
122
|
- All recording operations are Promise-based
|
|
105
123
|
- Event emission for recording state changes (`recordingStarted`, `started`, `stopped`, `completed`)
|
|
106
124
|
- `recordingStarted` event provides immediate notification with recording details
|
|
@@ -111,12 +129,25 @@ The MacRecorder class emits the following events:
|
|
|
111
129
|
- Display-relative coordinates (when recording)
|
|
112
130
|
- Window-relative coordinates (with windowInfo parameter)
|
|
113
131
|
|
|
114
|
-
### Platform Requirements
|
|
132
|
+
### Platform Requirements & Framework Selection
|
|
133
|
+
|
|
134
|
+
**ELECTRON-FIRST ARCHITECTURE:**
|
|
135
|
+
- **Primary Target**: Electron.js applications (full support, no restrictions)
|
|
136
|
+
- **Secondary**: Node.js standalone applications
|
|
115
137
|
- macOS only (enforced in install.js)
|
|
116
138
|
- Native module compilation required on install
|
|
117
139
|
- Requires screen recording and accessibility permissions
|
|
118
140
|
|
|
141
|
+
**Framework Selection Logic (Electron Priority):**
|
|
142
|
+
- **macOS 15+ + Electron**: ScreenCaptureKit with full capabilities
|
|
143
|
+
- **macOS 15+ + Node.js**: ScreenCaptureKit with full capabilities
|
|
144
|
+
- **macOS 14 + Electron**: AVFoundation with full capabilities
|
|
145
|
+
- **macOS 13 + Electron**: AVFoundation with limited features
|
|
146
|
+
- **macOS 14 + Node.js**: AVFoundation with full capabilities
|
|
147
|
+
- **macOS 13 + Node.js**: AVFoundation with limited features
|
|
148
|
+
|
|
119
149
|
### File Outputs
|
|
150
|
+
|
|
120
151
|
- Video recordings: `.mov` format (H.264/AAC)
|
|
121
152
|
- Cursor data: JSON format with timestamped events
|
|
122
153
|
- `x`, `y`: Cursor coordinates (coordinate system dependent)
|
|
@@ -131,14 +162,31 @@ The MacRecorder class emits the following events:
|
|
|
131
162
|
## Troubleshooting
|
|
132
163
|
|
|
133
164
|
### Build Issues
|
|
165
|
+
|
|
134
166
|
1. Ensure Xcode Command Line Tools: `xcode-select --install`
|
|
135
167
|
2. Clean rebuild: `npm run clean && npm run build`
|
|
136
168
|
3. Check Node.js version compatibility (14+)
|
|
137
169
|
|
|
138
170
|
### Runtime Issues
|
|
171
|
+
|
|
139
172
|
1. Permission failures: Check System Preferences > Security & Privacy
|
|
140
173
|
2. Recording failures: Verify target windows/displays are accessible
|
|
141
174
|
3. Audio issues: Check audio device availability and permissions
|
|
142
175
|
|
|
143
176
|
### Native Module Loading
|
|
144
|
-
|
|
177
|
+
|
|
178
|
+
The module tries loading from `build/Release/` first, then falls back to `build/Debug/` with helpful error messages if neither exists.
|
|
179
|
+
|
|
180
|
+
### **โก CRITICAL IMPLEMENTATION NOTES**
|
|
181
|
+
|
|
182
|
+
**ELECTRON.JS IS THE PRIMARY TARGET:**
|
|
183
|
+
1. **No Electron Restrictions**: All Electron detection logic is for optimization, NOT blocking
|
|
184
|
+
2. **Framework Support**: Both ScreenCaptureKit and AVFoundation work perfectly in Electron
|
|
185
|
+
3. **Environment Detection**: Code detects Electron to provide enhanced logging and optimization
|
|
186
|
+
4. **macOS Compatibility**:
|
|
187
|
+
- macOS 15+ (Sequoia+): Use ScreenCaptureKit for best performance
|
|
188
|
+
- macOS 14 (Sonoma): Use AVFoundation with full feature set
|
|
189
|
+
- macOS 13 (Ventura): Use AVFoundation with basic features
|
|
190
|
+
5. **Error Handling**: Any "Recording failed to start" errors should trigger fallback logic, never blocking
|
|
191
|
+
|
|
192
|
+
**NEVER BLOCK ELECTRON ENVIRONMENTS** - This is a core design principle.
|
package/package.json
CHANGED
|
@@ -123,7 +123,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
123
123
|
g_avCaptureRect = captureRect;
|
|
124
124
|
g_avFrameNumber = 0;
|
|
125
125
|
|
|
126
|
-
// Start capture timer (
|
|
126
|
+
// Start capture timer (10 FPS for Electron compatibility)
|
|
127
127
|
dispatch_queue_t captureQueue = dispatch_queue_create("AVFoundationCaptureQueue", DISPATCH_QUEUE_SERIAL);
|
|
128
128
|
g_avTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, captureQueue);
|
|
129
129
|
|
|
@@ -132,78 +132,107 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
132
132
|
return false;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
uint64_t interval = NSEC_PER_SEC /
|
|
135
|
+
uint64_t interval = NSEC_PER_SEC / 10; // 10 FPS for Electron stability
|
|
136
136
|
dispatch_source_set_timer(g_avTimer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, interval / 10);
|
|
137
137
|
|
|
138
138
|
dispatch_source_set_event_handler(g_avTimer, ^{
|
|
139
139
|
if (!g_avIsRecording) return;
|
|
140
140
|
|
|
141
141
|
@autoreleasepool {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
@try {
|
|
143
|
+
// Capture screen with Electron-safe error handling
|
|
144
|
+
CGImageRef screenImage = nil;
|
|
145
|
+
if (CGRectIsEmpty(g_avCaptureRect)) {
|
|
146
|
+
screenImage = CGDisplayCreateImage(g_avDisplayID);
|
|
147
|
+
} else {
|
|
148
|
+
CGImageRef fullScreen = CGDisplayCreateImage(g_avDisplayID);
|
|
149
|
+
if (fullScreen) {
|
|
150
|
+
screenImage = CGImageCreateWithImageInRect(fullScreen, g_avCaptureRect);
|
|
151
|
+
CGImageRelease(fullScreen);
|
|
152
|
+
}
|
|
151
153
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!screenImage) return;
|
|
155
|
-
|
|
156
|
-
// Convert to pixel buffer
|
|
157
|
-
CVPixelBufferRef pixelBuffer = nil;
|
|
158
|
-
CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, g_avPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
|
|
159
|
-
|
|
160
|
-
if (cvRet == kCVReturnSuccess && pixelBuffer) {
|
|
161
|
-
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
162
|
-
|
|
163
|
-
void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
|
|
164
|
-
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
165
|
-
|
|
166
|
-
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
167
154
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (currentPixelFormat == kCVPixelFormatType_32ARGB) {
|
|
172
|
-
bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
|
|
173
|
-
} else { // kCVPixelFormatType_32BGRA
|
|
174
|
-
bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little;
|
|
155
|
+
if (!screenImage) {
|
|
156
|
+
NSLog(@"โ ๏ธ Failed to capture screen image, skipping frame");
|
|
157
|
+
return;
|
|
175
158
|
}
|
|
159
|
+
|
|
160
|
+
// Convert to pixel buffer with Electron-safe error handling
|
|
161
|
+
CVPixelBufferRef pixelBuffer = nil;
|
|
162
|
+
CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, g_avPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
|
|
176
163
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
164
|
+
if (cvRet == kCVReturnSuccess && pixelBuffer) {
|
|
165
|
+
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
166
|
+
|
|
167
|
+
void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
|
|
168
|
+
if (!pixelData) {
|
|
169
|
+
NSLog(@"โ ๏ธ Failed to get pixel buffer base address");
|
|
170
|
+
CVPixelBufferRelease(pixelBuffer);
|
|
171
|
+
CGImageRelease(screenImage);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
176
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
177
|
+
if (!colorSpace) {
|
|
178
|
+
NSLog(@"โ ๏ธ Failed to create color space");
|
|
179
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
180
|
+
CVPixelBufferRelease(pixelBuffer);
|
|
181
|
+
CGImageRelease(screenImage);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Match bitmap info to pixel format for compatibility
|
|
186
|
+
CGBitmapInfo bitmapInfo;
|
|
187
|
+
OSType currentPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
|
|
188
|
+
if (currentPixelFormat == kCVPixelFormatType_32ARGB) {
|
|
189
|
+
bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
|
|
190
|
+
} else { // kCVPixelFormatType_32BGRA
|
|
191
|
+
bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
CGContextRef context = CGBitmapContextCreate(pixelData,
|
|
195
|
+
CVPixelBufferGetWidth(pixelBuffer),
|
|
196
|
+
CVPixelBufferGetHeight(pixelBuffer),
|
|
197
|
+
8, bytesPerRow, colorSpace, bitmapInfo);
|
|
198
|
+
|
|
199
|
+
if (context) {
|
|
200
|
+
CGContextDrawImage(context, CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)), screenImage);
|
|
201
|
+
CGContextRelease(context);
|
|
202
|
+
|
|
203
|
+
// Write frame only if input is ready
|
|
204
|
+
if (g_avVideoInput && g_avVideoInput.readyForMoreMediaData) {
|
|
205
|
+
CMTime frameTime = CMTimeAdd(g_avStartTime, CMTimeMakeWithSeconds(g_avFrameNumber / 10.0, 600));
|
|
206
|
+
BOOL appendSuccess = [g_avPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
|
|
207
|
+
if (appendSuccess) {
|
|
208
|
+
g_avFrameNumber++;
|
|
209
|
+
} else {
|
|
210
|
+
NSLog(@"โ ๏ธ Failed to append pixel buffer");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
NSLog(@"โ ๏ธ Failed to create bitmap context");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
CGColorSpaceRelease(colorSpace);
|
|
218
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
219
|
+
CVPixelBufferRelease(pixelBuffer);
|
|
220
|
+
} else {
|
|
221
|
+
NSLog(@"โ ๏ธ Failed to create pixel buffer: %d", cvRet);
|
|
194
222
|
}
|
|
195
223
|
|
|
196
|
-
|
|
224
|
+
CGImageRelease(screenImage);
|
|
225
|
+
} @catch (NSException *exception) {
|
|
226
|
+
NSLog(@"โ Exception in AVFoundation capture loop: %@", exception.reason);
|
|
227
|
+
g_avIsRecording = false; // Stop recording on exception to prevent crash
|
|
197
228
|
}
|
|
198
|
-
|
|
199
|
-
CGImageRelease(screenImage);
|
|
200
229
|
}
|
|
201
230
|
});
|
|
202
231
|
|
|
203
232
|
dispatch_resume(g_avTimer);
|
|
204
233
|
g_avIsRecording = true;
|
|
205
234
|
|
|
206
|
-
NSLog(@"๐ฅ AVFoundation recording started: %dx%d @
|
|
235
|
+
NSLog(@"๐ฅ AVFoundation recording started: %dx%d @ 10fps",
|
|
207
236
|
(int)recordingSize.width, (int)recordingSize.height);
|
|
208
237
|
|
|
209
238
|
return true;
|
|
@@ -222,10 +251,11 @@ extern "C" bool stopAVFoundationRecording() {
|
|
|
222
251
|
g_avIsRecording = false;
|
|
223
252
|
|
|
224
253
|
@try {
|
|
225
|
-
// Stop timer
|
|
254
|
+
// Stop timer with Electron-safe cleanup
|
|
226
255
|
if (g_avTimer) {
|
|
227
256
|
dispatch_source_cancel(g_avTimer);
|
|
228
257
|
g_avTimer = nil;
|
|
258
|
+
NSLog(@"โ
AVFoundation timer stopped safely");
|
|
229
259
|
}
|
|
230
260
|
|
|
231
261
|
// Finish writing
|
package/src/mac_recorder.mm
CHANGED
|
@@ -51,14 +51,8 @@ void cleanupRecording() {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// AVFoundation cleanup (
|
|
55
|
-
|
|
56
|
-
[NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
|
|
57
|
-
(NSProcessInfo.processInfo.processName &&
|
|
58
|
-
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
59
|
-
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
|
|
60
|
-
|
|
61
|
-
if (!isElectron && isAVFoundationRecording()) {
|
|
54
|
+
// AVFoundation cleanup (supports both Node.js and Electron)
|
|
55
|
+
if (isAVFoundationRecording()) {
|
|
62
56
|
stopAVFoundationRecording();
|
|
63
57
|
}
|
|
64
58
|
|
|
@@ -211,10 +205,15 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
211
205
|
NSLog(@"๐ง FORCE_AVFOUNDATION environment variable detected - skipping ScreenCaptureKit");
|
|
212
206
|
}
|
|
213
207
|
|
|
214
|
-
//
|
|
215
|
-
// macOS
|
|
216
|
-
|
|
217
|
-
|
|
208
|
+
// Electron-first priority: This application is built for Electron.js
|
|
209
|
+
// macOS 15+ โ ScreenCaptureKit (including Electron)
|
|
210
|
+
// macOS 14/13 โ AVFoundation (including Electron)
|
|
211
|
+
if (isM15Plus && !forceAVFoundation) {
|
|
212
|
+
if (isElectron) {
|
|
213
|
+
NSLog(@"โก ELECTRON PRIORITY: macOS 15+ Electron โ ScreenCaptureKit with full support");
|
|
214
|
+
} else {
|
|
215
|
+
NSLog(@"โ
macOS 15+ Node.js โ ScreenCaptureKit available with full compatibility");
|
|
216
|
+
}
|
|
218
217
|
|
|
219
218
|
// Try ScreenCaptureKit with extensive safety measures
|
|
220
219
|
@try {
|
|
@@ -288,25 +287,30 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
288
287
|
// If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
|
|
289
288
|
NSLog(@"โญ๏ธ ScreenCaptureKit failed - falling back to AVFoundation");
|
|
290
289
|
} else {
|
|
291
|
-
// macOS 14/13 or forced AVFoundation
|
|
290
|
+
// macOS 14/13 or forced AVFoundation โ ALWAYS use AVFoundation (Electron supported!)
|
|
292
291
|
if (isElectron) {
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
if (isM14Plus) {
|
|
293
|
+
NSLog(@"โก ELECTRON PRIORITY: macOS 14/13 Electron โ AVFoundation with full support");
|
|
294
|
+
} else if (isM13Plus) {
|
|
295
|
+
NSLog(@"โก ELECTRON PRIORITY: macOS 13 Electron โ AVFoundation with limited features");
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
if (isM15Plus) {
|
|
299
|
+
NSLog(@"๐ฏ macOS 15+ Node.js with FORCE_AVFOUNDATION โ using AVFoundation");
|
|
300
|
+
} else if (isM14Plus) {
|
|
301
|
+
NSLog(@"๐ฏ macOS 14 Node.js โ using AVFoundation (primary method)");
|
|
302
|
+
} else if (isM13Plus) {
|
|
303
|
+
NSLog(@"๐ฏ macOS 13 Node.js โ using AVFoundation (limited features)");
|
|
304
|
+
}
|
|
295
305
|
}
|
|
296
306
|
|
|
297
|
-
if (
|
|
298
|
-
NSLog(@"๐ฏ macOS 15+ with FORCE_AVFOUNDATION - using AVFoundation");
|
|
299
|
-
} else if (isM14Plus) {
|
|
300
|
-
NSLog(@"๐ฏ macOS 14 detected - using AVFoundation (primary method)");
|
|
301
|
-
} else if (isM13Plus) {
|
|
302
|
-
NSLog(@"๐ฏ macOS 13 detected - using AVFoundation (limited features)");
|
|
303
|
-
} else {
|
|
307
|
+
if (!isM13Plus) {
|
|
304
308
|
NSLog(@"โ macOS version too old (< 13.0) - Not supported");
|
|
305
309
|
return Napi::Boolean::New(env, false);
|
|
306
310
|
}
|
|
307
311
|
|
|
308
|
-
// DIRECT AVFoundation
|
|
309
|
-
NSLog(@"โญ๏ธ Using AVFoundation directly -
|
|
312
|
+
// DIRECT AVFoundation for all environments (Node.js + Electron)
|
|
313
|
+
NSLog(@"โญ๏ธ Using AVFoundation directly - supports both Node.js and Electron");
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
@@ -366,32 +370,26 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
|
|
|
366
370
|
}
|
|
367
371
|
}
|
|
368
372
|
|
|
369
|
-
// Try AVFoundation fallback (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
(NSProcessInfo.processInfo.processName &&
|
|
373
|
-
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
374
|
-
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
|
|
373
|
+
// Try AVFoundation fallback (supports both Node.js and Electron)
|
|
374
|
+
extern bool isAVFoundationRecording();
|
|
375
|
+
extern bool stopAVFoundationRecording();
|
|
375
376
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
} else {
|
|
387
|
-
NSLog(@"โ Failed to stop AVFoundation recording");
|
|
388
|
-
g_isRecording = false;
|
|
389
|
-
return Napi::Boolean::New(env, false);
|
|
390
|
-
}
|
|
377
|
+
@try {
|
|
378
|
+
if (isAVFoundationRecording()) {
|
|
379
|
+
NSLog(@"๐ Stopping AVFoundation recording");
|
|
380
|
+
if (stopAVFoundationRecording()) {
|
|
381
|
+
g_isRecording = false;
|
|
382
|
+
return Napi::Boolean::New(env, true);
|
|
383
|
+
} else {
|
|
384
|
+
NSLog(@"โ Failed to stop AVFoundation recording");
|
|
385
|
+
g_isRecording = false;
|
|
386
|
+
return Napi::Boolean::New(env, false);
|
|
391
387
|
}
|
|
392
|
-
} @catch (NSException *exception) {
|
|
393
|
-
NSLog(@"โ Exception stopping AVFoundation: %@", exception.reason);
|
|
394
388
|
}
|
|
389
|
+
} @catch (NSException *exception) {
|
|
390
|
+
NSLog(@"โ Exception stopping AVFoundation: %@", exception.reason);
|
|
391
|
+
g_isRecording = false;
|
|
392
|
+
return Napi::Boolean::New(env, false);
|
|
395
393
|
}
|
|
396
394
|
|
|
397
395
|
NSLog(@"โ ๏ธ No active recording found to stop");
|
|
@@ -662,14 +660,8 @@ Napi::Value GetRecordingStatus(const Napi::CallbackInfo& info) {
|
|
|
662
660
|
}
|
|
663
661
|
}
|
|
664
662
|
|
|
665
|
-
// Check AVFoundation
|
|
666
|
-
|
|
667
|
-
[NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
|
|
668
|
-
(NSProcessInfo.processInfo.processName &&
|
|
669
|
-
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
670
|
-
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
|
|
671
|
-
|
|
672
|
-
if (!isElectron && isAVFoundationRecording()) {
|
|
663
|
+
// Check AVFoundation (supports both Node.js and Electron)
|
|
664
|
+
if (isAVFoundationRecording()) {
|
|
673
665
|
isRecording = true;
|
|
674
666
|
}
|
|
675
667
|
|
|
@@ -935,8 +927,8 @@ Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
|
|
|
935
927
|
NSLog(@"๐ Permission check for macOS %ld.%ld.%ld",
|
|
936
928
|
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
937
929
|
|
|
938
|
-
// Determine which framework will be used
|
|
939
|
-
BOOL willUseScreenCaptureKit = (isM15Plus && !forceAVFoundation
|
|
930
|
+
// Determine which framework will be used (Electron fully supported!)
|
|
931
|
+
BOOL willUseScreenCaptureKit = (isM15Plus && !forceAVFoundation); // Electron can use ScreenCaptureKit on macOS 15+
|
|
940
932
|
BOOL willUseAVFoundation = (!willUseScreenCaptureKit && (isM13Plus || isM14Plus));
|
|
941
933
|
|
|
942
934
|
if (willUseScreenCaptureKit) {
|