mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
108 lines
3.0 KiB
Swift
108 lines
3.0 KiB
Swift
@preconcurrency import AVFoundation
|
|
import Foundation
|
|
|
|
final actor AudioPlayerNode {
|
|
static let bufferCounts: Int = 10
|
|
|
|
var currentTime: TimeInterval {
|
|
if playerNode.isPlaying {
|
|
guard
|
|
let nodeTime = playerNode.lastRenderTime,
|
|
let playerTime = playerNode.playerTime(forNodeTime: nodeTime) else {
|
|
return 0.0
|
|
}
|
|
return TimeInterval(playerTime.sampleTime) / playerTime.sampleRate
|
|
}
|
|
return 0.0
|
|
}
|
|
private(set) var isPaused = false
|
|
private(set) var isRunning = false
|
|
private(set) var soundTransfrom = SoundTransform()
|
|
private let playerNode: AVAudioPlayerNode
|
|
private var audioTime = AudioTime()
|
|
private var scheduledAudioBuffers: Int = 0
|
|
private var isBuffering = true
|
|
private weak var player: AudioPlayer?
|
|
private var format: AVAudioFormat? {
|
|
didSet {
|
|
guard format != oldValue else {
|
|
return
|
|
}
|
|
Task { [format] in
|
|
await player?.connect(self, format: format)
|
|
}
|
|
}
|
|
}
|
|
|
|
init(player: AudioPlayer, playerNode: AVAudioPlayerNode) {
|
|
self.player = player
|
|
self.playerNode = playerNode
|
|
}
|
|
|
|
func setSoundTransfrom(_ soundTransfrom: SoundTransform) {
|
|
soundTransfrom.apply(playerNode)
|
|
}
|
|
|
|
func enqueue(_ audioBuffer: AVAudioBuffer, when: AVAudioTime) async {
|
|
format = audioBuffer.format
|
|
guard let audioBuffer = audioBuffer as? AVAudioPCMBuffer, await player?.isConnected(self) == true else {
|
|
return
|
|
}
|
|
if !audioTime.hasAnchor {
|
|
audioTime.anchor(playerNode.lastRenderTime ?? AVAudioTime(hostTime: 0))
|
|
}
|
|
scheduledAudioBuffers += 1
|
|
if !isPaused && !playerNode.isPlaying && Self.bufferCounts <= scheduledAudioBuffers {
|
|
playerNode.play()
|
|
}
|
|
Task {
|
|
audioTime.advanced(Int64(audioBuffer.frameLength))
|
|
await playerNode.scheduleBuffer(audioBuffer, at: audioTime.at)
|
|
scheduledAudioBuffers -= 1
|
|
if scheduledAudioBuffers == 0 {
|
|
isBuffering = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func detach() async {
|
|
stopRunning()
|
|
await player?.detach(self)
|
|
}
|
|
}
|
|
|
|
extension AudioPlayerNode: AsyncRunner {
|
|
// MARK: AsyncRunner
|
|
func startRunning() {
|
|
guard !isRunning else {
|
|
return
|
|
}
|
|
scheduledAudioBuffers = 0
|
|
isRunning = true
|
|
}
|
|
|
|
func stopRunning() {
|
|
guard isRunning else {
|
|
return
|
|
}
|
|
if playerNode.isPlaying {
|
|
playerNode.stop()
|
|
playerNode.reset()
|
|
}
|
|
audioTime.reset()
|
|
format = nil
|
|
isRunning = false
|
|
}
|
|
}
|
|
|
|
extension AudioPlayerNode: Hashable {
|
|
// MARK: Hashable
|
|
nonisolated public static func == (lhs: AudioPlayerNode, rhs: AudioPlayerNode) -> Bool {
|
|
lhs === rhs
|
|
}
|
|
|
|
nonisolated public func hash(into hasher: inout Hasher) {
|
|
hasher.combine(ObjectIdentifier(self))
|
|
}
|
|
}
|