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.
- package/.claude/settings.local.json +3 -1
- package/package.json +1 -1
- package/src/screen_capture_kit.mm +77 -23
|
@@ -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
|
@@ -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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
[
|
|
279
|
+
[streamToStop stopCaptureWithCompletionHandler:^(NSError *error) {
|
|
257
280
|
if (error) {
|
|
258
281
|
NSLog(@"โ Stop error: %@", error);
|
|
259
282
|
}
|
|
260
283
|
NSLog(@"โ
Pure stream stopped");
|
|
261
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
NSLog(@"
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|