node-mac-recorder 2.21.19 → 2.21.21
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 +5 -1
- package/index.js +2 -2
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +17 -3
- package/src/screen_capture_kit.mm +47 -5
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
"Bash(chmod:*)",
|
|
6
6
|
"Bash(node test-sync.js:*)",
|
|
7
7
|
"Bash(node:*)",
|
|
8
|
-
"Bash(ALLOW_CONTINUITY_CAMERA=1 node:*)"
|
|
8
|
+
"Bash(ALLOW_CONTINUITY_CAMERA=1 node:*)",
|
|
9
|
+
"Bash(awk:*)",
|
|
10
|
+
"Bash(ffprobe:*)",
|
|
11
|
+
"Bash(sw_vers:*)",
|
|
12
|
+
"Bash(system_profiler:*)"
|
|
9
13
|
],
|
|
10
14
|
"deny": [],
|
|
11
15
|
"ask": []
|
package/index.js
CHANGED
|
@@ -449,8 +449,8 @@ class MacRecorder extends EventEmitter {
|
|
|
449
449
|
const originalBaseName = path.basename(outputPath, path.extname(outputPath));
|
|
450
450
|
const extension = path.extname(outputPath);
|
|
451
451
|
|
|
452
|
-
// Remove any existing timestamp from filename (pattern: -1234567890)
|
|
453
|
-
const cleanBaseName = originalBaseName.replace(
|
|
452
|
+
// Remove any existing timestamp from filename (pattern: -1234567890 or _1234567890)
|
|
453
|
+
const cleanBaseName = originalBaseName.replace(/[-_]\d{13}$/, '');
|
|
454
454
|
|
|
455
455
|
// Reconstruct path with sessionTimestamp
|
|
456
456
|
outputPath = path.join(outputDir, `${cleanBaseName}-${sessionTimestamp}${extension}`);
|
package/package.json
CHANGED
|
@@ -120,16 +120,30 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
120
120
|
codecKey = AVVideoCodecH264;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// QUALITY FIX: ULTRA HIGH quality screen recording
|
|
124
|
+
// ProMotion displays may capture at 10 FPS - use very high bitrate for perfect quality
|
|
125
|
+
NSInteger bitrate = (NSInteger)(recordingSize.width * recordingSize.height * 30);
|
|
126
|
+
bitrate = MAX(bitrate, 30 * 1000 * 1000); // Minimum 30 Mbps
|
|
127
|
+
bitrate = MIN(bitrate, 120 * 1000 * 1000); // Maximum 120 Mbps
|
|
128
|
+
|
|
129
|
+
NSLog(@"🎬 ULTRA QUALITY AVFoundation: %dx%d, bitrate=%.2fMbps",
|
|
130
|
+
(int)recordingSize.width, (int)recordingSize.height, bitrate / (1000.0 * 1000.0));
|
|
131
|
+
|
|
123
132
|
NSDictionary *videoSettings = @{
|
|
124
133
|
AVVideoCodecKey: codecKey,
|
|
125
134
|
AVVideoWidthKey: @((int)recordingSize.width),
|
|
126
135
|
AVVideoHeightKey: @((int)recordingSize.height),
|
|
127
136
|
AVVideoCompressionPropertiesKey: @{
|
|
128
|
-
AVVideoAverageBitRateKey: @(
|
|
129
|
-
AVVideoMaxKeyFrameIntervalKey: @30
|
|
137
|
+
AVVideoAverageBitRateKey: @(bitrate),
|
|
138
|
+
AVVideoMaxKeyFrameIntervalKey: @30,
|
|
139
|
+
AVVideoAllowFrameReorderingKey: @YES,
|
|
140
|
+
AVVideoExpectedSourceFrameRateKey: @60,
|
|
141
|
+
AVVideoQualityKey: @(0.95), // 0.0-1.0, higher is better
|
|
142
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
143
|
+
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
|
|
130
144
|
}
|
|
131
145
|
};
|
|
132
|
-
|
|
146
|
+
|
|
133
147
|
NSLog(@"🔧 Using codec: %@", codecKey);
|
|
134
148
|
|
|
135
149
|
// Create video input
|
|
@@ -33,6 +33,10 @@ static BOOL g_audioWriterStarted = NO;
|
|
|
33
33
|
static NSInteger g_configuredSampleRate = 48000;
|
|
34
34
|
static NSInteger g_configuredChannelCount = 2;
|
|
35
35
|
|
|
36
|
+
// Frame rate debugging
|
|
37
|
+
static NSInteger g_frameCount = 0;
|
|
38
|
+
static CFAbsoluteTime g_firstFrameTime = 0;
|
|
39
|
+
|
|
36
40
|
static void CleanupWriters(void);
|
|
37
41
|
static AVAssetWriterInputPixelBufferAdaptor * _Nullable CurrentPixelBufferAdaptor(void) {
|
|
38
42
|
if (!g_pixelBufferAdaptorRef) {
|
|
@@ -90,6 +94,10 @@ static void CleanupWriters(void) {
|
|
|
90
94
|
}
|
|
91
95
|
g_videoWriterStarted = NO;
|
|
92
96
|
g_videoStartTime = kCMTimeInvalid;
|
|
97
|
+
|
|
98
|
+
// Reset frame counting
|
|
99
|
+
g_frameCount = 0;
|
|
100
|
+
g_firstFrameTime = 0;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
if (g_audioWriter) {
|
|
@@ -217,6 +225,17 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
217
225
|
if (!appended) {
|
|
218
226
|
NSLog(@"⚠️ Failed appending pixel buffer: %@", g_videoWriter.error);
|
|
219
227
|
}
|
|
228
|
+
|
|
229
|
+
// Frame rate debugging
|
|
230
|
+
g_frameCount++;
|
|
231
|
+
if (g_firstFrameTime == 0) {
|
|
232
|
+
g_firstFrameTime = CFAbsoluteTimeGetCurrent();
|
|
233
|
+
}
|
|
234
|
+
if (g_frameCount % 60 == 0) {
|
|
235
|
+
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - g_firstFrameTime;
|
|
236
|
+
double actualFPS = g_frameCount / elapsed;
|
|
237
|
+
MRLog(@"📊 Frame stats: %ld frames in %.1fs = %.1f FPS", (long)g_frameCount, elapsed, actualFPS);
|
|
238
|
+
}
|
|
220
239
|
}
|
|
221
240
|
@end
|
|
222
241
|
|
|
@@ -310,11 +329,27 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
310
329
|
return NO;
|
|
311
330
|
}
|
|
312
331
|
|
|
332
|
+
// QUALITY FIX: ULTRA HIGH quality for screen recording
|
|
333
|
+
// ProMotion displays may run at 10Hz (low power) = 10 FPS capture
|
|
334
|
+
// Solution: Use VERY HIGH bitrate so each frame is perfect quality
|
|
335
|
+
// Use 30x multiplier for ULTRA quality (was 6x - way too low!)
|
|
336
|
+
NSInteger bitrate = (NSInteger)(width * height * 30);
|
|
337
|
+
bitrate = MAX(bitrate, 30 * 1000 * 1000); // Minimum 30 Mbps for crystal clear screen recording
|
|
338
|
+
bitrate = MIN(bitrate, 120 * 1000 * 1000); // Maximum 120 Mbps for ultra quality
|
|
339
|
+
|
|
340
|
+
MRLog(@"🎬 ULTRA QUALITY Screen encoder: %ldx%ld, bitrate=%.2fMbps",
|
|
341
|
+
(long)width, (long)height, bitrate / (1000.0 * 1000.0));
|
|
342
|
+
|
|
313
343
|
NSDictionary *compressionProps = @{
|
|
314
|
-
AVVideoAverageBitRateKey: @(
|
|
315
|
-
AVVideoMaxKeyFrameIntervalKey: @30
|
|
344
|
+
AVVideoAverageBitRateKey: @(bitrate),
|
|
345
|
+
AVVideoMaxKeyFrameIntervalKey: @30,
|
|
346
|
+
AVVideoAllowFrameReorderingKey: @YES,
|
|
347
|
+
AVVideoExpectedSourceFrameRateKey: @60,
|
|
348
|
+
AVVideoQualityKey: @(0.95), // 0.0-1.0, higher is better (0.95 = excellent)
|
|
349
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
350
|
+
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
|
|
316
351
|
};
|
|
317
|
-
|
|
352
|
+
|
|
318
353
|
NSDictionary *videoSettings = @{
|
|
319
354
|
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
320
355
|
AVVideoWidthKey: @(width),
|
|
@@ -602,13 +637,20 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
602
637
|
}
|
|
603
638
|
}
|
|
604
639
|
|
|
605
|
-
// Configure stream with
|
|
640
|
+
// Configure stream with HIGH QUALITY settings
|
|
606
641
|
SCStreamConfiguration *streamConfig = [[SCStreamConfiguration alloc] init];
|
|
607
642
|
streamConfig.width = recordingWidth;
|
|
608
643
|
streamConfig.height = recordingHeight;
|
|
609
|
-
streamConfig.minimumFrameInterval = CMTimeMake(1,
|
|
644
|
+
streamConfig.minimumFrameInterval = CMTimeMake(1, 60); // 60 FPS for smooth recording
|
|
610
645
|
streamConfig.pixelFormat = kCVPixelFormatType_32BGRA;
|
|
611
646
|
streamConfig.scalesToFit = NO;
|
|
647
|
+
|
|
648
|
+
// QUALITY FIX: Set high quality encoding parameters
|
|
649
|
+
if (@available(macOS 13.0, *)) {
|
|
650
|
+
streamConfig.queueDepth = 8; // Larger queue for smoother capture
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
MRLog(@"🎬 ScreenCaptureKit config: %ldx%ld @ 60fps", (long)recordingWidth, (long)recordingHeight);
|
|
612
654
|
|
|
613
655
|
BOOL shouldCaptureMic = includeMicrophone ? [includeMicrophone boolValue] : NO;
|
|
614
656
|
BOOL shouldCaptureSystemAudio = includeSystemAudio ? [includeSystemAudio boolValue] : NO;
|