node-mac-recorder 2.21.8 → 2.21.10
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 +2 -1
- package/index.js +32 -13
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +26 -11
- package/src/mac_recorder.mm +25 -27
package/index.js
CHANGED
|
@@ -152,21 +152,40 @@ class MacRecorder extends EventEmitter {
|
|
|
152
152
|
* Kayıt seçeneklerini ayarlar
|
|
153
153
|
*/
|
|
154
154
|
setOptions(options = {}) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
// Merge options instead of replacing to preserve previously set values
|
|
156
|
+
if (options.includeMicrophone !== undefined) {
|
|
157
|
+
this.options.includeMicrophone = options.includeMicrophone === true;
|
|
158
|
+
}
|
|
159
|
+
if (options.includeSystemAudio !== undefined) {
|
|
160
|
+
this.options.includeSystemAudio = options.includeSystemAudio === true;
|
|
161
|
+
}
|
|
162
|
+
if (options.captureCursor !== undefined) {
|
|
163
|
+
this.options.captureCursor = options.captureCursor || false;
|
|
164
|
+
}
|
|
165
|
+
if (options.displayId !== undefined) {
|
|
166
|
+
this.options.displayId = options.displayId || null;
|
|
167
|
+
}
|
|
168
|
+
if (options.windowId !== undefined) {
|
|
169
|
+
this.options.windowId = options.windowId || null;
|
|
170
|
+
}
|
|
171
|
+
if (options.audioDeviceId !== undefined) {
|
|
172
|
+
this.options.audioDeviceId = options.audioDeviceId || null;
|
|
173
|
+
}
|
|
174
|
+
if (options.systemAudioDeviceId !== undefined) {
|
|
175
|
+
this.options.systemAudioDeviceId = options.systemAudioDeviceId || null;
|
|
176
|
+
}
|
|
177
|
+
if (options.captureArea !== undefined) {
|
|
178
|
+
this.options.captureArea = options.captureArea || null;
|
|
179
|
+
}
|
|
180
|
+
if (options.captureCamera !== undefined) {
|
|
181
|
+
this.options.captureCamera = options.captureCamera === true;
|
|
182
|
+
}
|
|
183
|
+
if (options.cameraDeviceId !== undefined) {
|
|
184
|
+
this.options.cameraDeviceId =
|
|
166
185
|
typeof options.cameraDeviceId === "string" && options.cameraDeviceId.length > 0
|
|
167
186
|
? options.cameraDeviceId
|
|
168
|
-
: null
|
|
169
|
-
}
|
|
187
|
+
: null;
|
|
188
|
+
}
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
/**
|
package/package.json
CHANGED
|
@@ -26,23 +26,24 @@ static void* g_avAudioRecorder = nil;
|
|
|
26
26
|
static NSString* g_avAudioOutputPath = nil;
|
|
27
27
|
|
|
28
28
|
// AVFoundation screen recording implementation
|
|
29
|
-
extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
29
|
+
extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
30
30
|
CGDirectDisplayID displayID,
|
|
31
31
|
uint32_t windowID,
|
|
32
32
|
CGRect captureRect,
|
|
33
33
|
bool captureCursor,
|
|
34
|
-
bool includeMicrophone,
|
|
34
|
+
bool includeMicrophone,
|
|
35
35
|
bool includeSystemAudio,
|
|
36
|
-
NSString* audioDeviceId
|
|
36
|
+
NSString* audioDeviceId,
|
|
37
|
+
NSString* audioOutputPath) {
|
|
37
38
|
|
|
38
39
|
if (g_avIsRecording) {
|
|
39
40
|
NSLog(@"❌ AVFoundation recording already in progress");
|
|
40
41
|
return false;
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
@try {
|
|
44
45
|
MRLog(@"🎬 AVFoundation: Starting recording initialization");
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
// Create output URL
|
|
47
48
|
NSString *outputPathStr = [NSString stringWithUTF8String:outputPath.c_str()];
|
|
48
49
|
NSURL *outputURL = [NSURL fileURLWithPath:outputPathStr];
|
|
@@ -210,12 +211,22 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
210
211
|
if (includeMicrophone || includeSystemAudio) {
|
|
211
212
|
MRLog(@"🎤 Starting audio capture (mic=%d, system=%d)", includeMicrophone, includeSystemAudio);
|
|
212
213
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
// Use provided audio output path or generate one
|
|
215
|
+
if (audioOutputPath && [audioOutputPath length] > 0) {
|
|
216
|
+
g_avAudioOutputPath = audioOutputPath;
|
|
217
|
+
MRLog(@"🎤 Using provided audio path: %@", g_avAudioOutputPath);
|
|
218
|
+
} else {
|
|
219
|
+
NSString *videoDir = [outputPathStr stringByDeletingLastPathComponent];
|
|
220
|
+
NSString *audioFilename = [NSString stringWithFormat:@"avf_audio_%ld.mov", (long)[[NSDate date] timeIntervalSince1970]];
|
|
221
|
+
g_avAudioOutputPath = [videoDir stringByAppendingPathComponent:audioFilename];
|
|
222
|
+
MRLog(@"🎤 Generated audio path: %@", g_avAudioOutputPath);
|
|
223
|
+
}
|
|
217
224
|
|
|
218
|
-
|
|
225
|
+
// Ensure .mov extension
|
|
226
|
+
if (![g_avAudioOutputPath.pathExtension.lowercaseString isEqualToString:@"mov"]) {
|
|
227
|
+
g_avAudioOutputPath = [[g_avAudioOutputPath stringByDeletingPathExtension] stringByAppendingPathExtension:@"mov"];
|
|
228
|
+
MRLog(@"🔧 Fixed audio extension to .mov: %@", g_avAudioOutputPath);
|
|
229
|
+
}
|
|
219
230
|
|
|
220
231
|
// Create audio recorder
|
|
221
232
|
g_avAudioRecorder = createNativeAudioRecorder();
|
|
@@ -225,7 +236,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
225
236
|
const char* outputPathCStr = [g_avAudioOutputPath UTF8String];
|
|
226
237
|
|
|
227
238
|
if (startNativeAudioRecording(g_avAudioRecorder, deviceIdCStr, outputPathCStr)) {
|
|
228
|
-
MRLog(@"✅ Audio recording started");
|
|
239
|
+
MRLog(@"✅ Audio recording started to: %@", g_avAudioOutputPath);
|
|
229
240
|
} else {
|
|
230
241
|
NSLog(@"❌ Failed to start audio recording");
|
|
231
242
|
destroyNativeAudioRecorder(g_avAudioRecorder);
|
|
@@ -452,3 +463,7 @@ extern "C" bool stopAVFoundationRecording() {
|
|
|
452
463
|
extern "C" bool isAVFoundationRecording() {
|
|
453
464
|
return g_avIsRecording;
|
|
454
465
|
}
|
|
466
|
+
|
|
467
|
+
extern "C" NSString* getAVFoundationAudioPath() {
|
|
468
|
+
return g_avAudioOutputPath;
|
|
469
|
+
}
|
package/src/mac_recorder.mm
CHANGED
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
// AVFoundation fallback declarations
|
|
13
13
|
extern "C" {
|
|
14
|
-
bool startAVFoundationRecording(const std::string& outputPath,
|
|
14
|
+
bool startAVFoundationRecording(const std::string& outputPath,
|
|
15
15
|
CGDirectDisplayID displayID,
|
|
16
16
|
uint32_t windowID,
|
|
17
17
|
CGRect captureRect,
|
|
18
18
|
bool captureCursor,
|
|
19
|
-
bool includeMicrophone,
|
|
19
|
+
bool includeMicrophone,
|
|
20
20
|
bool includeSystemAudio,
|
|
21
|
-
NSString* audioDeviceId
|
|
21
|
+
NSString* audioDeviceId,
|
|
22
|
+
NSString* audioOutputPath);
|
|
22
23
|
bool stopAVFoundationRecording();
|
|
23
24
|
bool isAVFoundationRecording();
|
|
25
|
+
NSString* getAVFoundationAudioPath();
|
|
24
26
|
|
|
25
27
|
NSArray<NSDictionary *> *listCameraDevices();
|
|
26
28
|
bool startCameraRecording(NSString *outputPath, NSString *deviceId, NSError **error);
|
|
@@ -196,7 +198,7 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
196
198
|
|
|
197
199
|
if (info.Length() > 1 && info[1].IsObject()) {
|
|
198
200
|
Napi::Object options = info[1].As<Napi::Object>();
|
|
199
|
-
|
|
201
|
+
|
|
200
202
|
// Capture area
|
|
201
203
|
if (options.Has("captureArea") && options.Get("captureArea").IsObject()) {
|
|
202
204
|
Napi::Object rectObj = options.Get("captureArea").As<Napi::Object>();
|
|
@@ -217,15 +219,15 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
217
219
|
|
|
218
220
|
// Microphone
|
|
219
221
|
if (options.Has("includeMicrophone")) {
|
|
220
|
-
includeMicrophone = options.Get("includeMicrophone").As<Napi::Boolean>();
|
|
222
|
+
includeMicrophone = options.Get("includeMicrophone").As<Napi::Boolean>().Value();
|
|
221
223
|
}
|
|
222
|
-
|
|
224
|
+
|
|
223
225
|
// Audio device ID
|
|
224
226
|
if (options.Has("audioDeviceId") && !options.Get("audioDeviceId").IsNull()) {
|
|
225
227
|
std::string deviceId = options.Get("audioDeviceId").As<Napi::String>().Utf8Value();
|
|
226
228
|
audioDeviceId = [NSString stringWithUTF8String:deviceId.c_str()];
|
|
227
229
|
}
|
|
228
|
-
|
|
230
|
+
|
|
229
231
|
// System audio
|
|
230
232
|
if (options.Has("includeSystemAudio")) {
|
|
231
233
|
includeSystemAudio = options.Get("includeSystemAudio").As<Napi::Boolean>();
|
|
@@ -349,9 +351,7 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
349
351
|
BOOL forceAVFoundation = YES;
|
|
350
352
|
|
|
351
353
|
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");
|
|
354
|
+
MRLog(@" Using AVFoundation for stability with integrated audio capture");
|
|
355
355
|
|
|
356
356
|
if (isElectron) {
|
|
357
357
|
MRLog(@"⚡ Electron environment detected - using stable AVFoundation");
|
|
@@ -474,20 +474,21 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
474
474
|
|
|
475
475
|
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
476
476
|
MRLog(@"🎥 Starting AVFoundation recording...");
|
|
477
|
-
|
|
477
|
+
|
|
478
478
|
@try {
|
|
479
479
|
// Import AVFoundation recording functions (if available)
|
|
480
|
-
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
480
|
+
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
481
481
|
CGDirectDisplayID displayID,
|
|
482
482
|
uint32_t windowID,
|
|
483
483
|
CGRect captureRect,
|
|
484
484
|
bool captureCursor,
|
|
485
|
-
bool includeMicrophone,
|
|
485
|
+
bool includeMicrophone,
|
|
486
486
|
bool includeSystemAudio,
|
|
487
|
-
NSString* audioDeviceId
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
487
|
+
NSString* audioDeviceId,
|
|
488
|
+
NSString* audioOutputPath);
|
|
489
|
+
|
|
490
|
+
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
491
|
+
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId, audioOutputPath);
|
|
491
492
|
|
|
492
493
|
if (avResult) {
|
|
493
494
|
MRLog(@"🎥 RECORDING METHOD: AVFoundation");
|
|
@@ -500,16 +501,9 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
500
501
|
return Napi::Boolean::New(env, false);
|
|
501
502
|
}
|
|
502
503
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
stopCameraRecording();
|
|
507
|
-
}
|
|
508
|
-
stopAVFoundationRecording();
|
|
509
|
-
g_isRecording = false;
|
|
510
|
-
return Napi::Boolean::New(env, false);
|
|
511
|
-
}
|
|
512
|
-
|
|
504
|
+
// NOTE: Audio is handled internally by AVFoundation, no need for standalone audio
|
|
505
|
+
// AVFoundation integrates audio recording directly
|
|
506
|
+
|
|
513
507
|
g_isRecording = true;
|
|
514
508
|
return Napi::Boolean::New(env, true);
|
|
515
509
|
} else {
|
|
@@ -554,6 +548,7 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
|
|
|
554
548
|
// Try AVFoundation fallback (supports both Node.js and Electron)
|
|
555
549
|
extern bool isAVFoundationRecording();
|
|
556
550
|
extern bool stopAVFoundationRecording();
|
|
551
|
+
extern NSString* getAVFoundationAudioPath();
|
|
557
552
|
|
|
558
553
|
@try {
|
|
559
554
|
if (isAVFoundationRecording()) {
|
|
@@ -867,6 +862,9 @@ Napi::Value GetAudioRecordingPath(const Napi::CallbackInfo& info) {
|
|
|
867
862
|
if (!path || [path length] == 0) {
|
|
868
863
|
path = currentStandaloneAudioRecordingPath();
|
|
869
864
|
}
|
|
865
|
+
if (!path || [path length] == 0) {
|
|
866
|
+
path = getAVFoundationAudioPath();
|
|
867
|
+
}
|
|
870
868
|
if (!path || [path length] == 0) {
|
|
871
869
|
return env.Null();
|
|
872
870
|
}
|