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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.22.34",
3
+ "version": "2.23.0",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
- // Video settings with macOS compatibility
127
- NSString *codecKey;
128
- if (@available(macOS 10.13, *)) {
129
- codecKey = AVVideoCodecTypeH264;
130
- } else {
131
- // Fallback for older macOS versions
132
- codecKey = AVVideoCodecH264;
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
- AVVideoCodecKey: codecKey,
149
- AVVideoWidthKey: @((int)recordingSize.width),
150
- AVVideoHeightKey: @((int)recordingSize.height),
151
- AVVideoCompressionPropertiesKey: @{
152
- AVVideoAverageBitRateKey: @(bitrate),
153
- AVVideoMaxKeyFrameIntervalKey: @((int)fps),
154
- AVVideoAllowFrameReorderingKey: @YES,
155
- AVVideoExpectedSourceFrameRateKey: @((int)fps),
156
- AVVideoQualityKey: @(1.0),
157
- AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
158
- AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
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
- NSLog(@"🔧 Using codec: %@", codecKey);
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];
@@ -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
- SCKQualityBitrateForDimensions(normalizedQuality, width, height, &bitrate, &bitrateMultiplier, &minBitrate, &maxBitrate);
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
- }
792
-
793
- MRLog(@"🎬 Screen encoder (%@): %ldx%ld, multiplier=%ld, bitrate=%.2fMbps (min=%ldMbps max=%ldMbps)",
794
- normalizedQuality,
795
- (long)width,
796
- (long)height,
797
- (long)bitrateMultiplier,
798
- bitrate / (1000.0 * 1000.0),
799
- (long)(minBitrate / (1000 * 1000)),
800
- (long)(maxBitrate / (1000 * 1000)));
801
-
802
- NSDictionary *compressionProps = @{
803
- AVVideoAverageBitRateKey: @(bitrate),
804
- AVVideoMaxKeyFrameIntervalKey: @(MAX(1, g_targetFPS)),
805
- AVVideoAllowFrameReorderingKey: @YES,
806
- AVVideoExpectedSourceFrameRateKey: @(MAX(1, g_targetFPS)),
807
- AVVideoQualityKey: qualityHint, // 0.0-1.0, higher is better
808
- AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
809
- AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
810
- // Add data rate limits for consistent quality during motion
811
- AVVideoAverageNonDroppableFrameRateKey: @(MAX(1, g_targetFPS)),
812
- AVVideoMaxKeyFrameIntervalDurationKey: @(1.0) // Keyframe at least every second
813
- };
814
-
815
- NSDictionary *videoSettings = @{
816
- AVVideoCodecKey: AVVideoCodecTypeH264,
817
- AVVideoWidthKey: @(width),
818
- AVVideoHeightKey: @(height),
819
- AVVideoCompressionPropertiesKey: compressionProps
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;