node-mac-recorder 2.21.54 → 2.22.2

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.
@@ -48,7 +48,8 @@
48
48
  "Bash(grep:*)",
49
49
  "Bash(timeout 10 ffprobe:*)",
50
50
  "Bash(ffmpeg:*)",
51
- "Bash(timeout 30 node:*)"
51
+ "Bash(timeout 30 node:*)",
52
+ "Bash(MAC_RECORDER_DEBUG=1 node test-camera-audio-sync.js:*)"
52
53
  ],
53
54
  "deny": [],
54
55
  "ask": []
package/index.js CHANGED
@@ -1119,12 +1119,15 @@ class MacRecorder extends EventEmitter {
1119
1119
  try {
1120
1120
  console.log('🛑 SYNC: Stopping screen recording');
1121
1121
  const stopLimit = elapsedSeconds > 0 ? elapsedSeconds : 0;
1122
+ console.log(`📊 DEBUG: elapsedSeconds=${elapsedSeconds.toFixed(3)}, stopLimit=${stopLimit.toFixed(3)}`);
1123
+ console.log(`📊 DEBUG: typeof nativeBinding.stopRecording = ${typeof nativeBinding.stopRecording}`);
1124
+ console.log(`📊 DEBUG: nativeBinding.stopRecording = ${nativeBinding.stopRecording}`);
1122
1125
  success = nativeBinding.stopRecording(stopLimit);
1123
1126
  if (success) {
1124
1127
  console.log('✅ SYNC: Screen recording stopped');
1125
1128
  }
1126
1129
  } catch (nativeError) {
1127
- // console.log('Native stop failed:', nativeError.message);
1130
+ console.log('⚠️ Native stop failed:', nativeError.message);
1128
1131
  success = true; // Assume success to avoid throwing
1129
1132
  }
1130
1133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.54",
3
+ "version": "2.22.2",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -451,10 +451,11 @@ static void MRCameraRemoveFileIfExists(NSString *path) {
451
451
 
452
452
  MRLog(@"🎥 Camera recording dimensions: %zux%zu", width, height);
453
453
 
454
- // H.264 video settings (matching current quality)
455
- NSInteger bitrate = (NSInteger)(width * height * 24); // 24fps target
456
- bitrate = MAX(bitrate, 5 * 1000 * 1000); // Min 5 Mbps
457
- bitrate = MIN(bitrate, 30 * 1000 * 1000); // Max 30 Mbps
454
+ // H.264 video settings - optimized for smaller file size
455
+ // Using lower bitrate multiplier for camera (2x instead of 24x) to reduce file size
456
+ NSInteger bitrate = (NSInteger)(width * height * 2);
457
+ bitrate = MAX(bitrate, 1 * 1000 * 1000); // Min 1 Mbps
458
+ bitrate = MIN(bitrate, 6 * 1000 * 1000); // Max 6 Mbps (significantly reduced from 30)
458
459
 
459
460
  NSDictionary *compressionProps = @{
460
461
  AVVideoAverageBitRateKey: @(bitrate),
@@ -586,6 +587,39 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
586
587
  adjustedTimestamp = kCMTimeZero;
587
588
  }
588
589
 
590
+ // LIP SYNC FIX: Check stopLimit OR elapsed time to drop frames after recording duration
591
+ // This prevents camera from recording longer than audio
592
+ double frameTime = CMTimeGetSeconds(adjustedTimestamp);
593
+ double stopLimit = MRSyncGetStopLimitSeconds();
594
+ double videoTolerance = 0.05; // 50ms tolerance for video frames (larger than audio's 20ms)
595
+
596
+ // CRITICAL FIX: Also check elapsed time since recording started
597
+ // This works even if stopLimit hasn't been set yet
598
+ double elapsedTime = (g_cameraStartTimestamp > 0) ? (CFAbsoluteTimeGetCurrent() - g_cameraStartTimestamp) : 0;
599
+ double maxDuration = (stopLimit > 0) ? stopLimit : elapsedTime + 1.0; // Use stopLimit if available, else allow 1s more
600
+
601
+ // DEBUG: Log every 30th frame
602
+ static int frameCounter = 0;
603
+ frameCounter++;
604
+ if (frameCounter % 30 == 0) {
605
+ MRLog(@"📹 Camera frame #%d: frameTime=%.3fs, elapsedTime=%.3fs, stopLimit=%.3fs, maxDuration=%.3fs",
606
+ frameCounter, frameTime, elapsedTime, stopLimit, maxDuration);
607
+ }
608
+
609
+ // Drop frame if it exceeds stopLimit (when set) or if elapsed time is suspiciously long
610
+ if (stopLimit > 0 && frameTime > stopLimit + videoTolerance) {
611
+ MRLog(@"🛑 Camera dropping frame #%d: frameTime %.3fs > stopLimit %.3fs + tolerance",
612
+ frameCounter, frameTime, stopLimit);
613
+ return;
614
+ }
615
+
616
+ // Safety check: Drop frames if recording has been going for too long (failsafe)
617
+ if (elapsedTime > maxDuration + 1.0) { // Allow 1s grace period
618
+ MRLog(@"🛑 Camera dropping frame #%d: elapsed %.3fs > maxDuration %.3fs (failsafe)",
619
+ frameCounter, elapsedTime, maxDuration);
620
+ return;
621
+ }
622
+
589
623
  // Get pixel buffer from sample
590
624
  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
591
625
  if (!pixelBuffer) {
@@ -872,6 +906,14 @@ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
872
906
 
873
907
  // Finalize writer (audio_recorder.mm pattern)
874
908
  if (self.writer && self.writerStarted) {
909
+ // LIP SYNC FIX: End session at stopLimit to trim excess frames
910
+ double stopLimit = MRSyncGetStopLimitSeconds();
911
+ if (stopLimit > 0) {
912
+ CMTime endTime = CMTimeMakeWithSeconds(stopLimit, 600);
913
+ [self.writer endSessionAtSourceTime:endTime];
914
+ MRLog(@"🎯 Camera writer session ended at %.3fs (stopLimit)", stopLimit);
915
+ }
916
+
875
917
  if (self.writerInput) {
876
918
  [self.writerInput markAsFinished];
877
919
  }
@@ -786,14 +786,23 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
786
786
  }
787
787
 
788
788
  if (requestedStopLimit > 0) {
789
+ MRLog(@"🎯 SYNC FIX: Setting stopLimit = %.3f seconds", requestedStopLimit);
789
790
  MRStoreActiveStopLimit(requestedStopLimit);
790
791
  MRSyncSetStopLimitSeconds(requestedStopLimit);
791
792
  } else {
793
+ MRLog(@"⚠️ SYNC FIX: stopLimit NOT set (requestedStopLimit = %.3f)", requestedStopLimit);
792
794
  MRStoreActiveStopLimit(-1.0);
793
795
  MRSyncSetStopLimitSeconds(-1.0);
794
796
  }
795
797
  MRResetRecordingStartTimestamp();
796
-
798
+
799
+ // LIP SYNC FIX: Wait 150ms for camera/audio delegates to see stopLimit and drop excess frames
800
+ if (requestedStopLimit > 0 && (isCameraRecording() || isStandaloneAudioRecording())) {
801
+ MRLog(@"⏳ LIP SYNC: Waiting 150ms for delegates to process stopLimit...");
802
+ [NSThread sleepForTimeInterval:0.15];
803
+ MRLog(@"✅ LIP SYNC: Delegate processing time complete");
804
+ }
805
+
797
806
  // Try ScreenCaptureKit first
798
807
  if (@available(macOS 12.3, *)) {
799
808
  if ([ScreenCaptureKitRecorder isRecording]) {