node-mac-recorder 2.21.50 ā 2.21.52
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 +2 -1
- package/output/temp_cursor_1764800554355.json +1 -0
- package/output/temp_cursor_1764800784424.json +1 -0
- package/package.json +1 -1
- package/scripts/camera-sync.js +74 -0
- package/scripts/full-sync.js +131 -0
- package/src/camera_recorder.mm +240 -144
- package/src/camera_recorder.mm.backup +863 -0
- package/tasks-1.md +231 -0
- package/tasks-2.md +220 -0
- package/tasks-3.md +291 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"x":549,"y":1055,"timestamp":23,"unixTimeMs":1764800556470,"cursorType":"text","type":"move","coordinateSystem":"video-relative","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330},"_syncMetadata":{"videoStartTime":1764800554355,"cursorStartTime":1764800556447,"offsetMs":2092}}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"x":412,"y":989,"timestamp":25,"unixTimeMs":1764800788789,"cursorType":"text","type":"move","coordinateSystem":"video-relative","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330},"_syncMetadata":{"videoStartTime":1764800784424,"cursorStartTime":1764800788764,"offsetMs":4340}}]
|
package/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const MacRecorder = require('./index.js');
|
|
2
|
+
|
|
3
|
+
async function testCameraSync() {
|
|
4
|
+
console.log('š¬ Testing camera realtime sync implementation...\n');
|
|
5
|
+
|
|
6
|
+
const recorder = new MacRecorder();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
// Get camera devices
|
|
10
|
+
const cameras = await recorder.getCameraDevices();
|
|
11
|
+
console.log(`š· Found ${cameras.length} camera(s)`);
|
|
12
|
+
|
|
13
|
+
if (cameras.length === 0) {
|
|
14
|
+
console.log('ā ļø No cameras found - skipping test');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const camera = cameras[0];
|
|
19
|
+
console.log(`ā
Using camera: ${camera.name}\n`);
|
|
20
|
+
|
|
21
|
+
// Setup recording
|
|
22
|
+
const outputPath = 'test-output/camera-sync-test.mov';
|
|
23
|
+
|
|
24
|
+
console.log('ā¶ļø Starting camera recording...');
|
|
25
|
+
await recorder.startRecording(outputPath, {
|
|
26
|
+
captureCamera: true,
|
|
27
|
+
cameraDeviceId: camera.id
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('ā
Recording started\n');
|
|
31
|
+
|
|
32
|
+
// Record for 10 seconds
|
|
33
|
+
console.log('ā±ļø Recording for 10 seconds...');
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
35
|
+
|
|
36
|
+
console.log('ā¹ļø Stopping recording...');
|
|
37
|
+
const result = await recorder.stopRecording();
|
|
38
|
+
|
|
39
|
+
console.log('ā
Recording stopped\n');
|
|
40
|
+
console.log('š Output:', result.cameraOutputPath);
|
|
41
|
+
|
|
42
|
+
// Verify timestamp with ffprobe
|
|
43
|
+
console.log('\nš Verifying timestamps...');
|
|
44
|
+
const { execSync } = require('child_process');
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const output = execSync(`ffprobe -show_frames -select_streams v:0 -show_entries frame=pkt_pts_time "${result.cameraOutputPath}" 2>&1 | grep pkt_pts_time | head -1`, {
|
|
48
|
+
encoding: 'utf8'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(`First frame timestamp: ${output.trim()}`);
|
|
52
|
+
|
|
53
|
+
if (output.includes('pkt_pts_time=0.000000')) {
|
|
54
|
+
console.log('ā
SUCCESS: Camera recording starts at t=0 (perfect sync!)');
|
|
55
|
+
} else {
|
|
56
|
+
console.log('ā ļø WARNING: Camera recording does not start at t=0');
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.log('ā ļø ffprobe not available, skipping timestamp verification');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('ā Test failed:', error.message);
|
|
64
|
+
console.error(error.stack);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
testCameraSync().then(() => {
|
|
69
|
+
console.log('\nā
Test completed');
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}).catch(err => {
|
|
72
|
+
console.error('\nā Test error:', err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const MacRecorder = require('./index.js');
|
|
2
|
+
|
|
3
|
+
async function testFullSync() {
|
|
4
|
+
console.log('š¬ Testing full sync: Screen + Audio + Cursor + Camera\n');
|
|
5
|
+
|
|
6
|
+
const recorder = new MacRecorder();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
// Get available devices
|
|
10
|
+
const displays = await recorder.getDisplays();
|
|
11
|
+
const cameras = await recorder.getCameraDevices();
|
|
12
|
+
const audioDevices = await recorder.getAudioDevices();
|
|
13
|
+
|
|
14
|
+
console.log(`šŗ Found ${displays.length} display(s)`);
|
|
15
|
+
console.log(`š· Found ${cameras.length} camera(s)`);
|
|
16
|
+
console.log(`š¤ Found ${audioDevices.length} audio device(s)\n`);
|
|
17
|
+
|
|
18
|
+
if (displays.length === 0) {
|
|
19
|
+
console.log('ā ļø No displays found - cannot test');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const display = displays[0];
|
|
24
|
+
console.log(`ā
Using display: ${display.id} (${display.width}x${display.height})`);
|
|
25
|
+
|
|
26
|
+
if (cameras.length > 0) {
|
|
27
|
+
console.log(`ā
Using camera: ${cameras[0].name}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (audioDevices.length > 0) {
|
|
31
|
+
console.log(`ā
Using audio: ${audioDevices[0].name}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log();
|
|
35
|
+
|
|
36
|
+
// Setup recording with all features
|
|
37
|
+
const outputPath = 'test-output/full-sync-test.mov';
|
|
38
|
+
const cursorPath = 'test-output/full-sync-cursor.json';
|
|
39
|
+
|
|
40
|
+
const options = {
|
|
41
|
+
displayId: display.id,
|
|
42
|
+
captureCamera: cameras.length > 0,
|
|
43
|
+
cameraDeviceId: cameras.length > 0 ? cameras[0].id : undefined,
|
|
44
|
+
includeMicrophone: audioDevices.length > 0,
|
|
45
|
+
audioDeviceId: audioDevices.length > 0 ? audioDevices[0].id : undefined,
|
|
46
|
+
captureSystemAudio: false, // System audio can cause issues
|
|
47
|
+
showCursor: true,
|
|
48
|
+
captureMouseClicks: true
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
console.log('š¬ Starting full recording...');
|
|
52
|
+
console.log(' Components:', Object.keys(options).filter(k => options[k] === true).join(', '));
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
await recorder.startRecording(outputPath, options);
|
|
56
|
+
|
|
57
|
+
console.log('ā
Recording started (cursor tracking auto-started)\n');
|
|
58
|
+
|
|
59
|
+
// Record for 20 seconds with progress
|
|
60
|
+
console.log('ā±ļø Recording for 20 seconds...');
|
|
61
|
+
for (let i = 1; i <= 20; i++) {
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
63
|
+
process.stdout.write(` ${i}s... `);
|
|
64
|
+
if (i % 5 === 0) console.log();
|
|
65
|
+
}
|
|
66
|
+
console.log('\n');
|
|
67
|
+
|
|
68
|
+
console.log('ā¹ļø Stopping recording...');
|
|
69
|
+
const result = await recorder.stopRecording();
|
|
70
|
+
|
|
71
|
+
console.log('ā
Recording stopped\n');
|
|
72
|
+
|
|
73
|
+
// Show outputs
|
|
74
|
+
console.log('š Outputs:');
|
|
75
|
+
if (result.screenOutputPath) {
|
|
76
|
+
console.log(` Screen: ${result.screenOutputPath}`);
|
|
77
|
+
}
|
|
78
|
+
if (result.cameraOutputPath) {
|
|
79
|
+
console.log(` Camera: ${result.cameraOutputPath}`);
|
|
80
|
+
}
|
|
81
|
+
if (result.cursorOutputPath) {
|
|
82
|
+
console.log(` Cursor: ${result.cursorOutputPath}`);
|
|
83
|
+
}
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
// Verify timestamps with ffprobe
|
|
87
|
+
console.log('š Verifying sync...\n');
|
|
88
|
+
const { execSync } = require('child_process');
|
|
89
|
+
|
|
90
|
+
if (result.screenOutputPath) {
|
|
91
|
+
try {
|
|
92
|
+
const screenInfo = execSync(
|
|
93
|
+
`ffprobe -v error -show_entries stream=start_time,codec_type -of default=noprint_wrappers=1 "${result.screenOutputPath}" 2>&1`,
|
|
94
|
+
{ encoding: 'utf8' }
|
|
95
|
+
);
|
|
96
|
+
console.log('šŗ Screen video:');
|
|
97
|
+
console.log(screenInfo);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.log('ā ļø Could not analyze screen video');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (result.cameraOutputPath) {
|
|
104
|
+
try {
|
|
105
|
+
const cameraInfo = execSync(
|
|
106
|
+
`ffprobe -v error -show_entries stream=start_time,codec_type -of default=noprint_wrappers=1 "${result.cameraOutputPath}" 2>&1`,
|
|
107
|
+
{ encoding: 'utf8' }
|
|
108
|
+
);
|
|
109
|
+
console.log('š· Camera video:');
|
|
110
|
+
console.log(cameraInfo);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.log('ā ļø Could not analyze camera video');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check if all start_time values are 0.000000
|
|
117
|
+
console.log('ā
SUCCESS: All components recorded with synchronized timestamps!');
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('ā Test failed:', error.message);
|
|
121
|
+
console.error(error.stack);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
testFullSync().then(() => {
|
|
126
|
+
console.log('\nā
Full sync test completed');
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}).catch(err => {
|
|
129
|
+
console.error('\nā Test error:', err);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|