node-mac-recorder 2.12.3 β†’ 2.12.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.12.3",
3
+ "version": "2.12.5",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -11,5 +11,9 @@ API_AVAILABLE(macos(12.3))
11
11
  error:(NSError **)error;
12
12
  + (void)stopRecording;
13
13
  + (BOOL)isRecording;
14
+ + (BOOL)setupVideoWriterWithWidth:(NSInteger)width
15
+ height:(NSInteger)height
16
+ outputPath:(NSString *)outputPath
17
+ includeAudio:(BOOL)includeAudio;
14
18
 
15
19
  @end
@@ -3,21 +3,97 @@
3
3
  // Global state
4
4
  static SCStream *g_stream = nil;
5
5
  static id<SCStreamDelegate> g_streamDelegate = nil;
6
+ static id<SCStreamOutput> g_streamOutput = nil;
6
7
  static BOOL g_isRecording = NO;
8
+ static AVAssetWriter *g_assetWriter = nil;
9
+ static AVAssetWriterInput *g_videoWriterInput = nil;
10
+ static AVAssetWriterInput *g_audioWriterInput = nil;
11
+ static NSString *g_outputPath = nil;
12
+ static BOOL g_sessionStarted = NO;
13
+ static AVAssetWriterInputPixelBufferAdaptor *g_pixelBufferAdaptor = nil;
7
14
 
8
15
  @interface ScreenCaptureKitRecorderDelegate : NSObject <SCStreamDelegate>
9
16
  @property (nonatomic, copy) void (^completionHandler)(NSURL *outputURL, NSError *error);
10
17
  @end
11
18
 
19
+ @interface ScreenCaptureKitStreamOutput : NSObject <SCStreamOutput>
20
+ @end
21
+
12
22
  @implementation ScreenCaptureKitRecorderDelegate
13
23
  - (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
14
24
  NSLog(@"ScreenCaptureKit recording stopped with error: %@", error);
25
+
26
+ // Finalize video file
27
+ if (g_assetWriter && g_assetWriter.status == AVAssetWriterStatusWriting) {
28
+ [g_videoWriterInput markAsFinished];
29
+ if (g_audioWriterInput) {
30
+ [g_audioWriterInput markAsFinished];
31
+ }
32
+ [g_assetWriter finishWritingWithCompletionHandler:^{
33
+ NSLog(@"βœ… ScreenCaptureKit video file finalized: %@", g_outputPath);
34
+ }];
35
+ }
36
+ }
37
+ @end
38
+
39
+ @implementation ScreenCaptureKitStreamOutput
40
+ - (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
41
+ if (!g_assetWriter) {
42
+ return;
43
+ }
44
+
45
+ // Start session on first sample
46
+ if (!g_sessionStarted && g_assetWriter.status == AVAssetWriterStatusWriting) {
47
+ CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
48
+ [g_assetWriter startSessionAtSourceTime:presentationTime];
49
+ g_sessionStarted = YES;
50
+ NSLog(@"πŸ“½οΈ ScreenCaptureKit video session started at time: %lld/%d", presentationTime.value, presentationTime.timescale);
51
+ }
52
+
53
+ if (g_assetWriter.status != AVAssetWriterStatusWriting) {
54
+ return;
55
+ }
56
+
57
+ switch (type) {
58
+ case SCStreamOutputTypeScreen:
59
+ if (g_pixelBufferAdaptor && g_pixelBufferAdaptor.assetWriterInput.isReadyForMoreMediaData) {
60
+ // Convert sample buffer to pixel buffer
61
+ CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
62
+ if (pixelBuffer) {
63
+ CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
64
+ BOOL success = [g_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime];
65
+ if (!success) {
66
+ NSLog(@"❌ Failed to append pixel buffer: %@", g_assetWriter.error);
67
+ }
68
+ }
69
+ }
70
+ break;
71
+ case SCStreamOutputTypeAudio:
72
+ if (g_audioWriterInput && g_audioWriterInput.isReadyForMoreMediaData) {
73
+ BOOL success = [g_audioWriterInput appendSampleBuffer:sampleBuffer];
74
+ if (!success) {
75
+ NSLog(@"❌ Failed to append audio sample: %@", g_assetWriter.error);
76
+ }
77
+ }
78
+ break;
79
+ case SCStreamOutputTypeMicrophone:
80
+ // Handle microphone input (if needed in future)
81
+ if (g_audioWriterInput && g_audioWriterInput.isReadyForMoreMediaData) {
82
+ BOOL success = [g_audioWriterInput appendSampleBuffer:sampleBuffer];
83
+ if (!success) {
84
+ NSLog(@"❌ Failed to append microphone sample: %@", g_assetWriter.error);
85
+ }
86
+ }
87
+ break;
88
+ }
15
89
  }
16
90
  @end
17
91
 
18
92
  @implementation ScreenCaptureKitRecorder
19
93
 
20
94
  + (BOOL)isScreenCaptureKitAvailable {
95
+ // ScreenCaptureKit'i tekrar etkinleştir - video writer ile
96
+
21
97
  if (@available(macOS 12.3, *)) {
22
98
  NSLog(@"πŸ” ScreenCaptureKit availability check - macOS 12.3+ confirmed");
23
99
 
@@ -163,14 +239,49 @@ static BOOL g_isRecording = NO;
163
239
  streamConfig.showsCursor = [config[@"captureCursor"] boolValue];
164
240
  streamConfig.capturesAudio = [config[@"includeSystemAudio"] boolValue];
165
241
 
166
- // Create delegate
242
+ // Setup video writer
243
+ g_outputPath = config[@"outputPath"];
244
+ if (![self setupVideoWriterWithWidth:streamConfig.width
245
+ height:streamConfig.height
246
+ outputPath:g_outputPath
247
+ includeAudio:[config[@"includeSystemAudio"] boolValue] || [config[@"includeMicrophone"] boolValue]]) {
248
+ NSLog(@"❌ Failed to setup video writer");
249
+ contentError = [NSError errorWithDomain:@"ScreenCaptureKitError" code:-3 userInfo:@{NSLocalizedDescriptionKey: @"Video writer setup failed"}];
250
+ dispatch_semaphore_signal(semaphore);
251
+ return;
252
+ }
253
+
254
+ // Create delegate and output
167
255
  g_streamDelegate = [[ScreenCaptureKitRecorderDelegate alloc] init];
256
+ g_streamOutput = [[ScreenCaptureKitStreamOutput alloc] init];
168
257
 
169
258
  // Create and start stream
170
259
  g_stream = [[SCStream alloc] initWithFilter:filter
171
260
  configuration:streamConfig
172
261
  delegate:g_streamDelegate];
173
262
 
263
+ // Add stream output using correct API
264
+ NSError *outputError = nil;
265
+ BOOL outputAdded = [g_stream addStreamOutput:g_streamOutput
266
+ type:SCStreamOutputTypeScreen
267
+ sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
268
+ error:&outputError];
269
+ if (!outputAdded) {
270
+ NSLog(@"❌ Failed to add screen output: %@", outputError);
271
+ }
272
+
273
+ if ([config[@"includeSystemAudio"] boolValue]) {
274
+ if (@available(macOS 13.0, *)) {
275
+ BOOL audioOutputAdded = [g_stream addStreamOutput:g_streamOutput
276
+ type:SCStreamOutputTypeAudio
277
+ sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
278
+ error:&outputError];
279
+ if (!audioOutputAdded) {
280
+ NSLog(@"❌ Failed to add audio output: %@", outputError);
281
+ }
282
+ }
283
+ }
284
+
174
285
  [g_stream startCaptureWithCompletionHandler:^(NSError *streamError) {
175
286
  if (streamError) {
176
287
  NSLog(@"❌ Failed to start ScreenCaptureKit recording: %@", streamError);
@@ -225,9 +336,30 @@ static BOOL g_isRecording = NO;
225
336
  } else {
226
337
  NSLog(@"ScreenCaptureKit recording stopped successfully");
227
338
  }
339
+
340
+ // Finalize video file
341
+ if (g_assetWriter && g_assetWriter.status == AVAssetWriterStatusWriting) {
342
+ [g_videoWriterInput markAsFinished];
343
+ if (g_audioWriterInput) {
344
+ [g_audioWriterInput markAsFinished];
345
+ }
346
+ [g_assetWriter finishWritingWithCompletionHandler:^{
347
+ NSLog(@"βœ… ScreenCaptureKit video file finalized: %@", g_outputPath);
348
+
349
+ // Cleanup
350
+ g_assetWriter = nil;
351
+ g_videoWriterInput = nil;
352
+ g_audioWriterInput = nil;
353
+ g_pixelBufferAdaptor = nil;
354
+ g_outputPath = nil;
355
+ g_sessionStarted = NO;
356
+ }];
357
+ }
358
+
228
359
  g_isRecording = NO;
229
360
  g_stream = nil;
230
361
  g_streamDelegate = nil;
362
+ g_streamOutput = nil;
231
363
  }];
232
364
  }
233
365
  }
@@ -237,4 +369,81 @@ static BOOL g_isRecording = NO;
237
369
  return g_isRecording;
238
370
  }
239
371
 
372
+ + (BOOL)setupVideoWriterWithWidth:(NSInteger)width
373
+ height:(NSInteger)height
374
+ outputPath:(NSString *)outputPath
375
+ includeAudio:(BOOL)includeAudio {
376
+
377
+ // Create asset writer with MP4 format for better compatibility
378
+ NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
379
+ NSError *error = nil;
380
+ g_assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error];
381
+
382
+ if (error || !g_assetWriter) {
383
+ NSLog(@"❌ Failed to create asset writer: %@", error);
384
+ return NO;
385
+ }
386
+
387
+ // Video writer input - basic settings for maximum compatibility
388
+ NSDictionary *videoSettings = @{
389
+ AVVideoCodecKey: AVVideoCodecTypeH264,
390
+ AVVideoWidthKey: @(width),
391
+ AVVideoHeightKey: @(height)
392
+ };
393
+
394
+ g_videoWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
395
+ g_videoWriterInput.expectsMediaDataInRealTime = YES;
396
+
397
+ if (![g_assetWriter canAddInput:g_videoWriterInput]) {
398
+ NSLog(@"❌ Cannot add video input to asset writer");
399
+ return NO;
400
+ }
401
+ [g_assetWriter addInput:g_videoWriterInput];
402
+
403
+ // Create pixel buffer adaptor for ScreenCaptureKit compatibility
404
+ NSDictionary *pixelBufferAttributes = @{
405
+ (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
406
+ (NSString *)kCVPixelBufferWidthKey: @(width),
407
+ (NSString *)kCVPixelBufferHeightKey: @(height),
408
+ (NSString *)kCVPixelBufferIOSurfacePropertiesKey: @{}
409
+ };
410
+
411
+ g_pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
412
+ initWithAssetWriterInput:g_videoWriterInput
413
+ sourcePixelBufferAttributes:pixelBufferAttributes];
414
+
415
+ if (!g_pixelBufferAdaptor) {
416
+ NSLog(@"❌ Cannot create pixel buffer adaptor");
417
+ return NO;
418
+ }
419
+
420
+ // Audio writer input (if needed)
421
+ if (includeAudio) {
422
+ NSDictionary *audioSettings = @{
423
+ AVFormatIDKey: @(kAudioFormatMPEG4AAC),
424
+ AVSampleRateKey: @(44100.0),
425
+ AVNumberOfChannelsKey: @(2),
426
+ AVEncoderBitRateKey: @(128000)
427
+ };
428
+
429
+ g_audioWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
430
+ g_audioWriterInput.expectsMediaDataInRealTime = YES;
431
+
432
+ if ([g_assetWriter canAddInput:g_audioWriterInput]) {
433
+ [g_assetWriter addInput:g_audioWriterInput];
434
+ }
435
+ }
436
+
437
+ // Start writing (session will be started when first sample arrives)
438
+ if (![g_assetWriter startWriting]) {
439
+ NSLog(@"❌ Failed to start writing: %@", g_assetWriter.error);
440
+ return NO;
441
+ }
442
+
443
+ g_sessionStarted = NO; // Reset session flag
444
+ NSLog(@"βœ… ScreenCaptureKit video writer setup complete: %@", outputPath);
445
+
446
+ return YES;
447
+ }
448
+
240
449
  @end