node-mac-recorder 2.21.39 → 2.21.41

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.
@@ -0,0 +1,243 @@
1
+ # Dual/Multiple Window Recording Implementation Plan
2
+
3
+ ## Problem
4
+ Current implementation uses **global state** in native code, allowing only ONE recording at a time.
5
+
6
+ ## Goal
7
+ Support **multiple simultaneous recordings** - each with its own:
8
+ - Window/Display target
9
+ - Output file (temp_screen_0_xxx.mov, temp_screen_1_xxx.mov, etc.)
10
+ - Independent start/stop control
11
+
12
+ ## Required Changes
13
+
14
+ ### 1. Native Code Refactoring (screen_capture_kit.mm)
15
+
16
+ #### Current (Global State):
17
+ ```objc
18
+ static SCStream *g_stream = nil;
19
+ static BOOL g_isRecording = NO;
20
+ static NSString *g_outputPath = nil;
21
+ static AVAssetWriter *g_videoWriter = nil;
22
+ ```
23
+
24
+ #### New (Session-Based State):
25
+ ```objc
26
+ // Recording session structure
27
+ @interface RecordingSession : NSObject
28
+ @property (nonatomic, strong) NSString *sessionId;
29
+ @property (nonatomic, strong) SCStream *stream;
30
+ @property (nonatomic, strong) NSString *outputPath;
31
+ @property (nonatomic, strong) AVAssetWriter *videoWriter;
32
+ @property (nonatomic, strong) AVAssetWriterInput *videoInput;
33
+ @property (nonatomic, strong) dispatch_queue_t videoQueue;
34
+ @property (nonatomic, assign) BOOL isRecording;
35
+ @property (nonatomic, assign) CMTime startTime;
36
+ // ... other session-specific state
37
+ @end
38
+
39
+ // Global session registry
40
+ static NSMutableDictionary<NSString *, RecordingSession *> *g_sessions = nil;
41
+ static dispatch_queue_t g_sessionsQueue = nil;
42
+ ```
43
+
44
+ #### Key Changes:
45
+
46
+ 1. **Session Management Functions**:
47
+ ```objc
48
+ + (NSString *)createRecordingSession;
49
+ + (RecordingSession *)getSession:(NSString *)sessionId;
50
+ + (void)removeSession:(NSString *)sessionId;
51
+ + (NSArray<NSString *> *)getActiveSessions;
52
+ ```
53
+
54
+ 2. **Modified API**:
55
+ ```objc
56
+ // Old: + (BOOL)startRecordingWithConfiguration:(NSDictionary *)config
57
+ // New:
58
+ + (NSString *)startRecordingWithConfiguration:(NSDictionary *)config
59
+ delegate:(id)delegate
60
+ error:(NSError **)error;
61
+ // Returns: sessionId (e.g., "rec_1762850131780")
62
+
63
+ // Old: + (void)stopRecording;
64
+ // New:
65
+ + (BOOL)stopRecording:(NSString *)sessionId;
66
+ ```
67
+
68
+ 3. **Stream Output Delegates**:
69
+ - Each session needs its own video/audio output delegates
70
+ - Delegates must know which session they belong to
71
+ - Frame callbacks route to correct writer
72
+
73
+ ### 2. JavaScript API Updates (index.js)
74
+
75
+ #### Add Session Support:
76
+
77
+ ```javascript
78
+ class MacRecorder extends EventEmitter {
79
+ constructor() {
80
+ super();
81
+ this.sessionId = null; // Unique session ID from native
82
+ this.isRecording = false;
83
+ // ... existing code
84
+ }
85
+
86
+ async startRecording(outputPath, options = {}) {
87
+ // ... existing setup code ...
88
+
89
+ // Start native recording with session support
90
+ const recordingOptions = {
91
+ ...options,
92
+ // Request specific session management
93
+ createNewSession: true
94
+ };
95
+
96
+ // Native returns sessionId
97
+ const result = nativeBinding.startRecording(
98
+ outputPath,
99
+ recordingOptions
100
+ );
101
+
102
+ this.sessionId = result.sessionId;
103
+ this.isRecording = true;
104
+ // ...
105
+ }
106
+
107
+ async stopRecording() {
108
+ if (!this.sessionId) {
109
+ throw new Error("No active recording session");
110
+ }
111
+
112
+ // Stop specific session
113
+ const success = nativeBinding.stopRecording(this.sessionId);
114
+ // ...
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### 3. File Naming Strategy
120
+
121
+ When multiple recordings are active:
122
+
123
+ ```javascript
124
+ // First recording
125
+ const timestamp = Date.now();
126
+ const outputPath1 = `temp_screen_${timestamp}.mov`;
127
+
128
+ // Second recording (same timestamp, different index)
129
+ const outputPath2 = `temp_screen_1_${timestamp}.mov`;
130
+
131
+ // Third recording
132
+ const outputPath3 = `temp_screen_2_${timestamp}.mov`;
133
+ ```
134
+
135
+ Or use session IDs:
136
+ ```javascript
137
+ const outputPath = `temp_screen_${sessionId}.mov`;
138
+ ```
139
+
140
+ ## Implementation Steps
141
+
142
+ ### Phase 1: Core Session Infrastructure ✅
143
+ - [ ] Create RecordingSession class
144
+ - [ ] Add session registry (Map/Dictionary)
145
+ - [ ] Implement session lifecycle methods
146
+ - [ ] Add thread-safe session access
147
+
148
+ ### Phase 2: Refactor Native Recording ⏳
149
+ - [ ] Update startRecording to return sessionId
150
+ - [ ] Modify video output delegates to use session
151
+ - [ ] Modify audio output delegates to use session
152
+ - [ ] Update stopRecording to accept sessionId
153
+ - [ ] Test single session (backward compatibility)
154
+
155
+ ### Phase 3: Multi-Session Support 🔜
156
+ - [ ] Test two simultaneous recordings
157
+ - [ ] Test different targets (window vs display)
158
+ - [ ] Add session limits (max 4 simultaneous?)
159
+ - [ ] Handle memory/performance implications
160
+
161
+ ### Phase 4: JavaScript API 🔜
162
+ - [ ] Update MacRecorder to use sessions
163
+ - [ ] Add getActiveSessions() method
164
+ - [ ] Add getAllRecordingStatuses() method
165
+ - [ ] Update documentation
166
+
167
+ ### Phase 5: Testing 🧪
168
+ - [ ] Test dual window recording
169
+ - [ ] Test dual display recording
170
+ - [ ] Test mixed (window + display)
171
+ - [ ] Performance benchmarks
172
+ - [ ] Memory usage tests
173
+
174
+ ## Technical Considerations
175
+
176
+ ### Memory & Performance
177
+ - Each SCStream captures frames independently
178
+ - 2 recordings @ 1080p 60fps = ~240MB/s uncompressed
179
+ - Limit simultaneous recordings (recommend max 4)
180
+ - Add memory warnings
181
+
182
+ ### Thread Safety
183
+ - Use dispatch_queue for session access
184
+ - Prevent race conditions on session creation/removal
185
+ - Careful with delegate callbacks
186
+
187
+ ### File Naming
188
+ - Option A: Use indices (temp_screen_0_xxx, temp_screen_1_xxx)
189
+ - Option B: Use session IDs (temp_screen_rec_xxx)
190
+ - Option C: Let user specify base name
191
+
192
+ ### Backward Compatibility
193
+ - Single recording should work as before
194
+ - Default behavior: create implicit session
195
+ - Advanced users: explicit session management
196
+
197
+ ## Example Usage
198
+
199
+ ```javascript
200
+ const MacRecorder = require('node-mac-recorder');
201
+
202
+ async function recordTwoWindows() {
203
+ const recorder1 = new MacRecorder();
204
+ const recorder2 = new MacRecorder();
205
+
206
+ const windows = await recorder1.getWindows();
207
+
208
+ // Start both recordings
209
+ await recorder1.startRecording('output/window1.mov', {
210
+ windowId: windows[0].id
211
+ });
212
+
213
+ await recorder2.startRecording('output/window2.mov', {
214
+ windowId: windows[1].id
215
+ });
216
+
217
+ // Record for 10 seconds
218
+ await new Promise(r => setTimeout(r, 10000));
219
+
220
+ // Stop both
221
+ await recorder1.stopRecording();
222
+ await recorder2.stopRecording();
223
+
224
+ console.log('Both recordings complete!');
225
+ }
226
+ ```
227
+
228
+ ## Timeline Estimate
229
+
230
+ - **Phase 1-2**: 4-6 hours (core infrastructure)
231
+ - **Phase 3**: 2-3 hours (multi-session testing)
232
+ - **Phase 4**: 1-2 hours (JS API updates)
233
+ - **Phase 5**: 2-3 hours (comprehensive testing)
234
+
235
+ **Total: ~10-14 hours** for complete implementation
236
+
237
+ ## Questions to Decide
238
+
239
+ 1. **Session Limit**: Max how many simultaneous recordings? (Recommend 2-4)
240
+ 2. **File Naming**: Automatic indices or user-specified?
241
+ 3. **API Style**: Explicit sessions or implicit (current MacRecorder instances)?
242
+ 4. **Performance**: Add automatic quality reduction for multiple streams?
243
+ 5. **Error Handling**: What if one session fails? Stop all or continue others?
@@ -0,0 +1,270 @@
1
+ # Multi-Window/Display Recording
2
+
3
+ ## 🎉 Özellik: Aynı Anda Birden Fazla Kayıt
4
+
5
+ node-mac-recorder artık **aynı anda birden fazla pencere veya ekranı** kaydetme yeteneğine sahip!
6
+
7
+ ### ✨ Nasıl Çalışıyor?
8
+
9
+ **Child Process Yaklaşımı**: Her `MacRecorder` instance'ı kendi ayrı Node.js process'inde çalışır. Bu sayede:
10
+
11
+ - ✅ Native kod değişikliği **GEREKMEDİ**
12
+ - ✅ Her process kendi **bağımsız state**'ine sahip
13
+ - ✅ Gerçek **paralel kayıt** (aynı anda)
14
+ - ✅ Kolay kullanım - sadece yeni bir class kullan!
15
+
16
+ ## 📖 Kullanım
17
+
18
+ ### Basit Örnek - İki Display Kaydı
19
+
20
+ ```javascript
21
+ const MacRecorder = require('./index-multiprocess');
22
+
23
+ async function recordTwoDisplays() {
24
+ // Her recorder kendi process'inde çalışır
25
+ const recorder1 = new MacRecorder();
26
+ const recorder2 = new MacRecorder();
27
+
28
+ // Display'leri al
29
+ const displays = await recorder1.getDisplays();
30
+
31
+ // İki kaydı başlat (sırayla - ScreenCaptureKit init için)
32
+ await recorder1.startRecording('output/display1.mov', {
33
+ displayId: displays[0].id,
34
+ frameRate: 30
35
+ });
36
+
37
+ await new Promise(r => setTimeout(r, 1000)); // Kısa bekleme
38
+
39
+ await recorder2.startRecording('output/display2.mov', {
40
+ displayId: displays[1]?.id || displays[0].id,
41
+ frameRate: 30
42
+ });
43
+
44
+ // İkisi de aynı anda kaydediyor!
45
+ console.log('📹 İki display aynı anda kaydediliyor...');
46
+
47
+ // 10 saniye kaydet
48
+ await new Promise(r => setTimeout(r, 10000));
49
+
50
+ // İkisini de durdur
51
+ await recorder1.stopRecording();
52
+ await recorder2.stopRecording();
53
+
54
+ // Cleanup
55
+ recorder1.destroy();
56
+ recorder2.destroy();
57
+
58
+ console.log('✅ Kayıtlar tamamlandı!');
59
+ }
60
+
61
+ recordTwoDisplays();
62
+ ```
63
+
64
+ ### İleri Seviye - Farklı Window'ları Kaydet
65
+
66
+ ```javascript
67
+ const MacRecorder = require('./index-multiprocess');
68
+
69
+ async function recordTwoWindows() {
70
+ const recorder1 = new MacRecorder();
71
+ const recorder2 = new MacRecorder();
72
+
73
+ // Açık pencereleri al
74
+ const windows = await recorder1.getWindows();
75
+
76
+ if (windows.length < 2) {
77
+ console.error('En az 2 pencere açık olmalı!');
78
+ return;
79
+ }
80
+
81
+ console.log(`Kaydedilecek pencereler:`);
82
+ console.log(`1. ${windows[0].appName} - ${windows[0].title}`);
83
+ console.log(`2. ${windows[1].appName} - ${windows[1].title}`);
84
+
85
+ // Event listeners
86
+ recorder1.on('recordingStarted', () => {
87
+ console.log('✅ Pencere 1 kaydı başladı');
88
+ });
89
+
90
+ recorder2.on('recordingStarted', () => {
91
+ console.log('✅ Pencere 2 kaydı başladı');
92
+ });
93
+
94
+ // İlk pencereyi kaydet
95
+ await recorder1.startRecording('output/window1.mov', {
96
+ windowId: windows[0].id,
97
+ captureCursor: true,
98
+ frameRate: 30
99
+ });
100
+
101
+ // 1 saniye bekle (ScreenCaptureKit init için)
102
+ await new Promise(r => setTimeout(r, 1000));
103
+
104
+ // İkinci pencereyi kaydet
105
+ await recorder2.startRecording('output/window2.mov', {
106
+ windowId: windows[1].id,
107
+ captureCursor: true,
108
+ frameRate: 30
109
+ });
110
+
111
+ // Her ikisi de paralel kaydediyor!
112
+ await new Promise(r => setTimeout(r, 10000));
113
+
114
+ // Durdur
115
+ await recorder1.stopRecording();
116
+ await recorder2.stopRecording();
117
+
118
+ // Cleanup
119
+ recorder1.destroy();
120
+ recorder2.destroy();
121
+ }
122
+
123
+ recordTwoWindows();
124
+ ```
125
+
126
+ ## 📝 Önemli Notlar
127
+
128
+ ### Zamanlama (Timing)
129
+
130
+ ScreenCaptureKit'in düzgün başlaması için **kayıtlar arasında ~1 saniye** bekleme gerekli:
131
+
132
+ ```javascript
133
+ await recorder1.startRecording(...);
134
+ await new Promise(r => setTimeout(r, 1000)); // ⚠️ ÖNEMLİ!
135
+ await recorder2.startRecording(...);
136
+ ```
137
+
138
+ ### Dosya Adlandırma
139
+
140
+ Her kayıt **farklı bir dosyaya** yazılmalı:
141
+
142
+ ```javascript
143
+ ✅ DOĞRU:
144
+ recorder1.startRecording('video1.mov');
145
+ recorder2.startRecording('video2.mov');
146
+
147
+ ❌ YANLIŞ:
148
+ recorder1.startRecording('video.mov');
149
+ recorder2.startRecording('video.mov'); // Aynı dosya!
150
+ ```
151
+
152
+ ### Performans
153
+
154
+ - **2 kayıt**: Sorunsuz çalışır
155
+ - **3-4 kayıt**: İyi çalışır, ancak CPU kullanımı artar
156
+ - **5+ kayıt**: Önerilmez - sistem yavaşlayabilir
157
+
158
+ ### Cleanup
159
+
160
+ Recording bittiğinde mutlaka `destroy()` çağırın:
161
+
162
+ ```javascript
163
+ recorder.destroy(); // Worker process'i temizler
164
+ ```
165
+
166
+ ## 🧪 Test
167
+
168
+ ```bash
169
+ # Multi-process test
170
+ node test-multiprocess.js
171
+
172
+ # İki display aynı anda
173
+ node test-dual-recording.js
174
+
175
+ # İki window aynı anda (en az 2 pencere açık olmalı)
176
+ node test-dual-window.js
177
+ ```
178
+
179
+ ## 🔧 Teknik Detaylar
180
+
181
+ ### Mimari
182
+
183
+ ```
184
+ Ana Process (Node.js)
185
+ ├── MacRecorderMultiProcess Instance 1
186
+ │ └── Worker Process 1 (ayrı Node.js process)
187
+ │ └── Native Binding (ScreenCaptureKit)
188
+ │ └── Video File 1
189
+
190
+ └── MacRecorderMultiProcess Instance 2
191
+ └── Worker Process 2 (ayrı Node.js process)
192
+ └── Native Binding (ScreenCaptureKit)
193
+ └── Video File 2
194
+ ```
195
+
196
+ ### IPC (Inter-Process Communication)
197
+
198
+ Worker process'ler ile iletişim:
199
+
200
+ ```javascript
201
+ // Ana process → Worker
202
+ worker.send({ type: 'startRecording', data: { ... } });
203
+
204
+ // Worker → Ana process
205
+ process.send({ type: 'event', event: 'recordingStarted', data: { ... } });
206
+ ```
207
+
208
+ ### Global State Sorunu - ÇÖZÜLDÜ! ✅
209
+
210
+ **Eski sorun**: Native kod global state kullanıyordu → Sadece 1 kayıt
211
+
212
+ **Çözüm**: Her worker ayrı process → Her biri kendi global state'i
213
+
214
+ ```
215
+ Process 1: g_isRecording = true (Video 1 kaydediyor)
216
+ Process 2: g_isRecording = true (Video 2 kaydediyor)
217
+ ↑ Ayrı memory space, conflict yok!
218
+ ```
219
+
220
+ ## ⚡ Performans İpuçları
221
+
222
+ 1. **Frame Rate**: Çoklu kayıt için 30 FPS yeterli (60 yerine)
223
+ 2. **Başlangıç Gecikmesi**: Her recorder arasında 1 saniye
224
+ 3. **Cleanup**: Kayıt bitince mutlaka `destroy()` çağır
225
+ 4. **Memory**: Her worker ~200MB kullanır
226
+
227
+ ## 🐛 Sorun Giderme
228
+
229
+ ### "Worker not ready" hatası
230
+ ```javascript
231
+ // Çözüm: Worker'ın hazır olmasını bekle
232
+ await new Promise(r => setTimeout(r, 500));
233
+ ```
234
+
235
+ ### Sadece bir dosya oluştu
236
+ ```javascript
237
+ // Çözüm: Kayıtlar arasında bekleme ekle
238
+ await recorder1.startRecording(...);
239
+ await new Promise(r => setTimeout(r, 1000)); // Ekle!
240
+ await recorder2.startRecording(...);
241
+ ```
242
+
243
+ ### Worker crash oluyor
244
+ ```javascript
245
+ // Çözüm: Event listener ekle
246
+ recorder.on('error', (err) => {
247
+ console.error('Worker error:', err);
248
+ });
249
+ ```
250
+
251
+ ## 📊 Karşılaştırma
252
+
253
+ | Özellik | Tek Process (index.js) | Multi-Process (index-multiprocess.js) |
254
+ |---------|------------------------|---------------------------------------|
255
+ | Aynı anda kayıt | ❌ Hayır | ✅ Evet |
256
+ | Native kod değişikliği | - | ❌ Gerek yok |
257
+ | Memory kullanımı | Düşük | Orta (worker başına ~200MB) |
258
+ | Kullanım kolaylığı | Kolay | Çok kolay |
259
+ | Performans | En iyi | İyi |
260
+
261
+ ## 🎯 Kullanım Senaryoları
262
+
263
+ 1. **Çoklu Monitor Kaydı**: Her monitörü ayrı dosyaya
264
+ 2. **Uygulama + Notlar**: Bir ekranda uygulama, diğerde notlar
265
+ 3. **Webinar + Kamera**: Ekran + webcam ayrı ayrı
266
+ 4. **Oyun + Chat**: Oyun penceresi + Discord ayrı
267
+
268
+ ## 📄 Lisans
269
+
270
+ Bu özellik node-mac-recorder'ın bir parçasıdır ve aynı lisans altındadır.