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.
- package/index.js +117 -176
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -283,16 +283,25 @@ class MacRecorder extends EventEmitter {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
//
|
|
287
|
-
if (
|
|
286
|
+
// Ensure recordingDisplayInfo is always set for cursor tracking
|
|
287
|
+
if (!this.recordingDisplayInfo) {
|
|
288
288
|
try {
|
|
289
289
|
const displays = await this.getDisplays();
|
|
290
|
-
|
|
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:
|
|
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
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
*
|
|
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 {
|
|
687
|
-
* @param {
|
|
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
|
-
//
|
|
710
|
-
if (options.
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
x
|
|
845
|
-
y
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
//
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|