Skip to content

Commit 60cb24c

Browse files
committed
improve macos screencapturer thread safety
1 parent f1d51da commit 60cb24c

File tree

1 file changed

+36
-21
lines changed

1 file changed

+36
-21
lines changed

Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,19 @@ public class MacOSScreenCapturer: VideoCapturer {
3232
// TODO: Make it possible to change dynamically
3333
public let captureSource: MacOSScreenCaptureSource?
3434

35-
// SCStream
36-
private var _scStream: SCStream?
37-
38-
// cached frame for resending to maintain minimum of 1 fps
39-
private var _lastFrame: LKRTCVideoFrame?
40-
private var _resendTimer: Task<Void, Error>?
41-
4235
/// The ``ScreenShareCaptureOptions`` used for this capturer.
4336
public let options: ScreenShareCaptureOptions
4437

38+
struct State {
39+
// SCStream
40+
var scStream: SCStream?
41+
// Cached frame for resending to maintain minimum of 1 fps
42+
var lastFrame: LKRTCVideoFrame?
43+
var resendTimer: Task<Void, Error>?
44+
}
45+
46+
private var _screenCapturerState = StateSync(State())
47+
4548
init(delegate: LKRTCVideoCapturerDelegate, captureSource: MacOSScreenCaptureSource, options: ScreenShareCaptureOptions) {
4649
self.captureSource = captureSource
4750
self.options = options
@@ -96,7 +99,7 @@ public class MacOSScreenCapturer: VideoCapturer {
9699
try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: nil)
97100
try await stream.startCapture()
98101

99-
_scStream = stream
102+
_screenCapturerState.mutate { $0.scStream = stream }
100103

101104
return true
102105
}
@@ -107,17 +110,22 @@ public class MacOSScreenCapturer: VideoCapturer {
107110
// Already stopped
108111
guard didStop else { return false }
109112

110-
guard let stream = _scStream else {
113+
guard let stream = _screenCapturerState.read({ $0.scStream }) else {
111114
throw LiveKitError(.invalidState, message: "SCStream is nil")
112115
}
113116

114117
// Stop resending paused frames
115-
_resendTimer?.cancel()
116-
_resendTimer = nil
118+
_screenCapturerState.mutate {
119+
$0.resendTimer?.cancel()
120+
$0.resendTimer = nil
121+
}
117122

118123
try await stream.stopCapture()
119124
try stream.removeStreamOutput(self, type: .screen)
120-
_scStream = nil
125+
126+
_screenCapturerState.mutate {
127+
$0.scStream = nil
128+
}
121129

122130
return true
123131
}
@@ -149,10 +157,12 @@ public class MacOSScreenCapturer: VideoCapturer {
149157
rotation: ._0,
150158
timeStampNs: timeStampNs)
151159

152-
capture(frame: rtcFrame, capturer: capturer, options: options)
160+
// Cache last frame
161+
_screenCapturerState.mutate {
162+
$0.lastFrame = rtcFrame
163+
}
153164

154-
// cache last frame
155-
_lastFrame = rtcFrame
165+
capture(frame: rtcFrame, capturer: capturer, options: options)
156166
}
157167
}
158168

@@ -167,9 +177,9 @@ extension MacOSScreenCapturer {
167177
return
168178
}
169179

170-
log("No movement detected, resending frame...")
180+
log("No movement detected, resending frame...", .trace)
171181

172-
guard let frame = _lastFrame else { return }
182+
guard let frame = _screenCapturerState.read({ $0.lastFrame }) else { return }
173183

174184
// create a new frame with new time stamp
175185
let newFrame = LKRTCVideoFrame(buffer: frame.buffer,
@@ -215,17 +225,22 @@ extension MacOSScreenCapturer: SCStreamOutput {
215225
// let contentScale = attachments[.contentScale] as? CGFloat,
216226
let scaleFactor = attachments[.scaleFactor] as? CGFloat else { return }
217227

218-
capture(sampleBuffer, contentRect: contentRect, scaleFactor: scaleFactor)
219-
220-
_resendTimer?.cancel()
221-
_resendTimer = Task.detached(priority: .utility) { [weak self] in
228+
// Schedule resend timer
229+
let newTimer = Task.detached(priority: .utility) { [weak self] in
222230
while true {
223231
try? await Task.sleep(nanoseconds: UInt64(1 * 1_000_000_000))
224232
if Task.isCancelled { break }
225233
guard let self else { break }
226234
try await self._capturePreviousFrame()
227235
}
228236
}
237+
238+
_screenCapturerState.mutate {
239+
$0.resendTimer?.cancel()
240+
$0.resendTimer = newTimer
241+
}
242+
243+
capture(sampleBuffer, contentRect: contentRect, scaleFactor: scaleFactor)
229244
}
230245
}
231246

0 commit comments

Comments
 (0)