SwiftUI项目中如何监听AVAudioPlayerNode播放完成并自动停止
AVAudioPlayerNode 播放完成自动停止解决方案
AVAudioPlayerNode 没有系统原生的代理回调方法,你自定义的 audioPlayerDidFinishPlaying2 不会被系统自动触发,需要使用 scheduleFile 方法的带完成回调重载来实现播放结束监听。
核心修改点
- 将
AVAudioPlayerNode声明为类属性,避免每次播放重复创建、挂载节点导致的异常 - 调用带
completionCallbackType参数的scheduleFile方法,指定.dataPlayedBack类型的回调(所有音频数据播放完成后触发) - 回调默认在后台队列执行,操作UI相关的
isPlaying状态需要切换到主队列执行 - 每次播放前先停止上一次的播放任务、重置引擎,避免重复调度导致的崩溃
修改后完整代码
import SwiftUI import AVFAudio import Combine class AudioPlayer: NSObject, ObservableObject, AVAudioPlayerDelegate { let objectWillChange = PassthroughSubject<AudioPlayer, Never>() var isPlaying = false { didSet { objectWillChange.send(self) } } var audioPlayer: AVAudioPlayer! // 新增播放器节点作为类属性 var avPlayer: AVAudioPlayerNode! let engine = AVAudioEngine() let speedControl = AVAudioUnitVarispeed() let pitchControl = AVAudioUnitTimePitch() func startPlayback (audio: URL) { let playbackSession = AVAudioSession.sharedInstance() do { try playbackSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) } catch { print("Playing over the device's speakers failed") } do { audioPlayer = try AVAudioPlayer(contentsOf: audio) audioPlayer.delegate = self audioPlayer.play() isPlaying = true } catch { print("Playback failed.") } } func stopPlayback() { audioPlayer.stop() isPlaying = false } func stopPlayback2() { avPlayer.stop() engine.stop() engine.reset() isPlaying = false } func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { if flag { isPlaying = false print("Playback Stopped") } } func play(_ url: URL) throws { // 先停止上一次播放任务,避免冲突 if avPlayer != nil { avPlayer.stop() engine.detach(avPlayer) } if engine.isRunning { stopPlayback2() } let file = try AVAudioFile(forReading: url) avPlayer = AVAudioPlayerNode() engine.attach(avPlayer) engine.attach(pitchControl) engine.attach(speedControl) engine.connect(avPlayer, to: speedControl, format: nil) engine.connect(speedControl, to: pitchControl, format: nil) engine.connect(pitchControl, to: engine.mainMixerNode, format: nil) // 使用带完成回调的调度方法,音频全部播放完成后触发回调 avPlayer.scheduleFile(file, at: nil, completionCallbackType: .dataPlayedBack) { [weak self] _ in DispatchQueue.main.async { self?.stopPlayback2() print("Playback Engine Stopped") } } isPlaying = true try engine.start() avPlayer.play() } }
注意事项
- 变速、变调播放场景下该回调依然可以正常触发,不会受播放速度影响
- 手动调用停止方法时,不会触发该播放完成回调,符合常规业务逻辑
内容的提问来源于stack exchange,提问作者phiredrop
相关产品推荐
相关产品推荐

