@@ -12,7 +12,7 @@ import Foundation
12
12
import ScreenCaptureKit
13
13
14
14
/// A structure that contains the video data to render.
15
- struct CapturedFrame : Sendable {
15
+ struct CapturedFrame : Sendable {
16
16
static let invalid = CapturedFrame ( surface: nil , contentRect: . zero, contentScale: 0 , scaleFactor: 0 )
17
17
18
18
let surface : IOSurface ?
@@ -127,9 +127,10 @@ class CaptureEngine: NSObject, @unchecked Sendable, SCRecordingOutputDelegate {
127
127
}
128
128
129
129
/// A class that handles output from an SCStream, and handles stream errors.
130
+ @MainActor
130
131
private class CaptureEngineStreamOutput : NSObject , SCStreamOutput , SCStreamDelegate {
131
- var pcmBufferHandler : ( ( AVAudioPCMBuffer ) -> Void ) ?
132
- var capturedFrameHandler : ( ( CapturedFrame ) -> Void ) ?
132
+ var pcmBufferHandler : ( @ Sendable ( AVAudioPCMBuffer ) -> Void ) ?
133
+ var capturedFrameHandler : ( @ Sendable ( CapturedFrame ) -> Void ) ?
133
134
134
135
// Store the the startCapture continuation, so you can cancel it if an error occurs.
135
136
private var continuation : AsyncThrowingStream < CapturedFrame , any Error > . Continuation ?
@@ -138,7 +139,7 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
138
139
self . continuation = continuation
139
140
}
140
141
141
- func stream( _: SCStream , didOutputSampleBuffer sampleBuffer: CMSampleBuffer , of outputType: SCStreamOutputType ) {
142
+ nonisolated func stream( _: SCStream , didOutputSampleBuffer sampleBuffer: CMSampleBuffer , of outputType: SCStreamOutputType ) {
142
143
// Return early if the sample buffer is invalid.
143
144
guard sampleBuffer. isValid else { return }
144
145
@@ -147,20 +148,42 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
147
148
case . screen:
148
149
// Create a CapturedFrame structure for a video sample buffer.
149
150
guard let frame = createFrame ( for: sampleBuffer) else { return }
150
- capturedFrameHandler ? ( frame)
151
+ Task { @MainActor [ self ] in
152
+ self . capturedFrameHandler ? ( frame)
153
+ }
151
154
case . audio:
152
155
// Create an AVAudioPCMBuffer from an audio sample buffer.
153
156
guard let samples = createPCMBuffer ( for: sampleBuffer) else { return }
154
- pcmBufferHandler ? ( samples)
157
+ Task { @MainActor [ self ] in
158
+ self . pcmBufferHandler ? ( samples)
159
+ }
155
160
case . microphone:
156
161
return
157
162
@unknown default :
158
163
fatalError ( " Encountered unknown stream output type: \( outputType) " )
159
164
}
160
165
}
161
166
167
+ nonisolated func stream( _: SCStream , didStopWithError error: any Error ) {
168
+ if ( error as NSError ) . code == - 3817 {
169
+ // User stopped the stream. Call AppDelegate's method to stop recording gracefully
170
+ Task { @MainActor in
171
+ let pickerManager = ContentSharingPickerManager . shared
172
+ pickerManager. deactivatePicker ( )
173
+ AppDelegate . shared. stopRecording ( )
174
+ }
175
+ } else {
176
+ // Handle other errors
177
+ print ( " Stream stopped with error: \( error. localizedDescription) " )
178
+ }
179
+ // Finish the AsyncThrowingStream if it's still running
180
+ Task { @MainActor [ self ] in
181
+ self . continuation? . finish ( throwing: error)
182
+ }
183
+ }
184
+
162
185
/// Create a `CapturedFrame` for the video sample buffer.
163
- private func createFrame( for sampleBuffer: CMSampleBuffer ) -> CapturedFrame ? {
186
+ private nonisolated func createFrame( for sampleBuffer: CMSampleBuffer ) -> CapturedFrame ? {
164
187
// Retrieve the array of metadata attachments from the sample buffer.
165
188
guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray ( sampleBuffer,
166
189
createIfNecessary: false ) as? [ [ SCStreamFrameInfo : Any ] ] ,
@@ -193,7 +216,7 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
193
216
}
194
217
195
218
// Creates an AVAudioPCMBuffer instance on which to perform an average and peak audio level calculation.
196
- private func createPCMBuffer( for sampleBuffer: CMSampleBuffer ) -> AVAudioPCMBuffer ? {
219
+ private nonisolated func createPCMBuffer( for sampleBuffer: CMSampleBuffer ) -> AVAudioPCMBuffer ? {
197
220
var ablPointer : UnsafePointer < AudioBufferList > ?
198
221
try ? sampleBuffer. withAudioBufferList { audioBufferList, _ in
199
222
ablPointer = audioBufferList. unsafePointer
@@ -203,20 +226,4 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
203
226
let format = AVAudioFormat ( standardFormatWithSampleRate: absd. mSampleRate, channels: absd. mChannelsPerFrame) else { return nil }
204
227
return AVAudioPCMBuffer ( pcmFormat: format, bufferListNoCopy: audioBufferList)
205
228
}
206
-
207
- func stream( _: SCStream , didStopWithError error: any Error ) {
208
- if ( error as NSError ) . code == - 3817 {
209
- // User stopped the stream. Call AppDelegate's method to stop recording gracefully
210
- DispatchQueue . main. async {
211
- let pickerManager = ContentSharingPickerManager . shared
212
- pickerManager. deactivatePicker ( )
213
- AppDelegate . shared. stopRecording ( )
214
- }
215
- } else {
216
- // Handle other errors
217
- print ( " Stream stopped with error: \( error. localizedDescription) " )
218
- }
219
- // Finish the AsyncThrowingStream if it's still running
220
- continuation? . finish ( throwing: error)
221
- }
222
229
}
0 commit comments