You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 21:57:00