node-mac-recorder 2.21.6 → 2.21.8
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/audio_recorder.mm +33 -0
- package/src/avfoundation_recorder.mm +51 -3
- package/src/mac_recorder.mm +12 -4
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"Bash(MAC_RECORDER_DEBUG=1 node:*)",
|
|
11
11
|
"Read(//private/tmp/test-recording/**)",
|
|
12
12
|
"Bash(MAC_RECORDER_DEBUG=1 timeout 5 node:*)",
|
|
13
|
-
"Read(//private/tmp/**)"
|
|
13
|
+
"Read(//private/tmp/**)",
|
|
14
|
+
"Bash(ffprobe:*)",
|
|
15
|
+
"Bash(MAC_RECORDER_DEBUG=1 timeout 8 node:*)"
|
|
14
16
|
],
|
|
15
17
|
"deny": [],
|
|
16
18
|
"ask": []
|
package/package.json
CHANGED
package/src/audio_recorder.mm
CHANGED
|
@@ -369,4 +369,37 @@ NSString *currentStandaloneAudioRecordingPath() {
|
|
|
369
369
|
return g_audioRecorder.outputPath;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
// C API for AVFoundation integration
|
|
373
|
+
void* createNativeAudioRecorder() {
|
|
374
|
+
return (__bridge_retained void*)[[NativeAudioRecorder alloc] init];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
bool startNativeAudioRecording(void* recorder, const char* deviceId, const char* outputPath) {
|
|
378
|
+
if (!recorder || !outputPath) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
NativeAudioRecorder* audioRecorder = (__bridge NativeAudioRecorder*)recorder;
|
|
383
|
+
NSString* deviceIdStr = deviceId ? [NSString stringWithUTF8String:deviceId] : nil;
|
|
384
|
+
NSString* outputPathStr = [NSString stringWithUTF8String:outputPath];
|
|
385
|
+
|
|
386
|
+
NSError* error = nil;
|
|
387
|
+
return [audioRecorder startRecordingWithDeviceId:deviceIdStr outputPath:outputPathStr error:&error];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
bool stopNativeAudioRecording(void* recorder) {
|
|
391
|
+
if (!recorder) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
NativeAudioRecorder* audioRecorder = (__bridge NativeAudioRecorder*)recorder;
|
|
396
|
+
return [audioRecorder stopRecording];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
void destroyNativeAudioRecorder(void* recorder) {
|
|
400
|
+
if (recorder) {
|
|
401
|
+
CFRelease(recorder);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
372
405
|
}
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
#include <string>
|
|
8
8
|
#import "logging.h"
|
|
9
9
|
|
|
10
|
+
// Import audio recorder
|
|
11
|
+
extern "C" void* createNativeAudioRecorder(void);
|
|
12
|
+
extern "C" bool startNativeAudioRecording(void* recorder, const char* deviceId, const char* outputPath);
|
|
13
|
+
extern "C" bool stopNativeAudioRecording(void* recorder);
|
|
14
|
+
extern "C" void destroyNativeAudioRecorder(void* recorder);
|
|
15
|
+
|
|
10
16
|
static AVAssetWriter *g_avWriter = nil;
|
|
11
17
|
static AVAssetWriterInput *g_avVideoInput = nil;
|
|
12
18
|
static AVAssetWriterInputPixelBufferAdaptor *g_avPixelBufferAdaptor = nil;
|
|
@@ -16,6 +22,8 @@ static CGRect g_avCaptureRect = CGRectZero;
|
|
|
16
22
|
static bool g_avIsRecording = false;
|
|
17
23
|
static int64_t g_avFrameNumber = 0;
|
|
18
24
|
static CMTime g_avStartTime;
|
|
25
|
+
static void* g_avAudioRecorder = nil;
|
|
26
|
+
static NSString* g_avAudioOutputPath = nil;
|
|
19
27
|
|
|
20
28
|
// AVFoundation screen recording implementation
|
|
21
29
|
extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
@@ -197,7 +205,38 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
197
205
|
}
|
|
198
206
|
|
|
199
207
|
g_avFrameNumber = 0;
|
|
200
|
-
|
|
208
|
+
|
|
209
|
+
// Start audio recording if requested
|
|
210
|
+
if (includeMicrophone || includeSystemAudio) {
|
|
211
|
+
MRLog(@"🎤 Starting audio capture (mic=%d, system=%d)", includeMicrophone, includeSystemAudio);
|
|
212
|
+
|
|
213
|
+
// Generate audio output path
|
|
214
|
+
NSString *videoDir = [outputPathStr stringByDeletingLastPathComponent];
|
|
215
|
+
NSString *audioFilename = [NSString stringWithFormat:@"avf_audio_%ld.mov", (long)[[NSDate date] timeIntervalSince1970]];
|
|
216
|
+
g_avAudioOutputPath = [videoDir stringByAppendingPathComponent:audioFilename];
|
|
217
|
+
|
|
218
|
+
MRLog(@"🎤 Audio output: %@", g_avAudioOutputPath);
|
|
219
|
+
|
|
220
|
+
// Create audio recorder
|
|
221
|
+
g_avAudioRecorder = createNativeAudioRecorder();
|
|
222
|
+
|
|
223
|
+
if (g_avAudioRecorder) {
|
|
224
|
+
const char* deviceIdCStr = audioDeviceId ? [audioDeviceId UTF8String] : NULL;
|
|
225
|
+
const char* outputPathCStr = [g_avAudioOutputPath UTF8String];
|
|
226
|
+
|
|
227
|
+
if (startNativeAudioRecording(g_avAudioRecorder, deviceIdCStr, outputPathCStr)) {
|
|
228
|
+
MRLog(@"✅ Audio recording started");
|
|
229
|
+
} else {
|
|
230
|
+
NSLog(@"❌ Failed to start audio recording");
|
|
231
|
+
destroyNativeAudioRecorder(g_avAudioRecorder);
|
|
232
|
+
g_avAudioRecorder = nil;
|
|
233
|
+
g_avAudioOutputPath = nil;
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
NSLog(@"❌ Failed to create audio recorder");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
201
240
|
// Start capture timer (10 FPS for Electron compatibility)
|
|
202
241
|
dispatch_queue_t captureQueue = dispatch_queue_create("AVFoundationCaptureQueue", DISPATCH_QUEUE_SERIAL);
|
|
203
242
|
g_avTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, captureQueue);
|
|
@@ -348,9 +387,18 @@ extern "C" bool stopAVFoundationRecording() {
|
|
|
348
387
|
if (!g_avIsRecording) {
|
|
349
388
|
return true;
|
|
350
389
|
}
|
|
351
|
-
|
|
390
|
+
|
|
352
391
|
g_avIsRecording = false;
|
|
353
|
-
|
|
392
|
+
|
|
393
|
+
// Stop audio recording if active
|
|
394
|
+
if (g_avAudioRecorder) {
|
|
395
|
+
MRLog(@"🛑 Stopping audio recording");
|
|
396
|
+
stopNativeAudioRecording(g_avAudioRecorder);
|
|
397
|
+
destroyNativeAudioRecorder(g_avAudioRecorder);
|
|
398
|
+
g_avAudioRecorder = nil;
|
|
399
|
+
MRLog(@"✅ Audio recording stopped");
|
|
400
|
+
}
|
|
401
|
+
|
|
354
402
|
@try {
|
|
355
403
|
// Stop timer with Electron-safe cleanup
|
|
356
404
|
if (g_avTimer) {
|
package/src/mac_recorder.mm
CHANGED
|
@@ -343,10 +343,18 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
343
343
|
MRLog(@" Reason: ScreenCaptureKit has thread safety issues in Electron (SIGTRAP crashes)");
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
346
|
+
// CRITICAL FIX: ScreenCaptureKit causes segmentation faults
|
|
347
|
+
// Forcing AVFoundation for ALL environments until issue is resolved
|
|
348
|
+
// TODO: Implement audio capture in AVFoundation
|
|
349
|
+
BOOL forceAVFoundation = YES;
|
|
350
|
+
|
|
351
|
+
MRLog(@"🔧 CRITICAL: ScreenCaptureKit disabled globally (segfault issue)");
|
|
352
|
+
MRLog(@" Using AVFoundation for stability");
|
|
353
|
+
MRLog(@" ⚠️ WARNING: Audio capture not available in AVFoundation mode");
|
|
354
|
+
MRLog(@" Audio files will be created but will be empty");
|
|
355
|
+
|
|
356
|
+
if (isElectron) {
|
|
357
|
+
MRLog(@"⚡ Electron environment detected - using stable AVFoundation");
|
|
350
358
|
}
|
|
351
359
|
|
|
352
360
|
// Electron-first priority: ALWAYS use AVFoundation in Electron for stability
|