node-mac-recorder 2.18.2 → 2.18.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/.claude/settings.local.json +2 -1
- package/index.js +57 -10
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +113 -24
package/index.js
CHANGED
|
@@ -252,6 +252,12 @@ class MacRecorder extends EventEmitter {
|
|
|
252
252
|
|
|
253
253
|
// Recording için display bilgisini sakla (cursor capture için)
|
|
254
254
|
const targetDisplay = displays.find(d => d.id === targetDisplayId);
|
|
255
|
+
|
|
256
|
+
// CRITICAL FIX: Get actual physical dimensions for proper cursor scaling
|
|
257
|
+
// This fixes cursor coordinate issues on Retina displays during AVFoundation recording
|
|
258
|
+
const cursorPosition = nativeBinding.getCursorPosition();
|
|
259
|
+
const displayScalingInfo = cursorPosition.displayInfo;
|
|
260
|
+
|
|
255
261
|
this.recordingDisplayInfo = {
|
|
256
262
|
displayId: targetDisplayId,
|
|
257
263
|
x: targetDisplay.x,
|
|
@@ -261,6 +267,10 @@ class MacRecorder extends EventEmitter {
|
|
|
261
267
|
// Add scaling information for cursor coordinate transformation
|
|
262
268
|
logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
|
|
263
269
|
logicalHeight: parseInt(targetDisplay.resolution.split("x")[1]),
|
|
270
|
+
// CRITICAL: Add physical dimensions for proper cursor scaling
|
|
271
|
+
physicalWidth: displayScalingInfo ? displayScalingInfo.physicalWidth : parseInt(targetDisplay.resolution.split("x")[0]),
|
|
272
|
+
physicalHeight: displayScalingInfo ? displayScalingInfo.physicalHeight : parseInt(targetDisplay.resolution.split("x")[1]),
|
|
273
|
+
scaleFactor: displayScalingInfo ? displayScalingInfo.scaleFactor : 1.0,
|
|
264
274
|
};
|
|
265
275
|
}
|
|
266
276
|
|
|
@@ -298,6 +308,11 @@ class MacRecorder extends EventEmitter {
|
|
|
298
308
|
}
|
|
299
309
|
|
|
300
310
|
if (targetDisplay) {
|
|
311
|
+
// CRITICAL FIX: Get actual physical dimensions for proper cursor scaling
|
|
312
|
+
// This fixes cursor coordinate issues on Retina displays during AVFoundation recording
|
|
313
|
+
const cursorPosition = nativeBinding.getCursorPosition();
|
|
314
|
+
const displayScalingInfo = cursorPosition.displayInfo;
|
|
315
|
+
|
|
301
316
|
this.recordingDisplayInfo = {
|
|
302
317
|
displayId: targetDisplay.id,
|
|
303
318
|
x: targetDisplay.x || 0,
|
|
@@ -307,6 +322,10 @@ class MacRecorder extends EventEmitter {
|
|
|
307
322
|
// Add scaling information for cursor coordinate transformation
|
|
308
323
|
logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
|
|
309
324
|
logicalHeight: parseInt(targetDisplay.resolution.split("x")[1]),
|
|
325
|
+
// CRITICAL: Add physical dimensions for proper cursor scaling
|
|
326
|
+
physicalWidth: displayScalingInfo ? displayScalingInfo.physicalWidth : parseInt(targetDisplay.resolution.split("x")[0]),
|
|
327
|
+
physicalHeight: displayScalingInfo ? displayScalingInfo.physicalHeight : parseInt(targetDisplay.resolution.split("x")[1]),
|
|
328
|
+
scaleFactor: displayScalingInfo ? displayScalingInfo.scaleFactor : 1.0,
|
|
310
329
|
};
|
|
311
330
|
}
|
|
312
331
|
} catch (error) {
|
|
@@ -712,6 +731,10 @@ class MacRecorder extends EventEmitter {
|
|
|
712
731
|
displayY: options.displayInfo.y || 0,
|
|
713
732
|
displayWidth: options.displayInfo.width || options.displayInfo.logicalWidth,
|
|
714
733
|
displayHeight: options.displayInfo.height || options.displayInfo.logicalHeight,
|
|
734
|
+
// CRITICAL: Add physical dimensions for proper cursor scaling
|
|
735
|
+
physicalWidth: options.displayInfo.physicalWidth || options.displayInfo.width || options.displayInfo.logicalWidth,
|
|
736
|
+
physicalHeight: options.displayInfo.physicalHeight || options.displayInfo.height || options.displayInfo.logicalHeight,
|
|
737
|
+
scaleFactor: options.displayInfo.scaleFactor || 1.0,
|
|
715
738
|
videoOffsetX: videoOffsetX,
|
|
716
739
|
videoOffsetY: videoOffsetY,
|
|
717
740
|
videoWidth: videoWidth,
|
|
@@ -730,6 +753,10 @@ class MacRecorder extends EventEmitter {
|
|
|
730
753
|
displayY: this.recordingDisplayInfo.y || 0,
|
|
731
754
|
displayWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
|
|
732
755
|
displayHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
|
|
756
|
+
// CRITICAL: Include physical dimensions from recording display info
|
|
757
|
+
physicalWidth: this.recordingDisplayInfo.physicalWidth || this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
|
|
758
|
+
physicalHeight: this.recordingDisplayInfo.physicalHeight || this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
|
|
759
|
+
scaleFactor: this.recordingDisplayInfo.scaleFactor || 1.0,
|
|
733
760
|
videoOffsetX: 0,
|
|
734
761
|
videoOffsetY: 0,
|
|
735
762
|
videoWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
|
|
@@ -785,19 +812,32 @@ class MacRecorder extends EventEmitter {
|
|
|
785
812
|
const displayRelativeX = position.x - this.cursorDisplayInfo.displayX;
|
|
786
813
|
const displayRelativeY = position.y - this.cursorDisplayInfo.displayY;
|
|
787
814
|
|
|
815
|
+
// CRITICAL FIX: Apply physical scaling for AVFoundation recording
|
|
816
|
+
// AVFoundation records at physical resolution while cursor coordinates are logical
|
|
817
|
+
let scaledX = displayRelativeX;
|
|
818
|
+
let scaledY = displayRelativeY;
|
|
819
|
+
|
|
820
|
+
if (this.cursorDisplayInfo.scaleFactor && this.cursorDisplayInfo.scaleFactor > 1.0) {
|
|
821
|
+
// Scale logical cursor coordinates to match physical video dimensions
|
|
822
|
+
scaledX = displayRelativeX * this.cursorDisplayInfo.scaleFactor;
|
|
823
|
+
scaledY = displayRelativeY * this.cursorDisplayInfo.scaleFactor;
|
|
824
|
+
coordinateSystem = "video-relative-scaled";
|
|
825
|
+
}
|
|
826
|
+
|
|
788
827
|
// Step 2: Transform display-relative → video-relative coordinates
|
|
789
|
-
x =
|
|
790
|
-
y =
|
|
791
|
-
coordinateSystem = "video-relative";
|
|
828
|
+
x = scaledX - this.cursorDisplayInfo.videoOffsetX;
|
|
829
|
+
y = scaledY - this.cursorDisplayInfo.videoOffsetY;
|
|
830
|
+
coordinateSystem = this.cursorDisplayInfo.scaleFactor > 1.0 ? "video-relative-scaled" : "video-relative";
|
|
831
|
+
|
|
832
|
+
// Bounds check for video area (use physical video dimensions for AVFoundation)
|
|
833
|
+
const videoWidth = this.cursorDisplayInfo.physicalWidth || this.cursorDisplayInfo.videoWidth;
|
|
834
|
+
const videoHeight = this.cursorDisplayInfo.physicalHeight || this.cursorDisplayInfo.videoHeight;
|
|
792
835
|
|
|
793
|
-
|
|
794
|
-
const outsideVideo = x < 0 || y < 0 ||
|
|
795
|
-
x >= this.cursorDisplayInfo.videoWidth ||
|
|
796
|
-
y >= this.cursorDisplayInfo.videoHeight;
|
|
836
|
+
const outsideVideo = x < 0 || y < 0 || x >= videoWidth || y >= videoHeight;
|
|
797
837
|
|
|
798
838
|
// For debugging - add metadata if cursor is outside video area
|
|
799
839
|
if (outsideVideo) {
|
|
800
|
-
coordinateSystem = "
|
|
840
|
+
coordinateSystem = coordinateSystem + "-outside";
|
|
801
841
|
}
|
|
802
842
|
}
|
|
803
843
|
|
|
@@ -815,12 +855,19 @@ class MacRecorder extends EventEmitter {
|
|
|
815
855
|
width: this.cursorDisplayInfo.videoWidth,
|
|
816
856
|
height: this.cursorDisplayInfo.videoHeight,
|
|
817
857
|
offsetX: this.cursorDisplayInfo.videoOffsetX,
|
|
818
|
-
offsetY: this.cursorDisplayInfo.videoOffsetY
|
|
858
|
+
offsetY: this.cursorDisplayInfo.videoOffsetY,
|
|
859
|
+
// CRITICAL: Add physical dimensions for debugging
|
|
860
|
+
physicalWidth: this.cursorDisplayInfo.physicalWidth,
|
|
861
|
+
physicalHeight: this.cursorDisplayInfo.physicalHeight
|
|
819
862
|
} : null,
|
|
820
863
|
displayInfo: this.cursorDisplayInfo ? {
|
|
821
864
|
displayId: this.cursorDisplayInfo.displayId,
|
|
822
865
|
width: this.cursorDisplayInfo.displayWidth,
|
|
823
|
-
height: this.cursorDisplayInfo.displayHeight
|
|
866
|
+
height: this.cursorDisplayInfo.displayHeight,
|
|
867
|
+
// CRITICAL: Add scaling info for debugging AVFoundation cursor issues
|
|
868
|
+
physicalWidth: this.cursorDisplayInfo.physicalWidth,
|
|
869
|
+
physicalHeight: this.cursorDisplayInfo.physicalHeight,
|
|
870
|
+
scaleFactor: this.cursorDisplayInfo.scaleFactor
|
|
824
871
|
} : null
|
|
825
872
|
};
|
|
826
873
|
|
package/package.json
CHANGED
|
@@ -6,6 +6,83 @@
|
|
|
6
6
|
#import <AppKit/AppKit.h>
|
|
7
7
|
#include <string>
|
|
8
8
|
|
|
9
|
+
// Advanced display scaling detection (same algorithm as cursor_tracker.mm)
|
|
10
|
+
NSDictionary* getAdvancedDisplayScalingInfo(CGDirectDisplayID displayID) {
|
|
11
|
+
@try {
|
|
12
|
+
CGRect displayBounds = CGDisplayBounds(displayID);
|
|
13
|
+
CGSize logicalSize = displayBounds.size;
|
|
14
|
+
|
|
15
|
+
NSLog(@"🔍 Advanced scaling detection for display %u:", displayID);
|
|
16
|
+
NSLog(@" Logical bounds: %.0fx%.0f", logicalSize.width, logicalSize.height);
|
|
17
|
+
|
|
18
|
+
// CRITICAL FIX: Get REAL physical dimensions using multiple detection methods
|
|
19
|
+
// Method 1: CGDisplayCreateImage (may be scaled on some systems)
|
|
20
|
+
CGImageRef testImage = CGDisplayCreateImage(displayID);
|
|
21
|
+
CGSize imageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
|
|
22
|
+
CGImageRelease(testImage);
|
|
23
|
+
|
|
24
|
+
// Method 2: Native display mode detection for true physical resolution
|
|
25
|
+
CGSize actualPhysicalSize = imageSize;
|
|
26
|
+
CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
|
|
27
|
+
if (displayModes) {
|
|
28
|
+
CFIndex modeCount = CFArrayGetCount(displayModes);
|
|
29
|
+
CGSize maxResolution = CGSizeMake(0, 0);
|
|
30
|
+
|
|
31
|
+
// Find the highest resolution mode (native resolution)
|
|
32
|
+
for (CFIndex i = 0; i < modeCount; i++) {
|
|
33
|
+
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
|
|
34
|
+
CGSize modeSize = CGSizeMake(CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode));
|
|
35
|
+
|
|
36
|
+
if (modeSize.width > maxResolution.width ||
|
|
37
|
+
(modeSize.width == maxResolution.width && modeSize.height > maxResolution.height)) {
|
|
38
|
+
maxResolution = modeSize;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Use the max resolution if it's significantly higher than image size
|
|
43
|
+
if (maxResolution.width > imageSize.width * 1.5 || maxResolution.height > imageSize.height * 1.5) {
|
|
44
|
+
actualPhysicalSize = maxResolution;
|
|
45
|
+
NSLog(@" Using display mode detection: %.0fx%.0f (was %.0fx%.0f)",
|
|
46
|
+
maxResolution.width, maxResolution.height, imageSize.width, imageSize.height);
|
|
47
|
+
} else {
|
|
48
|
+
actualPhysicalSize = imageSize;
|
|
49
|
+
NSLog(@" Using image size detection: %.0fx%.0f", imageSize.width, imageSize.height);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
CFRelease(displayModes);
|
|
53
|
+
} else {
|
|
54
|
+
actualPhysicalSize = imageSize;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
CGSize reportedPhysicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
|
|
58
|
+
|
|
59
|
+
NSLog(@"🔍 ADVANCED scaling info:");
|
|
60
|
+
NSLog(@" Logical: %.0fx%.0f", logicalSize.width, logicalSize.height);
|
|
61
|
+
NSLog(@" Reported physical: %.0fx%.0f", reportedPhysicalSize.width, reportedPhysicalSize.height);
|
|
62
|
+
NSLog(@" ACTUAL physical: %.0fx%.0f", actualPhysicalSize.width, actualPhysicalSize.height);
|
|
63
|
+
|
|
64
|
+
CGFloat scaleX = actualPhysicalSize.width / logicalSize.width;
|
|
65
|
+
CGFloat scaleY = actualPhysicalSize.height / logicalSize.height;
|
|
66
|
+
CGFloat scaleFactor = MAX(scaleX, scaleY);
|
|
67
|
+
|
|
68
|
+
NSLog(@"🔍 ADVANCED scale factors: X=%.2f, Y=%.2f, Final=%.2f", scaleX, scaleY, scaleFactor);
|
|
69
|
+
|
|
70
|
+
return @{
|
|
71
|
+
@"displayID": @(displayID),
|
|
72
|
+
@"logicalSize": [NSValue valueWithSize:NSMakeSize(logicalSize.width, logicalSize.height)],
|
|
73
|
+
@"physicalSize": [NSValue valueWithSize:NSMakeSize(actualPhysicalSize.width, actualPhysicalSize.height)],
|
|
74
|
+
@"reportedPhysicalSize": [NSValue valueWithSize:NSMakeSize(reportedPhysicalSize.width, reportedPhysicalSize.height)],
|
|
75
|
+
@"scaleFactor": @(scaleFactor),
|
|
76
|
+
@"scaleX": @(scaleX),
|
|
77
|
+
@"scaleY": @(scaleY),
|
|
78
|
+
@"displayBounds": [NSValue valueWithRect:NSMakeRect(displayBounds.origin.x, displayBounds.origin.y, displayBounds.size.width, displayBounds.size.height)]
|
|
79
|
+
};
|
|
80
|
+
} @catch (NSException *exception) {
|
|
81
|
+
NSLog(@"❌ Advanced scaling detection failed: %@", exception);
|
|
82
|
+
return nil;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
9
86
|
static AVAssetWriter *g_avWriter = nil;
|
|
10
87
|
static AVAssetWriterInput *g_avVideoInput = nil;
|
|
11
88
|
static AVAssetWriterInputPixelBufferAdaptor *g_avPixelBufferAdaptor = nil;
|
|
@@ -53,23 +130,35 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
53
130
|
return false;
|
|
54
131
|
}
|
|
55
132
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
CGSize
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
133
|
+
// CRITICAL FIX: Use advanced scaling detection (same as cursor_tracker.mm)
|
|
134
|
+
NSDictionary *advancedScalingInfo = getAdvancedDisplayScalingInfo(displayID);
|
|
135
|
+
|
|
136
|
+
CGSize logicalSize;
|
|
137
|
+
CGSize actualImageSize;
|
|
138
|
+
CGFloat scaleFactor;
|
|
139
|
+
|
|
140
|
+
if (advancedScalingInfo) {
|
|
141
|
+
// Use advanced detection results
|
|
142
|
+
NSSize logicalSizeValue = [[advancedScalingInfo objectForKey:@"logicalSize"] sizeValue];
|
|
143
|
+
NSSize physicalSizeValue = [[advancedScalingInfo objectForKey:@"physicalSize"] sizeValue];
|
|
144
|
+
|
|
145
|
+
logicalSize = CGSizeMake(logicalSizeValue.width, logicalSizeValue.height);
|
|
146
|
+
actualImageSize = CGSizeMake(physicalSizeValue.width, physicalSizeValue.height);
|
|
147
|
+
scaleFactor = [[advancedScalingInfo objectForKey:@"scaleFactor"] doubleValue];
|
|
148
|
+
|
|
149
|
+
NSLog(@"✅ Using ADVANCED scaling detection for AVFoundation");
|
|
150
|
+
} else {
|
|
151
|
+
// Fallback to old method
|
|
152
|
+
NSLog(@"⚠️ Advanced scaling detection failed, using fallback");
|
|
153
|
+
CGRect displayBounds = CGDisplayBounds(displayID);
|
|
154
|
+
logicalSize = displayBounds.size;
|
|
155
|
+
|
|
156
|
+
CGImageRef testImage = CGDisplayCreateImage(displayID);
|
|
157
|
+
actualImageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
|
|
158
|
+
CGImageRelease(testImage);
|
|
159
|
+
|
|
160
|
+
scaleFactor = MAX(actualImageSize.width / logicalSize.width, actualImageSize.height / logicalSize.height);
|
|
161
|
+
}
|
|
73
162
|
|
|
74
163
|
// CRITICAL FIX: Use actual image dimensions to match what CGDisplayCreateImage returns
|
|
75
164
|
// This prevents the "1/4 recording area" bug on Retina displays
|
|
@@ -85,18 +174,18 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
85
174
|
recordingSize = actualImageSize;
|
|
86
175
|
}
|
|
87
176
|
|
|
88
|
-
NSLog(@"🎯
|
|
177
|
+
NSLog(@"🎯 ADVANCED: Logical %.0fx%.0f → Actual image %.0fx%.0f",
|
|
89
178
|
logicalSize.width, logicalSize.height, actualImageSize.width, actualImageSize.height);
|
|
90
|
-
|
|
179
|
+
|
|
91
180
|
NSLog(@"🖥️ Display bounds (logical): %.0fx%.0f", logicalSize.width, logicalSize.height);
|
|
92
|
-
NSLog(@"🖥️ Display
|
|
93
|
-
|
|
181
|
+
NSLog(@"🖥️ Display actual physical: %.0fx%.0f", actualImageSize.width, actualImageSize.height);
|
|
182
|
+
|
|
94
183
|
if (scaleFactor > 1.5) {
|
|
95
|
-
NSLog(@"🔍 Scale factor: %.
|
|
184
|
+
NSLog(@"🔍 ADVANCED Scale factor: %.2fx → Retina display detected", scaleFactor);
|
|
96
185
|
} else if (scaleFactor > 1.1) {
|
|
97
|
-
NSLog(@"🔍 Scale factor: %.
|
|
186
|
+
NSLog(@"🔍 ADVANCED Scale factor: %.2fx → Non-standard scaling detected", scaleFactor);
|
|
98
187
|
} else {
|
|
99
|
-
NSLog(@"🔍 Scale factor: %.
|
|
188
|
+
NSLog(@"🔍 ADVANCED Scale factor: %.2fx → Standard display", scaleFactor);
|
|
100
189
|
}
|
|
101
190
|
|
|
102
191
|
NSLog(@"🎯 Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|