node-mac-recorder 2.21.49 → 2.21.51

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.
Files changed (2) hide show
  1. package/index.js +242 -2
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -339,11 +339,246 @@ class MacRecorder extends EventEmitter {
339
339
  // Seçenekleri güncelle
340
340
  this.setOptions(options);
341
341
 
342
+ // Cache display list so we don't fetch multiple times during preparation
343
+ let cachedDisplays = null;
344
+ const getCachedDisplays = async () => {
345
+ if (cachedDisplays) {
346
+ return cachedDisplays;
347
+ }
348
+ try {
349
+ cachedDisplays = await this.getDisplays();
350
+ } catch (error) {
351
+ console.warn("Display bilgisi alınamadı:", error.message);
352
+ cachedDisplays = [];
353
+ }
354
+ return cachedDisplays;
355
+ };
356
+
357
+ /**
358
+ * Normalize capture area coordinates:
359
+ * - Pick correct display based on user-provided area/displayId info
360
+ * - Convert global coordinates to display-relative when needed
361
+ * - Clamp to valid bounds to avoid ScreenCaptureKit skipping crops
362
+ */
363
+ const normalizeCaptureArea = async () => {
364
+ if (!this.options.captureArea) {
365
+ return;
366
+ }
367
+
368
+ const displays = await getCachedDisplays();
369
+ if (!Array.isArray(displays) || displays.length === 0) {
370
+ return;
371
+ }
372
+
373
+ const rawArea = this.options.captureArea;
374
+ const parsedArea = {
375
+ x: Number(rawArea.x),
376
+ y: Number(rawArea.y),
377
+ width: Number(rawArea.width),
378
+ height: Number(rawArea.height),
379
+ };
380
+
381
+ if (
382
+ !Number.isFinite(parsedArea.x) ||
383
+ !Number.isFinite(parsedArea.y) ||
384
+ !Number.isFinite(parsedArea.width) ||
385
+ !Number.isFinite(parsedArea.height) ||
386
+ parsedArea.width <= 0 ||
387
+ parsedArea.height <= 0
388
+ ) {
389
+ return;
390
+ }
391
+
392
+ const areaRect = {
393
+ left: parsedArea.x,
394
+ top: parsedArea.y,
395
+ right: parsedArea.x + parsedArea.width,
396
+ bottom: parsedArea.y + parsedArea.height,
397
+ };
398
+
399
+ const getDisplayRect = (display) => {
400
+ const dx = Number(display.x) || 0;
401
+ const dy = Number(display.y) || 0;
402
+ const dw = Number(display.width) || 0;
403
+ const dh = Number(display.height) || 0;
404
+ return {
405
+ left: dx,
406
+ top: dy,
407
+ right: dx + dw,
408
+ bottom: dy + dh,
409
+ width: dw,
410
+ height: dh,
411
+ };
412
+ };
413
+
414
+ const requestedDisplayId =
415
+ this.options.displayId === null || this.options.displayId === undefined
416
+ ? null
417
+ : Number(this.options.displayId);
418
+
419
+ let targetDisplay = null;
420
+ if (requestedDisplayId !== null && Number.isFinite(requestedDisplayId)) {
421
+ targetDisplay =
422
+ displays.find(
423
+ (display) => Number(display.id) === requestedDisplayId
424
+ ) || null;
425
+ }
426
+
427
+ if (!targetDisplay) {
428
+ targetDisplay =
429
+ displays.find((display) => {
430
+ const rect = getDisplayRect(display);
431
+ return (
432
+ areaRect.left >= rect.left &&
433
+ areaRect.right <= rect.right &&
434
+ areaRect.top >= rect.top &&
435
+ areaRect.bottom <= rect.bottom
436
+ );
437
+ }) || null;
438
+ }
439
+
440
+ if (!targetDisplay) {
441
+ let bestDisplay = null;
442
+ let bestOverlap = 0;
443
+ displays.forEach((display) => {
444
+ const rect = getDisplayRect(display);
445
+ const overlapWidth =
446
+ Math.min(areaRect.right, rect.right) -
447
+ Math.max(areaRect.left, rect.left);
448
+ const overlapHeight =
449
+ Math.min(areaRect.bottom, rect.bottom) -
450
+ Math.max(areaRect.top, rect.top);
451
+ if (overlapWidth > 0 && overlapHeight > 0) {
452
+ const overlapArea = overlapWidth * overlapHeight;
453
+ if (overlapArea > bestOverlap) {
454
+ bestOverlap = overlapArea;
455
+ bestDisplay = display;
456
+ }
457
+ }
458
+ });
459
+ targetDisplay = bestDisplay;
460
+ }
461
+
462
+ if (!targetDisplay) {
463
+ targetDisplay =
464
+ displays.find((display) => display.isPrimary) || displays[0];
465
+ }
466
+
467
+ if (!targetDisplay) {
468
+ return;
469
+ }
470
+
471
+ const targetRect = getDisplayRect(targetDisplay);
472
+ if (targetRect.width <= 0 || targetRect.height <= 0) {
473
+ return;
474
+ }
475
+
476
+ const tolerance = 1; // allow sub-pixel offsets
477
+ const isRelativeToDisplay = () => {
478
+ const endX = parsedArea.x + parsedArea.width;
479
+ const endY = parsedArea.y + parsedArea.height;
480
+ return (
481
+ parsedArea.x >= -tolerance &&
482
+ parsedArea.y >= -tolerance &&
483
+ endX <= targetRect.width + tolerance &&
484
+ endY <= targetRect.height + tolerance
485
+ );
486
+ };
487
+
488
+ let relativeX = parsedArea.x;
489
+ let relativeY = parsedArea.y;
490
+
491
+ if (!isRelativeToDisplay()) {
492
+ relativeX = parsedArea.x - targetRect.left;
493
+ relativeY = parsedArea.y - targetRect.top;
494
+ }
495
+
496
+ let relativeWidth = parsedArea.width;
497
+ let relativeHeight = parsedArea.height;
498
+
499
+ // Discard if area sits completely outside the display
500
+ if (
501
+ relativeX >= targetRect.width ||
502
+ relativeY >= targetRect.height ||
503
+ relativeWidth <= 0 ||
504
+ relativeHeight <= 0
505
+ ) {
506
+ return;
507
+ }
508
+
509
+ if (relativeX < 0) {
510
+ relativeWidth += relativeX;
511
+ relativeX = 0;
512
+ }
513
+
514
+ if (relativeY < 0) {
515
+ relativeHeight += relativeY;
516
+ relativeY = 0;
517
+ }
518
+
519
+ const maxWidth = targetRect.width - relativeX;
520
+ const maxHeight = targetRect.height - relativeY;
521
+
522
+ if (maxWidth <= 0 || maxHeight <= 0) {
523
+ return;
524
+ }
525
+
526
+ relativeWidth = Math.min(relativeWidth, maxWidth);
527
+ relativeHeight = Math.min(relativeHeight, maxHeight);
528
+
529
+ if (relativeWidth <= 0 || relativeHeight <= 0) {
530
+ return;
531
+ }
532
+
533
+ const normalizeValue = (value, minValue) =>
534
+ Math.max(minValue, Math.round(value));
535
+ const normalizedArea = {
536
+ x: Math.max(0, Math.round(relativeX)),
537
+ y: Math.max(0, Math.round(relativeY)),
538
+ width: normalizeValue(relativeWidth, 1),
539
+ height: normalizeValue(relativeHeight, 1),
540
+ };
541
+
542
+ const originalRounded = {
543
+ x: Math.round(parsedArea.x),
544
+ y: Math.round(parsedArea.y),
545
+ width: normalizeValue(parsedArea.width, 1),
546
+ height: normalizeValue(parsedArea.height, 1),
547
+ };
548
+
549
+ const displayChanged =
550
+ !Number.isFinite(requestedDisplayId) ||
551
+ Number(targetDisplay.id) !== requestedDisplayId;
552
+ const areaChanged =
553
+ normalizedArea.x !== originalRounded.x ||
554
+ normalizedArea.y !== originalRounded.y ||
555
+ normalizedArea.width !== originalRounded.width ||
556
+ normalizedArea.height !== originalRounded.height;
557
+
558
+ if (displayChanged || areaChanged) {
559
+ console.log(
560
+ `🎯 Capture area normalize: display=${targetDisplay.id} -> (${rawArea.x},${rawArea.y},${rawArea.width}x${rawArea.height}) ➜ (${normalizedArea.x},${normalizedArea.y},${normalizedArea.width}x${normalizedArea.height})`
561
+ );
562
+ }
563
+
564
+ this.options.captureArea = normalizedArea;
565
+ this.options.displayId = Number(targetDisplay.id);
566
+ this.recordingDisplayInfo = {
567
+ displayId: Number(targetDisplay.id),
568
+ x: Number(targetDisplay.x) || 0,
569
+ y: Number(targetDisplay.y) || 0,
570
+ width: Number(targetDisplay.width) || 0,
571
+ height: Number(targetDisplay.height) || 0,
572
+ logicalWidth: Number(targetDisplay.width) || 0,
573
+ logicalHeight: Number(targetDisplay.height) || 0,
574
+ };
575
+ };
576
+
342
577
  // WindowId varsa captureArea'yı otomatik ayarla
343
578
  if (this.options.windowId && !this.options.captureArea) {
344
579
  try {
345
580
  const windows = await this.getWindows();
346
- const displays = await this.getDisplays();
581
+ const displays = await getCachedDisplays();
347
582
  const targetWindow = windows.find(
348
583
  (w) => w.id === this.options.windowId
349
584
  );
@@ -442,7 +677,7 @@ class MacRecorder extends EventEmitter {
442
677
  // Ensure recordingDisplayInfo is always set for cursor tracking
443
678
  if (!this.recordingDisplayInfo) {
444
679
  try {
445
- const displays = await this.getDisplays();
680
+ const displays = await getCachedDisplays();
446
681
  let targetDisplay;
447
682
 
448
683
  if (this.options.displayId !== null) {
@@ -470,6 +705,11 @@ class MacRecorder extends EventEmitter {
470
705
  }
471
706
  }
472
707
 
708
+ // Normalize capture area AFTER automatic window capture logic
709
+ if (this.options.captureArea) {
710
+ await normalizeCaptureArea();
711
+ }
712
+
473
713
  // Çıkış dizinini oluştur
474
714
  const outputDir = path.dirname(outputPath);
475
715
  if (!fs.existsSync(outputDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.49",
3
+ "version": "2.21.51",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [