Skip to content

Commit 6959e33

Browse files
committed
fix: fucked threading in video capture
1 parent e9a5eb9 commit 6959e33

File tree

1 file changed

+31
-24
lines changed

1 file changed

+31
-24
lines changed

ishare/Capture/CaptureEngine.swift

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Foundation
1212
import ScreenCaptureKit
1313

1414
/// A structure that contains the video data to render.
15-
struct CapturedFrame : Sendable {
15+
struct CapturedFrame: Sendable {
1616
static let invalid = CapturedFrame(surface: nil, contentRect: .zero, contentScale: 0, scaleFactor: 0)
1717

1818
let surface: IOSurface?
@@ -127,9 +127,10 @@ class CaptureEngine: NSObject, @unchecked Sendable, SCRecordingOutputDelegate {
127127
}
128128

129129
/// A class that handles output from an SCStream, and handles stream errors.
130+
@MainActor
130131
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)?
133134

134135
// Store the the startCapture continuation, so you can cancel it if an error occurs.
135136
private var continuation: AsyncThrowingStream<CapturedFrame, any Error>.Continuation?
@@ -138,7 +139,7 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
138139
self.continuation = continuation
139140
}
140141

141-
func stream(_: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of outputType: SCStreamOutputType) {
142+
nonisolated func stream(_: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of outputType: SCStreamOutputType) {
142143
// Return early if the sample buffer is invalid.
143144
guard sampleBuffer.isValid else { return }
144145

@@ -147,20 +148,42 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
147148
case .screen:
148149
// Create a CapturedFrame structure for a video sample buffer.
149150
guard let frame = createFrame(for: sampleBuffer) else { return }
150-
capturedFrameHandler?(frame)
151+
Task { @MainActor [self] in
152+
self.capturedFrameHandler?(frame)
153+
}
151154
case .audio:
152155
// Create an AVAudioPCMBuffer from an audio sample buffer.
153156
guard let samples = createPCMBuffer(for: sampleBuffer) else { return }
154-
pcmBufferHandler?(samples)
157+
Task { @MainActor [self] in
158+
self.pcmBufferHandler?(samples)
159+
}
155160
case .microphone:
156161
return
157162
@unknown default:
158163
fatalError("Encountered unknown stream output type: \(outputType)")
159164
}
160165
}
161166

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+
162185
/// 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? {
164187
// Retrieve the array of metadata attachments from the sample buffer.
165188
guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
166189
createIfNecessary: false) as? [[SCStreamFrameInfo: Any]],
@@ -193,7 +216,7 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
193216
}
194217

195218
// 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? {
197220
var ablPointer: UnsafePointer<AudioBufferList>?
198221
try? sampleBuffer.withAudioBufferList { audioBufferList, _ in
199222
ablPointer = audioBufferList.unsafePointer
@@ -203,20 +226,4 @@ private class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDeleg
203226
let format = AVAudioFormat(standardFormatWithSampleRate: absd.mSampleRate, channels: absd.mChannelsPerFrame) else { return nil }
204227
return AVAudioPCMBuffer(pcmFormat: format, bufferListNoCopy: audioBufferList)
205228
}
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-
}
222229
}

0 commit comments

Comments
 (0)