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.
@@ -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/index.js CHANGED
@@ -507,45 +507,44 @@ class MacRecorder extends EventEmitter {
507
507
  };
508
508
  }
509
509
 
510
- // SYNC FIX: Start cursor tracking BEFORE native recording for perfect sync
511
- // This ensures cursor data starts at exactly the same time as video
512
- console.log('🎯 SYNC: Starting cursor tracking at timestamp:', sessionTimestamp);
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 screen recording at timestamp:', sessionTimestamp);
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: Screen recording started successfully');
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('❌ Screen recording failed to start');
545
- // Stop cursor if recording fails
546
- if (this.cursorCaptureInterval) {
547
- console.log('πŸ”„ SYNC: Stopping cursor tracking due to recording failure');
548
- await this.stopCursorCapture().catch(() => {});
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.14",
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