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 +79 -1
- package/index.js +2 -2
- package/package.json +1 -1
- package/src/mac_recorder.mm +48 -16
- package/src/screen_capture_kit.mm +20 -0
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(
|
|
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
package/src/mac_recorder.mm
CHANGED
|
@@ -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
|
-
|
|
582
|
+
|
|
583
|
+
// CRITICAL FIX: Stop camera and audio FIRST (they are synchronous)
|
|
574
584
|
if (isCameraRecording()) {
|
|
575
|
-
|
|
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
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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;
|