node-mac-recorder 2.24.10 → 2.24.11
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/MultiWindowRecorder.js +85 -4
- package/README.md +18 -1
- package/index-multiprocess.js +50 -6
- package/index.js +153 -41
- package/package.json +1 -1
- package/recorder-worker.js +159 -69
- package/recorder_runtime_safety.cjs +101 -0
- package/src/audio_recorder.mm +43 -43
- package/src/avfoundation_recorder.mm +45 -62
- package/src/camera_recorder.mm +69 -78
- package/src/ios_device_recorder.mm +121 -20
- package/src/keyboard_tracker.mm +8 -1
- package/src/mac_recorder.mm +84 -13
- package/src/recording_writer_safety.h +66 -0
- package/src/screen_capture_kit.h +4 -2
- package/src/screen_capture_kit.mm +175 -195
- package/src/sync_timeline.h +9 -0
- package/src/sync_timeline.mm +54 -0
package/index.js
CHANGED
|
@@ -8,13 +8,14 @@ const fs = require("fs");
|
|
|
8
8
|
const VIDEO_START_TIMEOUT_MS = 12000;
|
|
9
9
|
const VIDEO_START_POLL_MS = 10;
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// The standard addon owns the complete writer/timeline feature set. Keep the
|
|
12
|
+
// legacy Electron-safe implementation opt-in; it cannot pause a writer without
|
|
13
|
+
// finalizing the current file.
|
|
12
14
|
let USE_ELECTRON_SAFE = false;
|
|
13
15
|
let ElectronSafeMacRecorder = null;
|
|
14
16
|
try {
|
|
15
|
-
const isElectron = !!(process && process.versions && process.versions.electron);
|
|
16
17
|
const preferElectronSafe = process.env.PREFER_ELECTRON_SAFE === "1" || process.env.USE_ELECTRON_SAFE === "1";
|
|
17
|
-
if (
|
|
18
|
+
if (preferElectronSafe) {
|
|
18
19
|
const rel = path.join(__dirname, "build", "Release", "mac_recorder_electron.node");
|
|
19
20
|
const dbg = path.join(__dirname, "build", "Debug", "mac_recorder_electron.node");
|
|
20
21
|
if (fs.existsSync(rel) || fs.existsSync(dbg) || preferElectronSafe) {
|
|
@@ -53,6 +54,9 @@ class MacRecorder extends EventEmitter {
|
|
|
53
54
|
this.outputPath = null;
|
|
54
55
|
this.recordingTimer = null;
|
|
55
56
|
this.recordingStartTime = null;
|
|
57
|
+
this.isPaused = false;
|
|
58
|
+
this.pauseStartedAt = null;
|
|
59
|
+
this.pausedDurationMs = 0;
|
|
56
60
|
|
|
57
61
|
// MULTI-SESSION: Unique session ID for this recorder instance
|
|
58
62
|
this.nativeSessionId = null; // Will be generated when recording starts
|
|
@@ -61,6 +65,9 @@ class MacRecorder extends EventEmitter {
|
|
|
61
65
|
this.cursorCaptureInterval = null;
|
|
62
66
|
this.cursorCaptureFile = null;
|
|
63
67
|
this.cursorCaptureStartTime = null;
|
|
68
|
+
this.cursorCapturePaused = false;
|
|
69
|
+
this.cursorPauseStartedAt = null;
|
|
70
|
+
this.cursorPausedDurationMs = 0;
|
|
64
71
|
this.cursorCaptureFirstWrite = true;
|
|
65
72
|
this.lastCapturedData = null;
|
|
66
73
|
this.cursorDisplayInfo = null;
|
|
@@ -212,6 +219,7 @@ class MacRecorder extends EventEmitter {
|
|
|
212
219
|
this.audioCaptureActive = options.includeMicrophone === true;
|
|
213
220
|
this.sessionTimestamp = sessionTimestamp;
|
|
214
221
|
this.recordingMode = "iphone";
|
|
222
|
+
this._resetPauseState();
|
|
215
223
|
|
|
216
224
|
try {
|
|
217
225
|
const success = nativeBinding.startIOSDeviceRecording(
|
|
@@ -233,10 +241,7 @@ class MacRecorder extends EventEmitter {
|
|
|
233
241
|
this.timelineStartTimestamp = this.recordingStartTime;
|
|
234
242
|
this.syncTimestamp = this.recordingStartTime;
|
|
235
243
|
this.recordingTimer = setInterval(() => {
|
|
236
|
-
this.emit(
|
|
237
|
-
"timeUpdate",
|
|
238
|
-
Math.floor((Date.now() - this.recordingStartTime) / 1000),
|
|
239
|
-
);
|
|
244
|
+
this.emit("timeUpdate", this._getRecordingTimeSeconds());
|
|
240
245
|
}, 1000);
|
|
241
246
|
|
|
242
247
|
const event = {
|
|
@@ -272,8 +277,11 @@ class MacRecorder extends EventEmitter {
|
|
|
272
277
|
throw new Error("No iPhone recording in progress");
|
|
273
278
|
}
|
|
274
279
|
let success = false;
|
|
280
|
+
const recordingTime = this._getRecordingTimeSeconds();
|
|
281
|
+
const pausedDuration = this._getPausedDurationMs() / 1000;
|
|
275
282
|
try {
|
|
276
283
|
success = nativeBinding.stopIOSDeviceRecording();
|
|
284
|
+
await require("./recorder_runtime_safety.cjs").waitForNativeIdle(nativeBinding);
|
|
277
285
|
} finally {
|
|
278
286
|
if (this.recordingTimer) clearInterval(this.recordingTimer);
|
|
279
287
|
this.recordingTimer = null;
|
|
@@ -289,6 +297,8 @@ class MacRecorder extends EventEmitter {
|
|
|
289
297
|
sessionTimestamp: this.sessionTimestamp,
|
|
290
298
|
syncTimestamp: this.syncTimestamp,
|
|
291
299
|
sourceType: "iphone",
|
|
300
|
+
recordingTime,
|
|
301
|
+
pausedDuration,
|
|
292
302
|
};
|
|
293
303
|
if (this.cameraCaptureActive) {
|
|
294
304
|
this.emit("cameraCaptureStopped", {
|
|
@@ -314,6 +324,7 @@ class MacRecorder extends EventEmitter {
|
|
|
314
324
|
}
|
|
315
325
|
this.sessionTimestamp = null;
|
|
316
326
|
this.syncTimestamp = null;
|
|
327
|
+
this._resetPauseState();
|
|
317
328
|
return result;
|
|
318
329
|
}
|
|
319
330
|
|
|
@@ -525,6 +536,7 @@ class MacRecorder extends EventEmitter {
|
|
|
525
536
|
if (!outputPath) {
|
|
526
537
|
throw new Error("Output path is required");
|
|
527
538
|
}
|
|
539
|
+
this._resetPauseState();
|
|
528
540
|
|
|
529
541
|
// Seçenekleri güncelle
|
|
530
542
|
this.setOptions(options);
|
|
@@ -913,6 +925,8 @@ class MacRecorder extends EventEmitter {
|
|
|
913
925
|
// MULTI-SESSION: Generate unique session ID for this recording
|
|
914
926
|
// Use provided sessionTimestamp from options, or generate new one
|
|
915
927
|
const sessionTimestamp = this.options.sessionTimestamp || Date.now();
|
|
928
|
+
const recordingGeneration = this._captureGeneration;
|
|
929
|
+
const isCurrentRecording = () => this._captureGeneration === recordingGeneration && !this._stopPromise;
|
|
916
930
|
this.sessionTimestamp = sessionTimestamp;
|
|
917
931
|
this.nativeSessionId = `rec_${sessionTimestamp}_${Math.random().toString(36).substr(2, 9)}`;
|
|
918
932
|
|
|
@@ -1088,7 +1102,7 @@ class MacRecorder extends EventEmitter {
|
|
|
1088
1102
|
this._videoStartWatcherActive = true;
|
|
1089
1103
|
this._videoStartWatcher = (async () => {
|
|
1090
1104
|
while (
|
|
1091
|
-
this._videoStartWatcherActive &&
|
|
1105
|
+
this._videoStartWatcherActive && isCurrentRecording() &&
|
|
1092
1106
|
Date.now() - watchStart < VIDEO_START_TIMEOUT_MS
|
|
1093
1107
|
) {
|
|
1094
1108
|
const value = readVideoStart();
|
|
@@ -1225,10 +1239,7 @@ class MacRecorder extends EventEmitter {
|
|
|
1225
1239
|
|
|
1226
1240
|
// Timer başlat (progress tracking için)
|
|
1227
1241
|
this.recordingTimer = setInterval(() => {
|
|
1228
|
-
|
|
1229
|
-
(Date.now() - this.recordingStartTime) / 1000
|
|
1230
|
-
);
|
|
1231
|
-
this.emit("timeUpdate", elapsed);
|
|
1242
|
+
this.emit("timeUpdate", this._getRecordingTimeSeconds());
|
|
1232
1243
|
}, 1000);
|
|
1233
1244
|
|
|
1234
1245
|
// Native kayıt gerçekten başladığını kontrol etmek için polling başlat
|
|
@@ -1267,6 +1278,10 @@ class MacRecorder extends EventEmitter {
|
|
|
1267
1278
|
}
|
|
1268
1279
|
};
|
|
1269
1280
|
const pollRecordingStatus = () => {
|
|
1281
|
+
if (!isCurrentRecording() || !this.isRecording) {
|
|
1282
|
+
clearInterval(checkRecordingStatus);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1270
1285
|
try {
|
|
1271
1286
|
const nativeStatus = isNativeRecordingLive();
|
|
1272
1287
|
if (nativeStatus && !recordingStartedEmitted) {
|
|
@@ -1314,10 +1329,11 @@ class MacRecorder extends EventEmitter {
|
|
|
1314
1329
|
}
|
|
1315
1330
|
};
|
|
1316
1331
|
|
|
1317
|
-
checkRecordingStatus = setInterval(pollRecordingStatus, 50);
|
|
1332
|
+
checkRecordingStatus = this.recordingStatusInterval = setInterval(pollRecordingStatus, 50);
|
|
1318
1333
|
|
|
1319
1334
|
// Timeout fallback - 5 saniye sonra hala başlamamışsa emit et
|
|
1320
|
-
setTimeout(() => {
|
|
1335
|
+
this.recordingStartTimeout = setTimeout(() => {
|
|
1336
|
+
if (!isCurrentRecording() || !this.isRecording) return;
|
|
1321
1337
|
if (!recordingStartedEmitted) {
|
|
1322
1338
|
recordingStartedEmitted = true;
|
|
1323
1339
|
clearInterval(checkRecordingStatus);
|
|
@@ -1344,25 +1360,13 @@ class MacRecorder extends EventEmitter {
|
|
|
1344
1360
|
} else {
|
|
1345
1361
|
this.cameraCaptureActive = false;
|
|
1346
1362
|
if (this.options.captureCamera === true) {
|
|
1347
|
-
|
|
1348
|
-
try {
|
|
1349
|
-
fs.unlinkSync(cameraFilePath);
|
|
1350
|
-
} catch (cleanupError) {
|
|
1351
|
-
console.warn("Camera temp file cleanup failed:", cleanupError.message);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1363
|
+
// Native cleanup owns partially written files; never unlink an active writer.
|
|
1354
1364
|
this.cameraCaptureFile = null;
|
|
1355
1365
|
}
|
|
1356
1366
|
|
|
1357
1367
|
if (captureAudio) {
|
|
1358
1368
|
this.audioCaptureActive = false;
|
|
1359
|
-
|
|
1360
|
-
try {
|
|
1361
|
-
fs.unlinkSync(audioFilePath);
|
|
1362
|
-
} catch (cleanupError) {
|
|
1363
|
-
console.warn("Audio temp file cleanup failed:", cleanupError.message);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1369
|
+
// Native cleanup owns partially written files; never unlink an active writer.
|
|
1366
1370
|
this.audioCaptureFile = null;
|
|
1367
1371
|
}
|
|
1368
1372
|
|
|
@@ -1383,6 +1387,86 @@ class MacRecorder extends EventEmitter {
|
|
|
1383
1387
|
});
|
|
1384
1388
|
}
|
|
1385
1389
|
|
|
1390
|
+
_resetPauseState() {
|
|
1391
|
+
this.isPaused = false;
|
|
1392
|
+
this.pauseStartedAt = null;
|
|
1393
|
+
this.pausedDurationMs = 0;
|
|
1394
|
+
this.cursorCapturePaused = false;
|
|
1395
|
+
this.cursorPauseStartedAt = null;
|
|
1396
|
+
this.cursorPausedDurationMs = 0;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
_getPausedDurationMs(now = Date.now()) {
|
|
1400
|
+
return this.pausedDurationMs +
|
|
1401
|
+
(this.isPaused && this.pauseStartedAt ? Math.max(0, now - this.pauseStartedAt) : 0);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
_getRecordingTimeSeconds(now = Date.now()) {
|
|
1405
|
+
if (!this.recordingStartTime) return 0;
|
|
1406
|
+
return Math.floor(Math.max(0, now - this.recordingStartTime - this._getPausedDurationMs(now)) / 1000);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
pauseCursorCapture(pausedAt = Date.now()) {
|
|
1410
|
+
if (!this.cursorCaptureInterval || this.cursorCapturePaused) return false;
|
|
1411
|
+
this.cursorCapturePaused = true;
|
|
1412
|
+
this.cursorPauseStartedAt = pausedAt;
|
|
1413
|
+
return true;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
resumeCursorCapture(resumedAt = Date.now()) {
|
|
1417
|
+
if (!this.cursorCapturePaused) return false;
|
|
1418
|
+
if (this.cursorPauseStartedAt) {
|
|
1419
|
+
this.cursorPausedDurationMs += Math.max(0, resumedAt - this.cursorPauseStartedAt);
|
|
1420
|
+
}
|
|
1421
|
+
this.cursorCapturePaused = false;
|
|
1422
|
+
this.cursorPauseStartedAt = null;
|
|
1423
|
+
return true;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
async pauseRecording() {
|
|
1427
|
+
if (!this.isRecording) throw new Error("No recording in progress");
|
|
1428
|
+
if (this.isPaused) return this.getStatus();
|
|
1429
|
+
|
|
1430
|
+
const pauseMethod = this.recordingMode === "iphone"
|
|
1431
|
+
? nativeBinding.pauseIOSDeviceRecording
|
|
1432
|
+
: nativeBinding.pauseRecording;
|
|
1433
|
+
if (typeof pauseMethod !== "function") {
|
|
1434
|
+
throw new Error("This recorder build does not support pausing");
|
|
1435
|
+
}
|
|
1436
|
+
const pausedAt = Date.now();
|
|
1437
|
+
if (pauseMethod.call(nativeBinding) !== true) {
|
|
1438
|
+
throw new Error("Recording could not be paused");
|
|
1439
|
+
}
|
|
1440
|
+
this.isPaused = true;
|
|
1441
|
+
this.pauseStartedAt = pausedAt;
|
|
1442
|
+
this.pauseCursorCapture(pausedAt);
|
|
1443
|
+
const status = this.getStatus();
|
|
1444
|
+
this.emit("paused", status);
|
|
1445
|
+
return status;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async resumeRecording() {
|
|
1449
|
+
if (!this.isRecording) throw new Error("No recording in progress");
|
|
1450
|
+
if (!this.isPaused) return this.getStatus();
|
|
1451
|
+
|
|
1452
|
+
const resumeMethod = this.recordingMode === "iphone"
|
|
1453
|
+
? nativeBinding.resumeIOSDeviceRecording
|
|
1454
|
+
: nativeBinding.resumeRecording;
|
|
1455
|
+
if (typeof resumeMethod !== "function") {
|
|
1456
|
+
throw new Error("This recorder build does not support resuming");
|
|
1457
|
+
}
|
|
1458
|
+
if (resumeMethod.call(nativeBinding) !== true) {
|
|
1459
|
+
throw new Error("Recording could not be resumed");
|
|
1460
|
+
}
|
|
1461
|
+
const resumedAt = Date.now();
|
|
1462
|
+
this.pausedDurationMs += Math.max(0, resumedAt - this.pauseStartedAt);
|
|
1463
|
+
this.pauseStartedAt = null;
|
|
1464
|
+
this.isPaused = false;
|
|
1465
|
+
this.resumeCursorCapture(resumedAt);
|
|
1466
|
+
const status = this.getStatus();
|
|
1467
|
+
this.emit("resumed", status);
|
|
1468
|
+
return status;
|
|
1469
|
+
}
|
|
1386
1470
|
|
|
1387
1471
|
/**
|
|
1388
1472
|
* Ekran kaydını durdurur - SYNCHRONIZED stop for all components
|
|
@@ -1400,14 +1484,23 @@ class MacRecorder extends EventEmitter {
|
|
|
1400
1484
|
// Sure, VIDEONUN baslangicindan olculur. recordingStartTime (JS'in
|
|
1401
1485
|
// hazir oldugunu fark ettigi an) videodan birkac on ms sonra oldugu
|
|
1402
1486
|
// icin buradan hesaplanan stopLimit videoyu kuyrugundan kirpiyordu.
|
|
1487
|
+
// ScreenCaptureKit may publish its first frame hundreds of milliseconds
|
|
1488
|
+
// after cursor capture starts. Cursor metadata compensates that offset, but
|
|
1489
|
+
// the native camera/audio stop limit must use the screen's real t=0.
|
|
1490
|
+
// Otherwise camera files are extended by the startup delay and lip sync
|
|
1491
|
+
// changes from one recording session to the next.
|
|
1492
|
+
const nativeVideoStartTimestamp = Number(this.videoStartTimestamp);
|
|
1403
1493
|
const durationReference =
|
|
1404
|
-
|
|
1405
|
-
?
|
|
1406
|
-
: this.
|
|
1494
|
+
Number.isFinite(nativeVideoStartTimestamp) && nativeVideoStartTimestamp > 0
|
|
1495
|
+
? nativeVideoStartTimestamp
|
|
1496
|
+
: this.timelineStartTimestamp && this.timelineStartTimestamp > 0
|
|
1497
|
+
? this.timelineStartTimestamp
|
|
1498
|
+
: this.recordingStartTime;
|
|
1407
1499
|
const elapsedSeconds =
|
|
1408
1500
|
durationReference && durationReference > 0
|
|
1409
|
-
? (stopRequestedAt - durationReference) / 1000
|
|
1501
|
+
? Math.max(0, stopRequestedAt - durationReference - this._getPausedDurationMs()) / 1000
|
|
1410
1502
|
: -1;
|
|
1503
|
+
const pausedDuration = this._getPausedDurationMs() / 1000;
|
|
1411
1504
|
try {
|
|
1412
1505
|
console.log('🛑 SYNC: Stopping all recording components simultaneously');
|
|
1413
1506
|
|
|
@@ -1457,6 +1550,8 @@ class MacRecorder extends EventEmitter {
|
|
|
1457
1550
|
success = true; // Assume success to avoid throwing
|
|
1458
1551
|
}
|
|
1459
1552
|
|
|
1553
|
+
await require("./recorder_runtime_safety.cjs").waitForNativeIdle(nativeBinding);
|
|
1554
|
+
|
|
1460
1555
|
if (this.options.captureCamera === true) {
|
|
1461
1556
|
try {
|
|
1462
1557
|
const nativeCameraPath = nativeBinding.getCameraRecordingPath
|
|
@@ -1527,6 +1622,8 @@ class MacRecorder extends EventEmitter {
|
|
|
1527
1622
|
outputPath: this.outputPath,
|
|
1528
1623
|
cameraOutputPath: this.cameraCaptureFile || null,
|
|
1529
1624
|
audioOutputPath: this.audioCaptureFile || null,
|
|
1625
|
+
recordingTime: elapsedSeconds > 0 ? elapsedSeconds : this._getRecordingTimeSeconds(),
|
|
1626
|
+
pausedDuration,
|
|
1530
1627
|
sessionTimestamp: sessionId,
|
|
1531
1628
|
syncTimestamp: this.syncTimestamp,
|
|
1532
1629
|
};
|
|
@@ -1535,15 +1632,18 @@ class MacRecorder extends EventEmitter {
|
|
|
1535
1632
|
|
|
1536
1633
|
if (success) {
|
|
1537
1634
|
// Dosyanın oluşturulmasını bekle
|
|
1635
|
+
const completedOutputPath = this.outputPath;
|
|
1636
|
+
const completedGeneration = this._captureGeneration;
|
|
1538
1637
|
setTimeout(() => {
|
|
1539
|
-
if (fs.existsSync(
|
|
1540
|
-
this.emit("completed",
|
|
1638
|
+
if (completedGeneration === this._captureGeneration && fs.existsSync(completedOutputPath)) {
|
|
1639
|
+
this.emit("completed", completedOutputPath);
|
|
1541
1640
|
}
|
|
1542
1641
|
}, 1000);
|
|
1543
1642
|
}
|
|
1544
1643
|
|
|
1545
1644
|
this.sessionTimestamp = null;
|
|
1546
1645
|
this.syncTimestamp = null;
|
|
1646
|
+
this._resetPauseState();
|
|
1547
1647
|
resolve(result);
|
|
1548
1648
|
} catch (error) {
|
|
1549
1649
|
this.isRecording = false;
|
|
@@ -1553,6 +1653,7 @@ class MacRecorder extends EventEmitter {
|
|
|
1553
1653
|
this.audioCaptureFile = null;
|
|
1554
1654
|
this.sessionTimestamp = null;
|
|
1555
1655
|
this.syncTimestamp = null;
|
|
1656
|
+
this._resetPauseState();
|
|
1556
1657
|
if (this.recordingTimer) {
|
|
1557
1658
|
clearInterval(this.recordingTimer);
|
|
1558
1659
|
this.recordingTimer = null;
|
|
@@ -1566,13 +1667,17 @@ class MacRecorder extends EventEmitter {
|
|
|
1566
1667
|
* Kayıt durumunu döndürür
|
|
1567
1668
|
*/
|
|
1568
1669
|
getStatus() {
|
|
1569
|
-
const
|
|
1670
|
+
const iosStatus =
|
|
1570
1671
|
this.recordingMode === "iphone" &&
|
|
1571
1672
|
typeof nativeBinding.getIOSDeviceRecordingStatus === "function"
|
|
1572
|
-
? nativeBinding.getIOSDeviceRecordingStatus()
|
|
1573
|
-
:
|
|
1673
|
+
? nativeBinding.getIOSDeviceRecordingStatus()
|
|
1674
|
+
: null;
|
|
1675
|
+
const nativeStatus = iosStatus
|
|
1676
|
+
? iosStatus.isRecording === true
|
|
1677
|
+
: nativeBinding.getRecordingStatus();
|
|
1574
1678
|
return {
|
|
1575
1679
|
isRecording: this.isRecording && nativeStatus,
|
|
1680
|
+
isPaused: this.isPaused || iosStatus?.isPaused === true,
|
|
1576
1681
|
outputPath: this.outputPath,
|
|
1577
1682
|
cameraOutputPath: this.cameraCaptureFile || null,
|
|
1578
1683
|
audioOutputPath: this.audioCaptureFile || null,
|
|
@@ -1581,9 +1686,8 @@ class MacRecorder extends EventEmitter {
|
|
|
1581
1686
|
sessionTimestamp: this.sessionTimestamp,
|
|
1582
1687
|
syncTimestamp: this.syncTimestamp,
|
|
1583
1688
|
options: this.options,
|
|
1584
|
-
recordingTime: this.
|
|
1585
|
-
|
|
1586
|
-
: 0,
|
|
1689
|
+
recordingTime: this._getRecordingTimeSeconds(),
|
|
1690
|
+
pausedDuration: this._getPausedDurationMs() / 1000,
|
|
1587
1691
|
};
|
|
1588
1692
|
}
|
|
1589
1693
|
|
|
@@ -1890,6 +1994,9 @@ class MacRecorder extends EventEmitter {
|
|
|
1890
1994
|
this.cursorCaptureFile = filepath;
|
|
1891
1995
|
// SYNC FIX: Use synchronized start time for accurate timestamp calculation
|
|
1892
1996
|
this.cursorCaptureStartTime = syncStartTime;
|
|
1997
|
+
this.cursorCapturePaused = false;
|
|
1998
|
+
this.cursorPauseStartedAt = null;
|
|
1999
|
+
this.cursorPausedDurationMs = 0;
|
|
1893
2000
|
this.cursorCaptureFirstWrite = true;
|
|
1894
2001
|
this.lastCapturedData = null;
|
|
1895
2002
|
// Store session timestamp for sync metadata
|
|
@@ -1898,8 +2005,9 @@ class MacRecorder extends EventEmitter {
|
|
|
1898
2005
|
// JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
|
|
1899
2006
|
this.cursorCaptureInterval = setInterval(() => {
|
|
1900
2007
|
try {
|
|
2008
|
+
if (this.cursorCapturePaused) return;
|
|
1901
2009
|
const position = nativeBinding.getCursorPosition();
|
|
1902
|
-
const timestamp = Date.now() - this.cursorCaptureStartTime;
|
|
2010
|
+
const timestamp = Date.now() - this.cursorCaptureStartTime - this.cursorPausedDurationMs;
|
|
1903
2011
|
|
|
1904
2012
|
// Video-relative coordinate transformation for all recording types
|
|
1905
2013
|
let x = position.x;
|
|
@@ -2090,6 +2198,9 @@ class MacRecorder extends EventEmitter {
|
|
|
2090
2198
|
// Değişkenleri temizle
|
|
2091
2199
|
this.lastCapturedData = null;
|
|
2092
2200
|
this.cursorCaptureStartTime = null;
|
|
2201
|
+
this.cursorCapturePaused = false;
|
|
2202
|
+
this.cursorPauseStartedAt = null;
|
|
2203
|
+
this.cursorPausedDurationMs = 0;
|
|
2093
2204
|
this.cursorCaptureFirstWrite = true;
|
|
2094
2205
|
this.cursorDisplayInfo = null;
|
|
2095
2206
|
|
|
@@ -2433,4 +2544,5 @@ class MacRecorder extends EventEmitter {
|
|
|
2433
2544
|
// WindowSelector modülünü de export edelim
|
|
2434
2545
|
MacRecorder.WindowSelector = require('./window-selector');
|
|
2435
2546
|
|
|
2547
|
+
if (!USE_ELECTRON_SAFE) require("./recorder_runtime_safety.cjs")(MacRecorder, nativeBinding);
|
|
2436
2548
|
module.exports = USE_ELECTRON_SAFE ? ElectronSafeMacRecorder : MacRecorder;
|