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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
- BOOL success = [self.videoInput appendSampleBuffer:sampleBuffer];
92
- NSLog(@"📺 Video sample buffer appended: %@", success ? @"SUCCESS" : @"FAILED");
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
  }
@@ -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
- cleanupWindowSelector();
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
- NSLog(@"🖥️ SCREEN BUTTON CLICKED: %@ (%@)",
239
- [screenInfo objectForKey:@"name"],
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
- cleanupScreenSelector();
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
- cleanupWindowSelector();
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
- Napi::TypeError::New(env, "Window selection already in progress").ThrowAsJavaScriptException();
976
- return env.Null();
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
- Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1275
- return env.Null();
1289
+ NSLog(@"⚠️ Window info object required");
1290
+ return Napi::Boolean::New(env, false);
1276
1291
  }
1277
1292
 
1278
1293
  if (!info[0].IsObject()) {
1279
- Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1280
- return env.Null();
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
  }
@@ -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
  }