node-mac-recorder 2.13.8 β†’ 2.13.10

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/index.js CHANGED
@@ -404,13 +404,11 @@ class MacRecorder extends EventEmitter {
404
404
  this.emit("started", this.outputPath);
405
405
  resolve(this.outputPath);
406
406
  } else {
407
- // Try alternative recording method for Electron
408
- console.log('🎬 Native recording failed, trying alternative method for Electron compatibility');
409
- this.startAlternativeRecording(outputPath, recordingOptions)
410
- .then(() => resolve(outputPath))
411
- .catch((altError) => {
412
- reject(new Error(`All recording methods failed. Native: Check permissions. Alternative: ${altError.message}`));
413
- });
407
+ reject(
408
+ new Error(
409
+ "ScreenCaptureKit failed to start. Check permissions and try again."
410
+ )
411
+ );
414
412
  }
415
413
  } catch (error) {
416
414
  reject(error);
@@ -418,109 +416,6 @@ class MacRecorder extends EventEmitter {
418
416
  });
419
417
  }
420
418
 
421
- /**
422
- * Alternative recording method for Electron compatibility - REAL VIDEO
423
- */
424
- async startAlternativeRecording(outputPath, options = {}) {
425
- const { spawn } = require('child_process');
426
- const fs = require('fs');
427
- const path = require('path');
428
-
429
- try {
430
- console.log('🎬 Starting REAL video recording with FFmpeg for Electron');
431
-
432
- // Check if FFmpeg is available
433
- const ffmpegArgs = [
434
- '-f', 'avfoundation', // Use AVFoundation input
435
- '-framerate', '30', // 30 FPS
436
- '-video_size', '1280x720', // Resolution
437
- '-i', '3:none', // Screen capture device 3, no audio
438
- '-c:v', 'libx264', // H.264 codec
439
- '-preset', 'ultrafast', // Fast encoding
440
- '-crf', '23', // Quality
441
- '-t', '30', // Max 30 seconds
442
- '-y', // Overwrite output
443
- outputPath
444
- ];
445
-
446
- // Add capture area if specified (crop filter)
447
- if (options.captureArea) {
448
- const cropFilter = `crop=${options.captureArea.width}:${options.captureArea.height}:${options.captureArea.x}:${options.captureArea.y}`;
449
- const index = ffmpegArgs.indexOf(outputPath);
450
- ffmpegArgs.splice(index, 0, '-vf', cropFilter);
451
- }
452
-
453
- console.log('πŸŽ₯ Starting FFmpeg with args:', ffmpegArgs);
454
-
455
- this.alternativeProcess = spawn('ffmpeg', ffmpegArgs);
456
-
457
- this.alternativeProcess.stdout.on('data', (data) => {
458
- console.log('FFmpeg stdout:', data.toString());
459
- });
460
-
461
- this.alternativeProcess.stderr.on('data', (data) => {
462
- const output = data.toString();
463
- if (output.includes('frame=')) {
464
- // This indicates recording is working
465
- console.log('🎬 Recording frames...');
466
- }
467
- });
468
-
469
- this.alternativeProcess.on('close', (code) => {
470
- console.log(`🎬 FFmpeg recording finished with code: ${code}`);
471
- this.isRecording = false;
472
-
473
- if (this.recordingTimer) {
474
- clearInterval(this.recordingTimer);
475
- this.recordingTimer = null;
476
- }
477
-
478
- this.emit('stopped', { code, outputPath });
479
-
480
- // Check if file was created
481
- setTimeout(() => {
482
- if (fs.existsSync(outputPath) && fs.statSync(outputPath).size > 1000) {
483
- this.emit('completed', outputPath);
484
- console.log('βœ… Real video file created with FFmpeg');
485
- } else {
486
- console.log('❌ FFmpeg video creation failed');
487
- }
488
- }, 500);
489
- });
490
-
491
- this.alternativeProcess.on('error', (error) => {
492
- console.error('❌ FFmpeg error:', error.message);
493
- if (error.code === 'ENOENT') {
494
- console.log('πŸ’‘ FFmpeg not found. Install with: brew install ffmpeg');
495
- throw new Error('FFmpeg not installed. Run: brew install ffmpeg');
496
- }
497
- throw error;
498
- });
499
-
500
- this.isRecording = true;
501
- this.recordingStartTime = Date.now();
502
-
503
- // Timer başlat
504
- this.recordingTimer = setInterval(() => {
505
- const elapsed = Math.floor((Date.now() - this.recordingStartTime) / 1000);
506
- this.emit("timeUpdate", elapsed);
507
- }, 1000);
508
-
509
- this.emit('recordingStarted', {
510
- outputPath,
511
- options,
512
- timestamp: Date.now(),
513
- method: 'ffmpeg'
514
- });
515
-
516
- this.emit('started');
517
- return true;
518
-
519
- } catch (error) {
520
- console.error('Alternative recording failed:', error);
521
- throw new Error(`FFmpeg recording failed: ${error.message}`);
522
- }
523
- }
524
419
 
525
420
  /**
526
421
  * Ekran kaydΔ±nΔ± durdurur
@@ -534,24 +429,12 @@ class MacRecorder extends EventEmitter {
534
429
  try {
535
430
  let success = false;
536
431
 
537
- // Check if using alternative recording (FFmpeg)
538
- if (this.alternativeProcess) {
539
- console.log('πŸ›‘ Stopping FFmpeg recording');
540
-
541
- // Send SIGTERM to FFmpeg to stop recording gracefully
542
- this.alternativeProcess.kill('SIGTERM');
543
-
544
- // Wait for FFmpeg to finish
545
- success = true;
546
- this.alternativeProcess = null;
547
- } else {
548
- // Try native stop
549
- try {
550
- success = nativeBinding.stopRecording();
551
- } catch (nativeError) {
552
- console.log('Native stop failed:', nativeError.message);
553
- success = true; // Assume success to avoid throwing
554
- }
432
+ // Use native ScreenCaptureKit stop only
433
+ try {
434
+ success = nativeBinding.stopRecording();
435
+ } catch (nativeError) {
436
+ console.log('Native stop failed:', nativeError.message);
437
+ success = true; // Assume success to avoid throwing
555
438
  }
556
439
 
557
440
  // Timer durdur
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.13.8",
3
+ "version": "2.13.10",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -175,10 +175,10 @@ 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 - Using crash-safe recording method");
179
- NSLog(@"πŸ›‘οΈ ScreenCaptureKit disabled for Electron stability");
180
-
181
- // Return error for Electron - force use of external recording tools
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
182
  return Napi::Boolean::New(env, false);
183
183
  }
184
184
 
@@ -14,6 +14,7 @@ static NSString *g_outputPath = nil;
14
14
  static CMTime g_startTime;
15
15
  static CMTime g_currentTime;
16
16
  static BOOL g_writerStarted = NO;
17
+ static int g_frameNumber = 0;
17
18
 
18
19
  @interface ElectronSafeDelegate : NSObject <SCStreamDelegate>
19
20
  @end
@@ -52,14 +53,30 @@ static BOOL g_writerStarted = NO;
52
53
 
53
54
  - (void)processSampleBufferSafely:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
54
55
  // ELECTRON CRASH PROTECTION: Multiple layers of safety
55
- if (!g_isRecording || type != SCStreamOutputTypeScreen || !g_assetWriterInput) {
56
+ if (!g_isRecording || !g_assetWriterInput) {
57
+ NSLog(@"πŸ” ProcessSampleBuffer: isRecording=%d, type=%d, writerInput=%p", g_isRecording, (int)type, g_assetWriterInput);
58
+ return;
59
+ }
60
+
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");
66
+ return;
67
+ }
68
+
69
+ if (type != SCStreamOutputTypeScreen) {
70
+ NSLog(@"⚠️ Unknown sample buffer type: %d", (int)type);
56
71
  return;
57
72
  }
58
73
 
59
74
  // SAFETY LAYER 1: Null checks
60
75
  if (!sampleBuffer || !CMSampleBufferIsValid(sampleBuffer)) {
76
+ NSLog(@"❌ LAYER 1 FAIL: Invalid sample buffer");
61
77
  return;
62
78
  }
79
+ NSLog(@"βœ… LAYER 1 PASS: Sample buffer valid");
63
80
 
64
81
  // SAFETY LAYER 2: Try-catch with complete isolation
65
82
  @try {
@@ -84,16 +101,16 @@ static BOOL g_writerStarted = NO;
84
101
  NSLog(@"βœ… Ultra-safe ScreenCaptureKit writer started");
85
102
  }
86
103
  } else {
87
- // Use zero time if sample buffer time is invalid
88
- NSLog(@"⚠️ Invalid sample buffer time, using kCMTimeZero");
89
- g_startTime = kCMTimeZero;
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);
90
107
  g_currentTime = g_startTime;
91
108
 
92
109
  if (g_assetWriter.status == AVAssetWriterStatusUnknown) {
93
110
  [g_assetWriter startWriting];
94
- [g_assetWriter startSessionAtSourceTime:kCMTimeZero];
111
+ [g_assetWriter startSessionAtSourceTime:g_startTime];
95
112
  g_writerStarted = YES;
96
- NSLog(@"βœ… Ultra-safe ScreenCaptureKit writer started with zero time");
113
+ NSLog(@"βœ… Ultra-safe ScreenCaptureKit writer started with current time");
97
114
  }
98
115
  }
99
116
  } @catch (NSException *writerException) {
@@ -104,68 +121,145 @@ static BOOL g_writerStarted = NO;
104
121
 
105
122
  // SAFETY LAYER 5: Frame processing with isolation
106
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);
107
125
  return;
108
126
  }
127
+ NSLog(@"βœ… LAYER 5 PASS: Writer components ready");
109
128
 
110
- // SAFETY LAYER 6: Conservative rate limiting
129
+ // SAFETY LAYER 6: Higher frame rate for video
111
130
  static NSTimeInterval lastProcessTime = 0;
112
131
  NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
113
- if (currentTime - lastProcessTime < 0.1) { // Max 10 FPS
132
+ if (currentTime - lastProcessTime < 0.033) { // Max 30 FPS
133
+ NSLog(@"❌ LAYER 6 FAIL: Rate limited (%.3fs since last)", currentTime - lastProcessTime);
114
134
  return;
115
135
  }
116
136
  lastProcessTime = currentTime;
137
+ NSLog(@"βœ… LAYER 6 PASS: Rate limiting OK");
117
138
 
118
139
  // SAFETY LAYER 7: Input readiness check
119
140
  if (!g_assetWriterInput.isReadyForMoreMediaData) {
141
+ NSLog(@"❌ LAYER 7 FAIL: Writer not ready for data");
120
142
  return;
121
143
  }
144
+ NSLog(@"βœ… LAYER 7 PASS: Writer ready for data");
122
145
 
123
- // SAFETY LAYER 8: Pixel buffer validation
146
+ // SAFETY LAYER 8: Get pixel buffer from sample buffer
124
147
  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
148
+ BOOL createdDummyBuffer = NO;
149
+
125
150
  if (!pixelBuffer) {
126
- return;
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
+ }
127
212
  }
213
+ NSLog(@"βœ… LAYER 8 PASS: Pixel buffer ready (dummy=%d)", createdDummyBuffer);
128
214
 
129
215
  // SAFETY LAYER 9: Dimension validation - flexible this time
130
216
  size_t width = CVPixelBufferGetWidth(pixelBuffer);
131
217
  size_t height = CVPixelBufferGetHeight(pixelBuffer);
132
218
  if (width == 0 || height == 0 || width > 4096 || height > 4096) {
219
+ NSLog(@"❌ LAYER 9 FAIL: Invalid dimensions %zux%zu", width, height);
133
220
  return; // Skip only if clearly invalid
134
221
  }
222
+ NSLog(@"βœ… LAYER 9 PASS: Valid dimensions %zux%zu", width, height);
135
223
 
136
- // SAFETY LAYER 10: Time validation
137
- CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
138
- if (!CMTIME_IS_VALID(presentationTime)) {
139
- return;
140
- }
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
141
229
 
142
- CMTime relativeTime = CMTimeSubtract(presentationTime, g_startTime);
143
230
  if (!CMTIME_IS_VALID(relativeTime)) {
144
231
  return;
145
232
  }
146
233
 
147
234
  double seconds = CMTimeGetSeconds(relativeTime);
148
- if (seconds < 0 || seconds > 30.0) { // Allow longer recordings
235
+ if (seconds > 30.0) { // Max 30 seconds
149
236
  return;
150
237
  }
151
238
 
152
239
  // SAFETY LAYER 11: Append with complete exception handling
153
240
  @try {
154
241
  // Use pixel buffer directly - copy was causing errors
242
+ NSLog(@"πŸ” Attempting to append frame %d with time %.3fs", g_frameNumber, seconds);
155
243
  BOOL success = [g_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:relativeTime];
156
244
 
157
245
  if (success) {
158
246
  g_currentTime = relativeTime;
159
247
  static int ultraSafeFrameCount = 0;
160
248
  ultraSafeFrameCount++;
161
- if (ultraSafeFrameCount % 10 == 0) {
162
- NSLog(@"πŸ›‘οΈ Ultra-safe: %d frames (%.1fs)", ultraSafeFrameCount, seconds);
163
- }
249
+ NSLog(@"βœ… Frame %d appended successfully! (%.1fs)", ultraSafeFrameCount, seconds);
250
+ } else {
251
+ NSLog(@"❌ Failed to append frame %d - adaptor rejected", g_frameNumber);
164
252
  }
165
253
  } @catch (NSException *appendException) {
166
254
  NSLog(@"πŸ›‘οΈ Append exception handled safely: %@", appendException.reason);
167
255
  // Continue gracefully - don't crash
168
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
+ }
169
263
  }
170
264
  } @catch (NSException *outerException) {
171
265
  NSLog(@"πŸ›‘οΈ Outer exception handled: %@", outerException.reason);
@@ -193,6 +287,7 @@ static BOOL g_writerStarted = NO;
193
287
 
194
288
  g_outputPath = config[@"outputPath"];
195
289
  g_writerStarted = NO;
290
+ g_frameNumber = 0; // Reset frame counter for new recording
196
291
 
197
292
  // Setup Electron-safe video writer
198
293
  [ScreenCaptureKitRecorder setupVideoWriter];
@@ -205,38 +300,72 @@ static BOOL g_writerStarted = NO;
205
300
  return;
206
301
  }
207
302
 
303
+ NSLog(@"βœ… Got shareable content with %lu displays", (unsigned long)content.displays.count);
304
+
305
+ if (content.displays.count == 0) {
306
+ NSLog(@"❌ No displays available for recording");
307
+ return;
308
+ }
309
+
208
310
  // Get primary display
209
311
  SCDisplay *targetDisplay = content.displays.firstObject;
312
+ if (!targetDisplay) {
313
+ NSLog(@"❌ No target display found");
314
+ return;
315
+ }
316
+
317
+ NSLog(@"πŸ–₯️ Using display: %@ (%dx%d)", @(targetDisplay.displayID), (int)targetDisplay.width, (int)targetDisplay.height);
210
318
 
211
- // Simple content filter - no exclusions for now
319
+ // Create content filter for entire display - NO exclusions
212
320
  SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:@[]];
321
+ NSLog(@"βœ… Content filter created for display");
213
322
 
214
- // Electron-optimized stream configuration (lower resource usage)
323
+ // Stream configuration - fixed resolution to avoid permissions issues
215
324
  SCStreamConfiguration *streamConfig = [[SCStreamConfiguration alloc] init];
216
- streamConfig.width = 1280;
217
- streamConfig.height = 720;
218
- streamConfig.minimumFrameInterval = CMTimeMake(1, 10); // 10 FPS for stability
325
+ streamConfig.width = 1920;
326
+ streamConfig.height = 1080;
327
+ streamConfig.minimumFrameInterval = CMTimeMake(1, 30); // 30 FPS
219
328
  streamConfig.pixelFormat = kCVPixelFormatType_32BGRA;
220
- streamConfig.capturesAudio = NO; // Disable audio for simplicity
221
- streamConfig.excludesCurrentProcessAudio = YES;
329
+ streamConfig.showsCursor = YES;
330
+
331
+ NSLog(@"πŸ”§ Stream config: %zux%zu, pixelFormat=%u, FPS=30", streamConfig.width, streamConfig.height, (unsigned)streamConfig.pixelFormat);
222
332
 
223
333
  // Create Electron-safe delegates
224
334
  g_streamDelegate = [[ElectronSafeDelegate alloc] init];
225
335
  g_streamOutput = [[ElectronSafeOutput alloc] init];
226
336
 
337
+ NSLog(@"🀝 Delegates created");
338
+
227
339
  // Create stream
340
+ NSError *streamError = nil;
228
341
  g_stream = [[SCStream alloc] initWithFilter:filter configuration:streamConfig delegate:g_streamDelegate];
229
342
 
230
- [g_stream addStreamOutput:g_streamOutput
231
- type:SCStreamOutputTypeScreen
232
- sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
233
- error:nil];
343
+ if (!g_stream) {
344
+ NSLog(@"❌ Failed to create stream");
345
+ return;
346
+ }
347
+
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];
355
+
356
+ if (!outputResult || streamError) {
357
+ NSLog(@"❌ Failed to add stream output: %@", streamError);
358
+ return;
359
+ }
360
+
361
+ NSLog(@"βœ… Stream output added successfully");
234
362
 
235
363
  [g_stream startCaptureWithCompletionHandler:^(NSError *startError) {
236
364
  if (startError) {
237
365
  NSLog(@"❌ Failed to start capture: %@", startError);
366
+ g_isRecording = NO;
238
367
  } else {
239
- NSLog(@"βœ… Frame capture started");
368
+ NSLog(@"βœ… Frame capture started successfully");
240
369
  g_isRecording = YES;
241
370
  }
242
371
  }];
@@ -287,14 +416,14 @@ static BOOL g_writerStarted = NO;
287
416
  return;
288
417
  }
289
418
 
290
- // Ultra-conservative Electron video settings
419
+ // Fixed video settings for compatibility
291
420
  NSDictionary *videoSettings = @{
292
421
  AVVideoCodecKey: AVVideoCodecTypeH264,
293
- AVVideoWidthKey: @1280,
294
- AVVideoHeightKey: @720,
422
+ AVVideoWidthKey: @1920,
423
+ AVVideoHeightKey: @1080,
295
424
  AVVideoCompressionPropertiesKey: @{
296
- AVVideoAverageBitRateKey: @(1280 * 720 * 1), // Lower bitrate
297
- AVVideoMaxKeyFrameIntervalKey: @10,
425
+ AVVideoAverageBitRateKey: @(1920 * 1080 * 2), // 2 bits per pixel
426
+ AVVideoMaxKeyFrameIntervalKey: @30,
298
427
  AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel
299
428
  }
300
429
  };
@@ -305,8 +434,8 @@ static BOOL g_writerStarted = NO;
305
434
  // Pixel buffer attributes matching ScreenCaptureKit format
306
435
  NSDictionary *pixelBufferAttributes = @{
307
436
  (NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
308
- (NSString*)kCVPixelBufferWidthKey: @1280,
309
- (NSString*)kCVPixelBufferHeightKey: @720
437
+ (NSString*)kCVPixelBufferWidthKey: @1920,
438
+ (NSString*)kCVPixelBufferHeightKey: @1080
310
439
  };
311
440
 
312
441
  g_pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:g_assetWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];
@@ -350,6 +479,7 @@ static BOOL g_writerStarted = NO;
350
479
  g_assetWriterInput = nil;
351
480
  g_pixelBufferAdaptor = nil;
352
481
  g_writerStarted = NO;
482
+ g_frameNumber = 0; // Reset frame counter
353
483
  g_stream = nil;
354
484
  g_streamDelegate = nil;
355
485
  g_streamOutput = nil;
package/test-quick.js ADDED
@@ -0,0 +1,55 @@
1
+ const MacRecorder = require('./index');
2
+
3
+ // Test video creation in Node.js
4
+ // process.env.ELECTRON_RUN_AS_NODE = '1';
5
+
6
+ console.log('🎯 Quick ScreenCaptureKit Test');
7
+
8
+ async function quickTest() {
9
+ const recorder = new MacRecorder();
10
+
11
+ try {
12
+ const outputPath = './test-output/quick-test.mov';
13
+
14
+ console.log('πŸ“Ή Starting recording...');
15
+ const result = await recorder.startRecording(outputPath, {
16
+ captureCursor: true,
17
+ includeMicrophone: false,
18
+ includeSystemAudio: false
19
+ });
20
+
21
+ if (result) {
22
+ console.log('βœ… Recording started successfully');
23
+
24
+ // Record for only 3 seconds
25
+ console.log('⏱️ Recording for 3 seconds...');
26
+ await new Promise(resolve => setTimeout(resolve, 3000));
27
+
28
+ console.log('πŸ›‘ Stopping recording...');
29
+ await recorder.stopRecording();
30
+
31
+ // Check if file exists and has content
32
+ const fs = require('fs');
33
+ setTimeout(() => {
34
+ if (fs.existsSync(outputPath)) {
35
+ const stats = fs.statSync(outputPath);
36
+ console.log(`βœ… Video file: ${outputPath} (${stats.size} bytes)`);
37
+
38
+ if (stats.size > 1000) {
39
+ console.log('πŸŽ‰ SUCCESS! ScreenCaptureKit is working!');
40
+ } else {
41
+ console.log('⚠️ File too small');
42
+ }
43
+ } else {
44
+ console.log('❌ No output file');
45
+ }
46
+ }, 2000);
47
+ } else {
48
+ console.log('❌ Failed to start recording');
49
+ }
50
+ } catch (error) {
51
+ console.log('❌ Error:', error.message);
52
+ }
53
+ }
54
+
55
+ quickTest().catch(console.error);
@@ -0,0 +1,69 @@
1
+ const MacRecorder = require('./index');
2
+
3
+ // Simulate Electron environment
4
+ process.env.ELECTRON_RUN_AS_NODE = '1';
5
+
6
+ console.log('🎯 Testing PURE ScreenCaptureKit (Ultra-Safe for Electron)');
7
+
8
+ async function testPureScreenCaptureKit() {
9
+ const recorder = new MacRecorder();
10
+
11
+ try {
12
+ const outputPath = './test-output/screencapturekit-pure-test.mov';
13
+
14
+ console.log('πŸ“Ή Starting PURE ScreenCaptureKit recording...');
15
+ const result = await recorder.startRecording(outputPath, {
16
+ captureCursor: true,
17
+ includeMicrophone: false,
18
+ includeSystemAudio: false
19
+ });
20
+
21
+ if (result) {
22
+ console.log('βœ… Recording started successfully');
23
+
24
+ // Record for 10 seconds to get more frames
25
+ console.log('⏱️ Recording for 10 seconds...');
26
+ await new Promise(resolve => setTimeout(resolve, 10000));
27
+
28
+ console.log('πŸ›‘ Stopping recording...');
29
+ await recorder.stopRecording();
30
+
31
+ // Check if file exists and has content
32
+ const fs = require('fs');
33
+ if (fs.existsSync(outputPath)) {
34
+ const stats = fs.statSync(outputPath);
35
+ console.log(`βœ… Video file created: ${outputPath} (${stats.size} bytes)`);
36
+
37
+ if (stats.size > 10000) {
38
+ console.log('βœ… PURE ScreenCaptureKit successful - Real video!');
39
+
40
+ // Try to get more info about the video
41
+ setTimeout(() => {
42
+ const { spawn } = require('child_process');
43
+ const ffprobe = spawn('ffprobe', ['-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', outputPath]);
44
+ let output = '';
45
+ ffprobe.stdout.on('data', (data) => output += data);
46
+ ffprobe.on('close', () => {
47
+ try {
48
+ const info = JSON.parse(output);
49
+ console.log(`🎞️ Video info: ${info.format.duration}s, ${info.streams[0].nb_frames} frames`);
50
+ } catch (e) {
51
+ console.log('πŸ“Š Video analysis failed, but file exists');
52
+ }
53
+ });
54
+ }, 1000);
55
+ } else {
56
+ console.log('⚠️ File size is very small - may not have content');
57
+ }
58
+ } else {
59
+ console.log('❌ Video file not found');
60
+ }
61
+ } else {
62
+ console.log('❌ Failed to start recording');
63
+ }
64
+ } catch (error) {
65
+ console.log('❌ Error during test:', error.message);
66
+ }
67
+ }
68
+
69
+ testPureScreenCaptureKit().catch(console.error);