node-mac-recorder 2.22.4 → 2.22.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.22.4",
3
+ "version": "2.22.6",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -134,9 +134,9 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
134
134
 
135
135
  // QUALITY FIX: ULTRA HIGH quality screen recording
136
136
  // ProMotion displays may capture at 10 FPS - use very high bitrate for perfect quality
137
- NSInteger bitrate = (NSInteger)(recordingSize.width * recordingSize.height * 45);
138
- bitrate = MAX(bitrate, 50 * 1000 * 1000); // Minimum 50 Mbps
139
- bitrate = MIN(bitrate, 200 * 1000 * 1000); // Maximum 200 Mbps
137
+ NSInteger bitrate = (NSInteger)(recordingSize.width * recordingSize.height * 60);
138
+ bitrate = MAX(bitrate, 70 * 1000 * 1000); // Minimum 70 Mbps
139
+ bitrate = MIN(bitrate, 250 * 1000 * 1000); // Maximum 250 Mbps
140
140
 
141
141
  NSLog(@"🎬 ULTRA QUALITY AVFoundation: %dx%d, bitrate=%.2fMbps",
142
142
  (int)recordingSize.width, (int)recordingSize.height, bitrate / (1000.0 * 1000.0));
@@ -214,9 +214,9 @@ static void SCKQualityBitrateForDimensions(NSString *preset,
214
214
  minBitrate = 18 * 1000 * 1000;
215
215
  maxBitrate = 80 * 1000 * 1000;
216
216
  } else { // high/default - ULTRA quality
217
- multiplier = 60;
218
- minBitrate = 80 * 1000 * 1000;
219
- maxBitrate = 300 * 1000 * 1000;
217
+ multiplier = 80;
218
+ minBitrate = 100 * 1000 * 1000;
219
+ maxBitrate = 400 * 1000 * 1000;
220
220
  }
221
221
 
222
222
  double base = ((double)MAX(1, width)) * ((double)MAX(1, height)) * (double)multiplier;
@@ -1383,11 +1383,46 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
1383
1383
  }
1384
1384
 
1385
1385
  if (targetWindow && targetApp) {
1386
- MRLog(@"🪟 Recording window: %@ (%ux%u)",
1387
- targetWindow.title, (unsigned)targetWindow.frame.size.width, (unsigned)targetWindow.frame.size.height);
1386
+ CGFloat windowLogicalWidth = targetWindow.frame.size.width;
1387
+ CGFloat windowLogicalHeight = targetWindow.frame.size.height;
1388
+
1389
+ // Find display containing this window to get scale factor
1390
+ CGFloat scaleFactor = 1.0;
1391
+ CGPoint windowCenter = CGPointMake(
1392
+ targetWindow.frame.origin.x + windowLogicalWidth / 2.0,
1393
+ targetWindow.frame.origin.y + windowLogicalHeight / 2.0
1394
+ );
1395
+ for (SCDisplay *disp in content.displays) {
1396
+ CGRect dispBounds = CGRectMake(disp.frame.origin.x, disp.frame.origin.y,
1397
+ disp.frame.size.width, disp.frame.size.height);
1398
+ if (CGRectContainsPoint(dispBounds, windowCenter)) {
1399
+ NSInteger physW = (NSInteger)CGDisplayPixelsWide(disp.displayID);
1400
+ NSInteger physH = (NSInteger)CGDisplayPixelsHigh(disp.displayID);
1401
+ CGFloat scX = (disp.width > 0) ? (CGFloat)physW / (CGFloat)disp.width : 1.0;
1402
+ CGFloat scY = (disp.height > 0) ? (CGFloat)physH / (CGFloat)disp.height : 1.0;
1403
+ scaleFactor = MAX(scX, scY);
1404
+ break;
1405
+ }
1406
+ }
1407
+ // Fallback: use main display scale factor
1408
+ if (scaleFactor == 1.0 && content.displays.count > 0) {
1409
+ SCDisplay *mainDisp = content.displays.firstObject;
1410
+ NSInteger physW = (NSInteger)CGDisplayPixelsWide(mainDisp.displayID);
1411
+ NSInteger physH = (NSInteger)CGDisplayPixelsHigh(mainDisp.displayID);
1412
+ CGFloat scX = (mainDisp.width > 0) ? (CGFloat)physW / (CGFloat)mainDisp.width : 1.0;
1413
+ CGFloat scY = (mainDisp.height > 0) ? (CGFloat)physH / (CGFloat)mainDisp.height : 1.0;
1414
+ scaleFactor = MAX(scX, scY);
1415
+ }
1416
+
1417
+ NSInteger physicalWindowWidth = (NSInteger)(windowLogicalWidth * scaleFactor);
1418
+ NSInteger physicalWindowHeight = (NSInteger)(windowLogicalHeight * scaleFactor);
1419
+
1420
+ MRLog(@"🪟 Recording window: %@ logical=%ux%u, physical=%ldx%ld, scale=%.2fx",
1421
+ targetWindow.title, (unsigned)windowLogicalWidth, (unsigned)windowLogicalHeight,
1422
+ (long)physicalWindowWidth, (long)physicalWindowHeight, scaleFactor);
1388
1423
  filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:targetWindow];
1389
- recordingWidth = (NSInteger)targetWindow.frame.size.width;
1390
- recordingHeight = (NSInteger)targetWindow.frame.size.height;
1424
+ recordingWidth = physicalWindowWidth;
1425
+ recordingHeight = physicalWindowHeight;
1391
1426
  } else {
1392
1427
  NSLog(@"❌ Window ID %@ not found", windowId);
1393
1428
  SCKFailScheduling();
@@ -1421,11 +1456,24 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
1421
1456
  }
1422
1457
 
1423
1458
  if (targetDisplay) {
1424
- MRLog(@"🖥️ Recording display %u (%dx%d)",
1425
- targetDisplay.displayID, (int)targetDisplay.width, (int)targetDisplay.height);
1459
+ // Use physical pixel dimensions for Retina displays (not logical points)
1460
+ CGDirectDisplayID displayID = targetDisplay.displayID;
1461
+ NSInteger physicalWidth = (NSInteger)CGDisplayPixelsWide(displayID);
1462
+ NSInteger physicalHeight = (NSInteger)CGDisplayPixelsHigh(displayID);
1463
+ NSInteger logicalWidth = targetDisplay.width;
1464
+ NSInteger logicalHeight = targetDisplay.height;
1465
+
1466
+ CGFloat scaleX = (logicalWidth > 0) ? (CGFloat)physicalWidth / (CGFloat)logicalWidth : 1.0;
1467
+ CGFloat scaleY = (logicalHeight > 0) ? (CGFloat)physicalHeight / (CGFloat)logicalHeight : 1.0;
1468
+ CGFloat scaleFactor = MAX(scaleX, scaleY);
1469
+
1470
+ MRLog(@"🖥️ Recording display %u: logical=%dx%d, physical=%ldx%ld, scale=%.2fx",
1471
+ displayID, (int)logicalWidth, (int)logicalHeight,
1472
+ (long)physicalWidth, (long)physicalHeight, scaleFactor);
1473
+
1426
1474
  filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:@[]];
1427
- recordingWidth = targetDisplay.width;
1428
- recordingHeight = targetDisplay.height;
1475
+ recordingWidth = physicalWidth;
1476
+ recordingHeight = physicalHeight;
1429
1477
  } else {
1430
1478
  NSLog(@"❌ No display available");
1431
1479
  SCKFailScheduling();
@@ -1437,11 +1485,22 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
1437
1485
  CGFloat cropWidth = [captureRect[@"width"] doubleValue];
1438
1486
  CGFloat cropHeight = [captureRect[@"height"] doubleValue];
1439
1487
  if (cropWidth > 0 && cropHeight > 0) {
1440
- MRLog(@"🔲 Crop area specified: %.0fx%.0f at (%.0f,%.0f)",
1441
- cropWidth, cropHeight,
1488
+ // Scale crop dimensions for Retina displays
1489
+ CGFloat cropScaleFactor = 1.0;
1490
+ if (targetDisplay) {
1491
+ NSInteger physW = (NSInteger)CGDisplayPixelsWide(targetDisplay.displayID);
1492
+ NSInteger physH = (NSInteger)CGDisplayPixelsHigh(targetDisplay.displayID);
1493
+ CGFloat scX = (targetDisplay.width > 0) ? (CGFloat)physW / (CGFloat)targetDisplay.width : 1.0;
1494
+ CGFloat scY = (targetDisplay.height > 0) ? (CGFloat)physH / (CGFloat)targetDisplay.height : 1.0;
1495
+ cropScaleFactor = MAX(scX, scY);
1496
+ }
1497
+ NSInteger physicalCropWidth = (NSInteger)(cropWidth * cropScaleFactor);
1498
+ NSInteger physicalCropHeight = (NSInteger)(cropHeight * cropScaleFactor);
1499
+ MRLog(@"🔲 Crop area: logical=%.0fx%.0f, physical=%ldx%ld, scale=%.2fx at (%.0f,%.0f)",
1500
+ cropWidth, cropHeight, (long)physicalCropWidth, (long)physicalCropHeight, cropScaleFactor,
1442
1501
  [captureRect[@"x"] doubleValue], [captureRect[@"y"] doubleValue]);
1443
- recordingWidth = (NSInteger)cropWidth;
1444
- recordingHeight = (NSInteger)cropHeight;
1502
+ recordingWidth = physicalCropWidth;
1503
+ recordingHeight = physicalCropHeight;
1445
1504
  }
1446
1505
  }
1447
1506
 
@@ -1,80 +0,0 @@
1
- const MacRecorder = require('./index.js');
2
- const path = require('path');
3
- const fs = require('fs');
4
-
5
- // Test output directory
6
- const outputDir = path.join(__dirname, 'test-output');
7
- if (!fs.existsSync(outputDir)) {
8
- fs.mkdirSync(outputDir, { recursive: true });
9
- }
10
-
11
- const timestamp = Date.now();
12
- const outputPath = path.join(outputDir, `noise-reduction-test-${timestamp}.mov`);
13
-
14
- console.log('🎙️ Starting microphone recording with noise reduction...');
15
- console.log('📝 Instructions:');
16
- console.log(' 1. Speak into your microphone (normal voice)');
17
- console.log(' 2. Type on your keyboard (heavy typing)');
18
- console.log(' 3. Click your mouse multiple times');
19
- console.log(' 4. Speak again to compare');
20
- console.log('');
21
- console.log('⏱️ Recording for 15 seconds...');
22
- console.log('');
23
-
24
- const recorder = new MacRecorder();
25
-
26
- recorder.on('recordingStarted', () => {
27
- console.log('✅ Recording started!');
28
- console.log('🎤 Custom noise reduction is ACTIVE');
29
- console.log('');
30
- console.log('Starting countdown:');
31
-
32
- let countdown = 15;
33
- const interval = setInterval(() => {
34
- process.stdout.write(`\r⏱️ ${countdown} seconds remaining... `);
35
- countdown--;
36
-
37
- if (countdown < 0) {
38
- clearInterval(interval);
39
- process.stdout.write('\r');
40
- console.log('⏱️ Time up! Stopping...');
41
- console.log('');
42
-
43
- recorder.stopRecording()
44
- .then(() => {
45
- console.log('✅ Recording saved to:', outputPath);
46
- console.log('');
47
- console.log('🎧 Play the recording to verify:');
48
- console.log(` open "${outputPath}"`);
49
- console.log('');
50
- console.log('Expected results:');
51
- console.log(' ✅ Voice should be clear');
52
- console.log(' ✅ Keyboard typing should be significantly reduced');
53
- console.log(' ✅ Mouse clicks should be filtered out');
54
- process.exit(0);
55
- })
56
- .catch(err => {
57
- console.error('❌ Error stopping:', err);
58
- process.exit(1);
59
- });
60
- }
61
- }, 1000);
62
- });
63
-
64
- recorder.on('stopped', () => {
65
- console.log('🛑 Recording stopped');
66
- });
67
-
68
- recorder.on('completed', (result) => {
69
- console.log('✅ Recording completed:', result);
70
- });
71
-
72
- // Start recording with microphone
73
- recorder.startRecording(outputPath, {
74
- includeMicrophone: true,
75
- includeSystemAudio: false,
76
- fps: 1 // Minimal FPS since we're only testing audio
77
- }).catch(err => {
78
- console.error('❌ Failed to start recording:', err);
79
- process.exit(1);
80
- });