node-mac-recorder 2.4.4 → 2.4.6
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/package.json +1 -1
- package/src/mac_recorder.mm +88 -5
- package/src/window_selector.mm +41 -19
- package/window-selector.js +27 -0
package/package.json
CHANGED
package/src/mac_recorder.mm
CHANGED
|
@@ -26,6 +26,7 @@ API_AVAILABLE(macos(12.3))
|
|
|
26
26
|
@property (nonatomic, strong) AVAssetWriter *assetWriter;
|
|
27
27
|
@property (nonatomic, strong) AVAssetWriterInput *videoInput;
|
|
28
28
|
@property (nonatomic, strong) AVAssetWriterInput *audioInput;
|
|
29
|
+
@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *pixelBufferAdaptor;
|
|
29
30
|
@property (nonatomic, strong) NSURL *outputURL;
|
|
30
31
|
@property (nonatomic, assign) BOOL isWriting;
|
|
31
32
|
@property (nonatomic, assign) CMTime startTime;
|
|
@@ -63,6 +64,13 @@ API_AVAILABLE(macos(12.3))
|
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
// Check asset writer status before processing
|
|
68
|
+
if (self.assetWriter.status == AVAssetWriterStatusFailed) {
|
|
69
|
+
NSLog(@"❌ Asset writer has failed status: %@", self.assetWriter.error.localizedDescription);
|
|
70
|
+
self.startFailed = YES;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
66
74
|
// Start asset writer on first sample buffer
|
|
67
75
|
if (!self.hasStartTime) {
|
|
68
76
|
NSLog(@"🚀 Starting asset writer with first sample buffer");
|
|
@@ -88,10 +96,62 @@ API_AVAILABLE(macos(12.3))
|
|
|
88
96
|
case SCStreamOutputTypeScreen: {
|
|
89
97
|
NSLog(@"📺 Processing screen sample buffer");
|
|
90
98
|
if (self.videoInput && self.videoInput.isReadyForMoreMediaData) {
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
// Check sample buffer validity
|
|
100
|
+
if (!CMSampleBufferIsValid(sampleBuffer)) {
|
|
101
|
+
NSLog(@"⚠️ Invalid sample buffer received");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check timing - ensure presentation time is advancing
|
|
106
|
+
CMTime currentTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
|
|
107
|
+
NSLog(@"📺 Sample buffer PTS: %lld", currentTime.value);
|
|
108
|
+
|
|
109
|
+
BOOL success = NO;
|
|
110
|
+
|
|
111
|
+
// Try using pixel buffer adaptor for better compatibility
|
|
112
|
+
if (self.pixelBufferAdaptor && self.pixelBufferAdaptor.assetWriterInput.isReadyForMoreMediaData) {
|
|
113
|
+
// Extract pixel buffer from sample buffer
|
|
114
|
+
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
|
115
|
+
if (pixelBuffer) {
|
|
116
|
+
success = [self.pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:currentTime];
|
|
117
|
+
NSLog(@"📺 Pixel buffer appended via adaptor: %@", success ? @"SUCCESS" : @"FAILED");
|
|
118
|
+
} else {
|
|
119
|
+
// Fallback to direct sample buffer append
|
|
120
|
+
success = [self.videoInput appendSampleBuffer:sampleBuffer];
|
|
121
|
+
NSLog(@"📺 Sample buffer appended directly: %@", success ? @"SUCCESS" : @"FAILED");
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Fallback to direct sample buffer append
|
|
125
|
+
success = [self.videoInput appendSampleBuffer:sampleBuffer];
|
|
126
|
+
NSLog(@"📺 Video sample buffer appended (fallback): %@", success ? @"SUCCESS" : @"FAILED");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!success) {
|
|
130
|
+
// Log detailed error information
|
|
131
|
+
NSLog(@"❌ Video input append failed - Asset Writer Status: %ld", (long)self.assetWriter.status);
|
|
132
|
+
if (self.assetWriter.error) {
|
|
133
|
+
NSLog(@"❌ Asset Writer Error: %@", self.assetWriter.error.localizedDescription);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check if asset writer has failed and mark for cleanup
|
|
137
|
+
if (self.assetWriter.status == AVAssetWriterStatusFailed) {
|
|
138
|
+
self.startFailed = YES;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
93
141
|
} else {
|
|
94
|
-
NSLog(@"⚠️ Video input not ready for more data"
|
|
142
|
+
NSLog(@"⚠️ Video input not ready for more data - isReadyForMoreMediaData: %@",
|
|
143
|
+
self.videoInput.isReadyForMoreMediaData ? @"YES" : @"NO");
|
|
144
|
+
|
|
145
|
+
// Also check pixel buffer adaptor readiness
|
|
146
|
+
if (self.pixelBufferAdaptor) {
|
|
147
|
+
NSLog(@"📊 Pixel buffer adaptor ready: %@",
|
|
148
|
+
self.pixelBufferAdaptor.assetWriterInput.isReadyForMoreMediaData ? @"YES" : @"NO");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Log asset writer input status
|
|
152
|
+
NSLog(@"📊 Asset Writer Status: %ld, Video Input Status: readyForMoreMediaData=%@",
|
|
153
|
+
(long)self.assetWriter.status,
|
|
154
|
+
self.videoInput.isReadyForMoreMediaData ? @"YES" : @"NO");
|
|
95
155
|
}
|
|
96
156
|
break;
|
|
97
157
|
}
|
|
@@ -100,6 +160,10 @@ API_AVAILABLE(macos(12.3))
|
|
|
100
160
|
if (self.audioInput && self.audioInput.isReadyForMoreMediaData) {
|
|
101
161
|
BOOL success = [self.audioInput appendSampleBuffer:sampleBuffer];
|
|
102
162
|
NSLog(@"🔊 Audio sample buffer appended: %@", success ? @"SUCCESS" : @"FAILED");
|
|
163
|
+
|
|
164
|
+
if (!success && self.assetWriter.error) {
|
|
165
|
+
NSLog(@"❌ Audio append error: %@", self.assetWriter.error.localizedDescription);
|
|
166
|
+
}
|
|
103
167
|
} else {
|
|
104
168
|
NSLog(@"⚠️ Audio input not ready for more data (or no audio input)");
|
|
105
169
|
}
|
|
@@ -110,6 +174,10 @@ API_AVAILABLE(macos(12.3))
|
|
|
110
174
|
if (self.audioInput && self.audioInput.isReadyForMoreMediaData) {
|
|
111
175
|
BOOL success = [self.audioInput appendSampleBuffer:sampleBuffer];
|
|
112
176
|
NSLog(@"🎤 Microphone sample buffer appended: %@", success ? @"SUCCESS" : @"FAILED");
|
|
177
|
+
|
|
178
|
+
if (!success && self.assetWriter.error) {
|
|
179
|
+
NSLog(@"❌ Microphone append error: %@", self.assetWriter.error.localizedDescription);
|
|
180
|
+
}
|
|
113
181
|
} else {
|
|
114
182
|
NSLog(@"⚠️ Microphone input not ready for more data (or no audio input)");
|
|
115
183
|
}
|
|
@@ -474,15 +542,30 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
474
542
|
NSDictionary *videoSettings = @{
|
|
475
543
|
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
476
544
|
AVVideoWidthKey: @((NSInteger)videoSize.width),
|
|
477
|
-
AVVideoHeightKey: @((NSInteger)videoSize.height)
|
|
545
|
+
AVVideoHeightKey: @((NSInteger)videoSize.height),
|
|
546
|
+
AVVideoCompressionPropertiesKey: @{
|
|
547
|
+
AVVideoAverageBitRateKey: @(2000000), // 2 Mbps
|
|
548
|
+
AVVideoMaxKeyFrameIntervalKey: @30
|
|
549
|
+
}
|
|
478
550
|
};
|
|
479
551
|
|
|
480
552
|
g_scDelegate.videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
|
|
481
553
|
g_scDelegate.videoInput.expectsMediaDataInRealTime = YES;
|
|
482
554
|
|
|
555
|
+
// Create pixel buffer adaptor for more robust handling
|
|
556
|
+
NSDictionary *pixelBufferAttributes = @{
|
|
557
|
+
(NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
|
|
558
|
+
(NSString*)kCVPixelBufferWidthKey: @((NSInteger)videoSize.width),
|
|
559
|
+
(NSString*)kCVPixelBufferHeightKey: @((NSInteger)videoSize.height),
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
g_scDelegate.pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
|
|
563
|
+
initWithAssetWriterInput:g_scDelegate.videoInput
|
|
564
|
+
sourcePixelBufferAttributes:pixelBufferAttributes];
|
|
565
|
+
|
|
483
566
|
if ([g_scDelegate.assetWriter canAddInput:g_scDelegate.videoInput]) {
|
|
484
567
|
[g_scDelegate.assetWriter addInput:g_scDelegate.videoInput];
|
|
485
|
-
NSLog(@"✅ Video input added to asset writer");
|
|
568
|
+
NSLog(@"✅ Video input added to asset writer with pixel buffer adaptor");
|
|
486
569
|
} else {
|
|
487
570
|
NSLog(@"❌ Cannot add video input to asset writer");
|
|
488
571
|
}
|
package/src/window_selector.mm
CHANGED
|
@@ -221,8 +221,10 @@ bool hideScreenRecordingPreview();
|
|
|
221
221
|
@implementation WindowSelectorDelegate
|
|
222
222
|
- (void)selectButtonClicked:(id)sender {
|
|
223
223
|
if (g_currentWindowUnderCursor) {
|
|
224
|
-
g_selectedWindowInfo = g_currentWindowUnderCursor;
|
|
225
|
-
|
|
224
|
+
g_selectedWindowInfo = [g_currentWindowUnderCursor copy];
|
|
225
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
226
|
+
cleanupWindowSelector();
|
|
227
|
+
});
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -233,23 +235,22 @@ bool hideScreenRecordingPreview();
|
|
|
233
235
|
// Get screen info from global array using button tag
|
|
234
236
|
if (g_allScreens && screenIndex >= 0 && screenIndex < [g_allScreens count]) {
|
|
235
237
|
NSDictionary *screenInfo = [g_allScreens objectAtIndex:screenIndex];
|
|
236
|
-
g_selectedScreenInfo = screenInfo;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
[screenInfo objectForKey:@"resolution"]);
|
|
241
|
-
|
|
242
|
-
cleanupScreenSelector();
|
|
238
|
+
g_selectedScreenInfo = [screenInfo copy];
|
|
239
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
240
|
+
cleanupScreenSelector();
|
|
241
|
+
});
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
244
|
|
|
246
245
|
- (void)cancelButtonClicked:(id)sender {
|
|
247
|
-
NSLog(@"🚫 CANCEL BUTTON CLICKED: Selection cancelled");
|
|
248
|
-
// Clean up without selecting anything
|
|
249
246
|
if (g_isScreenSelecting) {
|
|
250
|
-
|
|
247
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
248
|
+
cleanupScreenSelector();
|
|
249
|
+
});
|
|
251
250
|
} else {
|
|
252
|
-
|
|
251
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
252
|
+
cleanupWindowSelector();
|
|
253
|
+
});
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
|
|
@@ -565,6 +566,8 @@ void cleanupWindowSelector() {
|
|
|
565
566
|
dispatch_async(dispatch_get_main_queue(), ^{ cleanupWindowSelector(); });
|
|
566
567
|
return;
|
|
567
568
|
}
|
|
569
|
+
|
|
570
|
+
NSLog(@"🧹 Cleaning up window selector resources");
|
|
568
571
|
g_isWindowSelecting = false;
|
|
569
572
|
|
|
570
573
|
// Stop tracking timer
|
|
@@ -971,9 +974,15 @@ bool hideScreenRecordingPreview() {
|
|
|
971
974
|
Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
972
975
|
Napi::Env env = info.Env();
|
|
973
976
|
|
|
977
|
+
// Electron safety check - prevent crashes with overlay windows
|
|
978
|
+
if (getenv("ELECTRON_VERSION") || getenv("ELECTRON_RUN_AS_NODE")) {
|
|
979
|
+
NSLog(@"⚠️ Window selector disabled in Electron environment to prevent crashes");
|
|
980
|
+
return Napi::Boolean::New(env, false);
|
|
981
|
+
}
|
|
982
|
+
|
|
974
983
|
if (g_isWindowSelecting) {
|
|
975
|
-
|
|
976
|
-
return env
|
|
984
|
+
NSLog(@"⚠️ Window selection already in progress");
|
|
985
|
+
return Napi::Boolean::New(env, false);
|
|
977
986
|
}
|
|
978
987
|
|
|
979
988
|
@try {
|
|
@@ -1270,14 +1279,20 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
|
|
|
1270
1279
|
Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
|
|
1271
1280
|
Napi::Env env = info.Env();
|
|
1272
1281
|
|
|
1282
|
+
// Electron safety check - prevent crashes with overlay windows
|
|
1283
|
+
if (getenv("ELECTRON_VERSION") || getenv("ELECTRON_RUN_AS_NODE")) {
|
|
1284
|
+
NSLog(@"⚠️ Recording preview disabled in Electron environment to prevent crashes");
|
|
1285
|
+
return Napi::Boolean::New(env, false);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1273
1288
|
if (info.Length() < 1) {
|
|
1274
|
-
|
|
1275
|
-
return env
|
|
1289
|
+
NSLog(@"⚠️ Window info object required");
|
|
1290
|
+
return Napi::Boolean::New(env, false);
|
|
1276
1291
|
}
|
|
1277
1292
|
|
|
1278
1293
|
if (!info[0].IsObject()) {
|
|
1279
|
-
|
|
1280
|
-
return env
|
|
1294
|
+
NSLog(@"⚠️ Window info must be an object");
|
|
1295
|
+
return Napi::Boolean::New(env, false);
|
|
1281
1296
|
}
|
|
1282
1297
|
|
|
1283
1298
|
@try {
|
|
@@ -1335,11 +1350,18 @@ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
|
|
|
1335
1350
|
Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
|
|
1336
1351
|
Napi::Env env = info.Env();
|
|
1337
1352
|
|
|
1353
|
+
// Electron safety check - prevent crashes with overlay windows
|
|
1354
|
+
if (getenv("ELECTRON_VERSION") || getenv("ELECTRON_RUN_AS_NODE")) {
|
|
1355
|
+
NSLog(@"⚠️ Screen selector disabled in Electron environment to prevent crashes");
|
|
1356
|
+
return Napi::Boolean::New(env, false);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1338
1359
|
@try {
|
|
1339
1360
|
bool success = startScreenSelection();
|
|
1340
1361
|
return Napi::Boolean::New(env, success);
|
|
1341
1362
|
|
|
1342
1363
|
} @catch (NSException *exception) {
|
|
1364
|
+
NSLog(@"❌ Screen selection error: %@", exception);
|
|
1343
1365
|
return Napi::Boolean::New(env, false);
|
|
1344
1366
|
}
|
|
1345
1367
|
}
|
package/window-selector.js
CHANGED
|
@@ -32,6 +32,15 @@ class WindowSelector extends EventEmitter {
|
|
|
32
32
|
this.selectionTimer = null;
|
|
33
33
|
this.selectedWindow = null;
|
|
34
34
|
this.lastStatus = null;
|
|
35
|
+
|
|
36
|
+
// Electron environment detection
|
|
37
|
+
this.isElectron = !!(process.versions && process.versions.electron) ||
|
|
38
|
+
!!(process.env.ELECTRON_VERSION) ||
|
|
39
|
+
!!(process.env.ELECTRON_RUN_AS_NODE);
|
|
40
|
+
|
|
41
|
+
if (this.isElectron) {
|
|
42
|
+
console.warn("⚠️ WindowSelector: Running in Electron - some features disabled to prevent crashes");
|
|
43
|
+
}
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
/**
|
|
@@ -40,6 +49,10 @@ class WindowSelector extends EventEmitter {
|
|
|
40
49
|
* Select butonuna basılınca seçim tamamlanır
|
|
41
50
|
*/
|
|
42
51
|
async startSelection() {
|
|
52
|
+
if (this.isElectron) {
|
|
53
|
+
throw new Error("Window selection is not supported in Electron environment due to stability issues");
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
if (this.isSelecting) {
|
|
44
57
|
throw new Error("Window selection is already in progress");
|
|
45
58
|
}
|
|
@@ -291,6 +304,11 @@ class WindowSelector extends EventEmitter {
|
|
|
291
304
|
* @returns {Promise<boolean>} Success/failure
|
|
292
305
|
*/
|
|
293
306
|
async showRecordingPreview(windowInfo) {
|
|
307
|
+
if (this.isElectron) {
|
|
308
|
+
console.warn("⚠️ Recording preview disabled in Electron environment");
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
|
|
294
312
|
if (!windowInfo) {
|
|
295
313
|
throw new Error("Window info is required");
|
|
296
314
|
}
|
|
@@ -322,6 +340,10 @@ class WindowSelector extends EventEmitter {
|
|
|
322
340
|
* @returns {Promise<boolean>} Success/failure
|
|
323
341
|
*/
|
|
324
342
|
async startScreenSelection() {
|
|
343
|
+
if (this.isElectron) {
|
|
344
|
+
throw new Error("Screen selection is not supported in Electron environment due to stability issues");
|
|
345
|
+
}
|
|
346
|
+
|
|
325
347
|
try {
|
|
326
348
|
const success = nativeBinding.startScreenSelection();
|
|
327
349
|
if (success) {
|
|
@@ -434,6 +456,11 @@ class WindowSelector extends EventEmitter {
|
|
|
434
456
|
* @returns {Promise<boolean>} Success/failure
|
|
435
457
|
*/
|
|
436
458
|
async showScreenRecordingPreview(screenInfo) {
|
|
459
|
+
if (this.isElectron) {
|
|
460
|
+
console.warn("⚠️ Screen recording preview disabled in Electron environment");
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
437
464
|
if (!screenInfo) {
|
|
438
465
|
throw new Error("Screen info is required");
|
|
439
466
|
}
|