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 +1 -1
- package/src/screen_capture_kit.h +4 -0
- package/src/screen_capture_kit.mm +210 -1
package/package.json
CHANGED
package/src/screen_capture_kit.h
CHANGED
|
@@ -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
|
-
//
|
|
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
|