node-mac-recorder 2.17.9 → 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 +305 -89
- package/package.json +1 -1
- package/src/mac_recorder.mm +113 -29
- package/src/screen_capture_kit.h +2 -1
- package/src/screen_capture_kit.mm +58 -2
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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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:
|
|
297
|
-
height:
|
|
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
|
|
300
|
-
logicalHeight
|
|
338
|
+
logicalWidth,
|
|
339
|
+
logicalHeight,
|
|
301
340
|
};
|
|
302
341
|
}
|
|
303
342
|
} catch (error) {
|
|
@@ -356,51 +395,105 @@ class MacRecorder extends EventEmitter {
|
|
|
356
395
|
this.isRecording = true;
|
|
357
396
|
this.recordingStartTime = Date.now();
|
|
358
397
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// Persist capture area so we can rebuild global offsets reliably
|
|
379
|
-
captureArea: this.options.captureArea,
|
|
380
|
-
// Keep a snapshot of the window details for debugging/analytics
|
|
381
|
-
originalWindow: targetWindow,
|
|
382
|
-
// Store display info for multi-display coordinate fixes
|
|
383
|
-
targetDisplay: this.recordingDisplayInfo
|
|
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
|
+
}
|
|
384
417
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
|
464
|
+
this.startCursorCapture(cursorFilePath, {
|
|
465
|
+
windowRelative: true,
|
|
466
|
+
windowInfo: {
|
|
467
|
+
x: globalWindowX,
|
|
468
|
+
y: globalWindowY,
|
|
469
|
+
width: windowWidth,
|
|
470
|
+
height: windowHeight,
|
|
471
|
+
displayId: this.options.displayId,
|
|
472
|
+
captureArea: captureAreaForCursor,
|
|
473
|
+
originalWindow: targetWindow,
|
|
474
|
+
targetDisplay,
|
|
475
|
+
nativeFrame: nativeWindowFrame
|
|
476
|
+
}
|
|
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
|
+
});
|
|
390
483
|
});
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.warn('Could not prepare window cursor tracking:', error.message);
|
|
486
|
+
this.startCursorCapture(cursorFilePath).catch(cursorError => {
|
|
487
|
+
console.warn('Cursor tracking failed to start:', cursorError.message);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
startWindowCursorTracking();
|
|
493
|
+
} else {
|
|
494
|
+
// For display recording, use display-relative cursor tracking
|
|
495
|
+
this.startCursorCapture(cursorFilePath, {
|
|
496
|
+
displayRelative: true,
|
|
404
497
|
displayInfo: this.recordingDisplayInfo
|
|
405
498
|
}).catch(cursorError => {
|
|
406
499
|
console.warn('Display cursor tracking failed:', cursorError.message);
|
|
@@ -712,6 +805,16 @@ class MacRecorder extends EventEmitter {
|
|
|
712
805
|
const targetDisplay = windowInfo.targetDisplay || this.recordingDisplayInfo || null;
|
|
713
806
|
const captureArea = windowInfo.captureArea || null;
|
|
714
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);
|
|
715
818
|
|
|
716
819
|
let globalX = hasNumber(windowInfo.x) ? windowInfo.x : null;
|
|
717
820
|
let globalY = hasNumber(windowInfo.y) ? windowInfo.y : null;
|
|
@@ -745,12 +848,40 @@ class MacRecorder extends EventEmitter {
|
|
|
745
848
|
? captureArea.x
|
|
746
849
|
: (targetDisplay && hasNumber(globalX) && hasNumber(targetDisplay.x)
|
|
747
850
|
? globalX - targetDisplay.x
|
|
748
|
-
: globalX);
|
|
851
|
+
: globalX ?? 0);
|
|
749
852
|
const displayOffsetY = captureArea && hasNumber(captureArea.y)
|
|
750
853
|
? captureArea.y
|
|
751
854
|
: (targetDisplay && hasNumber(globalY) && hasNumber(targetDisplay.y)
|
|
752
855
|
? globalY - targetDisplay.y
|
|
753
|
-
: globalY);
|
|
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
|
+
}
|
|
754
885
|
|
|
755
886
|
this.cursorDisplayInfo = {
|
|
756
887
|
displayId:
|
|
@@ -762,25 +893,40 @@ class MacRecorder extends EventEmitter {
|
|
|
762
893
|
y: globalY,
|
|
763
894
|
width: windowInfo.width,
|
|
764
895
|
height: windowInfo.height,
|
|
896
|
+
pixelWidth: windowPixelWidth,
|
|
897
|
+
pixelHeight: windowPixelHeight,
|
|
898
|
+
scaleX: displayScaleX,
|
|
899
|
+
scaleY: displayScaleY,
|
|
900
|
+
displayRelativeX: displayOffsetX,
|
|
901
|
+
displayRelativeY: displayOffsetY,
|
|
765
902
|
windowRelative: true,
|
|
766
903
|
windowInfo: {
|
|
767
|
-
...
|
|
768
|
-
globalX,
|
|
769
|
-
globalY,
|
|
770
|
-
displayOffsetX,
|
|
771
|
-
displayOffsetY
|
|
904
|
+
...enrichedWindowInfo
|
|
772
905
|
},
|
|
773
906
|
targetDisplay,
|
|
774
|
-
captureArea
|
|
907
|
+
captureArea: captureAreaInfo
|
|
775
908
|
};
|
|
776
909
|
} else if (options.displayRelative && options.displayInfo) {
|
|
777
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
|
+
|
|
778
920
|
this.cursorDisplayInfo = {
|
|
779
921
|
displayId: options.displayInfo.displayId,
|
|
780
922
|
x: options.displayInfo.x,
|
|
781
923
|
y: options.displayInfo.y,
|
|
782
924
|
width: options.displayInfo.width,
|
|
783
925
|
height: options.displayInfo.height,
|
|
926
|
+
pixelWidth,
|
|
927
|
+
pixelHeight,
|
|
928
|
+
scaleX,
|
|
929
|
+
scaleY,
|
|
784
930
|
displayRelative: true
|
|
785
931
|
};
|
|
786
932
|
} else if (this.recordingDisplayInfo) {
|
|
@@ -796,8 +942,16 @@ class MacRecorder extends EventEmitter {
|
|
|
796
942
|
displayId: mainDisplay.id,
|
|
797
943
|
x: mainDisplay.x,
|
|
798
944
|
y: mainDisplay.y,
|
|
799
|
-
width: parseInt(mainDisplay.resolution.split("x")[0]),
|
|
800
|
-
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),
|
|
801
955
|
};
|
|
802
956
|
}
|
|
803
957
|
} catch (error) {
|
|
@@ -813,15 +967,24 @@ class MacRecorder extends EventEmitter {
|
|
|
813
967
|
fs.writeFileSync(filepath, "[");
|
|
814
968
|
|
|
815
969
|
this.cursorCaptureFile = filepath;
|
|
816
|
-
|
|
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);
|
|
817
977
|
this.cursorCaptureFirstWrite = true;
|
|
818
978
|
this.lastCapturedData = null;
|
|
819
979
|
|
|
820
980
|
// JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
|
|
821
981
|
this.cursorCaptureInterval = setInterval(() => {
|
|
822
982
|
try {
|
|
983
|
+
const now = Date.now();
|
|
823
984
|
const position = nativeBinding.getCursorPosition();
|
|
824
|
-
const
|
|
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);
|
|
825
988
|
|
|
826
989
|
// Transform coordinates based on recording type
|
|
827
990
|
let x = position.x;
|
|
@@ -830,9 +993,30 @@ class MacRecorder extends EventEmitter {
|
|
|
830
993
|
|
|
831
994
|
if (this.cursorDisplayInfo) {
|
|
832
995
|
if (this.cursorDisplayInfo.windowRelative) {
|
|
833
|
-
// Window recording:
|
|
834
|
-
|
|
835
|
-
|
|
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)) {
|
|
1002
|
+
const displayRelativeX = position.x - targetDisplay.x;
|
|
1003
|
+
const displayRelativeY = position.y - targetDisplay.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;
|
|
1015
|
+
} else {
|
|
1016
|
+
// Fallback: global offsets
|
|
1017
|
+
x = position.x - this.cursorDisplayInfo.x;
|
|
1018
|
+
y = position.y - this.cursorDisplayInfo.y;
|
|
1019
|
+
}
|
|
836
1020
|
coordinateSystem = "window-relative";
|
|
837
1021
|
|
|
838
1022
|
// Window bounds check - skip if cursor is outside window
|
|
@@ -857,11 +1041,28 @@ class MacRecorder extends EventEmitter {
|
|
|
857
1041
|
}
|
|
858
1042
|
}
|
|
859
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
|
+
|
|
860
1055
|
const cursorData = {
|
|
861
|
-
x:
|
|
862
|
-
y:
|
|
863
|
-
|
|
864
|
-
|
|
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,
|
|
865
1066
|
cursorType: position.cursorType,
|
|
866
1067
|
type: position.eventType || "move",
|
|
867
1068
|
coordinateSystem: coordinateSystem,
|
|
@@ -870,7 +1071,14 @@ class MacRecorder extends EventEmitter {
|
|
|
870
1071
|
windowInfo: {
|
|
871
1072
|
width: this.cursorDisplayInfo.width,
|
|
872
1073
|
height: this.cursorDisplayInfo.height,
|
|
873
|
-
|
|
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
|
|
874
1082
|
}
|
|
875
1083
|
}),
|
|
876
1084
|
// Include display context for display-relative coordinates
|
|
@@ -878,7 +1086,11 @@ class MacRecorder extends EventEmitter {
|
|
|
878
1086
|
displayInfo: {
|
|
879
1087
|
displayId: this.cursorDisplayInfo.displayId,
|
|
880
1088
|
width: this.cursorDisplayInfo.width,
|
|
881
|
-
height: this.cursorDisplayInfo.height
|
|
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
|
|
882
1094
|
}
|
|
883
1095
|
})
|
|
884
1096
|
};
|
|
@@ -937,6 +1149,8 @@ class MacRecorder extends EventEmitter {
|
|
|
937
1149
|
this.cursorCaptureStartTime = null;
|
|
938
1150
|
this.cursorCaptureFirstWrite = true;
|
|
939
1151
|
this.cursorDisplayInfo = null;
|
|
1152
|
+
this.cursorRecordingBaseTime = null;
|
|
1153
|
+
this.cursorRecordingOffsetMs = 0;
|
|
940
1154
|
|
|
941
1155
|
this.emit("cursorCaptureStopped");
|
|
942
1156
|
resolve(true);
|
|
@@ -1062,6 +1276,8 @@ class MacRecorder extends EventEmitter {
|
|
|
1062
1276
|
isCapturing: !!this.cursorCaptureInterval,
|
|
1063
1277
|
outputFile: this.cursorCaptureFile || null,
|
|
1064
1278
|
startTime: this.cursorCaptureStartTime || null,
|
|
1279
|
+
recordingBaseTime: this.cursorRecordingBaseTime || null,
|
|
1280
|
+
recordingOffsetMs: this.cursorRecordingOffsetMs || 0,
|
|
1065
1281
|
displayInfo: this.cursorDisplayInfo || null,
|
|
1066
1282
|
};
|
|
1067
1283
|
}
|
package/package.json
CHANGED
package/src/mac_recorder.mm
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
209
|
-
|
|
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
|
|
275
|
+
NSLog(@"⚡ ELECTRON PRIORITY: macOS 14+ Electron → ScreenCaptureKit with full support (fallbacks enabled)");
|
|
214
276
|
} else {
|
|
215
|
-
NSLog(@"✅ macOS
|
|
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
|
|
352
|
+
// macOS 13 or forced AVFoundation → use AVFoundation (Electron supported!)
|
|
291
353
|
if (isElectron) {
|
|
292
|
-
if (
|
|
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 (
|
|
299
|
-
NSLog(@"🎯 macOS
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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)
|
package/src/screen_capture_kit.h
CHANGED
|
@@ -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
|
-
|
|
484
|
+
+ (NSDictionary *)currentCaptureInfo {
|
|
485
|
+
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
486
|
+
return g_currentCaptureInfo;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
@end
|