node-mac-recorder 2.17.8 → 2.17.10

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/index.js CHANGED
@@ -31,6 +31,8 @@ class MacRecorder extends EventEmitter {
31
31
  this.cursorCaptureFile = null;
32
32
  this.cursorCaptureStartTime = null;
33
33
  this.cursorCaptureFirstWrite = true;
34
+ this.cursorRecordingBaseTime = null;
35
+ this.cursorRecordingOffsetMs = 0;
34
36
  this.lastCapturedData = null;
35
37
  this.cursorDisplayInfo = null;
36
38
  this.recordingDisplayInfo = null;
@@ -41,7 +43,7 @@ class MacRecorder extends EventEmitter {
41
43
  quality: "medium",
42
44
  frameRate: 30,
43
45
  captureArea: null, // { x, y, width, height }
44
- captureCursor: false, // Default olarak cursor gizli
46
+ captureCursor: false, // Default: cursor hidden in video
45
47
  showClicks: false,
46
48
  displayId: null, // Hangi ekranı kaydedeceği (null = ana ekran)
47
49
  windowId: null, // Hangi pencereyi kaydedeceği (null = tam ekran)
@@ -79,16 +81,29 @@ class MacRecorder extends EventEmitter {
79
81
  */
80
82
  async getDisplays() {
81
83
  const displays = nativeBinding.getDisplays();
82
- return displays.map((display, index) => ({
83
- id: display.id, // Use the actual display ID from native code
84
- name: display.name,
85
- width: display.width,
86
- height: display.height,
87
- x: display.x,
88
- y: display.y,
89
- isPrimary: display.isPrimary,
90
- resolution: `${display.width}x${display.height}`,
91
- }));
84
+ return displays.map((display, index) => {
85
+ const logicalWidth = Number(display.width) || 0;
86
+ const logicalHeight = Number(display.height) || 0;
87
+ const pixelWidth = Number(display.pixelWidth) || logicalWidth;
88
+ const pixelHeight = Number(display.pixelHeight) || logicalHeight;
89
+ const scaleX = display.scaleX ? Number(display.scaleX) : (logicalWidth > 0 ? pixelWidth / logicalWidth : 1);
90
+ const scaleY = display.scaleY ? Number(display.scaleY) : (logicalHeight > 0 ? pixelHeight / logicalHeight : 1);
91
+
92
+ return {
93
+ id: display.id, // Use the actual display ID from native code
94
+ name: display.name,
95
+ width: logicalWidth,
96
+ height: logicalHeight,
97
+ pixelWidth,
98
+ pixelHeight,
99
+ scaleX,
100
+ scaleY,
101
+ x: display.x,
102
+ y: display.y,
103
+ isPrimary: display.isPrimary,
104
+ resolution: `${logicalWidth}x${logicalHeight}`,
105
+ };
106
+ });
92
107
  }
93
108
 
94
109
  /**
@@ -252,16 +267,29 @@ class MacRecorder extends EventEmitter {
252
267
 
253
268
  // Recording için display bilgisini sakla (cursor capture için)
254
269
  const targetDisplay = displays.find(d => d.id === targetDisplayId);
255
- this.recordingDisplayInfo = {
256
- displayId: targetDisplayId,
257
- x: targetDisplay.x,
258
- y: targetDisplay.y,
259
- width: parseInt(targetDisplay.resolution.split("x")[0]),
260
- height: parseInt(targetDisplay.resolution.split("x")[1]),
261
- // Add scaling information for cursor coordinate transformation
262
- logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
263
- logicalHeight: parseInt(targetDisplay.resolution.split("x")[1]),
264
- };
270
+ if (targetDisplay) {
271
+ const logicalWidth = Number(targetDisplay.width) || parseInt(targetDisplay.resolution.split("x")[0]);
272
+ const logicalHeight = Number(targetDisplay.height) || parseInt(targetDisplay.resolution.split("x")[1]);
273
+ const pixelWidth = Number(targetDisplay.pixelWidth) || logicalWidth;
274
+ const pixelHeight = Number(targetDisplay.pixelHeight) || logicalHeight;
275
+ const scaleX = Number.isFinite(targetDisplay.scaleX) ? Number(targetDisplay.scaleX) : (logicalWidth > 0 ? pixelWidth / logicalWidth : 1);
276
+ const scaleY = Number.isFinite(targetDisplay.scaleY) ? Number(targetDisplay.scaleY) : (logicalHeight > 0 ? pixelHeight / logicalHeight : 1);
277
+
278
+ this.recordingDisplayInfo = {
279
+ displayId: targetDisplayId,
280
+ x: targetDisplay.x,
281
+ y: targetDisplay.y,
282
+ width: logicalWidth,
283
+ height: logicalHeight,
284
+ pixelWidth,
285
+ pixelHeight,
286
+ scaleX,
287
+ scaleY,
288
+ // Add scaling information for cursor coordinate transformation
289
+ logicalWidth,
290
+ logicalHeight,
291
+ };
292
+ }
265
293
  }
266
294
 
267
295
  this.options.captureArea = {
@@ -289,15 +317,26 @@ class MacRecorder extends EventEmitter {
289
317
  const displays = await this.getDisplays();
290
318
  const targetDisplay = displays.find(d => d.id === this.options.displayId);
291
319
  if (targetDisplay) {
320
+ const logicalWidth = Number(targetDisplay.width) || parseInt(targetDisplay.resolution.split("x")[0]);
321
+ const logicalHeight = Number(targetDisplay.height) || parseInt(targetDisplay.resolution.split("x")[1]);
322
+ const pixelWidth = Number(targetDisplay.pixelWidth) || logicalWidth;
323
+ const pixelHeight = Number(targetDisplay.pixelHeight) || logicalHeight;
324
+ const scaleX = Number.isFinite(targetDisplay.scaleX) ? Number(targetDisplay.scaleX) : (logicalWidth > 0 ? pixelWidth / logicalWidth : 1);
325
+ const scaleY = Number.isFinite(targetDisplay.scaleY) ? Number(targetDisplay.scaleY) : (logicalHeight > 0 ? pixelHeight / logicalHeight : 1);
326
+
292
327
  this.recordingDisplayInfo = {
293
328
  displayId: this.options.displayId,
294
329
  x: targetDisplay.x,
295
330
  y: targetDisplay.y,
296
- width: parseInt(targetDisplay.resolution.split("x")[0]),
297
- height: parseInt(targetDisplay.resolution.split("x")[1]),
331
+ width: logicalWidth,
332
+ height: logicalHeight,
333
+ pixelWidth,
334
+ pixelHeight,
335
+ scaleX,
336
+ scaleY,
298
337
  // Add scaling information for cursor coordinate transformation
299
- logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
300
- logicalHeight: parseInt(targetDisplay.resolution.split("x")[1]),
338
+ logicalWidth,
339
+ logicalHeight,
301
340
  };
302
341
  }
303
342
  } catch (error) {
@@ -356,63 +395,108 @@ class MacRecorder extends EventEmitter {
356
395
  this.isRecording = true;
357
396
  this.recordingStartTime = Date.now();
358
397
 
359
- // Start cursor tracking automatically with recording
360
- let cursorOptions = {};
361
-
362
- // For window recording, use the original window coordinates (not display-relative captureArea)
363
- if (this.options.windowId) {
364
- // Use cached window info from the earlier window detection
365
- this.getWindows().then(windows => {
366
- const targetWindow = windows.find(w => w.id === this.options.windowId);
367
- if (targetWindow) {
368
- // Restart cursor capture with correct window coordinates
369
- if (this.cursorCaptureInterval) {
370
- this.stopCursorCapture().then(() => {
371
- this.startCursorCapture(cursorFilePath, {
372
- windowRelative: true,
373
- windowInfo: {
374
- x: targetWindow.x, // Global window X coordinate
375
- y: targetWindow.y, // Global window Y coordinate
376
- width: targetWindow.width,
377
- height: targetWindow.height,
378
- displayId: this.options.displayId,
379
- originalWindow: targetWindow,
380
- captureArea: this.options.captureArea
381
- }
382
- }).catch(cursorError => {
383
- console.warn('Cursor tracking failed to restart:', cursorError.message);
384
- });
385
- }).catch(stopError => {
386
- console.warn('Failed to stop cursor capture:', stopError.message);
387
- });
388
- } else {
398
+ // Start cursor tracking automatically with recording
399
+ // For window recording, align cursor coordinates against actual capture geometry
400
+ if (this.options.windowId) {
401
+ const waitForActiveCaptureInfo = async () => {
402
+ if (typeof nativeBinding.getActiveCaptureInfo !== "function") {
403
+ return null;
404
+ }
405
+
406
+ const maxAttempts = 30;
407
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
408
+ try {
409
+ const info = nativeBinding.getActiveCaptureInfo();
410
+ if (info && typeof info === "object" && Object.keys(info).length > 0) {
411
+ return info;
412
+ }
413
+ } catch (nativeInfoError) {
414
+ if (attempt === 0) {
415
+ console.warn('getActiveCaptureInfo failed:', nativeInfoError.message);
416
+ }
417
+ }
418
+
419
+ await new Promise((resolve) => setTimeout(resolve, 20));
420
+ }
421
+
422
+ return null;
423
+ };
424
+
425
+ const startWindowCursorTracking = async () => {
426
+ try {
427
+ const [windows, activeCaptureInfo] = await Promise.all([
428
+ this.getWindows(),
429
+ waitForActiveCaptureInfo()
430
+ ]);
431
+
432
+ const targetWindow = windows.find(w => w.id === this.options.windowId);
433
+ if (!targetWindow) {
434
+ throw new Error('Target window not found for cursor tracking');
435
+ }
436
+
437
+ const hasNumber = (value) => typeof value === "number" && Number.isFinite(value);
438
+ const targetDisplay = this.recordingDisplayInfo;
439
+ const nativeWindowFrame = activeCaptureInfo?.type === 'window' ? activeCaptureInfo.window?.frame : null;
440
+
441
+ const windowWidth = hasNumber(nativeWindowFrame?.width) ? nativeWindowFrame.width : targetWindow.width;
442
+ const windowHeight = hasNumber(nativeWindowFrame?.height) ? nativeWindowFrame.height : targetWindow.height;
443
+
444
+ let captureAreaForCursor = this.options.captureArea;
445
+ let globalWindowX = targetWindow.x;
446
+ let globalWindowY = targetWindow.y;
447
+
448
+ if (nativeWindowFrame && hasNumber(nativeWindowFrame.x) && hasNumber(nativeWindowFrame.y)) {
449
+ globalWindowX = nativeWindowFrame.x;
450
+ globalWindowY = nativeWindowFrame.y;
451
+
452
+ if (targetDisplay && hasNumber(targetDisplay.x) && hasNumber(targetDisplay.y)) {
453
+ captureAreaForCursor = {
454
+ x: nativeWindowFrame.x - targetDisplay.x,
455
+ y: nativeWindowFrame.y - targetDisplay.y,
456
+ width: windowWidth,
457
+ height: windowHeight
458
+ };
459
+ this.options.captureArea = captureAreaForCursor;
460
+ }
461
+ }
462
+
463
+ // Start cursor capture with geometry-aware window-relative tracking
389
464
  this.startCursorCapture(cursorFilePath, {
390
465
  windowRelative: true,
391
466
  windowInfo: {
392
- x: targetWindow.x, // Global window X coordinate
393
- y: targetWindow.y, // Global window Y coordinate
394
- width: targetWindow.width,
395
- height: targetWindow.height,
467
+ x: globalWindowX,
468
+ y: globalWindowY,
469
+ width: windowWidth,
470
+ height: windowHeight,
396
471
  displayId: this.options.displayId,
472
+ captureArea: captureAreaForCursor,
397
473
  originalWindow: targetWindow,
398
- captureArea: this.options.captureArea
474
+ targetDisplay,
475
+ nativeFrame: nativeWindowFrame
399
476
  }
400
477
  }).catch(cursorError => {
478
+ console.warn('Window cursor tracking failed:', cursorError.message);
479
+ // Fallback to display recording
480
+ this.startCursorCapture(cursorFilePath).catch(fallbackError => {
481
+ console.warn('Fallback cursor tracking failed:', fallbackError.message);
482
+ });
483
+ });
484
+ } catch (error) {
485
+ console.warn('Could not prepare window cursor tracking:', error.message);
486
+ this.startCursorCapture(cursorFilePath).catch(cursorError => {
401
487
  console.warn('Cursor tracking failed to start:', cursorError.message);
402
488
  });
403
489
  }
404
- }
405
- }).catch(error => {
406
- console.warn('Could not get window info for cursor tracking:', error.message);
407
- // Fallback to basic cursor tracking
408
- this.startCursorCapture(cursorFilePath, cursorOptions).catch(cursorError => {
409
- console.warn('Cursor tracking failed to start:', cursorError.message);
410
- });
411
- });
412
- } else {
413
- // For display recording, use basic cursor tracking
414
- this.startCursorCapture(cursorFilePath, cursorOptions).catch(cursorError => {
415
- console.warn('Cursor tracking failed to start:', cursorError.message);
490
+ };
491
+
492
+ startWindowCursorTracking();
493
+ } else {
494
+ // For display recording, use display-relative cursor tracking
495
+ this.startCursorCapture(cursorFilePath, {
496
+ displayRelative: true,
497
+ displayInfo: this.recordingDisplayInfo
498
+ }).catch(cursorError => {
499
+ console.warn('Display cursor tracking failed:', cursorError.message);
416
500
  });
417
501
  }
418
502
 
@@ -717,46 +801,157 @@ class MacRecorder extends EventEmitter {
717
801
 
718
802
  // Koordinat sistemi belirle: window-relative, display-relative veya global
719
803
  if (options.windowRelative && options.windowInfo) {
720
- // Window recording için display'e relative captureArea koordinatlarını kullan
721
- const captureArea = options.windowInfo.captureArea;
722
- if (captureArea) {
723
- // captureArea zaten display-relative koordinatlar
724
- this.cursorDisplayInfo = {
725
- displayId: options.windowInfo.displayId || null,
726
- x: captureArea.x, // Display-relative X
727
- y: captureArea.y, // Display-relative Y
728
- width: captureArea.width,
729
- height: captureArea.height,
730
- windowRelative: true,
731
- windowInfo: options.windowInfo
732
- };
733
- } else {
734
- // Fallback: orijinal window koordinatları
735
- this.cursorDisplayInfo = {
736
- displayId: options.windowInfo.displayId || null,
737
- x: options.windowInfo.x || 0,
738
- y: options.windowInfo.y || 0,
739
- width: options.windowInfo.width,
740
- height: options.windowInfo.height,
741
- windowRelative: true,
742
- windowInfo: options.windowInfo
743
- };
804
+ const windowInfo = options.windowInfo;
805
+ const targetDisplay = windowInfo.targetDisplay || this.recordingDisplayInfo || null;
806
+ const captureArea = windowInfo.captureArea || null;
807
+ const hasNumber = (value) => typeof value === "number" && Number.isFinite(value);
808
+ const displayScaleX = targetDisplay && hasNumber(targetDisplay.scaleX)
809
+ ? Number(targetDisplay.scaleX)
810
+ : (targetDisplay && hasNumber(targetDisplay.pixelWidth) && hasNumber(targetDisplay.width)
811
+ ? targetDisplay.pixelWidth / targetDisplay.width
812
+ : 1);
813
+ const displayScaleY = targetDisplay && hasNumber(targetDisplay.scaleY)
814
+ ? Number(targetDisplay.scaleY)
815
+ : (targetDisplay && hasNumber(targetDisplay.pixelHeight) && hasNumber(targetDisplay.height)
816
+ ? targetDisplay.pixelHeight / targetDisplay.height
817
+ : 1);
818
+
819
+ let globalX = hasNumber(windowInfo.x) ? windowInfo.x : null;
820
+ let globalY = hasNumber(windowInfo.y) ? windowInfo.y : null;
821
+
822
+ if (captureArea && targetDisplay) {
823
+ if (!hasNumber(globalX) && hasNumber(captureArea.x) && hasNumber(targetDisplay.x)) {
824
+ globalX = targetDisplay.x + captureArea.x;
825
+ }
826
+ if (!hasNumber(globalY) && hasNumber(captureArea.y) && hasNumber(targetDisplay.y)) {
827
+ globalY = targetDisplay.y + captureArea.y;
828
+ }
744
829
  }
830
+
831
+ if (!hasNumber(globalX)) {
832
+ if (captureArea && hasNumber(captureArea.x) && targetDisplay && hasNumber(targetDisplay.x)) {
833
+ globalX = targetDisplay.x + captureArea.x;
834
+ } else {
835
+ globalX = hasNumber(captureArea?.x) ? captureArea.x : 0;
836
+ }
837
+ }
838
+
839
+ if (!hasNumber(globalY)) {
840
+ if (captureArea && hasNumber(captureArea.y) && targetDisplay && hasNumber(targetDisplay.y)) {
841
+ globalY = targetDisplay.y + captureArea.y;
842
+ } else {
843
+ globalY = hasNumber(captureArea?.y) ? captureArea.y : 0;
844
+ }
845
+ }
846
+
847
+ const displayOffsetX = captureArea && hasNumber(captureArea.x)
848
+ ? captureArea.x
849
+ : (targetDisplay && hasNumber(globalX) && hasNumber(targetDisplay.x)
850
+ ? globalX - targetDisplay.x
851
+ : globalX ?? 0);
852
+ const displayOffsetY = captureArea && hasNumber(captureArea.y)
853
+ ? captureArea.y
854
+ : (targetDisplay && hasNumber(globalY) && hasNumber(targetDisplay.y)
855
+ ? globalY - targetDisplay.y
856
+ : globalY ?? 0);
857
+
858
+ const windowPixelWidth = hasNumber(windowInfo.width) ? windowInfo.width * displayScaleX : null;
859
+ const windowPixelHeight = hasNumber(windowInfo.height) ? windowInfo.height * displayScaleY : null;
860
+ const captureAreaInfo = captureArea
861
+ ? {
862
+ ...captureArea,
863
+ pixelX: hasNumber(captureArea.x) ? captureArea.x * displayScaleX : null,
864
+ pixelY: hasNumber(captureArea.y) ? captureArea.y * displayScaleY : null,
865
+ pixelWidth: hasNumber(captureArea.width) ? captureArea.width * displayScaleX : windowPixelWidth,
866
+ pixelHeight: hasNumber(captureArea.height) ? captureArea.height * displayScaleY : windowPixelHeight,
867
+ }
868
+ : null;
869
+
870
+ const enrichedWindowInfo = {
871
+ ...windowInfo,
872
+ globalX,
873
+ globalY,
874
+ displayOffsetX,
875
+ displayOffsetY,
876
+ scaleX: displayScaleX,
877
+ scaleY: displayScaleY,
878
+ pixelWidth: windowPixelWidth,
879
+ pixelHeight: windowPixelHeight,
880
+ };
881
+
882
+ if (captureAreaInfo) {
883
+ enrichedWindowInfo.captureArea = captureAreaInfo;
884
+ }
885
+
886
+ this.cursorDisplayInfo = {
887
+ displayId:
888
+ windowInfo.displayId ??
889
+ targetDisplay?.displayId ??
890
+ targetDisplay?.id ??
891
+ null,
892
+ x: globalX,
893
+ y: globalY,
894
+ width: windowInfo.width,
895
+ height: windowInfo.height,
896
+ pixelWidth: windowPixelWidth,
897
+ pixelHeight: windowPixelHeight,
898
+ scaleX: displayScaleX,
899
+ scaleY: displayScaleY,
900
+ displayRelativeX: displayOffsetX,
901
+ displayRelativeY: displayOffsetY,
902
+ windowRelative: true,
903
+ windowInfo: {
904
+ ...enrichedWindowInfo
905
+ },
906
+ targetDisplay,
907
+ captureArea: captureAreaInfo
908
+ };
909
+ } else if (options.displayRelative && options.displayInfo) {
910
+ // Display recording: Use display-relative coordinates
911
+ const pixelWidth = Number(options.displayInfo.pixelWidth) || Number(options.displayInfo.width);
912
+ const pixelHeight = Number(options.displayInfo.pixelHeight) || Number(options.displayInfo.height);
913
+ const scaleX = Number.isFinite(options.displayInfo.scaleX)
914
+ ? Number(options.displayInfo.scaleX)
915
+ : (Number(options.displayInfo.width) > 0 ? pixelWidth / Number(options.displayInfo.width) : 1);
916
+ const scaleY = Number.isFinite(options.displayInfo.scaleY)
917
+ ? Number(options.displayInfo.scaleY)
918
+ : (Number(options.displayInfo.height) > 0 ? pixelHeight / Number(options.displayInfo.height) : 1);
919
+
920
+ this.cursorDisplayInfo = {
921
+ displayId: options.displayInfo.displayId,
922
+ x: options.displayInfo.x,
923
+ y: options.displayInfo.y,
924
+ width: options.displayInfo.width,
925
+ height: options.displayInfo.height,
926
+ pixelWidth,
927
+ pixelHeight,
928
+ scaleX,
929
+ scaleY,
930
+ displayRelative: true
931
+ };
745
932
  } else if (this.recordingDisplayInfo) {
746
- // Recording başlatılmışsa o display'i kullan
933
+ // Fallback: Use recording display info if available
747
934
  this.cursorDisplayInfo = this.recordingDisplayInfo;
748
935
  } else {
749
- // Main display bilgisini al (her zaman relative koordinatlar için)
936
+ // Final fallback: Main display global coordinates
750
937
  try {
751
938
  const displays = await this.getDisplays();
752
939
  const mainDisplay = displays.find((d) => d.isPrimary) || displays[0];
753
940
  if (mainDisplay) {
754
941
  this.cursorDisplayInfo = {
755
- displayId: 0,
942
+ displayId: mainDisplay.id,
756
943
  x: mainDisplay.x,
757
944
  y: mainDisplay.y,
758
- width: parseInt(mainDisplay.resolution.split("x")[0]),
759
- height: parseInt(mainDisplay.resolution.split("x")[1]),
945
+ width: Number(mainDisplay.width) || parseInt(mainDisplay.resolution.split("x")[0]),
946
+ height: Number(mainDisplay.height) || parseInt(mainDisplay.resolution.split("x")[1]),
947
+ pixelWidth: Number(mainDisplay.pixelWidth) || Number(mainDisplay.width) || parseInt(mainDisplay.resolution.split("x")[0]),
948
+ pixelHeight: Number(mainDisplay.pixelHeight) || Number(mainDisplay.height) || parseInt(mainDisplay.resolution.split("x")[1]),
949
+ scaleX: Number.isFinite(mainDisplay.scaleX)
950
+ ? Number(mainDisplay.scaleX)
951
+ : ((Number(mainDisplay.width) || 0) > 0 ? (Number(mainDisplay.pixelWidth) || Number(mainDisplay.width)) / Number(mainDisplay.width) : 1),
952
+ scaleY: Number.isFinite(mainDisplay.scaleY)
953
+ ? Number(mainDisplay.scaleY)
954
+ : ((Number(mainDisplay.height) || 0) > 0 ? (Number(mainDisplay.pixelHeight) || Number(mainDisplay.height)) / Number(mainDisplay.height) : 1),
760
955
  };
761
956
  }
762
957
  } catch (error) {
@@ -772,86 +967,130 @@ class MacRecorder extends EventEmitter {
772
967
  fs.writeFileSync(filepath, "[");
773
968
 
774
969
  this.cursorCaptureFile = filepath;
775
- this.cursorCaptureStartTime = Date.now();
970
+ const captureStart = Date.now();
971
+ const recordingBase = this.isRecording && this.recordingStartTime
972
+ ? this.recordingStartTime
973
+ : captureStart;
974
+ this.cursorCaptureStartTime = captureStart;
975
+ this.cursorRecordingBaseTime = recordingBase;
976
+ this.cursorRecordingOffsetMs = Math.max(0, captureStart - recordingBase);
776
977
  this.cursorCaptureFirstWrite = true;
777
978
  this.lastCapturedData = null;
778
979
 
779
980
  // JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
780
981
  this.cursorCaptureInterval = setInterval(() => {
781
982
  try {
983
+ const now = Date.now();
782
984
  const position = nativeBinding.getCursorPosition();
783
- const timestamp = Date.now() - this.cursorCaptureStartTime;
985
+ const captureTimestamp = this.cursorCaptureStartTime ? now - this.cursorCaptureStartTime : 0;
986
+ const recordingBase = this.cursorRecordingBaseTime || this.cursorCaptureStartTime || now;
987
+ const recordingTimestamp = Math.max(0, now - recordingBase);
784
988
 
785
- // Global koordinatları relative koordinatlara çevir
989
+ // Transform coordinates based on recording type
786
990
  let x = position.x;
787
991
  let y = position.y;
788
992
  let coordinateSystem = "global";
789
993
 
790
994
  if (this.cursorDisplayInfo) {
791
995
  if (this.cursorDisplayInfo.windowRelative) {
792
- // Window recording: cursor'ı önce target display'e relative yap, sonra captureArea'ya relative yap
793
- const targetDisplay = this.recordingDisplayInfo;
794
- if (targetDisplay) {
795
- // 1. Global -> Display-relative
996
+ // Window recording: base on display-relative offsets when available
997
+ const captureArea = this.cursorDisplayInfo.captureArea || this.cursorDisplayInfo.windowInfo?.captureArea;
998
+ const targetDisplay = this.cursorDisplayInfo.targetDisplay || this.recordingDisplayInfo;
999
+ const hasCoord = (value) => typeof value === "number" && Number.isFinite(value);
1000
+
1001
+ if (targetDisplay && captureArea && hasCoord(captureArea.x) && hasCoord(captureArea.y)) {
796
1002
  const displayRelativeX = position.x - targetDisplay.x;
797
1003
  const displayRelativeY = position.y - targetDisplay.y;
798
-
799
- // 2. Display-relative -> CaptureArea-relative (window-relative)
800
- x = displayRelativeX - this.cursorDisplayInfo.x;
801
- y = displayRelativeY - this.cursorDisplayInfo.y;
1004
+ x = displayRelativeX - captureArea.x;
1005
+ y = displayRelativeY - captureArea.y;
1006
+ } else if (
1007
+ hasCoord(this.cursorDisplayInfo.displayRelativeX) &&
1008
+ hasCoord(this.cursorDisplayInfo.displayRelativeY) &&
1009
+ targetDisplay
1010
+ ) {
1011
+ const displayRelativeX = position.x - targetDisplay.x;
1012
+ const displayRelativeY = position.y - targetDisplay.y;
1013
+ x = displayRelativeX - this.cursorDisplayInfo.displayRelativeX;
1014
+ y = displayRelativeY - this.cursorDisplayInfo.displayRelativeY;
802
1015
  } else {
803
- // Fallback: doğrudan offset çıkar
1016
+ // Fallback: global offsets
804
1017
  x = position.x - this.cursorDisplayInfo.x;
805
1018
  y = position.y - this.cursorDisplayInfo.y;
806
1019
  }
807
- } else {
808
- // Display recording: doğrudan offset çıkar
1020
+ coordinateSystem = "window-relative";
1021
+
1022
+ // Window bounds check - skip if cursor is outside window
1023
+ if (x < 0 || y < 0 || x >= this.cursorDisplayInfo.width || y >= this.cursorDisplayInfo.height) {
1024
+ return; // Skip frame - cursor outside window
1025
+ }
1026
+ } else if (this.cursorDisplayInfo.displayRelative) {
1027
+ // Display recording: Transform global → display-relative coordinates
809
1028
  x = position.x - this.cursorDisplayInfo.x;
810
1029
  y = position.y - this.cursorDisplayInfo.y;
811
- }
1030
+ coordinateSystem = "display-relative";
812
1031
 
813
- if (this.cursorDisplayInfo.windowRelative) {
814
- // Window-relative koordinatlar
815
- coordinateSystem = "window-relative";
816
-
817
- // Window bounds kontrolü - cursor window dışındaysa kaydetme
818
- if (
819
- x < 0 ||
820
- y < 0 ||
821
- x >= this.cursorDisplayInfo.width ||
822
- y >= this.cursorDisplayInfo.height
823
- ) {
824
- return; // Bu frame'i skip et - cursor pencere dışında
1032
+ // Display bounds check - skip if cursor is outside display
1033
+ if (x < 0 || y < 0 || x >= this.cursorDisplayInfo.width || y >= this.cursorDisplayInfo.height) {
1034
+ return; // Skip frame - cursor outside display
825
1035
  }
826
1036
  } else {
827
- // Display-relative koordinatlar
1037
+ // Legacy fallback: Use global coordinates with basic offset
1038
+ x = position.x - this.cursorDisplayInfo.x;
1039
+ y = position.y - this.cursorDisplayInfo.y;
828
1040
  coordinateSystem = "display-relative";
829
-
830
- // Display bounds kontrolü
831
- if (
832
- x < 0 ||
833
- y < 0 ||
834
- x >= this.cursorDisplayInfo.width ||
835
- y >= this.cursorDisplayInfo.height
836
- ) {
837
- return; // Bu frame'i skip et - cursor display dışında
838
- }
839
1041
  }
840
1042
  }
841
1043
 
1044
+ const logicalX = x;
1045
+ const logicalY = y;
1046
+ const scaleX = Number.isFinite(this.cursorDisplayInfo?.windowInfo?.scaleX)
1047
+ ? Number(this.cursorDisplayInfo.windowInfo.scaleX)
1048
+ : (Number.isFinite(this.cursorDisplayInfo?.scaleX) ? Number(this.cursorDisplayInfo.scaleX) : 1);
1049
+ const scaleY = Number.isFinite(this.cursorDisplayInfo?.windowInfo?.scaleY)
1050
+ ? Number(this.cursorDisplayInfo.windowInfo.scaleY)
1051
+ : (Number.isFinite(this.cursorDisplayInfo?.scaleY) ? Number(this.cursorDisplayInfo.scaleY) : 1);
1052
+ const pixelX = logicalX * scaleX;
1053
+ const pixelY = logicalY * scaleY;
1054
+
842
1055
  const cursorData = {
843
- x: x,
844
- y: y,
845
- timestamp: timestamp,
846
- unixTimeMs: Date.now(),
1056
+ x: logicalX,
1057
+ y: logicalY,
1058
+ pixelX,
1059
+ pixelY,
1060
+ scaleX,
1061
+ scaleY,
1062
+ timestamp: recordingTimestamp,
1063
+ captureTimestamp,
1064
+ recordingOffsetMs: this.cursorRecordingOffsetMs,
1065
+ unixTimeMs: now,
847
1066
  cursorType: position.cursorType,
848
1067
  type: position.eventType || "move",
849
1068
  coordinateSystem: coordinateSystem,
1069
+ // Include recording context for window-relative coordinates
850
1070
  ...(this.cursorDisplayInfo?.windowRelative && {
851
1071
  windowInfo: {
852
1072
  width: this.cursorDisplayInfo.width,
853
1073
  height: this.cursorDisplayInfo.height,
854
- originalWindow: this.cursorDisplayInfo.windowInfo
1074
+ pixelWidth: this.cursorDisplayInfo.pixelWidth ?? (this.cursorDisplayInfo.width * scaleX),
1075
+ pixelHeight: this.cursorDisplayInfo.pixelHeight ?? (this.cursorDisplayInfo.height * scaleY),
1076
+ displayId: this.cursorDisplayInfo.displayId,
1077
+ displayOffsetX: this.cursorDisplayInfo.displayRelativeX,
1078
+ displayOffsetY: this.cursorDisplayInfo.displayRelativeY,
1079
+ scaleX,
1080
+ scaleY,
1081
+ captureArea: this.cursorDisplayInfo.captureArea || null
1082
+ }
1083
+ }),
1084
+ // Include display context for display-relative coordinates
1085
+ ...(this.cursorDisplayInfo?.displayRelative && {
1086
+ displayInfo: {
1087
+ displayId: this.cursorDisplayInfo.displayId,
1088
+ width: this.cursorDisplayInfo.width,
1089
+ height: this.cursorDisplayInfo.height,
1090
+ pixelWidth: this.cursorDisplayInfo.pixelWidth ?? (this.cursorDisplayInfo.width * scaleX),
1091
+ pixelHeight: this.cursorDisplayInfo.pixelHeight ?? (this.cursorDisplayInfo.height * scaleY),
1092
+ scaleX,
1093
+ scaleY
855
1094
  }
856
1095
  })
857
1096
  };
@@ -910,6 +1149,8 @@ class MacRecorder extends EventEmitter {
910
1149
  this.cursorCaptureStartTime = null;
911
1150
  this.cursorCaptureFirstWrite = true;
912
1151
  this.cursorDisplayInfo = null;
1152
+ this.cursorRecordingBaseTime = null;
1153
+ this.cursorRecordingOffsetMs = 0;
913
1154
 
914
1155
  this.emit("cursorCaptureStopped");
915
1156
  resolve(true);
@@ -1035,6 +1276,8 @@ class MacRecorder extends EventEmitter {
1035
1276
  isCapturing: !!this.cursorCaptureInterval,
1036
1277
  outputFile: this.cursorCaptureFile || null,
1037
1278
  startTime: this.cursorCaptureStartTime || null,
1279
+ recordingBaseTime: this.cursorRecordingBaseTime || null,
1280
+ recordingOffsetMs: this.cursorRecordingOffsetMs || 0,
1038
1281
  displayInfo: this.cursorDisplayInfo || null,
1039
1282
  };
1040
1283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.17.8",
3
+ "version": "2.17.10",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -4,6 +4,7 @@
4
4
  #import <CoreGraphics/CoreGraphics.h>
5
5
  #import <ImageIO/ImageIO.h>
6
6
  #import <CoreAudio/CoreAudio.h>
7
+ #include <cstring>
7
8
 
8
9
  // Import screen capture (ScreenCaptureKit only)
9
10
  #import "screen_capture_kit.h"
@@ -59,6 +60,69 @@ void cleanupRecording() {
59
60
  g_isRecording = false;
60
61
  }
61
62
 
63
+ static Napi::Value NSObjectToNapiValue(Napi::Env env, id value) {
64
+ if (!value || value == [NSNull null]) {
65
+ return env.Null();
66
+ }
67
+
68
+ if ([value isKindOfClass:[NSDictionary class]]) {
69
+ NSDictionary *dict = (NSDictionary *)value;
70
+ Napi::Object obj = Napi::Object::New(env);
71
+
72
+ for (id key in dict) {
73
+ id nestedValue = [dict objectForKey:key];
74
+ NSString *keyString = nil;
75
+ if ([key isKindOfClass:[NSString class]]) {
76
+ keyString = (NSString *)key;
77
+ } else {
78
+ keyString = [key description];
79
+ }
80
+
81
+ if (!keyString) {
82
+ continue;
83
+ }
84
+
85
+ obj.Set(Napi::String::New(env, [keyString UTF8String]), NSObjectToNapiValue(env, nestedValue));
86
+ }
87
+
88
+ return obj;
89
+ }
90
+
91
+ if ([value isKindOfClass:[NSArray class]]) {
92
+ NSArray *array = (NSArray *)value;
93
+ Napi::Array result = Napi::Array::New(env, array.count);
94
+
95
+ for (NSUInteger i = 0; i < array.count; i++) {
96
+ result.Set(i, NSObjectToNapiValue(env, array[i]));
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ if ([value isKindOfClass:[NSNumber class]]) {
103
+ NSNumber *number = (NSNumber *)value;
104
+ const char *type = [number objCType];
105
+
106
+ if (strcmp(type, @encode(BOOL)) == 0) {
107
+ return Napi::Boolean::New(env, [number boolValue]);
108
+ }
109
+
110
+ return Napi::Number::New(env, [number doubleValue]);
111
+ }
112
+
113
+ if ([value isKindOfClass:[NSString class]]) {
114
+ NSString *stringValue = (NSString *)value;
115
+ return Napi::String::New(env, [stringValue UTF8String]);
116
+ }
117
+
118
+ if ([value isKindOfClass:[NSDate class]]) {
119
+ NSDate *dateValue = (NSDate *)value;
120
+ return Napi::Number::New(env, [dateValue timeIntervalSince1970] * 1000.0);
121
+ }
122
+
123
+ return Napi::String::New(env, [[value description] UTF8String]);
124
+ }
125
+
62
126
  // NAPI Function: Start Recording
63
127
  Napi::Value StartRecording(const Napi::CallbackInfo& info) {
64
128
  Napi::Env env = info.Env();
@@ -82,7 +146,7 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
82
146
 
83
147
  // Options parsing
84
148
  CGRect captureRect = CGRectNull;
85
- bool captureCursor = false; // Default olarak cursor gizli
149
+ bool captureCursor = false; // Default: cursor hidden in video
86
150
  bool includeMicrophone = false; // Default olarak mikrofon kapalı
87
151
  bool includeSystemAudio = true; // Default olarak sistem sesi açık
88
152
  CGDirectDisplayID displayID = CGMainDisplayID(); // Default ana ekran
@@ -205,14 +269,12 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
205
269
  NSLog(@"🔧 FORCE_AVFOUNDATION environment variable detected - skipping ScreenCaptureKit");
206
270
  }
207
271
 
208
- // Electron-first priority: This application is built for Electron.js
209
- // macOS 15+ ScreenCaptureKit (including Electron)
210
- // macOS 14/13 → AVFoundation (including Electron)
211
- if (isM15Plus && !forceAVFoundation) {
272
+ // Try ScreenCaptureKit on macOS 14+ (Electron supported) with robust fallback
273
+ if (isM14Plus && !forceAVFoundation) {
212
274
  if (isElectron) {
213
- NSLog(@"⚡ ELECTRON PRIORITY: macOS 15+ Electron → ScreenCaptureKit with full support");
275
+ NSLog(@"⚡ ELECTRON PRIORITY: macOS 14+ Electron → ScreenCaptureKit with full support (fallbacks enabled)");
214
276
  } else {
215
- NSLog(@"✅ macOS 15+ Node.js → ScreenCaptureKit available with full compatibility");
277
+ NSLog(@"✅ macOS 14+ Node.js → ScreenCaptureKit available with full compatibility");
216
278
  }
217
279
 
218
280
  // Try ScreenCaptureKit with extensive safety measures
@@ -287,18 +349,14 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
287
349
  // If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
288
350
  NSLog(@"⏭️ ScreenCaptureKit failed - falling back to AVFoundation");
289
351
  } else {
290
- // macOS 14/13 or forced AVFoundation → ALWAYS use AVFoundation (Electron supported!)
352
+ // macOS 13 or forced AVFoundation → use AVFoundation (Electron supported!)
291
353
  if (isElectron) {
292
- if (isM14Plus) {
293
- NSLog(@"⚡ ELECTRON PRIORITY: macOS 14/13 Electron → AVFoundation with full support");
294
- } else if (isM13Plus) {
354
+ if (isM13Plus) {
295
355
  NSLog(@"⚡ ELECTRON PRIORITY: macOS 13 Electron → AVFoundation with limited features");
296
356
  }
297
357
  } else {
298
- if (isM15Plus) {
299
- NSLog(@"🎯 macOS 15+ Node.js with FORCE_AVFOUNDATION → using AVFoundation");
300
- } else if (isM14Plus) {
301
- NSLog(@"🎯 macOS 14 Node.js → using AVFoundation (primary method)");
358
+ if (isM14Plus) {
359
+ NSLog(@"🎯 macOS 14+ Node.js with FORCE_AVFOUNDATION → using AVFoundation");
302
360
  } else if (isM13Plus) {
303
361
  NSLog(@"🎯 macOS 13 Node.js → using AVFoundation (limited features)");
304
362
  }
@@ -575,19 +633,29 @@ Napi::Value GetDisplays(const Napi::CallbackInfo& info) {
575
633
 
576
634
  for (uint32_t i = 0; i < displayCount; i++) {
577
635
  CGDirectDisplayID displayID = activeDisplays[i];
578
- CGRect displayBounds = CGDisplayBounds(displayID);
579
- bool isPrimary = (displayID == CGMainDisplayID());
580
-
581
- NSDictionary *displayInfo = @{
582
- @"id": @(displayID), // Direct CGDirectDisplayID
583
- @"name": [NSString stringWithFormat:@"Display %u", (unsigned int)(i + 1)],
584
- @"width": @((int)displayBounds.size.width),
585
- @"height": @((int)displayBounds.size.height),
586
- @"x": @((int)displayBounds.origin.x),
587
- @"y": @((int)displayBounds.origin.y),
588
- @"isPrimary": @(isPrimary)
589
- };
590
- [displays addObject:displayInfo];
636
+ CGRect displayBounds = CGDisplayBounds(displayID);
637
+ bool isPrimary = (displayID == CGMainDisplayID());
638
+ size_t pixelWidth = CGDisplayPixelsWide(displayID);
639
+ size_t pixelHeight = CGDisplayPixelsHigh(displayID);
640
+ double logicalWidth = displayBounds.size.width;
641
+ double logicalHeight = displayBounds.size.height;
642
+ double scaleX = (logicalWidth > 0.0) ? ((double)pixelWidth / logicalWidth) : 1.0;
643
+ double scaleY = (logicalHeight > 0.0) ? ((double)pixelHeight / logicalHeight) : 1.0;
644
+
645
+ NSDictionary *displayInfo = @{
646
+ @"id": @(displayID), // Direct CGDirectDisplayID
647
+ @"name": [NSString stringWithFormat:@"Display %u", (unsigned int)(i + 1)],
648
+ @"width": @((int)displayBounds.size.width),
649
+ @"height": @((int)displayBounds.size.height),
650
+ @"x": @((int)displayBounds.origin.x),
651
+ @"y": @((int)displayBounds.origin.y),
652
+ @"isPrimary": @(isPrimary),
653
+ @"pixelWidth": @((int)pixelWidth),
654
+ @"pixelHeight": @((int)pixelHeight),
655
+ @"scaleX": @((double)scaleX),
656
+ @"scaleY": @((double)scaleY)
657
+ };
658
+ [displays addObject:displayInfo];
591
659
  }
592
660
  Napi::Array result = Napi::Array::New(env, displays.count);
593
661
 
@@ -642,6 +710,21 @@ Napi::Value GetRecordingStatus(const Napi::CallbackInfo& info) {
642
710
  return Napi::Boolean::New(env, isRecording);
643
711
  }
644
712
 
713
+ Napi::Value GetActiveCaptureInfo(const Napi::CallbackInfo& info) {
714
+ Napi::Env env = info.Env();
715
+
716
+ if (@available(macOS 15.0, *)) {
717
+ NSDictionary *captureInfo = [ScreenCaptureKitRecorder currentCaptureInfo];
718
+ if (!captureInfo || captureInfo.count == 0) {
719
+ return env.Null();
720
+ }
721
+
722
+ return NSObjectToNapiValue(env, captureInfo);
723
+ }
724
+
725
+ return env.Null();
726
+ }
727
+
645
728
  // NAPI Function: Get Window Thumbnail
646
729
  Napi::Value GetWindowThumbnail(const Napi::CallbackInfo& info) {
647
730
  Napi::Env env = info.Env();
@@ -982,6 +1065,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
982
1065
  exports.Set(Napi::String::New(env, "getDisplays"), Napi::Function::New(env, GetDisplays));
983
1066
  exports.Set(Napi::String::New(env, "getWindows"), Napi::Function::New(env, GetWindows));
984
1067
  exports.Set(Napi::String::New(env, "getRecordingStatus"), Napi::Function::New(env, GetRecordingStatus));
1068
+ exports.Set(Napi::String::New(env, "getActiveCaptureInfo"), Napi::Function::New(env, GetActiveCaptureInfo));
985
1069
  exports.Set(Napi::String::New(env, "checkPermissions"), Napi::Function::New(env, CheckPermissions));
986
1070
 
987
1071
  // Thumbnail functions
@@ -997,4 +1081,4 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
997
1081
  return exports;
998
1082
  }
999
1083
 
1000
- NODE_API_MODULE(mac_recorder, Init)
1084
+ NODE_API_MODULE(mac_recorder, Init)
@@ -15,5 +15,6 @@ API_AVAILABLE(macos(15.0))
15
15
  + (void)finalizeRecording;
16
16
  + (void)finalizeVideoWriter;
17
17
  + (void)cleanupVideoWriter;
18
+ + (NSDictionary *)currentCaptureInfo;
18
19
 
19
- @end
20
+ @end
@@ -7,6 +7,7 @@ static id<SCStreamDelegate> API_AVAILABLE(macos(12.3)) g_streamDelegate = nil;
7
7
  static BOOL g_isRecording = NO;
8
8
  static BOOL g_isCleaningUp = NO; // Prevent recursive cleanup
9
9
  static NSString *g_outputPath = nil;
10
+ static NSDictionary *g_currentCaptureInfo = nil;
10
11
 
11
12
  @interface PureScreenCaptureDelegate : NSObject <SCStreamDelegate>
12
13
  @end
@@ -58,6 +59,8 @@ static NSString *g_outputPath = nil;
58
59
  g_isCleaningUp = NO;
59
60
  }
60
61
 
62
+ g_currentCaptureInfo = nil;
63
+
61
64
  NSString *outputPath = config[@"outputPath"];
62
65
  if (!outputPath || [outputPath length] == 0) {
63
66
  NSLog(@"❌ Invalid output path provided");
@@ -124,6 +127,34 @@ static NSString *g_outputPath = nil;
124
127
  filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:targetWindow];
125
128
  recordingWidth = (NSInteger)targetWindow.frame.size.width;
126
129
  recordingHeight = (NSInteger)targetWindow.frame.size.height;
130
+
131
+ CGRect windowFrame = targetWindow.frame;
132
+ NSDictionary *frameDict = @{
133
+ @"x": @(windowFrame.origin.x),
134
+ @"y": @(windowFrame.origin.y),
135
+ @"width": @(windowFrame.size.width),
136
+ @"height": @(windowFrame.size.height)
137
+ };
138
+
139
+ NSMutableDictionary *windowInfo = [@{
140
+ @"id": @(targetWindow.windowID),
141
+ @"frame": frameDict
142
+ } mutableCopy];
143
+
144
+ if (targetWindow.title) {
145
+ windowInfo[@"title"] = targetWindow.title;
146
+ }
147
+ if (targetApp && targetApp.applicationName) {
148
+ windowInfo[@"appName"] = targetApp.applicationName;
149
+ }
150
+ if (targetApp && targetApp.bundleIdentifier) {
151
+ windowInfo[@"bundleId"] = targetApp.bundleIdentifier;
152
+ }
153
+
154
+ g_currentCaptureInfo = @{
155
+ @"type": @"window",
156
+ @"window": windowInfo
157
+ };
127
158
  } else {
128
159
  NSLog(@"❌ Window ID %@ not found", windowId);
129
160
  return;
@@ -163,6 +194,24 @@ static NSString *g_outputPath = nil;
163
194
  filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:@[]];
164
195
  recordingWidth = targetDisplay.width;
165
196
  recordingHeight = targetDisplay.height;
197
+
198
+ CGRect displayFrame = targetDisplay.frame;
199
+ NSDictionary *displayFrameDict = @{
200
+ @"x": @(displayFrame.origin.x),
201
+ @"y": @(displayFrame.origin.y),
202
+ @"width": @(displayFrame.size.width),
203
+ @"height": @(displayFrame.size.height)
204
+ };
205
+
206
+ g_currentCaptureInfo = @{
207
+ @"type": @"display",
208
+ @"display": @{
209
+ @"id": @(targetDisplay.displayID),
210
+ @"frame": displayFrameDict,
211
+ @"width": @(targetDisplay.width),
212
+ @"height": @(targetDisplay.height)
213
+ }
214
+ };
166
215
  }
167
216
 
168
217
  // CROP AREA SUPPORT - Adjust dimensions and source rect
@@ -393,7 +442,8 @@ static NSString *g_outputPath = nil;
393
442
  // SCRecordingOutput finalizes automatically
394
443
  NSLog(@"✅ Pure recording output finalized");
395
444
  }
396
-
445
+
446
+ g_currentCaptureInfo = nil;
397
447
  [ScreenCaptureKitRecorder cleanupVideoWriter];
398
448
  }
399
449
  }
@@ -431,4 +481,10 @@ static NSString *g_outputPath = nil;
431
481
  }
432
482
  }
433
483
 
434
- @end
484
+ + (NSDictionary *)currentCaptureInfo {
485
+ @synchronized([ScreenCaptureKitRecorder class]) {
486
+ return g_currentCaptureInfo;
487
+ }
488
+ }
489
+
490
+ @end