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