node-mac-recorder 2.16.12 โ†’ 2.16.14

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
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\")"
4
+ "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing AVFoundation area recording...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\n// Test specific area recording (top-left 500x500)\nconst options = {\n captureArea: {\n x: 100,\n y: 100, \n width: 500,\n height: 500\n }\n};\n\nrecorder.startRecording(''/tmp/area-test.mov'', options)\n .then(success => {\n console.log(''Area recording:'', success ? ''โœ… SUCCESS'' : ''โŒ FAILED'');\n if (success) {\n setTimeout(() => {\n recorder.stopRecording().then(() => {\n console.log(''โœ… Area recording complete'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/area-test.mov'')) {\n const size = Math.round(fs.statSync(''/tmp/area-test.mov'').size/1024);\n console.log(''๐Ÿ“น File size:'', size + ''KB'');\n }\n });\n }, 2000);\n }\n })\n .catch(console.error);\n\")"
5
5
  ],
6
6
  "deny": [],
7
7
  "ask": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.12",
3
+ "version": "2.16.14",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -53,9 +53,14 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
53
53
  return false;
54
54
  }
55
55
 
56
- // Get display dimensions
56
+ // Get display dimensions - use width/height directly to avoid coordinate issues
57
57
  CGRect displayBounds = CGDisplayBounds(displayID);
58
- CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : displayBounds.size;
58
+ CGSize displaySize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
59
+ CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : displaySize;
60
+
61
+ NSLog(@"๐Ÿ–ฅ๏ธ Display bounds: %.0f,%.0f %.0fx%.0f", displayBounds.origin.x, displayBounds.origin.y, displayBounds.size.width, displayBounds.size.height);
62
+ NSLog(@"๐Ÿ–ฅ๏ธ Display pixels: %.0fx%.0f", displaySize.width, displaySize.height);
63
+ NSLog(@"๐ŸŽฏ Recording size: %.0fx%.0f", recordingSize.width, recordingSize.height);
59
64
 
60
65
  // Video settings with macOS compatibility
61
66
  NSString *codecKey;
@@ -135,9 +140,20 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
135
140
  uint64_t interval = NSEC_PER_SEC / 10; // 10 FPS for Electron stability
136
141
  dispatch_source_set_timer(g_avTimer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, interval / 10);
137
142
 
143
+ // Retain objects before passing to block to prevent deallocation
144
+ AVAssetWriterInput *localVideoInput = g_avVideoInput;
145
+ AVAssetWriterInputPixelBufferAdaptor *localPixelBufferAdaptor = g_avPixelBufferAdaptor;
146
+
138
147
  dispatch_source_set_event_handler(g_avTimer, ^{
139
148
  if (!g_avIsRecording) return;
140
149
 
150
+ // Additional null checks for Electron safety
151
+ if (!localVideoInput || !localPixelBufferAdaptor) {
152
+ NSLog(@"โš ๏ธ Video input or pixel buffer adaptor is nil, stopping recording");
153
+ g_avIsRecording = false;
154
+ return;
155
+ }
156
+
141
157
  @autoreleasepool {
142
158
  @try {
143
159
  // Capture screen with Electron-safe error handling
@@ -159,9 +175,19 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
159
175
 
160
176
  // Convert to pixel buffer with Electron-safe error handling
161
177
  CVPixelBufferRef pixelBuffer = nil;
162
- CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, g_avPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
178
+ CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, localPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
163
179
 
164
180
  if (cvRet == kCVReturnSuccess && pixelBuffer) {
181
+ // Check pixel buffer dimensions match screen image
182
+ size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
183
+ size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
184
+ size_t imageWidth = CGImageGetWidth(screenImage);
185
+ size_t imageHeight = CGImageGetHeight(screenImage);
186
+
187
+ if (bufferWidth != imageWidth || bufferHeight != imageHeight) {
188
+ NSLog(@"โš ๏ธ Size mismatch! Buffer %zux%zu vs Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
189
+ }
190
+
165
191
  CVPixelBufferLockBaseAddress(pixelBuffer, 0);
166
192
 
167
193
  void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
@@ -201,9 +227,9 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
201
227
  CGContextRelease(context);
202
228
 
203
229
  // Write frame only if input is ready
204
- if (g_avVideoInput && g_avVideoInput.readyForMoreMediaData) {
230
+ if (localVideoInput && localVideoInput.readyForMoreMediaData) {
205
231
  CMTime frameTime = CMTimeAdd(g_avStartTime, CMTimeMakeWithSeconds(g_avFrameNumber / 10.0, 600));
206
- BOOL appendSuccess = [g_avPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
232
+ BOOL appendSuccess = [localPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
207
233
  if (appendSuccess) {
208
234
  g_avFrameNumber++;
209
235
  } else {
@@ -253,22 +279,36 @@ extern "C" bool stopAVFoundationRecording() {
253
279
  @try {
254
280
  // Stop timer with Electron-safe cleanup
255
281
  if (g_avTimer) {
282
+ // Mark as not recording FIRST to stop timer callbacks
283
+ g_avIsRecording = false;
284
+
285
+ // Cancel timer and wait a brief moment for completion
256
286
  dispatch_source_cancel(g_avTimer);
287
+
288
+ // Use async to avoid deadlock in Electron
289
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
290
+ // Timer should be fully cancelled by now
291
+ });
292
+
257
293
  g_avTimer = nil;
258
294
  NSLog(@"โœ… AVFoundation timer stopped safely");
259
295
  }
260
296
 
261
- // Finish writing
262
- if (g_avVideoInput) {
263
- [g_avVideoInput markAsFinished];
297
+ // Finish writing with null checks
298
+ AVAssetWriterInput *writerInput = g_avVideoInput;
299
+ if (writerInput) {
300
+ [writerInput markAsFinished];
264
301
  }
265
302
 
266
- if (g_avWriter && g_avWriter.status == AVAssetWriterStatusWriting) {
303
+ AVAssetWriter *writer = g_avWriter;
304
+ if (writer && writer.status == AVAssetWriterStatusWriting) {
267
305
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
268
- [g_avWriter finishWritingWithCompletionHandler:^{
306
+ [writer finishWritingWithCompletionHandler:^{
269
307
  dispatch_semaphore_signal(semaphore);
270
308
  }];
271
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
309
+ // Add timeout to prevent infinite wait in Electron
310
+ dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
311
+ dispatch_semaphore_wait(semaphore, timeout);
272
312
  }
273
313
 
274
314
  // Cleanup
@@ -875,25 +875,32 @@ void updateOverlay() {
875
875
  }
876
876
 
877
877
  // Determine if this is a primary display window
878
- BOOL isPrimaryDisplayWindow = (x >= 0 && x <= 2048); // Primary display width
878
+ NSArray *allScreens = [NSScreen screens];
879
+ NSScreen *primaryScreen = [allScreens objectAtIndex:0]; // Primary screen
880
+ NSRect primaryFrame = [primaryScreen frame];
881
+ BOOL isPrimaryDisplayWindow = (x >= primaryFrame.origin.x &&
882
+ x <= primaryFrame.origin.x + primaryFrame.size.width &&
883
+ y >= primaryFrame.origin.y &&
884
+ y <= primaryFrame.origin.y + primaryFrame.size.height);
879
885
 
880
886
  CGFloat localX, localY;
881
887
  if (isPrimaryDisplayWindow) {
882
- // Primary display windows: Offset to their position in the combined overlay
883
- // Primary display starts at (3440, 56) within the combined frame
884
- localX = x + 3440; // Primary starts 3440px from overlay origin
885
- localY = ([g_overlayView frame].size.height - (y + 56)) - height; // Y offset for primary
888
+ // Primary display windows: Calculate dynamic offset from combined frame
889
+ NSRect combinedFrame = [g_overlayWindow frame];
890
+
891
+ // Calculate primary screen offset within combined frame
892
+ CGFloat primaryOffsetX = primaryFrame.origin.x - combinedFrame.origin.x;
893
+ CGFloat primaryOffsetY = primaryFrame.origin.y - combinedFrame.origin.y;
894
+
895
+ localX = x + primaryOffsetX;
896
+ localY = ([g_overlayView frame].size.height - (y + primaryOffsetY)) - height;
897
+
886
898
  } else {
887
899
  // Secondary display windows: Apply standard coordinate transformation
888
900
  localX = x - globalOffset.x;
889
901
  localY = ([g_overlayView frame].size.height - (y - globalOffset.y)) - height;
890
902
  }
891
903
 
892
- NSLog(@"๐Ÿ”ง COORDINATE DEBUG: Window (%d, %d) %dx%d [%@]", (int)x, (int)y, (int)width, (int)height, isPrimaryDisplayWindow ? @"PRIMARY" : @"SECONDARY");
893
- NSLog(@" GlobalOffset: (%.0f, %.0f)", globalOffset.x, globalOffset.y);
894
- NSLog(@" LocalCoords: (%.0f, %.0f)", localX, localY);
895
- NSLog(@" ViewFrame: %.0fx%.0f", [g_overlayView frame].size.width, [g_overlayView frame].size.height);
896
-
897
904
  // Update overlay view window info for highlighting
898
905
  [overlayView setWindowInfo:targetWindow];
899
906
  [overlayView setHighlightFrame:NSMakeRect(localX, localY, width, height)];