node-mac-recorder 2.22.16 → 2.22.18

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.
@@ -3,7 +3,19 @@
3
3
  const fs = require("fs");
4
4
  const { resolveCursorDisplayInfo } = require("./displayInfo");
5
5
 
6
- const TEXT_INPUT_SAMPLE_MS = 95;
6
+ const IS_ELECTRON = !!(
7
+ process &&
8
+ process.versions &&
9
+ process.versions.electron
10
+ );
11
+
12
+ const NATIVE_TEXT_INPUT_SAMPLE_MS = 120;
13
+ const NATIVE_TEXT_INPUT_GRACE_MS = 600;
14
+
15
+ const ELECTRON_SYNTH_GRACE_MS = 400;
16
+ const ELECTRON_SYNTH_THROTTLE_MS = 220;
17
+
18
+ const DEFAULT_CURSOR_INTERVAL_MS = IS_ELECTRON ? 50 : 20;
7
19
 
8
20
  function shouldCaptureCursorSample(lastCapturedData, currentData) {
9
21
  if (!lastCapturedData) {
@@ -68,6 +80,96 @@ function transformInputFrameGlobal(ifr, d) {
68
80
  };
69
81
  }
70
82
 
83
+ function tryAppendSyntheticTextInputRow(recorder, filepath, cursorData, timestamp) {
84
+ if (!IS_ELECTRON) {
85
+ return;
86
+ }
87
+ const ct = cursorData.cursorType || "";
88
+ if (ct !== "text" && ct !== "vertical-text") {
89
+ return;
90
+ }
91
+ if (timestamp < ELECTRON_SYNTH_GRACE_MS) {
92
+ return;
93
+ }
94
+ const wall = Date.now();
95
+ if (
96
+ wall - (recorder._electronSynthWallMs || 0) <
97
+ ELECTRON_SYNTH_THROTTLE_MS
98
+ ) {
99
+ return;
100
+ }
101
+ const vx = cursorData.x;
102
+ const vy = cursorData.y;
103
+ if (!Number.isFinite(vx) || !Number.isFinite(vy)) {
104
+ return;
105
+ }
106
+
107
+ const tiRow = {
108
+ x: vx,
109
+ y: vy,
110
+ timestamp,
111
+ unixTimeMs: wall,
112
+ cursorType: "text",
113
+ type: "textInput",
114
+ caretX: vx,
115
+ caretY: vy,
116
+ inputFrame: {
117
+ x: vx - 1,
118
+ y: vy - 9,
119
+ width: 2,
120
+ height: 18,
121
+ },
122
+ coordinateSystem: cursorData.coordinateSystem,
123
+ recordingType: cursorData.recordingType,
124
+ videoInfo: cursorData.videoInfo || {},
125
+ displayInfo: cursorData.displayInfo || {},
126
+ };
127
+
128
+ if (cursorData.location) {
129
+ tiRow.location = cursorData.location;
130
+ }
131
+ if (cursorData.windowRelative) {
132
+ tiRow.windowRelative = cursorData.windowRelative;
133
+ }
134
+
135
+ if (
136
+ recorder.cursorCaptureFirstWrite &&
137
+ recorder.cursorCaptureSessionTimestamp
138
+ ) {
139
+ tiRow._syncMetadata = {
140
+ videoStartTime: recorder.cursorCaptureSessionTimestamp,
141
+ cursorStartTime: recorder.cursorCaptureStartTime,
142
+ offsetMs:
143
+ recorder.cursorCaptureStartTime -
144
+ recorder.cursorCaptureSessionTimestamp,
145
+ };
146
+ }
147
+
148
+ const le = recorder._lastTextInputEmitted;
149
+ if (
150
+ le &&
151
+ Math.abs(le.caretX - tiRow.caretX) < 0.75 &&
152
+ Math.abs(le.caretY - tiRow.caretY) < 0.75 &&
153
+ timestamp - le.timestamp < 220
154
+ ) {
155
+ return;
156
+ }
157
+ recorder._lastTextInputEmitted = {
158
+ caretX: tiRow.caretX,
159
+ caretY: tiRow.caretY,
160
+ timestamp,
161
+ };
162
+ recorder._electronSynthWallMs = wall;
163
+
164
+ const jsonString = JSON.stringify(tiRow);
165
+ if (recorder.cursorCaptureFirstWrite) {
166
+ fs.appendFileSync(filepath, jsonString);
167
+ recorder.cursorCaptureFirstWrite = false;
168
+ } else {
169
+ fs.appendFileSync(filepath, "," + jsonString);
170
+ }
171
+ }
172
+
71
173
  function tryAppendTextInput(
72
174
  recorder,
73
175
  nativeBinding,
@@ -75,11 +177,24 @@ function tryAppendTextInput(
75
177
  position,
76
178
  timestamp,
77
179
  ) {
180
+ if (IS_ELECTRON) {
181
+ return;
182
+ }
78
183
  if (typeof nativeBinding.getTextInputSnapshot !== "function") {
79
184
  return;
80
185
  }
186
+ if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
187
+ return;
188
+ }
189
+ const ct = position.cursorType || "";
190
+ if (ct !== "text" && ct !== "vertical-text") {
191
+ return;
192
+ }
81
193
  const wall = Date.now();
82
- if (wall - (recorder._tiSampleWallMs || 0) < TEXT_INPUT_SAMPLE_MS) {
194
+ if (
195
+ wall - (recorder._tiSampleWallMs || 0) <
196
+ NATIVE_TEXT_INPUT_SAMPLE_MS
197
+ ) {
83
198
  return;
84
199
  }
85
200
  recorder._tiSampleWallMs = wall;
@@ -172,9 +287,53 @@ function tryAppendTextInput(
172
287
  }
173
288
  }
174
289
 
290
+ function queueDeferredTextInputSample(
291
+ recorder,
292
+ nativeBinding,
293
+ filepath,
294
+ position,
295
+ timestamp,
296
+ ) {
297
+ if (IS_ELECTRON) {
298
+ return;
299
+ }
300
+ if (typeof nativeBinding.getTextInputSnapshot !== "function") {
301
+ return;
302
+ }
303
+ if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
304
+ return;
305
+ }
306
+ const ct = position.cursorType || "";
307
+ if (ct !== "text" && ct !== "vertical-text") {
308
+ return;
309
+ }
310
+ if (recorder._tiDeferredPending) {
311
+ return;
312
+ }
313
+ recorder._tiDeferredPending = true;
314
+ const pos = {
315
+ x: position.x,
316
+ y: position.y,
317
+ cursorType: position.cursorType,
318
+ eventType: position.eventType,
319
+ };
320
+ const ts = timestamp;
321
+ setImmediate(() => {
322
+ recorder._tiDeferredPending = false;
323
+ if (!recorder.cursorCaptureFile || recorder.cursorCaptureFile !== filepath) {
324
+ return;
325
+ }
326
+ try {
327
+ tryAppendTextInput(recorder, nativeBinding, filepath, pos, ts);
328
+ } catch {
329
+ /* ignore */
330
+ }
331
+ });
332
+ }
333
+
175
334
  async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, options = {}) {
176
335
  let filepath;
177
- let interval = 20;
336
+ let interval = DEFAULT_CURSOR_INTERVAL_MS;
178
337
 
179
338
  if (typeof intervalOrFilepath === "number") {
180
339
  interval = Math.max(10, intervalOrFilepath);
@@ -227,7 +386,9 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
227
386
  recorder.lastCapturedData = null;
228
387
  recorder.cursorCaptureSessionTimestamp = recorder.sessionTimestamp;
229
388
  recorder._tiSampleWallMs = 0;
389
+ recorder._electronSynthWallMs = 0;
230
390
  recorder._lastTextInputEmitted = null;
391
+ recorder._tiDeferredPending = false;
231
392
 
232
393
  recorder.cursorCaptureInterval = setInterval(() => {
233
394
  try {
@@ -348,13 +509,22 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
348
509
  recorder.lastCapturedData = { ...cursorData };
349
510
  }
350
511
 
351
- tryAppendTextInput(
352
- recorder,
353
- nativeBinding,
354
- filepath,
355
- position,
356
- timestamp,
357
- );
512
+ if (IS_ELECTRON) {
513
+ tryAppendSyntheticTextInputRow(
514
+ recorder,
515
+ filepath,
516
+ cursorData,
517
+ timestamp,
518
+ );
519
+ } else {
520
+ queueDeferredTextInputSample(
521
+ recorder,
522
+ nativeBinding,
523
+ filepath,
524
+ position,
525
+ timestamp,
526
+ );
527
+ }
358
528
  } catch (error) {
359
529
  console.error("Cursor capture error:", error);
360
530
  }
@@ -387,7 +557,9 @@ async function stopCursorCapture(recorder) {
387
557
  recorder.cursorCaptureFirstWrite = true;
388
558
  recorder.cursorDisplayInfo = null;
389
559
  recorder._tiSampleWallMs = 0;
560
+ recorder._electronSynthWallMs = 0;
390
561
  recorder._lastTextInputEmitted = null;
562
+ recorder._tiDeferredPending = false;
391
563
 
392
564
  recorder.emit("cursorCaptureStopped");
393
565
  resolve(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.22.16",
3
+ "version": "2.22.18",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [