node-mac-recorder 2.20.16 → 2.21.0
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/AUDIO_CAPTURE.md +84 -0
- package/CAMERA_CAPTURE.md +77 -0
- package/MACOS_PERMISSIONS.md +56 -0
- package/README.md +76 -3
- package/binding.gyp +3 -2
- package/index.js +278 -23
- package/package.json +1 -1
- package/scripts/test-multi-capture.js +103 -0
- package/src/audio_recorder.mm +321 -0
- package/src/camera_recorder.mm +677 -0
- package/src/mac_recorder.mm +310 -67
- package/src/screen_capture_kit.h +2 -2
- package/src/screen_capture_kit.mm +351 -92
- package/src/window_selector.mm +1 -1
- package/src/audio_capture.mm +0 -41
package/index.js
CHANGED
|
@@ -34,6 +34,11 @@ class MacRecorder extends EventEmitter {
|
|
|
34
34
|
this.lastCapturedData = null;
|
|
35
35
|
this.cursorDisplayInfo = null;
|
|
36
36
|
this.recordingDisplayInfo = null;
|
|
37
|
+
this.cameraCaptureFile = null;
|
|
38
|
+
this.cameraCaptureActive = false;
|
|
39
|
+
this.sessionTimestamp = null;
|
|
40
|
+
this.audioCaptureFile = null;
|
|
41
|
+
this.audioCaptureActive = false;
|
|
37
42
|
|
|
38
43
|
this.options = {
|
|
39
44
|
includeMicrophone: false, // Default olarak mikrofon kapalı
|
|
@@ -45,6 +50,9 @@ class MacRecorder extends EventEmitter {
|
|
|
45
50
|
showClicks: false,
|
|
46
51
|
displayId: null, // Hangi ekranı kaydedeceği (null = ana ekran)
|
|
47
52
|
windowId: null, // Hangi pencereyi kaydedeceği (null = tam ekran)
|
|
53
|
+
captureCamera: false,
|
|
54
|
+
cameraDeviceId: null,
|
|
55
|
+
systemAudioDeviceId: null,
|
|
48
56
|
};
|
|
49
57
|
|
|
50
58
|
// Display cache için async initialization
|
|
@@ -63,9 +71,11 @@ class MacRecorder extends EventEmitter {
|
|
|
63
71
|
try {
|
|
64
72
|
const devices = nativeBinding.getAudioDevices();
|
|
65
73
|
const formattedDevices = devices.map((device) => ({
|
|
66
|
-
name:
|
|
67
|
-
id:
|
|
68
|
-
|
|
74
|
+
name: device?.name || "Unknown Audio Device",
|
|
75
|
+
id: device?.id || "",
|
|
76
|
+
manufacturer: device?.manufacturer || null,
|
|
77
|
+
isDefault: device?.isDefault === true,
|
|
78
|
+
transportType: device?.transportType ?? null,
|
|
69
79
|
}));
|
|
70
80
|
resolve(formattedDevices);
|
|
71
81
|
} catch (error) {
|
|
@@ -74,6 +84,39 @@ class MacRecorder extends EventEmitter {
|
|
|
74
84
|
});
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
/**
|
|
88
|
+
* macOS kamera cihazlarını listeler
|
|
89
|
+
*/
|
|
90
|
+
async getCameraDevices() {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
try {
|
|
93
|
+
const devices = nativeBinding.getCameraDevices();
|
|
94
|
+
if (!Array.isArray(devices)) {
|
|
95
|
+
return resolve([]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const formatted = devices.map((device) => ({
|
|
99
|
+
id: device?.id ?? "",
|
|
100
|
+
name: device?.name ?? "Unknown Camera",
|
|
101
|
+
model: device?.model ?? null,
|
|
102
|
+
manufacturer: device?.manufacturer ?? null,
|
|
103
|
+
position: device?.position ?? "unspecified",
|
|
104
|
+
transportType: device?.transportType ?? null,
|
|
105
|
+
isConnected: device?.isConnected ?? false,
|
|
106
|
+
hasFlash: device?.hasFlash ?? false,
|
|
107
|
+
supportsDepth: device?.supportsDepth ?? false,
|
|
108
|
+
deviceType: device?.deviceType ?? null,
|
|
109
|
+
requiresContinuityCameraPermission: device?.requiresContinuityCameraPermission ?? false,
|
|
110
|
+
maxResolution: device?.maxResolution ?? null,
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
resolve(formatted);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
reject(error);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
77
120
|
/**
|
|
78
121
|
* macOS ekranlarını listeler
|
|
79
122
|
*/
|
|
@@ -118,6 +161,11 @@ class MacRecorder extends EventEmitter {
|
|
|
118
161
|
audioDeviceId: options.audioDeviceId || null, // null = default device
|
|
119
162
|
systemAudioDeviceId: options.systemAudioDeviceId || null, // null = auto-detect system audio device
|
|
120
163
|
captureArea: options.captureArea || null,
|
|
164
|
+
captureCamera: options.captureCamera === true,
|
|
165
|
+
cameraDeviceId:
|
|
166
|
+
typeof options.cameraDeviceId === "string" && options.cameraDeviceId.length > 0
|
|
167
|
+
? options.cameraDeviceId
|
|
168
|
+
: null,
|
|
121
169
|
};
|
|
122
170
|
}
|
|
123
171
|
|
|
@@ -129,6 +177,15 @@ class MacRecorder extends EventEmitter {
|
|
|
129
177
|
return this.options.includeMicrophone;
|
|
130
178
|
}
|
|
131
179
|
|
|
180
|
+
setAudioDevice(deviceId) {
|
|
181
|
+
if (typeof deviceId === "string" && deviceId.length > 0) {
|
|
182
|
+
this.options.audioDeviceId = deviceId;
|
|
183
|
+
} else {
|
|
184
|
+
this.options.audioDeviceId = null;
|
|
185
|
+
}
|
|
186
|
+
return this.options.audioDeviceId;
|
|
187
|
+
}
|
|
188
|
+
|
|
132
189
|
/**
|
|
133
190
|
* Sistem sesi kaydını açar/kapatır
|
|
134
191
|
*/
|
|
@@ -137,6 +194,38 @@ class MacRecorder extends EventEmitter {
|
|
|
137
194
|
return this.options.includeSystemAudio;
|
|
138
195
|
}
|
|
139
196
|
|
|
197
|
+
setSystemAudioDevice(deviceId) {
|
|
198
|
+
if (typeof deviceId === "string" && deviceId.length > 0) {
|
|
199
|
+
this.options.systemAudioDeviceId = deviceId;
|
|
200
|
+
} else {
|
|
201
|
+
this.options.systemAudioDeviceId = null;
|
|
202
|
+
}
|
|
203
|
+
return this.options.systemAudioDeviceId;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Kamera kaydını açar/kapatır
|
|
208
|
+
*/
|
|
209
|
+
setCameraEnabled(enabled) {
|
|
210
|
+
this.options.captureCamera = enabled === true;
|
|
211
|
+
if (!this.options.captureCamera) {
|
|
212
|
+
this.cameraCaptureActive = false;
|
|
213
|
+
}
|
|
214
|
+
return this.options.captureCamera;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Kamera cihazını seçer
|
|
219
|
+
*/
|
|
220
|
+
setCameraDevice(deviceId) {
|
|
221
|
+
if (typeof deviceId === "string" && deviceId.length > 0) {
|
|
222
|
+
this.options.cameraDeviceId = deviceId;
|
|
223
|
+
} else {
|
|
224
|
+
this.options.cameraDeviceId = null;
|
|
225
|
+
}
|
|
226
|
+
return this.options.cameraDeviceId;
|
|
227
|
+
}
|
|
228
|
+
|
|
140
229
|
/**
|
|
141
230
|
* Mikrofon durumunu döndürür
|
|
142
231
|
*/
|
|
@@ -151,6 +240,13 @@ class MacRecorder extends EventEmitter {
|
|
|
151
240
|
return this.options.includeSystemAudio === true;
|
|
152
241
|
}
|
|
153
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Kamera durumunu döndürür
|
|
245
|
+
*/
|
|
246
|
+
isCameraEnabled() {
|
|
247
|
+
return this.options.captureCamera === true;
|
|
248
|
+
}
|
|
249
|
+
|
|
154
250
|
/**
|
|
155
251
|
* Audio ayarlarını toplu olarak değiştirir
|
|
156
252
|
*/
|
|
@@ -325,9 +421,34 @@ class MacRecorder extends EventEmitter {
|
|
|
325
421
|
return new Promise((resolve, reject) => {
|
|
326
422
|
try {
|
|
327
423
|
// Create cursor file path with timestamp in the same directory as video
|
|
328
|
-
const
|
|
424
|
+
const sessionTimestamp = Date.now();
|
|
425
|
+
this.sessionTimestamp = sessionTimestamp;
|
|
329
426
|
const outputDir = path.dirname(outputPath);
|
|
330
|
-
const cursorFilePath = path.join(outputDir, `temp_cursor_${
|
|
427
|
+
const cursorFilePath = path.join(outputDir, `temp_cursor_${sessionTimestamp}.json`);
|
|
428
|
+
const cameraFilePath =
|
|
429
|
+
this.options.captureCamera === true
|
|
430
|
+
? path.join(outputDir, `temp_camera_${sessionTimestamp}.webm`)
|
|
431
|
+
: null;
|
|
432
|
+
const captureAudio = this.options.includeMicrophone === true || this.options.includeSystemAudio === true;
|
|
433
|
+
const audioFilePath = captureAudio
|
|
434
|
+
? path.join(outputDir, `temp_audio_${sessionTimestamp}.webm`)
|
|
435
|
+
: null;
|
|
436
|
+
|
|
437
|
+
if (this.options.captureCamera === true) {
|
|
438
|
+
this.cameraCaptureFile = cameraFilePath;
|
|
439
|
+
this.cameraCaptureActive = false;
|
|
440
|
+
} else {
|
|
441
|
+
this.cameraCaptureFile = null;
|
|
442
|
+
this.cameraCaptureActive = false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (captureAudio) {
|
|
446
|
+
this.audioCaptureFile = audioFilePath;
|
|
447
|
+
this.audioCaptureActive = false;
|
|
448
|
+
} else {
|
|
449
|
+
this.audioCaptureFile = null;
|
|
450
|
+
this.audioCaptureActive = false;
|
|
451
|
+
}
|
|
331
452
|
|
|
332
453
|
// Native kayıt başlat
|
|
333
454
|
const recordingOptions = {
|
|
@@ -338,8 +459,19 @@ class MacRecorder extends EventEmitter {
|
|
|
338
459
|
windowId: this.options.windowId || null, // null = tam ekran
|
|
339
460
|
audioDeviceId: this.options.audioDeviceId || null, // null = default device
|
|
340
461
|
systemAudioDeviceId: this.options.systemAudioDeviceId || null, // null = auto-detect system audio device
|
|
462
|
+
captureCamera: this.options.captureCamera === true,
|
|
463
|
+
cameraDeviceId: this.options.cameraDeviceId || null,
|
|
464
|
+
sessionTimestamp,
|
|
341
465
|
};
|
|
342
466
|
|
|
467
|
+
if (cameraFilePath) {
|
|
468
|
+
recordingOptions.cameraOutputPath = cameraFilePath;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (audioFilePath) {
|
|
472
|
+
recordingOptions.audioOutputPath = audioFilePath;
|
|
473
|
+
}
|
|
474
|
+
|
|
343
475
|
// Manuel captureArea varsa onu kullan
|
|
344
476
|
if (this.options.captureArea) {
|
|
345
477
|
recordingOptions.captureArea = {
|
|
@@ -365,6 +497,29 @@ class MacRecorder extends EventEmitter {
|
|
|
365
497
|
this.isRecording = true;
|
|
366
498
|
this.recordingStartTime = Date.now();
|
|
367
499
|
|
|
500
|
+
if (this.options.captureCamera === true && cameraFilePath) {
|
|
501
|
+
this.cameraCaptureActive = true;
|
|
502
|
+
this.emit("cameraCaptureStarted", {
|
|
503
|
+
outputPath: cameraFilePath,
|
|
504
|
+
deviceId: this.options.cameraDeviceId || null,
|
|
505
|
+
timestamp: sessionTimestamp,
|
|
506
|
+
sessionTimestamp,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (captureAudio && audioFilePath) {
|
|
511
|
+
this.audioCaptureActive = true;
|
|
512
|
+
this.emit("audioCaptureStarted", {
|
|
513
|
+
outputPath: audioFilePath,
|
|
514
|
+
deviceIds: {
|
|
515
|
+
microphone: this.options.audioDeviceId || null,
|
|
516
|
+
system: this.options.systemAudioDeviceId || null,
|
|
517
|
+
},
|
|
518
|
+
timestamp: sessionTimestamp,
|
|
519
|
+
sessionTimestamp,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
368
523
|
// Start unified cursor tracking with video-relative coordinates
|
|
369
524
|
// This ensures cursor positions match exactly with video frames
|
|
370
525
|
const standardCursorOptions = {
|
|
@@ -398,24 +553,32 @@ class MacRecorder extends EventEmitter {
|
|
|
398
553
|
clearInterval(checkRecordingStatus);
|
|
399
554
|
|
|
400
555
|
// Kayıt gerçekten başladığı anda event emit et
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
556
|
+
this.emit("recordingStarted", {
|
|
557
|
+
outputPath: this.outputPath,
|
|
558
|
+
timestamp: Date.now(), // Gerçek başlangıç zamanı
|
|
559
|
+
options: this.options,
|
|
560
|
+
nativeConfirmed: true,
|
|
561
|
+
cameraOutputPath: this.cameraCaptureFile || null,
|
|
562
|
+
audioOutputPath: this.audioCaptureFile || null,
|
|
563
|
+
cursorOutputPath: cursorFilePath,
|
|
564
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
565
|
+
});
|
|
407
566
|
}
|
|
408
567
|
} catch (error) {
|
|
409
568
|
// Native status check error - fallback
|
|
410
569
|
if (!recordingStartedEmitted) {
|
|
411
570
|
recordingStartedEmitted = true;
|
|
412
571
|
clearInterval(checkRecordingStatus);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
572
|
+
this.emit("recordingStarted", {
|
|
573
|
+
outputPath: this.outputPath,
|
|
574
|
+
timestamp: this.recordingStartTime,
|
|
575
|
+
options: this.options,
|
|
576
|
+
nativeConfirmed: false,
|
|
577
|
+
cameraOutputPath: this.cameraCaptureFile || null,
|
|
578
|
+
audioOutputPath: this.audioCaptureFile || null,
|
|
579
|
+
cursorOutputPath: cursorFilePath,
|
|
580
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
581
|
+
});
|
|
419
582
|
}
|
|
420
583
|
}
|
|
421
584
|
}, 50); // Her 50ms kontrol et
|
|
@@ -425,18 +588,48 @@ class MacRecorder extends EventEmitter {
|
|
|
425
588
|
if (!recordingStartedEmitted) {
|
|
426
589
|
recordingStartedEmitted = true;
|
|
427
590
|
clearInterval(checkRecordingStatus);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
591
|
+
this.emit("recordingStarted", {
|
|
592
|
+
outputPath: this.outputPath,
|
|
593
|
+
timestamp: this.recordingStartTime,
|
|
594
|
+
options: this.options,
|
|
595
|
+
nativeConfirmed: false,
|
|
596
|
+
cameraOutputPath: this.cameraCaptureFile || null,
|
|
597
|
+
audioOutputPath: this.audioCaptureFile || null,
|
|
598
|
+
cursorOutputPath: cursorFilePath,
|
|
599
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
600
|
+
});
|
|
434
601
|
}
|
|
435
602
|
}, 5000);
|
|
436
603
|
|
|
437
604
|
this.emit("started", this.outputPath);
|
|
438
605
|
resolve(this.outputPath);
|
|
439
606
|
} else {
|
|
607
|
+
this.cameraCaptureActive = false;
|
|
608
|
+
if (this.options.captureCamera === true) {
|
|
609
|
+
if (cameraFilePath && fs.existsSync(cameraFilePath)) {
|
|
610
|
+
try {
|
|
611
|
+
fs.unlinkSync(cameraFilePath);
|
|
612
|
+
} catch (cleanupError) {
|
|
613
|
+
console.warn("Camera temp file cleanup failed:", cleanupError.message);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
this.cameraCaptureFile = null;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (captureAudio) {
|
|
620
|
+
this.audioCaptureActive = false;
|
|
621
|
+
if (audioFilePath && fs.existsSync(audioFilePath)) {
|
|
622
|
+
try {
|
|
623
|
+
fs.unlinkSync(audioFilePath);
|
|
624
|
+
} catch (cleanupError) {
|
|
625
|
+
console.warn("Audio temp file cleanup failed:", cleanupError.message);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
this.audioCaptureFile = null;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
this.sessionTimestamp = null;
|
|
632
|
+
|
|
440
633
|
reject(
|
|
441
634
|
new Error(
|
|
442
635
|
"Recording failed to start. Check permissions, output path, and system compatibility."
|
|
@@ -444,6 +637,7 @@ class MacRecorder extends EventEmitter {
|
|
|
444
637
|
);
|
|
445
638
|
}
|
|
446
639
|
} catch (error) {
|
|
640
|
+
this.sessionTimestamp = null;
|
|
447
641
|
reject(error);
|
|
448
642
|
}
|
|
449
643
|
});
|
|
@@ -470,6 +664,24 @@ class MacRecorder extends EventEmitter {
|
|
|
470
664
|
success = true; // Assume success to avoid throwing
|
|
471
665
|
}
|
|
472
666
|
|
|
667
|
+
if (this.cameraCaptureActive) {
|
|
668
|
+
this.cameraCaptureActive = false;
|
|
669
|
+
this.emit("cameraCaptureStopped", {
|
|
670
|
+
outputPath: this.cameraCaptureFile || null,
|
|
671
|
+
success: success === true,
|
|
672
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (this.audioCaptureActive) {
|
|
677
|
+
this.audioCaptureActive = false;
|
|
678
|
+
this.emit("audioCaptureStopped", {
|
|
679
|
+
outputPath: this.audioCaptureFile || null,
|
|
680
|
+
success: success === true,
|
|
681
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
473
685
|
// Stop cursor tracking automatically
|
|
474
686
|
if (this.cursorCaptureInterval) {
|
|
475
687
|
this.stopCursorCapture().catch(cursorError => {
|
|
@@ -486,9 +698,13 @@ class MacRecorder extends EventEmitter {
|
|
|
486
698
|
this.isRecording = false;
|
|
487
699
|
this.recordingDisplayInfo = null;
|
|
488
700
|
|
|
701
|
+
const sessionId = this.sessionTimestamp;
|
|
489
702
|
const result = {
|
|
490
703
|
code: success ? 0 : 1,
|
|
491
704
|
outputPath: this.outputPath,
|
|
705
|
+
cameraOutputPath: this.cameraCaptureFile || null,
|
|
706
|
+
audioOutputPath: this.audioCaptureFile || null,
|
|
707
|
+
sessionTimestamp: sessionId,
|
|
492
708
|
};
|
|
493
709
|
|
|
494
710
|
this.emit("stopped", result);
|
|
@@ -502,10 +718,15 @@ class MacRecorder extends EventEmitter {
|
|
|
502
718
|
}, 1000);
|
|
503
719
|
}
|
|
504
720
|
|
|
721
|
+
this.sessionTimestamp = null;
|
|
505
722
|
resolve(result);
|
|
506
723
|
} catch (error) {
|
|
507
724
|
this.isRecording = false;
|
|
508
725
|
this.recordingDisplayInfo = null;
|
|
726
|
+
this.cameraCaptureActive = false;
|
|
727
|
+
this.audioCaptureActive = false;
|
|
728
|
+
this.audioCaptureFile = null;
|
|
729
|
+
this.sessionTimestamp = null;
|
|
509
730
|
if (this.recordingTimer) {
|
|
510
731
|
clearInterval(this.recordingTimer);
|
|
511
732
|
this.recordingTimer = null;
|
|
@@ -523,6 +744,11 @@ class MacRecorder extends EventEmitter {
|
|
|
523
744
|
return {
|
|
524
745
|
isRecording: this.isRecording && nativeStatus,
|
|
525
746
|
outputPath: this.outputPath,
|
|
747
|
+
cameraOutputPath: this.cameraCaptureFile || null,
|
|
748
|
+
audioOutputPath: this.audioCaptureFile || null,
|
|
749
|
+
cameraCapturing: this.cameraCaptureActive,
|
|
750
|
+
audioCapturing: this.audioCaptureActive,
|
|
751
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
526
752
|
options: this.options,
|
|
527
753
|
recordingTime: this.recordingStartTime
|
|
528
754
|
? Math.floor((Date.now() - this.recordingStartTime) / 1000)
|
|
@@ -995,6 +1221,35 @@ class MacRecorder extends EventEmitter {
|
|
|
995
1221
|
return this.getCursorPosition();
|
|
996
1222
|
}
|
|
997
1223
|
|
|
1224
|
+
/**
|
|
1225
|
+
* Kamera capture durumunu döndürür
|
|
1226
|
+
*/
|
|
1227
|
+
getCameraCaptureStatus() {
|
|
1228
|
+
return {
|
|
1229
|
+
isCapturing: this.cameraCaptureActive === true,
|
|
1230
|
+
outputFile: this.cameraCaptureFile || null,
|
|
1231
|
+
deviceId: this.options.cameraDeviceId || null,
|
|
1232
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Audio capture durumunu döndürür
|
|
1238
|
+
*/
|
|
1239
|
+
getAudioCaptureStatus() {
|
|
1240
|
+
return {
|
|
1241
|
+
isCapturing: this.audioCaptureActive === true,
|
|
1242
|
+
outputFile: this.audioCaptureFile || null,
|
|
1243
|
+
deviceIds: {
|
|
1244
|
+
microphone: this.options.audioDeviceId || null,
|
|
1245
|
+
system: this.options.systemAudioDeviceId || null,
|
|
1246
|
+
},
|
|
1247
|
+
includeMicrophone: this.options.includeMicrophone === true,
|
|
1248
|
+
includeSystemAudio: this.options.includeSystemAudio === true,
|
|
1249
|
+
sessionTimestamp: this.sessionTimestamp,
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
|
|
998
1253
|
/**
|
|
999
1254
|
* Cursor capture durumunu döndürür
|
|
1000
1255
|
*/
|
package/package.json
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const MacRecorder = require("../index");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const recorder = new MacRecorder();
|
|
7
|
+
|
|
8
|
+
// Optional: list audio and camera devices for reference
|
|
9
|
+
const audioDevices = await recorder.getAudioDevices();
|
|
10
|
+
const cameraDevices = await recorder.getCameraDevices();
|
|
11
|
+
|
|
12
|
+
console.log("Audio devices:");
|
|
13
|
+
audioDevices.forEach((device, idx) => {
|
|
14
|
+
console.log(`${idx + 1}. ${device.name} (id: ${device.id})`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
console.log("\nCamera devices:");
|
|
18
|
+
cameraDevices.forEach((device, idx) => {
|
|
19
|
+
console.log(`${idx + 1}. ${device.name} (id: ${device.id})`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Pick the first available devices (customize as needed)
|
|
23
|
+
const preferredCamera = cameraDevices.find(device => !device.requiresContinuityCameraPermission);
|
|
24
|
+
const selectedCameraId = preferredCamera ? preferredCamera.id : null;
|
|
25
|
+
if (!selectedCameraId && cameraDevices.length > 0) {
|
|
26
|
+
console.warn("Skipping camera capture: only Continuity Camera devices detected. Add NSCameraUseContinuityCameraDeviceType to Info.plist or set ALLOW_CONTINUITY_CAMERA=1.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (selectedCameraId) {
|
|
30
|
+
console.log(`\nSelected camera: ${preferredCamera.name} (id: ${selectedCameraId})`);
|
|
31
|
+
} else {
|
|
32
|
+
console.log("\nSelected camera: none (camera capture disabled)");
|
|
33
|
+
}
|
|
34
|
+
const selectedMicId = audioDevices[0]?.id || null;
|
|
35
|
+
|
|
36
|
+
if (selectedCameraId) {
|
|
37
|
+
recorder.setCameraDevice(selectedCameraId);
|
|
38
|
+
recorder.setCameraEnabled(true);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
recorder.setAudioSettings({
|
|
42
|
+
microphone: !!selectedMicId,
|
|
43
|
+
systemAudio: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (selectedMicId) {
|
|
47
|
+
recorder.setAudioDevice(selectedMicId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const outputDir = path.resolve(__dirname, "../tmp-tests");
|
|
51
|
+
if (!fs.existsSync(outputDir)) {
|
|
52
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const outputPath = path.join(outputDir, `test_capture_${Date.now()}.mov`);
|
|
56
|
+
console.log("\nStarting recording to:", outputPath);
|
|
57
|
+
|
|
58
|
+
recorder.on("recordingStarted", (payload) => {
|
|
59
|
+
console.log("recordingStarted", payload);
|
|
60
|
+
});
|
|
61
|
+
recorder.on("cameraCaptureStarted", (payload) => {
|
|
62
|
+
console.log("cameraCaptureStarted", payload);
|
|
63
|
+
});
|
|
64
|
+
recorder.on("audioCaptureStarted", (payload) => {
|
|
65
|
+
console.log("audioCaptureStarted", payload);
|
|
66
|
+
});
|
|
67
|
+
recorder.on("cameraCaptureStopped", (payload) => {
|
|
68
|
+
console.log("cameraCaptureStopped", payload);
|
|
69
|
+
});
|
|
70
|
+
recorder.on("audioCaptureStopped", (payload) => {
|
|
71
|
+
console.log("audioCaptureStopped", payload);
|
|
72
|
+
});
|
|
73
|
+
recorder.on("stopped", (payload) => {
|
|
74
|
+
console.log("stopped", payload);
|
|
75
|
+
});
|
|
76
|
+
recorder.on("completed", (filePath) => {
|
|
77
|
+
console.log("completed", filePath);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await recorder.startRecording(outputPath, {
|
|
81
|
+
includeMicrophone: !!selectedMicId,
|
|
82
|
+
includeSystemAudio: true,
|
|
83
|
+
captureCursor: true,
|
|
84
|
+
captureCamera: !!selectedCameraId,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log("Recording for 10 seconds...");
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, 10_000));
|
|
89
|
+
|
|
90
|
+
const result = await recorder.stopRecording();
|
|
91
|
+
console.log("\nRecording finished:", result);
|
|
92
|
+
|
|
93
|
+
console.log("\nArtifacts:");
|
|
94
|
+
console.log("Video:", result.outputPath);
|
|
95
|
+
console.log("Camera:", result.cameraOutputPath);
|
|
96
|
+
console.log("Audio:", result.audioOutputPath);
|
|
97
|
+
console.log("Session timestamp:", result.sessionTimestamp);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main().catch((error) => {
|
|
101
|
+
console.error("Test capture failed:", error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|