node-mac-recorder 2.22.34 → 2.23.0
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/package.json +1 -1
- package/src/avfoundation_recorder.mm +50 -31
- package/src/mac_recorder.mm +6 -3
- package/src/screen_capture_kit.mm +52 -41
|
@@ -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/package.json
CHANGED
|
@@ -36,7 +36,8 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
36
36
|
bool includeSystemAudio,
|
|
37
37
|
NSString* audioDeviceId,
|
|
38
38
|
NSString* audioOutputPath,
|
|
39
|
-
double requestedFrameRate
|
|
39
|
+
double requestedFrameRate,
|
|
40
|
+
NSString* qualityPreset) {
|
|
40
41
|
|
|
41
42
|
if (g_avIsRecording) {
|
|
42
43
|
NSLog(@"❌ AVFoundation recording already in progress");
|
|
@@ -123,43 +124,61 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
123
124
|
|
|
124
125
|
MRLog(@"🎯 Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
NSString
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
NSString *normalizedQuality = @"high";
|
|
128
|
+
if ([qualityPreset isKindOfClass:[NSString class]] && qualityPreset.length > 0) {
|
|
129
|
+
normalizedQuality = [qualityPreset lowercaseString];
|
|
130
|
+
if (![normalizedQuality isEqualToString:@"low"] &&
|
|
131
|
+
![normalizedQuality isEqualToString:@"medium"] &&
|
|
132
|
+
![normalizedQuality isEqualToString:@"high"]) {
|
|
133
|
+
normalizedQuality = @"high";
|
|
134
|
+
}
|
|
133
135
|
}
|
|
134
|
-
|
|
135
|
-
NSInteger bitrate = (NSInteger)(recordingSize.width * recordingSize.height * 100);
|
|
136
|
-
bitrate = MAX(bitrate, 120 * 1000 * 1000);
|
|
137
|
-
bitrate = MIN(bitrate, 500 * 1000 * 1000);
|
|
136
|
+
BOOL useProRes = [normalizedQuality isEqualToString:@"high"];
|
|
138
137
|
|
|
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
138
|
double fps = requestedFrameRate > 0 ? requestedFrameRate : 60.0;
|
|
144
139
|
if (fps < 1.0) fps = 1.0;
|
|
145
140
|
if (fps > 120.0) fps = 120.0;
|
|
146
141
|
|
|
147
|
-
NSDictionary *videoSettings =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
142
|
+
NSDictionary *videoSettings = nil;
|
|
143
|
+
if (useProRes) {
|
|
144
|
+
MRLog(@"🎬 AVFoundation encoder (high): %dx%d, codec=ProRes 422 HQ",
|
|
145
|
+
(int)recordingSize.width, (int)recordingSize.height);
|
|
146
|
+
videoSettings = @{
|
|
147
|
+
AVVideoCodecKey: AVVideoCodecTypeAppleProRes422HQ,
|
|
148
|
+
AVVideoWidthKey: @((int)recordingSize.width),
|
|
149
|
+
AVVideoHeightKey: @((int)recordingSize.height)
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
NSString *codecKey = AVVideoCodecTypeH264;
|
|
153
|
+
NSInteger multiplier = [normalizedQuality isEqualToString:@"medium"] ? 18 : 10;
|
|
154
|
+
NSInteger minBitrate = [normalizedQuality isEqualToString:@"medium"] ? (18 * 1000 * 1000) : (10 * 1000 * 1000);
|
|
155
|
+
NSInteger maxBitrate = [normalizedQuality isEqualToString:@"medium"] ? (80 * 1000 * 1000) : (45 * 1000 * 1000);
|
|
156
|
+
NSInteger bitrate = (NSInteger)(recordingSize.width * recordingSize.height * multiplier);
|
|
157
|
+
bitrate = MAX(bitrate, minBitrate);
|
|
158
|
+
bitrate = MIN(bitrate, maxBitrate);
|
|
159
|
+
NSNumber *qualityHint = [normalizedQuality isEqualToString:@"medium"] ? @0.9 : @0.85;
|
|
160
|
+
|
|
161
|
+
MRLog(@"🎬 AVFoundation encoder (%@): %dx%d, codec=H.264, bitrate=%.2fMbps",
|
|
162
|
+
normalizedQuality,
|
|
163
|
+
(int)recordingSize.width,
|
|
164
|
+
(int)recordingSize.height,
|
|
165
|
+
bitrate / (1000.0 * 1000.0));
|
|
161
166
|
|
|
162
|
-
|
|
167
|
+
videoSettings = @{
|
|
168
|
+
AVVideoCodecKey: codecKey,
|
|
169
|
+
AVVideoWidthKey: @((int)recordingSize.width),
|
|
170
|
+
AVVideoHeightKey: @((int)recordingSize.height),
|
|
171
|
+
AVVideoCompressionPropertiesKey: @{
|
|
172
|
+
AVVideoAverageBitRateKey: @(bitrate),
|
|
173
|
+
AVVideoMaxKeyFrameIntervalKey: @((int)fps),
|
|
174
|
+
AVVideoAllowFrameReorderingKey: @YES,
|
|
175
|
+
AVVideoExpectedSourceFrameRateKey: @((int)fps),
|
|
176
|
+
AVVideoQualityKey: qualityHint,
|
|
177
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
178
|
+
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
163
182
|
|
|
164
183
|
// Create video input
|
|
165
184
|
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");
|
|
@@ -776,48 +776,59 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
776
776
|
return NO;
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
-
NSInteger bitrate = 0;
|
|
780
|
-
NSInteger bitrateMultiplier = 0;
|
|
781
|
-
NSInteger minBitrate = 0;
|
|
782
|
-
NSInteger maxBitrate = 0;
|
|
783
779
|
NSString *normalizedQuality = SCKNormalizeQualityPreset(g_qualityPreset);
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
780
|
+
BOOL useProRes = [normalizedQuality isEqualToString:@"high"];
|
|
781
|
+
|
|
782
|
+
NSDictionary *videoSettings = nil;
|
|
783
|
+
|
|
784
|
+
if (useProRes) {
|
|
785
|
+
// Screen Studio-style: ProRes 422 HQ — intra-frame, visually lossless,
|
|
786
|
+
// hardware-accelerated on Apple Silicon. Best for editor scrubbing & high-zoom clarity.
|
|
787
|
+
MRLog(@"🎬 Screen encoder (high): %ldx%ld, codec=ProRes 422 HQ",
|
|
788
|
+
(long)width, (long)height);
|
|
789
|
+
|
|
790
|
+
videoSettings = @{
|
|
791
|
+
AVVideoCodecKey: AVVideoCodecTypeAppleProRes422HQ,
|
|
792
|
+
AVVideoWidthKey: @(width),
|
|
793
|
+
AVVideoHeightKey: @(height)
|
|
794
|
+
};
|
|
795
|
+
} else {
|
|
796
|
+
NSInteger bitrate = 0;
|
|
797
|
+
NSInteger bitrateMultiplier = 0;
|
|
798
|
+
NSInteger minBitrate = 0;
|
|
799
|
+
NSInteger maxBitrate = 0;
|
|
800
|
+
SCKQualityBitrateForDimensions(normalizedQuality, width, height, &bitrate, &bitrateMultiplier, &minBitrate, &maxBitrate);
|
|
801
|
+
|
|
802
|
+
NSNumber *qualityHint = [normalizedQuality isEqualToString:@"medium"] ? @0.9 : @0.85;
|
|
803
|
+
|
|
804
|
+
MRLog(@"🎬 Screen encoder (%@): %ldx%ld, codec=H.264, multiplier=%ld, bitrate=%.2fMbps (min=%ldMbps max=%ldMbps)",
|
|
805
|
+
normalizedQuality,
|
|
806
|
+
(long)width,
|
|
807
|
+
(long)height,
|
|
808
|
+
(long)bitrateMultiplier,
|
|
809
|
+
bitrate / (1000.0 * 1000.0),
|
|
810
|
+
(long)(minBitrate / (1000 * 1000)),
|
|
811
|
+
(long)(maxBitrate / (1000 * 1000)));
|
|
812
|
+
|
|
813
|
+
NSDictionary *compressionProps = @{
|
|
814
|
+
AVVideoAverageBitRateKey: @(bitrate),
|
|
815
|
+
AVVideoMaxKeyFrameIntervalKey: @(MAX(1, g_targetFPS)),
|
|
816
|
+
AVVideoAllowFrameReorderingKey: @YES,
|
|
817
|
+
AVVideoExpectedSourceFrameRateKey: @(MAX(1, g_targetFPS)),
|
|
818
|
+
AVVideoQualityKey: qualityHint,
|
|
819
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
820
|
+
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
|
|
821
|
+
AVVideoAverageNonDroppableFrameRateKey: @(MAX(1, g_targetFPS)),
|
|
822
|
+
AVVideoMaxKeyFrameIntervalDurationKey: @(1.0)
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
videoSettings = @{
|
|
826
|
+
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
827
|
+
AVVideoWidthKey: @(width),
|
|
828
|
+
AVVideoHeightKey: @(height),
|
|
829
|
+
AVVideoCompressionPropertiesKey: compressionProps
|
|
830
|
+
};
|
|
831
|
+
}
|
|
821
832
|
|
|
822
833
|
g_videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
|
|
823
834
|
g_videoInput.expectsMediaDataInRealTime = YES;
|