98 lines
2.9 KiB
Swift
98 lines
2.9 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import Clawdbot
|
|
|
|
@Suite(.serialized) struct TalkAudioPlayerTests {
|
|
@MainActor
|
|
@Test func playDoesNotHangWhenPlaybackEndsOrFails() async throws {
|
|
let wav = makeWav16Mono(sampleRate: 8000, samples: 80)
|
|
defer { _ = TalkAudioPlayer.shared.stop() }
|
|
|
|
_ = try await withTimeout(seconds: 2.0) {
|
|
await TalkAudioPlayer.shared.play(data: wav)
|
|
}
|
|
|
|
#expect(true)
|
|
}
|
|
|
|
@MainActor
|
|
@Test func playDoesNotHangWhenPlayIsCalledTwice() async throws {
|
|
let wav = makeWav16Mono(sampleRate: 8000, samples: 800)
|
|
defer { _ = TalkAudioPlayer.shared.stop() }
|
|
|
|
let first = Task { @MainActor in
|
|
await TalkAudioPlayer.shared.play(data: wav)
|
|
}
|
|
|
|
await Task.yield()
|
|
_ = await TalkAudioPlayer.shared.play(data: wav)
|
|
|
|
_ = try await withTimeout(seconds: 2.0) {
|
|
await first.value
|
|
}
|
|
#expect(true)
|
|
}
|
|
}
|
|
|
|
private struct TimeoutError: Error {}
|
|
|
|
private func withTimeout<T: Sendable>(
|
|
seconds: Double,
|
|
_ work: @escaping @Sendable () async throws -> T) async throws -> T
|
|
{
|
|
try await withThrowingTaskGroup(of: T.self) { group in
|
|
group.addTask {
|
|
try await work()
|
|
}
|
|
group.addTask {
|
|
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
|
throw TimeoutError()
|
|
}
|
|
let result = try await group.next()
|
|
group.cancelAll()
|
|
guard let result else { throw TimeoutError() }
|
|
return result
|
|
}
|
|
}
|
|
|
|
private func makeWav16Mono(sampleRate: UInt32, samples: Int) -> Data {
|
|
let channels: UInt16 = 1
|
|
let bitsPerSample: UInt16 = 16
|
|
let blockAlign = channels * (bitsPerSample / 8)
|
|
let byteRate = sampleRate * UInt32(blockAlign)
|
|
let dataSize = UInt32(samples) * UInt32(blockAlign)
|
|
|
|
var data = Data()
|
|
data.append(contentsOf: [0x52, 0x49, 0x46, 0x46]) // RIFF
|
|
data.appendLEUInt32(36 + dataSize)
|
|
data.append(contentsOf: [0x57, 0x41, 0x56, 0x45]) // WAVE
|
|
|
|
data.append(contentsOf: [0x66, 0x6D, 0x74, 0x20]) // fmt
|
|
data.appendLEUInt32(16) // PCM
|
|
data.appendLEUInt16(1) // audioFormat
|
|
data.appendLEUInt16(channels)
|
|
data.appendLEUInt32(sampleRate)
|
|
data.appendLEUInt32(byteRate)
|
|
data.appendLEUInt16(blockAlign)
|
|
data.appendLEUInt16(bitsPerSample)
|
|
|
|
data.append(contentsOf: [0x64, 0x61, 0x74, 0x61]) // data
|
|
data.appendLEUInt32(dataSize)
|
|
|
|
// Silence samples.
|
|
data.append(Data(repeating: 0, count: Int(dataSize)))
|
|
return data
|
|
}
|
|
|
|
extension Data {
|
|
fileprivate mutating func appendLEUInt16(_ value: UInt16) {
|
|
var v = value.littleEndian
|
|
Swift.withUnsafeBytes(of: &v) { append(contentsOf: $0) }
|
|
}
|
|
|
|
fileprivate mutating func appendLEUInt32(_ value: UInt32) {
|
|
var v = value.littleEndian
|
|
Swift.withUnsafeBytes(of: &v) { append(contentsOf: $0) }
|
|
}
|
|
}
|