node-mac-recorder 2.16.28 → 2.16.30
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 +4 -6
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +43 -28
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
4
|
"Bash(FORCE_AVFOUNDATION=1 node test-sequential.js)",
|
|
5
|
-
"Bash(grep:*)"
|
|
5
|
+
"Bash(grep:*)",
|
|
6
|
+
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''🧪 Testing macOS 14/13 coordinate scaling fix...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nrecorder.startRecording(''/tmp/coordinate-fix-test.mov'')\n .then(success => {\n console.log(''Start result:'', success ? ''✅ SUCCESS'' : ''❌ FAILED'');\n if (success) {\n setTimeout(() => {\n recorder.stopRecording().then(() => {\n console.log(''✅ Recording stopped'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/coordinate-fix-test.mov'')) {\n const size = Math.round(fs.statSync(''/tmp/coordinate-fix-test.mov'').size/1024);\n console.log(''📹 File size:'', size + ''KB'');\n if (size > 100) {\n console.log(''🎉 macOS 14/13 coordinate fix test SUCCESS!'');\n } else {\n console.log(''⚠️ File too small, may have issues'');\n }\n } else {\n console.log(''❌ No output file created'');\n }\n });\n }, 3000); // 3 seconds recording\n }\n })\n .catch(console.error);\n\")"
|
|
6
7
|
],
|
|
7
8
|
"deny": [],
|
|
8
9
|
"ask": []
|
package/index.js
CHANGED
|
@@ -212,14 +212,12 @@ class MacRecorder extends EventEmitter {
|
|
|
212
212
|
targetWindow.y < display.y + displayHeight
|
|
213
213
|
) {
|
|
214
214
|
targetDisplayId = display.id; // Use actual display ID, not array index
|
|
215
|
-
//
|
|
215
|
+
// CRITICAL FIX: Convert global coordinates to display-relative coordinates
|
|
216
|
+
// AVFoundation expects simple display-relative top-left coordinates (no flipping)
|
|
216
217
|
adjustedX = targetWindow.x - display.x;
|
|
218
|
+
adjustedY = targetWindow.y - display.y;
|
|
217
219
|
|
|
218
|
-
|
|
219
|
-
// Overlay'deki dönüşümle aynı mantık: screenHeight - windowY - windowHeight
|
|
220
|
-
const displayHeight = parseInt(display.resolution.split("x")[1]);
|
|
221
|
-
const convertedY = displayHeight - targetWindow.y - targetWindow.height;
|
|
222
|
-
adjustedY = Math.max(0, convertedY - display.y);
|
|
220
|
+
console.log(`🔧 macOS 14/13 coordinate fix: Global (${targetWindow.x},${targetWindow.y}) -> Display-relative (${adjustedX},${adjustedY})`);
|
|
223
221
|
break;
|
|
224
222
|
}
|
|
225
223
|
}
|
package/package.json
CHANGED
|
@@ -65,14 +65,25 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
65
65
|
CGFloat scaleY = physicalSize.height / logicalSize.height;
|
|
66
66
|
CGFloat scaleFactor = MAX(scaleX, scaleY); // Use max to handle non-uniform scaling
|
|
67
67
|
|
|
68
|
-
// CRITICAL FIX: Use actual captured image dimensions
|
|
69
|
-
// CGDisplayCreateImage
|
|
68
|
+
// CRITICAL FIX: Use actual captured image dimensions for pixel buffer
|
|
69
|
+
// CGDisplayCreateImage returns physical pixels on Retina displays
|
|
70
70
|
CGImageRef testImage = CGDisplayCreateImage(displayID);
|
|
71
71
|
CGSize actualImageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
|
|
72
72
|
CGImageRelease(testImage);
|
|
73
73
|
|
|
74
|
-
//
|
|
75
|
-
|
|
74
|
+
// CRITICAL FIX: Use actual image dimensions to match what CGDisplayCreateImage returns
|
|
75
|
+
// This prevents the "1/4 recording area" bug on Retina displays
|
|
76
|
+
CGSize recordingSize;
|
|
77
|
+
if (!CGRectIsEmpty(captureRect)) {
|
|
78
|
+
// Scale capture rect to match actual image dimensions
|
|
79
|
+
recordingSize = CGSizeMake(
|
|
80
|
+
captureRect.size.width * (actualImageSize.width / logicalSize.width),
|
|
81
|
+
captureRect.size.height * (actualImageSize.height / logicalSize.height)
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
// Full screen: use actual image size
|
|
85
|
+
recordingSize = actualImageSize;
|
|
86
|
+
}
|
|
76
87
|
|
|
77
88
|
NSLog(@"🎯 CRITICAL: Logical %.0fx%.0f → Actual image %.0fx%.0f",
|
|
78
89
|
logicalSize.width, logicalSize.height, actualImageSize.width, actualImageSize.height);
|
|
@@ -88,7 +99,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
88
99
|
NSLog(@"🔍 Scale factor: %.1fx → Standard display", scaleFactor);
|
|
89
100
|
}
|
|
90
101
|
|
|
91
|
-
NSLog(@"🎯 Recording size: %.0fx%.0f (using
|
|
102
|
+
NSLog(@"🎯 Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|
|
92
103
|
|
|
93
104
|
// Video settings with macOS compatibility
|
|
94
105
|
NSString *codecKey;
|
|
@@ -154,32 +165,34 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
154
165
|
// Store recording parameters with scaling correction
|
|
155
166
|
g_avDisplayID = displayID;
|
|
156
167
|
|
|
157
|
-
//
|
|
168
|
+
// CRITICAL FIX: Scale capture coordinates to match physical pixels
|
|
158
169
|
if (!CGRectIsEmpty(captureRect)) {
|
|
159
|
-
// Scale
|
|
160
|
-
CGFloat
|
|
161
|
-
CGFloat
|
|
170
|
+
// Scale coordinates from logical to physical (for CGDisplayCreateImage)
|
|
171
|
+
CGFloat scaleFactorX = actualImageSize.width / logicalSize.width;
|
|
172
|
+
CGFloat scaleFactorY = actualImageSize.height / logicalSize.height;
|
|
173
|
+
|
|
174
|
+
g_avCaptureRect = CGRectMake(
|
|
175
|
+
captureRect.origin.x * scaleFactorX,
|
|
176
|
+
captureRect.origin.y * scaleFactorY,
|
|
177
|
+
captureRect.size.width * scaleFactorX,
|
|
178
|
+
captureRect.size.height * scaleFactorY
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
NSLog(@"🔲 RETINA FIX: Logical (%.0f,%.0f %.0fx%.0f) → Physical (%.0f,%.0f %.0fx%.0f)",
|
|
182
|
+
captureRect.origin.x, captureRect.origin.y, captureRect.size.width, captureRect.size.height,
|
|
183
|
+
g_avCaptureRect.origin.x, g_avCaptureRect.origin.y, g_avCaptureRect.size.width, g_avCaptureRect.size.height);
|
|
162
184
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
captureRect.size.width * imageScaleX,
|
|
169
|
-
captureRect.size.height * imageScaleY
|
|
170
|
-
);
|
|
171
|
-
g_avCaptureRect = scaledRect;
|
|
172
|
-
NSLog(@"🔲 Capture area scaled: %.0f,%.0f %.0fx%.0f (scale %.1fx%.1fx)",
|
|
173
|
-
scaledRect.origin.x, scaledRect.origin.y, scaledRect.size.width, scaledRect.size.height,
|
|
174
|
-
imageScaleX, imageScaleY);
|
|
185
|
+
// Validate coordinates are within physical display bounds
|
|
186
|
+
if (g_avCaptureRect.origin.x >= 0 && g_avCaptureRect.origin.y >= 0 &&
|
|
187
|
+
g_avCaptureRect.origin.x + g_avCaptureRect.size.width <= actualImageSize.width &&
|
|
188
|
+
g_avCaptureRect.origin.y + g_avCaptureRect.size.height <= actualImageSize.height) {
|
|
189
|
+
NSLog(@"✅ Coordinates validated within physical display bounds %.0fx%.0f", actualImageSize.width, actualImageSize.height);
|
|
175
190
|
} else {
|
|
176
|
-
|
|
177
|
-
NSLog(@"🔲 Capture area (no scaling): %.0f,%.0f %.0fx%.0f",
|
|
178
|
-
captureRect.origin.x, captureRect.origin.y, captureRect.size.width, captureRect.size.height);
|
|
191
|
+
NSLog(@"⚠️ Coordinates may be outside physical display bounds - clipping may occur");
|
|
179
192
|
}
|
|
180
193
|
} else {
|
|
181
194
|
g_avCaptureRect = CGRectZero; // Full screen
|
|
182
|
-
NSLog(@"🖥️ Full screen capture
|
|
195
|
+
NSLog(@"🖥️ Full screen capture using physical dimensions %.0fx%.0f", actualImageSize.width, actualImageSize.height);
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
g_avFrameNumber = 0;
|
|
@@ -243,8 +256,8 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
243
256
|
NSLog(@"🔍 Debug: Buffer %zux%zu, Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
|
|
244
257
|
|
|
245
258
|
if (bufferWidth != imageWidth || bufferHeight != imageHeight) {
|
|
246
|
-
NSLog(@"
|
|
247
|
-
NSLog(@" This
|
|
259
|
+
NSLog(@"🔧 EXPECTED SIZE DIFFERENCE: Buffer %zux%zu (logical) vs Image %zux%zu (physical)", bufferWidth, bufferHeight, imageWidth, imageHeight);
|
|
260
|
+
NSLog(@" This is normal on Retina displays - scaling handled correctly now");
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
@@ -282,7 +295,9 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
282
295
|
8, bytesPerRow, colorSpace, bitmapInfo);
|
|
283
296
|
|
|
284
297
|
if (context) {
|
|
285
|
-
|
|
298
|
+
// CRITICAL FIX: Draw image at correct size - scale physical image to logical buffer size
|
|
299
|
+
CGSize bufferSize = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer));
|
|
300
|
+
CGContextDrawImage(context, CGRectMake(0, 0, bufferSize.width, bufferSize.height), screenImage);
|
|
286
301
|
CGContextRelease(context);
|
|
287
302
|
|
|
288
303
|
// Write frame only if input is ready
|