node-mac-recorder 2.18.0 → 2.18.2

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 +117 -176
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -283,16 +283,25 @@ class MacRecorder extends EventEmitter {
283
283
  }
284
284
  }
285
285
 
286
- // DisplayId manuel ayarlanmışsa display bilgisini sakla
287
- if (this.options.displayId !== null && !this.recordingDisplayInfo) {
286
+ // Ensure recordingDisplayInfo is always set for cursor tracking
287
+ if (!this.recordingDisplayInfo) {
288
288
  try {
289
289
  const displays = await this.getDisplays();
290
- const targetDisplay = displays.find(d => d.id === this.options.displayId);
290
+ let targetDisplay;
291
+
292
+ if (this.options.displayId !== null) {
293
+ // Manual displayId specified
294
+ targetDisplay = displays.find(d => d.id === this.options.displayId);
295
+ } else {
296
+ // Default to main display
297
+ targetDisplay = displays.find(d => d.isPrimary) || displays[0];
298
+ }
299
+
291
300
  if (targetDisplay) {
292
301
  this.recordingDisplayInfo = {
293
- displayId: this.options.displayId,
294
- x: targetDisplay.x,
295
- y: targetDisplay.y,
302
+ displayId: targetDisplay.id,
303
+ x: targetDisplay.x || 0,
304
+ y: targetDisplay.y || 0,
296
305
  width: parseInt(targetDisplay.resolution.split("x")[0]),
297
306
  height: parseInt(targetDisplay.resolution.split("x")[1]),
298
307
  // Add scaling information for cursor coordinate transformation
@@ -356,56 +365,20 @@ class MacRecorder extends EventEmitter {
356
365
  this.isRecording = true;
357
366
  this.recordingStartTime = Date.now();
358
367
 
359
- // Start cursor tracking automatically with recording
360
- let cursorOptions = {};
361
-
362
- // For window recording, use simplified window-relative coordinates
363
- if (this.options.windowId) {
364
- // Use cached window info from the earlier window detection
365
- this.getWindows().then(windows => {
366
- const targetWindow = windows.find(w => w.id === this.options.windowId);
367
- if (targetWindow) {
368
- // Start cursor capture with simplified window-relative tracking
369
- this.startCursorCapture(cursorFilePath, {
370
- windowRelative: true,
371
- windowInfo: {
372
- // Use original global window coordinates for reference
373
- x: targetWindow.x,
374
- y: targetWindow.y,
375
- width: targetWindow.width,
376
- height: targetWindow.height,
377
- displayId: this.options.displayId,
378
- // Persist capture area so we can rebuild global offsets reliably
379
- captureArea: this.options.captureArea,
380
- // Keep a snapshot of the window details for debugging/analytics
381
- originalWindow: targetWindow,
382
- // Store display info for multi-display coordinate fixes
383
- targetDisplay: this.recordingDisplayInfo
384
- }
385
- }).catch(cursorError => {
386
- console.warn('Window cursor tracking failed:', cursorError.message);
387
- // Fallback to display recording
388
- this.startCursorCapture(cursorFilePath).catch(fallbackError => {
389
- console.warn('Fallback cursor tracking failed:', fallbackError.message);
390
- });
391
- });
392
- }
393
- }).catch(error => {
394
- console.warn('Could not get window info for cursor tracking:', error.message);
395
- // Fallback to display cursor tracking
396
- this.startCursorCapture(cursorFilePath).catch(cursorError => {
397
- console.warn('Cursor tracking failed to start:', cursorError.message);
398
- });
399
- });
400
- } else {
401
- // For display recording, use display-relative cursor tracking
402
- this.startCursorCapture(cursorFilePath, {
403
- displayRelative: true,
404
- displayInfo: this.recordingDisplayInfo
405
- }).catch(cursorError => {
406
- console.warn('Display cursor tracking failed:', cursorError.message);
407
- });
408
- }
368
+ // Start unified cursor tracking with video-relative coordinates
369
+ // This ensures cursor positions match exactly with video frames
370
+ const standardCursorOptions = {
371
+ videoRelative: true,
372
+ displayInfo: this.recordingDisplayInfo,
373
+ recordingType: this.options.windowId ? 'window' :
374
+ this.options.captureArea ? 'area' : 'display',
375
+ captureArea: this.options.captureArea,
376
+ windowId: this.options.windowId
377
+ };
378
+
379
+ this.startCursorCapture(cursorFilePath, standardCursorOptions).catch(cursorError => {
380
+ console.warn('Unified cursor tracking failed:', cursorError.message);
381
+ });
409
382
 
410
383
  // Timer başlat (progress tracking için)
411
384
  this.recordingTimer = setInterval(() => {
@@ -679,12 +652,14 @@ class MacRecorder extends EventEmitter {
679
652
  }
680
653
 
681
654
  /**
682
- * Cursor capture başlatır - otomatik olarak dosyaya yazmaya başlar
683
- * Recording başlatılmışsa otomatik olarak display-relative koordinatlar kullanır
655
+ * Unified cursor capture for all recording types - uses video-relative coordinates
684
656
  * @param {string|number} intervalOrFilepath - Cursor data JSON dosya yolu veya interval
685
657
  * @param {Object} options - Cursor capture seçenekleri
686
- * @param {Object} options.windowInfo - Pencere bilgileri (window-relative koordinatlar için)
687
- * @param {boolean} options.windowRelative - Koordinatları pencereye göre relative yap
658
+ * @param {boolean} options.videoRelative - Use video-relative coordinates (recommended)
659
+ * @param {Object} options.displayInfo - Display information for coordinate transformation
660
+ * @param {string} options.recordingType - Type of recording: 'display', 'window', 'area'
661
+ * @param {Object} options.captureArea - Capture area for area recording coordinate transformation
662
+ * @param {number} options.windowId - Window ID for window recording coordinate transformation
688
663
  */
689
664
  async startCursorCapture(intervalOrFilepath = 100, options = {}) {
690
665
  let filepath;
@@ -706,86 +681,62 @@ class MacRecorder extends EventEmitter {
706
681
  throw new Error("Cursor capture is already running");
707
682
  }
708
683
 
709
- // Koordinat sistemi belirle: window-relative, display-relative veya global
710
- if (options.windowRelative && options.windowInfo) {
711
- const windowInfo = options.windowInfo;
712
- const targetDisplay = windowInfo.targetDisplay || this.recordingDisplayInfo || null;
713
- const captureArea = windowInfo.captureArea || null;
714
- const hasNumber = (value) => typeof value === "number" && Number.isFinite(value);
715
-
716
- let globalX = hasNumber(windowInfo.x) ? windowInfo.x : null;
717
- let globalY = hasNumber(windowInfo.y) ? windowInfo.y : null;
718
-
719
- if (captureArea && targetDisplay) {
720
- if (!hasNumber(globalX) && hasNumber(captureArea.x) && hasNumber(targetDisplay.x)) {
721
- globalX = targetDisplay.x + captureArea.x;
722
- }
723
- if (!hasNumber(globalY) && hasNumber(captureArea.y) && hasNumber(targetDisplay.y)) {
724
- globalY = targetDisplay.y + captureArea.y;
725
- }
726
- }
727
-
728
- if (!hasNumber(globalX)) {
729
- if (captureArea && hasNumber(captureArea.x) && targetDisplay && hasNumber(targetDisplay.x)) {
730
- globalX = targetDisplay.x + captureArea.x;
731
- } else {
732
- globalX = hasNumber(captureArea?.x) ? captureArea.x : 0;
733
- }
734
- }
735
-
736
- if (!hasNumber(globalY)) {
737
- if (captureArea && hasNumber(captureArea.y) && targetDisplay && hasNumber(targetDisplay.y)) {
738
- globalY = targetDisplay.y + captureArea.y;
739
- } else {
740
- globalY = hasNumber(captureArea?.y) ? captureArea.y : 0;
684
+ // Use video-relative coordinate system for all recording types
685
+ if (options.videoRelative && options.displayInfo) {
686
+ // Calculate video offset based on recording type
687
+ let videoOffsetX = 0;
688
+ let videoOffsetY = 0;
689
+ let videoWidth = options.displayInfo.width || options.displayInfo.logicalWidth;
690
+ let videoHeight = options.displayInfo.height || options.displayInfo.logicalHeight;
691
+
692
+ if (options.recordingType === 'window' && options.windowId) {
693
+ // For window recording: offset = window position in display
694
+ if (options.captureArea) {
695
+ videoOffsetX = options.captureArea.x;
696
+ videoOffsetY = options.captureArea.y;
697
+ videoWidth = options.captureArea.width;
698
+ videoHeight = options.captureArea.height;
741
699
  }
700
+ } else if (options.recordingType === 'area' && options.captureArea) {
701
+ // For area recording: offset = area position in display
702
+ videoOffsetX = options.captureArea.x;
703
+ videoOffsetY = options.captureArea.y;
704
+ videoWidth = options.captureArea.width;
705
+ videoHeight = options.captureArea.height;
742
706
  }
707
+ // For display recording: offset remains 0,0
743
708
 
744
- const displayOffsetX = captureArea && hasNumber(captureArea.x)
745
- ? captureArea.x
746
- : (targetDisplay && hasNumber(globalX) && hasNumber(targetDisplay.x)
747
- ? globalX - targetDisplay.x
748
- : globalX);
749
- const displayOffsetY = captureArea && hasNumber(captureArea.y)
750
- ? captureArea.y
751
- : (targetDisplay && hasNumber(globalY) && hasNumber(targetDisplay.y)
752
- ? globalY - targetDisplay.y
753
- : globalY);
754
-
755
- this.cursorDisplayInfo = {
756
- displayId:
757
- windowInfo.displayId ??
758
- targetDisplay?.displayId ??
759
- targetDisplay?.id ??
760
- null,
761
- x: globalX,
762
- y: globalY,
763
- width: windowInfo.width,
764
- height: windowInfo.height,
765
- windowRelative: true,
766
- windowInfo: {
767
- ...windowInfo,
768
- globalX,
769
- globalY,
770
- displayOffsetX,
771
- displayOffsetY
772
- },
773
- targetDisplay,
774
- captureArea
775
- };
776
- } else if (options.displayRelative && options.displayInfo) {
777
- // Display recording: Use display-relative coordinates
778
709
  this.cursorDisplayInfo = {
779
- displayId: options.displayInfo.displayId,
780
- x: options.displayInfo.x,
781
- y: options.displayInfo.y,
782
- width: options.displayInfo.width,
783
- height: options.displayInfo.height,
784
- displayRelative: true
710
+ displayId: options.displayInfo.displayId || options.displayInfo.id,
711
+ displayX: options.displayInfo.x || 0,
712
+ displayY: options.displayInfo.y || 0,
713
+ displayWidth: options.displayInfo.width || options.displayInfo.logicalWidth,
714
+ displayHeight: options.displayInfo.height || options.displayInfo.logicalHeight,
715
+ videoOffsetX: videoOffsetX,
716
+ videoOffsetY: videoOffsetY,
717
+ videoWidth: videoWidth,
718
+ videoHeight: videoHeight,
719
+ videoRelative: true,
720
+ recordingType: options.recordingType || 'display',
721
+ // Store additional context for debugging
722
+ captureArea: options.captureArea,
723
+ windowId: options.windowId
785
724
  };
786
725
  } else if (this.recordingDisplayInfo) {
787
726
  // Fallback: Use recording display info if available
788
- this.cursorDisplayInfo = this.recordingDisplayInfo;
727
+ this.cursorDisplayInfo = {
728
+ ...this.recordingDisplayInfo,
729
+ displayX: this.recordingDisplayInfo.x || 0,
730
+ displayY: this.recordingDisplayInfo.y || 0,
731
+ displayWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
732
+ displayHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
733
+ videoOffsetX: 0,
734
+ videoOffsetY: 0,
735
+ videoWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
736
+ videoHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
737
+ videoRelative: true,
738
+ recordingType: options.recordingType || 'display'
739
+ };
789
740
  } else {
790
741
  // Final fallback: Main display global coordinates
791
742
  try {
@@ -823,37 +774,30 @@ class MacRecorder extends EventEmitter {
823
774
  const position = nativeBinding.getCursorPosition();
824
775
  const timestamp = Date.now() - this.cursorCaptureStartTime;
825
776
 
826
- // Transform coordinates based on recording type
777
+ // Video-relative coordinate transformation for all recording types
827
778
  let x = position.x;
828
779
  let y = position.y;
829
780
  let coordinateSystem = "global";
830
781
 
831
- if (this.cursorDisplayInfo) {
832
- if (this.cursorDisplayInfo.windowRelative) {
833
- // Window recording: Transform global → window-relative coordinates
834
- x = position.x - this.cursorDisplayInfo.x;
835
- y = position.y - this.cursorDisplayInfo.y;
836
- coordinateSystem = "window-relative";
837
-
838
- // Window bounds check - skip if cursor is outside window
839
- if (x < 0 || y < 0 || x >= this.cursorDisplayInfo.width || y >= this.cursorDisplayInfo.height) {
840
- return; // Skip frame - cursor outside window
841
- }
842
- } else if (this.cursorDisplayInfo.displayRelative) {
843
- // Display recording: Transform global display-relative coordinates
844
- x = position.x - this.cursorDisplayInfo.x;
845
- y = position.y - this.cursorDisplayInfo.y;
846
- coordinateSystem = "display-relative";
847
-
848
- // Display bounds check - skip if cursor is outside display
849
- if (x < 0 || y < 0 || x >= this.cursorDisplayInfo.width || y >= this.cursorDisplayInfo.height) {
850
- return; // Skip frame - cursor outside display
851
- }
852
- } else {
853
- // Legacy fallback: Use global coordinates with basic offset
854
- x = position.x - this.cursorDisplayInfo.x;
855
- y = position.y - this.cursorDisplayInfo.y;
856
- coordinateSystem = "display-relative";
782
+ // Apply video-relative transformation for all recording types
783
+ if (this.cursorDisplayInfo && this.cursorDisplayInfo.videoRelative) {
784
+ // Step 1: Transform global → display-relative coordinates
785
+ const displayRelativeX = position.x - this.cursorDisplayInfo.displayX;
786
+ const displayRelativeY = position.y - this.cursorDisplayInfo.displayY;
787
+
788
+ // Step 2: Transform display-relative → video-relative coordinates
789
+ x = displayRelativeX - this.cursorDisplayInfo.videoOffsetX;
790
+ y = displayRelativeY - this.cursorDisplayInfo.videoOffsetY;
791
+ coordinateSystem = "video-relative";
792
+
793
+ // Bounds check for video area (don't skip, just note if outside)
794
+ const outsideVideo = x < 0 || y < 0 ||
795
+ x >= this.cursorDisplayInfo.videoWidth ||
796
+ y >= this.cursorDisplayInfo.videoHeight;
797
+
798
+ // For debugging - add metadata if cursor is outside video area
799
+ if (outsideVideo) {
800
+ coordinateSystem = "video-relative-outside";
857
801
  }
858
802
  }
859
803
 
@@ -865,22 +809,19 @@ class MacRecorder extends EventEmitter {
865
809
  cursorType: position.cursorType,
866
810
  type: position.eventType || "move",
867
811
  coordinateSystem: coordinateSystem,
868
- // Include recording context for window-relative coordinates
869
- ...(this.cursorDisplayInfo?.windowRelative && {
870
- windowInfo: {
871
- width: this.cursorDisplayInfo.width,
872
- height: this.cursorDisplayInfo.height,
873
- displayId: this.cursorDisplayInfo.displayId
874
- }
875
- }),
876
- // Include display context for display-relative coordinates
877
- ...(this.cursorDisplayInfo?.displayRelative && {
878
- displayInfo: {
879
- displayId: this.cursorDisplayInfo.displayId,
880
- width: this.cursorDisplayInfo.width,
881
- height: this.cursorDisplayInfo.height
882
- }
883
- })
812
+ // Video-relative metadata for all recording types
813
+ recordingType: this.cursorDisplayInfo?.recordingType || "display",
814
+ videoInfo: this.cursorDisplayInfo ? {
815
+ width: this.cursorDisplayInfo.videoWidth,
816
+ height: this.cursorDisplayInfo.videoHeight,
817
+ offsetX: this.cursorDisplayInfo.videoOffsetX,
818
+ offsetY: this.cursorDisplayInfo.videoOffsetY
819
+ } : null,
820
+ displayInfo: this.cursorDisplayInfo ? {
821
+ displayId: this.cursorDisplayInfo.displayId,
822
+ width: this.cursorDisplayInfo.displayWidth,
823
+ height: this.cursorDisplayInfo.displayHeight
824
+ } : null
884
825
  };
885
826
 
886
827
  // Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.18.0",
3
+ "version": "2.18.2",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [