node-mac-recorder 2.21.14 β 2.21.16
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 +27 -28
- package/package.json +1 -1
- package/src/audio_recorder.mm +12 -13
- package/src/mac_recorder.mm +57 -22
package/index.js
CHANGED
|
@@ -507,45 +507,44 @@ class MacRecorder extends EventEmitter {
|
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
// SYNC FIX: Start
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
const standardCursorOptions = {
|
|
514
|
-
videoRelative: true,
|
|
515
|
-
displayInfo: this.recordingDisplayInfo,
|
|
516
|
-
recordingType: this.options.windowId ? 'window' :
|
|
517
|
-
this.options.captureArea ? 'area' : 'display',
|
|
518
|
-
captureArea: this.options.captureArea,
|
|
519
|
-
windowId: this.options.windowId,
|
|
520
|
-
startTimestamp: sessionTimestamp // Use the same timestamp base
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
try {
|
|
524
|
-
await this.startCursorCapture(cursorFilePath, standardCursorOptions);
|
|
525
|
-
console.log('β
SYNC: Cursor tracking started successfully');
|
|
526
|
-
} catch (cursorError) {
|
|
527
|
-
console.warn('β οΈ Cursor tracking failed to start:', cursorError.message);
|
|
528
|
-
// Continue with recording even if cursor fails
|
|
529
|
-
}
|
|
510
|
+
// CRITICAL SYNC FIX: Start native recording FIRST (video/audio/camera)
|
|
511
|
+
// Then IMMEDIATELY start cursor tracking with the SAME timestamp
|
|
512
|
+
// This ensures ALL components capture their first frame at the same time
|
|
530
513
|
|
|
531
514
|
let success;
|
|
532
515
|
try {
|
|
533
|
-
console.log('π― SYNC: Starting
|
|
516
|
+
console.log('π― SYNC: Starting native recording (screen/audio/camera) at timestamp:', sessionTimestamp);
|
|
534
517
|
success = nativeBinding.startRecording(
|
|
535
518
|
outputPath,
|
|
536
519
|
recordingOptions
|
|
537
520
|
);
|
|
538
521
|
if (success) {
|
|
539
|
-
console.log('β
SYNC:
|
|
522
|
+
console.log('β
SYNC: Native recording started successfully');
|
|
540
523
|
}
|
|
541
524
|
} catch (error) {
|
|
542
|
-
// console.log('Native recording failed, trying alternative method');
|
|
543
525
|
success = false;
|
|
544
|
-
console.warn('β
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
526
|
+
console.warn('β Native recording failed to start:', error.message);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Only start cursor if native recording started successfully
|
|
530
|
+
if (success) {
|
|
531
|
+
const standardCursorOptions = {
|
|
532
|
+
videoRelative: true,
|
|
533
|
+
displayInfo: this.recordingDisplayInfo,
|
|
534
|
+
recordingType: this.options.windowId ? 'window' :
|
|
535
|
+
this.options.captureArea ? 'area' : 'display',
|
|
536
|
+
captureArea: this.options.captureArea,
|
|
537
|
+
windowId: this.options.windowId,
|
|
538
|
+
startTimestamp: sessionTimestamp // Use the same timestamp base
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
console.log('π― SYNC: Starting cursor tracking at timestamp:', sessionTimestamp);
|
|
543
|
+
await this.startCursorCapture(cursorFilePath, standardCursorOptions);
|
|
544
|
+
console.log('β
SYNC: Cursor tracking started successfully');
|
|
545
|
+
} catch (cursorError) {
|
|
546
|
+
console.warn('β οΈ Cursor tracking failed to start:', cursorError.message);
|
|
547
|
+
// Continue with recording even if cursor fails - don't stop native recording
|
|
549
548
|
}
|
|
550
549
|
}
|
|
551
550
|
|
package/package.json
CHANGED
package/src/audio_recorder.mm
CHANGED
|
@@ -110,16 +110,7 @@ static dispatch_queue_t g_audioCaptureQueue = nil;
|
|
|
110
110
|
|
|
111
111
|
MRLog(@"π€ Audio format: %.0f Hz, %lu channel(s)", sampleRate, (unsigned long)channels);
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
size_t layoutSize = 0;
|
|
115
|
-
if (channels == 1) {
|
|
116
|
-
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
|
|
117
|
-
layoutSize = sizeof(AudioChannelLayout);
|
|
118
|
-
} else if (channels == 2) {
|
|
119
|
-
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
|
|
120
|
-
layoutSize = sizeof(AudioChannelLayout);
|
|
121
|
-
}
|
|
122
|
-
|
|
113
|
+
// Create audio settings
|
|
123
114
|
NSMutableDictionary *audioSettings = [@{
|
|
124
115
|
AVFormatIDKey: @(kAudioFormatMPEG4AAC),
|
|
125
116
|
AVSampleRateKey: @(sampleRate),
|
|
@@ -128,9 +119,17 @@ static dispatch_queue_t g_audioCaptureQueue = nil;
|
|
|
128
119
|
AVEncoderAudioQualityKey: @(AVAudioQualityHigh)
|
|
129
120
|
} mutableCopy];
|
|
130
121
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
// CRITICAL FIX: AVChannelLayoutKey is REQUIRED for ALL channel counts
|
|
123
|
+
// Force to stereo or mono for AAC compatibility
|
|
124
|
+
NSUInteger validChannels = (channels <= 1) ? 1 : 2; // Force to mono or stereo
|
|
125
|
+
audioSettings[AVNumberOfChannelsKey] = @(validChannels); // Update settings
|
|
126
|
+
|
|
127
|
+
AudioChannelLayout layout = {0};
|
|
128
|
+
layout.mChannelLayoutTag = (validChannels == 1) ? kAudioChannelLayoutTag_Mono : kAudioChannelLayoutTag_Stereo;
|
|
129
|
+
size_t layoutSize = sizeof(AudioChannelLayout);
|
|
130
|
+
audioSettings[AVChannelLayoutKey] = [NSData dataWithBytes:&layout length:layoutSize];
|
|
131
|
+
|
|
132
|
+
MRLog(@"π€ Using %lu channel(s) for AAC encoding (original: %lu)", (unsigned long)validChannels, (unsigned long)channels);
|
|
134
133
|
|
|
135
134
|
self.writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
|
|
136
135
|
self.writerInput.expectsMediaDataInRealTime = YES;
|
package/src/mac_recorder.mm
CHANGED
|
@@ -403,33 +403,50 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
403
403
|
|
|
404
404
|
// Use ScreenCaptureKit with window exclusion and timeout protection
|
|
405
405
|
NSError *sckError = nil;
|
|
406
|
-
|
|
406
|
+
|
|
407
407
|
// Set timeout for ScreenCaptureKit initialization
|
|
408
408
|
// Attempt to start ScreenCaptureKit with safety wrapper
|
|
409
409
|
@try {
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
// CRITICAL SYNC FIX: Start camera BEFORE ScreenCaptureKit for perfect sync
|
|
411
|
+
bool cameraStarted = true;
|
|
412
|
+
if (captureCamera) {
|
|
413
|
+
MRLog(@"π― SYNC: Starting camera recording first for parallel sync");
|
|
414
|
+
cameraStarted = startCameraIfRequested(captureCamera, &cameraOutputPath, cameraDeviceId, outputPath, sessionTimestamp);
|
|
415
|
+
if (!cameraStarted) {
|
|
416
|
+
MRLog(@"β Camera start failed - aborting");
|
|
417
|
+
return Napi::Boolean::New(env, false);
|
|
418
|
+
}
|
|
419
|
+
MRLog(@"β
SYNC: Camera recording started");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Now start ScreenCaptureKit immediately after camera
|
|
423
|
+
MRLog(@"π― SYNC: Starting ScreenCaptureKit recording immediately");
|
|
424
|
+
if ([ScreenCaptureKitRecorder startRecordingWithConfiguration:sckConfig
|
|
425
|
+
delegate:g_delegate
|
|
412
426
|
error:&sckError]) {
|
|
413
|
-
|
|
427
|
+
|
|
414
428
|
// ScreenCaptureKit baΕlatma baΕarΔ±lΔ± - validation yapmΔ±yoruz
|
|
415
429
|
MRLog(@"π¬ RECORDING METHOD: ScreenCaptureKit");
|
|
416
|
-
MRLog(@"β
ScreenCaptureKit recording started successfully");
|
|
417
|
-
|
|
418
|
-
if (!startCameraIfRequested(captureCamera, &cameraOutputPath, cameraDeviceId, outputPath, sessionTimestamp)) {
|
|
419
|
-
MRLog(@"β Camera start failed - stopping ScreenCaptureKit session");
|
|
420
|
-
[ScreenCaptureKitRecorder stopRecording];
|
|
421
|
-
g_isRecording = false;
|
|
422
|
-
return Napi::Boolean::New(env, false);
|
|
423
|
-
}
|
|
424
|
-
|
|
430
|
+
MRLog(@"β
SYNC: ScreenCaptureKit recording started successfully");
|
|
431
|
+
|
|
425
432
|
g_isRecording = true;
|
|
426
433
|
return Napi::Boolean::New(env, true);
|
|
427
434
|
} else {
|
|
428
435
|
NSLog(@"β ScreenCaptureKit failed to start");
|
|
429
436
|
NSLog(@"β Error: %@", sckError ? sckError.localizedDescription : @"Unknown error");
|
|
437
|
+
|
|
438
|
+
// Cleanup camera if ScreenCaptureKit failed
|
|
439
|
+
if (cameraStarted && isCameraRecording()) {
|
|
440
|
+
stopCameraRecording();
|
|
441
|
+
}
|
|
430
442
|
}
|
|
431
443
|
} @catch (NSException *sckException) {
|
|
432
444
|
NSLog(@"β Exception during ScreenCaptureKit startup: %@", sckException.reason);
|
|
445
|
+
|
|
446
|
+
// Cleanup camera on exception
|
|
447
|
+
if (isCameraRecording()) {
|
|
448
|
+
stopCameraRecording();
|
|
449
|
+
}
|
|
433
450
|
}
|
|
434
451
|
NSLog(@"β ScreenCaptureKit failed or unsafe - will fallback to AVFoundation");
|
|
435
452
|
|
|
@@ -487,19 +504,27 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
487
504
|
NSString* audioDeviceId,
|
|
488
505
|
NSString* audioOutputPath);
|
|
489
506
|
|
|
507
|
+
// CRITICAL SYNC FIX: Start camera BEFORE screen recording for perfect sync
|
|
508
|
+
// This ensures both capture their first frame at approximately the same time
|
|
509
|
+
bool cameraStarted = true;
|
|
510
|
+
if (captureCamera) {
|
|
511
|
+
MRLog(@"π― SYNC: Starting camera recording first for parallel sync");
|
|
512
|
+
cameraStarted = startCameraIfRequested(captureCamera, &cameraOutputPath, cameraDeviceId, outputPath, sessionTimestamp);
|
|
513
|
+
if (!cameraStarted) {
|
|
514
|
+
MRLog(@"β Camera start failed - aborting");
|
|
515
|
+
return Napi::Boolean::New(env, false);
|
|
516
|
+
}
|
|
517
|
+
MRLog(@"β
SYNC: Camera recording started");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Now start screen recording immediately after camera
|
|
521
|
+
MRLog(@"π― SYNC: Starting screen recording immediately");
|
|
490
522
|
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
491
523
|
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId, audioOutputPath);
|
|
492
|
-
|
|
524
|
+
|
|
493
525
|
if (avResult) {
|
|
494
526
|
MRLog(@"π₯ RECORDING METHOD: AVFoundation");
|
|
495
|
-
MRLog(@"β
|
|
496
|
-
|
|
497
|
-
if (!startCameraIfRequested(captureCamera, &cameraOutputPath, cameraDeviceId, outputPath, sessionTimestamp)) {
|
|
498
|
-
MRLog(@"β Camera start failed - stopping AVFoundation session");
|
|
499
|
-
stopAVFoundationRecording();
|
|
500
|
-
g_isRecording = false;
|
|
501
|
-
return Napi::Boolean::New(env, false);
|
|
502
|
-
}
|
|
527
|
+
MRLog(@"β
SYNC: Screen recording started successfully");
|
|
503
528
|
|
|
504
529
|
// NOTE: Audio is handled internally by AVFoundation, no need for standalone audio
|
|
505
530
|
// AVFoundation integrates audio recording directly
|
|
@@ -509,10 +534,20 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
509
534
|
} else {
|
|
510
535
|
NSLog(@"β AVFoundation recording failed to start");
|
|
511
536
|
NSLog(@"β Check permissions and output path validity");
|
|
537
|
+
|
|
538
|
+
// Cleanup camera if screen recording failed
|
|
539
|
+
if (cameraStarted && isCameraRecording()) {
|
|
540
|
+
stopCameraRecording();
|
|
541
|
+
}
|
|
512
542
|
}
|
|
513
543
|
} @catch (NSException *avException) {
|
|
514
544
|
NSLog(@"β Exception during AVFoundation startup: %@", avException.reason);
|
|
515
545
|
NSLog(@"β Stack trace: %@", [avException callStackSymbols]);
|
|
546
|
+
|
|
547
|
+
// Cleanup camera on exception
|
|
548
|
+
if (isCameraRecording()) {
|
|
549
|
+
stopCameraRecording();
|
|
550
|
+
}
|
|
516
551
|
}
|
|
517
552
|
|
|
518
553
|
// Both ScreenCaptureKit and AVFoundation failed
|