node-mac-recorder 2.21.28 → 2.21.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.28",
3
+ "version": "2.21.30",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -754,31 +754,41 @@ static BOOL MRIsContinuityCamera(AVCaptureDevice *device) {
754
754
  return YES; // Success - nothing to finish
755
755
  }
756
756
 
757
- if (self.assetWriterInput) {
758
- [self.assetWriterInput markAsFinished];
757
+ AVAssetWriter *writer = self.assetWriter;
758
+ AVAssetWriterInput *writerInput = self.assetWriterInput;
759
+
760
+ if (writerInput) {
761
+ [writerInput markAsFinished];
759
762
  }
760
763
 
761
764
  __block BOOL finished = NO;
762
765
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
763
766
 
764
- [self.assetWriter finishWritingWithCompletionHandler:^{
767
+ [writer finishWritingWithCompletionHandler:^{
765
768
  finished = YES;
766
769
  dispatch_semaphore_signal(semaphore);
767
770
  }];
768
771
 
769
- // Reduced timeout to 1 second for external devices
770
- dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
772
+ // Allow generous flush time so final frames are preserved.
773
+ const int64_t primaryWaitSeconds = 3;
774
+ dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(primaryWaitSeconds * NSEC_PER_SEC));
771
775
  long result = dispatch_semaphore_wait(semaphore, timeout);
772
776
 
773
777
  if (result != 0 || !finished) {
774
- MRLog(@"⚠️ CameraRecorder: Timed out waiting for writer (external device?) - forcing cancel");
775
- // Force cancel if timeout
776
- [self.assetWriter cancelWriting];
778
+ MRLog(@"⚠️ CameraRecorder: Writer still finishing after %ds waiting longer", (int)primaryWaitSeconds);
779
+ const int64_t extendedWaitSeconds = 5;
780
+ dispatch_time_t extendedTimeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(extendedWaitSeconds * NSEC_PER_SEC));
781
+ result = dispatch_semaphore_wait(semaphore, extendedTimeout);
782
+ }
783
+
784
+ if (result != 0 || !finished) {
785
+ MRLog(@"⚠️ CameraRecorder: Writer did not finish after extended wait – forcing cancel");
786
+ [writer cancelWriting];
777
787
  }
778
788
 
779
- BOOL success = (self.assetWriter.status == AVAssetWriterStatusCompleted);
789
+ BOOL success = (writer.status == AVAssetWriterStatusCompleted);
780
790
  if (!success) {
781
- MRLog(@"⚠️ CameraRecorder: Writer finished with status %ld error %@", (long)self.assetWriter.status, self.assetWriter.error);
791
+ MRLog(@"⚠️ CameraRecorder: Writer finished with status %ld error %@", (long)writer.status, writer.error);
782
792
  } else {
783
793
  MRLog(@"✅ CameraRecorder writer finished successfully");
784
794
  }
@@ -630,33 +630,51 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
630
630
  if (isAVFoundationRecording()) {
631
631
  MRLog(@"🛑 Stopping AVFoundation recording");
632
632
 
633
- // CRITICAL FIX: Stop camera FIRST (synchronous)
634
- if (isCameraRecording()) {
633
+ BOOL cameraWasRecording = isCameraRecording();
634
+ __block BOOL cameraStopResult = YES;
635
+ dispatch_group_t cameraStopGroup = NULL;
636
+
637
+ if (cameraWasRecording) {
635
638
  MRLog(@"🛑 Stopping camera recording...");
636
- bool cameraStopped = stopCameraRecording();
637
- if (cameraStopped) {
638
- MRLog(@"✅ Camera stopped successfully");
639
- } else {
640
- MRLog(@"⚠️ Camera stop may have timed out");
641
- }
639
+ cameraStopResult = NO;
640
+ cameraStopGroup = dispatch_group_create();
641
+ dispatch_group_enter(cameraStopGroup);
642
+ // Stop camera on a background queue so audio/screen shutdown can proceed immediately.
643
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
644
+ cameraStopResult = stopCameraRecording() ? YES : NO;
645
+ dispatch_group_leave(cameraStopGroup);
646
+ });
642
647
  }
643
648
 
644
- // Stop standalone audio if used
649
+ // Stop standalone audio if used (ScreenCaptureKit fallback)
645
650
  if (g_usingStandaloneAudio && isStandaloneAudioRecording()) {
646
651
  MRLog(@"🛑 Stopping standalone audio...");
647
652
  stopStandaloneAudioRecording();
648
653
  }
649
654
 
650
- // Stop AVFoundation recording
651
- if (stopAVFoundationRecording()) {
652
- g_isRecording = false;
653
- g_usingStandaloneAudio = false;
655
+ bool avFoundationStopped = stopAVFoundationRecording();
656
+
657
+ if (cameraStopGroup) {
658
+ dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
659
+ long waitResult = dispatch_group_wait(cameraStopGroup, waitTime);
660
+ if (waitResult != 0) {
661
+ MRLog(@"⚠️ Camera stop did not finish within 2 seconds (AVFoundation)");
662
+ cameraStopResult = NO;
663
+ } else if (cameraStopResult) {
664
+ MRLog(@"✅ Camera stopped successfully");
665
+ } else {
666
+ MRLog(@"⚠️ Camera stop reported failure");
667
+ }
668
+ }
669
+
670
+ g_isRecording = false;
671
+ g_usingStandaloneAudio = false;
672
+
673
+ if (avFoundationStopped && (!cameraWasRecording || cameraStopResult)) {
654
674
  MRLog(@"✅ AVFoundation recording stopped");
655
675
  return Napi::Boolean::New(env, true);
656
676
  } else {
657
677
  NSLog(@"❌ Failed to stop AVFoundation recording");
658
- g_isRecording = false;
659
- g_usingStandaloneAudio = false;
660
678
  return Napi::Boolean::New(env, false);
661
679
  }
662
680
  }