node-mac-recorder 2.22.24 → 2.22.32
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 +0 -1
- package/electron-safe-binding.gyp +0 -1
- package/electron-safe-index.js +104 -196
- package/index.js +355 -8
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +1 -2
- package/src/cursor_tracker.mm +4 -90
- package/src/electron_safe/cursor_tracker_electron.mm +0 -34
- package/src/electron_safe/window_selector_electron.mm +5 -2
- package/src/screen_capture_kit.mm +11 -17
- package/src/window_selector.mm +5 -2
- package/lib/cursorCapture/displayInfo.js +0 -110
- package/lib/cursorCapture/polling.js +0 -585
- package/src/text_input_ax_snapshot.h +0 -3
- package/src/text_input_ax_snapshot.mm +0 -161
package/binding.gyp
CHANGED
package/electron-safe-index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { EventEmitter } = require("events");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
|
-
const cursorCapturePolling = require("./lib/cursorCapture/polling");
|
|
5
4
|
|
|
6
5
|
// Electron-safe native module loading
|
|
7
6
|
let electronSafeNativeBinding;
|
|
@@ -42,17 +41,6 @@ class ElectronSafeMacRecorder extends EventEmitter {
|
|
|
42
41
|
this.recordingTimer = null;
|
|
43
42
|
this.recordingStartTime = null;
|
|
44
43
|
|
|
45
|
-
this.cursorCaptureInterval = null;
|
|
46
|
-
this.cursorCaptureFile = null;
|
|
47
|
-
this.cursorCaptureStartTime = null;
|
|
48
|
-
this.cursorCaptureFirstWrite = true;
|
|
49
|
-
this.lastCapturedData = null;
|
|
50
|
-
this.cursorDisplayInfo = null;
|
|
51
|
-
this.recordingDisplayInfo = null;
|
|
52
|
-
this.cursorCaptureSessionTimestamp = null;
|
|
53
|
-
this.sessionTimestamp = null;
|
|
54
|
-
this.syncTimestamp = null;
|
|
55
|
-
|
|
56
44
|
this.options = {
|
|
57
45
|
includeMicrophone: false,
|
|
58
46
|
includeSystemAudio: false,
|
|
@@ -97,8 +85,10 @@ class ElectronSafeMacRecorder extends EventEmitter {
|
|
|
97
85
|
throw new Error("Output path is required");
|
|
98
86
|
}
|
|
99
87
|
|
|
88
|
+
// Update options
|
|
100
89
|
this.setOptions(options);
|
|
101
90
|
|
|
91
|
+
// Ensure output directory exists
|
|
102
92
|
const outputDir = path.dirname(outputPath);
|
|
103
93
|
if (!fs.existsSync(outputDir)) {
|
|
104
94
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -106,123 +96,63 @@ class ElectronSafeMacRecorder extends EventEmitter {
|
|
|
106
96
|
|
|
107
97
|
this.outputPath = outputPath;
|
|
108
98
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
recordingDisplayInfo = {
|
|
165
|
-
displayId: target.id,
|
|
166
|
-
x: target.x || 0,
|
|
167
|
-
y: target.y || 0,
|
|
168
|
-
width: target.width,
|
|
169
|
-
height: target.height,
|
|
170
|
-
logicalWidth: target.width,
|
|
171
|
-
logicalHeight: target.height,
|
|
172
|
-
};
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
try {
|
|
101
|
+
console.log("🎬 Starting Electron-safe recording...");
|
|
102
|
+
console.log("📁 Output path:", outputPath);
|
|
103
|
+
console.log("⚙️ Options:", this.options);
|
|
104
|
+
|
|
105
|
+
// Call native function with timeout protection
|
|
106
|
+
const startTimeout = setTimeout(() => {
|
|
107
|
+
this.isRecording = false;
|
|
108
|
+
reject(new Error("Recording start timeout - Electron protection"));
|
|
109
|
+
}, 10000); // 10 second timeout
|
|
110
|
+
|
|
111
|
+
const success = electronSafeNativeBinding.startRecording(
|
|
112
|
+
outputPath,
|
|
113
|
+
this.options
|
|
114
|
+
);
|
|
115
|
+
clearTimeout(startTimeout);
|
|
116
|
+
|
|
117
|
+
if (success) {
|
|
118
|
+
this.isRecording = true;
|
|
119
|
+
this.recordingStartTime = Date.now();
|
|
120
|
+
|
|
121
|
+
// Start progress timer
|
|
122
|
+
this.recordingTimer = setInterval(() => {
|
|
123
|
+
const elapsed = Math.floor(
|
|
124
|
+
(Date.now() - this.recordingStartTime) / 1000
|
|
125
|
+
);
|
|
126
|
+
this.emit("timeUpdate", elapsed);
|
|
127
|
+
}, 1000);
|
|
128
|
+
|
|
129
|
+
// Emit started event
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.emit("recordingStarted", {
|
|
132
|
+
outputPath: this.outputPath,
|
|
133
|
+
timestamp: this.recordingStartTime,
|
|
134
|
+
options: this.options,
|
|
135
|
+
electronSafe: true,
|
|
136
|
+
});
|
|
137
|
+
}, 100);
|
|
138
|
+
|
|
139
|
+
this.emit("started", this.outputPath);
|
|
140
|
+
console.log("✅ Electron-safe recording started successfully");
|
|
141
|
+
resolve(this.outputPath);
|
|
142
|
+
} else {
|
|
143
|
+
console.error("❌ Failed to start Electron-safe recording");
|
|
144
|
+
reject(new Error("Failed to start recording - check permissions"));
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("❌ Exception during recording start:", error);
|
|
148
|
+
this.isRecording = false;
|
|
149
|
+
if (this.recordingTimer) {
|
|
150
|
+
clearInterval(this.recordingTimer);
|
|
151
|
+
this.recordingTimer = null;
|
|
152
|
+
}
|
|
153
|
+
reject(error);
|
|
173
154
|
}
|
|
174
|
-
}
|
|
175
|
-
recordingDisplayInfo = null;
|
|
176
|
-
}
|
|
177
|
-
this.recordingDisplayInfo = recordingDisplayInfo;
|
|
178
|
-
|
|
179
|
-
const syncTimestamp = Date.now();
|
|
180
|
-
this.syncTimestamp = syncTimestamp;
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
await cursorCapturePolling.startCursorCapture(
|
|
184
|
-
this,
|
|
185
|
-
electronSafeNativeBinding,
|
|
186
|
-
cursorFilePath,
|
|
187
|
-
{
|
|
188
|
-
videoRelative: !!recordingDisplayInfo,
|
|
189
|
-
displayInfo: recordingDisplayInfo,
|
|
190
|
-
recordingType: this.options.windowId
|
|
191
|
-
? "window"
|
|
192
|
-
: this.options.captureArea
|
|
193
|
-
? "area"
|
|
194
|
-
: "display",
|
|
195
|
-
captureArea: this.options.captureArea || null,
|
|
196
|
-
windowId: this.options.windowId || null,
|
|
197
|
-
startTimestamp: syncTimestamp,
|
|
198
|
-
},
|
|
199
|
-
);
|
|
200
|
-
} catch (cursorError) {
|
|
201
|
-
console.warn(
|
|
202
|
-
"⚠️ Cursor tracking failed to start:",
|
|
203
|
-
cursorError.message,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const startPayloadTs = this.syncTimestamp || this.recordingStartTime;
|
|
208
|
-
const fileTimestampPayload = this.sessionTimestamp;
|
|
209
|
-
|
|
210
|
-
setTimeout(() => {
|
|
211
|
-
this.emit("recordingStarted", {
|
|
212
|
-
outputPath: this.outputPath,
|
|
213
|
-
timestamp: startPayloadTs,
|
|
214
|
-
options: this.options,
|
|
215
|
-
electronSafe: true,
|
|
216
|
-
cursorOutputPath: cursorFilePath,
|
|
217
|
-
sessionTimestamp: fileTimestampPayload,
|
|
218
|
-
syncTimestamp: startPayloadTs,
|
|
219
|
-
fileTimestamp: fileTimestampPayload,
|
|
220
|
-
});
|
|
221
|
-
}, 100);
|
|
222
|
-
|
|
223
|
-
this.emit("started", this.outputPath);
|
|
224
|
-
console.log("✅ Electron-safe recording started successfully");
|
|
225
|
-
return this.outputPath;
|
|
155
|
+
});
|
|
226
156
|
}
|
|
227
157
|
|
|
228
158
|
/**
|
|
@@ -233,66 +163,64 @@ class ElectronSafeMacRecorder extends EventEmitter {
|
|
|
233
163
|
throw new Error("No recording in progress");
|
|
234
164
|
}
|
|
235
165
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
try {
|
|
168
|
+
console.log("🛑 Stopping Electron-safe recording...");
|
|
169
|
+
|
|
170
|
+
// Call native function with timeout protection
|
|
171
|
+
const stopTimeout = setTimeout(() => {
|
|
172
|
+
this.isRecording = false;
|
|
173
|
+
if (this.recordingTimer) {
|
|
174
|
+
clearInterval(this.recordingTimer);
|
|
175
|
+
this.recordingTimer = null;
|
|
176
|
+
}
|
|
177
|
+
reject(new Error("Recording stop timeout - forced cleanup"));
|
|
178
|
+
}, 10000); // 10 second timeout
|
|
179
|
+
|
|
180
|
+
const success = electronSafeNativeBinding.stopRecording();
|
|
181
|
+
clearTimeout(stopTimeout);
|
|
249
182
|
|
|
250
|
-
|
|
183
|
+
// Always cleanup
|
|
251
184
|
this.isRecording = false;
|
|
252
185
|
if (this.recordingTimer) {
|
|
253
186
|
clearInterval(this.recordingTimer);
|
|
254
187
|
this.recordingTimer = null;
|
|
255
188
|
}
|
|
256
|
-
}, 10000);
|
|
257
189
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
clearInterval(this.recordingTimer);
|
|
264
|
-
this.recordingTimer = null;
|
|
265
|
-
}
|
|
190
|
+
const result = {
|
|
191
|
+
code: success ? 0 : 1,
|
|
192
|
+
outputPath: this.outputPath,
|
|
193
|
+
electronSafe: true,
|
|
194
|
+
};
|
|
266
195
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
196
|
+
this.emit("stopped", result);
|
|
197
|
+
|
|
198
|
+
if (success) {
|
|
199
|
+
// Check if file exists
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
if (fs.existsSync(this.outputPath)) {
|
|
202
|
+
this.emit("completed", this.outputPath);
|
|
203
|
+
console.log("✅ Recording completed successfully");
|
|
204
|
+
} else {
|
|
205
|
+
console.warn("⚠️ Recording completed but file not found");
|
|
206
|
+
}
|
|
207
|
+
}, 1000);
|
|
208
|
+
}
|
|
272
209
|
|
|
273
|
-
|
|
210
|
+
resolve(result);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("❌ Exception during recording stop:", error);
|
|
274
213
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
console.warn("⚠️ Recording completed but file not found");
|
|
282
|
-
}
|
|
283
|
-
}, 1000);
|
|
284
|
-
}
|
|
214
|
+
// Force cleanup
|
|
215
|
+
this.isRecording = false;
|
|
216
|
+
if (this.recordingTimer) {
|
|
217
|
+
clearInterval(this.recordingTimer);
|
|
218
|
+
this.recordingTimer = null;
|
|
219
|
+
}
|
|
285
220
|
|
|
286
|
-
|
|
287
|
-
} catch (error) {
|
|
288
|
-
console.error("❌ Exception during recording stop:", error);
|
|
289
|
-
this.isRecording = false;
|
|
290
|
-
if (this.recordingTimer) {
|
|
291
|
-
clearInterval(this.recordingTimer);
|
|
292
|
-
this.recordingTimer = null;
|
|
221
|
+
reject(error);
|
|
293
222
|
}
|
|
294
|
-
|
|
295
|
-
}
|
|
223
|
+
});
|
|
296
224
|
}
|
|
297
225
|
|
|
298
226
|
/**
|
|
@@ -452,26 +380,6 @@ class ElectronSafeMacRecorder extends EventEmitter {
|
|
|
452
380
|
}
|
|
453
381
|
}
|
|
454
382
|
|
|
455
|
-
async startCursorCapture(intervalOrFilepath, options = {}) {
|
|
456
|
-
if (!loadElectronSafeModule()) {
|
|
457
|
-
throw new Error("Failed to load Electron-safe native module");
|
|
458
|
-
}
|
|
459
|
-
return cursorCapturePolling.startCursorCapture(
|
|
460
|
-
this,
|
|
461
|
-
electronSafeNativeBinding,
|
|
462
|
-
intervalOrFilepath,
|
|
463
|
-
options,
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
async stopCursorCapture() {
|
|
468
|
-
loadElectronSafeModule();
|
|
469
|
-
if (!electronSafeNativeBinding) {
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
return cursorCapturePolling.stopCursorCapture(this);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
383
|
/**
|
|
476
384
|
* Get module information
|
|
477
385
|
*/
|