node-mac-recorder 2.18.10 → 2.19.1
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 +3 -1
- package/CLAUDE.md +20 -23
- package/binding.gyp +1 -1
- package/index.js +8 -96
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +24 -113
- package/src/mac_recorder.mm +72 -81
- package/src/screen_capture_kit.h +1 -0
- package/src/screen_capture_kit.mm +9 -15
- package/temp_cursor_1758625423860.json +1 -0
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"Bash(jq:*)",
|
|
7
7
|
"Bash(FORCE_AVFOUNDATION=1 timeout 15 node test-cursor-position.js)",
|
|
8
8
|
"Bash(FORCE_AVFOUNDATION=1 timeout 8 node test-cursor-debug.js)",
|
|
9
|
-
"Bash(FORCE_AVFOUNDATION=1 timeout 5 node test-cursor-debug.js)"
|
|
9
|
+
"Bash(FORCE_AVFOUNDATION=1 timeout 5 node test-cursor-debug.js)",
|
|
10
|
+
"Bash(FORCE_AVFOUNDATION=1 timeout 8 node test-unified-cursor.js)",
|
|
11
|
+
"Bash(FORCE_AVFOUNDATION=1 timeout 5 node test-unified-cursor.js)"
|
|
10
12
|
],
|
|
11
13
|
"deny": [],
|
|
12
14
|
"ask": []
|
package/CLAUDE.md
CHANGED
|
@@ -131,20 +131,18 @@ The MacRecorder class emits the following events:
|
|
|
131
131
|
|
|
132
132
|
### Platform Requirements & Framework Selection
|
|
133
133
|
|
|
134
|
-
**
|
|
135
|
-
- **Primary Target**:
|
|
136
|
-
- **
|
|
137
|
-
- macOS only (
|
|
134
|
+
**SCREENCAPTUREKIT-FIRST ARCHITECTURE:**
|
|
135
|
+
- **Primary Target**: ScreenCaptureKit for consistent behavior across all environments
|
|
136
|
+
- **Support**: Both Electron.js applications and Node.js standalone applications
|
|
137
|
+
- macOS 12.3+ only (ScreenCaptureKit requirement)
|
|
138
138
|
- Native module compilation required on install
|
|
139
|
-
- Requires screen recording
|
|
139
|
+
- Requires screen recording permissions
|
|
140
140
|
|
|
141
|
-
**Framework Selection Logic (
|
|
142
|
-
- **macOS
|
|
143
|
-
- **macOS
|
|
144
|
-
- **macOS
|
|
145
|
-
- **
|
|
146
|
-
- **macOS 14 + Node.js**: AVFoundation with full capabilities
|
|
147
|
-
- **macOS 13 + Node.js**: AVFoundation with limited features
|
|
141
|
+
**Framework Selection Logic (ScreenCaptureKit Priority):**
|
|
142
|
+
- **macOS 12.3+ + Electron**: ScreenCaptureKit with full capabilities
|
|
143
|
+
- **macOS 12.3+ + Node.js**: ScreenCaptureKit with full capabilities
|
|
144
|
+
- **macOS < 12.3**: Not supported (requires macOS 12.3+ for ScreenCaptureKit)
|
|
145
|
+
- **AVFoundation**: Available only as fallback with FORCE_AVFOUNDATION environment variable
|
|
148
146
|
|
|
149
147
|
### File Outputs
|
|
150
148
|
|
|
@@ -179,14 +177,13 @@ The module tries loading from `build/Release/` first, then falls back to `build/
|
|
|
179
177
|
|
|
180
178
|
### **⚡ CRITICAL IMPLEMENTATION NOTES**
|
|
181
179
|
|
|
182
|
-
**
|
|
183
|
-
1. **
|
|
184
|
-
2. **
|
|
185
|
-
3. **Environment
|
|
186
|
-
4. **macOS Compatibility**:
|
|
187
|
-
- macOS
|
|
188
|
-
- macOS
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
**NEVER BLOCK ELECTRON ENVIRONMENTS** - This is a core design principle.
|
|
180
|
+
**SCREENCAPTUREKIT-FIRST DESIGN:**
|
|
181
|
+
1. **Unified Framework**: ScreenCaptureKit is used for all macOS 12.3+ environments (Electron + Node.js)
|
|
182
|
+
2. **Simplified Architecture**: Single framework reduces complexity and maintenance overhead
|
|
183
|
+
3. **Environment Agnostic**: Both Electron and Node.js use the same ScreenCaptureKit implementation
|
|
184
|
+
4. **macOS Compatibility**:
|
|
185
|
+
- macOS 12.3+ (Monterey+): Use ScreenCaptureKit for all features
|
|
186
|
+
- macOS < 12.3: Not supported (upgrade required)
|
|
187
|
+
5. **Fallback**: AVFoundation available only with FORCE_AVFOUNDATION environment variable
|
|
188
|
+
|
|
189
|
+
**CONSISTENT BEHAVIOR ACROSS ENVIRONMENTS** - Single framework ensures predictable results.
|
package/binding.gyp
CHANGED
package/index.js
CHANGED
|
@@ -252,12 +252,6 @@ 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: For now, use logical dimensions to avoid cross-display scaling issues
|
|
257
|
-
// This prevents using wrong display's physical dimensions
|
|
258
|
-
let displayScalingInfo = null;
|
|
259
|
-
// TODO: Implement proper per-display scaling detection in native layer
|
|
260
|
-
|
|
261
255
|
this.recordingDisplayInfo = {
|
|
262
256
|
displayId: targetDisplayId,
|
|
263
257
|
x: targetDisplay.x,
|
|
@@ -267,10 +261,6 @@ class MacRecorder extends EventEmitter {
|
|
|
267
261
|
// Add scaling information for cursor coordinate transformation
|
|
268
262
|
logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
|
|
269
263
|
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,
|
|
274
264
|
};
|
|
275
265
|
}
|
|
276
266
|
|
|
@@ -308,11 +298,6 @@ class MacRecorder extends EventEmitter {
|
|
|
308
298
|
}
|
|
309
299
|
|
|
310
300
|
if (targetDisplay) {
|
|
311
|
-
// CRITICAL FIX: For now, use logical dimensions to avoid cross-display scaling issues
|
|
312
|
-
// This prevents using wrong display's physical dimensions
|
|
313
|
-
const displayScalingInfo = null;
|
|
314
|
-
// TODO: Implement proper per-display scaling detection in native layer
|
|
315
|
-
|
|
316
301
|
this.recordingDisplayInfo = {
|
|
317
302
|
displayId: targetDisplay.id,
|
|
318
303
|
x: targetDisplay.x || 0,
|
|
@@ -322,10 +307,6 @@ class MacRecorder extends EventEmitter {
|
|
|
322
307
|
// Add scaling information for cursor coordinate transformation
|
|
323
308
|
logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
|
|
324
309
|
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,
|
|
329
310
|
};
|
|
330
311
|
}
|
|
331
312
|
} catch (error) {
|
|
@@ -705,8 +686,6 @@ class MacRecorder extends EventEmitter {
|
|
|
705
686
|
// Calculate video offset based on recording type
|
|
706
687
|
let videoOffsetX = 0;
|
|
707
688
|
let videoOffsetY = 0;
|
|
708
|
-
// UNIFIED CURSOR SYSTEM: Always use logical dimensions for cursor coordinates
|
|
709
|
-
// This ensures cursor tracking works consistently across all recording engines
|
|
710
689
|
let videoWidth = options.displayInfo.width || options.displayInfo.logicalWidth;
|
|
711
690
|
let videoHeight = options.displayInfo.height || options.displayInfo.logicalHeight;
|
|
712
691
|
|
|
@@ -733,19 +712,10 @@ class MacRecorder extends EventEmitter {
|
|
|
733
712
|
displayY: options.displayInfo.y || 0,
|
|
734
713
|
displayWidth: options.displayInfo.width || options.displayInfo.logicalWidth,
|
|
735
714
|
displayHeight: options.displayInfo.height || options.displayInfo.logicalHeight,
|
|
736
|
-
// CRITICAL: Add physical dimensions for proper cursor scaling
|
|
737
|
-
physicalWidth: options.displayInfo.physicalWidth || options.displayInfo.width || options.displayInfo.logicalWidth,
|
|
738
|
-
physicalHeight: options.displayInfo.physicalHeight || options.displayInfo.height || options.displayInfo.logicalHeight,
|
|
739
|
-
scaleFactor: options.displayInfo.scaleFactor || 1.0,
|
|
740
715
|
videoOffsetX: videoOffsetX,
|
|
741
716
|
videoOffsetY: videoOffsetY,
|
|
742
717
|
videoWidth: videoWidth,
|
|
743
718
|
videoHeight: videoHeight,
|
|
744
|
-
// CRITICAL: Video output size for proper cursor scaling
|
|
745
|
-
videoOutputWidth: (options.displayInfo.scaleFactor > 1.0) ?
|
|
746
|
-
(options.displayInfo.physicalWidth || videoWidth) : videoWidth,
|
|
747
|
-
videoOutputHeight: (options.displayInfo.scaleFactor > 1.0) ?
|
|
748
|
-
(options.displayInfo.physicalHeight || videoHeight) : videoHeight,
|
|
749
719
|
videoRelative: true,
|
|
750
720
|
recordingType: options.recordingType || 'display',
|
|
751
721
|
// Store additional context for debugging
|
|
@@ -760,22 +730,10 @@ class MacRecorder extends EventEmitter {
|
|
|
760
730
|
displayY: this.recordingDisplayInfo.y || 0,
|
|
761
731
|
displayWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
|
|
762
732
|
displayHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
|
|
763
|
-
// CRITICAL: Include physical dimensions from recording display info
|
|
764
|
-
physicalWidth: this.recordingDisplayInfo.physicalWidth || this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
|
|
765
|
-
physicalHeight: this.recordingDisplayInfo.physicalHeight || this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
|
|
766
|
-
scaleFactor: this.recordingDisplayInfo.scaleFactor || 1.0,
|
|
767
733
|
videoOffsetX: 0,
|
|
768
734
|
videoOffsetY: 0,
|
|
769
|
-
// UNIFIED CURSOR SYSTEM: Always use logical dimensions
|
|
770
735
|
videoWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
|
|
771
736
|
videoHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
|
|
772
|
-
// CRITICAL: Video output size for proper cursor scaling
|
|
773
|
-
videoOutputWidth: (this.recordingDisplayInfo.scaleFactor > 1.0) ?
|
|
774
|
-
(this.recordingDisplayInfo.physicalWidth || this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth) :
|
|
775
|
-
(this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth),
|
|
776
|
-
videoOutputHeight: (this.recordingDisplayInfo.scaleFactor > 1.0) ?
|
|
777
|
-
(this.recordingDisplayInfo.physicalHeight || this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight) :
|
|
778
|
-
(this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight),
|
|
779
737
|
videoRelative: true,
|
|
780
738
|
recordingType: options.recordingType || 'display'
|
|
781
739
|
};
|
|
@@ -823,23 +781,6 @@ class MacRecorder extends EventEmitter {
|
|
|
823
781
|
|
|
824
782
|
// Apply video-relative transformation for all recording types
|
|
825
783
|
if (this.cursorDisplayInfo && this.cursorDisplayInfo.videoRelative) {
|
|
826
|
-
// FILTER: Only record cursor when it's on the target display
|
|
827
|
-
// Use cached display info to avoid async calls
|
|
828
|
-
const displayX = this.cursorDisplayInfo.displayX;
|
|
829
|
-
const displayY = this.cursorDisplayInfo.displayY;
|
|
830
|
-
const displayWidth = this.cursorDisplayInfo.displayWidth;
|
|
831
|
-
const displayHeight = this.cursorDisplayInfo.displayHeight;
|
|
832
|
-
|
|
833
|
-
const onTargetDisplay = position.x >= displayX &&
|
|
834
|
-
position.x < (displayX + displayWidth) &&
|
|
835
|
-
position.y >= displayY &&
|
|
836
|
-
position.y < (displayY + displayHeight);
|
|
837
|
-
|
|
838
|
-
if (!onTargetDisplay) {
|
|
839
|
-
// Skip cursor data when cursor is on different display
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
784
|
// Step 1: Transform global → display-relative coordinates
|
|
844
785
|
const displayRelativeX = position.x - this.cursorDisplayInfo.displayX;
|
|
845
786
|
const displayRelativeY = position.y - this.cursorDisplayInfo.displayY;
|
|
@@ -847,31 +788,16 @@ class MacRecorder extends EventEmitter {
|
|
|
847
788
|
// Step 2: Transform display-relative → video-relative coordinates
|
|
848
789
|
x = displayRelativeX - this.cursorDisplayInfo.videoOffsetX;
|
|
849
790
|
y = displayRelativeY - this.cursorDisplayInfo.videoOffsetY;
|
|
791
|
+
coordinateSystem = "video-relative";
|
|
850
792
|
|
|
851
|
-
//
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (scaleX !== 1.0 || scaleY !== 1.0) {
|
|
857
|
-
x = x * scaleX;
|
|
858
|
-
y = y * scaleY;
|
|
859
|
-
coordinateSystem = "video-output-scaled";
|
|
860
|
-
} else {
|
|
861
|
-
coordinateSystem = "video-relative";
|
|
862
|
-
}
|
|
863
|
-
} else {
|
|
864
|
-
coordinateSystem = "video-relative";
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// Bounds check for video area using actual video output dimensions
|
|
868
|
-
const checkWidth = this.cursorDisplayInfo.videoOutputWidth || this.cursorDisplayInfo.videoWidth;
|
|
869
|
-
const checkHeight = this.cursorDisplayInfo.videoOutputHeight || this.cursorDisplayInfo.videoHeight;
|
|
870
|
-
const outsideVideo = x < 0 || y < 0 || x >= checkWidth || y >= checkHeight;
|
|
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;
|
|
871
797
|
|
|
872
798
|
// For debugging - add metadata if cursor is outside video area
|
|
873
799
|
if (outsideVideo) {
|
|
874
|
-
coordinateSystem =
|
|
800
|
+
coordinateSystem = "video-relative-outside";
|
|
875
801
|
}
|
|
876
802
|
}
|
|
877
803
|
|
|
@@ -889,19 +815,12 @@ class MacRecorder extends EventEmitter {
|
|
|
889
815
|
width: this.cursorDisplayInfo.videoWidth,
|
|
890
816
|
height: this.cursorDisplayInfo.videoHeight,
|
|
891
817
|
offsetX: this.cursorDisplayInfo.videoOffsetX,
|
|
892
|
-
offsetY: this.cursorDisplayInfo.videoOffsetY
|
|
893
|
-
// CRITICAL: Add physical dimensions for debugging
|
|
894
|
-
physicalWidth: this.cursorDisplayInfo.physicalWidth,
|
|
895
|
-
physicalHeight: this.cursorDisplayInfo.physicalHeight
|
|
818
|
+
offsetY: this.cursorDisplayInfo.videoOffsetY
|
|
896
819
|
} : null,
|
|
897
820
|
displayInfo: this.cursorDisplayInfo ? {
|
|
898
821
|
displayId: this.cursorDisplayInfo.displayId,
|
|
899
822
|
width: this.cursorDisplayInfo.displayWidth,
|
|
900
|
-
height: this.cursorDisplayInfo.displayHeight
|
|
901
|
-
// CRITICAL: Add scaling info for debugging AVFoundation cursor issues
|
|
902
|
-
physicalWidth: this.cursorDisplayInfo.physicalWidth,
|
|
903
|
-
physicalHeight: this.cursorDisplayInfo.physicalHeight,
|
|
904
|
-
scaleFactor: this.cursorDisplayInfo.scaleFactor
|
|
823
|
+
height: this.cursorDisplayInfo.displayHeight
|
|
905
824
|
} : null
|
|
906
825
|
};
|
|
907
826
|
|
|
@@ -954,13 +873,6 @@ class MacRecorder extends EventEmitter {
|
|
|
954
873
|
this.cursorCaptureFile = null;
|
|
955
874
|
}
|
|
956
875
|
|
|
957
|
-
// Native cursor tracking'i durdur
|
|
958
|
-
try {
|
|
959
|
-
nativeBinding.stopCursorTracking();
|
|
960
|
-
} catch (nativeError) {
|
|
961
|
-
console.warn('Native cursor tracking stop failed:', nativeError.message);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
876
|
// Değişkenleri temizle
|
|
965
877
|
this.lastCapturedData = null;
|
|
966
878
|
this.cursorCaptureStartTime = null;
|
package/package.json
CHANGED
|
@@ -6,83 +6,6 @@
|
|
|
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
|
-
|
|
86
9
|
static AVAssetWriter *g_avWriter = nil;
|
|
87
10
|
static AVAssetWriterInput *g_avVideoInput = nil;
|
|
88
11
|
static AVAssetWriterInputPixelBufferAdaptor *g_avPixelBufferAdaptor = nil;
|
|
@@ -130,35 +53,23 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
130
53
|
return false;
|
|
131
54
|
}
|
|
132
55
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
CGSize
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
}
|
|
56
|
+
// Get display dimensions with proper scaling for macOS 14/13 compatibility
|
|
57
|
+
CGRect displayBounds = CGDisplayBounds(displayID);
|
|
58
|
+
|
|
59
|
+
// Get both logical (bounds) and physical (pixels) dimensions
|
|
60
|
+
CGSize logicalSize = displayBounds.size;
|
|
61
|
+
CGSize physicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
|
|
62
|
+
|
|
63
|
+
// Calculate scale factor
|
|
64
|
+
CGFloat scaleX = physicalSize.width / logicalSize.width;
|
|
65
|
+
CGFloat scaleY = physicalSize.height / logicalSize.height;
|
|
66
|
+
CGFloat scaleFactor = MAX(scaleX, scaleY); // Use max to handle non-uniform scaling
|
|
67
|
+
|
|
68
|
+
// CRITICAL FIX: Use actual captured image dimensions for pixel buffer
|
|
69
|
+
// CGDisplayCreateImage returns physical pixels on Retina displays
|
|
70
|
+
CGImageRef testImage = CGDisplayCreateImage(displayID);
|
|
71
|
+
CGSize actualImageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
|
|
72
|
+
CGImageRelease(testImage);
|
|
162
73
|
|
|
163
74
|
// CRITICAL FIX: Use actual image dimensions to match what CGDisplayCreateImage returns
|
|
164
75
|
// This prevents the "1/4 recording area" bug on Retina displays
|
|
@@ -174,18 +85,18 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
174
85
|
recordingSize = actualImageSize;
|
|
175
86
|
}
|
|
176
87
|
|
|
177
|
-
NSLog(@"🎯
|
|
88
|
+
NSLog(@"🎯 CRITICAL: Logical %.0fx%.0f → Actual image %.0fx%.0f",
|
|
178
89
|
logicalSize.width, logicalSize.height, actualImageSize.width, actualImageSize.height);
|
|
179
|
-
|
|
90
|
+
|
|
180
91
|
NSLog(@"🖥️ Display bounds (logical): %.0fx%.0f", logicalSize.width, logicalSize.height);
|
|
181
|
-
NSLog(@"🖥️ Display
|
|
182
|
-
|
|
92
|
+
NSLog(@"🖥️ Display pixels (physical): %.0fx%.0f", physicalSize.width, physicalSize.height);
|
|
93
|
+
|
|
183
94
|
if (scaleFactor > 1.5) {
|
|
184
|
-
NSLog(@"🔍
|
|
95
|
+
NSLog(@"🔍 Scale factor: %.1fx → Retina display detected (macOS 14/13 scaling fix applied)", scaleFactor);
|
|
185
96
|
} else if (scaleFactor > 1.1) {
|
|
186
|
-
NSLog(@"🔍
|
|
97
|
+
NSLog(@"🔍 Scale factor: %.1fx → Non-standard scaling detected", scaleFactor);
|
|
187
98
|
} else {
|
|
188
|
-
NSLog(@"🔍
|
|
99
|
+
NSLog(@"🔍 Scale factor: %.1fx → Standard display", scaleFactor);
|
|
189
100
|
}
|
|
190
101
|
|
|
191
102
|
NSLog(@"🎯 Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|
package/src/mac_recorder.mm
CHANGED
|
@@ -192,27 +192,25 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
192
192
|
|
|
193
193
|
// Check macOS version for ScreenCaptureKit compatibility
|
|
194
194
|
NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
|
|
195
|
-
BOOL
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
NSLog(@"🖥️ macOS Version: %ld.%ld.%ld",
|
|
195
|
+
BOOL isM12_3Plus = (osVersion.majorVersion > 12) ||
|
|
196
|
+
(osVersion.majorVersion == 12 && osVersion.minorVersion >= 3);
|
|
197
|
+
|
|
198
|
+
NSLog(@"🖥️ macOS Version: %ld.%ld.%ld",
|
|
200
199
|
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
201
|
-
|
|
202
|
-
// Force AVFoundation for debugging/testing
|
|
200
|
+
|
|
201
|
+
// Force AVFoundation for debugging/testing (still available but discouraged)
|
|
203
202
|
BOOL forceAVFoundation = (getenv("FORCE_AVFOUNDATION") != NULL);
|
|
204
203
|
if (forceAVFoundation) {
|
|
205
|
-
NSLog(@"🔧 FORCE_AVFOUNDATION environment variable detected -
|
|
204
|
+
NSLog(@"🔧 FORCE_AVFOUNDATION environment variable detected - will fallback to AVFoundation");
|
|
206
205
|
}
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
if (isM15Plus && !forceAVFoundation) {
|
|
206
|
+
|
|
207
|
+
// ScreenCaptureKit-first approach: Use ScreenCaptureKit for macOS 12.3+
|
|
208
|
+
// This simplifies the codebase and provides consistent behavior
|
|
209
|
+
if (isM12_3Plus && !forceAVFoundation) {
|
|
212
210
|
if (isElectron) {
|
|
213
|
-
NSLog(@"⚡ ELECTRON
|
|
211
|
+
NSLog(@"⚡ ELECTRON: macOS 12.3+ Electron → ScreenCaptureKit with full support");
|
|
214
212
|
} else {
|
|
215
|
-
NSLog(@"✅ macOS
|
|
213
|
+
NSLog(@"✅ macOS 12.3+ Node.js → ScreenCaptureKit available with full compatibility");
|
|
216
214
|
}
|
|
217
215
|
|
|
218
216
|
// Try ScreenCaptureKit with extensive safety measures
|
|
@@ -287,65 +285,53 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
287
285
|
// If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
|
|
288
286
|
NSLog(@"⏭️ ScreenCaptureKit failed - falling back to AVFoundation");
|
|
289
287
|
} else {
|
|
290
|
-
// macOS
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
} else if (isM13Plus) {
|
|
295
|
-
NSLog(@"⚡ ELECTRON PRIORITY: macOS 13 Electron → AVFoundation with limited features");
|
|
296
|
-
}
|
|
297
|
-
} else {
|
|
298
|
-
if (isM15Plus) {
|
|
299
|
-
NSLog(@"🎯 macOS 15+ Node.js with FORCE_AVFOUNDATION → using AVFoundation");
|
|
300
|
-
} else if (isM14Plus) {
|
|
301
|
-
NSLog(@"🎯 macOS 14 Node.js → using AVFoundation (primary method)");
|
|
302
|
-
} else if (isM13Plus) {
|
|
303
|
-
NSLog(@"🎯 macOS 13 Node.js → using AVFoundation (limited features)");
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (!isM13Plus) {
|
|
308
|
-
NSLog(@"❌ macOS version too old (< 13.0) - Not supported");
|
|
288
|
+
// macOS < 12.3 or forced AVFoundation → Use AVFoundation as fallback
|
|
289
|
+
if (!isM12_3Plus) {
|
|
290
|
+
NSLog(@"❌ macOS version too old (< 12.3) - ScreenCaptureKit not supported");
|
|
291
|
+
NSLog(@"❌ This version requires macOS 12.3+ for ScreenCaptureKit");
|
|
309
292
|
return Napi::Boolean::New(env, false);
|
|
310
293
|
}
|
|
311
|
-
|
|
312
|
-
//
|
|
313
|
-
NSLog(@"⏭️ Using AVFoundation
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
317
|
-
NSLog(@"🎥 Starting AVFoundation recording...");
|
|
318
|
-
|
|
319
|
-
@try {
|
|
320
|
-
// Import AVFoundation recording functions (if available)
|
|
321
|
-
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
322
|
-
CGDirectDisplayID displayID,
|
|
323
|
-
uint32_t windowID,
|
|
324
|
-
CGRect captureRect,
|
|
325
|
-
bool captureCursor,
|
|
326
|
-
bool includeMicrophone,
|
|
327
|
-
bool includeSystemAudio,
|
|
328
|
-
NSString* audioDeviceId);
|
|
329
|
-
|
|
330
|
-
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
331
|
-
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId);
|
|
332
|
-
|
|
333
|
-
if (avResult) {
|
|
334
|
-
NSLog(@"🎥 RECORDING METHOD: AVFoundation");
|
|
335
|
-
NSLog(@"✅ AVFoundation recording started successfully");
|
|
336
|
-
g_isRecording = true;
|
|
337
|
-
return Napi::Boolean::New(env, true);
|
|
338
|
-
} else {
|
|
339
|
-
NSLog(@"❌ AVFoundation recording failed to start");
|
|
340
|
-
NSLog(@"❌ Check permissions and output path validity");
|
|
341
|
-
}
|
|
342
|
-
} @catch (NSException *avException) {
|
|
343
|
-
NSLog(@"❌ Exception during AVFoundation startup: %@", avException.reason);
|
|
344
|
-
NSLog(@"❌ Stack trace: %@", [avException callStackSymbols]);
|
|
294
|
+
|
|
295
|
+
// Only reach here if FORCE_AVFOUNDATION is set
|
|
296
|
+
NSLog(@"⏭️ Using AVFoundation fallback (FORCE_AVFOUNDATION enabled)");
|
|
345
297
|
}
|
|
346
298
|
|
|
347
|
-
//
|
|
348
|
-
|
|
299
|
+
// AVFoundation recording (fallback only when FORCE_AVFOUNDATION is set)
|
|
300
|
+
if (forceAVFoundation) {
|
|
301
|
+
NSLog(@"🎥 Starting AVFoundation recording (forced fallback)...");
|
|
302
|
+
|
|
303
|
+
@try {
|
|
304
|
+
// Import AVFoundation recording functions (if available)
|
|
305
|
+
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
306
|
+
CGDirectDisplayID displayID,
|
|
307
|
+
uint32_t windowID,
|
|
308
|
+
CGRect captureRect,
|
|
309
|
+
bool captureCursor,
|
|
310
|
+
bool includeMicrophone,
|
|
311
|
+
bool includeSystemAudio,
|
|
312
|
+
NSString* audioDeviceId);
|
|
313
|
+
|
|
314
|
+
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
315
|
+
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId);
|
|
316
|
+
|
|
317
|
+
if (avResult) {
|
|
318
|
+
NSLog(@"🎥 RECORDING METHOD: AVFoundation (fallback)");
|
|
319
|
+
NSLog(@"✅ AVFoundation recording started successfully");
|
|
320
|
+
g_isRecording = true;
|
|
321
|
+
return Napi::Boolean::New(env, true);
|
|
322
|
+
} else {
|
|
323
|
+
NSLog(@"❌ AVFoundation recording failed to start");
|
|
324
|
+
NSLog(@"❌ Check permissions and output path validity");
|
|
325
|
+
}
|
|
326
|
+
} @catch (NSException *avException) {
|
|
327
|
+
NSLog(@"❌ Exception during AVFoundation startup: %@", avException.reason);
|
|
328
|
+
NSLog(@"❌ Stack trace: %@", [avException callStackSymbols]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ScreenCaptureKit failed and no fallback available
|
|
333
|
+
NSLog(@"❌ ScreenCaptureKit recording failed - no recording available");
|
|
334
|
+
NSLog(@"💡 Ensure macOS 12.3+ and screen recording permissions are granted");
|
|
349
335
|
return Napi::Boolean::New(env, false);
|
|
350
336
|
|
|
351
337
|
} @catch (NSException *exception) {
|
|
@@ -890,27 +876,32 @@ Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
|
|
|
890
876
|
|
|
891
877
|
// Check for force AVFoundation flag
|
|
892
878
|
BOOL forceAVFoundation = (getenv("FORCE_AVFOUNDATION") != NULL);
|
|
893
|
-
|
|
879
|
+
|
|
880
|
+
// Check macOS version for ScreenCaptureKit compatibility
|
|
881
|
+
BOOL isM12_3Plus = (osVersion.majorVersion > 12) ||
|
|
882
|
+
(osVersion.majorVersion == 12 && osVersion.minorVersion >= 3);
|
|
883
|
+
|
|
894
884
|
// Electron detection
|
|
895
|
-
BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
|
|
885
|
+
BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
|
|
896
886
|
[NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
|
|
897
|
-
(NSProcessInfo.processInfo.processName &&
|
|
887
|
+
(NSProcessInfo.processInfo.processName &&
|
|
898
888
|
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
899
889
|
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
|
|
900
|
-
|
|
901
|
-
NSLog(@"🔒 Permission check for macOS %ld.%ld.%ld",
|
|
890
|
+
|
|
891
|
+
NSLog(@"🔒 Permission check for macOS %ld.%ld.%ld",
|
|
902
892
|
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
903
|
-
|
|
904
|
-
// Determine which framework will be used (
|
|
905
|
-
BOOL willUseScreenCaptureKit = (
|
|
906
|
-
BOOL willUseAVFoundation = (!willUseScreenCaptureKit &&
|
|
893
|
+
|
|
894
|
+
// Determine which framework will be used (ScreenCaptureKit-first approach)
|
|
895
|
+
BOOL willUseScreenCaptureKit = (isM12_3Plus && !forceAVFoundation); // ScreenCaptureKit for macOS 12.3+
|
|
896
|
+
BOOL willUseAVFoundation = (!willUseScreenCaptureKit && forceAVFoundation);
|
|
907
897
|
|
|
908
898
|
if (willUseScreenCaptureKit) {
|
|
909
899
|
NSLog(@"🎯 Will use ScreenCaptureKit - checking ScreenCaptureKit permissions");
|
|
910
900
|
} else if (willUseAVFoundation) {
|
|
911
|
-
NSLog(@"🎯 Will use AVFoundation - checking AVFoundation permissions");
|
|
901
|
+
NSLog(@"🎯 Will use AVFoundation fallback - checking AVFoundation permissions");
|
|
912
902
|
} else {
|
|
913
|
-
NSLog(@"❌
|
|
903
|
+
NSLog(@"❌ macOS version < 12.3 - ScreenCaptureKit not supported");
|
|
904
|
+
NSLog(@"💡 This version requires macOS 12.3+ for ScreenCaptureKit");
|
|
914
905
|
return Napi::Boolean::New(env, false);
|
|
915
906
|
}
|
|
916
907
|
|
package/src/screen_capture_kit.h
CHANGED
|
@@ -297,11 +297,9 @@ static NSString *g_outputPath = nil;
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
300
|
+
if (!g_recordingOutput) {
|
|
301
|
+
NSLog(@"❌ Failed to create SCRecordingOutput");
|
|
302
|
+
return;
|
|
305
303
|
}
|
|
306
304
|
|
|
307
305
|
NSLog(@"✅ Pure ScreenCaptureKit recording output created");
|
|
@@ -391,11 +389,9 @@ static NSString *g_outputPath = nil;
|
|
|
391
389
|
g_isCleaningUp = YES;
|
|
392
390
|
g_isRecording = NO;
|
|
393
391
|
|
|
394
|
-
if (
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
NSLog(@"✅ Pure recording output finalized");
|
|
398
|
-
}
|
|
392
|
+
if (g_recordingOutput) {
|
|
393
|
+
// SCRecordingOutput finalizes automatically
|
|
394
|
+
NSLog(@"✅ Pure recording output finalized");
|
|
399
395
|
}
|
|
400
396
|
|
|
401
397
|
[ScreenCaptureKitRecorder cleanupVideoWriter];
|
|
@@ -417,11 +413,9 @@ static NSString *g_outputPath = nil;
|
|
|
417
413
|
NSLog(@"✅ Stream reference cleared");
|
|
418
414
|
}
|
|
419
415
|
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
NSLog(@"✅ Recording output reference cleared");
|
|
424
|
-
}
|
|
416
|
+
if (g_recordingOutput) {
|
|
417
|
+
g_recordingOutput = nil;
|
|
418
|
+
NSLog(@"✅ Recording output reference cleared");
|
|
425
419
|
}
|
|
426
420
|
|
|
427
421
|
if (g_streamDelegate) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"x":-2062,"y":527,"timestamp":217,"unixTimeMs":1758625424081,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2028,"y":513,"timestamp":236,"unixTimeMs":1758625424100,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2025,"y":512,"timestamp":318,"unixTimeMs":1758625424182,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2020,"y":510,"timestamp":336,"unixTimeMs":1758625424200,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2012,"y":508,"timestamp":356,"unixTimeMs":1758625424220,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2009,"y":507,"timestamp":377,"unixTimeMs":1758625424241,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2004,"y":505,"timestamp":398,"unixTimeMs":1758625424262,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2001,"y":504,"timestamp":421,"unixTimeMs":1758625424285,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-1999,"y":504,"timestamp":464,"unixTimeMs":1758625424328,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2010,"y":506,"timestamp":869,"unixTimeMs":1758625424733,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2022,"y":508,"timestamp":894,"unixTimeMs":1758625424758,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2048,"y":508,"timestamp":912,"unixTimeMs":1758625424776,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2103,"y":495,"timestamp":934,"unixTimeMs":1758625424798,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2158,"y":478,"timestamp":954,"unixTimeMs":1758625424818,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2254,"y":432,"timestamp":971,"unixTimeMs":1758625424835,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2293,"y":397,"timestamp":997,"unixTimeMs":1758625424861,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2359,"y":329,"timestamp":1015,"unixTimeMs":1758625424879,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2445,"y":239,"timestamp":1036,"unixTimeMs":1758625424900,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2468,"y":214,"timestamp":1054,"unixTimeMs":1758625424918,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2478,"y":198,"timestamp":1076,"unixTimeMs":1758625424940,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2494,"y":173,"timestamp":1097,"unixTimeMs":1758625424961,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2510,"y":155,"timestamp":1120,"unixTimeMs":1758625424984,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2529,"y":139,"timestamp":1139,"unixTimeMs":1758625425003,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2539,"y":128,"timestamp":1158,"unixTimeMs":1758625425022,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2541,"y":121,"timestamp":1177,"unixTimeMs":1758625425041,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2538,"y":110,"timestamp":1202,"unixTimeMs":1758625425066,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2535,"y":101,"timestamp":1228,"unixTimeMs":1758625425092,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2533,"y":98,"timestamp":1248,"unixTimeMs":1758625425112,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2517,"y":97,"timestamp":1308,"unixTimeMs":1758625425172,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2498,"y":97,"timestamp":1335,"unixTimeMs":1758625425199,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2491,"y":98,"timestamp":1358,"unixTimeMs":1758625425222,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2488,"y":99,"timestamp":1384,"unixTimeMs":1758625425248,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1530,"unixTimeMs":1758625425394,"cursorType":"default","type":"mousedown","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1547,"unixTimeMs":1758625425411,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1588,"unixTimeMs":1758625425452,"cursorType":"default","type":"mouseup","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1607,"unixTimeMs":1758625425471,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1670,"unixTimeMs":1758625425534,"cursorType":"default","type":"mousedown","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1695,"unixTimeMs":1758625425559,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1734,"unixTimeMs":1758625425598,"cursorType":"default","type":"mouseup","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2487,"y":99,"timestamp":1754,"unixTimeMs":1758625425618,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2483,"y":101,"timestamp":2675,"unixTimeMs":1758625426539,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2475,"y":105,"timestamp":2694,"unixTimeMs":1758625426558,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2470,"y":108,"timestamp":2715,"unixTimeMs":1758625426579,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2461,"y":113,"timestamp":2737,"unixTimeMs":1758625426601,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2430,"y":127,"timestamp":2765,"unixTimeMs":1758625426629,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2384,"y":148,"timestamp":2808,"unixTimeMs":1758625426672,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2233,"y":201,"timestamp":2839,"unixTimeMs":1758625426703,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-2074,"y":243,"timestamp":2858,"unixTimeMs":1758625426722,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-1985,"y":265,"timestamp":2878,"unixTimeMs":1758625426742,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-1942,"y":282,"timestamp":2899,"unixTimeMs":1758625426763,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-1920,"y":296,"timestamp":2918,"unixTimeMs":1758625426782,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-1916,"y":301,"timestamp":2937,"unixTimeMs":1758625426801,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}},{"x":-1913,"y":303,"timestamp":2958,"unixTimeMs":1758625426822,"cursorType":"default","type":"move","coordinateSystem":"video-relative-outside","recordingType":"display","videoInfo":{"width":2048,"height":1330,"offsetX":0,"offsetY":0},"displayInfo":{"displayId":1,"width":2048,"height":1330}}]
|