Serialized the capturing service calls for the RecordingViewModel view model in the Recording package target.

This commit is contained in:
2026-07-04 14:55:49 +02:00
parent 702333038c
commit ed9d1e86fa
2 changed files with 70 additions and 40 deletions
@@ -43,6 +43,10 @@ extension RecordingView {
@ObservationIgnored
private let locale: Binding<Locale>
/// The task that serializes the calls to the capturing service, so rapid state changes can never reach the service out of order.
@ObservationIgnored
private var taskCapturer: Task<Void, Never>?
/// The task that updates ``elapsedSeconds`` from the measured recording time once per second while recording.
@ObservationIgnored
private var taskTimer: Task<Void, Never>?
@@ -133,8 +137,8 @@ extension RecordingView {
stopTimer()
Task {
try? await capturer.pause()
enqueueCapturer {
try? await self.capturer.pause()
}
case .paused:
state = .recording
@@ -156,8 +160,8 @@ extension RecordingView {
stopTimer()
Task {
await processInput()
enqueueCapturer {
await self.processInput()
}
default:
break
@@ -207,21 +211,34 @@ private extension RecordingView.Model {
state = .notRecording
}
/// Enqueues an operation behind any previously enqueued ones, so the calls to the capturing service always reach it in the order
/// the states changed, no matter how quickly the user presses the view's controls.
///
/// - Parameter operation: The operation on the capturing service to enqueue.
func enqueueCapturer(
_ operation: @escaping @MainActor () async -> Void
) {
taskCapturer = Task { [taskCapturer] in
await taskCapturer?.value
await operation()
}
}
/// Starts the audio capture through the attached ``Capturing``, falling back to the not-recording state when the service fails.
///
/// - Parameter isNewRecording: Whether a new recording should be started, as opposed to a paused one being resumed.
func startCapturer(
_ isNewRecording: Bool
) {
Task {
enqueueCapturer {
do {
isNewRecording
? try await capturer.start()
: try await capturer.resume()
? try await self.capturer.start()
: try await self.capturer.resume()
} catch {
stopTimer()
self.stopTimer()
state = .notRecording
self.state = .notRecording
}
}
}