node-mac-recorder 2.12.3 → 2.12.4
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 +167 -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,69 @@
|
|
|
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;
|
|
7
12
|
|
|
8
13
|
@interface ScreenCaptureKitRecorderDelegate : NSObject <SCStreamDelegate>
|
|
9
14
|
@property (nonatomic, copy) void (^completionHandler)(NSURL *outputURL, NSError *error);
|
|
10
15
|
@end
|
|
11
16
|
|
|
17
|
+
@interface ScreenCaptureKitStreamOutput : NSObject <SCStreamOutput>
|
|
18
|
+
@end
|
|
19
|
+
|
|
12
20
|
@implementation ScreenCaptureKitRecorderDelegate
|
|
13
21
|
- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
|
|
14
22
|
NSLog(@"ScreenCaptureKit recording stopped with error: %@", error);
|
|
23
|
+
|
|
24
|
+
// Finalize video file
|
|
25
|
+
if (g_assetWriter && g_assetWriter.status == AVAssetWriterStatusWriting) {
|
|
26
|
+
[g_videoWriterInput markAsFinished];
|
|
27
|
+
if (g_audioWriterInput) {
|
|
28
|
+
[g_audioWriterInput markAsFinished];
|
|
29
|
+
}
|
|
30
|
+
[g_assetWriter finishWritingWithCompletionHandler:^{
|
|
31
|
+
NSLog(@"✅ ScreenCaptureKit video file finalized: %@", g_outputPath);
|
|
32
|
+
}];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@implementation ScreenCaptureKitStreamOutput
|
|
38
|
+
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
|
|
39
|
+
if (!g_assetWriter || g_assetWriter.status != AVAssetWriterStatusWriting) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
switch (type) {
|
|
44
|
+
case SCStreamOutputTypeScreen:
|
|
45
|
+
if (g_videoWriterInput && g_videoWriterInput.isReadyForMoreMediaData) {
|
|
46
|
+
[g_videoWriterInput appendSampleBuffer:sampleBuffer];
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case SCStreamOutputTypeAudio:
|
|
50
|
+
if (g_audioWriterInput && g_audioWriterInput.isReadyForMoreMediaData) {
|
|
51
|
+
[g_audioWriterInput appendSampleBuffer:sampleBuffer];
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case SCStreamOutputTypeMicrophone:
|
|
55
|
+
// Handle microphone input (if needed in future)
|
|
56
|
+
if (g_audioWriterInput && g_audioWriterInput.isReadyForMoreMediaData) {
|
|
57
|
+
[g_audioWriterInput appendSampleBuffer:sampleBuffer];
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
15
61
|
}
|
|
16
62
|
@end
|
|
17
63
|
|
|
18
64
|
@implementation ScreenCaptureKitRecorder
|
|
19
65
|
|
|
20
66
|
+ (BOOL)isScreenCaptureKitAvailable {
|
|
67
|
+
// ScreenCaptureKit'i tekrar etkinleştir - video writer ile
|
|
68
|
+
|
|
21
69
|
if (@available(macOS 12.3, *)) {
|
|
22
70
|
NSLog(@"🔍 ScreenCaptureKit availability check - macOS 12.3+ confirmed");
|
|
23
71
|
|
|
@@ -163,14 +211,49 @@ static BOOL g_isRecording = NO;
|
|
|
163
211
|
streamConfig.showsCursor = [config[@"captureCursor"] boolValue];
|
|
164
212
|
streamConfig.capturesAudio = [config[@"includeSystemAudio"] boolValue];
|
|
165
213
|
|
|
166
|
-
//
|
|
214
|
+
// Setup video writer
|
|
215
|
+
g_outputPath = config[@"outputPath"];
|
|
216
|
+
if (![self setupVideoWriterWithWidth:streamConfig.width
|
|
217
|
+
height:streamConfig.height
|
|
218
|
+
outputPath:g_outputPath
|
|
219
|
+
includeAudio:[config[@"includeSystemAudio"] boolValue] || [config[@"includeMicrophone"] boolValue]]) {
|
|
220
|
+
NSLog(@"❌ Failed to setup video writer");
|
|
221
|
+
contentError = [NSError errorWithDomain:@"ScreenCaptureKitError" code:-3 userInfo:@{NSLocalizedDescriptionKey: @"Video writer setup failed"}];
|
|
222
|
+
dispatch_semaphore_signal(semaphore);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Create delegate and output
|
|
167
227
|
g_streamDelegate = [[ScreenCaptureKitRecorderDelegate alloc] init];
|
|
228
|
+
g_streamOutput = [[ScreenCaptureKitStreamOutput alloc] init];
|
|
168
229
|
|
|
169
230
|
// Create and start stream
|
|
170
231
|
g_stream = [[SCStream alloc] initWithFilter:filter
|
|
171
232
|
configuration:streamConfig
|
|
172
233
|
delegate:g_streamDelegate];
|
|
173
234
|
|
|
235
|
+
// Add stream output using correct API
|
|
236
|
+
NSError *outputError = nil;
|
|
237
|
+
BOOL outputAdded = [g_stream addStreamOutput:g_streamOutput
|
|
238
|
+
type:SCStreamOutputTypeScreen
|
|
239
|
+
sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
|
|
240
|
+
error:&outputError];
|
|
241
|
+
if (!outputAdded) {
|
|
242
|
+
NSLog(@"❌ Failed to add screen output: %@", outputError);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if ([config[@"includeSystemAudio"] boolValue]) {
|
|
246
|
+
if (@available(macOS 13.0, *)) {
|
|
247
|
+
BOOL audioOutputAdded = [g_stream addStreamOutput:g_streamOutput
|
|
248
|
+
type:SCStreamOutputTypeAudio
|
|
249
|
+
sampleHandlerQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
|
|
250
|
+
error:&outputError];
|
|
251
|
+
if (!audioOutputAdded) {
|
|
252
|
+
NSLog(@"❌ Failed to add audio output: %@", outputError);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
174
257
|
[g_stream startCaptureWithCompletionHandler:^(NSError *streamError) {
|
|
175
258
|
if (streamError) {
|
|
176
259
|
NSLog(@"❌ Failed to start ScreenCaptureKit recording: %@", streamError);
|
|
@@ -225,9 +308,28 @@ static BOOL g_isRecording = NO;
|
|
|
225
308
|
} else {
|
|
226
309
|
NSLog(@"ScreenCaptureKit recording stopped successfully");
|
|
227
310
|
}
|
|
311
|
+
|
|
312
|
+
// Finalize video file
|
|
313
|
+
if (g_assetWriter && g_assetWriter.status == AVAssetWriterStatusWriting) {
|
|
314
|
+
[g_videoWriterInput markAsFinished];
|
|
315
|
+
if (g_audioWriterInput) {
|
|
316
|
+
[g_audioWriterInput markAsFinished];
|
|
317
|
+
}
|
|
318
|
+
[g_assetWriter finishWritingWithCompletionHandler:^{
|
|
319
|
+
NSLog(@"✅ ScreenCaptureKit video file finalized: %@", g_outputPath);
|
|
320
|
+
|
|
321
|
+
// Cleanup
|
|
322
|
+
g_assetWriter = nil;
|
|
323
|
+
g_videoWriterInput = nil;
|
|
324
|
+
g_audioWriterInput = nil;
|
|
325
|
+
g_outputPath = nil;
|
|
326
|
+
}];
|
|
327
|
+
}
|
|
328
|
+
|
|
228
329
|
g_isRecording = NO;
|
|
229
330
|
g_stream = nil;
|
|
230
331
|
g_streamDelegate = nil;
|
|
332
|
+
g_streamOutput = nil;
|
|
231
333
|
}];
|
|
232
334
|
}
|
|
233
335
|
}
|
|
@@ -237,4 +339,68 @@ static BOOL g_isRecording = NO;
|
|
|
237
339
|
return g_isRecording;
|
|
238
340
|
}
|
|
239
341
|
|
|
342
|
+
+ (BOOL)setupVideoWriterWithWidth:(NSInteger)width
|
|
343
|
+
height:(NSInteger)height
|
|
344
|
+
outputPath:(NSString *)outputPath
|
|
345
|
+
includeAudio:(BOOL)includeAudio {
|
|
346
|
+
|
|
347
|
+
// Create asset writer
|
|
348
|
+
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
|
|
349
|
+
NSError *error = nil;
|
|
350
|
+
g_assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:&error];
|
|
351
|
+
|
|
352
|
+
if (error || !g_assetWriter) {
|
|
353
|
+
NSLog(@"❌ Failed to create asset writer: %@", error);
|
|
354
|
+
return NO;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Video writer input
|
|
358
|
+
NSDictionary *videoSettings = @{
|
|
359
|
+
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
360
|
+
AVVideoWidthKey: @(width),
|
|
361
|
+
AVVideoHeightKey: @(height),
|
|
362
|
+
AVVideoCompressionPropertiesKey: @{
|
|
363
|
+
AVVideoAverageBitRateKey: @(width * height * 8), // 8 bits per pixel
|
|
364
|
+
AVVideoExpectedSourceFrameRateKey: @(60)
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
g_videoWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
|
|
369
|
+
g_videoWriterInput.expectsMediaDataInRealTime = YES;
|
|
370
|
+
|
|
371
|
+
if (![g_assetWriter canAddInput:g_videoWriterInput]) {
|
|
372
|
+
NSLog(@"❌ Cannot add video input to asset writer");
|
|
373
|
+
return NO;
|
|
374
|
+
}
|
|
375
|
+
[g_assetWriter addInput:g_videoWriterInput];
|
|
376
|
+
|
|
377
|
+
// Audio writer input (if needed)
|
|
378
|
+
if (includeAudio) {
|
|
379
|
+
NSDictionary *audioSettings = @{
|
|
380
|
+
AVFormatIDKey: @(kAudioFormatMPEG4AAC),
|
|
381
|
+
AVSampleRateKey: @(44100.0),
|
|
382
|
+
AVNumberOfChannelsKey: @(2),
|
|
383
|
+
AVEncoderBitRateKey: @(128000)
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
g_audioWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
|
|
387
|
+
g_audioWriterInput.expectsMediaDataInRealTime = YES;
|
|
388
|
+
|
|
389
|
+
if ([g_assetWriter canAddInput:g_audioWriterInput]) {
|
|
390
|
+
[g_assetWriter addInput:g_audioWriterInput];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Start writing
|
|
395
|
+
if (![g_assetWriter startWriting]) {
|
|
396
|
+
NSLog(@"❌ Failed to start writing: %@", g_assetWriter.error);
|
|
397
|
+
return NO;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
[g_assetWriter startSessionAtSourceTime:kCMTimeZero];
|
|
401
|
+
NSLog(@"✅ ScreenCaptureKit video writer setup complete: %@", outputPath);
|
|
402
|
+
|
|
403
|
+
return YES;
|
|
404
|
+
}
|
|
405
|
+
|
|
240
406
|
@end
|