node-mac-recorder 2.1.3 → 2.2.1
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/.claude/settings.local.json +6 -1
- package/backup/binding.gyp +44 -0
- package/backup/src/audio_capture.mm +116 -0
- package/backup/src/cursor_tracker.mm +518 -0
- package/backup/src/mac_recorder.mm +829 -0
- package/backup/src/screen_capture.h +19 -0
- package/backup/src/screen_capture.mm +162 -0
- package/backup/src/screen_capture_kit.h +15 -0
- package/backup/src/window_selector.mm +1457 -0
- package/binding.gyp +20 -4
- package/index.js +14 -0
- package/package.json +1 -1
- package/src/audio_capture.mm +96 -40
- package/src/cursor_tracker.mm +3 -4
- package/src/mac_recorder.mm +669 -419
- package/src/screen_capture.h +5 -0
- package/src/screen_capture.mm +150 -53
- package/src/window_selector.mm +12 -22
- package/test-api-compatibility.js +92 -0
- package/test-audio.js +94 -0
- package/test-comprehensive.js +164 -0
- package/test-recording.js +142 -0
- package/test-sck-simple.js +37 -0
- package/test-sck.js +60 -1
- package/test-sync.js +52 -0
- package/test-windows.js +57 -0
package/binding.gyp
CHANGED
|
@@ -22,15 +22,28 @@
|
|
|
22
22
|
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
|
23
23
|
"CLANG_CXX_LIBRARY": "libc++",
|
|
24
24
|
"MACOSX_DEPLOYMENT_TARGET": "12.3",
|
|
25
|
+
<<<<<<< HEAD
|
|
26
|
+
=======
|
|
27
|
+
"ARCHS": ["arm64"],
|
|
28
|
+
"VALID_ARCHS": ["arm64"],
|
|
29
|
+
>>>>>>> screencapture
|
|
25
30
|
"OTHER_CFLAGS": [
|
|
26
|
-
"-ObjC++"
|
|
27
|
-
|
|
31
|
+
"-ObjC++",
|
|
32
|
+
"-fmodules"
|
|
33
|
+
],
|
|
34
|
+
"CLANG_ENABLE_OBJC_ARC": "YES"
|
|
28
35
|
},
|
|
29
36
|
"link_settings": {
|
|
30
37
|
"libraries": [
|
|
38
|
+
<<<<<<< HEAD
|
|
39
|
+
=======
|
|
40
|
+
"-framework ScreenCaptureKit",
|
|
41
|
+
"-framework AVFoundation",
|
|
42
|
+
"-framework CoreMedia",
|
|
43
|
+
"-framework CoreVideo",
|
|
44
|
+
>>>>>>> screencapture
|
|
31
45
|
"-framework Foundation",
|
|
32
46
|
"-framework AppKit",
|
|
33
|
-
"-framework ScreenCaptureKit",
|
|
34
47
|
"-framework ApplicationServices",
|
|
35
48
|
"-framework Carbon",
|
|
36
49
|
"-framework Accessibility",
|
|
@@ -40,7 +53,10 @@
|
|
|
40
53
|
"-framework CoreVideo"
|
|
41
54
|
]
|
|
42
55
|
},
|
|
43
|
-
"defines": [
|
|
56
|
+
"defines": [
|
|
57
|
+
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
58
|
+
"USE_SCREENCAPTUREKIT=1"
|
|
59
|
+
]
|
|
44
60
|
}
|
|
45
61
|
]
|
|
46
62
|
}
|
package/index.js
CHANGED
|
@@ -129,6 +129,7 @@ class MacRecorder extends EventEmitter {
|
|
|
129
129
|
audioDeviceId: options.audioDeviceId || null, // null = default device
|
|
130
130
|
systemAudioDeviceId: options.systemAudioDeviceId || null, // null = auto-detect system audio device
|
|
131
131
|
captureArea: options.captureArea || null,
|
|
132
|
+
<<<<<<< HEAD
|
|
132
133
|
useScreenCaptureKit: options.useScreenCaptureKit || false,
|
|
133
134
|
excludedAppBundleIds: options.excludedAppBundleIds || [],
|
|
134
135
|
excludedPIDs: options.excludedPIDs || [],
|
|
@@ -137,6 +138,13 @@ class MacRecorder extends EventEmitter {
|
|
|
137
138
|
typeof options.autoExcludeSelf === "boolean"
|
|
138
139
|
? options.autoExcludeSelf
|
|
139
140
|
: !!(process.versions && process.versions.electron),
|
|
141
|
+
=======
|
|
142
|
+
// Exclusion options
|
|
143
|
+
excludeCurrentApp: options.excludeCurrentApp || false,
|
|
144
|
+
excludeWindowIds: Array.isArray(options.excludeWindowIds)
|
|
145
|
+
? options.excludeWindowIds
|
|
146
|
+
: [],
|
|
147
|
+
>>>>>>> screencapture
|
|
140
148
|
};
|
|
141
149
|
}
|
|
142
150
|
|
|
@@ -297,11 +305,17 @@ class MacRecorder extends EventEmitter {
|
|
|
297
305
|
windowId: this.options.windowId || null, // null = tam ekran
|
|
298
306
|
audioDeviceId: this.options.audioDeviceId || null, // null = default device
|
|
299
307
|
systemAudioDeviceId: this.options.systemAudioDeviceId || null, // null = auto-detect system audio device
|
|
308
|
+
<<<<<<< HEAD
|
|
300
309
|
useScreenCaptureKit: this.options.useScreenCaptureKit || false,
|
|
301
310
|
excludedAppBundleIds: this.options.excludedAppBundleIds || [],
|
|
302
311
|
excludedPIDs: this.options.excludedPIDs || [],
|
|
303
312
|
excludedWindowIds: this.options.excludedWindowIds || [],
|
|
304
313
|
autoExcludeSelf: this.options.autoExcludeSelf === true,
|
|
314
|
+
=======
|
|
315
|
+
// Exclusion options passthrough
|
|
316
|
+
excludeCurrentApp: this.options.excludeCurrentApp || false,
|
|
317
|
+
excludeWindowIds: this.options.excludeWindowIds || [],
|
|
318
|
+
>>>>>>> screencapture
|
|
305
319
|
};
|
|
306
320
|
|
|
307
321
|
// Manuel captureArea varsa onu kullan
|
package/package.json
CHANGED
package/src/audio_capture.mm
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
|
1
2
|
#import <AVFoundation/AVFoundation.h>
|
|
2
3
|
#import <CoreAudio/CoreAudio.h>
|
|
3
4
|
|
|
4
5
|
@interface AudioCapture : NSObject
|
|
5
6
|
|
|
6
7
|
+ (NSArray *)getAudioDevices;
|
|
8
|
+
+ (NSArray *)getSystemAudioDevices;
|
|
7
9
|
+ (BOOL)hasAudioPermission;
|
|
8
10
|
+ (void)requestAudioPermission:(void(^)(BOOL granted))completion;
|
|
9
11
|
|
|
@@ -14,25 +16,39 @@
|
|
|
14
16
|
+ (NSArray *)getAudioDevices {
|
|
15
17
|
NSMutableArray *devices = [NSMutableArray array];
|
|
16
18
|
|
|
17
|
-
// Get
|
|
18
|
-
|
|
19
|
+
// Get microphone devices using AVFoundation
|
|
20
|
+
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
|
|
21
|
+
discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceTypeExternalUnknown]
|
|
22
|
+
mediaType:AVMediaTypeAudio
|
|
23
|
+
position:AVCaptureDevicePositionUnspecified];
|
|
24
|
+
NSArray *audioDevices = discoverySession.devices;
|
|
19
25
|
|
|
20
26
|
for (AVCaptureDevice *device in audioDevices) {
|
|
21
27
|
NSDictionary *deviceInfo = @{
|
|
22
28
|
@"id": device.uniqueID,
|
|
23
29
|
@"name": device.localizedName,
|
|
24
30
|
@"manufacturer": device.manufacturer ?: @"Unknown",
|
|
31
|
+
@"type": @"microphone",
|
|
25
32
|
@"isDefault": @([device isEqual:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]])
|
|
26
33
|
};
|
|
27
34
|
|
|
28
35
|
[devices addObject:deviceInfo];
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
//
|
|
38
|
+
// Add system audio devices using Core Audio API
|
|
39
|
+
NSArray *systemDevices = [self getSystemAudioDevices];
|
|
40
|
+
[devices addObjectsFromArray:systemDevices];
|
|
41
|
+
|
|
42
|
+
return [devices copy];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
+ (NSArray *)getSystemAudioDevices {
|
|
46
|
+
NSMutableArray *devices = [NSMutableArray array];
|
|
47
|
+
|
|
32
48
|
AudioObjectPropertyAddress propertyAddress = {
|
|
33
49
|
kAudioHardwarePropertyDevices,
|
|
34
50
|
kAudioObjectPropertyScopeGlobal,
|
|
35
|
-
kAudioObjectPropertyElementMaster
|
|
51
|
+
kAudioObjectPropertyElementMain // Changed from kAudioObjectPropertyElementMaster (deprecated)
|
|
36
52
|
};
|
|
37
53
|
|
|
38
54
|
UInt32 dataSize = 0;
|
|
@@ -49,37 +65,41 @@
|
|
|
49
65
|
AudioDeviceID deviceID = audioDeviceIDs[i];
|
|
50
66
|
|
|
51
67
|
// Get device name
|
|
52
|
-
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
|
53
|
-
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
|
54
|
-
|
|
55
68
|
CFStringRef deviceName = NULL;
|
|
56
|
-
|
|
69
|
+
UInt32 size = sizeof(deviceName);
|
|
70
|
+
AudioObjectPropertyAddress nameAddress = {
|
|
71
|
+
kAudioDevicePropertyDeviceNameCFString,
|
|
72
|
+
kAudioDevicePropertyScopeOutput, // Focus on output devices for system audio
|
|
73
|
+
kAudioObjectPropertyElementMain
|
|
74
|
+
};
|
|
57
75
|
|
|
58
|
-
status = AudioObjectGetPropertyData(deviceID, &
|
|
76
|
+
status = AudioObjectGetPropertyData(deviceID, &nameAddress, 0, NULL, &size, &deviceName);
|
|
59
77
|
|
|
60
78
|
if (status == kAudioHardwareNoError && deviceName) {
|
|
61
|
-
// Check if
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
// Check if this is an output device
|
|
80
|
+
AudioObjectPropertyAddress streamAddress = {
|
|
81
|
+
kAudioDevicePropertyStreams,
|
|
82
|
+
kAudioDevicePropertyScopeOutput,
|
|
83
|
+
kAudioObjectPropertyElementMain
|
|
84
|
+
};
|
|
64
85
|
|
|
65
|
-
|
|
86
|
+
UInt32 streamSize = 0;
|
|
87
|
+
status = AudioObjectGetPropertyDataSize(deviceID, &streamAddress, 0, NULL, &streamSize);
|
|
66
88
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
if (status == kAudioHardwareNoError && streamSize > 0) {
|
|
90
|
+
// This is an output device - can be used for system audio capture
|
|
91
|
+
const char *name = CFStringGetCStringPtr(deviceName, kCFStringEncodingUTF8);
|
|
92
|
+
NSString *deviceNameStr = name ? [NSString stringWithUTF8String:name] : @"Unknown Device";
|
|
70
93
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
[devices addObject:deviceInfo];
|
|
80
|
-
}
|
|
94
|
+
NSDictionary *deviceInfo = @{
|
|
95
|
+
@"id": [NSString stringWithFormat:@"%u", deviceID],
|
|
96
|
+
@"name": deviceNameStr,
|
|
97
|
+
@"manufacturer": @"System",
|
|
98
|
+
@"type": @"system_audio",
|
|
99
|
+
@"isDefault": @(NO) // We'll determine default separately if needed
|
|
100
|
+
};
|
|
81
101
|
|
|
82
|
-
|
|
102
|
+
[devices addObject:deviceInfo];
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
CFRelease(deviceName);
|
|
@@ -94,23 +114,59 @@
|
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
+ (BOOL)hasAudioPermission {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
return YES; // Older versions don't require explicit permission
|
|
117
|
+
// Check microphone permission using AVFoundation
|
|
118
|
+
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
|
119
|
+
return authStatus == AVAuthorizationStatusAuthorized;
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
+ (void)requestAudioPermission:(void(^)(BOOL granted))completion {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
|
|
124
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
125
|
+
if (completion) {
|
|
108
126
|
completion(granted);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@end
|
|
133
|
+
|
|
134
|
+
// ScreenCaptureKit Audio Configuration Helper
|
|
135
|
+
API_AVAILABLE(macos(12.3))
|
|
136
|
+
@interface SCKAudioConfiguration : NSObject
|
|
137
|
+
|
|
138
|
+
+ (BOOL)configureAudioForStream:(SCStreamConfiguration *)config
|
|
139
|
+
includeMicrophone:(BOOL)includeMicrophone
|
|
140
|
+
includeSystemAudio:(BOOL)includeSystemAudio
|
|
141
|
+
microphoneDevice:(NSString *)micDeviceID
|
|
142
|
+
systemAudioDevice:(NSString *)sysDeviceID;
|
|
143
|
+
|
|
144
|
+
@end
|
|
145
|
+
|
|
146
|
+
@implementation SCKAudioConfiguration
|
|
147
|
+
|
|
148
|
+
+ (BOOL)configureAudioForStream:(SCStreamConfiguration *)config
|
|
149
|
+
includeMicrophone:(BOOL)includeMicrophone
|
|
150
|
+
includeSystemAudio:(BOOL)includeSystemAudio
|
|
151
|
+
microphoneDevice:(NSString *)micDeviceID
|
|
152
|
+
systemAudioDevice:(NSString *)sysDeviceID {
|
|
153
|
+
|
|
154
|
+
// Configure system audio capture (requires macOS 13.0+)
|
|
155
|
+
if (@available(macOS 13.0, *)) {
|
|
156
|
+
config.capturesAudio = includeSystemAudio;
|
|
157
|
+
config.excludesCurrentProcessAudio = YES;
|
|
158
|
+
|
|
159
|
+
if (includeSystemAudio) {
|
|
160
|
+
// ScreenCaptureKit will capture system audio from the selected content
|
|
161
|
+
// Quality settings
|
|
162
|
+
config.channelCount = 2; // Stereo
|
|
163
|
+
config.sampleRate = 48000; // 48kHz
|
|
164
|
+
} else {
|
|
165
|
+
config.capturesAudio = NO;
|
|
166
|
+
}
|
|
113
167
|
}
|
|
168
|
+
|
|
169
|
+
return YES;
|
|
114
170
|
}
|
|
115
171
|
|
|
116
|
-
@end
|
|
172
|
+
@end
|
package/src/cursor_tracker.mm
CHANGED
|
@@ -168,7 +168,7 @@ void writeToFile(NSDictionary *cursorData) {
|
|
|
168
168
|
options:0
|
|
169
169
|
error:&error];
|
|
170
170
|
if (jsonData && !error) {
|
|
171
|
-
NSString *jsonString = [[
|
|
171
|
+
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
172
172
|
|
|
173
173
|
if (g_isFirstWrite) {
|
|
174
174
|
// İlk yazma - array başlat
|
|
@@ -292,7 +292,6 @@ void cleanupCursorTracking() {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
if (g_timerTarget) {
|
|
295
|
-
[g_timerTarget autorelease];
|
|
296
295
|
g_timerTarget = nil;
|
|
297
296
|
}
|
|
298
297
|
|
|
@@ -352,12 +351,12 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
|
|
|
352
351
|
@try {
|
|
353
352
|
// Dosyayı oluştur ve aç
|
|
354
353
|
g_outputPath = [NSString stringWithUTF8String:outputPath.c_str()];
|
|
355
|
-
g_fileHandle = [
|
|
354
|
+
g_fileHandle = [NSFileHandle fileHandleForWritingAtPath:g_outputPath];
|
|
356
355
|
|
|
357
356
|
if (!g_fileHandle) {
|
|
358
357
|
// Dosya yoksa oluştur
|
|
359
358
|
[[NSFileManager defaultManager] createFileAtPath:g_outputPath contents:nil attributes:nil];
|
|
360
|
-
g_fileHandle = [
|
|
359
|
+
g_fileHandle = [NSFileHandle fileHandleForWritingAtPath:g_outputPath];
|
|
361
360
|
}
|
|
362
361
|
|
|
363
362
|
if (!g_fileHandle) {
|