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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.4.5",
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
  }
@@ -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
- Napi::TypeError::New(env, "Window selection already in progress").ThrowAsJavaScriptException();
977
- return env.Null();
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
- Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1276
- return env.Null();
1289
+ NSLog(@"⚠️ Window info object required");
1290
+ return Napi::Boolean::New(env, false);
1277
1291
  }
1278
1292
 
1279
1293
  if (!info[0].IsObject()) {
1280
- Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1281
- return env.Null();
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
  }
@@ -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
  }