node-mac-recorder 2.13.10 โ†’ 2.13.12

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.
@@ -28,7 +28,9 @@
28
28
  "Bash(ELECTRON_VERSION=25.0.0 node test-native-call.js)",
29
29
  "Bash(chmod:*)",
30
30
  "Bash(ffprobe:*)",
31
- "Bash(ffmpeg:*)"
31
+ "Bash(ffmpeg:*)",
32
+ "WebSearch",
33
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ” Testing with proper permissions and Electron env'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function test() {\n try {\n const outputPath = ''./test-output/proper-test.mov'';\n console.log(''๐Ÿ“น Starting recording...'');\n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: false\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 2 seconds'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โœ… Test completed'');\n } else {\n console.log(''โŒ Recording start failed'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\ntest();\n\")"
32
34
  ],
33
35
  "deny": []
34
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.13.10",
3
+ "version": "2.13.12",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -165,7 +165,7 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
165
165
  // Smart Recording Selection: ScreenCaptureKit vs Alternative
166
166
  NSLog(@"๐ŸŽฏ Smart Recording Engine Selection");
167
167
 
168
- // Detect Electron environment with multiple checks
168
+ // Electron environment detection (removed disable logic)
169
169
  BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
170
170
  [NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
171
171
  (NSProcessInfo.processInfo.processName &&
@@ -175,11 +175,8 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
175
175
  [NSBundle.mainBundle.bundlePath containsString:@"Electron"]);
176
176
 
177
177
  if (isElectron) {
178
- NSLog(@"โšก Electron environment detected - ScreenCaptureKit DISABLED for crash prevention");
179
- NSLog(@"๐Ÿ›ก๏ธ Recording not supported in Electron to prevent crashes");
180
- // Skip ScreenCaptureKit completely for Electron
181
- NSLog(@"โŒ Recording disabled in Electron for stability - use Node.js environment instead");
182
- return Napi::Boolean::New(env, false);
178
+ NSLog(@"โšก Electron environment detected - continuing with ScreenCaptureKit");
179
+ NSLog(@"โš ๏ธ Warning: ScreenCaptureKit in Electron may require additional stability measures");
183
180
  }
184
181
 
185
182
  // Non-Electron: Use ScreenCaptureKit
@@ -11,7 +11,7 @@ API_AVAILABLE(macos(12.3))
11
11
  error:(NSError **)error;
12
12
  + (void)stopRecording;
13
13
  + (BOOL)isRecording;
14
- + (void)setupVideoWriter;
14
+ + (BOOL)setupVideoWriter;
15
15
  + (void)finalizeVideoWriter;
16
16
  + (void)cleanupVideoWriter;
17
17
 
@@ -6,267 +6,100 @@ static id<SCStreamDelegate> g_streamDelegate = nil;
6
6
  static id<SCStreamOutput> g_streamOutput = nil;
7
7
  static BOOL g_isRecording = NO;
8
8
 
9
- // Electron-safe direct writing approach
9
+ // Modern ScreenCaptureKit writer
10
10
  static AVAssetWriter *g_assetWriter = nil;
11
11
  static AVAssetWriterInput *g_assetWriterInput = nil;
12
12
  static AVAssetWriterInputPixelBufferAdaptor *g_pixelBufferAdaptor = nil;
13
13
  static NSString *g_outputPath = nil;
14
- static CMTime g_startTime;
15
- static CMTime g_currentTime;
16
14
  static BOOL g_writerStarted = NO;
17
- static int g_frameNumber = 0;
15
+ static int g_frameCount = 0;
18
16
 
19
- @interface ElectronSafeDelegate : NSObject <SCStreamDelegate>
17
+ @interface ModernStreamDelegate : NSObject <SCStreamDelegate>
20
18
  @end
21
19
 
22
- @implementation ElectronSafeDelegate
20
+ @implementation ModernStreamDelegate
23
21
  - (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
24
- NSLog(@"๐Ÿ›‘ ScreenCaptureKit stream stopped in delegate");
22
+ NSLog(@"๐Ÿ›‘ Stream stopped");
25
23
  g_isRecording = NO;
26
24
 
27
25
  if (error) {
28
- NSLog(@"โŒ Stream stopped with error: %@", error);
29
- } else {
30
- NSLog(@"โœ… ScreenCaptureKit stream stopped successfully in delegate");
26
+ NSLog(@"โŒ Stream error: %@", error);
31
27
  }
32
28
 
33
- // Finalize video writer
34
- NSLog(@"๐ŸŽฌ Delegate calling finalizeVideoWriter...");
35
29
  [ScreenCaptureKitRecorder finalizeVideoWriter];
36
- NSLog(@"๐ŸŽฌ Delegate finished calling finalizeVideoWriter");
37
30
  }
38
31
  @end
39
32
 
40
- @interface ElectronSafeOutput : NSObject <SCStreamOutput>
41
- - (void)processSampleBufferSafely:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type;
33
+ @interface ModernStreamOutput : NSObject <SCStreamOutput>
42
34
  @end
43
35
 
44
- @implementation ElectronSafeOutput
36
+ @implementation ModernStreamOutput
45
37
  - (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
46
- // EXTREME SAFETY: Complete isolation with separate thread
47
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
48
- @autoreleasepool {
49
- [self processSampleBufferSafely:sampleBuffer ofType:type];
50
- }
51
- });
52
- }
53
-
54
- - (void)processSampleBufferSafely:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
55
- // ELECTRON CRASH PROTECTION: Multiple layers of safety
56
- if (!g_isRecording || !g_assetWriterInput) {
57
- NSLog(@"๐Ÿ” ProcessSampleBuffer: isRecording=%d, type=%d, writerInput=%p", g_isRecording, (int)type, g_assetWriterInput);
38
+ if (!g_isRecording) return;
39
+
40
+ // Only process screen frames
41
+ if (type != SCStreamOutputTypeScreen) return;
42
+
43
+ // Validate sample buffer
44
+ if (!sampleBuffer || !CMSampleBufferIsValid(sampleBuffer)) {
45
+ NSLog(@"โš ๏ธ Invalid sample buffer");
58
46
  return;
59
47
  }
60
48
 
61
- NSLog(@"๐Ÿ” ProcessSampleBuffer: Processing frame, type=%d (Screen=%d, Audio=%d)...", (int)type, (int)SCStreamOutputTypeScreen, (int)SCStreamOutputTypeAudio);
62
-
63
- // Process both screen and audio if available
64
- if (type == SCStreamOutputTypeAudio) {
65
- NSLog(@"๐Ÿ”Š Received audio sample buffer - skipping for video-only recording");
49
+ // Get pixel buffer
50
+ CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
51
+ if (!pixelBuffer) {
52
+ NSLog(@"โš ๏ธ No pixel buffer in sample");
66
53
  return;
67
54
  }
68
55
 
69
- if (type != SCStreamOutputTypeScreen) {
70
- NSLog(@"โš ๏ธ Unknown sample buffer type: %d", (int)type);
56
+ // Initialize writer on first frame
57
+ static dispatch_once_t onceToken;
58
+ dispatch_once(&onceToken, ^{
59
+ [self initializeWriterWithSampleBuffer:sampleBuffer];
60
+ });
61
+
62
+ if (!g_writerStarted) {
71
63
  return;
72
64
  }
73
65
 
74
- // SAFETY LAYER 1: Null checks
75
- if (!sampleBuffer || !CMSampleBufferIsValid(sampleBuffer)) {
76
- NSLog(@"โŒ LAYER 1 FAIL: Invalid sample buffer");
66
+ // Write frame
67
+ [self writePixelBuffer:pixelBuffer];
68
+ }
69
+
70
+ - (void)initializeWriterWithSampleBuffer:(CMSampleBufferRef)sampleBuffer {
71
+ if (!g_assetWriter) return;
72
+
73
+ NSLog(@"๐ŸŽฌ Initializing writer with first sample");
74
+
75
+ CMTime startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
76
+ if (!CMTIME_IS_VALID(startTime)) {
77
+ startTime = CMTimeMakeWithSeconds(0, 600);
78
+ }
79
+
80
+ [g_assetWriter startWriting];
81
+ [g_assetWriter startSessionAtSourceTime:startTime];
82
+ g_writerStarted = YES;
83
+
84
+ NSLog(@"โœ… Writer initialized");
85
+ }
86
+
87
+ - (void)writePixelBuffer:(CVPixelBufferRef)pixelBuffer {
88
+ if (!g_assetWriterInput.isReadyForMoreMediaData) {
77
89
  return;
78
90
  }
79
- NSLog(@"โœ… LAYER 1 PASS: Sample buffer valid");
80
-
81
- // SAFETY LAYER 2: Try-catch with complete isolation
82
- @try {
83
- @autoreleasepool {
84
- // SAFETY LAYER 3: Initialize writer safely (only once)
85
- static BOOL initializationAttempted = NO;
86
- if (!g_writerStarted && !initializationAttempted && g_assetWriter && g_assetWriterInput) {
87
- initializationAttempted = YES;
88
- @try {
89
- CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
90
-
91
- // SAFETY CHECK: Ensure valid time
92
- if (CMTIME_IS_VALID(presentationTime) && CMTIME_IS_NUMERIC(presentationTime)) {
93
- g_startTime = presentationTime;
94
- g_currentTime = g_startTime;
95
-
96
- // SAFETY LAYER 4: Writer state validation
97
- if (g_assetWriter.status == AVAssetWriterStatusUnknown) {
98
- [g_assetWriter startWriting];
99
- [g_assetWriter startSessionAtSourceTime:g_startTime];
100
- g_writerStarted = YES;
101
- NSLog(@"โœ… Ultra-safe ScreenCaptureKit writer started");
102
- }
103
- } else {
104
- // Use current time if sample buffer time is invalid
105
- NSLog(@"โš ๏ธ Invalid sample buffer time, using current time");
106
- g_startTime = CMTimeMakeWithSeconds(CACurrentMediaTime(), 600);
107
- g_currentTime = g_startTime;
108
-
109
- if (g_assetWriter.status == AVAssetWriterStatusUnknown) {
110
- [g_assetWriter startWriting];
111
- [g_assetWriter startSessionAtSourceTime:g_startTime];
112
- g_writerStarted = YES;
113
- NSLog(@"โœ… Ultra-safe ScreenCaptureKit writer started with current time");
114
- }
115
- }
116
- } @catch (NSException *writerException) {
117
- NSLog(@"โš ๏ธ Writer initialization failed safely: %@", writerException.reason);
118
- return;
119
- }
120
- }
121
-
122
- // SAFETY LAYER 5: Frame processing with isolation
123
- if (!g_writerStarted || !g_assetWriterInput || !g_pixelBufferAdaptor) {
124
- NSLog(@"โŒ LAYER 5 FAIL: writer=%d, input=%p, adaptor=%p", g_writerStarted, g_assetWriterInput, g_pixelBufferAdaptor);
125
- return;
126
- }
127
- NSLog(@"โœ… LAYER 5 PASS: Writer components ready");
128
-
129
- // SAFETY LAYER 6: Higher frame rate for video
130
- static NSTimeInterval lastProcessTime = 0;
131
- NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
132
- if (currentTime - lastProcessTime < 0.033) { // Max 30 FPS
133
- NSLog(@"โŒ LAYER 6 FAIL: Rate limited (%.3fs since last)", currentTime - lastProcessTime);
134
- return;
135
- }
136
- lastProcessTime = currentTime;
137
- NSLog(@"โœ… LAYER 6 PASS: Rate limiting OK");
138
-
139
- // SAFETY LAYER 7: Input readiness check
140
- if (!g_assetWriterInput.isReadyForMoreMediaData) {
141
- NSLog(@"โŒ LAYER 7 FAIL: Writer not ready for data");
142
- return;
143
- }
144
- NSLog(@"โœ… LAYER 7 PASS: Writer ready for data");
145
-
146
- // SAFETY LAYER 8: Get pixel buffer from sample buffer
147
- CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
148
- BOOL createdDummyBuffer = NO;
149
-
150
- if (!pixelBuffer) {
151
- // Try alternative methods to get pixel buffer
152
- CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
153
- if (formatDesc) {
154
- CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDesc);
155
- NSLog(@"๐Ÿ” Sample buffer media type: %u (Video=%u)", (unsigned int)mediaType, (unsigned int)kCMMediaType_Video);
156
- return; // Skip processing if no pixel buffer
157
- } else {
158
- NSLog(@"โŒ No pixel buffer and no format description - permissions issue");
159
-
160
- // Create a dummy pixel buffer using the pool from adaptor
161
- CVPixelBufferRef dummyBuffer = NULL;
162
-
163
- // Try to get a pixel buffer from the adaptor's buffer pool
164
- CVPixelBufferPoolRef bufferPool = g_pixelBufferAdaptor.pixelBufferPool;
165
- if (bufferPool) {
166
- CVReturn poolResult = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, bufferPool, &dummyBuffer);
167
- if (poolResult == kCVReturnSuccess && dummyBuffer) {
168
- pixelBuffer = dummyBuffer;
169
- createdDummyBuffer = YES;
170
- NSLog(@"โœ… Created dummy buffer from adaptor pool");
171
-
172
- // Fill buffer with black pixels
173
- CVPixelBufferLockBaseAddress(pixelBuffer, 0);
174
- void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
175
- size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
176
- size_t height = CVPixelBufferGetHeight(pixelBuffer);
177
- if (baseAddress) {
178
- memset(baseAddress, 0, bytesPerRow * height);
179
- }
180
- CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
181
- } else {
182
- NSLog(@"โŒ Failed to create buffer from pool: %d", poolResult);
183
- }
184
- }
185
-
186
- // Fallback: create manual buffer if pool method failed
187
- if (!dummyBuffer) {
188
- CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
189
- 1920, 1080,
190
- kCVPixelFormatType_32BGRA,
191
- NULL, &dummyBuffer);
192
- if (result == kCVReturnSuccess && dummyBuffer) {
193
- pixelBuffer = dummyBuffer;
194
- createdDummyBuffer = YES;
195
- NSLog(@"โœ… Created manual dummy buffer");
196
-
197
- // Fill buffer with black pixels
198
- CVPixelBufferLockBaseAddress(pixelBuffer, 0);
199
- void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
200
- size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
201
- size_t height = CVPixelBufferGetHeight(pixelBuffer);
202
- if (baseAddress) {
203
- memset(baseAddress, 0, bytesPerRow * height);
204
- }
205
- CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
206
- } else {
207
- NSLog(@"โŒ Failed to create dummy pixel buffer");
208
- return;
209
- }
210
- }
211
- }
212
- }
213
- NSLog(@"โœ… LAYER 8 PASS: Pixel buffer ready (dummy=%d)", createdDummyBuffer);
214
-
215
- // SAFETY LAYER 9: Dimension validation - flexible this time
216
- size_t width = CVPixelBufferGetWidth(pixelBuffer);
217
- size_t height = CVPixelBufferGetHeight(pixelBuffer);
218
- if (width == 0 || height == 0 || width > 4096 || height > 4096) {
219
- NSLog(@"โŒ LAYER 9 FAIL: Invalid dimensions %zux%zu", width, height);
220
- return; // Skip only if clearly invalid
221
- }
222
- NSLog(@"โœ… LAYER 9 PASS: Valid dimensions %zux%zu", width, height);
223
-
224
- // SAFETY LAYER 10: Time validation - use sequential timing
225
- g_frameNumber++;
226
-
227
- // Create sequential time stamps
228
- CMTime relativeTime = CMTimeMake(g_frameNumber, 30); // 30 FPS sequential
229
-
230
- if (!CMTIME_IS_VALID(relativeTime)) {
231
- return;
232
- }
233
-
234
- double seconds = CMTimeGetSeconds(relativeTime);
235
- if (seconds > 30.0) { // Max 30 seconds
236
- return;
237
- }
238
-
239
- // SAFETY LAYER 11: Append with complete exception handling
240
- @try {
241
- // Use pixel buffer directly - copy was causing errors
242
- NSLog(@"๐Ÿ” Attempting to append frame %d with time %.3fs", g_frameNumber, seconds);
243
- BOOL success = [g_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:relativeTime];
244
-
245
- if (success) {
246
- g_currentTime = relativeTime;
247
- static int ultraSafeFrameCount = 0;
248
- ultraSafeFrameCount++;
249
- NSLog(@"โœ… Frame %d appended successfully! (%.1fs)", ultraSafeFrameCount, seconds);
250
- } else {
251
- NSLog(@"โŒ Failed to append frame %d - adaptor rejected", g_frameNumber);
252
- }
253
- } @catch (NSException *appendException) {
254
- NSLog(@"๐Ÿ›ก๏ธ Append exception handled safely: %@", appendException.reason);
255
- // Continue gracefully - don't crash
256
- }
257
-
258
- // Cleanup dummy pixel buffer if we created one
259
- if (pixelBuffer && createdDummyBuffer) {
260
- CVPixelBufferRelease(pixelBuffer);
261
- NSLog(@"๐Ÿงน Released dummy pixel buffer");
262
- }
263
- }
264
- } @catch (NSException *outerException) {
265
- NSLog(@"๐Ÿ›ก๏ธ Outer exception handled: %@", outerException.reason);
266
- // Ultimate safety - graceful continue
267
- } @catch (...) {
268
- NSLog(@"๐Ÿ›ก๏ธ Unknown exception caught and handled safely");
269
- // Catch any C++ exceptions too
91
+
92
+ // Create time for this frame
93
+ CMTime frameTime = CMTimeMakeWithSeconds(g_frameCount / 30.0, 600);
94
+ g_frameCount++;
95
+
96
+ // Write the frame
97
+ BOOL success = [g_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
98
+
99
+ if (success) {
100
+ NSLog(@"โœ… Frame %d written", g_frameCount);
101
+ } else {
102
+ NSLog(@"โŒ Failed to write frame %d", g_frameCount);
270
103
  }
271
104
  }
272
105
  @end
@@ -282,62 +115,63 @@ static int g_frameNumber = 0;
282
115
 
283
116
  + (BOOL)startRecordingWithConfiguration:(NSDictionary *)config delegate:(id)delegate error:(NSError **)error {
284
117
  if (g_isRecording) {
118
+ NSLog(@"โš ๏ธ Already recording");
285
119
  return NO;
286
120
  }
287
121
 
288
122
  g_outputPath = config[@"outputPath"];
289
- g_writerStarted = NO;
290
- g_frameNumber = 0; // Reset frame counter for new recording
123
+ g_frameCount = 0;
291
124
 
292
- // Setup Electron-safe video writer
293
- [ScreenCaptureKitRecorder setupVideoWriter];
125
+ NSLog(@"๐ŸŽฌ Starting modern ScreenCaptureKit recording");
294
126
 
295
- NSLog(@"๐ŸŽฌ Starting Electron-safe ScreenCaptureKit recording");
127
+ // Setup writer first
128
+ if (![self setupVideoWriter]) {
129
+ NSLog(@"โŒ Failed to setup video writer");
130
+ return NO;
131
+ }
296
132
 
133
+ // Get shareable content
297
134
  [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *content, NSError *contentError) {
298
135
  if (contentError) {
299
- NSLog(@"โŒ Failed to get content: %@", contentError);
136
+ NSLog(@"โŒ Content error: %@", contentError);
300
137
  return;
301
138
  }
302
139
 
303
- NSLog(@"โœ… Got shareable content with %lu displays", (unsigned long)content.displays.count);
140
+ NSLog(@"โœ… Got %lu displays", content.displays.count);
304
141
 
305
142
  if (content.displays.count == 0) {
306
- NSLog(@"โŒ No displays available for recording");
307
- return;
308
- }
309
-
310
- // Get primary display
311
- SCDisplay *targetDisplay = content.displays.firstObject;
312
- if (!targetDisplay) {
313
- NSLog(@"โŒ No target display found");
143
+ NSLog(@"โŒ No displays found");
314
144
  return;
315
145
  }
316
146
 
317
- NSLog(@"๐Ÿ–ฅ๏ธ Using display: %@ (%dx%d)", @(targetDisplay.displayID), (int)targetDisplay.width, (int)targetDisplay.height);
147
+ // Use first display
148
+ SCDisplay *display = content.displays.firstObject;
149
+ NSLog(@"๐Ÿ–ฅ๏ธ Using display %u (%dx%d)", display.displayID, (int)display.width, (int)display.height);
318
150
 
319
- // Create content filter for entire display - NO exclusions
320
- SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:@[]];
321
- NSLog(@"โœ… Content filter created for display");
151
+ // Create filter for entire display
152
+ SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:display excludingWindows:@[]];
322
153
 
323
- // Stream configuration - fixed resolution to avoid permissions issues
154
+ // Configure stream
324
155
  SCStreamConfiguration *streamConfig = [[SCStreamConfiguration alloc] init];
325
- streamConfig.width = 1920;
326
- streamConfig.height = 1080;
156
+
157
+ // Use display's actual dimensions but scale if too large
158
+ NSInteger targetWidth = MIN(display.width, 1920);
159
+ NSInteger targetHeight = MIN(display.height, 1080);
160
+
161
+ streamConfig.width = targetWidth;
162
+ streamConfig.height = targetHeight;
327
163
  streamConfig.minimumFrameInterval = CMTimeMake(1, 30); // 30 FPS
328
164
  streamConfig.pixelFormat = kCVPixelFormatType_32BGRA;
329
165
  streamConfig.showsCursor = YES;
166
+ streamConfig.scalesToFit = YES;
330
167
 
331
- NSLog(@"๐Ÿ”ง Stream config: %zux%zu, pixelFormat=%u, FPS=30", streamConfig.width, streamConfig.height, (unsigned)streamConfig.pixelFormat);
168
+ NSLog(@"๐Ÿ”ง Stream: %ldx%ld @ 30fps", targetWidth, targetHeight);
332
169
 
333
- // Create Electron-safe delegates
334
- g_streamDelegate = [[ElectronSafeDelegate alloc] init];
335
- g_streamOutput = [[ElectronSafeOutput alloc] init];
170
+ // Create delegates
171
+ g_streamDelegate = [[ModernStreamDelegate alloc] init];
172
+ g_streamOutput = [[ModernStreamOutput alloc] init];
336
173
 
337
- NSLog(@"๐Ÿค Delegates created");
338
-
339
- // Create stream
340
- NSError *streamError = nil;
174
+ // Create and start stream
341
175
  g_stream = [[SCStream alloc] initWithFilter:filter configuration:streamConfig delegate:g_streamDelegate];
342
176
 
343
177
  if (!g_stream) {
@@ -345,27 +179,27 @@ static int g_frameNumber = 0;
345
179
  return;
346
180
  }
347
181
 
348
- NSLog(@"โœ… Stream created successfully");
349
-
350
- // Add stream output with explicit error checking
351
- BOOL outputResult = [g_stream addStreamOutput:g_streamOutput
352
- type:SCStreamOutputTypeScreen
353
- sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
354
- error:&streamError];
182
+ // Add output
183
+ NSError *outputError = nil;
184
+ BOOL outputAdded = [g_stream addStreamOutput:g_streamOutput
185
+ type:SCStreamOutputTypeScreen
186
+ sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
187
+ error:&outputError];
355
188
 
356
- if (!outputResult || streamError) {
357
- NSLog(@"โŒ Failed to add stream output: %@", streamError);
189
+ if (!outputAdded || outputError) {
190
+ NSLog(@"โŒ Output error: %@", outputError);
358
191
  return;
359
192
  }
360
193
 
361
- NSLog(@"โœ… Stream output added successfully");
194
+ NSLog(@"โœ… Output added");
362
195
 
196
+ // Start capture
363
197
  [g_stream startCaptureWithCompletionHandler:^(NSError *startError) {
364
198
  if (startError) {
365
- NSLog(@"โŒ Failed to start capture: %@", startError);
199
+ NSLog(@"โŒ Start error: %@", startError);
366
200
  g_isRecording = NO;
367
201
  } else {
368
- NSLog(@"โœ… Frame capture started successfully");
202
+ NSLog(@"โœ… Capture started successfully!");
369
203
  g_isRecording = YES;
370
204
  }
371
205
  }];
@@ -379,19 +213,14 @@ static int g_frameNumber = 0;
379
213
  return;
380
214
  }
381
215
 
382
- NSLog(@"๐Ÿ›‘ Stopping Electron-safe ScreenCaptureKit recording");
216
+ NSLog(@"๐Ÿ›‘ Stopping recording");
383
217
 
384
- [g_stream stopCaptureWithCompletionHandler:^(NSError *stopError) {
385
- if (stopError) {
386
- NSLog(@"โŒ Stop error: %@", stopError);
387
- } else {
388
- NSLog(@"โœ… ScreenCaptureKit stream stopped in completion handler");
218
+ [g_stream stopCaptureWithCompletionHandler:^(NSError *error) {
219
+ if (error) {
220
+ NSLog(@"โŒ Stop error: %@", error);
389
221
  }
390
-
391
- // Finalize video since delegate might not be called
392
- NSLog(@"๐ŸŽฌ Completion handler calling finalizeVideoWriter...");
222
+ NSLog(@"โœ… Stream stopped");
393
223
  [ScreenCaptureKitRecorder finalizeVideoWriter];
394
- NSLog(@"๐ŸŽฌ Completion handler finished calling finalizeVideoWriter");
395
224
  }];
396
225
  }
397
226
 
@@ -399,12 +228,10 @@ static int g_frameNumber = 0;
399
228
  return g_isRecording;
400
229
  }
401
230
 
402
- + (void)setupVideoWriter {
403
- if (g_assetWriter) {
404
- return; // Already setup
405
- }
231
+ + (BOOL)setupVideoWriter {
232
+ if (g_assetWriter) return YES;
406
233
 
407
- NSLog(@"๐Ÿ”ง Setting up Electron-safe video writer");
234
+ NSLog(@"๐Ÿ”ง Setting up video writer");
408
235
 
409
236
  NSURL *outputURL = [NSURL fileURLWithPath:g_outputPath];
410
237
  NSError *error = nil;
@@ -412,66 +239,67 @@ static int g_frameNumber = 0;
412
239
  g_assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:&error];
413
240
 
414
241
  if (error || !g_assetWriter) {
415
- NSLog(@"โŒ Failed to create asset writer: %@", error);
416
- return;
242
+ NSLog(@"โŒ Writer creation error: %@", error);
243
+ return NO;
417
244
  }
418
245
 
419
- // Fixed video settings for compatibility
246
+ // Video settings
420
247
  NSDictionary *videoSettings = @{
421
248
  AVVideoCodecKey: AVVideoCodecTypeH264,
422
249
  AVVideoWidthKey: @1920,
423
250
  AVVideoHeightKey: @1080,
424
251
  AVVideoCompressionPropertiesKey: @{
425
- AVVideoAverageBitRateKey: @(1920 * 1080 * 2), // 2 bits per pixel
426
- AVVideoMaxKeyFrameIntervalKey: @30,
427
- AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel
252
+ AVVideoAverageBitRateKey: @(5000000), // 5 Mbps
253
+ AVVideoMaxKeyFrameIntervalKey: @30
428
254
  }
429
255
  };
430
256
 
431
257
  g_assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
432
- g_assetWriterInput.expectsMediaDataInRealTime = NO; // Safer for Electron
258
+ g_assetWriterInput.expectsMediaDataInRealTime = YES;
433
259
 
434
- // Pixel buffer attributes matching ScreenCaptureKit format
260
+ // Pixel buffer adaptor
435
261
  NSDictionary *pixelBufferAttributes = @{
436
262
  (NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
437
263
  (NSString*)kCVPixelBufferWidthKey: @1920,
438
264
  (NSString*)kCVPixelBufferHeightKey: @1080
439
265
  };
440
266
 
441
- g_pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:g_assetWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];
267
+ g_pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor
268
+ assetWriterInputPixelBufferAdaptorWithAssetWriterInput:g_assetWriterInput
269
+ sourcePixelBufferAttributes:pixelBufferAttributes];
442
270
 
443
271
  if ([g_assetWriter canAddInput:g_assetWriterInput]) {
444
272
  [g_assetWriter addInput:g_assetWriterInput];
445
- NSLog(@"โœ… Electron-safe video writer setup complete");
273
+ NSLog(@"โœ… Video writer ready");
274
+ return YES;
446
275
  } else {
447
- NSLog(@"โŒ Failed to add input to asset writer");
276
+ NSLog(@"โŒ Cannot add input to writer");
277
+ return NO;
448
278
  }
449
279
  }
450
280
 
451
281
  + (void)finalizeVideoWriter {
452
- NSLog(@"๐ŸŽฌ Finalizing video writer - writer: %p, started: %d", g_assetWriter, g_writerStarted);
282
+ NSLog(@"๐ŸŽฌ Finalizing video");
283
+
284
+ g_isRecording = NO;
453
285
 
454
286
  if (!g_assetWriter || !g_writerStarted) {
455
- NSLog(@"โš ๏ธ Video writer not started properly - writer: %p, started: %d", g_assetWriter, g_writerStarted);
456
- [ScreenCaptureKitRecorder cleanupVideoWriter];
287
+ NSLog(@"โš ๏ธ Writer not ready for finalization");
288
+ [self cleanupVideoWriter];
457
289
  return;
458
290
  }
459
291
 
460
- NSLog(@"๐ŸŽฌ Marking input as finished and finalizing...");
461
292
  [g_assetWriterInput markAsFinished];
462
293
 
463
294
  [g_assetWriter finishWritingWithCompletionHandler:^{
464
- NSLog(@"๐ŸŽฌ Finalization completion handler called");
465
295
  if (g_assetWriter.status == AVAssetWriterStatusCompleted) {
466
- NSLog(@"โœ… Video finalization successful: %@", g_outputPath);
296
+ NSLog(@"โœ… Video saved: %@", g_outputPath);
467
297
  } else {
468
- NSLog(@"โŒ Video finalization failed - status: %ld, error: %@", (long)g_assetWriter.status, g_assetWriter.error);
298
+ NSLog(@"โŒ Write failed: %@", g_assetWriter.error);
469
299
  }
470
300
 
471
301
  [ScreenCaptureKitRecorder cleanupVideoWriter];
472
302
  }];
473
-
474
- NSLog(@"๐ŸŽฌ Finalization request submitted, waiting for completion...");
475
303
  }
476
304
 
477
305
  + (void)cleanupVideoWriter {
@@ -479,12 +307,12 @@ static int g_frameNumber = 0;
479
307
  g_assetWriterInput = nil;
480
308
  g_pixelBufferAdaptor = nil;
481
309
  g_writerStarted = NO;
482
- g_frameNumber = 0; // Reset frame counter
310
+ g_frameCount = 0;
483
311
  g_stream = nil;
484
312
  g_streamDelegate = nil;
485
313
  g_streamOutput = nil;
486
314
 
487
- NSLog(@"๐Ÿงน Video writer cleanup complete");
315
+ NSLog(@"๐Ÿงน Cleanup complete");
488
316
  }
489
317
 
490
318
  @end