diff --git a/apps/shared/ClawdisKit/Sources/ClawdisKit/PCMStreamingAudioPlayer.swift b/apps/shared/ClawdisKit/Sources/ClawdisKit/PCMStreamingAudioPlayer.swift index 66238cd5c..05d122974 100644 --- a/apps/shared/ClawdisKit/Sources/ClawdisKit/PCMStreamingAudioPlayer.swift +++ b/apps/shared/ClawdisKit/Sources/ClawdisKit/PCMStreamingAudioPlayer.swift @@ -89,9 +89,10 @@ public final class PCMStreamingAudioPlayer { } self.pendingBuffers += 1 - self.player.scheduleBuffer(buffer) { [weak self] in - Task { @MainActor in - guard let self else { return } + Task.detached { [weak self] in + guard let self else { return } + await self.player.scheduleBuffer(buffer) + await MainActor.run { self.pendingBuffers = max(0, self.pendingBuffers - 1) if self.inputFinished && self.pendingBuffers == 0 { self.finish(StreamingPlaybackResult(finished: true, interruptedAt: nil)) diff --git a/apps/shared/ClawdisKit/Tests/ClawdisKitTests/PCMStreamingAudioPlayerTests.swift b/apps/shared/ClawdisKit/Tests/ClawdisKitTests/PCMStreamingAudioPlayerTests.swift new file mode 100644 index 000000000..c502bfdd2 --- /dev/null +++ b/apps/shared/ClawdisKit/Tests/ClawdisKitTests/PCMStreamingAudioPlayerTests.swift @@ -0,0 +1,26 @@ +import XCTest +@testable import ClawdisKit + +final class PCMStreamingAudioPlayerTests: XCTestCase { + @MainActor + func testStopDuringPCMStreamReturnsInterruptedResult() async { + var continuation: AsyncThrowingStream.Continuation? + let stream = AsyncThrowingStream { cont in + continuation = cont + let samples = Data(repeating: 0, count: 44_100) + cont.yield(samples) + } + + let task = Task { @MainActor in + await PCMStreamingAudioPlayer.shared.play(stream: stream, sampleRate: 44_100) + } + + try? await Task.sleep(nanoseconds: 120_000_000) + let interruptedAt = PCMStreamingAudioPlayer.shared.stop() + continuation?.finish() + + let result = await task.value + XCTAssertFalse(result.finished) + XCTAssertNotNil(interruptedAt) + } +}