node-mac-recorder 2.4.5 → 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 +27 -6
- 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
|
@@ -566,6 +566,8 @@ void cleanupWindowSelector() {
|
|
|
566
566
|
dispatch_async(dispatch_get_main_queue(), ^{ cleanupWindowSelector(); });
|
|
567
567
|
return;
|
|
568
568
|
}
|
|
569
|
+
|
|
570
|
+
NSLog(@"🧹 Cleaning up window selector resources");
|
|
569
571
|
g_isWindowSelecting = false;
|
|
570
572
|
|
|
571
573
|
// Stop tracking timer
|
|
@@ -972,9 +974,15 @@ bool hideScreenRecordingPreview() {
|
|
|
972
974
|
Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
973
975
|
Napi::Env env = info.Env();
|
|
974
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
|
+
|
|
975
983
|
if (g_isWindowSelecting) {
|
|
976
|
-
|
|
977
|
-
return env
|
|
984
|
+
NSLog(@"⚠️ Window selection already in progress");
|
|
985
|
+
return Napi::Boolean::New(env, false);
|
|
978
986
|
}
|
|
979
987
|
|
|
980
988
|
@try {
|
|
@@ -1271,14 +1279,20 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
|
|
|
1271
1279
|
Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
|
|
1272
1280
|
Napi::Env env = info.Env();
|
|
1273
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
|
+
|
|
1274
1288
|
if (info.Length() < 1) {
|
|
1275
|
-
|
|
1276
|
-
return env
|
|
1289
|
+
NSLog(@"⚠️ Window info object required");
|
|
1290
|
+
return Napi::Boolean::New(env, false);
|
|
1277
1291
|
}
|
|
1278
1292
|
|
|
1279
1293
|
if (!info[0].IsObject()) {
|
|
1280
|
-
|
|
1281
|
-
return env
|
|
1294
|
+
NSLog(@"⚠️ Window info must be an object");
|
|
1295
|
+
return Napi::Boolean::New(env, false);
|
|
1282
1296
|
}
|
|
1283
1297
|
|
|
1284
1298
|
@try {
|
|
@@ -1336,11 +1350,18 @@ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
|
|
|
1336
1350
|
Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
|
|
1337
1351
|
Napi::Env env = info.Env();
|
|
1338
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
|
+
|
|
1339
1359
|
@try {
|
|
1340
1360
|
bool success = startScreenSelection();
|
|
1341
1361
|
return Napi::Boolean::New(env, success);
|
|
1342
1362
|
|
|
1343
1363
|
} @catch (NSException *exception) {
|
|
1364
|
+
NSLog(@"❌ Screen selection error: %@", exception);
|
|
1344
1365
|
return Napi::Boolean::New(env, false);
|
|
1345
1366
|
}
|
|
1346
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
|
}
|