node-mac-recorder 2.22.34 → 2.23.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 +7 -1
- package/AGENTS.md +192 -0
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +40 -20
- package/src/mac_recorder.mm +6 -3
- package/src/screen_capture_kit.mm +11 -18
|
@@ -50,7 +50,13 @@
|
|
|
50
50
|
"Bash(ffmpeg:*)",
|
|
51
51
|
"Bash(timeout 30 node:*)",
|
|
52
52
|
"Bash(MAC_RECORDER_DEBUG=1 node test-camera-audio-sync.js:*)",
|
|
53
|
-
"WebSearch"
|
|
53
|
+
"WebSearch",
|
|
54
|
+
"mcp__plugin_context-mode_context-mode__ctx_execute_file",
|
|
55
|
+
"mcp__plugin_context-mode_context-mode__ctx_execute",
|
|
56
|
+
"mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
|
57
|
+
"Bash(nm -D /System/Library/Frameworks/AppKit.framework/AppKit)",
|
|
58
|
+
"Bash(otool -L /System/Library/Frameworks/AppKit.framework/AppKit)",
|
|
59
|
+
"Bash(mdfind -name CoreCursor)"
|
|
54
60
|
],
|
|
55
61
|
"deny": [],
|
|
56
62
|
"ask": []
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
**ELECTRON-FIRST PRIORITY**: node-mac-recorder is a Node.js native addon specifically designed and optimized for Electron.js applications. It provides macOS screen recording capabilities using ScreenCaptureKit (macOS 15+) and AVFoundation (macOS 13+). The package allows recording of full screens, specific windows, or custom areas, with support for multi-display setups, audio capture, and cursor tracking.
|
|
8
|
+
|
|
9
|
+
### **🚀 CRITICAL: Electron.js Support**
|
|
10
|
+
This module is **PRIMARILY BUILT FOR ELECTRON.JS**. All features work seamlessly in Electron applications without any restrictions. Both ScreenCaptureKit and AVFoundation are fully supported in Electron environments.
|
|
11
|
+
|
|
12
|
+
## Build System & Commands
|
|
13
|
+
|
|
14
|
+
### Building the Native Module
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run build # Build the native module using node-gyp
|
|
18
|
+
npm run rebuild # Clean rebuild of the native module
|
|
19
|
+
npm run clean # Clean build artifacts
|
|
20
|
+
npm install # Runs install.js which builds the module automatically
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Testing
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm test # Run the main test suite (test.js)
|
|
27
|
+
node cursor-test.js # Test cursor tracking functionality only
|
|
28
|
+
node test.js # Run comprehensive API tests
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Development
|
|
32
|
+
|
|
33
|
+
The package uses node-gyp for building the native C++/Objective-C module. Requires:
|
|
34
|
+
|
|
35
|
+
- macOS 10.15+ (Catalina or later)
|
|
36
|
+
- Xcode Command Line Tools
|
|
37
|
+
- Node.js 14+
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
### Core Components
|
|
42
|
+
|
|
43
|
+
**Main Entry Point**
|
|
44
|
+
|
|
45
|
+
- `index.js` - Main MacRecorder class (EventEmitter-based)
|
|
46
|
+
- Handles all high-level recording operations and coordinate transformations
|
|
47
|
+
|
|
48
|
+
**Native Module** (`src/`)
|
|
49
|
+
|
|
50
|
+
- `mac_recorder.mm` - Main native module entry point and N-API bindings
|
|
51
|
+
- `screen_capture.mm` - AVFoundation-based screen/window recording
|
|
52
|
+
- `audio_capture.mm` - Audio device enumeration and capture
|
|
53
|
+
- `cursor_tracker.mm` - Real-time cursor position and event tracking
|
|
54
|
+
|
|
55
|
+
**Build Configuration**
|
|
56
|
+
|
|
57
|
+
- `binding.gyp` - Native module build configuration
|
|
58
|
+
- Links against AVFoundation, ScreenCaptureKit, AppKit, and other macOS frameworks
|
|
59
|
+
|
|
60
|
+
### Key Features
|
|
61
|
+
|
|
62
|
+
1. **Multi-Display Support**: Automatic display detection and coordinate conversion
|
|
63
|
+
2. **Window Recording**: Smart window detection with thumbnail generation
|
|
64
|
+
3. **Audio Control**: Separate microphone and system audio controls with device selection
|
|
65
|
+
4. **Cursor Tracking**: Real-time cursor position, type, and click event capture
|
|
66
|
+
5. **Permission Management**: Built-in macOS permission checking and requesting
|
|
67
|
+
|
|
68
|
+
### Coordinate System Handling
|
|
69
|
+
|
|
70
|
+
The package handles complex multi-display coordinate transformations:
|
|
71
|
+
|
|
72
|
+
- Global macOS coordinates (can be negative for secondary displays)
|
|
73
|
+
- Display-relative coordinates (always positive, 0-based)
|
|
74
|
+
- Automatic window-to-display mapping for recording
|
|
75
|
+
|
|
76
|
+
## API Structure
|
|
77
|
+
|
|
78
|
+
### Main Class Methods
|
|
79
|
+
|
|
80
|
+
- `startRecording(outputPath, options)` - Begin screen/window recording
|
|
81
|
+
- `stopRecording()` - Stop recording and finalize video file
|
|
82
|
+
- `getWindows()` - List all recordable application windows
|
|
83
|
+
- `getDisplays()` - Get all available displays with metadata
|
|
84
|
+
- `getAudioDevices()` - Enumerate available audio input devices
|
|
85
|
+
- `checkPermissions()` - Verify macOS recording permissions
|
|
86
|
+
|
|
87
|
+
### Cursor Tracking
|
|
88
|
+
|
|
89
|
+
- `startCursorCapture(filepath, options)` - Begin real-time cursor tracking to JSON
|
|
90
|
+
- `options.windowInfo` - Window information for window-relative coordinates
|
|
91
|
+
- `options.windowRelative` - Set to true for window-relative coordinates
|
|
92
|
+
- `stopCursorCapture()` - Stop tracking and close output file
|
|
93
|
+
- `getCursorPosition()` - Get current cursor position and state
|
|
94
|
+
|
|
95
|
+
### Events
|
|
96
|
+
|
|
97
|
+
The MacRecorder class emits the following events:
|
|
98
|
+
|
|
99
|
+
- `recordingStarted` - Emitted immediately when recording starts with recording details
|
|
100
|
+
- `started` - Emitted when recording is confirmed started (legacy event)
|
|
101
|
+
- `stopped` - Emitted when recording stops
|
|
102
|
+
- `completed` - Emitted when recording file is finalized
|
|
103
|
+
- `timeUpdate` - Emitted every second with elapsed time
|
|
104
|
+
- `cursorCaptureStarted` - Emitted when cursor capture begins
|
|
105
|
+
- `cursorCaptureStopped` - Emitted when cursor capture ends
|
|
106
|
+
|
|
107
|
+
### Thumbnails
|
|
108
|
+
|
|
109
|
+
- `getWindowThumbnail(windowId, options)` - Capture window preview image
|
|
110
|
+
- `getDisplayThumbnail(displayId, options)` - Capture display preview image
|
|
111
|
+
|
|
112
|
+
## Development Notes
|
|
113
|
+
|
|
114
|
+
### Testing Strategy
|
|
115
|
+
|
|
116
|
+
- Use `npm test` for full API validation
|
|
117
|
+
- `cursor-test.js` for testing cursor tracking specifically
|
|
118
|
+
- Test files create output in `test-output/` directory
|
|
119
|
+
|
|
120
|
+
### Common Development Patterns
|
|
121
|
+
|
|
122
|
+
- All recording operations are Promise-based
|
|
123
|
+
- Event emission for recording state changes (`recordingStarted`, `started`, `stopped`, `completed`)
|
|
124
|
+
- `recordingStarted` event provides immediate notification with recording details
|
|
125
|
+
- Automatic permission checking before operations
|
|
126
|
+
- Error handling with descriptive messages for permission issues
|
|
127
|
+
- Cursor tracking supports multiple coordinate systems:
|
|
128
|
+
- Global coordinates (default)
|
|
129
|
+
- Display-relative coordinates (when recording)
|
|
130
|
+
- Window-relative coordinates (with windowInfo parameter)
|
|
131
|
+
|
|
132
|
+
### Platform Requirements & Framework Selection
|
|
133
|
+
|
|
134
|
+
**ELECTRON-FIRST ARCHITECTURE:**
|
|
135
|
+
- **Primary Target**: Electron.js applications (full support, no restrictions)
|
|
136
|
+
- **Secondary**: Node.js standalone applications
|
|
137
|
+
- macOS only (enforced in install.js)
|
|
138
|
+
- Native module compilation required on install
|
|
139
|
+
- Requires screen recording and accessibility permissions
|
|
140
|
+
|
|
141
|
+
**Framework Selection Logic (Electron Priority):**
|
|
142
|
+
- **macOS 15+ + Electron**: ScreenCaptureKit with full capabilities
|
|
143
|
+
- **macOS 15+ + Node.js**: ScreenCaptureKit with full capabilities
|
|
144
|
+
- **macOS 14 + Electron**: AVFoundation with full capabilities
|
|
145
|
+
- **macOS 13 + Electron**: AVFoundation with limited features
|
|
146
|
+
- **macOS 14 + Node.js**: AVFoundation with full capabilities
|
|
147
|
+
- **macOS 13 + Node.js**: AVFoundation with limited features
|
|
148
|
+
|
|
149
|
+
### File Outputs
|
|
150
|
+
|
|
151
|
+
- Video recordings: `.mov` format (H.264/AAC)
|
|
152
|
+
- Cursor data: JSON format with timestamped events
|
|
153
|
+
- `x`, `y`: Cursor coordinates (coordinate system dependent)
|
|
154
|
+
- `timestamp`: Time from capture start (ms)
|
|
155
|
+
- `unixTimeMs`: Unix timestamp
|
|
156
|
+
- `cursorType`: macOS cursor type
|
|
157
|
+
- `type`: Event type (move, click, etc.)
|
|
158
|
+
- `coordinateSystem`: "global", "display-relative", or "window-relative"
|
|
159
|
+
- `windowInfo`: Window metadata (when using window-relative coordinates)
|
|
160
|
+
- Thumbnails: Base64-encoded PNG data URIs
|
|
161
|
+
|
|
162
|
+
## Troubleshooting
|
|
163
|
+
|
|
164
|
+
### Build Issues
|
|
165
|
+
|
|
166
|
+
1. Ensure Xcode Command Line Tools: `xcode-select --install`
|
|
167
|
+
2. Clean rebuild: `npm run clean && npm run build`
|
|
168
|
+
3. Check Node.js version compatibility (14+)
|
|
169
|
+
|
|
170
|
+
### Runtime Issues
|
|
171
|
+
|
|
172
|
+
1. Permission failures: Check System Preferences > Security & Privacy
|
|
173
|
+
2. Recording failures: Verify target windows/displays are accessible
|
|
174
|
+
3. Audio issues: Check audio device availability and permissions
|
|
175
|
+
|
|
176
|
+
### Native Module Loading
|
|
177
|
+
|
|
178
|
+
The module tries loading from `build/Release/` first, then falls back to `build/Debug/` with helpful error messages if neither exists.
|
|
179
|
+
|
|
180
|
+
### **⚡ CRITICAL IMPLEMENTATION NOTES**
|
|
181
|
+
|
|
182
|
+
**ELECTRON.JS IS THE PRIMARY TARGET:**
|
|
183
|
+
1. **No Electron Restrictions**: All Electron detection logic is for optimization, NOT blocking
|
|
184
|
+
2. **Framework Support**: Both ScreenCaptureKit and AVFoundation work perfectly in Electron
|
|
185
|
+
3. **Environment Detection**: Code detects Electron to provide enhanced logging and optimization
|
|
186
|
+
4. **macOS Compatibility**:
|
|
187
|
+
- macOS 15+ (Sequoia+): Use ScreenCaptureKit for best performance
|
|
188
|
+
- macOS 14 (Sonoma): Use AVFoundation with full feature set
|
|
189
|
+
- macOS 13 (Ventura): Use AVFoundation with basic features
|
|
190
|
+
5. **Error Handling**: Any "Recording failed to start" errors should trigger fallback logic, never blocking
|
|
191
|
+
|
|
192
|
+
**NEVER BLOCK ELECTRON ENVIRONMENTS** - This is a core design principle.
|
package/package.json
CHANGED
|
@@ -25,6 +25,7 @@ static int64_t g_avFrameNumber = 0;
|
|
|
25
25
|
static CMTime g_avStartTime;
|
|
26
26
|
static void* g_avAudioRecorder = nil;
|
|
27
27
|
static NSString* g_avAudioOutputPath = nil;
|
|
28
|
+
static const NSInteger kAVFoundationHighQualityVideoBitrate = 50 * 1000 * 1000;
|
|
28
29
|
|
|
29
30
|
// AVFoundation screen recording implementation
|
|
30
31
|
extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
@@ -36,7 +37,8 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
36
37
|
bool includeSystemAudio,
|
|
37
38
|
NSString* audioDeviceId,
|
|
38
39
|
NSString* audioOutputPath,
|
|
39
|
-
double requestedFrameRate
|
|
40
|
+
double requestedFrameRate,
|
|
41
|
+
NSString* qualityPreset) {
|
|
40
42
|
|
|
41
43
|
if (g_avIsRecording) {
|
|
42
44
|
NSLog(@"❌ AVFoundation recording already in progress");
|
|
@@ -123,27 +125,47 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
123
125
|
|
|
124
126
|
MRLog(@"🎯 Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
NSString
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
NSString *normalizedQuality = @"high";
|
|
129
|
+
if ([qualityPreset isKindOfClass:[NSString class]] && qualityPreset.length > 0) {
|
|
130
|
+
normalizedQuality = [qualityPreset lowercaseString];
|
|
131
|
+
if (![normalizedQuality isEqualToString:@"low"] &&
|
|
132
|
+
![normalizedQuality isEqualToString:@"medium"] &&
|
|
133
|
+
![normalizedQuality isEqualToString:@"high"]) {
|
|
134
|
+
normalizedQuality = @"high";
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
|
-
|
|
135
|
-
NSInteger bitrate = (NSInteger)(recordingSize.width * recordingSize.height * 100);
|
|
136
|
-
bitrate = MAX(bitrate, 120 * 1000 * 1000);
|
|
137
|
-
bitrate = MIN(bitrate, 500 * 1000 * 1000);
|
|
138
|
-
|
|
139
|
-
NSLog(@"🎬 ULTRA QUALITY AVFoundation: %dx%d, bitrate=%.2fMbps",
|
|
140
|
-
(int)recordingSize.width, (int)recordingSize.height, bitrate / (1000.0 * 1000.0));
|
|
141
|
-
|
|
142
|
-
// Resolve target FPS
|
|
143
137
|
double fps = requestedFrameRate > 0 ? requestedFrameRate : 60.0;
|
|
144
138
|
if (fps < 1.0) fps = 1.0;
|
|
145
139
|
if (fps > 120.0) fps = 120.0;
|
|
146
140
|
|
|
141
|
+
NSString *codecKey = AVVideoCodecTypeH264;
|
|
142
|
+
NSInteger bitrate = kAVFoundationHighQualityVideoBitrate;
|
|
143
|
+
NSNumber *qualityHint = @1.0;
|
|
144
|
+
|
|
145
|
+
if ([normalizedQuality isEqualToString:@"low"]) {
|
|
146
|
+
NSInteger multiplier = 10;
|
|
147
|
+
NSInteger minBitrate = 10 * 1000 * 1000;
|
|
148
|
+
NSInteger maxBitrate = 45 * 1000 * 1000;
|
|
149
|
+
bitrate = (NSInteger)(recordingSize.width * recordingSize.height * multiplier);
|
|
150
|
+
bitrate = MAX(bitrate, minBitrate);
|
|
151
|
+
bitrate = MIN(bitrate, maxBitrate);
|
|
152
|
+
qualityHint = @0.85;
|
|
153
|
+
} else if ([normalizedQuality isEqualToString:@"medium"]) {
|
|
154
|
+
NSInteger multiplier = 18;
|
|
155
|
+
NSInteger minBitrate = 18 * 1000 * 1000;
|
|
156
|
+
NSInteger maxBitrate = 80 * 1000 * 1000;
|
|
157
|
+
bitrate = (NSInteger)(recordingSize.width * recordingSize.height * multiplier);
|
|
158
|
+
bitrate = MAX(bitrate, minBitrate);
|
|
159
|
+
bitrate = MIN(bitrate, maxBitrate);
|
|
160
|
+
qualityHint = @0.9;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
MRLog(@"🎬 AVFoundation encoder (%@): %dx%d, codec=H.264 High Profile, bitrate=%.2fMbps",
|
|
164
|
+
normalizedQuality,
|
|
165
|
+
(int)recordingSize.width,
|
|
166
|
+
(int)recordingSize.height,
|
|
167
|
+
bitrate / (1000.0 * 1000.0));
|
|
168
|
+
|
|
147
169
|
NSDictionary *videoSettings = @{
|
|
148
170
|
AVVideoCodecKey: codecKey,
|
|
149
171
|
AVVideoWidthKey: @((int)recordingSize.width),
|
|
@@ -153,13 +175,11 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
153
175
|
AVVideoMaxKeyFrameIntervalKey: @((int)fps),
|
|
154
176
|
AVVideoAllowFrameReorderingKey: @YES,
|
|
155
177
|
AVVideoExpectedSourceFrameRateKey: @((int)fps),
|
|
156
|
-
AVVideoQualityKey:
|
|
178
|
+
AVVideoQualityKey: qualityHint,
|
|
157
179
|
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
158
180
|
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
|
|
159
181
|
}
|
|
160
182
|
};
|
|
161
|
-
|
|
162
|
-
NSLog(@"🔧 Using codec: %@", codecKey);
|
|
163
183
|
|
|
164
184
|
// Create video input
|
|
165
185
|
g_avVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
|
package/src/mac_recorder.mm
CHANGED
|
@@ -21,7 +21,8 @@ extern "C" {
|
|
|
21
21
|
bool includeSystemAudio,
|
|
22
22
|
NSString* audioDeviceId,
|
|
23
23
|
NSString* audioOutputPath,
|
|
24
|
-
double frameRate
|
|
24
|
+
double frameRate,
|
|
25
|
+
NSString* qualityPreset);
|
|
25
26
|
bool stopAVFoundationRecording();
|
|
26
27
|
bool isAVFoundationRecording();
|
|
27
28
|
NSString* getAVFoundationAudioPath();
|
|
@@ -728,7 +729,8 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
728
729
|
bool includeSystemAudio,
|
|
729
730
|
NSString* audioDeviceId,
|
|
730
731
|
NSString* audioOutputPath,
|
|
731
|
-
double frameRate
|
|
732
|
+
double frameRate,
|
|
733
|
+
NSString* qualityPreset);
|
|
732
734
|
|
|
733
735
|
// A/V SYNC: Start camera non-blocking BEFORE AVFoundation
|
|
734
736
|
if (captureCamera) {
|
|
@@ -742,7 +744,8 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
742
744
|
MRLog(@"🎯 SYNC: Starting screen recording");
|
|
743
745
|
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
744
746
|
captureCursor, includeMicrophone, includeSystemAudio,
|
|
745
|
-
audioDeviceId, audioOutputPath, frameRate
|
|
747
|
+
audioDeviceId, audioOutputPath, frameRate,
|
|
748
|
+
qualityPreset ?: @"high");
|
|
746
749
|
|
|
747
750
|
if (avResult) {
|
|
748
751
|
MRLog(@"🎥 RECORDING METHOD: AVFoundation");
|
|
@@ -165,6 +165,7 @@ static NSInteger g_targetFPS = 60;
|
|
|
165
165
|
static NSString *g_qualityPreset = @"high";
|
|
166
166
|
static NSInteger g_frameCount = 0;
|
|
167
167
|
static CFAbsoluteTime g_firstFrameTime = 0;
|
|
168
|
+
static const NSInteger kSCKHighQualityVideoBitrate = 50 * 1000 * 1000;
|
|
168
169
|
|
|
169
170
|
// Quality helpers
|
|
170
171
|
static NSString *SCKNormalizeQualityPreset(id preset) {
|
|
@@ -213,10 +214,10 @@ static void SCKQualityBitrateForDimensions(NSString *preset,
|
|
|
213
214
|
multiplier = 18;
|
|
214
215
|
minBitrate = 18 * 1000 * 1000;
|
|
215
216
|
maxBitrate = 80 * 1000 * 1000;
|
|
216
|
-
} else { // high/default -
|
|
217
|
-
multiplier =
|
|
218
|
-
minBitrate =
|
|
219
|
-
maxBitrate =
|
|
217
|
+
} else { // high/default - fixed high-quality H.264 target
|
|
218
|
+
multiplier = 0;
|
|
219
|
+
minBitrate = kSCKHighQualityVideoBitrate;
|
|
220
|
+
maxBitrate = kSCKHighQualityVideoBitrate;
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
double base = ((double)MAX(1, width)) * ((double)MAX(1, height)) * (double)multiplier;
|
|
@@ -776,25 +777,18 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
776
777
|
return NO;
|
|
777
778
|
}
|
|
778
779
|
|
|
780
|
+
NSString *normalizedQuality = SCKNormalizeQualityPreset(g_qualityPreset);
|
|
779
781
|
NSInteger bitrate = 0;
|
|
780
|
-
NSInteger bitrateMultiplier = 0;
|
|
781
782
|
NSInteger minBitrate = 0;
|
|
782
783
|
NSInteger maxBitrate = 0;
|
|
783
|
-
|
|
784
|
-
SCKQualityBitrateForDimensions(normalizedQuality, width, height, &bitrate, &bitrateMultiplier, &minBitrate, &maxBitrate);
|
|
784
|
+
SCKQualityBitrateForDimensions(normalizedQuality, width, height, &bitrate, NULL, &minBitrate, &maxBitrate);
|
|
785
785
|
|
|
786
|
-
NSNumber *qualityHint = @1.0;
|
|
787
|
-
if ([normalizedQuality isEqualToString:@"medium"]) {
|
|
788
|
-
qualityHint = @0.9;
|
|
789
|
-
} else if ([normalizedQuality isEqualToString:@"low"]) {
|
|
790
|
-
qualityHint = @0.85;
|
|
791
|
-
}
|
|
786
|
+
NSNumber *qualityHint = [normalizedQuality isEqualToString:@"high"] ? @1.0 : ([normalizedQuality isEqualToString:@"medium"] ? @0.9 : @0.85);
|
|
792
787
|
|
|
793
|
-
MRLog(@"🎬 Screen encoder (%@): %ldx%ld,
|
|
788
|
+
MRLog(@"🎬 Screen encoder (%@): %ldx%ld, codec=H.264 High Profile, bitrate=%.2fMbps (min=%ldMbps max=%ldMbps)",
|
|
794
789
|
normalizedQuality,
|
|
795
790
|
(long)width,
|
|
796
791
|
(long)height,
|
|
797
|
-
(long)bitrateMultiplier,
|
|
798
792
|
bitrate / (1000.0 * 1000.0),
|
|
799
793
|
(long)(minBitrate / (1000 * 1000)),
|
|
800
794
|
(long)(maxBitrate / (1000 * 1000)));
|
|
@@ -804,12 +798,11 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
804
798
|
AVVideoMaxKeyFrameIntervalKey: @(MAX(1, g_targetFPS)),
|
|
805
799
|
AVVideoAllowFrameReorderingKey: @YES,
|
|
806
800
|
AVVideoExpectedSourceFrameRateKey: @(MAX(1, g_targetFPS)),
|
|
807
|
-
AVVideoQualityKey: qualityHint,
|
|
801
|
+
AVVideoQualityKey: qualityHint,
|
|
808
802
|
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
809
803
|
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
|
|
810
|
-
// Add data rate limits for consistent quality during motion
|
|
811
804
|
AVVideoAverageNonDroppableFrameRateKey: @(MAX(1, g_targetFPS)),
|
|
812
|
-
AVVideoMaxKeyFrameIntervalDurationKey: @(1.0)
|
|
805
|
+
AVVideoMaxKeyFrameIntervalDurationKey: @(1.0)
|
|
813
806
|
};
|
|
814
807
|
|
|
815
808
|
NSDictionary *videoSettings = @{
|