node-mac-recorder 2.17.19 → 2.17.20
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/index.js +4 -2
- package/package.json +1 -1
- package/test-output/temp_cursor_1758313956591.json +1 -0
- package/test-recording-with-cursor.js +83 -0
- package/cursor-data-1751364226346.json +0 -1
- package/cursor-data-1751364314136.json +0 -1
- package/cursor-data.json +0 -1
- package/debug-cursor-output.json +0 -1
- package/examples/electron-integration-example.js +0 -230
- package/examples/electron-preload.js +0 -46
- package/examples/electron-renderer.html +0 -634
- package/examples/integration-example.js +0 -228
- package/examples/window-selector-example.js +0 -254
- package/quick-cursor-test.json +0 -1
- package/test-both-cursor.json +0 -1
- package/test-output/primary-fix-test-1758266910543.json +0 -1
- package/test-output/unified-cursor-1758313640878.json +0 -1
- package/test-output/unified-cursor-1758313689471.json +0 -1
package/index.js
CHANGED
|
@@ -456,9 +456,11 @@ class MacRecorder extends EventEmitter {
|
|
|
456
456
|
|
|
457
457
|
// Stop cursor tracking automatically
|
|
458
458
|
if (this.cursorCaptureInterval) {
|
|
459
|
-
|
|
459
|
+
try {
|
|
460
|
+
this.stopCursorCapture();
|
|
461
|
+
} catch (cursorError) {
|
|
460
462
|
console.warn('Cursor tracking failed to stop:', cursorError.message);
|
|
461
|
-
}
|
|
463
|
+
}
|
|
462
464
|
}
|
|
463
465
|
|
|
464
466
|
// Timer durdur
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"x":609,"y":795,"timestamp":184,"unixTimeMs":1758313956779,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":609,"y":795,"timestamp":205,"unixTimeMs":1758313956800,"cursorType":"default","type":"mousedown","coordinateSystem":"display-relative"},{"x":609,"y":795,"timestamp":224,"unixTimeMs":1758313956819,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":609,"y":795,"timestamp":286,"unixTimeMs":1758313956881,"cursorType":"default","type":"mouseup","coordinateSystem":"display-relative"},{"x":609,"y":795,"timestamp":306,"unixTimeMs":1758313956901,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":609,"y":802,"timestamp":824,"unixTimeMs":1758313957419,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":608,"y":812,"timestamp":842,"unixTimeMs":1758313957437,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":605,"y":828,"timestamp":863,"unixTimeMs":1758313957458,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":601,"y":851,"timestamp":883,"unixTimeMs":1758313957478,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":596,"y":877,"timestamp":905,"unixTimeMs":1758313957500,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":594,"y":887,"timestamp":923,"unixTimeMs":1758313957518,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":591,"y":900,"timestamp":942,"unixTimeMs":1758313957537,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":590,"y":905,"timestamp":965,"unixTimeMs":1758313957560,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":589,"y":908,"timestamp":984,"unixTimeMs":1758313957579,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":588,"y":910,"timestamp":1025,"unixTimeMs":1758313957620,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":588,"y":912,"timestamp":1065,"unixTimeMs":1758313957660,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":588,"y":914,"timestamp":1124,"unixTimeMs":1758313957719,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":587,"y":917,"timestamp":1145,"unixTimeMs":1758313957740,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":586,"y":920,"timestamp":1167,"unixTimeMs":1758313957762,"cursorType":"default","type":"move","coordinateSystem":"display-relative"},{"x":586,"y":922,"timestamp":1186,"unixTimeMs":1758313957781,"cursorType":"default","type":"move","coordinateSystem":"display-relative"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const MacRecorder = require('./index.js');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
async function testRecordingWithCursor() {
|
|
8
|
+
const recorder = new MacRecorder();
|
|
9
|
+
|
|
10
|
+
console.log('🎯 Testing recording with cursor tracking...');
|
|
11
|
+
|
|
12
|
+
// Create test output directory
|
|
13
|
+
const outputDir = './test-output';
|
|
14
|
+
if (!fs.existsSync(outputDir)) {
|
|
15
|
+
fs.mkdirSync(outputDir);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const videoPath = path.join(outputDir, `test-recording-${Date.now()}.mov`);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
console.log('Starting recording with auto cursor tracking...');
|
|
22
|
+
|
|
23
|
+
// Start recording (should auto-start cursor tracking)
|
|
24
|
+
await recorder.startRecording(videoPath, {
|
|
25
|
+
captureCursor: true, // This should trigger auto cursor tracking
|
|
26
|
+
quality: 'low',
|
|
27
|
+
frameRate: 10
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('✅ Recording started successfully');
|
|
31
|
+
console.log('🔥 Recording for 3 seconds...');
|
|
32
|
+
|
|
33
|
+
// Wait 3 seconds
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
35
|
+
|
|
36
|
+
console.log('Stopping recording...');
|
|
37
|
+
const stopResult = await recorder.stopRecording();
|
|
38
|
+
console.log('✅ Recording stopped:', stopResult);
|
|
39
|
+
|
|
40
|
+
// Wait a bit for file to be written
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
42
|
+
|
|
43
|
+
// Check if files exist
|
|
44
|
+
if (fs.existsSync(videoPath)) {
|
|
45
|
+
const videoStats = fs.statSync(videoPath);
|
|
46
|
+
console.log('📹 Video file size:', videoStats.size, 'bytes');
|
|
47
|
+
} else {
|
|
48
|
+
console.log('❌ Video file not found:', videoPath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for cursor files
|
|
52
|
+
const cursorFiles = fs.readdirSync(outputDir).filter(f => f.includes('cursor') && f.endsWith('.json'));
|
|
53
|
+
console.log('📊 Cursor files found:', cursorFiles.length);
|
|
54
|
+
|
|
55
|
+
if (cursorFiles.length > 0) {
|
|
56
|
+
const cursorFile = path.join(outputDir, cursorFiles[0]);
|
|
57
|
+
const cursorContent = fs.readFileSync(cursorFile, 'utf8');
|
|
58
|
+
console.log('📁 Cursor file size:', cursorContent.length, 'bytes');
|
|
59
|
+
|
|
60
|
+
if (cursorContent.trim()) {
|
|
61
|
+
try {
|
|
62
|
+
const cursorData = JSON.parse(cursorContent);
|
|
63
|
+
console.log('📍 Cursor data points:', cursorData.length);
|
|
64
|
+
} catch (parseError) {
|
|
65
|
+
console.log('⚠️ Cursor file content preview:', cursorContent.substring(0, 200));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('❌ Test failed:', error.message);
|
|
72
|
+
console.error('Stack:', error.stack);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Run test
|
|
77
|
+
testRecordingWithCursor().then(() => {
|
|
78
|
+
console.log('\n✨ Test completed');
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}).catch(error => {
|
|
81
|
+
console.error('💥 Test error:', error);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[]
|
package/cursor-data.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[{"x":1151,"y":726,"timestamp":20,"cursorType":"text","type":"move"}
|
package/debug-cursor-output.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[{"x":48,"y":72,"timestamp":22,"unixTimeMs":1752410259890,"cursorType":"pointer","type":"move"},{"x":47,"y":71,"timestamp":87,"unixTimeMs":1752410259955,"cursorType":"pointer","type":"mousedown"},{"x":47,"y":71,"timestamp":107,"unixTimeMs":1752410259975,"cursorType":"pointer","type":"move"},{"x":47,"y":71,"timestamp":169,"unixTimeMs":1752410260037,"cursorType":"pointer","type":"mouseup"},{"x":47,"y":71,"timestamp":781,"unixTimeMs":1752410260649,"cursorType":"default","type":"move"}]
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// Electron Integration Example for node-mac-recorder
|
|
2
|
-
// This example shows how to use the Electron-safe version in an Electron app
|
|
3
|
-
|
|
4
|
-
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
|
|
7
|
-
// Import the Electron-safe version
|
|
8
|
-
const ElectronSafeMacRecorder = require("../electron-safe-index");
|
|
9
|
-
|
|
10
|
-
let mainWindow;
|
|
11
|
-
let recorder;
|
|
12
|
-
|
|
13
|
-
function createWindow() {
|
|
14
|
-
// Create the browser window
|
|
15
|
-
mainWindow = new BrowserWindow({
|
|
16
|
-
width: 1200,
|
|
17
|
-
height: 800,
|
|
18
|
-
webPreferences: {
|
|
19
|
-
nodeIntegration: false,
|
|
20
|
-
contextIsolation: true,
|
|
21
|
-
preload: path.join(__dirname, "electron-preload.js"),
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Load the app
|
|
26
|
-
mainWindow.loadFile("electron-renderer.html");
|
|
27
|
-
|
|
28
|
-
// Initialize the Electron-safe recorder
|
|
29
|
-
try {
|
|
30
|
-
recorder = new ElectronSafeMacRecorder();
|
|
31
|
-
console.log("✅ ElectronSafeMacRecorder initialized");
|
|
32
|
-
|
|
33
|
-
// Setup event listeners
|
|
34
|
-
recorder.on("recordingStarted", (data) => {
|
|
35
|
-
console.log("🎬 Recording started:", data);
|
|
36
|
-
mainWindow.webContents.send("recording-started", data);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
recorder.on("stopped", (result) => {
|
|
40
|
-
console.log("🛑 Recording stopped:", result);
|
|
41
|
-
mainWindow.webContents.send("recording-stopped", result);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
recorder.on("completed", (outputPath) => {
|
|
45
|
-
console.log("✅ Recording completed:", outputPath);
|
|
46
|
-
mainWindow.webContents.send("recording-completed", outputPath);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
recorder.on("timeUpdate", (elapsed) => {
|
|
50
|
-
mainWindow.webContents.send("recording-time-update", elapsed);
|
|
51
|
-
});
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error("❌ Failed to initialize recorder:", error);
|
|
54
|
-
dialog.showErrorBox(
|
|
55
|
-
"Recorder Error",
|
|
56
|
-
"Failed to initialize screen recorder. Please ensure the Electron-safe module is built."
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// IPC handlers for safe communication with renderer
|
|
62
|
-
ipcMain.handle("recorder:getModuleInfo", async () => {
|
|
63
|
-
try {
|
|
64
|
-
return recorder ? recorder.getModuleInfo() : null;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error("Error getting module info:", error);
|
|
67
|
-
return { error: error.message };
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
ipcMain.handle("recorder:checkPermissions", async () => {
|
|
72
|
-
try {
|
|
73
|
-
return await recorder.checkPermissions();
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error("Error checking permissions:", error);
|
|
76
|
-
return { error: error.message };
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
ipcMain.handle("recorder:getDisplays", async () => {
|
|
81
|
-
try {
|
|
82
|
-
return await recorder.getDisplays();
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error("Error getting displays:", error);
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
ipcMain.handle("recorder:getWindows", async () => {
|
|
90
|
-
try {
|
|
91
|
-
return await recorder.getWindows();
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.error("Error getting windows:", error);
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
ipcMain.handle(
|
|
99
|
-
"recorder:startRecording",
|
|
100
|
-
async (event, outputPath, options) => {
|
|
101
|
-
try {
|
|
102
|
-
if (!recorder) {
|
|
103
|
-
throw new Error("Recorder not initialized");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
console.log("🎬 Starting recording with options:", options);
|
|
107
|
-
const result = await recorder.startRecording(outputPath, options);
|
|
108
|
-
return { success: true, result };
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error("Error starting recording:", error);
|
|
111
|
-
return { success: false, error: error.message };
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
ipcMain.handle("recorder:stopRecording", async () => {
|
|
117
|
-
try {
|
|
118
|
-
if (!recorder) {
|
|
119
|
-
throw new Error("Recorder not initialized");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
console.log("🛑 Stopping recording");
|
|
123
|
-
const result = await recorder.stopRecording();
|
|
124
|
-
return { success: true, result };
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error("Error stopping recording:", error);
|
|
127
|
-
return { success: false, error: error.message };
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
ipcMain.handle("recorder:getStatus", async () => {
|
|
132
|
-
try {
|
|
133
|
-
return recorder ? recorder.getStatus() : null;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error("Error getting status:", error);
|
|
136
|
-
return { error: error.message };
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
ipcMain.handle("recorder:getCursorPosition", async () => {
|
|
141
|
-
try {
|
|
142
|
-
return recorder ? recorder.getCursorPosition() : null;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.error("Error getting cursor position:", error);
|
|
145
|
-
return { error: error.message };
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
ipcMain.handle(
|
|
150
|
-
"recorder:getDisplayThumbnail",
|
|
151
|
-
async (event, displayId, options) => {
|
|
152
|
-
try {
|
|
153
|
-
return await recorder.getDisplayThumbnail(displayId, options);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error("Error getting display thumbnail:", error);
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
ipcMain.handle(
|
|
162
|
-
"recorder:getWindowThumbnail",
|
|
163
|
-
async (event, windowId, options) => {
|
|
164
|
-
try {
|
|
165
|
-
return await recorder.getWindowThumbnail(windowId, options);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.error("Error getting window thumbnail:", error);
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
ipcMain.handle("dialog:showSaveDialog", async () => {
|
|
174
|
-
const result = await dialog.showSaveDialog(mainWindow, {
|
|
175
|
-
title: "Save Recording",
|
|
176
|
-
defaultPath: "recording.mov",
|
|
177
|
-
filters: [{ name: "Movies", extensions: ["mov", "mp4"] }],
|
|
178
|
-
});
|
|
179
|
-
return result;
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// App event listeners
|
|
183
|
-
app.whenReady().then(createWindow);
|
|
184
|
-
|
|
185
|
-
app.on("window-all-closed", () => {
|
|
186
|
-
// Stop any ongoing recording before quitting
|
|
187
|
-
if (recorder && recorder.getStatus().isRecording) {
|
|
188
|
-
console.log("🛑 Stopping recording before quit");
|
|
189
|
-
recorder.stopRecording().finally(() => {
|
|
190
|
-
if (process.platform !== "darwin") {
|
|
191
|
-
app.quit();
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
} else {
|
|
195
|
-
if (process.platform !== "darwin") {
|
|
196
|
-
app.quit();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
app.on("activate", () => {
|
|
202
|
-
if (BrowserWindow.getAllWindows().length === 0) {
|
|
203
|
-
createWindow();
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Handle app termination gracefully
|
|
208
|
-
process.on("SIGINT", async () => {
|
|
209
|
-
console.log("🛑 SIGINT received, stopping recording...");
|
|
210
|
-
if (recorder && recorder.getStatus().isRecording) {
|
|
211
|
-
try {
|
|
212
|
-
await recorder.stopRecording();
|
|
213
|
-
} catch (error) {
|
|
214
|
-
console.error("Error stopping recording on exit:", error);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
app.quit();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
process.on("SIGTERM", async () => {
|
|
221
|
-
console.log("🛑 SIGTERM received, stopping recording...");
|
|
222
|
-
if (recorder && recorder.getStatus().isRecording) {
|
|
223
|
-
try {
|
|
224
|
-
await recorder.stopRecording();
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error("Error stopping recording on exit:", error);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
app.quit();
|
|
230
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// Preload script for secure IPC communication
|
|
2
|
-
const { contextBridge, ipcRenderer } = require("electron");
|
|
3
|
-
|
|
4
|
-
// Expose protected methods that allow the renderer process to use
|
|
5
|
-
// the ipcRenderer without exposing the entire object
|
|
6
|
-
contextBridge.exposeInMainWorld("electronAPI", {
|
|
7
|
-
// Recorder API
|
|
8
|
-
recorder: {
|
|
9
|
-
getModuleInfo: () => ipcRenderer.invoke("recorder:getModuleInfo"),
|
|
10
|
-
checkPermissions: () => ipcRenderer.invoke("recorder:checkPermissions"),
|
|
11
|
-
getDisplays: () => ipcRenderer.invoke("recorder:getDisplays"),
|
|
12
|
-
getWindows: () => ipcRenderer.invoke("recorder:getWindows"),
|
|
13
|
-
startRecording: (outputPath, options) =>
|
|
14
|
-
ipcRenderer.invoke("recorder:startRecording", outputPath, options),
|
|
15
|
-
stopRecording: () => ipcRenderer.invoke("recorder:stopRecording"),
|
|
16
|
-
getStatus: () => ipcRenderer.invoke("recorder:getStatus"),
|
|
17
|
-
getCursorPosition: () => ipcRenderer.invoke("recorder:getCursorPosition"),
|
|
18
|
-
getDisplayThumbnail: (displayId, options) =>
|
|
19
|
-
ipcRenderer.invoke("recorder:getDisplayThumbnail", displayId, options),
|
|
20
|
-
getWindowThumbnail: (windowId, options) =>
|
|
21
|
-
ipcRenderer.invoke("recorder:getWindowThumbnail", windowId, options),
|
|
22
|
-
|
|
23
|
-
// Event listeners
|
|
24
|
-
onRecordingStarted: (callback) =>
|
|
25
|
-
ipcRenderer.on("recording-started", callback),
|
|
26
|
-
onRecordingStopped: (callback) =>
|
|
27
|
-
ipcRenderer.on("recording-stopped", callback),
|
|
28
|
-
onRecordingCompleted: (callback) =>
|
|
29
|
-
ipcRenderer.on("recording-completed", callback),
|
|
30
|
-
onTimeUpdate: (callback) =>
|
|
31
|
-
ipcRenderer.on("recording-time-update", callback),
|
|
32
|
-
|
|
33
|
-
// Remove listeners
|
|
34
|
-
removeAllListeners: () => {
|
|
35
|
-
ipcRenderer.removeAllListeners("recording-started");
|
|
36
|
-
ipcRenderer.removeAllListeners("recording-stopped");
|
|
37
|
-
ipcRenderer.removeAllListeners("recording-completed");
|
|
38
|
-
ipcRenderer.removeAllListeners("recording-time-update");
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
// Dialog API
|
|
43
|
-
dialog: {
|
|
44
|
-
showSaveDialog: () => ipcRenderer.invoke("dialog:showSaveDialog"),
|
|
45
|
-
},
|
|
46
|
-
});
|