node-mac-recorder 2.20.6 โ 2.20.8
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 +1 -1
- package/src/avfoundation_recorder.mm +18 -17
- package/src/cursor_tracker.mm +45 -6
- package/src/electron_safe/cursor_tracker_electron.mm +50 -31
- package/src/logging.h +17 -0
- package/src/mac_recorder.mm +31 -31
- package/src/screen_capture_kit.mm +43 -42
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#import <QuartzCore/QuartzCore.h>
|
|
6
6
|
#import <AppKit/AppKit.h>
|
|
7
7
|
#include <string>
|
|
8
|
+
#import "logging.h"
|
|
8
9
|
|
|
9
10
|
static AVAssetWriter *g_avWriter = nil;
|
|
10
11
|
static AVAssetWriterInput *g_avVideoInput = nil;
|
|
@@ -32,7 +33,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
@try {
|
|
35
|
-
|
|
36
|
+
MRLog(@"๐ฌ AVFoundation: Starting recording initialization");
|
|
36
37
|
|
|
37
38
|
// Create output URL
|
|
38
39
|
NSString *outputPathStr = [NSString stringWithUTF8String:outputPath.c_str()];
|
|
@@ -42,7 +43,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
42
43
|
NSError *removeError = nil;
|
|
43
44
|
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:&removeError];
|
|
44
45
|
if (removeError && removeError.code != NSFileNoSuchFileError) {
|
|
45
|
-
|
|
46
|
+
MRLog(@"โ ๏ธ AVFoundation: Warning removing existing file: %@", removeError);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Create asset writer
|
|
@@ -85,21 +86,21 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
85
86
|
recordingSize = actualImageSize;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
MRLog(@"๐ฏ CRITICAL: Logical %.0fx%.0f โ Actual image %.0fx%.0f",
|
|
89
90
|
logicalSize.width, logicalSize.height, actualImageSize.width, actualImageSize.height);
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
MRLog(@"๐ฅ๏ธ Display bounds (logical): %.0fx%.0f", logicalSize.width, logicalSize.height);
|
|
93
|
+
MRLog(@"๐ฅ๏ธ Display pixels (physical): %.0fx%.0f", physicalSize.width, physicalSize.height);
|
|
93
94
|
|
|
94
95
|
if (scaleFactor > 1.5) {
|
|
95
|
-
|
|
96
|
+
MRLog(@"๐ Scale factor: %.1fx โ Retina display detected (macOS 14/13 scaling fix applied)", scaleFactor);
|
|
96
97
|
} else if (scaleFactor > 1.1) {
|
|
97
|
-
|
|
98
|
+
MRLog(@"๐ Scale factor: %.1fx โ Non-standard scaling detected", scaleFactor);
|
|
98
99
|
} else {
|
|
99
|
-
|
|
100
|
+
MRLog(@"๐ Scale factor: %.1fx โ Standard display", scaleFactor);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
MRLog(@"๐ฏ Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|
|
103
104
|
|
|
104
105
|
// Video settings with macOS compatibility
|
|
105
106
|
NSString *codecKey;
|
|
@@ -142,7 +143,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
142
143
|
(NSString*)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
|
|
143
144
|
};
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
MRLog(@"๐ง Using pixel format: %u", pixelFormat);
|
|
146
147
|
|
|
147
148
|
g_avPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:g_avVideoInput sourcePixelBufferAttributes:pixelBufferAttributes];
|
|
148
149
|
|
|
@@ -253,11 +254,11 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
253
254
|
size_t imageWidth = CGImageGetWidth(screenImage);
|
|
254
255
|
size_t imageHeight = CGImageGetHeight(screenImage);
|
|
255
256
|
|
|
256
|
-
|
|
257
|
+
MRLog(@"๐ Debug: Buffer %zux%zu, Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
|
|
257
258
|
|
|
258
259
|
if (bufferWidth != imageWidth || bufferHeight != imageHeight) {
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
MRLog(@"๐ง EXPECTED SIZE DIFFERENCE: Buffer %zux%zu (logical) vs Image %zux%zu (physical)", bufferWidth, bufferHeight, imageWidth, imageHeight);
|
|
261
|
+
MRLog(@" This is normal on Retina displays - scaling handled correctly now");
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
@@ -332,7 +333,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
332
333
|
dispatch_resume(g_avTimer);
|
|
333
334
|
g_avIsRecording = true;
|
|
334
335
|
|
|
335
|
-
|
|
336
|
+
MRLog(@"๐ฅ AVFoundation recording started: %dx%d @ 10fps",
|
|
336
337
|
(int)recordingSize.width, (int)recordingSize.height);
|
|
337
338
|
|
|
338
339
|
return true;
|
|
@@ -365,7 +366,7 @@ extern "C" bool stopAVFoundationRecording() {
|
|
|
365
366
|
});
|
|
366
367
|
|
|
367
368
|
g_avTimer = nil;
|
|
368
|
-
|
|
369
|
+
MRLog(@"โ
AVFoundation timer stopped safely");
|
|
369
370
|
}
|
|
370
371
|
|
|
371
372
|
// Finish writing with null checks
|
|
@@ -391,7 +392,7 @@ extern "C" bool stopAVFoundationRecording() {
|
|
|
391
392
|
g_avPixelBufferAdaptor = nil;
|
|
392
393
|
g_avFrameNumber = 0;
|
|
393
394
|
|
|
394
|
-
|
|
395
|
+
MRLog(@"โ
AVFoundation recording stopped");
|
|
395
396
|
return true;
|
|
396
397
|
|
|
397
398
|
} @catch (NSException *exception) {
|
|
@@ -402,4 +403,4 @@ extern "C" bool stopAVFoundationRecording() {
|
|
|
402
403
|
|
|
403
404
|
extern "C" bool isAVFoundationRecording() {
|
|
404
405
|
return g_avIsRecording;
|
|
405
|
-
}
|
|
406
|
+
}
|
package/src/cursor_tracker.mm
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#import <Carbon/Carbon.h>
|
|
7
7
|
#import <Accessibility/Accessibility.h>
|
|
8
8
|
#import <dispatch/dispatch.h>
|
|
9
|
+
#import "logging.h"
|
|
9
10
|
|
|
10
11
|
#ifndef kAXHitTestParameterizedAttribute
|
|
11
12
|
#define kAXHitTestParameterizedAttribute CFSTR("AXHitTest")
|
|
@@ -547,7 +548,7 @@ static NSString* detectSystemCursorType(void) {
|
|
|
547
548
|
cursorType = @"default";
|
|
548
549
|
}
|
|
549
550
|
|
|
550
|
-
|
|
551
|
+
MRLog(@"๐ฏ SYSTEM CURSOR TYPE: %@", cursorType ? cursorType : @"(nil)");
|
|
551
552
|
};
|
|
552
553
|
|
|
553
554
|
if ([NSThread isMainThread]) {
|
|
@@ -563,13 +564,51 @@ NSString* getCursorType() {
|
|
|
563
564
|
@autoreleasepool {
|
|
564
565
|
g_cursorTypeCounter++;
|
|
565
566
|
|
|
566
|
-
//
|
|
567
|
+
// Hybrid: AX (strict) first, then system cursor fallback.
|
|
568
|
+
BOOL hasCursorPosition = NO;
|
|
569
|
+
CGPoint cursorPos = CGPointZero;
|
|
570
|
+
|
|
571
|
+
CGEventRef event = CGEventCreate(NULL);
|
|
572
|
+
if (event) {
|
|
573
|
+
cursorPos = CGEventGetLocation(event);
|
|
574
|
+
hasCursorPosition = YES;
|
|
575
|
+
CFRelease(event);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!hasCursorPosition) {
|
|
579
|
+
if ([NSThread isMainThread]) {
|
|
580
|
+
cursorPos = [NSEvent mouseLocation];
|
|
581
|
+
hasCursorPosition = YES;
|
|
582
|
+
} else {
|
|
583
|
+
__block CGPoint fallbackPos = CGPointZero;
|
|
584
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
585
|
+
fallbackPos = [NSEvent mouseLocation];
|
|
586
|
+
});
|
|
587
|
+
cursorPos = fallbackPos;
|
|
588
|
+
hasCursorPosition = YES;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
NSString *axCursorType = nil;
|
|
593
|
+
if (hasCursorPosition) {
|
|
594
|
+
axCursorType = detectCursorTypeUsingAccessibility(cursorPos);
|
|
595
|
+
}
|
|
596
|
+
|
|
567
597
|
NSString *systemCursorType = detectSystemCursorType();
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
598
|
+
|
|
599
|
+
NSString *finalType = nil;
|
|
600
|
+
if (axCursorType && ![axCursorType isEqualToString:@"default"]) {
|
|
601
|
+
finalType = axCursorType;
|
|
602
|
+
} else if (systemCursorType && [systemCursorType length] > 0) {
|
|
603
|
+
finalType = systemCursorType;
|
|
604
|
+
} else if (axCursorType && [axCursorType length] > 0) {
|
|
605
|
+
finalType = axCursorType;
|
|
606
|
+
} else {
|
|
607
|
+
finalType = @"default";
|
|
571
608
|
}
|
|
572
|
-
|
|
609
|
+
|
|
610
|
+
MRLog(@"๐ฏ FINAL CURSOR TYPE: %@", finalType);
|
|
611
|
+
return finalType;
|
|
573
612
|
}
|
|
574
613
|
}
|
|
575
614
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import <napi.h>
|
|
2
2
|
#import <CoreGraphics/CoreGraphics.h>
|
|
3
3
|
#import <AppKit/AppKit.h>
|
|
4
|
+
#import "../logging.h"
|
|
4
5
|
|
|
5
6
|
// Thread-safe cursor tracking for Electron
|
|
6
7
|
static dispatch_queue_t g_cursorQueue = nil;
|
|
@@ -12,47 +13,65 @@ static void initializeCursorQueue() {
|
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
static NSString* MapCursorToType(NSCursor *cursor) {
|
|
17
|
+
if (!cursor) return @"default";
|
|
18
|
+
|
|
19
|
+
if (cursor == [NSCursor arrowCursor]) return @"default";
|
|
20
|
+
if (cursor == [NSCursor IBeamCursor]) return @"text";
|
|
21
|
+
if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
|
|
22
|
+
cursor == [NSCursor IBeamCursorForVerticalLayout]) return @"text";
|
|
23
|
+
if (cursor == [NSCursor pointingHandCursor]) return @"pointer";
|
|
24
|
+
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
|
|
25
|
+
if (cursor == [NSCursor resizeLeftRightCursor] ||
|
|
26
|
+
[NSCursor instancesRespondToSelector:@selector(resizeLeftCursor)] && (cursor == [NSCursor resizeLeftCursor]) ||
|
|
27
|
+
[NSCursor instancesRespondToSelector:@selector(resizeRightCursor)] && (cursor == [NSCursor resizeRightCursor])) {
|
|
28
|
+
return @"col-resize";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
|
|
32
|
+
if (cursor == [NSCursor resizeUpDownCursor] ||
|
|
33
|
+
[NSCursor instancesRespondToSelector:@selector(resizeUpCursor)] && (cursor == [NSCursor resizeUpCursor]) ||
|
|
34
|
+
[NSCursor instancesRespondToSelector:@selector(resizeDownCursor)] && (cursor == [NSCursor resizeDownCursor])) {
|
|
35
|
+
return @"ns-resize";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if ([NSCursor respondsToSelector:@selector(openHandCursor)] && cursor == [NSCursor openHandCursor]) return @"grab";
|
|
39
|
+
if ([NSCursor respondsToSelector:@selector(closedHandCursor)] && cursor == [NSCursor closedHandCursor]) return @"grabbing";
|
|
40
|
+
if ([NSCursor respondsToSelector:@selector(crosshairCursor)] && cursor == [NSCursor crosshairCursor]) return @"crosshair";
|
|
41
|
+
if ([NSCursor respondsToSelector:@selector(operationNotAllowedCursor)] && cursor == [NSCursor operationNotAllowedCursor]) return @"not-allowed";
|
|
42
|
+
if ([NSCursor respondsToSelector:@selector(dragCopyCursor)] && cursor == [NSCursor dragCopyCursor]) return @"copy";
|
|
43
|
+
if ([NSCursor respondsToSelector:@selector(dragLinkCursor)] && cursor == [NSCursor dragLinkCursor]) return @"alias";
|
|
44
|
+
if ([NSCursor respondsToSelector:@selector(contextualMenuCursor)] && cursor == [NSCursor contextualMenuCursor]) return @"context-menu";
|
|
45
|
+
|
|
46
|
+
return @"default";
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
// NAPI Function: Get Cursor Position (Electron-safe)
|
|
16
50
|
Napi::Value GetCursorPositionElectronSafe(const Napi::CallbackInfo& info) {
|
|
17
51
|
Napi::Env env = info.Env();
|
|
18
52
|
|
|
19
53
|
@try {
|
|
20
54
|
initializeCursorQueue();
|
|
21
|
-
|
|
22
55
|
__block CGPoint mouseLocation = CGPointZero;
|
|
23
|
-
__block NSString *cursorType = @"
|
|
24
|
-
|
|
56
|
+
__block NSString *cursorType = @"default";
|
|
57
|
+
|
|
25
58
|
dispatch_sync(g_cursorQueue, ^{
|
|
26
59
|
@try {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
// Use CGEventGetLocation to match global logical coordinates used elsewhere
|
|
61
|
+
CGEventRef event = CGEventCreate(NULL);
|
|
62
|
+
mouseLocation = CGEventGetLocation(event);
|
|
63
|
+
if (event) CFRelease(event);
|
|
64
|
+
|
|
65
|
+
// Prefer currentSystemCursor when available for accurate system-wide state
|
|
66
|
+
NSCursor *cursor = nil;
|
|
67
|
+
if ([NSCursor respondsToSelector:@selector(currentSystemCursor)]) {
|
|
68
|
+
cursor = [NSCursor performSelector:@selector(currentSystemCursor)];
|
|
36
69
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
NSCursor *currentCursor = [NSCursor currentCursor];
|
|
40
|
-
if (currentCursor) {
|
|
41
|
-
if (currentCursor == [NSCursor arrowCursor]) {
|
|
42
|
-
cursorType = @"arrow";
|
|
43
|
-
} else if (currentCursor == [NSCursor IBeamCursor]) {
|
|
44
|
-
cursorType = @"ibeam";
|
|
45
|
-
} else if (currentCursor == [NSCursor pointingHandCursor]) {
|
|
46
|
-
cursorType = @"hand";
|
|
47
|
-
} else if (currentCursor == [NSCursor resizeLeftRightCursor]) {
|
|
48
|
-
cursorType = @"resize-horizontal";
|
|
49
|
-
} else if (currentCursor == [NSCursor resizeUpDownCursor]) {
|
|
50
|
-
cursorType = @"resize-vertical";
|
|
51
|
-
} else {
|
|
52
|
-
cursorType = @"default";
|
|
53
|
-
}
|
|
70
|
+
if (!cursor) {
|
|
71
|
+
cursor = [NSCursor currentCursor];
|
|
54
72
|
}
|
|
55
|
-
|
|
73
|
+
cursorType = MapCursorToType(cursor);
|
|
74
|
+
MRLog(@"Electron-safe cursor: %@ at (%.0f,%.0f)", cursorType, mouseLocation.x, mouseLocation.y);
|
|
56
75
|
} @catch (NSException *e) {
|
|
57
76
|
NSLog(@"โ Exception getting cursor position: %@", e.reason);
|
|
58
77
|
}
|
|
@@ -80,7 +99,7 @@ Napi::Object InitCursorTrackerElectron(Napi::Env env, Napi::Object exports) {
|
|
|
80
99
|
|
|
81
100
|
exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPositionElectronSafe));
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
MRLog(@"โ
Electron-safe cursor tracker initialized");
|
|
84
103
|
return exports;
|
|
85
104
|
|
|
86
105
|
} @catch (NSException *e) {
|
package/src/logging.h
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Lightweight runtime-controlled logging helpers
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
|
|
4
|
+
static inline BOOL MRShouldVerboseLog(void) {
|
|
5
|
+
static dispatch_once_t onceToken;
|
|
6
|
+
static BOOL enabled = NO;
|
|
7
|
+
dispatch_once(&onceToken, ^{
|
|
8
|
+
const char *env = getenv("MAC_RECORDER_DEBUG");
|
|
9
|
+
if (env && (*env == '1' || *env == 't' || *env == 'T' || *env == 'y' || *env == 'Y')) {
|
|
10
|
+
enabled = YES;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return enabled;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#define MRLog(fmt, ...) do { if (MRShouldVerboseLog()) { NSLog((fmt), ##__VA_ARGS__); } } while(0)
|
|
17
|
+
|
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
|
+
#import "logging.h"
|
|
7
8
|
|
|
8
9
|
// Import screen capture (ScreenCaptureKit only)
|
|
9
10
|
#import "screen_capture_kit.h"
|
|
@@ -70,11 +71,11 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
70
71
|
|
|
71
72
|
// IMPORTANT: Clean up any stale recording state before starting
|
|
72
73
|
// This fixes the issue where macOS 14/13 users get "recording already in progress"
|
|
73
|
-
|
|
74
|
+
MRLog(@"๐งน Cleaning up any previous recording state...");
|
|
74
75
|
cleanupRecording();
|
|
75
76
|
|
|
76
77
|
if (g_isRecording) {
|
|
77
|
-
|
|
78
|
+
MRLog(@"โ ๏ธ Still recording after cleanup - forcing stop");
|
|
78
79
|
return Napi::Boolean::New(env, false);
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -168,13 +169,13 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
168
169
|
// Window ID support
|
|
169
170
|
if (options.Has("windowId") && !options.Get("windowId").IsNull()) {
|
|
170
171
|
windowID = options.Get("windowId").As<Napi::Number>().Uint32Value();
|
|
171
|
-
|
|
172
|
+
MRLog(@"๐ช Window ID specified: %u", windowID);
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
@try {
|
|
176
177
|
// Smart Recording Selection: ScreenCaptureKit vs Alternative
|
|
177
|
-
|
|
178
|
+
MRLog(@"๐ฏ Smart Recording Engine Selection");
|
|
178
179
|
|
|
179
180
|
// Electron environment detection (removed disable logic)
|
|
180
181
|
BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
|
|
@@ -186,8 +187,8 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
186
187
|
[NSBundle.mainBundle.bundlePath containsString:@"Electron"]);
|
|
187
188
|
|
|
188
189
|
if (isElectron) {
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
MRLog(@"โก Electron environment detected - continuing with ScreenCaptureKit");
|
|
191
|
+
MRLog(@"โ ๏ธ Warning: ScreenCaptureKit in Electron may require additional stability measures");
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
// Check macOS version for ScreenCaptureKit compatibility
|
|
@@ -196,13 +197,13 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
196
197
|
BOOL isM14Plus = (osVersion.majorVersion >= 14);
|
|
197
198
|
BOOL isM13Plus = (osVersion.majorVersion >= 13);
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
MRLog(@"๐ฅ๏ธ macOS Version: %ld.%ld.%ld",
|
|
200
201
|
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
201
202
|
|
|
202
203
|
// Force AVFoundation for debugging/testing
|
|
203
204
|
BOOL forceAVFoundation = (getenv("FORCE_AVFOUNDATION") != NULL);
|
|
204
205
|
if (forceAVFoundation) {
|
|
205
|
-
|
|
206
|
+
MRLog(@"๐ง FORCE_AVFOUNDATION environment variable detected - skipping ScreenCaptureKit");
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
// Electron-first priority: This application is built for Electron.js
|
|
@@ -210,16 +211,16 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
210
211
|
// macOS 14/13 โ AVFoundation (including Electron)
|
|
211
212
|
if (isM15Plus && !forceAVFoundation) {
|
|
212
213
|
if (isElectron) {
|
|
213
|
-
|
|
214
|
+
MRLog(@"โก ELECTRON PRIORITY: macOS 15+ Electron โ ScreenCaptureKit with full support");
|
|
214
215
|
} else {
|
|
215
|
-
|
|
216
|
+
MRLog(@"โ
macOS 15+ Node.js โ ScreenCaptureKit available with full compatibility");
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
// Try ScreenCaptureKit with extensive safety measures
|
|
219
220
|
@try {
|
|
220
221
|
if ([ScreenCaptureKitRecorder isScreenCaptureKitAvailable]) {
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
MRLog(@"โ
ScreenCaptureKit availability check passed");
|
|
223
|
+
MRLog(@"๐ฏ Using ScreenCaptureKit - overlay windows will be automatically excluded");
|
|
223
224
|
|
|
224
225
|
// Create configuration for ScreenCaptureKit
|
|
225
226
|
NSMutableDictionary *sckConfig = [NSMutableDictionary dictionary];
|
|
@@ -251,7 +252,7 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
251
252
|
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
252
253
|
if (!sckStarted && !g_isRecording) {
|
|
253
254
|
sckTimedOut = YES;
|
|
254
|
-
|
|
255
|
+
MRLog(@"โฐ ScreenCaptureKit initialization timeout (3s)");
|
|
255
256
|
}
|
|
256
257
|
});
|
|
257
258
|
|
|
@@ -263,8 +264,8 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
263
264
|
|
|
264
265
|
// ScreenCaptureKit baลlatma baลarฤฑlฤฑ - validation yapmฤฑyoruz
|
|
265
266
|
sckStarted = YES;
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
MRLog(@"๐ฌ RECORDING METHOD: ScreenCaptureKit");
|
|
268
|
+
MRLog(@"โ
ScreenCaptureKit recording started successfully");
|
|
268
269
|
g_isRecording = true;
|
|
269
270
|
return Napi::Boolean::New(env, true);
|
|
270
271
|
} else {
|
|
@@ -274,7 +275,6 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
274
275
|
} @catch (NSException *sckException) {
|
|
275
276
|
NSLog(@"โ Exception during ScreenCaptureKit startup: %@", sckException.reason);
|
|
276
277
|
}
|
|
277
|
-
|
|
278
278
|
NSLog(@"โ ScreenCaptureKit failed or unsafe - will fallback to AVFoundation");
|
|
279
279
|
|
|
280
280
|
} else {
|
|
@@ -285,22 +285,22 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
// If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
|
|
288
|
-
|
|
288
|
+
MRLog(@"โญ๏ธ ScreenCaptureKit failed - falling back to AVFoundation");
|
|
289
289
|
} else {
|
|
290
290
|
// macOS 14/13 or forced AVFoundation โ ALWAYS use AVFoundation (Electron supported!)
|
|
291
291
|
if (isElectron) {
|
|
292
292
|
if (isM14Plus) {
|
|
293
|
-
|
|
293
|
+
MRLog(@"โก ELECTRON PRIORITY: macOS 14/13 Electron โ AVFoundation with full support");
|
|
294
294
|
} else if (isM13Plus) {
|
|
295
|
-
|
|
295
|
+
MRLog(@"โก ELECTRON PRIORITY: macOS 13 Electron โ AVFoundation with limited features");
|
|
296
296
|
}
|
|
297
297
|
} else {
|
|
298
298
|
if (isM15Plus) {
|
|
299
|
-
|
|
299
|
+
MRLog(@"๐ฏ macOS 15+ Node.js with FORCE_AVFOUNDATION โ using AVFoundation");
|
|
300
300
|
} else if (isM14Plus) {
|
|
301
|
-
|
|
301
|
+
MRLog(@"๐ฏ macOS 14 Node.js โ using AVFoundation (primary method)");
|
|
302
302
|
} else if (isM13Plus) {
|
|
303
|
-
|
|
303
|
+
MRLog(@"๐ฏ macOS 13 Node.js โ using AVFoundation (limited features)");
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
|
|
@@ -310,11 +310,11 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
// DIRECT AVFoundation for all environments (Node.js + Electron)
|
|
313
|
-
|
|
313
|
+
MRLog(@"โญ๏ธ Using AVFoundation directly - supports both Node.js and Electron");
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
317
|
-
|
|
317
|
+
MRLog(@"๐ฅ Starting AVFoundation recording...");
|
|
318
318
|
|
|
319
319
|
@try {
|
|
320
320
|
// Import AVFoundation recording functions (if available)
|
|
@@ -331,8 +331,8 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
331
331
|
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId);
|
|
332
332
|
|
|
333
333
|
if (avResult) {
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
MRLog(@"๐ฅ RECORDING METHOD: AVFoundation");
|
|
335
|
+
MRLog(@"โ
AVFoundation recording started successfully");
|
|
336
336
|
g_isRecording = true;
|
|
337
337
|
return Napi::Boolean::New(env, true);
|
|
338
338
|
} else {
|
|
@@ -358,12 +358,12 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
358
358
|
Napi::Value StopRecording(const Napi::CallbackInfo& info) {
|
|
359
359
|
Napi::Env env = info.Env();
|
|
360
360
|
|
|
361
|
-
|
|
361
|
+
MRLog(@"๐ StopRecording native method called");
|
|
362
362
|
|
|
363
363
|
// Try ScreenCaptureKit first
|
|
364
364
|
if (@available(macOS 12.3, *)) {
|
|
365
365
|
if ([ScreenCaptureKitRecorder isRecording]) {
|
|
366
|
-
|
|
366
|
+
MRLog(@"๐ Stopping ScreenCaptureKit recording");
|
|
367
367
|
[ScreenCaptureKitRecorder stopRecording];
|
|
368
368
|
g_isRecording = false;
|
|
369
369
|
return Napi::Boolean::New(env, true);
|
|
@@ -376,7 +376,7 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
|
|
|
376
376
|
|
|
377
377
|
@try {
|
|
378
378
|
if (isAVFoundationRecording()) {
|
|
379
|
-
|
|
379
|
+
MRLog(@"๐ Stopping AVFoundation recording");
|
|
380
380
|
if (stopAVFoundationRecording()) {
|
|
381
381
|
g_isRecording = false;
|
|
382
382
|
return Napi::Boolean::New(env, true);
|
|
@@ -392,7 +392,7 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
|
|
|
392
392
|
return Napi::Boolean::New(env, false);
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
|
|
395
|
+
MRLog(@"โ ๏ธ No active recording found to stop");
|
|
396
396
|
g_isRecording = false;
|
|
397
397
|
return Napi::Boolean::New(env, true);
|
|
398
398
|
}
|
|
@@ -997,4 +997,4 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
997
997
|
return exports;
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
|
-
NODE_API_MODULE(mac_recorder, Init)
|
|
1000
|
+
NODE_API_MODULE(mac_recorder, Init)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "screen_capture_kit.h"
|
|
2
|
+
#import "logging.h"
|
|
2
3
|
|
|
3
4
|
// Pure ScreenCaptureKit implementation - NO AVFoundation
|
|
4
5
|
static SCStream * API_AVAILABLE(macos(12.3)) g_stream = nil;
|
|
@@ -13,11 +14,11 @@ static NSString *g_outputPath = nil;
|
|
|
13
14
|
|
|
14
15
|
@implementation PureScreenCaptureDelegate
|
|
15
16
|
- (void)stream:(SCStream * API_AVAILABLE(macos(12.3)))stream didStopWithError:(NSError *)error API_AVAILABLE(macos(12.3)) {
|
|
16
|
-
|
|
17
|
+
MRLog(@"๐ Pure ScreenCapture stream stopped");
|
|
17
18
|
|
|
18
19
|
// Prevent recursive calls during cleanup
|
|
19
20
|
if (g_isCleaningUp) {
|
|
20
|
-
|
|
21
|
+
MRLog(@"โ ๏ธ Already cleaning up, ignoring delegate callback");
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -26,7 +27,7 @@ static NSString *g_outputPath = nil;
|
|
|
26
27
|
if (error) {
|
|
27
28
|
NSLog(@"โ Stream error: %@", error);
|
|
28
29
|
} else {
|
|
29
|
-
|
|
30
|
+
MRLog(@"โ
Stream stopped cleanly");
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
// Use dispatch_async to prevent potential deadlocks in Electron
|
|
@@ -50,7 +51,7 @@ static NSString *g_outputPath = nil;
|
|
|
50
51
|
+ (BOOL)startRecordingWithConfiguration:(NSDictionary *)config delegate:(id)delegate error:(NSError **)error {
|
|
51
52
|
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
52
53
|
if (g_isRecording || g_isCleaningUp) {
|
|
53
|
-
|
|
54
|
+
MRLog(@"โ ๏ธ Already recording or cleaning up (recording:%d cleaning:%d)", g_isRecording, g_isCleaningUp);
|
|
54
55
|
return NO;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -73,13 +74,13 @@ static NSString *g_outputPath = nil;
|
|
|
73
74
|
NSNumber *includeMicrophone = config[@"includeMicrophone"];
|
|
74
75
|
NSNumber *includeSystemAudio = config[@"includeSystemAudio"];
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
MRLog(@"๐ฌ Starting PURE ScreenCaptureKit recording (NO AVFoundation)");
|
|
78
|
+
MRLog(@"๐ง Config: cursor=%@ mic=%@ system=%@ display=%@ window=%@ crop=%@",
|
|
78
79
|
captureCursor, includeMicrophone, includeSystemAudio, displayId, windowId, captureRect);
|
|
79
80
|
|
|
80
81
|
// CRITICAL DEBUG: Log EXACT audio parameter values
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
MRLog(@"๐ AUDIO DEBUG: includeMicrophone type=%@ value=%d", [includeMicrophone class], [includeMicrophone boolValue]);
|
|
83
|
+
MRLog(@"๐ AUDIO DEBUG: includeSystemAudio type=%@ value=%d", [includeSystemAudio class], [includeSystemAudio boolValue]);
|
|
83
84
|
|
|
84
85
|
// Get shareable content
|
|
85
86
|
[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *content, NSError *contentError) {
|
|
@@ -88,13 +89,13 @@ static NSString *g_outputPath = nil;
|
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
MRLog(@"โ
Got %lu displays, %lu windows for pure recording",
|
|
92
93
|
content.displays.count, content.windows.count);
|
|
93
94
|
|
|
94
95
|
// CRITICAL DEBUG: List all available displays in ScreenCaptureKit
|
|
95
|
-
|
|
96
|
+
MRLog(@"๐ ScreenCaptureKit available displays:");
|
|
96
97
|
for (SCDisplay *display in content.displays) {
|
|
97
|
-
|
|
98
|
+
MRLog(@" Display ID=%u, Size=%dx%d, Frame=(%.0f,%.0f,%.0fx%.0f)",
|
|
98
99
|
display.displayID, (int)display.width, (int)display.height,
|
|
99
100
|
display.frame.origin.x, display.frame.origin.y,
|
|
100
101
|
display.frame.size.width, display.frame.size.height);
|
|
@@ -119,7 +120,7 @@ static NSString *g_outputPath = nil;
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
if (targetWindow && targetApp) {
|
|
122
|
-
|
|
123
|
+
MRLog(@"๐ช Recording window: %@ (%ux%u)",
|
|
123
124
|
targetWindow.title, (unsigned)targetWindow.frame.size.width, (unsigned)targetWindow.frame.size.height);
|
|
124
125
|
filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:targetWindow];
|
|
125
126
|
recordingWidth = (NSInteger)targetWindow.frame.size.width;
|
|
@@ -134,12 +135,12 @@ static NSString *g_outputPath = nil;
|
|
|
134
135
|
|
|
135
136
|
if (displayId && [displayId integerValue] != 0) {
|
|
136
137
|
// Find specific display
|
|
137
|
-
|
|
138
|
+
MRLog(@"๐ฏ Looking for display ID=%@ in ScreenCaptureKit list", displayId);
|
|
138
139
|
for (SCDisplay *display in content.displays) {
|
|
139
|
-
|
|
140
|
+
MRLog(@" Checking display ID=%u vs requested=%u", display.displayID, [displayId unsignedIntValue]);
|
|
140
141
|
if (display.displayID == [displayId unsignedIntValue]) {
|
|
141
142
|
targetDisplay = display;
|
|
142
|
-
|
|
143
|
+
MRLog(@"โ
FOUND matching display ID=%u", display.displayID);
|
|
143
144
|
break;
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -158,7 +159,7 @@ static NSString *g_outputPath = nil;
|
|
|
158
159
|
return;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
MRLog(@"๐ฅ๏ธ Recording display %u (%dx%d)",
|
|
162
163
|
targetDisplay.displayID, (int)targetDisplay.width, (int)targetDisplay.height);
|
|
163
164
|
filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:@[]];
|
|
164
165
|
recordingWidth = targetDisplay.width;
|
|
@@ -171,7 +172,7 @@ static NSString *g_outputPath = nil;
|
|
|
171
172
|
CGFloat cropHeight = [captureRect[@"height"] doubleValue];
|
|
172
173
|
|
|
173
174
|
if (cropWidth > 0 && cropHeight > 0) {
|
|
174
|
-
|
|
175
|
+
MRLog(@"๐ฒ Crop area specified: %.0fx%.0f at (%.0f,%.0f)",
|
|
175
176
|
cropWidth, cropHeight,
|
|
176
177
|
[captureRect[@"x"] doubleValue], [captureRect[@"y"] doubleValue]);
|
|
177
178
|
recordingWidth = (NSInteger)cropWidth;
|
|
@@ -200,11 +201,11 @@ static NSString *g_outputPath = nil;
|
|
|
200
201
|
CGFloat displayRelativeX = globalX - displayBounds.origin.x;
|
|
201
202
|
CGFloat displayRelativeY = globalY - displayBounds.origin.y;
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
MRLog(@"๐ Global coords: (%.0f,%.0f) on Display ID=%u", globalX, globalY, targetDisplay.displayID);
|
|
205
|
+
MRLog(@"๐ฅ๏ธ Display bounds: (%.0f,%.0f,%.0fx%.0f)",
|
|
205
206
|
displayBounds.origin.x, displayBounds.origin.y,
|
|
206
207
|
displayBounds.size.width, displayBounds.size.height);
|
|
207
|
-
|
|
208
|
+
MRLog(@"๐ Display-relative: (%.0f,%.0f) -> SourceRect", displayRelativeX, displayRelativeY);
|
|
208
209
|
|
|
209
210
|
// Validate coordinates are within display bounds
|
|
210
211
|
if (displayRelativeX >= 0 && displayRelativeY >= 0 &&
|
|
@@ -213,11 +214,11 @@ static NSString *g_outputPath = nil;
|
|
|
213
214
|
|
|
214
215
|
CGRect sourceRect = CGRectMake(displayRelativeX, displayRelativeY, cropWidth, cropHeight);
|
|
215
216
|
streamConfig.sourceRect = sourceRect;
|
|
216
|
-
|
|
217
|
+
MRLog(@"โ๏ธ Crop sourceRect applied: (%.0f,%.0f) %.0fx%.0f (display-relative)",
|
|
217
218
|
displayRelativeX, displayRelativeY, cropWidth, cropHeight);
|
|
218
219
|
} else {
|
|
219
220
|
NSLog(@"โ Crop coordinates out of display bounds - skipping crop");
|
|
220
|
-
|
|
221
|
+
MRLog(@" Relative: (%.0f,%.0f) size:(%.0fx%.0f) vs display:(%.0fx%.0f)",
|
|
221
222
|
displayRelativeX, displayRelativeY, cropWidth, cropHeight,
|
|
222
223
|
displayBounds.size.width, displayBounds.size.height);
|
|
223
224
|
}
|
|
@@ -228,14 +229,14 @@ static NSString *g_outputPath = nil;
|
|
|
228
229
|
BOOL shouldShowCursor = captureCursor ? [captureCursor boolValue] : YES;
|
|
229
230
|
streamConfig.showsCursor = shouldShowCursor;
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
MRLog(@"๐ฅ Pure ScreenCapture config: %ldx%ld @ 30fps, cursor=%d",
|
|
232
233
|
recordingWidth, recordingHeight, shouldShowCursor);
|
|
233
234
|
|
|
234
235
|
// AUDIO SUPPORT - Enable both microphone and system audio
|
|
235
|
-
|
|
236
|
+
MRLog(@"๐ AUDIO PROCESSING: includeMicrophone=%@ includeSystemAudio=%@", includeMicrophone, includeSystemAudio);
|
|
236
237
|
BOOL shouldCaptureMic = includeMicrophone ? [includeMicrophone boolValue] : NO;
|
|
237
238
|
BOOL shouldCaptureSystemAudio = includeSystemAudio ? [includeSystemAudio boolValue] : NO;
|
|
238
|
-
|
|
239
|
+
MRLog(@"๐ AUDIO COMPUTED: shouldCaptureMic=%d shouldCaptureSystemAudio=%d", shouldCaptureMic, shouldCaptureSystemAudio);
|
|
239
240
|
|
|
240
241
|
// Enable audio if either microphone or system audio is requested
|
|
241
242
|
if (@available(macOS 13.0, *)) {
|
|
@@ -245,19 +246,19 @@ static NSString *g_outputPath = nil;
|
|
|
245
246
|
streamConfig.channelCount = 2;
|
|
246
247
|
|
|
247
248
|
if (shouldCaptureMic && shouldCaptureSystemAudio) {
|
|
248
|
-
|
|
249
|
+
MRLog(@"๐ต Both microphone and system audio enabled");
|
|
249
250
|
} else if (shouldCaptureMic) {
|
|
250
|
-
|
|
251
|
+
MRLog(@"๐ค Microphone audio enabled");
|
|
251
252
|
} else {
|
|
252
|
-
|
|
253
|
+
MRLog(@"๐ System audio enabled");
|
|
253
254
|
}
|
|
254
255
|
} else {
|
|
255
256
|
streamConfig.capturesAudio = NO;
|
|
256
|
-
|
|
257
|
+
MRLog(@"๐ Audio disabled");
|
|
257
258
|
}
|
|
258
259
|
} else {
|
|
259
260
|
streamConfig.capturesAudio = NO;
|
|
260
|
-
|
|
261
|
+
MRLog(@"๐ Audio disabled (macOS < 13.0)");
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
// Create pure ScreenCaptureKit recording output
|
|
@@ -328,7 +329,7 @@ static NSString *g_outputPath = nil;
|
|
|
328
329
|
return;
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
|
|
332
|
+
MRLog(@"โ
Pure recording output added to stream");
|
|
332
333
|
|
|
333
334
|
// Start capture with recording
|
|
334
335
|
[g_stream startCaptureWithCompletionHandler:^(NSError *startError) {
|
|
@@ -336,7 +337,7 @@ static NSString *g_outputPath = nil;
|
|
|
336
337
|
NSLog(@"โ Failed to start pure capture: %@", startError);
|
|
337
338
|
g_isRecording = NO;
|
|
338
339
|
} else {
|
|
339
|
-
|
|
340
|
+
MRLog(@"๐ PURE ScreenCaptureKit recording started successfully!");
|
|
340
341
|
g_isRecording = YES;
|
|
341
342
|
}
|
|
342
343
|
}];
|
|
@@ -351,7 +352,7 @@ static NSString *g_outputPath = nil;
|
|
|
351
352
|
return;
|
|
352
353
|
}
|
|
353
354
|
|
|
354
|
-
|
|
355
|
+
MRLog(@"๐ Stopping pure ScreenCaptureKit recording");
|
|
355
356
|
|
|
356
357
|
// Store stream reference to prevent it from being deallocated
|
|
357
358
|
SCStream *streamToStop = g_stream;
|
|
@@ -360,7 +361,7 @@ static NSString *g_outputPath = nil;
|
|
|
360
361
|
if (error) {
|
|
361
362
|
NSLog(@"โ Stop error: %@", error);
|
|
362
363
|
}
|
|
363
|
-
|
|
364
|
+
MRLog(@"โ
Pure stream stopped");
|
|
364
365
|
|
|
365
366
|
// Immediately reset recording state to allow new recordings
|
|
366
367
|
g_isRecording = NO;
|
|
@@ -383,7 +384,7 @@ static NSString *g_outputPath = nil;
|
|
|
383
384
|
|
|
384
385
|
+ (void)finalizeRecording {
|
|
385
386
|
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
386
|
-
|
|
387
|
+
MRLog(@"๐ฌ Finalizing pure ScreenCaptureKit recording");
|
|
387
388
|
|
|
388
389
|
// Set cleanup flag now that we're actually cleaning up
|
|
389
390
|
g_isCleaningUp = YES;
|
|
@@ -391,7 +392,7 @@ static NSString *g_outputPath = nil;
|
|
|
391
392
|
|
|
392
393
|
if (g_recordingOutput) {
|
|
393
394
|
// SCRecordingOutput finalizes automatically
|
|
394
|
-
|
|
395
|
+
MRLog(@"โ
Pure recording output finalized");
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
[ScreenCaptureKitRecorder cleanupVideoWriter];
|
|
@@ -405,30 +406,30 @@ static NSString *g_outputPath = nil;
|
|
|
405
406
|
|
|
406
407
|
+ (void)cleanupVideoWriter {
|
|
407
408
|
@synchronized([ScreenCaptureKitRecorder class]) {
|
|
408
|
-
|
|
409
|
+
MRLog(@"๐งน Starting ScreenCaptureKit cleanup");
|
|
409
410
|
|
|
410
411
|
// Clean up in proper order to prevent crashes
|
|
411
412
|
if (g_stream) {
|
|
412
413
|
g_stream = nil;
|
|
413
|
-
|
|
414
|
+
MRLog(@"โ
Stream reference cleared");
|
|
414
415
|
}
|
|
415
416
|
|
|
416
417
|
if (g_recordingOutput) {
|
|
417
418
|
g_recordingOutput = nil;
|
|
418
|
-
|
|
419
|
+
MRLog(@"โ
Recording output reference cleared");
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
if (g_streamDelegate) {
|
|
422
423
|
g_streamDelegate = nil;
|
|
423
|
-
|
|
424
|
+
MRLog(@"โ
Stream delegate reference cleared");
|
|
424
425
|
}
|
|
425
426
|
|
|
426
427
|
g_isRecording = NO;
|
|
427
428
|
g_isCleaningUp = NO; // Reset cleanup flag
|
|
428
429
|
g_outputPath = nil;
|
|
429
430
|
|
|
430
|
-
|
|
431
|
+
MRLog(@"๐งน Pure ScreenCaptureKit cleanup complete");
|
|
431
432
|
}
|
|
432
433
|
}
|
|
433
434
|
|
|
434
|
-
@end
|
|
435
|
+
@end
|