node-mac-recorder 2.16.33 → 2.17.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 +3 -1
- package/index.js +70 -0
- package/package.json +1 -1
- package/test-integrated-recording.js +120 -0
- package/test-window-recording.js +149 -0
package/index.js
CHANGED
|
@@ -315,6 +315,11 @@ class MacRecorder extends EventEmitter {
|
|
|
315
315
|
|
|
316
316
|
return new Promise((resolve, reject) => {
|
|
317
317
|
try {
|
|
318
|
+
// Create cursor file path with timestamp in the same directory as video
|
|
319
|
+
const timestamp = Date.now();
|
|
320
|
+
const outputDir = path.dirname(outputPath);
|
|
321
|
+
const cursorFilePath = path.join(outputDir, `temp_cursor_${timestamp}.json`);
|
|
322
|
+
|
|
318
323
|
// Native kayıt başlat
|
|
319
324
|
const recordingOptions = {
|
|
320
325
|
includeMicrophone: this.options.includeMicrophone === true, // Only if explicitly enabled
|
|
@@ -351,6 +356,64 @@ class MacRecorder extends EventEmitter {
|
|
|
351
356
|
this.isRecording = true;
|
|
352
357
|
this.recordingStartTime = Date.now();
|
|
353
358
|
|
|
359
|
+
// Start cursor tracking automatically with recording
|
|
360
|
+
let cursorOptions = {};
|
|
361
|
+
|
|
362
|
+
// For window recording, use the original window coordinates (not display-relative captureArea)
|
|
363
|
+
if (this.options.windowId) {
|
|
364
|
+
// Use cached window info from the earlier window detection
|
|
365
|
+
this.getWindows().then(windows => {
|
|
366
|
+
const targetWindow = windows.find(w => w.id === this.options.windowId);
|
|
367
|
+
if (targetWindow) {
|
|
368
|
+
// Restart cursor capture with correct window coordinates
|
|
369
|
+
if (this.cursorCaptureInterval) {
|
|
370
|
+
this.stopCursorCapture().then(() => {
|
|
371
|
+
this.startCursorCapture(cursorFilePath, {
|
|
372
|
+
windowRelative: true,
|
|
373
|
+
windowInfo: {
|
|
374
|
+
x: targetWindow.x, // Global window X coordinate
|
|
375
|
+
y: targetWindow.y, // Global window Y coordinate
|
|
376
|
+
width: targetWindow.width,
|
|
377
|
+
height: targetWindow.height,
|
|
378
|
+
displayId: this.options.displayId,
|
|
379
|
+
originalWindow: targetWindow
|
|
380
|
+
}
|
|
381
|
+
}).catch(cursorError => {
|
|
382
|
+
console.warn('Cursor tracking failed to restart:', cursorError.message);
|
|
383
|
+
});
|
|
384
|
+
}).catch(stopError => {
|
|
385
|
+
console.warn('Failed to stop cursor capture:', stopError.message);
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
this.startCursorCapture(cursorFilePath, {
|
|
389
|
+
windowRelative: true,
|
|
390
|
+
windowInfo: {
|
|
391
|
+
x: targetWindow.x, // Global window X coordinate
|
|
392
|
+
y: targetWindow.y, // Global window Y coordinate
|
|
393
|
+
width: targetWindow.width,
|
|
394
|
+
height: targetWindow.height,
|
|
395
|
+
displayId: this.options.displayId,
|
|
396
|
+
originalWindow: targetWindow
|
|
397
|
+
}
|
|
398
|
+
}).catch(cursorError => {
|
|
399
|
+
console.warn('Cursor tracking failed to start:', cursorError.message);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}).catch(error => {
|
|
404
|
+
console.warn('Could not get window info for cursor tracking:', error.message);
|
|
405
|
+
// Fallback to basic cursor tracking
|
|
406
|
+
this.startCursorCapture(cursorFilePath, cursorOptions).catch(cursorError => {
|
|
407
|
+
console.warn('Cursor tracking failed to start:', cursorError.message);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
} else {
|
|
411
|
+
// For display recording, use basic cursor tracking
|
|
412
|
+
this.startCursorCapture(cursorFilePath, cursorOptions).catch(cursorError => {
|
|
413
|
+
console.warn('Cursor tracking failed to start:', cursorError.message);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
354
417
|
// Timer başlat (progress tracking için)
|
|
355
418
|
this.recordingTimer = setInterval(() => {
|
|
356
419
|
const elapsed = Math.floor(
|
|
@@ -441,6 +504,13 @@ class MacRecorder extends EventEmitter {
|
|
|
441
504
|
success = true; // Assume success to avoid throwing
|
|
442
505
|
}
|
|
443
506
|
|
|
507
|
+
// Stop cursor tracking automatically
|
|
508
|
+
if (this.cursorCaptureInterval) {
|
|
509
|
+
this.stopCursorCapture().catch(cursorError => {
|
|
510
|
+
console.warn('Cursor tracking failed to stop:', cursorError.message);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
444
514
|
// Timer durdur
|
|
445
515
|
if (this.recordingTimer) {
|
|
446
516
|
clearInterval(this.recordingTimer);
|
package/package.json
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const MacRecorder = require('./index.js');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// Test output directory
|
|
8
|
+
const outputDir = './test-output';
|
|
9
|
+
if (!fs.existsSync(outputDir)) {
|
|
10
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const recorder = new MacRecorder();
|
|
14
|
+
|
|
15
|
+
async function testIntegratedRecording() {
|
|
16
|
+
console.log('🎬 Testing integrated screen recording with automatic cursor tracking...');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Test permissions first
|
|
20
|
+
const permissions = await recorder.checkPermissions();
|
|
21
|
+
console.log('📋 Permissions:', permissions);
|
|
22
|
+
|
|
23
|
+
if (!permissions.screenRecording) {
|
|
24
|
+
console.log('❌ Screen recording permission required');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Get displays and windows
|
|
29
|
+
const displays = await recorder.getDisplays();
|
|
30
|
+
const windows = await recorder.getWindows();
|
|
31
|
+
|
|
32
|
+
console.log(`📺 Found ${displays.length} displays and ${windows.length} windows`);
|
|
33
|
+
|
|
34
|
+
// Setup recording options
|
|
35
|
+
const timestamp = Date.now();
|
|
36
|
+
const videoPath = path.join(outputDir, `integrated-test-${timestamp}.mov`);
|
|
37
|
+
|
|
38
|
+
console.log(`🎥 Starting recording to: ${videoPath}`);
|
|
39
|
+
console.log('📍 Cursor tracking will start automatically and create a JSON file in the same directory');
|
|
40
|
+
|
|
41
|
+
// Start recording (cursor tracking will start automatically)
|
|
42
|
+
// Use the primary display (displayId: 1) where cursor is located
|
|
43
|
+
await recorder.startRecording(videoPath, {
|
|
44
|
+
includeMicrophone: false,
|
|
45
|
+
includeSystemAudio: false,
|
|
46
|
+
captureCursor: true,
|
|
47
|
+
displayId: 1 // Use primary display where cursor is located
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log('✅ Recording started successfully!');
|
|
51
|
+
console.log('🖱️ Cursor tracking started automatically');
|
|
52
|
+
console.log('⏰ Recording for 5 seconds...');
|
|
53
|
+
console.log('💡 Move your mouse around to generate cursor data');
|
|
54
|
+
|
|
55
|
+
// Wait for 5 seconds
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
57
|
+
|
|
58
|
+
console.log('🛑 Stopping recording...');
|
|
59
|
+
|
|
60
|
+
// Stop recording (cursor tracking will stop automatically)
|
|
61
|
+
const result = await recorder.stopRecording();
|
|
62
|
+
|
|
63
|
+
console.log('✅ Recording stopped:', result);
|
|
64
|
+
console.log('📁 Checking output files...');
|
|
65
|
+
|
|
66
|
+
// Wait a moment for files to be written
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
68
|
+
|
|
69
|
+
// Check for video file
|
|
70
|
+
if (fs.existsSync(videoPath)) {
|
|
71
|
+
const videoStats = fs.statSync(videoPath);
|
|
72
|
+
console.log(`📹 Video file created: ${videoPath} (${videoStats.size} bytes)`);
|
|
73
|
+
} else {
|
|
74
|
+
console.log('❌ Video file not found');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for cursor file
|
|
78
|
+
const files = fs.readdirSync(outputDir);
|
|
79
|
+
const cursorFile = files.find(f => f.startsWith('temp_cursor_') && f.endsWith('.json'));
|
|
80
|
+
|
|
81
|
+
if (cursorFile) {
|
|
82
|
+
const cursorPath = path.join(outputDir, cursorFile);
|
|
83
|
+
const cursorStats = fs.statSync(cursorPath);
|
|
84
|
+
console.log(`🖱️ Cursor file created: ${cursorFile} (${cursorStats.size} bytes)`);
|
|
85
|
+
|
|
86
|
+
// Read and validate cursor data
|
|
87
|
+
try {
|
|
88
|
+
const cursorData = JSON.parse(fs.readFileSync(cursorPath, 'utf8'));
|
|
89
|
+
console.log(`📊 Cursor data points: ${cursorData.length}`);
|
|
90
|
+
|
|
91
|
+
if (cursorData.length > 0) {
|
|
92
|
+
const first = cursorData[0];
|
|
93
|
+
const last = cursorData[cursorData.length - 1];
|
|
94
|
+
console.log(`📍 First cursor position: (${first.x}, ${first.y}) at ${first.timestamp}ms`);
|
|
95
|
+
console.log(`📍 Last cursor position: (${last.x}, ${last.y}) at ${last.timestamp}ms`);
|
|
96
|
+
console.log(`📐 Coordinate system: ${first.coordinateSystem || 'global'}`);
|
|
97
|
+
}
|
|
98
|
+
} catch (parseError) {
|
|
99
|
+
console.log('⚠️ Could not parse cursor data:', parseError.message);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
console.log('❌ Cursor file not found');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('✅ Test completed successfully!');
|
|
106
|
+
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('❌ Test failed:', error.message);
|
|
109
|
+
console.error(error.stack);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Run the test
|
|
114
|
+
testIntegratedRecording().then(() => {
|
|
115
|
+
console.log('🏁 All tests finished');
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}).catch(error => {
|
|
118
|
+
console.error('💥 Fatal error:', error);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const MacRecorder = require('./index.js');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// Test output directory
|
|
8
|
+
const outputDir = './test-output';
|
|
9
|
+
if (!fs.existsSync(outputDir)) {
|
|
10
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const recorder = new MacRecorder();
|
|
14
|
+
|
|
15
|
+
async function testWindowRecording() {
|
|
16
|
+
console.log('🎬 Testing window recording with automatic cursor tracking...');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Test permissions first
|
|
20
|
+
const permissions = await recorder.checkPermissions();
|
|
21
|
+
console.log('📋 Permissions:', permissions);
|
|
22
|
+
|
|
23
|
+
if (!permissions.screenRecording) {
|
|
24
|
+
console.log('❌ Screen recording permission required');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Get windows
|
|
29
|
+
const windows = await recorder.getWindows();
|
|
30
|
+
console.log(`🪟 Found ${windows.length} windows`);
|
|
31
|
+
|
|
32
|
+
// Find a suitable window to record (preferably a visible one)
|
|
33
|
+
const visibleWindows = windows.filter(w => w.width > 200 && w.height > 100);
|
|
34
|
+
if (visibleWindows.length === 0) {
|
|
35
|
+
console.log('❌ No suitable windows found for recording');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const targetWindow = visibleWindows[0];
|
|
40
|
+
console.log(`🎯 Target window: ${targetWindow.appName} (${targetWindow.width}x${targetWindow.height})`);
|
|
41
|
+
console.log(`📍 Window position: (${targetWindow.x}, ${targetWindow.y})`);
|
|
42
|
+
|
|
43
|
+
// Setup recording options
|
|
44
|
+
const timestamp = Date.now();
|
|
45
|
+
const videoPath = path.join(outputDir, `window-test-${timestamp}.mov`);
|
|
46
|
+
|
|
47
|
+
console.log(`🎥 Starting window recording to: ${videoPath}`);
|
|
48
|
+
console.log('🖱️ Cursor tracking will use window-relative coordinates');
|
|
49
|
+
console.log('💡 Move your mouse over the target window to generate cursor data');
|
|
50
|
+
|
|
51
|
+
// Start recording the specific window (cursor tracking will start automatically)
|
|
52
|
+
await recorder.startRecording(videoPath, {
|
|
53
|
+
includeMicrophone: false,
|
|
54
|
+
includeSystemAudio: false,
|
|
55
|
+
captureCursor: true,
|
|
56
|
+
windowId: targetWindow.id
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log('✅ Window recording started successfully!');
|
|
60
|
+
console.log('🖱️ Cursor tracking started automatically with window-relative coordinates');
|
|
61
|
+
console.log('⏰ Recording for 8 seconds...');
|
|
62
|
+
console.log(`🔍 Please move your mouse over the "${targetWindow.appName}" window`);
|
|
63
|
+
|
|
64
|
+
// Wait for 8 seconds
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 8000));
|
|
66
|
+
|
|
67
|
+
console.log('🛑 Stopping recording...');
|
|
68
|
+
|
|
69
|
+
// Stop recording (cursor tracking will stop automatically)
|
|
70
|
+
const result = await recorder.stopRecording();
|
|
71
|
+
|
|
72
|
+
console.log('✅ Recording stopped:', result);
|
|
73
|
+
console.log('📁 Checking output files...');
|
|
74
|
+
|
|
75
|
+
// Wait a moment for files to be written
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
77
|
+
|
|
78
|
+
// Check for video file
|
|
79
|
+
if (fs.existsSync(videoPath)) {
|
|
80
|
+
const videoStats = fs.statSync(videoPath);
|
|
81
|
+
console.log(`📹 Video file created: ${path.basename(videoPath)} (${videoStats.size} bytes)`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log('❌ Video file not found');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for cursor file
|
|
87
|
+
const files = fs.readdirSync(outputDir);
|
|
88
|
+
const cursorFile = files.find(f => f.startsWith('temp_cursor_') && f.endsWith('.json'));
|
|
89
|
+
|
|
90
|
+
if (cursorFile) {
|
|
91
|
+
const cursorPath = path.join(outputDir, cursorFile);
|
|
92
|
+
const cursorStats = fs.statSync(cursorPath);
|
|
93
|
+
console.log(`🖱️ Cursor file created: ${cursorFile} (${cursorStats.size} bytes)`);
|
|
94
|
+
|
|
95
|
+
// Read and validate cursor data
|
|
96
|
+
try {
|
|
97
|
+
const cursorData = JSON.parse(fs.readFileSync(cursorPath, 'utf8'));
|
|
98
|
+
console.log(`📊 Cursor data points: ${cursorData.length}`);
|
|
99
|
+
|
|
100
|
+
if (cursorData.length > 0) {
|
|
101
|
+
const first = cursorData[0];
|
|
102
|
+
const last = cursorData[cursorData.length - 1];
|
|
103
|
+
|
|
104
|
+
console.log(`📍 First cursor position: (${first.x}, ${first.y}) at ${first.timestamp}ms`);
|
|
105
|
+
console.log(`📍 Last cursor position: (${last.x}, ${last.y}) at ${last.timestamp}ms`);
|
|
106
|
+
console.log(`📐 Coordinate system: ${first.coordinateSystem}`);
|
|
107
|
+
|
|
108
|
+
if (first.windowInfo) {
|
|
109
|
+
console.log(`🪟 Window info: ${first.windowInfo.width}x${first.windowInfo.height}`);
|
|
110
|
+
console.log(`🌐 Original window position: (${first.windowInfo.originalWindow?.x}, ${first.windowInfo.originalWindow?.y})`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Analyze cursor positions
|
|
114
|
+
const windowRelativePositions = cursorData.filter(d => d.coordinateSystem === 'window-relative');
|
|
115
|
+
if (windowRelativePositions.length > 0) {
|
|
116
|
+
console.log(`✅ ${windowRelativePositions.length} window-relative cursor positions recorded`);
|
|
117
|
+
|
|
118
|
+
// Check if coordinates are within window bounds
|
|
119
|
+
const validPositions = windowRelativePositions.filter(d =>
|
|
120
|
+
d.x >= 0 && d.y >= 0 &&
|
|
121
|
+
d.x <= targetWindow.width && d.y <= targetWindow.height
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
console.log(`✅ ${validPositions.length}/${windowRelativePositions.length} positions within window bounds`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch (parseError) {
|
|
128
|
+
console.log('⚠️ Could not parse cursor data:', parseError.message);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
console.log('❌ Cursor file not found');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log('✅ Window recording test completed!');
|
|
135
|
+
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('❌ Test failed:', error.message);
|
|
138
|
+
console.error(error.stack);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Run the test
|
|
143
|
+
testWindowRecording().then(() => {
|
|
144
|
+
console.log('🏁 Window recording test finished');
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}).catch(error => {
|
|
147
|
+
console.error('💥 Fatal error:', error);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|