node-mac-recorder 2.24.2 → 2.24.4
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/index.js +147 -48
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,6 +2,12 @@ const { EventEmitter } = require("events");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
|
|
5
|
+
// Videonun ilk karesi yakalanana kadar beklenecek ust sinir.
|
|
6
|
+
// Normalde birkac yuz ms; clamshell'de ekran envanteri yeniden kuruldugu icin
|
|
7
|
+
// saniyelere cikabiliyor. Beklemek, cursor'i yanlis referansa baglamaktan iyidir.
|
|
8
|
+
const VIDEO_START_TIMEOUT_MS = 12000;
|
|
9
|
+
const VIDEO_START_POLL_MS = 10;
|
|
10
|
+
|
|
5
11
|
// Auto-switch to Electron-safe implementation when running under Electron and binary exists
|
|
6
12
|
let USE_ELECTRON_SAFE = false;
|
|
7
13
|
let ElectronSafeMacRecorder = null;
|
|
@@ -860,52 +866,111 @@ class MacRecorder extends EventEmitter {
|
|
|
860
866
|
|
|
861
867
|
// Only start cursor if native recording started successfully
|
|
862
868
|
if (success) {
|
|
863
|
-
//
|
|
864
|
-
//
|
|
869
|
+
// ==========================================================
|
|
870
|
+
// VIDEONUN ILK KARESINI BEKLE — SENKRONUN TEMELI
|
|
871
|
+
// ==========================================================
|
|
872
|
+
// getRecordingStatus() BU AMAC ICIN KULLANILAMAZ:
|
|
873
|
+
// ScreenCaptureKit stream'i henuz baslamadiysa native taraf
|
|
874
|
+
// `g_isRecording` fallback'ine dusup TRUE donuyor, yani
|
|
875
|
+
// "kayit komutu verildi"yi "kayit basladi" saniyor.
|
|
876
|
+
// Olculdu: "fully ready after 0ms" + videoStartTimestamp = 0.
|
|
877
|
+
// Sonucu: cursor timeline'i video'dan ONCE basliyor. Normal
|
|
878
|
+
// kosulda fark ~150-300ms, clamshell'de (ekran envanteri
|
|
879
|
+
// yeniden kuruldugu icin) SANIYELER — editordeki 1sn'lik
|
|
880
|
+
// telafi limiti bunu kapatamiyor ve cursor gorunur sekilde kayiyor.
|
|
881
|
+
//
|
|
882
|
+
// Dogru sinyal getVideoStartTimestamp(): native ilk kareyi
|
|
883
|
+
// yakalayip g_videoStartTime'i set ettiginde gecerli bir
|
|
884
|
+
// wall-clock degeri doner. Timeline'i buna baglayinca
|
|
885
|
+
// baslatma hizi (prewarm vb.) senkronu ETKILEMEZ.
|
|
886
|
+
const usesScreenCaptureKit =
|
|
887
|
+
this.options.preferScreenCaptureKit === true;
|
|
888
|
+
|
|
889
|
+
const readVideoStart = () => {
|
|
890
|
+
try {
|
|
891
|
+
const value = Number(
|
|
892
|
+
typeof nativeBinding.getVideoStartTimestamp === 'function'
|
|
893
|
+
? nativeBinding.getVideoStartTimestamp()
|
|
894
|
+
: 0
|
|
895
|
+
);
|
|
896
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
897
|
+
} catch (_) {
|
|
898
|
+
return 0;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
let videoStartTimestamp = 0;
|
|
865
903
|
const waitStart = Date.now();
|
|
866
|
-
|
|
904
|
+
|
|
905
|
+
if (usesScreenCaptureKit) {
|
|
906
|
+
while (Date.now() - waitStart < VIDEO_START_TIMEOUT_MS) {
|
|
907
|
+
videoStartTimestamp = readVideoStart();
|
|
908
|
+
if (videoStartTimestamp > 0) {
|
|
909
|
+
console.log(
|
|
910
|
+
`✅ SYNC: Video ilk karesi ${Date.now() - waitStart}ms'de yakalandi`
|
|
911
|
+
);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
await new Promise(r => setTimeout(r, VIDEO_START_POLL_MS));
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (!videoStartTimestamp) {
|
|
918
|
+
console.warn(
|
|
919
|
+
`⚠️ SYNC: Video baslangici ${VIDEO_START_TIMEOUT_MS}ms icinde okunamadi — heuristik hizalamaya dusuluyor`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
// AVFoundation yolunda video-start damgasi yok; eski
|
|
924
|
+
// hazir-olma beklemesi korunuyor.
|
|
867
925
|
while (Date.now() - waitStart < 600) {
|
|
868
926
|
try {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
927
|
+
if (
|
|
928
|
+
nativeBinding &&
|
|
929
|
+
nativeBinding.getRecordingStatus &&
|
|
930
|
+
nativeBinding.getRecordingStatus()
|
|
931
|
+
) {
|
|
872
932
|
break;
|
|
873
933
|
}
|
|
874
934
|
} catch (_) {}
|
|
875
935
|
await new Promise(r => setTimeout(r, 30));
|
|
876
936
|
}
|
|
877
|
-
}
|
|
937
|
+
}
|
|
938
|
+
|
|
878
939
|
this.sessionTimestamp = sessionTimestamp;
|
|
940
|
+
this.videoStartTimestamp = videoStartTimestamp;
|
|
941
|
+
// Onceki kayittan kalan referans sizmasin
|
|
942
|
+
this.timelineStartTimestamp = 0;
|
|
879
943
|
|
|
880
|
-
// Native sync_timeline handles A/V alignment - no JS-level delay needed
|
|
881
944
|
const syncTimestamp = Date.now();
|
|
882
945
|
this.syncTimestamp = syncTimestamp;
|
|
883
946
|
this.recordingStartTime = syncTimestamp;
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
console.
|
|
947
|
+
|
|
948
|
+
if (videoStartTimestamp > 0) {
|
|
949
|
+
console.log(
|
|
950
|
+
`🎯 SYNC: Video first-frame timestamp: ${videoStartTimestamp} (JS bunu ${(syncTimestamp - videoStartTimestamp).toFixed(0)}ms sonra fark etti)`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// ZAMAN REFERANSI: cursor/klavye timeline'i VIDEONUN ilk karesine
|
|
955
|
+
// hizalanir, JS'in "kayit hazir" dedigi ana degil.
|
|
956
|
+
//
|
|
957
|
+
// NEDEN: syncTimestamp, yukaridaki hazir-olma dongusunden sonraki an.
|
|
958
|
+
// Bu an ile videonun gercek t=0'i arasindaki fark, baslatma hizina
|
|
959
|
+
// gore DEGISIYOR (ornegin ScreenCaptureKit isitilmissa video cok daha
|
|
960
|
+
// erken basliyor). Cursor bu degisken ana baglanirsa her kayitta
|
|
961
|
+
// farkli bir kayma olusuyor ve editordeki telafi 1sn limitine
|
|
962
|
+
// takilabiliyor. Videonun kendi baslangicini referans alinca fark
|
|
963
|
+
// yapisal olarak sifir olur; hizlanma senkronu bozmaz.
|
|
964
|
+
const timelineStartTimestamp =
|
|
965
|
+
this.videoStartTimestamp > 0
|
|
966
|
+
? this.videoStartTimestamp
|
|
967
|
+
: syncTimestamp;
|
|
968
|
+
this.timelineStartTimestamp = timelineStartTimestamp;
|
|
969
|
+
|
|
970
|
+
if (timelineStartTimestamp !== syncTimestamp) {
|
|
971
|
+
console.log(
|
|
972
|
+
`🎯 SYNC: Timeline referansi video ilk karesine cekildi (${(syncTimestamp - timelineStartTimestamp).toFixed(0)}ms geri)`
|
|
973
|
+
);
|
|
909
974
|
}
|
|
910
975
|
|
|
911
976
|
const standardCursorOptions = {
|
|
@@ -915,11 +980,11 @@ class MacRecorder extends EventEmitter {
|
|
|
915
980
|
this.options.captureArea ? 'area' : 'display',
|
|
916
981
|
captureArea: this.options.captureArea,
|
|
917
982
|
windowId: this.options.windowId,
|
|
918
|
-
startTimestamp:
|
|
983
|
+
startTimestamp: timelineStartTimestamp
|
|
919
984
|
};
|
|
920
985
|
|
|
921
986
|
try {
|
|
922
|
-
console.log('🎯 SYNC: Starting cursor tracking at timestamp:',
|
|
987
|
+
console.log('🎯 SYNC: Starting cursor tracking at timestamp:', timelineStartTimestamp);
|
|
923
988
|
await this.startCursorCapture(cursorFilePath, standardCursorOptions);
|
|
924
989
|
console.log('✅ SYNC: Cursor tracking started successfully');
|
|
925
990
|
} catch (cursorError) {
|
|
@@ -929,8 +994,8 @@ class MacRecorder extends EventEmitter {
|
|
|
929
994
|
|
|
930
995
|
// Klavye kısayolu yakalama (cursor ile AYNI zaman referansı)
|
|
931
996
|
try {
|
|
932
|
-
console.log('⌨️ SYNC: Starting keyboard shortcut capture at timestamp:',
|
|
933
|
-
await this.startKeyboardCapture(keyboardFilePath, { startTimestamp:
|
|
997
|
+
console.log('⌨️ SYNC: Starting keyboard shortcut capture at timestamp:', timelineStartTimestamp);
|
|
998
|
+
await this.startKeyboardCapture(keyboardFilePath, { startTimestamp: timelineStartTimestamp });
|
|
934
999
|
console.log('✅ SYNC: Keyboard shortcut capture started successfully');
|
|
935
1000
|
} catch (keyboardError) {
|
|
936
1001
|
console.warn('⚠️ Keyboard capture failed to start:', keyboardError.message);
|
|
@@ -1021,7 +1086,8 @@ class MacRecorder extends EventEmitter {
|
|
|
1021
1086
|
|
|
1022
1087
|
// Native kayıt gerçekten başladığını kontrol etmek için polling başlat
|
|
1023
1088
|
let recordingStartedEmitted = false;
|
|
1024
|
-
|
|
1089
|
+
let checkRecordingStatus = null;
|
|
1090
|
+
const pollRecordingStatus = () => {
|
|
1025
1091
|
try {
|
|
1026
1092
|
const nativeStatus = nativeBinding.getRecordingStatus();
|
|
1027
1093
|
if (nativeStatus && !recordingStartedEmitted) {
|
|
@@ -1067,8 +1133,10 @@ class MacRecorder extends EventEmitter {
|
|
|
1067
1133
|
});
|
|
1068
1134
|
}
|
|
1069
1135
|
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
checkRecordingStatus = setInterval(pollRecordingStatus, 50);
|
|
1139
|
+
|
|
1072
1140
|
// Timeout fallback - 5 saniye sonra hala başlamamışsa emit et
|
|
1073
1141
|
setTimeout(() => {
|
|
1074
1142
|
if (!recordingStartedEmitted) {
|
|
@@ -1147,9 +1215,16 @@ class MacRecorder extends EventEmitter {
|
|
|
1147
1215
|
|
|
1148
1216
|
return new Promise(async (resolve, reject) => {
|
|
1149
1217
|
const stopRequestedAt = Date.now();
|
|
1218
|
+
// Sure, VIDEONUN baslangicindan olculur. recordingStartTime (JS'in
|
|
1219
|
+
// hazir oldugunu fark ettigi an) videodan birkac on ms sonra oldugu
|
|
1220
|
+
// icin buradan hesaplanan stopLimit videoyu kuyrugundan kirpiyordu.
|
|
1221
|
+
const durationReference =
|
|
1222
|
+
this.timelineStartTimestamp && this.timelineStartTimestamp > 0
|
|
1223
|
+
? this.timelineStartTimestamp
|
|
1224
|
+
: this.recordingStartTime;
|
|
1150
1225
|
const elapsedSeconds =
|
|
1151
|
-
|
|
1152
|
-
? (stopRequestedAt -
|
|
1226
|
+
durationReference && durationReference > 0
|
|
1227
|
+
? (stopRequestedAt - durationReference) / 1000
|
|
1153
1228
|
: -1;
|
|
1154
1229
|
try {
|
|
1155
1230
|
console.log('🛑 SYNC: Stopping all recording components simultaneously');
|
|
@@ -1700,12 +1775,30 @@ class MacRecorder extends EventEmitter {
|
|
|
1700
1775
|
}
|
|
1701
1776
|
|
|
1702
1777
|
// Add sync metadata to first event only
|
|
1778
|
+
//
|
|
1779
|
+
// KRITIK: `videoStartTime` SADECE native'in bildirdigi gercek video
|
|
1780
|
+
// baslangici varsa yazilir. Onceden buraya `cursorCaptureSessionTimestamp`
|
|
1781
|
+
// yaziliyordu; o videonun ilk karesi DEGIL, dosya adi icin uretilen
|
|
1782
|
+
// oturum damgasi. Editor (cursorPlaybackTimeline) bu alani video t=0
|
|
1783
|
+
// sanip birebir telafi uyguladigi icin cursor kayiyordu.
|
|
1784
|
+
// Gercek deger yoksa alani hic yazmiyoruz -> editor heuristik
|
|
1785
|
+
// hizalamaya duser, ki yanlis bir offset uygulamaktan iyidir.
|
|
1703
1786
|
if (this.cursorCaptureFirstWrite && this.cursorCaptureSessionTimestamp) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1787
|
+
const knownVideoStart =
|
|
1788
|
+
Number(this.videoStartTimestamp) > 0
|
|
1789
|
+
? Number(this.videoStartTimestamp)
|
|
1790
|
+
: null;
|
|
1791
|
+
|
|
1792
|
+
cursorData._syncMetadata = knownVideoStart
|
|
1793
|
+
? {
|
|
1794
|
+
videoStartTime: knownVideoStart,
|
|
1795
|
+
cursorStartTime: this.cursorCaptureStartTime,
|
|
1796
|
+
offsetMs: this.cursorCaptureStartTime - knownVideoStart,
|
|
1797
|
+
}
|
|
1798
|
+
: {
|
|
1799
|
+
cursorStartTime: this.cursorCaptureStartTime,
|
|
1800
|
+
sessionTimestamp: this.cursorCaptureSessionTimestamp,
|
|
1801
|
+
};
|
|
1709
1802
|
}
|
|
1710
1803
|
|
|
1711
1804
|
// Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
|
|
@@ -1793,7 +1886,13 @@ class MacRecorder extends EventEmitter {
|
|
|
1793
1886
|
*/
|
|
1794
1887
|
_writeCursorSyncMetadata(cursorFilePath) {
|
|
1795
1888
|
const videoStartTime = Number(this.videoStartTimestamp);
|
|
1796
|
-
|
|
1889
|
+
// Cursor timeline'inin GERCEK referansi. Video baslangici okunabildiyse
|
|
1890
|
+
// timeline zaten ona hizalandi (delay 0); okunamadiysa syncTimestamp'e
|
|
1891
|
+
// dusuldu. Burada varsayim yapmak yerine kullanilan degeri yaziyoruz,
|
|
1892
|
+
// aksi halde editor var olmayan bir kaymayi telafi etmeye calisir.
|
|
1893
|
+
const cursorStartTime = Number(
|
|
1894
|
+
this.timelineStartTimestamp || this.syncTimestamp
|
|
1895
|
+
);
|
|
1797
1896
|
if (
|
|
1798
1897
|
!Number.isFinite(videoStartTime) ||
|
|
1799
1898
|
videoStartTime <= 0 ||
|