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.
Files changed (2) hide show
  1. package/index.js +147 -48
  2. 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
- // For ScreenCaptureKit (async startup), wait briefly until native fully initialized
864
- // ScreenCaptureKit needs ~150-300ms to start + ~150ms for first 10 frames
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
- try {
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
- const nativeStatus = nativeBinding && nativeBinding.getRecordingStatus ? nativeBinding.getRecordingStatus() : true;
870
- if (nativeStatus) {
871
- console.log(`✅ SYNC: Native recording fully ready after ${Date.now() - waitStart}ms`);
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
- } catch (_) {}
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
- console.log(`🎯 CURSOR SYNC: Cursor tracking will use timestamp: ${syncTimestamp}`);
885
-
886
- // CURSOR/VIDEO TIME ALIGNMENT:
887
- // Cursor timeline'in t=0'i bu andir (syncTimestamp), ama videonun
888
- // ILK KARESI daha once yakalanmis olabilir (ScreenCaptureKit
889
- // baslatma gecikmesi + yukaridaki hazir-olma beklemesi). Editor
890
- // "cursor'un ilk ornegi = video t=0" varsayarsa aradaki fark sabit
891
- // bir zaman kaymasi olarak kalir. Native gercek video baslangicini
892
- // biliyor; okuyup sakla ki stop'ta cursor JSON'una yazabilelim.
893
- this.videoStartTimestamp = 0;
894
- try {
895
- const nativeVideoStart =
896
- typeof nativeBinding.getVideoStartTimestamp === 'function'
897
- ? Number(nativeBinding.getVideoStartTimestamp())
898
- : 0;
899
- if (Number.isFinite(nativeVideoStart) && nativeVideoStart > 0) {
900
- this.videoStartTimestamp = nativeVideoStart;
901
- console.log(
902
- `🎯 SYNC: Video first-frame timestamp: ${nativeVideoStart} (cursor starts ${(syncTimestamp - nativeVideoStart).toFixed(0)}ms later)`
903
- );
904
- } else {
905
- console.warn('⚠️ SYNC: Video start timestamp unavailable — cursor sync metadata will be skipped');
906
- }
907
- } catch (videoStartError) {
908
- console.warn('⚠️ SYNC: Video start timestamp read failed:', videoStartError.message);
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: syncTimestamp // Align cursor timeline to actual start
983
+ startTimestamp: timelineStartTimestamp
919
984
  };
920
985
 
921
986
  try {
922
- console.log('🎯 SYNC: Starting cursor tracking at timestamp:', syncTimestamp);
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:', syncTimestamp);
933
- await this.startKeyboardCapture(keyboardFilePath, { startTimestamp: syncTimestamp });
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
- const checkRecordingStatus = setInterval(() => {
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
- }, 50); // Her 50ms kontrol et
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
- this.recordingStartTime && this.recordingStartTime > 0
1152
- ? (stopRequestedAt - this.recordingStartTime) / 1000
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
- cursorData._syncMetadata = {
1705
- videoStartTime: this.cursorCaptureSessionTimestamp,
1706
- cursorStartTime: this.cursorCaptureStartTime,
1707
- offsetMs: this.cursorCaptureStartTime - this.cursorCaptureSessionTimestamp
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
- const cursorStartTime = Number(this.syncTimestamp);
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 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.24.2",
3
+ "version": "2.24.4",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [