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

iOS中AVAudioPlayer连续播放失败:audioPlayerDidFinishPlaying未触发

音频顺序播放问题排查与解决

问题描述

需要按顺序播放三段音频:getReady→nextExercise→burpees,预期流程:

  • 在WorkoutTabataViewController中调用playGetReady()
  • 播放第一段音频
  • 音频播放结束后自动触发audioPlayerDidFinishPlaying()
  • 调用playNextSound()播放下一段音频

实际问题:仅第一段音频播放,且audioPlayerDidFinishPlaying()未被触发,相关代码如下:

class AudioPlayerManager: AVAudioPlayerDelegate {
    var description: String
    
    static let shared = AudioPlayerManager()
    var audioPlayer: AVAudioPlayer?
    var workoutVC: WorkoutTabataViewController?
    var mainVC: MainTabataViewController?
    
    var currentSound = 0
    let urls: [URL]
    
    init() {
        self.description = ""
        //First sound
        let getReady = Bundle.main.path(forResource: "Get ready", ofType: "mp3")!
        let urlGetReady = URL(fileURLWithPath: getReady)
        
        //Second sound
        let nextExercise = Bundle.main.path(forResource: "Next Exercise", ofType: "mp3")!
        let urlNextExercise = URL(fileURLWithPath: nextExercise)
        
        //Third sound
        let burpees = Bundle.main.path(forResource: "Burpees", ofType: "mp3")!
        let urlBurpees = URL(fileURLWithPath: burpees)
        
        urls = [urlGetReady, urlNextExercise, urlBurpees]
    }
    
    func playGetReady() {

        do {
            audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound])
            audioPlayer?.delegate = self
            audioPlayer?.play()
        } catch {
            print(error)
        }
    }
    
    func playNextSound() {
        currentSound += 1

        if currentSound < urls.count {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound])
                audioPlayer?.delegate = self
                audioPlayer?.play()
            } catch {
                print(error)
            }
        }
    }
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
 
        if flag {
            playNextSound()
        }
    }
}

问题根源

  1. 未配置音频会话:AVAudioPlayer的播放完成回调依赖活跃的AVAudioSession,未初始化激活会话会导致系统无法传递播放完成信号。
  2. 未继承NSObject:AVAudioPlayerDelegate是Objective-C协议,Swift类必须继承NSObject才能正确响应代理回调。
  3. 播放索引未重置:多次调用playGetReady()时,currentSound保留之前数值会导致播放顺序混乱(虽不是当前回调失效直接原因,但属于潜在问题)。

解决方案

修正后的代码如下,解决了所有问题:

import AVFoundation

class AudioPlayerManager: NSObject, AVAudioPlayerDelegate {
    var description: String
    
    static let shared = AudioPlayerManager()
    var audioPlayer: AVAudioPlayer?
    var workoutVC: WorkoutTabataViewController?
    var mainVC: MainTabataViewController?
    
    var currentSound = 0
    let urls: [URL]
    
    // 私有化初始化,保证单例唯一性
    private override init() {
        self.description = ""
        // 初始化音频URL
        let getReady = Bundle.main.path(forResource: "Get ready", ofType: "mp3")!
        let urlGetReady = URL(fileURLWithPath: getReady)
        
        let nextExercise = Bundle.main.path(forResource: "Next Exercise", ofType: "mp3")!
        let urlNextExercise = URL(fileURLWithPath: nextExercise)
        
        let burpees = Bundle.main.path(forResource: "Burpees", ofType: "mp3")!
        let urlBurpees = URL(fileURLWithPath: burpees)
        
        urls = [urlGetReady, urlNextExercise, urlBurpees]
        super.init()
        
        // 初始化并激活音频会话
        setupAudioSession()
    }
    
    private func setupAudioSession() {
        let session = AVAudioSession.sharedInstance()
        do {
            // 设置音频会话为播放模式
            try session.setCategory(.playback, mode: .default)
            // 激活音频会话
            try session.activate()
        } catch {
            print("音频会话初始化失败: \(error)")
        }
    }
    
    func playGetReady() {
        currentSound = 0 // 重置播放索引,确保每次调用从第一段开始
        guard currentSound < urls.count else { return }
        
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound])
            audioPlayer?.delegate = self
            audioPlayer?.play()
        } catch {
            print("播放音频失败: \(error)")
        }
    }
    
    func playNextSound() {
        currentSound += 1

        guard currentSound < urls.count else { return }
        
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound])
            audioPlayer?.delegate = self
            audioPlayer?.play()
        } catch {
            print("播放下一段音频失败: \(error)")
        }
    }
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        guard flag else { return }
        playNextSound()
    }
}

关键修正点说明

  • 继承NSObject:确保Swift类能正确响应Objective-C代理回调
  • 初始化音频会话:激活播放模式会话是触发audioPlayerDidFinishPlaying回调的核心前提
  • 重置播放索引:每次调用playGetReady()时重置索引,避免重复调用导致的播放位置错误
  • 私有化初始化:严格保证单例唯一实例,避免意外创建多个管理实例

内容的提问来源于stack exchange,提问作者Ростислав Григорьев

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 07:15:32