如何用AVMIDIPlayer循环播放MIDI文件?解决延迟与音频截断问题
解决AVMIDIPlayer循环播放延迟与音频截断问题
问题说明
使用AVMIDIPlayer的完成回调实现MIDI文件循环播放时,循环间隙出现微小延迟与音频截断现象,以下是针对性解决方案:
核心解决方案
1. 优化音频会话与播放准备
- 配置音频会话为播放优先模式,避免系统音频策略干扰播放连续性
- 确保
AVMIDIPlayer完全完成预加载后再启动播放,避免因资源未就绪导致的截断
2. 替换回调式循环为精确进度调度
依赖play()的完成回调会因主线程调度延迟导致间隙,改为监听播放进度,在曲目接近结束时提前重置播放位置,实现无缝衔接
3. 双实例交替预加载(可选,极端场景)
对于对无缝要求极高的场景,使用两个AVMIDIPlayer实例交替预加载与播放,彻底消除切换间隙
修改后的完整代码
import SwiftUI import AVFoundation struct ContentView: View { @StateObject private var midiPlayer = MIDIPlayer() var body: some View { VStack { Button(action: { midiPlayer.startLooping() }) { Text("播放MIDI") } Button(action: { midiPlayer.stopLooping() }) { Text("停止播放") } } .onAppear { midiPlayer.configureAudioSession() } } } class MIDIPlayer: ObservableObject { private var midiPlayer: AVMIDIPlayer? private var progressTimer: Timer? private var isLooping: Bool = false // 提前触发重置的阈值(单位:秒,根据曲目长度调整) private let loopThreshold: TimeInterval = 0.1 init() { loadMIDI() } func configureAudioSession() { do { let session = AVAudioSession.sharedInstance() try session.setCategory(.playback, mode: .default) try session.setActive(true) } catch { print("音频会话配置失败: \(error)") } } func loadMIDI() { guard let midiFileURL = Bundle.main.url(forResource: "cdec", withExtension: "mid") else { print("未找到MIDI文件") return } do { let soundBankURL = Bundle.main.url(forResource: "Nokia", withExtension: "sf2") midiPlayer = try AVMIDIPlayer(contentsOf: midiFileURL, soundBankURL: soundBankURL) // 等待预加载完成 midiPlayer?.prepareToPlay() } catch { print("加载MIDI文件失败: \(error)") } } func startLooping() { guard let player = midiPlayer else { return } isLooping = true player.currentPosition = 0 player.play() // 启动进度监听定时器 startProgressMonitoring() } func stopLooping() { isLooping = false progressTimer?.invalidate() progressTimer = nil midiPlayer?.stop() midiPlayer?.currentPosition = 0 } private func startProgressMonitoring() { progressTimer?.invalidate() // 每0.05秒检查一次播放进度 progressTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in guard let self = self, let player = self.midiPlayer, self.isLooping else { self?.progressTimer?.invalidate() return } let remainingTime = player.duration - player.currentPosition // 当剩余时间小于阈值时,重置播放位置 if remainingTime <= self.loopThreshold { player.currentPosition = 0 } } } }
代码说明
- 音频会话配置:设置为
playback类别,确保后台播放优先级,避免系统自动暂停或降低音量 - 进度监听:通过定时器高频检查播放进度,在曲目接近结束时提前重置位置,避免回调延迟
- 阈值调整:
loopThreshold可根据MIDI曲目长度微调,推荐设置为0.1-0.2秒,平衡无缝性与准确性
内容的提问来源于stack exchange,提问作者Jesse
相关产品推荐
相关产品推荐

