node-mac-recorder 2.21.18 → 2.21.20

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/CHANGELOG-SYNC.md CHANGED
@@ -149,6 +149,77 @@ const audioFilePath = path.join(outputDir, `temp_audio_${sessionTimestamp}.mov`)
149
149
 
150
150
  ---
151
151
 
152
+ ### 5. RACE CONDITION KORUMASI - Durdurma Sırasında Başlatma Engelleme
153
+
154
+ **Önceki Durum:**
155
+ - stopRecording() çağrıldıktan sonra yeni kayıt başlatılabiliyordu
156
+ - ScreenCaptureKit async durdururken g_isRecording senkron değişiyordu
157
+ - Kayıt durduktan sonra bile devam edebiliyordu
158
+ - Kamera ve ses ekrandan SONRA duruyordu (yanlış sıralama)
159
+
160
+ **Yeni Durum:**
161
+ - Stop işlemi sırasında yeni kayıt başlatılamıyor
162
+ - g_isCleaningUp flag ile async koruma
163
+ - Hızlı start/stop döngüleri güvenli çalışıyor
164
+ - Kamera ve ses ekrandan ÖNCE duruyor (doğru sıralama)
165
+
166
+ **Kod Değişiklikleri:**
167
+ ```objc
168
+ // screen_capture_kit.mm
169
+ + (void)stopRecording {
170
+ // Set cleanup flag IMMEDIATELY to prevent race conditions
171
+ @synchronized([ScreenCaptureKitRecorder class]) {
172
+ g_isCleaningUp = YES;
173
+ }
174
+
175
+ [streamToStop stopCaptureWithCompletionHandler:^(NSError *stopError) {
176
+ @synchronized([ScreenCaptureKitRecorder class]) {
177
+ g_isRecording = NO;
178
+ g_isCleaningUp = NO; // Reset when done
179
+ }
180
+ CleanupWriters();
181
+ }];
182
+ }
183
+
184
+ // Export C function for checking cleanup state
185
+ BOOL isScreenCaptureKitCleaningUp() API_AVAILABLE(macos(12.3)) {
186
+ return [ScreenCaptureKitRecorder isCleaningUp];
187
+ }
188
+
189
+ // mac_recorder.mm
190
+ // Check if ScreenCaptureKit is still cleaning up
191
+ if (@available(macOS 12.3, *)) {
192
+ if (isScreenCaptureKitCleaningUp()) {
193
+ MRLog(@"⚠️ ScreenCaptureKit is still stopping - please wait");
194
+ return Napi::Boolean::New(env, false);
195
+ }
196
+ }
197
+
198
+ // Stop camera FIRST (synchronous) before screen
199
+ if (isCameraRecording()) {
200
+ MRLog(@"🛑 Stopping camera recording...");
201
+ stopCameraRecording();
202
+ }
203
+ ```
204
+
205
+ **Test Sonucu:**
206
+ ```
207
+ 📋 Test 1: Normal stop/start (1 saniye ara)
208
+ ✅ Test 1 BAŞARILI
209
+
210
+ 📋 Test 2: Hızlı stop/start (100ms ara)
211
+ ✅ Test 2 BAŞARILI
212
+
213
+ 📋 Test 3: Çok hızlı stop/start (0ms ara - RACE CONDITION)
214
+ ✅ Recording 6 başlatılamadı (BEKLENİYOR): Recording is already in progress
215
+ ✅ Test 3 BAŞARILI (race condition yakalandı)
216
+
217
+ ✅ TÜM TESTLER BAŞARILI
218
+ Stop işlemi güvenilir çalışıyor
219
+ ```
220
+
221
+ ---
222
+
152
223
  ## 🧪 Test Komutları
153
224
 
154
225
  ```bash
@@ -161,6 +232,9 @@ node test-stop.js
161
232
  # Timestamp tutarlılığı testi
162
233
  node test-timestamp.js
163
234
 
235
+ # Race condition testi (hızlı start/stop döngüleri)
236
+ node test-stop-race.js
237
+
164
238
  # Cihaz listesi
165
239
  node check-devices.js
166
240
  ```
@@ -176,6 +250,7 @@ node check-devices.js
176
250
  | iPhone görünürlük | Görünmüyor | **Görünüyor** ✅ |
177
251
  | Timestamp tutarlılığı | Farklı | **Aynı** ✅ |
178
252
  | Dosya uzantıları | .webm (yanlış) | **.mov** ✅ |
253
+ | Race condition | Kayıt devam ediyor | **Korunuyor** ✅ |
179
254
 
180
255
  ---
181
256
 
@@ -183,7 +258,8 @@ node check-devices.js
183
258
 
184
259
  **Değiştirilen:**
185
260
  - `index.js`: Senkronizasyon, timestamp, dosya isimleri
186
- - `src/mac_recorder.mm`: Kamera önce başlatma, timestamp aktarma
261
+ - `src/mac_recorder.mm`: Kamera önce başlatma, timestamp aktarma, race condition kontrolü
262
+ - `src/screen_capture_kit.mm`: g_isCleaningUp flag, async stop koruma
187
263
  - `src/camera_recorder.mm`: Hızlı durdurma, Continuity Camera
188
264
  - `src/audio_recorder.mm`: Hızlı durdurma, Continuity Audio, AVChannelLayoutKey
189
265
 
@@ -191,6 +267,7 @@ node check-devices.js
191
267
  - `test-real-stop.js`: Gerçek kayıt testi
192
268
  - `test-stop.js`: Hızlı durdurma testi
193
269
  - `test-timestamp.js`: Timestamp tutarlılığı testi
270
+ - `test-stop-race.js`: Race condition testi
194
271
  - `check-devices.js`: Cihaz listesi
195
272
 
196
273
  ---
@@ -204,3 +281,4 @@ Tüm kayıt bileşenleri (ekran, ses, kamera, cursor) artık:
204
281
  - ✅ Doğru dosya uzantıları (.mov)
205
282
  - ✅ iPhone/Continuity desteği
206
283
  - ✅ Ses ve görüntü perfect sync
284
+ - ✅ Race condition koruması (async stop sırasında başlatma engelleniyor)
package/index.js CHANGED
@@ -449,8 +449,8 @@ class MacRecorder extends EventEmitter {
449
449
  const originalBaseName = path.basename(outputPath, path.extname(outputPath));
450
450
  const extension = path.extname(outputPath);
451
451
 
452
- // Remove any existing timestamp from filename (pattern: -1234567890)
453
- const cleanBaseName = originalBaseName.replace(/-\d{13}$/, '');
452
+ // Remove any existing timestamp from filename (pattern: -1234567890 or _1234567890)
453
+ const cleanBaseName = originalBaseName.replace(/[-_]\d{13}$/, '');
454
454
 
455
455
  // Reconstruct path with sessionTimestamp
456
456
  outputPath = path.join(outputDir, `${cleanBaseName}-${sessionTimestamp}${extension}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.18",
3
+ "version": "2.21.20",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -172,11 +172,20 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
172
172
  // This fixes the issue where macOS 14/13 users get "recording already in progress"
173
173
  MRLog(@"🧹 Cleaning up any previous recording state...");
174
174
  cleanupRecording();
175
-
175
+
176
176
  if (g_isRecording) {
177
177
  MRLog(@"⚠️ Still recording after cleanup - forcing stop");
178
178
  return Napi::Boolean::New(env, false);
179
179
  }
180
+
181
+ // CRITICAL FIX: Check if ScreenCaptureKit is still cleaning up (async stop in progress)
182
+ if (@available(macOS 12.3, *)) {
183
+ extern BOOL isScreenCaptureKitCleaningUp();
184
+ if (isScreenCaptureKitCleaningUp()) {
185
+ MRLog(@"⚠️ ScreenCaptureKit is still stopping previous recording - please wait");
186
+ return Napi::Boolean::New(env, false);
187
+ }
188
+ }
180
189
  g_usingStandaloneAudio = false;
181
190
 
182
191
  std::string outputPath = info[0].As<Napi::String>().Utf8Value();
@@ -570,11 +579,25 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
570
579
  if (@available(macOS 12.3, *)) {
571
580
  if ([ScreenCaptureKitRecorder isRecording]) {
572
581
  MRLog(@"🛑 Stopping ScreenCaptureKit recording");
573
- [ScreenCaptureKitRecorder stopRecording];
582
+
583
+ // CRITICAL FIX: Stop camera and audio FIRST (they are synchronous)
574
584
  if (isCameraRecording()) {
575
- stopCameraRecording();
585
+ MRLog(@"🛑 Stopping camera recording...");
586
+ bool cameraStopped = stopCameraRecording();
587
+ if (cameraStopped) {
588
+ MRLog(@"✅ Camera stopped successfully");
589
+ } else {
590
+ MRLog(@"⚠️ Camera stop may have timed out");
591
+ }
576
592
  }
577
- g_isRecording = false;
593
+
594
+ // Now stop ScreenCaptureKit (asynchronous)
595
+ // WARNING: [ScreenCaptureKitRecorder stopRecording] is ASYNC!
596
+ // It will set g_isRecording = NO in its completion handler
597
+ [ScreenCaptureKitRecorder stopRecording];
598
+
599
+ // DO NOT set g_isRecording here - let ScreenCaptureKit completion handler do it
600
+ // Otherwise we have a race condition where JS thinks recording stopped but it's still running
578
601
  g_usingStandaloneAudio = false;
579
602
  return Napi::Boolean::New(env, true);
580
603
  }
@@ -588,24 +611,32 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
588
611
  @try {
589
612
  if (isAVFoundationRecording()) {
590
613
  MRLog(@"🛑 Stopping AVFoundation recording");
591
- if (stopAVFoundationRecording()) {
592
- if (g_usingStandaloneAudio && isStandaloneAudioRecording()) {
593
- stopStandaloneAudioRecording();
594
- }
595
- if (isCameraRecording()) {
596
- stopCameraRecording();
614
+
615
+ // CRITICAL FIX: Stop camera FIRST (synchronous)
616
+ if (isCameraRecording()) {
617
+ MRLog(@"🛑 Stopping camera recording...");
618
+ bool cameraStopped = stopCameraRecording();
619
+ if (cameraStopped) {
620
+ MRLog(@"✅ Camera stopped successfully");
621
+ } else {
622
+ MRLog(@"⚠️ Camera stop may have timed out");
597
623
  }
624
+ }
625
+
626
+ // Stop standalone audio if used
627
+ if (g_usingStandaloneAudio && isStandaloneAudioRecording()) {
628
+ MRLog(@"🛑 Stopping standalone audio...");
629
+ stopStandaloneAudioRecording();
630
+ }
631
+
632
+ // Stop AVFoundation recording
633
+ if (stopAVFoundationRecording()) {
598
634
  g_isRecording = false;
599
635
  g_usingStandaloneAudio = false;
636
+ MRLog(@"✅ AVFoundation recording stopped");
600
637
  return Napi::Boolean::New(env, true);
601
638
  } else {
602
639
  NSLog(@"❌ Failed to stop AVFoundation recording");
603
- if (g_usingStandaloneAudio && isStandaloneAudioRecording()) {
604
- stopStandaloneAudioRecording();
605
- }
606
- if (isCameraRecording()) {
607
- stopCameraRecording();
608
- }
609
640
  g_isRecording = false;
610
641
  g_usingStandaloneAudio = false;
611
642
  return Napi::Boolean::New(env, false);
@@ -614,6 +645,7 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
614
645
  } @catch (NSException *exception) {
615
646
  NSLog(@"❌ Exception stopping AVFoundation: %@", exception.reason);
616
647
  g_isRecording = false;
648
+ g_usingStandaloneAudio = false;
617
649
  return Napi::Boolean::New(env, false);
618
650
  }
619
651
 
@@ -792,6 +792,12 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
792
792
 
793
793
  MRLog(@"🛑 Stopping pure ScreenCaptureKit recording");
794
794
 
795
+ // CRITICAL FIX: Set cleanup flag IMMEDIATELY to prevent race conditions
796
+ // This prevents startRecording from being called while stop is in progress
797
+ @synchronized([ScreenCaptureKitRecorder class]) {
798
+ g_isCleaningUp = YES;
799
+ }
800
+
795
801
  // Store stream reference to prevent it from being deallocated
796
802
  SCStream *streamToStop = g_stream;
797
803
 
@@ -807,6 +813,7 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
807
813
  // Reset recording state to allow new recordings
808
814
  @synchronized([ScreenCaptureKitRecorder class]) {
809
815
  g_isRecording = NO;
816
+ g_isCleaningUp = NO; // CRITICAL: Reset cleanup flag when done
810
817
  }
811
818
 
812
819
  // Cleanup after stop completes
@@ -820,6 +827,19 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
820
827
  return g_isRecording;
821
828
  }
822
829
 
830
+ + (BOOL)isCleaningUp {
831
+ return g_isCleaningUp;
832
+ }
833
+
834
+ @end
835
+
836
+ // Export C function for checking cleanup state
837
+ BOOL isScreenCaptureKitCleaningUp() API_AVAILABLE(macos(12.3)) {
838
+ return [ScreenCaptureKitRecorder isCleaningUp];
839
+ }
840
+
841
+ @implementation ScreenCaptureKitRecorder (Methods)
842
+
823
843
  + (BOOL)setupVideoWriter {
824
844
  // No setup needed - SCRecordingOutput handles everything
825
845
  return YES;