node-mac-recorder 2.21.15 β†’ 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.
@@ -3,7 +3,8 @@
3
3
  "allow": [
4
4
  "Bash(ffmpeg:*)",
5
5
  "Bash(chmod:*)",
6
- "Bash(node test-sync.js:*)"
6
+ "Bash(node test-sync.js:*)",
7
+ "Bash(node:*)"
7
8
  ],
8
9
  "deny": [],
9
10
  "ask": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.15",
3
+ "version": "2.21.16",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
- AudioChannelLayout layout = {0};
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
- if (layoutSize > 0) {
132
- audioSettings[AVChannelLayoutKey] = [NSData dataWithBytes:&layout length:layoutSize];
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;
@@ -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
- if ([ScreenCaptureKitRecorder startRecordingWithConfiguration:sckConfig
411
- delegate:g_delegate
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(@"βœ… AVFoundation recording started successfully");
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