node-mac-recorder 2.21.4 → 2.21.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/.claude/settings.local.json +3 -1
- package/package.json +1 -1
- package/src/camera_recorder.mm +3 -5
- package/src/mac_recorder.mm +16 -16
- package/src/screen_capture_kit.mm +61 -37
- package/scripts/test-multi-capture.js +0 -103
- package/test-electron-fix.js +0 -90
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"Read(//Users/onur/codes/**)",
|
|
9
9
|
"Bash(log show:*)",
|
|
10
10
|
"Bash(MAC_RECORDER_DEBUG=1 node:*)",
|
|
11
|
-
"Read(//private/tmp/test-recording/**)"
|
|
11
|
+
"Read(//private/tmp/test-recording/**)",
|
|
12
|
+
"Bash(MAC_RECORDER_DEBUG=1 timeout 5 node:*)",
|
|
13
|
+
"Read(//private/tmp/**)"
|
|
12
14
|
],
|
|
13
15
|
"deny": [],
|
|
14
16
|
"ask": []
|
package/package.json
CHANGED
package/src/camera_recorder.mm
CHANGED
|
@@ -127,11 +127,9 @@ static BOOL MRIsContinuityCamera(AVCaptureDevice *device) {
|
|
|
127
127
|
[deviceTypes addObject:AVCaptureDeviceTypeExternalUnknown];
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
//
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
[deviceTypes addObject:AVCaptureDeviceTypeContinuityCamera];
|
|
134
|
-
}
|
|
130
|
+
// ALWAYS add Continuity Camera type - filtering happens later
|
|
131
|
+
if (@available(macOS 14.0, *)) {
|
|
132
|
+
[deviceTypes addObject:AVCaptureDeviceTypeContinuityCamera];
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
AVCaptureDeviceDiscoverySession *discoverySession =
|
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");
|
|
@@ -225,10 +225,15 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
225
225
|
|
|
226
226
|
@implementation ScreenCaptureAudioOutput
|
|
227
227
|
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type API_AVAILABLE(macos(12.3)) {
|
|
228
|
+
static dispatch_once_t onceToken;
|
|
229
|
+
dispatch_once(&onceToken, ^{
|
|
230
|
+
MRLog(@"🎤 First audio sample callback received from ScreenCaptureKit");
|
|
231
|
+
});
|
|
232
|
+
|
|
228
233
|
if (!g_isRecording || !g_shouldCaptureAudio) {
|
|
229
234
|
return;
|
|
230
235
|
}
|
|
231
|
-
|
|
236
|
+
|
|
232
237
|
if (@available(macOS 13.0, *)) {
|
|
233
238
|
if (type != SCStreamOutputTypeAudio) {
|
|
234
239
|
return;
|
|
@@ -236,8 +241,9 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
236
241
|
} else {
|
|
237
242
|
return;
|
|
238
243
|
}
|
|
239
|
-
|
|
244
|
+
|
|
240
245
|
if (!CMSampleBufferDataIsReady(sampleBuffer)) {
|
|
246
|
+
MRLog(@"⚠️ Audio sample buffer data not ready");
|
|
241
247
|
return;
|
|
242
248
|
}
|
|
243
249
|
|
|
@@ -263,11 +269,21 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
263
269
|
}
|
|
264
270
|
|
|
265
271
|
if (!g_audioInput.readyForMoreMediaData) {
|
|
272
|
+
static int notReadyCount = 0;
|
|
273
|
+
if (notReadyCount++ % 100 == 0) {
|
|
274
|
+
MRLog(@"⚠️ Audio input not ready for data (count: %d)", notReadyCount);
|
|
275
|
+
}
|
|
266
276
|
return;
|
|
267
277
|
}
|
|
268
|
-
|
|
269
|
-
|
|
278
|
+
|
|
279
|
+
BOOL success = [g_audioInput appendSampleBuffer:sampleBuffer];
|
|
280
|
+
if (!success) {
|
|
270
281
|
NSLog(@"⚠️ Failed appending audio sample buffer: %@", g_audioWriter.error);
|
|
282
|
+
} else {
|
|
283
|
+
static int appendCount = 0;
|
|
284
|
+
if (appendCount++ % 100 == 0) {
|
|
285
|
+
MRLog(@"✅ Audio sample appended successfully (count: %d)", appendCount);
|
|
286
|
+
}
|
|
271
287
|
}
|
|
272
288
|
}
|
|
273
289
|
@end
|
|
@@ -365,13 +381,20 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
365
381
|
[[NSFileManager defaultManager] removeItemAtURL:audioURL error:nil];
|
|
366
382
|
|
|
367
383
|
NSError *writerError = nil;
|
|
384
|
+
// CRITICAL FIX: AVAssetWriter does NOT support WebM for audio
|
|
385
|
+
// Always use QuickTime Movie format (.mov) for audio files
|
|
368
386
|
AVFileType requestedFileType = AVFileTypeQuickTimeMovie;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
387
|
+
|
|
388
|
+
// Ensure path has .mov extension for audio
|
|
389
|
+
NSString *audioPath = originalPath;
|
|
390
|
+
if (![audioPath.pathExtension.lowercaseString isEqualToString:@"mov"]) {
|
|
391
|
+
MRLog(@"⚠️ Audio path has wrong extension '%@', changing to .mov", audioPath.pathExtension);
|
|
392
|
+
audioPath = [[audioPath stringByDeletingPathExtension] stringByAppendingPathExtension:@"mov"];
|
|
393
|
+
g_audioOutputPath = audioPath;
|
|
373
394
|
}
|
|
374
|
-
|
|
395
|
+
audioURL = [NSURL fileURLWithPath:audioPath];
|
|
396
|
+
[[NSFileManager defaultManager] removeItemAtURL:audioURL error:nil];
|
|
397
|
+
|
|
375
398
|
@try {
|
|
376
399
|
g_audioWriter = [[AVAssetWriter alloc] initWithURL:audioURL fileType:requestedFileType error:&writerError];
|
|
377
400
|
} @catch (NSException *exception) {
|
|
@@ -382,28 +405,6 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
382
405
|
g_audioWriter = nil;
|
|
383
406
|
}
|
|
384
407
|
|
|
385
|
-
if ((!g_audioWriter || writerError) && requestedWebM) {
|
|
386
|
-
MRLog(@"⚠️ ScreenCaptureKit audio writer unavailable (%@) – falling back to QuickTime container", writerError.localizedDescription);
|
|
387
|
-
NSString *fallbackPath = [[originalPath stringByDeletingPathExtension] stringByAppendingPathExtension:@"mov"];
|
|
388
|
-
if (!fallbackPath || [fallbackPath length] == 0) {
|
|
389
|
-
fallbackPath = [originalPath stringByAppendingString:@".mov"];
|
|
390
|
-
}
|
|
391
|
-
[[NSFileManager defaultManager] removeItemAtPath:fallbackPath error:nil];
|
|
392
|
-
NSURL *fallbackURL = [NSURL fileURLWithPath:fallbackPath];
|
|
393
|
-
g_audioOutputPath = fallbackPath;
|
|
394
|
-
writerError = nil;
|
|
395
|
-
@try {
|
|
396
|
-
g_audioWriter = [[AVAssetWriter alloc] initWithURL:fallbackURL fileType:AVFileTypeQuickTimeMovie error:&writerError];
|
|
397
|
-
} @catch (NSException *exception) {
|
|
398
|
-
NSDictionary *info = @{
|
|
399
|
-
NSLocalizedDescriptionKey: exception.reason ?: @"Failed to initialize audio writer"
|
|
400
|
-
};
|
|
401
|
-
writerError = [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-202 userInfo:info];
|
|
402
|
-
g_audioWriter = nil;
|
|
403
|
-
}
|
|
404
|
-
audioURL = fallbackURL;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
408
|
if (!g_audioWriter || writerError) {
|
|
408
409
|
NSLog(@"❌ Failed to create audio writer: %@", writerError);
|
|
409
410
|
return NO;
|
|
@@ -433,7 +434,10 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
433
434
|
|
|
434
435
|
g_audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
|
|
435
436
|
g_audioInput.expectsMediaDataInRealTime = YES;
|
|
436
|
-
|
|
437
|
+
|
|
438
|
+
MRLog(@"🎙️ Audio input created: sampleRate=%ld, channels=%ld, bitrate=192k",
|
|
439
|
+
(long)g_configuredSampleRate, (long)channelCount);
|
|
440
|
+
|
|
437
441
|
if (![g_audioWriter canAddInput:g_audioInput]) {
|
|
438
442
|
NSLog(@"❌ Audio writer cannot add input");
|
|
439
443
|
return NO;
|
|
@@ -441,7 +445,8 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
441
445
|
[g_audioWriter addInput:g_audioInput];
|
|
442
446
|
g_audioWriterStarted = NO;
|
|
443
447
|
g_audioStartTime = kCMTimeInvalid;
|
|
444
|
-
|
|
448
|
+
|
|
449
|
+
MRLog(@"✅ Audio writer prepared successfully (path: %@)", g_audioOutputPath);
|
|
445
450
|
return YES;
|
|
446
451
|
}
|
|
447
452
|
|
|
@@ -608,24 +613,43 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
608
613
|
BOOL shouldCaptureMic = includeMicrophone ? [includeMicrophone boolValue] : NO;
|
|
609
614
|
BOOL shouldCaptureSystemAudio = includeSystemAudio ? [includeSystemAudio boolValue] : NO;
|
|
610
615
|
g_shouldCaptureAudio = shouldCaptureMic || shouldCaptureSystemAudio;
|
|
611
|
-
|
|
616
|
+
|
|
617
|
+
// SAFETY: Ensure audioOutputPath is NSString, not NSURL or other type
|
|
618
|
+
if (audioOutputPath && ![audioOutputPath isKindOfClass:[NSString class]]) {
|
|
619
|
+
MRLog(@"⚠️ audioOutputPath type mismatch: %@, converting...", NSStringFromClass([audioOutputPath class]));
|
|
620
|
+
g_audioOutputPath = nil;
|
|
621
|
+
} else {
|
|
622
|
+
g_audioOutputPath = audioOutputPath;
|
|
623
|
+
}
|
|
624
|
+
|
|
612
625
|
if (g_shouldCaptureAudio && (!g_audioOutputPath || [g_audioOutputPath length] == 0)) {
|
|
613
626
|
NSLog(@"⚠️ Audio capture requested but no audio output path supplied – audio will be disabled");
|
|
614
627
|
g_shouldCaptureAudio = NO;
|
|
615
628
|
}
|
|
616
629
|
|
|
617
630
|
if (@available(macos 13.0, *)) {
|
|
631
|
+
// capturesAudio enables audio capture (both mic and system audio)
|
|
618
632
|
streamConfig.capturesAudio = g_shouldCaptureAudio;
|
|
619
|
-
streamConfig.sampleRate = g_configuredSampleRate;
|
|
620
|
-
streamConfig.channelCount = g_configuredChannelCount;
|
|
633
|
+
streamConfig.sampleRate = g_configuredSampleRate ?: 48000;
|
|
634
|
+
streamConfig.channelCount = g_configuredChannelCount ?: 2;
|
|
635
|
+
|
|
636
|
+
// excludesCurrentProcessAudio = YES means ONLY microphone
|
|
637
|
+
// excludesCurrentProcessAudio = NO means system audio + mic
|
|
621
638
|
streamConfig.excludesCurrentProcessAudio = !shouldCaptureSystemAudio;
|
|
639
|
+
|
|
640
|
+
MRLog(@"🎤 Audio config (macOS 13+): capturesAudio=%d, excludeProcess=%d (mic=%d sys=%d)",
|
|
641
|
+
g_shouldCaptureAudio, streamConfig.excludesCurrentProcessAudio,
|
|
642
|
+
shouldCaptureMic, shouldCaptureSystemAudio);
|
|
622
643
|
}
|
|
623
|
-
|
|
644
|
+
|
|
624
645
|
if (@available(macos 15.0, *)) {
|
|
646
|
+
// macOS 15+ has explicit microphone control
|
|
625
647
|
streamConfig.captureMicrophone = shouldCaptureMic;
|
|
626
648
|
if (microphoneDeviceId && microphoneDeviceId.length > 0) {
|
|
627
649
|
streamConfig.microphoneCaptureDeviceID = microphoneDeviceId;
|
|
628
650
|
}
|
|
651
|
+
MRLog(@"🎤 Microphone (macOS 15+): enabled=%d, deviceID=%@",
|
|
652
|
+
shouldCaptureMic, microphoneDeviceId ?: @"default");
|
|
629
653
|
}
|
|
630
654
|
|
|
631
655
|
// Apply crop area using sourceRect - CONVERT GLOBAL TO DISPLAY-RELATIVE COORDINATES
|
|
@@ -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
|
-
});
|