node-mac-recorder 2.4.11 → 2.4.13
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/binding.gyp +5 -12
- package/index.js +25 -104
- package/install.js +2 -19
- package/package.json +2 -6
- package/src/audio_capture.mm +40 -96
- package/src/cursor_tracker.mm +4 -3
- package/src/mac_recorder.mm +673 -753
- package/src/screen_capture.h +0 -5
- package/src/screen_capture.mm +60 -139
- package/src/window_selector.mm +113 -399
- package/window-selector.js +34 -112
- package/ELECTRON-INTEGRATION.md +0 -710
- package/WINDOW_SELECTOR_USAGE.md +0 -447
- package/backup/binding.gyp +0 -44
- package/backup/src/audio_capture.mm +0 -116
- package/backup/src/cursor_tracker.mm +0 -518
- package/backup/src/mac_recorder.mm +0 -829
- package/backup/src/screen_capture.h +0 -19
- package/backup/src/screen_capture.mm +0 -162
- package/backup/src/screen_capture_kit.h +0 -15
- package/backup/src/window_selector.mm +0 -1457
- package/electron-window-selector.js +0 -698
- package/node-mac-recorder-2.4.2.tgz +0 -0
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/test-api-compatibility.js +0 -92
- package/test-audio.js +0 -94
- package/test-comprehensive.js +0 -164
- package/test-electron-window-selector.js +0 -119
- package/test-overlay-fix.js +0 -72
- package/test-recording.js +0 -142
- package/test-sck-availability.js +0 -26
- package/test-sck-simple.js +0 -37
- package/test-sck.js +0 -56
- package/test-screencapture-overlay.js +0 -54
- package/test-simple-windows.js +0 -29
- package/test-sync.js +0 -52
- package/test-window-details.js +0 -34
- package/test-windows.js +0 -57
package/binding.gyp
CHANGED
|
@@ -20,32 +20,25 @@
|
|
|
20
20
|
"xcode_settings": {
|
|
21
21
|
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
|
22
22
|
"CLANG_CXX_LIBRARY": "libc++",
|
|
23
|
-
"MACOSX_DEPLOYMENT_TARGET": "
|
|
24
|
-
"ARCHS": ["arm64"],
|
|
25
|
-
"VALID_ARCHS": ["arm64"],
|
|
23
|
+
"MACOSX_DEPLOYMENT_TARGET": "10.15",
|
|
26
24
|
"OTHER_CFLAGS": [
|
|
27
|
-
"-ObjC++"
|
|
28
|
-
|
|
29
|
-
],
|
|
30
|
-
"CLANG_ENABLE_OBJC_ARC": "YES"
|
|
25
|
+
"-ObjC++"
|
|
26
|
+
]
|
|
31
27
|
},
|
|
32
28
|
"link_settings": {
|
|
33
29
|
"libraries": [
|
|
34
|
-
"-framework ScreenCaptureKit",
|
|
35
30
|
"-framework AVFoundation",
|
|
36
31
|
"-framework CoreMedia",
|
|
37
32
|
"-framework CoreVideo",
|
|
38
33
|
"-framework Foundation",
|
|
39
34
|
"-framework AppKit",
|
|
35
|
+
"-framework ScreenCaptureKit",
|
|
40
36
|
"-framework ApplicationServices",
|
|
41
37
|
"-framework Carbon",
|
|
42
38
|
"-framework Accessibility"
|
|
43
39
|
]
|
|
44
40
|
},
|
|
45
|
-
"defines": [
|
|
46
|
-
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
47
|
-
"USE_SCREENCAPTUREKIT=1"
|
|
48
|
-
]
|
|
41
|
+
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
|
|
49
42
|
}
|
|
50
43
|
]
|
|
51
44
|
}
|
package/index.js
CHANGED
|
@@ -5,25 +5,16 @@ const fs = require("fs");
|
|
|
5
5
|
// Native modülü yükle
|
|
6
6
|
let nativeBinding;
|
|
7
7
|
try {
|
|
8
|
-
|
|
9
|
-
if (process.platform === "darwin" && process.arch === "arm64") {
|
|
10
|
-
nativeBinding = require("./prebuilds/darwin-arm64/node.napi.node");
|
|
11
|
-
} else {
|
|
12
|
-
nativeBinding = require("./build/Release/mac_recorder.node");
|
|
13
|
-
}
|
|
8
|
+
nativeBinding = require("./build/Release/mac_recorder.node");
|
|
14
9
|
} catch (error) {
|
|
15
10
|
try {
|
|
16
|
-
nativeBinding = require("./build/
|
|
17
|
-
} catch (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"Original error: " +
|
|
24
|
-
error.message
|
|
25
|
-
);
|
|
26
|
-
}
|
|
11
|
+
nativeBinding = require("./build/Debug/mac_recorder.node");
|
|
12
|
+
} catch (debugError) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'Native module not found. Please run "npm run build" to compile the native module.\n' +
|
|
15
|
+
"Original error: " +
|
|
16
|
+
error.message
|
|
17
|
+
);
|
|
27
18
|
}
|
|
28
19
|
}
|
|
29
20
|
|
|
@@ -127,11 +118,6 @@ class MacRecorder extends EventEmitter {
|
|
|
127
118
|
audioDeviceId: options.audioDeviceId || null, // null = default device
|
|
128
119
|
systemAudioDeviceId: options.systemAudioDeviceId || null, // null = auto-detect system audio device
|
|
129
120
|
captureArea: options.captureArea || null,
|
|
130
|
-
// Exclusion options
|
|
131
|
-
excludeCurrentApp: options.excludeCurrentApp || false,
|
|
132
|
-
excludeWindowIds: Array.isArray(options.excludeWindowIds)
|
|
133
|
-
? options.excludeWindowIds
|
|
134
|
-
: [],
|
|
135
121
|
};
|
|
136
122
|
}
|
|
137
123
|
|
|
@@ -181,12 +167,11 @@ class MacRecorder extends EventEmitter {
|
|
|
181
167
|
targetDisplayId = display.id; // Use actual display ID, not array index
|
|
182
168
|
// Koordinatları display'e göre normalize et
|
|
183
169
|
adjustedX = targetWindow.x - display.x;
|
|
184
|
-
|
|
170
|
+
|
|
185
171
|
// Y coordinate conversion: CGWindow (top-left) to AVFoundation (bottom-left)
|
|
186
172
|
// Overlay'deki dönüşümle aynı mantık: screenHeight - windowY - windowHeight
|
|
187
173
|
const displayHeight = parseInt(display.resolution.split("x")[1]);
|
|
188
|
-
const convertedY =
|
|
189
|
-
displayHeight - targetWindow.y - targetWindow.height;
|
|
174
|
+
const convertedY = displayHeight - targetWindow.y - targetWindow.height;
|
|
190
175
|
adjustedY = Math.max(0, convertedY - display.y);
|
|
191
176
|
break;
|
|
192
177
|
}
|
|
@@ -221,9 +206,7 @@ class MacRecorder extends EventEmitter {
|
|
|
221
206
|
this.options.displayId = targetDisplayId;
|
|
222
207
|
|
|
223
208
|
// Recording için display bilgisini sakla (cursor capture için)
|
|
224
|
-
const targetDisplay = displays.find(
|
|
225
|
-
(d) => d.id === targetDisplayId
|
|
226
|
-
);
|
|
209
|
+
const targetDisplay = displays.find(d => d.id === targetDisplayId);
|
|
227
210
|
this.recordingDisplayInfo = {
|
|
228
211
|
displayId: targetDisplayId,
|
|
229
212
|
x: targetDisplay.x,
|
|
@@ -256,9 +239,7 @@ class MacRecorder extends EventEmitter {
|
|
|
256
239
|
if (this.options.displayId !== null && !this.recordingDisplayInfo) {
|
|
257
240
|
try {
|
|
258
241
|
const displays = await this.getDisplays();
|
|
259
|
-
const targetDisplay = displays.find(
|
|
260
|
-
(d) => d.id === this.options.displayId
|
|
261
|
-
);
|
|
242
|
+
const targetDisplay = displays.find(d => d.id === this.options.displayId);
|
|
262
243
|
if (targetDisplay) {
|
|
263
244
|
this.recordingDisplayInfo = {
|
|
264
245
|
displayId: this.options.displayId,
|
|
@@ -292,9 +273,6 @@ class MacRecorder extends EventEmitter {
|
|
|
292
273
|
windowId: this.options.windowId || null, // null = tam ekran
|
|
293
274
|
audioDeviceId: this.options.audioDeviceId || null, // null = default device
|
|
294
275
|
systemAudioDeviceId: this.options.systemAudioDeviceId || null, // null = auto-detect system audio device
|
|
295
|
-
// Exclusion options passthrough
|
|
296
|
-
excludeCurrentApp: this.options.excludeCurrentApp || false,
|
|
297
|
-
excludeWindowIds: this.options.excludeWindowIds || [],
|
|
298
276
|
};
|
|
299
277
|
|
|
300
278
|
// Manuel captureArea varsa onu kullan
|
|
@@ -332,13 +310,13 @@ class MacRecorder extends EventEmitter {
|
|
|
332
310
|
if (nativeStatus && !recordingStartedEmitted) {
|
|
333
311
|
recordingStartedEmitted = true;
|
|
334
312
|
clearInterval(checkRecordingStatus);
|
|
335
|
-
|
|
313
|
+
|
|
336
314
|
// Kayıt gerçekten başladığı anda event emit et
|
|
337
315
|
this.emit("recordingStarted", {
|
|
338
316
|
outputPath: this.outputPath,
|
|
339
317
|
timestamp: Date.now(), // Gerçek başlangıç zamanı
|
|
340
318
|
options: this.options,
|
|
341
|
-
nativeConfirmed: true
|
|
319
|
+
nativeConfirmed: true
|
|
342
320
|
});
|
|
343
321
|
}
|
|
344
322
|
} catch (error) {
|
|
@@ -350,12 +328,12 @@ class MacRecorder extends EventEmitter {
|
|
|
350
328
|
outputPath: this.outputPath,
|
|
351
329
|
timestamp: this.recordingStartTime,
|
|
352
330
|
options: this.options,
|
|
353
|
-
nativeConfirmed: false
|
|
331
|
+
nativeConfirmed: false
|
|
354
332
|
});
|
|
355
333
|
}
|
|
356
334
|
}
|
|
357
335
|
}, 50); // Her 50ms kontrol et
|
|
358
|
-
|
|
336
|
+
|
|
359
337
|
// Timeout fallback - 5 saniye sonra hala başlamamışsa emit et
|
|
360
338
|
setTimeout(() => {
|
|
361
339
|
if (!recordingStartedEmitted) {
|
|
@@ -365,11 +343,11 @@ class MacRecorder extends EventEmitter {
|
|
|
365
343
|
outputPath: this.outputPath,
|
|
366
344
|
timestamp: this.recordingStartTime,
|
|
367
345
|
options: this.options,
|
|
368
|
-
nativeConfirmed: false
|
|
346
|
+
nativeConfirmed: false
|
|
369
347
|
});
|
|
370
348
|
}
|
|
371
349
|
}, 5000);
|
|
372
|
-
|
|
350
|
+
|
|
373
351
|
this.emit("started", this.outputPath);
|
|
374
352
|
resolve(this.outputPath);
|
|
375
353
|
} else {
|
|
@@ -610,7 +588,7 @@ class MacRecorder extends EventEmitter {
|
|
|
610
588
|
width: options.windowInfo.width,
|
|
611
589
|
height: options.windowInfo.height,
|
|
612
590
|
windowRelative: true,
|
|
613
|
-
windowInfo: options.windowInfo
|
|
591
|
+
windowInfo: options.windowInfo
|
|
614
592
|
};
|
|
615
593
|
} else if (this.recordingDisplayInfo) {
|
|
616
594
|
// Recording başlatılmışsa o display'i kullan
|
|
@@ -666,7 +644,7 @@ class MacRecorder extends EventEmitter {
|
|
|
666
644
|
if (this.cursorDisplayInfo.windowRelative) {
|
|
667
645
|
// Window-relative koordinatlar
|
|
668
646
|
coordinateSystem = "window-relative";
|
|
669
|
-
|
|
647
|
+
|
|
670
648
|
// Window bounds kontrolü - cursor window dışındaysa kaydetme
|
|
671
649
|
if (
|
|
672
650
|
x < 0 ||
|
|
@@ -679,7 +657,7 @@ class MacRecorder extends EventEmitter {
|
|
|
679
657
|
} else {
|
|
680
658
|
// Display-relative koordinatlar
|
|
681
659
|
coordinateSystem = "display-relative";
|
|
682
|
-
|
|
660
|
+
|
|
683
661
|
// Display bounds kontrolü
|
|
684
662
|
if (
|
|
685
663
|
x < 0 ||
|
|
@@ -704,9 +682,9 @@ class MacRecorder extends EventEmitter {
|
|
|
704
682
|
windowInfo: {
|
|
705
683
|
width: this.cursorDisplayInfo.width,
|
|
706
684
|
height: this.cursorDisplayInfo.height,
|
|
707
|
-
originalWindow: this.cursorDisplayInfo.windowInfo
|
|
708
|
-
}
|
|
709
|
-
})
|
|
685
|
+
originalWindow: this.cursorDisplayInfo.windowInfo
|
|
686
|
+
}
|
|
687
|
+
})
|
|
710
688
|
};
|
|
711
689
|
|
|
712
690
|
// Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
|
|
@@ -950,66 +928,9 @@ class MacRecorder extends EventEmitter {
|
|
|
950
928
|
|
|
951
929
|
return Promise.all(windowPromises);
|
|
952
930
|
}
|
|
953
|
-
|
|
954
|
-
// Window Selection Methods (ScreenCaptureKit compatible)
|
|
955
|
-
startWindowSelection() {
|
|
956
|
-
if (!nativeBinding.startWindowSelection) {
|
|
957
|
-
throw new Error('Window selection is not available in this build');
|
|
958
|
-
}
|
|
959
|
-
return nativeBinding.startWindowSelection();
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
stopWindowSelection() {
|
|
963
|
-
if (!nativeBinding.stopWindowSelection) {
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
966
|
-
return nativeBinding.stopWindowSelection();
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
getSelectedWindowInfo() {
|
|
970
|
-
if (!nativeBinding.getSelectedWindowInfo) {
|
|
971
|
-
return null;
|
|
972
|
-
}
|
|
973
|
-
return nativeBinding.getSelectedWindowInfo();
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
getWindowSelectionStatus() {
|
|
977
|
-
if (!nativeBinding.getWindowSelectionStatus) {
|
|
978
|
-
return { isSelecting: false, hasSelectedWindow: false };
|
|
979
|
-
}
|
|
980
|
-
return nativeBinding.getWindowSelectionStatus();
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
bringWindowToFront(windowId) {
|
|
984
|
-
if (!nativeBinding.bringWindowToFront) {
|
|
985
|
-
return false;
|
|
986
|
-
}
|
|
987
|
-
return nativeBinding.bringWindowToFront(windowId);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
setBringToFrontEnabled(enabled) {
|
|
991
|
-
if (!nativeBinding.setBringToFrontEnabled) {
|
|
992
|
-
return false;
|
|
993
|
-
}
|
|
994
|
-
return nativeBinding.setBringToFrontEnabled(enabled);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
showRecordingPreview(windowInfo) {
|
|
998
|
-
if (!nativeBinding.showRecordingPreview) {
|
|
999
|
-
return false;
|
|
1000
|
-
}
|
|
1001
|
-
return nativeBinding.showRecordingPreview(windowInfo);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
hideRecordingPreview() {
|
|
1005
|
-
if (!nativeBinding.hideRecordingPreview) {
|
|
1006
|
-
return false;
|
|
1007
|
-
}
|
|
1008
|
-
return nativeBinding.hideRecordingPreview();
|
|
1009
|
-
}
|
|
1010
931
|
}
|
|
1011
932
|
|
|
1012
933
|
// WindowSelector modülünü de export edelim
|
|
1013
|
-
MacRecorder.WindowSelector = require(
|
|
934
|
+
MacRecorder.WindowSelector = require('./window-selector');
|
|
1014
935
|
|
|
1015
936
|
module.exports = MacRecorder;
|
package/install.js
CHANGED
|
@@ -2,7 +2,7 @@ const { spawn } = require("child_process");
|
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
|
|
5
|
-
console.log("🔨
|
|
5
|
+
console.log("🔨 Building native macOS recorder module...\n");
|
|
6
6
|
|
|
7
7
|
// Check if we're on macOS
|
|
8
8
|
if (process.platform !== "darwin") {
|
|
@@ -10,24 +10,7 @@ if (process.platform !== "darwin") {
|
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
const prebuildPath = path.join(
|
|
15
|
-
__dirname,
|
|
16
|
-
"prebuilds",
|
|
17
|
-
`darwin-${process.arch}`,
|
|
18
|
-
"node.napi.node"
|
|
19
|
-
);
|
|
20
|
-
if (
|
|
21
|
-
process.platform === "darwin" &&
|
|
22
|
-
process.arch === "arm64" &&
|
|
23
|
-
fs.existsSync(prebuildPath)
|
|
24
|
-
) {
|
|
25
|
-
console.log("✅ Using prebuilt binary:", prebuildPath);
|
|
26
|
-
console.log("🎉 node-mac-recorder is ready to use (no compilation needed)");
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Fallback to building from source
|
|
13
|
+
// Check if Xcode Command Line Tools are installed
|
|
31
14
|
console.log("🔍 Checking Xcode Command Line Tools...");
|
|
32
15
|
const xcodebuild = spawn("xcode-select", ["--print-path"], { stdio: "pipe" });
|
|
33
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-mac-recorder",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.13",
|
|
4
4
|
"description": "Native macOS screen recording package for Node.js applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -37,9 +37,6 @@
|
|
|
37
37
|
"install": "node install.js",
|
|
38
38
|
"build": "node-gyp build",
|
|
39
39
|
"rebuild": "node-gyp rebuild",
|
|
40
|
-
"prebuild:node-arm64": "prebuildify --platform darwin --arch arm64 --napi --strip",
|
|
41
|
-
"prebuild:electron-arm64": "prebuildify --platform darwin --arch arm64 --napi --strip --targets electron@27.0.0",
|
|
42
|
-
"prebuild:all-arm64": "npm run prebuild:node-arm64 && npm run prebuild:electron-arm64",
|
|
43
40
|
"clean": "node-gyp clean",
|
|
44
41
|
"test:window-selector": "node window-selector-test.js",
|
|
45
42
|
"example:window-selector": "node examples/window-selector-example.js"
|
|
@@ -48,8 +45,7 @@
|
|
|
48
45
|
"node-addon-api": "^7.0.0"
|
|
49
46
|
},
|
|
50
47
|
"devDependencies": {
|
|
51
|
-
"node-gyp": "^10.0.0"
|
|
52
|
-
"prebuildify": "^6.0.1"
|
|
48
|
+
"node-gyp": "^10.0.0"
|
|
53
49
|
},
|
|
54
50
|
"gypfile": true
|
|
55
51
|
}
|
package/src/audio_capture.mm
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
|
2
1
|
#import <AVFoundation/AVFoundation.h>
|
|
3
2
|
#import <CoreAudio/CoreAudio.h>
|
|
4
3
|
|
|
5
4
|
@interface AudioCapture : NSObject
|
|
6
5
|
|
|
7
6
|
+ (NSArray *)getAudioDevices;
|
|
8
|
-
+ (NSArray *)getSystemAudioDevices;
|
|
9
7
|
+ (BOOL)hasAudioPermission;
|
|
10
8
|
+ (void)requestAudioPermission:(void(^)(BOOL granted))completion;
|
|
11
9
|
|
|
@@ -16,39 +14,25 @@
|
|
|
16
14
|
+ (NSArray *)getAudioDevices {
|
|
17
15
|
NSMutableArray *devices = [NSMutableArray array];
|
|
18
16
|
|
|
19
|
-
// Get
|
|
20
|
-
|
|
21
|
-
discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceTypeExternalUnknown]
|
|
22
|
-
mediaType:AVMediaTypeAudio
|
|
23
|
-
position:AVCaptureDevicePositionUnspecified];
|
|
24
|
-
NSArray *audioDevices = discoverySession.devices;
|
|
17
|
+
// Get all audio devices
|
|
18
|
+
NSArray *audioDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
|
|
25
19
|
|
|
26
20
|
for (AVCaptureDevice *device in audioDevices) {
|
|
27
21
|
NSDictionary *deviceInfo = @{
|
|
28
22
|
@"id": device.uniqueID,
|
|
29
23
|
@"name": device.localizedName,
|
|
30
24
|
@"manufacturer": device.manufacturer ?: @"Unknown",
|
|
31
|
-
@"type": @"microphone",
|
|
32
25
|
@"isDefault": @([device isEqual:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]])
|
|
33
26
|
};
|
|
34
27
|
|
|
35
28
|
[devices addObject:deviceInfo];
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
//
|
|
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
|
-
|
|
31
|
+
// Also get system audio devices using Core Audio
|
|
48
32
|
AudioObjectPropertyAddress propertyAddress = {
|
|
49
33
|
kAudioHardwarePropertyDevices,
|
|
50
34
|
kAudioObjectPropertyScopeGlobal,
|
|
51
|
-
|
|
35
|
+
kAudioObjectPropertyElementMaster
|
|
52
36
|
};
|
|
53
37
|
|
|
54
38
|
UInt32 dataSize = 0;
|
|
@@ -65,41 +49,37 @@
|
|
|
65
49
|
AudioDeviceID deviceID = audioDeviceIDs[i];
|
|
66
50
|
|
|
67
51
|
// Get device name
|
|
52
|
+
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
|
53
|
+
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
|
54
|
+
|
|
68
55
|
CFStringRef deviceName = NULL;
|
|
69
|
-
|
|
70
|
-
AudioObjectPropertyAddress nameAddress = {
|
|
71
|
-
kAudioDevicePropertyDeviceNameCFString,
|
|
72
|
-
kAudioDevicePropertyScopeOutput, // Focus on output devices for system audio
|
|
73
|
-
kAudioObjectPropertyElementMain
|
|
74
|
-
};
|
|
56
|
+
dataSize = sizeof(deviceName);
|
|
75
57
|
|
|
76
|
-
status = AudioObjectGetPropertyData(deviceID, &
|
|
58
|
+
status = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
|
77
59
|
|
|
78
60
|
if (status == kAudioHardwareNoError && deviceName) {
|
|
79
|
-
// Check if
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
kAudioDevicePropertyScopeOutput,
|
|
83
|
-
kAudioObjectPropertyElementMain
|
|
84
|
-
};
|
|
61
|
+
// Check if it's an input device
|
|
62
|
+
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
|
63
|
+
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
|
|
85
64
|
|
|
86
|
-
|
|
87
|
-
status = AudioObjectGetPropertyDataSize(deviceID, &streamAddress, 0, NULL, &streamSize);
|
|
65
|
+
AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &dataSize);
|
|
88
66
|
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
NSString *deviceNameStr = name ? [NSString stringWithUTF8String:name] : @"Unknown Device";
|
|
67
|
+
if (dataSize > 0) {
|
|
68
|
+
AudioBufferList *bufferList = (AudioBufferList *)malloc(dataSize);
|
|
69
|
+
AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, bufferList);
|
|
93
70
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
71
|
+
if (bufferList->mNumberBuffers > 0) {
|
|
72
|
+
NSDictionary *deviceInfo = @{
|
|
73
|
+
@"id": @(deviceID),
|
|
74
|
+
@"name": (__bridge NSString *)deviceName,
|
|
75
|
+
@"type": @"System Audio Input",
|
|
76
|
+
@"isSystemDevice": @YES
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
[devices addObject:deviceInfo];
|
|
80
|
+
}
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
free(bufferList);
|
|
103
83
|
}
|
|
104
84
|
|
|
105
85
|
CFRelease(deviceName);
|
|
@@ -114,59 +94,23 @@
|
|
|
114
94
|
}
|
|
115
95
|
|
|
116
96
|
+ (BOOL)hasAudioPermission {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
if (@available(macOS 10.14, *)) {
|
|
98
|
+
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
|
99
|
+
return status == AVAuthorizationStatusAuthorized;
|
|
100
|
+
}
|
|
101
|
+
return YES; // Older versions don't require explicit permission
|
|
120
102
|
}
|
|
121
103
|
|
|
122
104
|
+ (void)requestAudioPermission:(void(^)(BOOL granted))completion {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
if (@available(macOS 10.14, *)) {
|
|
106
|
+
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
|
|
107
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
126
108
|
completion(granted);
|
|
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
|
-
}
|
|
109
|
+
});
|
|
110
|
+
}];
|
|
111
|
+
} else {
|
|
112
|
+
completion(YES);
|
|
167
113
|
}
|
|
168
|
-
|
|
169
|
-
return YES;
|
|
170
114
|
}
|
|
171
115
|
|
|
172
|
-
@end
|
|
116
|
+
@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 = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
171
|
+
NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
|
|
172
172
|
|
|
173
173
|
if (g_isFirstWrite) {
|
|
174
174
|
// İlk yazma - array başlat
|
|
@@ -292,6 +292,7 @@ void cleanupCursorTracking() {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
if (g_timerTarget) {
|
|
295
|
+
[g_timerTarget autorelease];
|
|
295
296
|
g_timerTarget = nil;
|
|
296
297
|
}
|
|
297
298
|
|
|
@@ -351,12 +352,12 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
|
|
|
351
352
|
@try {
|
|
352
353
|
// Dosyayı oluştur ve aç
|
|
353
354
|
g_outputPath = [NSString stringWithUTF8String:outputPath.c_str()];
|
|
354
|
-
g_fileHandle = [NSFileHandle fileHandleForWritingAtPath:g_outputPath];
|
|
355
|
+
g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
|
|
355
356
|
|
|
356
357
|
if (!g_fileHandle) {
|
|
357
358
|
// Dosya yoksa oluştur
|
|
358
359
|
[[NSFileManager defaultManager] createFileAtPath:g_outputPath contents:nil attributes:nil];
|
|
359
|
-
g_fileHandle = [NSFileHandle fileHandleForWritingAtPath:g_outputPath];
|
|
360
|
+
g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
|
|
360
361
|
}
|
|
361
362
|
|
|
362
363
|
if (!g_fileHandle) {
|