node-mac-recorder 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
static SCStream *g_scStream = nil;
|
|
6
6
|
static SCRecordingOutput *g_scRecordingOutput = nil;
|
|
7
7
|
static NSURL *g_outputURL = nil;
|
|
8
|
+
static ScreenCaptureKitRecorder *g_scDelegate = nil;
|
|
9
|
+
static NSArray<SCRunningApplication *> *g_appsToExclude = nil;
|
|
10
|
+
static NSArray<SCWindow *> *g_windowsToExclude = nil;
|
|
8
11
|
|
|
9
12
|
@interface ScreenCaptureKitRecorder () <SCRecordingOutputDelegate>
|
|
10
13
|
@end
|
|
@@ -110,25 +113,41 @@ static NSURL *g_outputURL = nil;
|
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
// Keep strong references to excluded items for the lifetime of the stream
|
|
117
|
+
g_appsToExclude = [appsToExclude copy];
|
|
118
|
+
g_windowsToExclude = [windowsToExclude copy];
|
|
119
|
+
|
|
113
120
|
SCContentFilter *filter = nil;
|
|
114
121
|
if (appsToExclude.count > 0) {
|
|
115
122
|
if (@available(macOS 13.0, *)) {
|
|
116
|
-
filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingApplications:
|
|
123
|
+
filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingApplications:(g_appsToExclude ?: @[]) exceptingWindows:(g_windowsToExclude ?: @[])];
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
if (!filter) {
|
|
120
127
|
if (@available(macOS 13.0, *)) {
|
|
121
|
-
filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:
|
|
128
|
+
filter = [[SCContentFilter alloc] initWithDisplay:targetDisplay excludingWindows:(g_windowsToExclude ?: @[])];
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
if (!filter) { return NO; }
|
|
125
132
|
|
|
126
133
|
SCStreamConfiguration *cfg = [[SCStreamConfiguration alloc] init];
|
|
127
134
|
if (captureArea && captureArea[@"width"] && captureArea[@"height"]) {
|
|
128
|
-
CGRect
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
CGRect displayFrame = targetDisplay.frame;
|
|
136
|
+
double x = [captureArea[@"x"] doubleValue];
|
|
137
|
+
double yBottom = [captureArea[@"y"] doubleValue];
|
|
138
|
+
double w = [captureArea[@"width"] doubleValue];
|
|
139
|
+
double h = [captureArea[@"height"] doubleValue];
|
|
140
|
+
|
|
141
|
+
// Convert bottom-left origin (used by legacy path) to top-left for SC
|
|
142
|
+
double y = displayFrame.size.height - yBottom - h;
|
|
143
|
+
|
|
144
|
+
// Clamp to display bounds to avoid invalid sourceRect
|
|
145
|
+
if (w < 1) w = 1; if (h < 1) h = 1;
|
|
146
|
+
if (x < 0) x = 0; if (y < 0) y = 0;
|
|
147
|
+
if (x + w > displayFrame.size.width) w = MAX(1, displayFrame.size.width - x);
|
|
148
|
+
if (y + h > displayFrame.size.height) h = MAX(1, displayFrame.size.height - y);
|
|
149
|
+
|
|
150
|
+
CGRect src = CGRectMake(x, y, w, h);
|
|
132
151
|
cfg.sourceRect = src;
|
|
133
152
|
cfg.width = (int)src.size.width;
|
|
134
153
|
cfg.height = (int)src.size.height;
|
|
@@ -139,38 +158,63 @@ static NSURL *g_outputURL = nil;
|
|
|
139
158
|
}
|
|
140
159
|
cfg.showsCursor = captureCursorNum.boolValue;
|
|
141
160
|
if (includeMicNum || includeSystemAudioNum) {
|
|
142
|
-
|
|
161
|
+
if (@available(macOS 13.0, *)) {
|
|
162
|
+
cfg.capturesAudio = YES;
|
|
163
|
+
}
|
|
143
164
|
}
|
|
144
165
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
__block NSError *startErr = nil;
|
|
167
|
+
__block BOOL startedOK = NO;
|
|
168
|
+
dispatch_semaphore_t startSem = dispatch_semaphore_create(0);
|
|
169
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
170
|
+
if (@available(macOS 15.0, *)) {
|
|
171
|
+
g_scStream = [[SCStream alloc] initWithFilter:filter configuration:cfg delegate:nil];
|
|
172
|
+
|
|
173
|
+
SCRecordingOutputConfiguration *recCfg = [[SCRecordingOutputConfiguration alloc] init];
|
|
174
|
+
g_outputURL = [NSURL fileURLWithPath:outputPath];
|
|
175
|
+
recCfg.outputURL = g_outputURL;
|
|
176
|
+
recCfg.outputFileType = AVFileTypeQuickTimeMovie;
|
|
177
|
+
|
|
178
|
+
id<SCRecordingOutputDelegate> delegateObject = (id<SCRecordingOutputDelegate>)delegate;
|
|
179
|
+
if (!delegateObject) {
|
|
180
|
+
if (!g_scDelegate) {
|
|
181
|
+
g_scDelegate = [[ScreenCaptureKitRecorder alloc] init];
|
|
182
|
+
}
|
|
183
|
+
delegateObject = (id<SCRecordingOutputDelegate>)g_scDelegate;
|
|
184
|
+
}
|
|
185
|
+
g_scRecordingOutput = [[SCRecordingOutput alloc] initWithConfiguration:recCfg delegate:delegateObject];
|
|
186
|
+
|
|
187
|
+
NSError *addErr = nil;
|
|
188
|
+
BOOL added = [g_scStream addRecordingOutput:g_scRecordingOutput error:&addErr];
|
|
189
|
+
if (!added) {
|
|
190
|
+
startErr = addErr ?: [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-3 userInfo:nil];
|
|
191
|
+
g_scRecordingOutput = nil; g_scStream = nil; g_outputURL = nil;
|
|
192
|
+
dispatch_semaphore_signal(startSem);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
160
195
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
196
|
+
[g_scStream startCaptureWithCompletionHandler:^(NSError * _Nullable err) {
|
|
197
|
+
startErr = err;
|
|
198
|
+
startedOK = (err == nil);
|
|
199
|
+
dispatch_semaphore_signal(startSem);
|
|
200
|
+
}];
|
|
201
|
+
} else {
|
|
202
|
+
startErr = [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-4 userInfo:@{NSLocalizedDescriptionKey: @"Requires macOS 15+"}];
|
|
165
203
|
dispatch_semaphore_signal(startSem);
|
|
166
|
-
}];
|
|
167
|
-
dispatch_semaphore_wait(startSem, DISPATCH_TIME_FOREVER);
|
|
168
|
-
if (startErr) {
|
|
169
|
-
if (error) { *error = startErr; }
|
|
170
|
-
[g_scStream removeRecordingOutput:g_scRecordingOutput error:nil];
|
|
171
|
-
g_scRecordingOutput = nil; g_scStream = nil; g_outputURL = nil;
|
|
172
|
-
return NO;
|
|
173
204
|
}
|
|
205
|
+
});
|
|
206
|
+
dispatch_semaphore_wait(startSem, DISPATCH_TIME_FOREVER);
|
|
207
|
+
if (startErr) {
|
|
208
|
+
if (error) { *error = startErr; }
|
|
209
|
+
if (g_scRecordingOutput && g_scStream) {
|
|
210
|
+
if (@available(macOS 15.0, *)) {
|
|
211
|
+
[g_scStream removeRecordingOutput:g_scRecordingOutput error:nil];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
g_scRecordingOutput = nil; g_scStream = nil; g_outputURL = nil;
|
|
215
|
+
return NO;
|
|
216
|
+
}
|
|
217
|
+
if (startedOK) {
|
|
174
218
|
return YES;
|
|
175
219
|
}
|
|
176
220
|
|