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

Swift AVFoundation问题:切换AudioSession引发动画卡顿

解决训练App提示音播放时动画与声音延迟+背景音恢复的矛盾问题

问题场景与矛盾点

  • 开发的训练类App支持用户设置训练间隔时长(如30秒)
  • 核心需求:训练启动后运行动画,计时结束时需同步播放提示音、重置动画,同时临时压低其他App的背景音乐
  • 遇到的两难问题:
    • 原代码播放提示音时,动画会卡在轮结束状态,近1秒后才出声音,之后才重启动画,延迟明显
    • 若删掉try audioSession.setCategory(.ambient, options: .mixWithOthers)这行,延迟消失,但其他App的背景音乐会一直保持压低,无法自动恢复

原问题代码

计时器与动画视图代码

ZStack{
            Circle()
                .trim(from: 0, to: 1)
                .stroke(colorScheme == .dark ? Color.white.opacity(0.08) : Color.black.opacity(0.09), style: StrokeStyle(lineWidth: 35, lineCap: .round))
                .frame(width: x - x/7, height: y - y/7)
            
            Circle()
                .trim(from: 0, to: intervallTimer.position)
                .stroke(intervallTimer.ringColor.convertToColor(), style: StrokeStyle(lineWidth: 35, lineCap: .round))
                .frame(width: x - x/7, height: y - y/7)
                .rotationEffect(.init(degrees: -90))
            
            VStack(spacing: 0){
                
                intervallTimer.time.inTrainingsFormat()
                
                Text(intervallTimer.currentEx)
                    .font(.system(size: 40))
                    .padding(.top, 20)
                    .multilineTextAlignment(.center)
                    .lineLimit(3)
                
            }
        }
.onAppear{
    self.timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
}
.onReceive(timer){ _ in
     Sound.playSounds(sound.wav)
     intervallTimer.position = 0.01
 }

初始提示音播放代码(存在延迟问题)

class Sound{

static var audioPlayer:AVAudioPlayer?

static func playSounds(soundfile: String) {
    
    if let path = Bundle.main.path(forResource: soundfile, ofType: nil){
        let audioSession = AVAudioSession.sharedInstance()
        
        do{
            try audioSession.setCategory(.ambient, options: .duckOthers)
        
            audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
            audioPlayer?.prepareToPlay()
            
            DispatchQueue.global(qos: .background).async {
                audioPlayer?.play()
            }
            
            try audioSession.setCategory(.ambient, options: .mixWithOthers) //移除该行则无延迟,但背景音保持压低
            
        }catch {
            print("Error")
        }
    }
}
}

解决方案与优化后代码

核心优化思路

  • 提前初始化并预加载音频播放器,避免播放时才创建资源导致的延迟
  • 通过AVAudioPlayerDelegate监听音频播放完成事件,在播放结束后再恢复音频会话,而非立即修改类别
  • 切换到.playback音频类别配合.duckOthers选项,更适配主动播放提示音的场景
  • 用setActive(false, options: .notifyOthersOnDeactivation)在音频结束后通知其他App恢复音量

优化后的Sound类代码

class Sound: NSObject, AVAudioPlayerDelegate{

var audioPlayer: AVAudioPlayer?
let audioSession = AVAudioSession.sharedInstance()

override init() {
    super.init()
}

// 提前准备音频播放器,建议在App启动或进入训练页面时调用
func prepareAudioPlayer(soundfile: String){
    if let path = Bundle.main.path(forResource: soundfile, ofType: nil){
        do{
            try audioSession.setCategory(.playback, options: .duckOthers)
            
            audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
            audioPlayer?.delegate = self
            audioPlayer?.prepareToPlay()
            try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
        }catch{
            print("Error - Coudnt create audioplayer")
        }
    }else{
        print("Error - Path not found")
    }
}

// 播放提示音,直接调用即可
func play() {
    if let player = audioPlayer{
        player.play()
    }else{
        print("Error - AudioPlayer is nil")
    }
}

// 音频播放完成后恢复音频会话,让其他App的背景音恢复正常
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
    print("Did finish Playing")
    DispatchQueue.global(qos: .userInitiated).async {
        try? self.audioSession.setActive(false, options: .notifyOthersOnDeactivation)
    }
}
}

计时器部分的调用调整

需要提前初始化Sound实例并完成音频准备,比如在视图的onAppear中处理:

@State private var soundPlayer = Sound()

// ... 在视图的onAppear中添加
.onAppear{
    self.timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    soundPlayer.prepareAudioPlayer(soundfile: "sound.wav") // 提前加载音频资源
}
.onReceive(timer){ _ in
     soundPlayer.play() // 直接触发播放
     intervallTimer.position = 0.01
 }

内容的提问来源于stack exchange,提问作者Lukas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 11:35:32