node-mac-recorder 2.15.0 โ†’ 2.15.1

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.
@@ -31,7 +31,9 @@
31
31
  "Bash(ffmpeg:*)",
32
32
  "WebSearch",
33
33
  "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ” Testing with proper permissions and Electron env'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function test() {\n try {\n const outputPath = ''./test-output/proper-test.mov'';\n console.log(''๐Ÿ“น Starting recording...'');\n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: false\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 2 seconds'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โœ… Test completed'');\n } else {\n console.log(''โŒ Recording start failed'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\ntest();\n\")",
34
- "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''''๐Ÿ” Debugging frame writing...'''');\nconst MacRecorder = require(''''./index'''');\nconst recorder = new MacRecorder();\n\nasync function debugFrameWriting() {\n try {\n const outputPath = ''''./test-output/frame-debug.mov'''';\n console.log(''''๐Ÿ“น Starting debug test...'''');\n \n const success = await recorder.startRecording(outputPath);\n \n if (success) {\n console.log(''''โฑ๏ธ Recording for 2 seconds...'''');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n console.log(''''๐Ÿ›‘ Stopping...'''');\n await recorder.stopRecording();\n \n // Wait for finalization\n await new Promise(resolve => setTimeout(resolve, 1000));\n \n } else {\n console.log(''''โŒ Failed to start'''');\n }\n } catch (error) {\n console.log(''''โŒ Error:'''', error);\n }\n}\n\ndebugFrameWriting();\n\")"
34
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''''๐Ÿ” Debugging frame writing...'''');\nconst MacRecorder = require(''''./index'''');\nconst recorder = new MacRecorder();\n\nasync function debugFrameWriting() {\n try {\n const outputPath = ''''./test-output/frame-debug.mov'''';\n console.log(''''๐Ÿ“น Starting debug test...'''');\n \n const success = await recorder.startRecording(outputPath);\n \n if (success) {\n console.log(''''โฑ๏ธ Recording for 2 seconds...'''');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n console.log(''''๐Ÿ›‘ Stopping...'''');\n await recorder.stopRecording();\n \n // Wait for finalization\n await new Promise(resolve => setTimeout(resolve, 1000));\n \n } else {\n console.log(''''โŒ Failed to start'''');\n }\n } catch (error) {\n console.log(''''โŒ Error:'''', error);\n }\n}\n\ndebugFrameWriting();\n\")",
35
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ” Testing with proper permissions and Electron env'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function test() {\n try {\n const outputPath = ''./test-output/crash-test.mov'';\n console.log(''๐Ÿ“น Starting recording...'');\n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: false\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โœ… Test completed without crash'');\n } else {\n console.log(''โŒ Recording start failed'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntest();\n\")",
36
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ” Debugging frame writing...'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function debugFrameWriting() {\n try {\n const outputPath = ''./test-output/frame-debug.mov'';\n console.log(''๐Ÿ“น Starting debug test...'');\n \n const success = await recorder.startRecording(outputPath);\n \n if (success) {\n console.log(''โฑ๏ธ Recording for 2 seconds...'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n console.log(''๐Ÿ›‘ Stopping...'');\n await recorder.stopRecording();\n \n // Wait for finalization\n await new Promise(resolve => setTimeout(resolve, 1000));\n \n } else {\n console.log(''โŒ Failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error);\n }\n}\n\ndebugFrameWriting();\n\")"
35
37
  ],
36
38
  "deny": []
37
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.15.0",
3
+ "version": "2.15.1",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -5,6 +5,7 @@ static SCStream * API_AVAILABLE(macos(12.3)) g_stream = nil;
5
5
  static SCRecordingOutput * API_AVAILABLE(macos(15.0)) g_recordingOutput = nil;
6
6
  static id<SCStreamDelegate> API_AVAILABLE(macos(12.3)) g_streamDelegate = nil;
7
7
  static BOOL g_isRecording = NO;
8
+ static BOOL g_isCleaningUp = NO; // Prevent recursive cleanup
8
9
  static NSString *g_outputPath = nil;
9
10
 
10
11
  @interface PureScreenCaptureDelegate : NSObject <SCStreamDelegate>
@@ -13,6 +14,13 @@ static NSString *g_outputPath = nil;
13
14
  @implementation PureScreenCaptureDelegate
14
15
  - (void)stream:(SCStream * API_AVAILABLE(macos(12.3)))stream didStopWithError:(NSError *)error API_AVAILABLE(macos(12.3)) {
15
16
  NSLog(@"๐Ÿ›‘ Pure ScreenCapture stream stopped");
17
+
18
+ // Prevent recursive calls during cleanup
19
+ if (g_isCleaningUp) {
20
+ NSLog(@"โš ๏ธ Already cleaning up, ignoring delegate callback");
21
+ return;
22
+ }
23
+
16
24
  g_isRecording = NO;
17
25
 
18
26
  if (error) {
@@ -21,7 +29,12 @@ static NSString *g_outputPath = nil;
21
29
  NSLog(@"โœ… Stream stopped cleanly");
22
30
  }
23
31
 
24
- [ScreenCaptureKitRecorder finalizeRecording];
32
+ // Use dispatch_async to prevent potential deadlocks in Electron
33
+ dispatch_async(dispatch_get_main_queue(), ^{
34
+ if (!g_isCleaningUp) { // Double-check before finalizing
35
+ [ScreenCaptureKitRecorder finalizeRecording];
36
+ }
37
+ });
25
38
  }
26
39
  @end
27
40
 
@@ -35,9 +48,14 @@ static NSString *g_outputPath = nil;
35
48
  }
36
49
 
37
50
  + (BOOL)startRecordingWithConfiguration:(NSDictionary *)config delegate:(id)delegate error:(NSError **)error {
38
- if (g_isRecording) {
39
- NSLog(@"โš ๏ธ Already recording");
40
- return NO;
51
+ @synchronized([ScreenCaptureKitRecorder class]) {
52
+ if (g_isRecording || g_isCleaningUp) {
53
+ NSLog(@"โš ๏ธ Already recording or cleaning up (recording:%d cleaning:%d)", g_isRecording, g_isCleaningUp);
54
+ return NO;
55
+ }
56
+
57
+ // Reset any stale state
58
+ g_isCleaningUp = NO;
41
59
  }
42
60
 
43
61
  g_outputPath = config[@"outputPath"];
@@ -247,18 +265,29 @@ static NSString *g_outputPath = nil;
247
265
  }
248
266
 
249
267
  + (void)stopRecording {
250
- if (!g_isRecording || !g_stream) {
268
+ if (!g_isRecording || !g_stream || g_isCleaningUp) {
269
+ NSLog(@"โš ๏ธ Cannot stop: recording=%d stream=%@ cleaning=%d", g_isRecording, g_stream, g_isCleaningUp);
251
270
  return;
252
271
  }
253
272
 
254
273
  NSLog(@"๐Ÿ›‘ Stopping pure ScreenCaptureKit recording");
274
+ g_isCleaningUp = YES;
275
+
276
+ // Store stream reference to prevent it from being deallocated
277
+ SCStream *streamToStop = g_stream;
255
278
 
256
- [g_stream stopCaptureWithCompletionHandler:^(NSError *error) {
279
+ [streamToStop stopCaptureWithCompletionHandler:^(NSError *error) {
257
280
  if (error) {
258
281
  NSLog(@"โŒ Stop error: %@", error);
259
282
  }
260
283
  NSLog(@"โœ… Pure stream stopped");
261
- [ScreenCaptureKitRecorder finalizeRecording];
284
+
285
+ // Finalize on main queue to prevent threading issues
286
+ dispatch_async(dispatch_get_main_queue(), ^{
287
+ if (g_isCleaningUp) { // Only finalize if we initiated cleanup
288
+ [ScreenCaptureKitRecorder finalizeRecording];
289
+ }
290
+ });
262
291
  }];
263
292
  }
264
293
 
@@ -272,16 +301,23 @@ static NSString *g_outputPath = nil;
272
301
  }
273
302
 
274
303
  + (void)finalizeRecording {
275
- NSLog(@"๐ŸŽฌ Finalizing pure ScreenCaptureKit recording");
276
-
277
- g_isRecording = NO;
278
-
279
- if (g_recordingOutput) {
280
- // SCRecordingOutput finalizes automatically
281
- NSLog(@"โœ… Pure recording output finalized");
304
+ @synchronized([ScreenCaptureKitRecorder class]) {
305
+ if (g_isCleaningUp && g_isRecording == NO) {
306
+ NSLog(@"โš ๏ธ Already finalizing, skipping duplicate call");
307
+ return;
308
+ }
309
+
310
+ NSLog(@"๐ŸŽฌ Finalizing pure ScreenCaptureKit recording");
311
+
312
+ g_isRecording = NO;
313
+
314
+ if (g_recordingOutput) {
315
+ // SCRecordingOutput finalizes automatically
316
+ NSLog(@"โœ… Pure recording output finalized");
317
+ }
318
+
319
+ [ScreenCaptureKitRecorder cleanupVideoWriter];
282
320
  }
283
-
284
- [ScreenCaptureKitRecorder cleanupVideoWriter];
285
321
  }
286
322
 
287
323
  + (void)finalizeVideoWriter {
@@ -290,13 +326,31 @@ static NSString *g_outputPath = nil;
290
326
  }
291
327
 
292
328
  + (void)cleanupVideoWriter {
293
- g_stream = nil;
294
- g_recordingOutput = nil;
295
- g_streamDelegate = nil;
296
- g_isRecording = NO;
297
- g_outputPath = nil;
298
-
299
- NSLog(@"๐Ÿงน Pure ScreenCaptureKit cleanup complete");
329
+ @synchronized([ScreenCaptureKitRecorder class]) {
330
+ NSLog(@"๐Ÿงน Starting ScreenCaptureKit cleanup");
331
+
332
+ // Clean up in proper order to prevent crashes
333
+ if (g_stream) {
334
+ g_stream = nil;
335
+ NSLog(@"โœ… Stream reference cleared");
336
+ }
337
+
338
+ if (g_recordingOutput) {
339
+ g_recordingOutput = nil;
340
+ NSLog(@"โœ… Recording output reference cleared");
341
+ }
342
+
343
+ if (g_streamDelegate) {
344
+ g_streamDelegate = nil;
345
+ NSLog(@"โœ… Stream delegate reference cleared");
346
+ }
347
+
348
+ g_isRecording = NO;
349
+ g_isCleaningUp = NO; // Reset cleanup flag
350
+ g_outputPath = nil;
351
+
352
+ NSLog(@"๐Ÿงน Pure ScreenCaptureKit cleanup complete");
353
+ }
300
354
  }
301
355
 
302
356
  @end