node-mac-recorder 2.22.17 → 2.22.19

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.
@@ -9,9 +9,13 @@ const IS_ELECTRON = !!(
9
9
  process.versions.electron
10
10
  );
11
11
 
12
- const TEXT_INPUT_SAMPLE_MS = IS_ELECTRON ? 280 : 95;
12
+ const NATIVE_TEXT_INPUT_SAMPLE_MS = 120;
13
+ const NATIVE_TEXT_INPUT_GRACE_MS = 600;
13
14
 
14
- const TEXT_INPUT_GRACE_MS = IS_ELECTRON ? 3200 : 600;
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;
15
19
 
16
20
  function shouldCaptureCursorSample(lastCapturedData, currentData) {
17
21
  if (!lastCapturedData) {
@@ -33,6 +37,21 @@ function shouldCaptureCursorSample(lastCapturedData, currentData) {
33
37
  return false;
34
38
  }
35
39
 
40
+ function packDisplayInfoForExport(di) {
41
+ if (!di) return {};
42
+ return {
43
+ displayId: di.displayId,
44
+ displayX: Number.isFinite(Number(di.displayX))
45
+ ? Number(di.displayX)
46
+ : Number(di.x) || 0,
47
+ displayY: Number.isFinite(Number(di.displayY))
48
+ ? Number(di.displayY)
49
+ : Number(di.y) || 0,
50
+ width: di.displayWidth ?? di.width,
51
+ height: di.displayHeight ?? di.height,
52
+ };
53
+ }
54
+
36
55
  function transformGlobalToVideo(globalX, globalY, d) {
37
56
  if (!d || !d.videoRelative) {
38
57
  return {
@@ -76,6 +95,96 @@ function transformInputFrameGlobal(ifr, d) {
76
95
  };
77
96
  }
78
97
 
98
+ function tryAppendSyntheticTextInputRow(recorder, filepath, cursorData, timestamp) {
99
+ if (!IS_ELECTRON) {
100
+ return;
101
+ }
102
+ const ct = cursorData.cursorType || "";
103
+ if (ct !== "text" && ct !== "vertical-text") {
104
+ return;
105
+ }
106
+ if (timestamp < ELECTRON_SYNTH_GRACE_MS) {
107
+ return;
108
+ }
109
+ const wall = Date.now();
110
+ if (
111
+ wall - (recorder._electronSynthWallMs || 0) <
112
+ ELECTRON_SYNTH_THROTTLE_MS
113
+ ) {
114
+ return;
115
+ }
116
+ const vx = cursorData.x;
117
+ const vy = cursorData.y;
118
+ if (!Number.isFinite(vx) || !Number.isFinite(vy)) {
119
+ return;
120
+ }
121
+
122
+ const tiRow = {
123
+ x: vx,
124
+ y: vy,
125
+ timestamp,
126
+ unixTimeMs: wall,
127
+ cursorType: "text",
128
+ type: "textInput",
129
+ caretX: vx,
130
+ caretY: vy,
131
+ inputFrame: {
132
+ x: vx - 1,
133
+ y: vy - 9,
134
+ width: 2,
135
+ height: 18,
136
+ },
137
+ coordinateSystem: cursorData.coordinateSystem,
138
+ recordingType: cursorData.recordingType,
139
+ videoInfo: cursorData.videoInfo || {},
140
+ displayInfo: cursorData.displayInfo || {},
141
+ };
142
+
143
+ if (cursorData.location) {
144
+ tiRow.location = cursorData.location;
145
+ }
146
+ if (cursorData.windowRelative) {
147
+ tiRow.windowRelative = cursorData.windowRelative;
148
+ }
149
+
150
+ if (
151
+ recorder.cursorCaptureFirstWrite &&
152
+ recorder.cursorCaptureSessionTimestamp
153
+ ) {
154
+ tiRow._syncMetadata = {
155
+ videoStartTime: recorder.cursorCaptureSessionTimestamp,
156
+ cursorStartTime: recorder.cursorCaptureStartTime,
157
+ offsetMs:
158
+ recorder.cursorCaptureStartTime -
159
+ recorder.cursorCaptureSessionTimestamp,
160
+ };
161
+ }
162
+
163
+ const le = recorder._lastTextInputEmitted;
164
+ if (
165
+ le &&
166
+ Math.abs(le.caretX - tiRow.caretX) < 0.75 &&
167
+ Math.abs(le.caretY - tiRow.caretY) < 0.75 &&
168
+ timestamp - le.timestamp < 220
169
+ ) {
170
+ return;
171
+ }
172
+ recorder._lastTextInputEmitted = {
173
+ caretX: tiRow.caretX,
174
+ caretY: tiRow.caretY,
175
+ timestamp,
176
+ };
177
+ recorder._electronSynthWallMs = wall;
178
+
179
+ const jsonString = JSON.stringify(tiRow);
180
+ if (recorder.cursorCaptureFirstWrite) {
181
+ fs.appendFileSync(filepath, jsonString);
182
+ recorder.cursorCaptureFirstWrite = false;
183
+ } else {
184
+ fs.appendFileSync(filepath, "," + jsonString);
185
+ }
186
+ }
187
+
79
188
  function tryAppendTextInput(
80
189
  recorder,
81
190
  nativeBinding,
@@ -83,10 +192,13 @@ function tryAppendTextInput(
83
192
  position,
84
193
  timestamp,
85
194
  ) {
195
+ if (IS_ELECTRON) {
196
+ return;
197
+ }
86
198
  if (typeof nativeBinding.getTextInputSnapshot !== "function") {
87
199
  return;
88
200
  }
89
- if (timestamp < TEXT_INPUT_GRACE_MS) {
201
+ if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
90
202
  return;
91
203
  }
92
204
  const ct = position.cursorType || "";
@@ -94,7 +206,10 @@ function tryAppendTextInput(
94
206
  return;
95
207
  }
96
208
  const wall = Date.now();
97
- if (wall - (recorder._tiSampleWallMs || 0) < TEXT_INPUT_SAMPLE_MS) {
209
+ if (
210
+ wall - (recorder._tiSampleWallMs || 0) <
211
+ NATIVE_TEXT_INPUT_SAMPLE_MS
212
+ ) {
98
213
  return;
99
214
  }
100
215
  recorder._tiSampleWallMs = wall;
@@ -141,13 +256,7 @@ function tryAppendTextInput(
141
256
  offsetY: d.videoOffsetY,
142
257
  }
143
258
  : {},
144
- displayInfo: d
145
- ? {
146
- displayId: d.displayId,
147
- width: d.displayWidth,
148
- height: d.displayHeight,
149
- }
150
- : {},
259
+ displayInfo: packDisplayInfoForExport(d),
151
260
  };
152
261
 
153
262
  if (
@@ -194,10 +303,13 @@ function queueDeferredTextInputSample(
194
303
  position,
195
304
  timestamp,
196
305
  ) {
306
+ if (IS_ELECTRON) {
307
+ return;
308
+ }
197
309
  if (typeof nativeBinding.getTextInputSnapshot !== "function") {
198
310
  return;
199
311
  }
200
- if (timestamp < TEXT_INPUT_GRACE_MS) {
312
+ if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
201
313
  return;
202
314
  }
203
315
  const ct = position.cursorType || "";
@@ -230,7 +342,7 @@ function queueDeferredTextInputSample(
230
342
 
231
343
  async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, options = {}) {
232
344
  let filepath;
233
- let interval = 20;
345
+ let interval = DEFAULT_CURSOR_INTERVAL_MS;
234
346
 
235
347
  if (typeof intervalOrFilepath === "number") {
236
348
  interval = Math.max(10, intervalOrFilepath);
@@ -283,6 +395,7 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
283
395
  recorder.lastCapturedData = null;
284
396
  recorder.cursorCaptureSessionTimestamp = recorder.sessionTimestamp;
285
397
  recorder._tiSampleWallMs = 0;
398
+ recorder._electronSynthWallMs = 0;
286
399
  recorder._lastTextInputEmitted = null;
287
400
  recorder._tiDeferredPending = false;
288
401
 
@@ -330,13 +443,7 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
330
443
  offsetY: di.videoOffsetY,
331
444
  }
332
445
  : {},
333
- displayInfo: di
334
- ? {
335
- displayId: di.displayId,
336
- width: di.displayWidth,
337
- height: di.displayHeight,
338
- }
339
- : {},
446
+ displayInfo: packDisplayInfoForExport(di),
340
447
  };
341
448
 
342
449
  if (di?.multiWindowBounds && di.multiWindowBounds.length > 0) {
@@ -405,13 +512,22 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
405
512
  recorder.lastCapturedData = { ...cursorData };
406
513
  }
407
514
 
408
- queueDeferredTextInputSample(
409
- recorder,
410
- nativeBinding,
411
- filepath,
412
- position,
413
- timestamp,
414
- );
515
+ if (IS_ELECTRON) {
516
+ tryAppendSyntheticTextInputRow(
517
+ recorder,
518
+ filepath,
519
+ cursorData,
520
+ timestamp,
521
+ );
522
+ } else {
523
+ queueDeferredTextInputSample(
524
+ recorder,
525
+ nativeBinding,
526
+ filepath,
527
+ position,
528
+ timestamp,
529
+ );
530
+ }
415
531
  } catch (error) {
416
532
  console.error("Cursor capture error:", error);
417
533
  }
@@ -444,6 +560,7 @@ async function stopCursorCapture(recorder) {
444
560
  recorder.cursorCaptureFirstWrite = true;
445
561
  recorder.cursorDisplayInfo = null;
446
562
  recorder._tiSampleWallMs = 0;
563
+ recorder._electronSynthWallMs = 0;
447
564
  recorder._lastTextInputEmitted = null;
448
565
  recorder._tiDeferredPending = false;
449
566
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.22.17",
3
+ "version": "2.22.19",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [