node-mac-recorder 2.21.3 → 2.21.5
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 +5 -2
- package/package.json +1 -1
- package/src/camera_recorder.mm +19 -13
- package/src/mac_recorder.mm +16 -16
- package/src/screen_capture_kit.mm +63 -56
- package/scripts/test-multi-capture.js +0 -103
- package/test-electron-fix.js +0 -90
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
"Bash(node:*)",
|
|
6
6
|
"Bash(timeout:*)",
|
|
7
7
|
"Bash(open:*)",
|
|
8
|
-
"Read(//Users/onur/codes/**)"
|
|
8
|
+
"Read(//Users/onur/codes/**)",
|
|
9
|
+
"Bash(log show:*)",
|
|
10
|
+
"Bash(MAC_RECORDER_DEBUG=1 node:*)",
|
|
11
|
+
"Read(//private/tmp/test-recording/**)"
|
|
9
12
|
],
|
|
10
13
|
"deny": [],
|
|
11
14
|
"ask": []
|
|
12
15
|
}
|
|
13
|
-
}
|
|
16
|
+
}
|
package/package.json
CHANGED
package/src/camera_recorder.mm
CHANGED
|
@@ -109,34 +109,40 @@ static BOOL MRIsContinuityCamera(AVCaptureDevice *device) {
|
|
|
109
109
|
|
|
110
110
|
+ (NSArray<NSDictionary *> *)availableCameraDevices {
|
|
111
111
|
NSMutableArray<NSDictionary *> *devicesInfo = [NSMutableArray array];
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
NSMutableArray<AVCaptureDeviceType> *deviceTypes = [NSMutableArray array];
|
|
114
114
|
BOOL allowContinuity = MRAllowContinuityCamera();
|
|
115
|
-
|
|
115
|
+
|
|
116
|
+
// Always include built-in and external cameras
|
|
116
117
|
if (@available(macOS 10.15, *)) {
|
|
117
118
|
[deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
|
|
118
119
|
} else {
|
|
119
120
|
[deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
[deviceTypes addObject:AVCaptureDeviceTypeExternalUnknown];
|
|
128
|
-
}
|
|
122
|
+
|
|
123
|
+
// ALWAYS add external cameras - they should be available regardless of Continuity permission
|
|
124
|
+
if (@available(macOS 14.0, *)) {
|
|
125
|
+
[deviceTypes addObject:AVCaptureDeviceTypeExternal];
|
|
126
|
+
} else {
|
|
127
|
+
[deviceTypes addObject:AVCaptureDeviceTypeExternalUnknown];
|
|
129
128
|
}
|
|
130
|
-
|
|
129
|
+
|
|
130
|
+
// ALWAYS add Continuity Camera type - filtering happens later
|
|
131
|
+
if (@available(macOS 14.0, *)) {
|
|
132
|
+
[deviceTypes addObject:AVCaptureDeviceTypeContinuityCamera];
|
|
133
|
+
}
|
|
134
|
+
|
|
131
135
|
AVCaptureDeviceDiscoverySession *discoverySession =
|
|
132
136
|
[AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes
|
|
133
137
|
mediaType:AVMediaTypeVideo
|
|
134
138
|
position:AVCaptureDevicePositionUnspecified];
|
|
135
|
-
|
|
139
|
+
|
|
136
140
|
for (AVCaptureDevice *device in discoverySession.devices) {
|
|
137
141
|
BOOL continuityCamera = MRIsContinuityCamera(device);
|
|
142
|
+
// ONLY skip Continuity cameras when permission is missing
|
|
143
|
+
// Regular USB/external cameras should ALWAYS be listed
|
|
138
144
|
if (continuityCamera && !allowContinuity) {
|
|
139
|
-
|
|
145
|
+
MRLog(@"⏭️ Skipping Continuity Camera (permission required): %@", device.localizedName);
|
|
140
146
|
continue;
|
|
141
147
|
}
|
|
142
148
|
|
package/src/mac_recorder.mm
CHANGED
|
@@ -328,29 +328,29 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
328
328
|
(NSBundle.mainBundle.bundlePath &&
|
|
329
329
|
[NSBundle.mainBundle.bundlePath containsString:@"Electron"]);
|
|
330
330
|
|
|
331
|
-
if (isElectron) {
|
|
332
|
-
MRLog(@"⚡ Electron environment detected - continuing with ScreenCaptureKit");
|
|
333
|
-
MRLog(@"⚠️ Warning: ScreenCaptureKit in Electron may require additional stability measures");
|
|
334
|
-
}
|
|
335
|
-
|
|
336
331
|
// Check macOS version for ScreenCaptureKit compatibility
|
|
337
332
|
NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
|
|
338
333
|
BOOL isM15Plus = (osVersion.majorVersion >= 15);
|
|
339
334
|
BOOL isM14Plus = (osVersion.majorVersion >= 14);
|
|
340
335
|
BOOL isM13Plus = (osVersion.majorVersion >= 13);
|
|
341
|
-
|
|
342
|
-
MRLog(@"🖥️ macOS Version: %ld.%ld.%ld",
|
|
336
|
+
|
|
337
|
+
MRLog(@"🖥️ macOS Version: %ld.%ld.%ld",
|
|
343
338
|
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
MRLog(@"
|
|
339
|
+
|
|
340
|
+
if (isElectron) {
|
|
341
|
+
MRLog(@"⚡ Electron environment detected");
|
|
342
|
+
MRLog(@"🔧 CRITICAL FIX: Forcing AVFoundation for Electron stability");
|
|
343
|
+
MRLog(@" Reason: ScreenCaptureKit has thread safety issues in Electron (SIGTRAP crashes)");
|
|
349
344
|
}
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
345
|
+
|
|
346
|
+
// Force AVFoundation for debugging/testing OR Electron
|
|
347
|
+
BOOL forceAVFoundation = (getenv("FORCE_AVFOUNDATION") != NULL) || isElectron;
|
|
348
|
+
if (getenv("FORCE_AVFOUNDATION") != NULL) {
|
|
349
|
+
MRLog(@"🔧 FORCE_AVFOUNDATION environment variable detected");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Electron-first priority: ALWAYS use AVFoundation in Electron for stability
|
|
353
|
+
// ScreenCaptureKit has severe thread safety issues in Electron causing SIGTRAP crashes
|
|
354
354
|
if (isM15Plus && !forceAVFoundation) {
|
|
355
355
|
if (isElectron) {
|
|
356
356
|
MRLog(@"⚡ ELECTRON PRIORITY: macOS 15+ Electron → ScreenCaptureKit with full support");
|
|
@@ -120,31 +120,33 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
120
120
|
|
|
121
121
|
@implementation PureScreenCaptureDelegate
|
|
122
122
|
- (void)stream:(SCStream * API_AVAILABLE(macos(12.3)))stream didStopWithError:(NSError *)error API_AVAILABLE(macos(12.3)) {
|
|
123
|
-
|
|
123
|
+
// ELECTRON FIX: Run cleanup on background thread to avoid blocking Electron
|
|
124
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
125
|
+
MRLog(@"🛑 Pure ScreenCapture stream stopped");
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
// Prevent recursive calls during cleanup
|
|
128
|
+
if (g_isCleaningUp) {
|
|
129
|
+
MRLog(@"⚠️ Already cleaning up, ignoring delegate callback");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
134
|
+
g_isRecording = NO;
|
|
135
|
+
}
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
if (error) {
|
|
138
|
+
NSLog(@"❌ Stream error: %@", error);
|
|
139
|
+
} else {
|
|
140
|
+
MRLog(@"✅ Stream stopped cleanly");
|
|
141
|
+
}
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
// Finalize on background thread with synchronization
|
|
144
|
+
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
145
|
+
if (!g_isCleaningUp) {
|
|
146
|
+
[ScreenCaptureKitRecorder finalizeRecording];
|
|
147
|
+
}
|
|
146
148
|
}
|
|
147
|
-
}
|
|
149
|
+
});
|
|
148
150
|
}
|
|
149
151
|
@end
|
|
150
152
|
|
|
@@ -460,16 +462,13 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
460
462
|
// Reset any stale state
|
|
461
463
|
g_isCleaningUp = NO;
|
|
462
464
|
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
+
// DON'T set g_isRecording here - wait for stream to actually start
|
|
466
|
+
// This prevents the "recording=1 stream=null" issue
|
|
465
467
|
}
|
|
466
468
|
|
|
467
469
|
NSString *outputPath = config[@"outputPath"];
|
|
468
470
|
if (!outputPath || [outputPath length] == 0) {
|
|
469
471
|
NSLog(@"❌ Invalid output path provided");
|
|
470
|
-
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
471
|
-
g_isRecording = NO;
|
|
472
|
-
}
|
|
473
472
|
return NO;
|
|
474
473
|
}
|
|
475
474
|
g_outputPath = outputPath;
|
|
@@ -495,15 +494,15 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
495
494
|
|
|
496
495
|
// ELECTRON FIX: Get shareable content FULLY ASYNCHRONOUSLY
|
|
497
496
|
// NO semaphores, NO blocking - pure async to prevent Electron crashes
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
497
|
+
// CRITICAL: Run on background queue to avoid blocking Electron's main thread
|
|
498
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
499
|
+
[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *content, NSError *contentError) {
|
|
500
|
+
@autoreleasepool {
|
|
501
|
+
if (contentError) {
|
|
502
|
+
NSLog(@"❌ Content error: %@", contentError);
|
|
503
|
+
// No need to set g_isRecording=NO since it was never set to YES
|
|
504
|
+
return; // Early return from completion handler block
|
|
504
505
|
}
|
|
505
|
-
return; // Early return from completion handler block
|
|
506
|
-
}
|
|
507
506
|
|
|
508
507
|
MRLog(@"✅ Got %lu displays, %lu windows for pure recording",
|
|
509
508
|
content.displays.count, content.windows.count);
|
|
@@ -543,9 +542,7 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
543
542
|
recordingHeight = (NSInteger)targetWindow.frame.size.height;
|
|
544
543
|
} else {
|
|
545
544
|
NSLog(@"❌ Window ID %@ not found", windowId);
|
|
546
|
-
|
|
547
|
-
g_isRecording = NO;
|
|
548
|
-
}
|
|
545
|
+
// No need to set g_isRecording=NO since it was never set to YES
|
|
549
546
|
return; // Early return from completion handler block
|
|
550
547
|
}
|
|
551
548
|
}
|
|
@@ -575,9 +572,7 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
575
572
|
|
|
576
573
|
if (!targetDisplay) {
|
|
577
574
|
NSLog(@"❌ Display not found");
|
|
578
|
-
|
|
579
|
-
g_isRecording = NO;
|
|
580
|
-
}
|
|
575
|
+
// No need to set g_isRecording=NO since it was never set to YES
|
|
581
576
|
return; // Early return from completion handler block
|
|
582
577
|
}
|
|
583
578
|
|
|
@@ -613,7 +608,15 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
613
608
|
BOOL shouldCaptureMic = includeMicrophone ? [includeMicrophone boolValue] : NO;
|
|
614
609
|
BOOL shouldCaptureSystemAudio = includeSystemAudio ? [includeSystemAudio boolValue] : NO;
|
|
615
610
|
g_shouldCaptureAudio = shouldCaptureMic || shouldCaptureSystemAudio;
|
|
616
|
-
|
|
611
|
+
|
|
612
|
+
// SAFETY: Ensure audioOutputPath is NSString, not NSURL or other type
|
|
613
|
+
if (audioOutputPath && ![audioOutputPath isKindOfClass:[NSString class]]) {
|
|
614
|
+
MRLog(@"⚠️ audioOutputPath type mismatch: %@, converting...", NSStringFromClass([audioOutputPath class]));
|
|
615
|
+
g_audioOutputPath = nil;
|
|
616
|
+
} else {
|
|
617
|
+
g_audioOutputPath = audioOutputPath;
|
|
618
|
+
}
|
|
619
|
+
|
|
617
620
|
if (g_shouldCaptureAudio && (!g_audioOutputPath || [g_audioOutputPath length] == 0)) {
|
|
618
621
|
NSLog(@"⚠️ Audio capture requested but no audio output path supplied – audio will be disabled");
|
|
619
622
|
g_shouldCaptureAudio = NO;
|
|
@@ -680,9 +683,7 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
680
683
|
NSError *writerError = nil;
|
|
681
684
|
if (![ScreenCaptureKitRecorder prepareVideoWriterWithWidth:recordingWidth height:recordingHeight error:&writerError]) {
|
|
682
685
|
NSLog(@"❌ Failed to prepare video writer: %@", writerError);
|
|
683
|
-
|
|
684
|
-
g_isRecording = NO;
|
|
685
|
-
}
|
|
686
|
+
// No need to set g_isRecording=NO since it was never set to YES
|
|
686
687
|
return; // Early return from completion handler block
|
|
687
688
|
}
|
|
688
689
|
|
|
@@ -695,29 +696,30 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
695
696
|
g_audioStreamOutput = nil;
|
|
696
697
|
}
|
|
697
698
|
|
|
699
|
+
// Create stream outputs and delegate
|
|
698
700
|
g_streamDelegate = [[PureScreenCaptureDelegate alloc] init];
|
|
699
701
|
g_stream = [[SCStream alloc] initWithFilter:filter configuration:streamConfig delegate:g_streamDelegate];
|
|
700
|
-
|
|
702
|
+
|
|
703
|
+
// Check if stream was created successfully
|
|
701
704
|
if (!g_stream) {
|
|
702
705
|
NSLog(@"❌ Failed to create pure stream");
|
|
703
706
|
CleanupWriters();
|
|
704
|
-
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
705
|
-
g_isRecording = NO;
|
|
706
|
-
}
|
|
707
707
|
return; // Early return from completion handler block
|
|
708
708
|
}
|
|
709
|
-
|
|
709
|
+
|
|
710
|
+
MRLog(@"✅ Stream created successfully");
|
|
711
|
+
|
|
710
712
|
NSError *outputError = nil;
|
|
711
713
|
BOOL videoOutputAdded = [g_stream addStreamOutput:g_videoStreamOutput type:SCStreamOutputTypeScreen sampleHandlerQueue:g_videoQueue error:&outputError];
|
|
712
714
|
if (!videoOutputAdded || outputError) {
|
|
713
715
|
NSLog(@"❌ Failed to add video output: %@", outputError);
|
|
714
716
|
CleanupWriters();
|
|
715
717
|
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
716
|
-
|
|
718
|
+
g_stream = nil;
|
|
717
719
|
}
|
|
718
720
|
return; // Early return from completion handler block
|
|
719
721
|
}
|
|
720
|
-
|
|
722
|
+
|
|
721
723
|
if (g_shouldCaptureAudio) {
|
|
722
724
|
if (@available(macOS 13.0, *)) {
|
|
723
725
|
NSError *audioError = nil;
|
|
@@ -726,7 +728,7 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
726
728
|
NSLog(@"❌ Failed to add audio output: %@", audioError);
|
|
727
729
|
CleanupWriters();
|
|
728
730
|
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
729
|
-
|
|
731
|
+
g_stream = nil;
|
|
730
732
|
}
|
|
731
733
|
return; // Early return from completion handler block
|
|
732
734
|
}
|
|
@@ -735,27 +737,32 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
735
737
|
g_shouldCaptureAudio = NO;
|
|
736
738
|
}
|
|
737
739
|
}
|
|
738
|
-
|
|
740
|
+
|
|
739
741
|
MRLog(@"✅ Stream outputs configured (audio=%d)", g_shouldCaptureAudio);
|
|
740
742
|
if (sessionTimestampNumber) {
|
|
741
743
|
MRLog(@"🕒 Session timestamp: %@", sessionTimestampNumber);
|
|
742
744
|
}
|
|
743
745
|
|
|
744
|
-
//
|
|
746
|
+
// Start capture - can be async
|
|
745
747
|
[g_stream startCaptureWithCompletionHandler:^(NSError *startError) {
|
|
746
748
|
if (startError) {
|
|
747
749
|
NSLog(@"❌ Failed to start pure capture: %@", startError);
|
|
748
750
|
CleanupWriters();
|
|
749
751
|
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
750
752
|
g_isRecording = NO;
|
|
753
|
+
g_stream = nil;
|
|
751
754
|
}
|
|
752
755
|
} else {
|
|
753
756
|
MRLog(@"🎉 PURE ScreenCaptureKit recording started successfully!");
|
|
754
|
-
//
|
|
757
|
+
// NOW set recording flag - stream is actually running
|
|
758
|
+
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
759
|
+
g_isRecording = YES;
|
|
760
|
+
}
|
|
755
761
|
}
|
|
756
|
-
}];
|
|
762
|
+
}]; // End of startCaptureWithCompletionHandler
|
|
757
763
|
} // End of autoreleasepool
|
|
758
|
-
|
|
764
|
+
}]; // End of getShareableContentWithCompletionHandler
|
|
765
|
+
}); // End of dispatch_async
|
|
759
766
|
|
|
760
767
|
// Return immediately - async completion will handle success/failure
|
|
761
768
|
return YES;
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
const MacRecorder = require("../index");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
|
|
5
|
-
async function main() {
|
|
6
|
-
const recorder = new MacRecorder();
|
|
7
|
-
|
|
8
|
-
// Optional: list audio and camera devices for reference
|
|
9
|
-
const audioDevices = await recorder.getAudioDevices();
|
|
10
|
-
const cameraDevices = await recorder.getCameraDevices();
|
|
11
|
-
|
|
12
|
-
console.log("Audio devices:");
|
|
13
|
-
audioDevices.forEach((device, idx) => {
|
|
14
|
-
console.log(`${idx + 1}. ${device.name} (id: ${device.id})`);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
console.log("\nCamera devices:");
|
|
18
|
-
cameraDevices.forEach((device, idx) => {
|
|
19
|
-
console.log(`${idx + 1}. ${device.name} (id: ${device.id})`);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// Pick the first available devices (customize as needed)
|
|
23
|
-
const preferredCamera = cameraDevices.find(device => !device.requiresContinuityCameraPermission);
|
|
24
|
-
const selectedCameraId = preferredCamera ? preferredCamera.id : null;
|
|
25
|
-
if (!selectedCameraId && cameraDevices.length > 0) {
|
|
26
|
-
console.warn("Skipping camera capture: only Continuity Camera devices detected. Add NSCameraUseContinuityCameraDeviceType to Info.plist or set ALLOW_CONTINUITY_CAMERA=1.");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (selectedCameraId) {
|
|
30
|
-
console.log(`\nSelected camera: ${preferredCamera.name} (id: ${selectedCameraId})`);
|
|
31
|
-
} else {
|
|
32
|
-
console.log("\nSelected camera: none (camera capture disabled)");
|
|
33
|
-
}
|
|
34
|
-
const selectedMicId = audioDevices[0]?.id || null;
|
|
35
|
-
|
|
36
|
-
if (selectedCameraId) {
|
|
37
|
-
recorder.setCameraDevice(selectedCameraId);
|
|
38
|
-
recorder.setCameraEnabled(true);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
recorder.setAudioSettings({
|
|
42
|
-
microphone: !!selectedMicId,
|
|
43
|
-
systemAudio: true,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (selectedMicId) {
|
|
47
|
-
recorder.setAudioDevice(selectedMicId);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const outputDir = path.resolve(__dirname, "../tmp-tests");
|
|
51
|
-
if (!fs.existsSync(outputDir)) {
|
|
52
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const outputPath = path.join(outputDir, `test_capture_${Date.now()}.mov`);
|
|
56
|
-
console.log("\nStarting recording to:", outputPath);
|
|
57
|
-
|
|
58
|
-
recorder.on("recordingStarted", (payload) => {
|
|
59
|
-
console.log("recordingStarted", payload);
|
|
60
|
-
});
|
|
61
|
-
recorder.on("cameraCaptureStarted", (payload) => {
|
|
62
|
-
console.log("cameraCaptureStarted", payload);
|
|
63
|
-
});
|
|
64
|
-
recorder.on("audioCaptureStarted", (payload) => {
|
|
65
|
-
console.log("audioCaptureStarted", payload);
|
|
66
|
-
});
|
|
67
|
-
recorder.on("cameraCaptureStopped", (payload) => {
|
|
68
|
-
console.log("cameraCaptureStopped", payload);
|
|
69
|
-
});
|
|
70
|
-
recorder.on("audioCaptureStopped", (payload) => {
|
|
71
|
-
console.log("audioCaptureStopped", payload);
|
|
72
|
-
});
|
|
73
|
-
recorder.on("stopped", (payload) => {
|
|
74
|
-
console.log("stopped", payload);
|
|
75
|
-
});
|
|
76
|
-
recorder.on("completed", (filePath) => {
|
|
77
|
-
console.log("completed", filePath);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
await recorder.startRecording(outputPath, {
|
|
81
|
-
includeMicrophone: !!selectedMicId,
|
|
82
|
-
includeSystemAudio: true,
|
|
83
|
-
captureCursor: true,
|
|
84
|
-
captureCamera: !!selectedCameraId,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
console.log("Recording for 10 seconds...");
|
|
88
|
-
await new Promise((resolve) => setTimeout(resolve, 10_000));
|
|
89
|
-
|
|
90
|
-
const result = await recorder.stopRecording();
|
|
91
|
-
console.log("\nRecording finished:", result);
|
|
92
|
-
|
|
93
|
-
console.log("\nArtifacts:");
|
|
94
|
-
console.log("Video:", result.outputPath);
|
|
95
|
-
console.log("Camera:", result.cameraOutputPath);
|
|
96
|
-
console.log("Audio:", result.audioOutputPath);
|
|
97
|
-
console.log("Session timestamp:", result.sessionTimestamp);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
main().catch((error) => {
|
|
101
|
-
console.error("Test capture failed:", error);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
});
|
package/test-electron-fix.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Test script for Electron crash fix
|
|
4
|
-
* Tests ScreenCaptureKit recording with synchronous semaphore-based approach
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const MacRecorder = require('./index.js');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
|
|
11
|
-
async function testRecording() {
|
|
12
|
-
console.log('🧪 Testing ScreenCaptureKit Electron crash fix...\n');
|
|
13
|
-
|
|
14
|
-
const recorder = new MacRecorder();
|
|
15
|
-
|
|
16
|
-
// Check permissions first
|
|
17
|
-
console.log('1️⃣ Checking permissions...');
|
|
18
|
-
const permissions = await recorder.checkPermissions();
|
|
19
|
-
console.log(' Permissions:', permissions);
|
|
20
|
-
|
|
21
|
-
if (!permissions.screenRecording) {
|
|
22
|
-
console.error('❌ Screen recording permission not granted');
|
|
23
|
-
console.log(' Please enable screen recording in System Settings > Privacy & Security');
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Get displays
|
|
28
|
-
console.log('\n2️⃣ Getting displays...');
|
|
29
|
-
const displays = await recorder.getDisplays();
|
|
30
|
-
console.log(` Found ${displays.length} display(s):`);
|
|
31
|
-
displays.forEach(d => {
|
|
32
|
-
console.log(` - Display ${d.id}: ${d.width}x${d.height} (Primary: ${d.isPrimary})`);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Prepare output path
|
|
36
|
-
const outputDir = path.join(__dirname, 'test-output');
|
|
37
|
-
if (!fs.existsSync(outputDir)) {
|
|
38
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const outputPath = path.join(outputDir, `electron-fix-test-${Date.now()}.mov`);
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
// Start recording
|
|
45
|
-
console.log('\n3️⃣ Starting recording...');
|
|
46
|
-
console.log(` Output: ${outputPath}`);
|
|
47
|
-
|
|
48
|
-
await recorder.startRecording(outputPath, {
|
|
49
|
-
displayId: displays[0].id,
|
|
50
|
-
captureCursor: true,
|
|
51
|
-
includeMicrophone: false,
|
|
52
|
-
includeSystemAudio: false
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
console.log('✅ Recording started successfully!');
|
|
56
|
-
console.log(' Recording for 3 seconds...\n');
|
|
57
|
-
|
|
58
|
-
// Record for 3 seconds
|
|
59
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
60
|
-
|
|
61
|
-
// Stop recording
|
|
62
|
-
console.log('4️⃣ Stopping recording...');
|
|
63
|
-
const result = await recorder.stopRecording();
|
|
64
|
-
console.log('✅ Recording stopped successfully!');
|
|
65
|
-
console.log(' Result:', result);
|
|
66
|
-
|
|
67
|
-
// Check output file
|
|
68
|
-
if (fs.existsSync(outputPath)) {
|
|
69
|
-
const stats = fs.statSync(outputPath);
|
|
70
|
-
console.log(`\n✅ Output file created: ${outputPath}`);
|
|
71
|
-
console.log(` File size: ${(stats.size / 1024).toFixed(2)} KB`);
|
|
72
|
-
} else {
|
|
73
|
-
console.log('\n⚠️ Output file not found (may still be finalizing)');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log('\n🎉 Test completed successfully! No crashes detected.');
|
|
77
|
-
console.log(' The Electron crash fix appears to be working.\n');
|
|
78
|
-
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('\n❌ Test failed:', error.message);
|
|
81
|
-
console.error(' Stack:', error.stack);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Run test
|
|
87
|
-
testRecording().catch(error => {
|
|
88
|
-
console.error('Fatal error:', error);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|