UITableView使用AVPlayer播放Firebase音频错误及滚动停止如何解决?
问题成因
- 全局唯一的
AVPlayer实例被无规律覆盖:你在cellForRowAt单元格渲染回调中直接调用异步的loadAudio方法,每次音频下载URL请求返回后都会直接替换全局的audioPlayer,由于异步请求返回顺序不固定,最终audioPlayer绑定的音频完全不可控,点击播放时就会出现随机播放的问题。 - 滚动时播放中断是单元格复用机制导致的:UITableView滚动过程中,新进入可视区域的单元格会触发
cellForRowAt回调,重复调用loadAudio,请求返回后会直接替换正在播放的AVPlayer实例,原有播放器被释放自然就停止播放。 - 播放逻辑未绑定目标音频:
playMusic方法传入的索引参数未被使用,没有根据点击的单元格索引加载对应音频,直接调用当前audioPlayer的播放方法,也没有切歌的逻辑处理,自然无法实现点击其他单元格自动切换歌曲的效果。
修复方案
1. 新增全局状态管理
新增两个变量用于缓存音频URL和记录当前播放状态,避免重复请求和播放错乱:
// 缓存已获取的音频URL,key用post的mp3唯一标识即可 var audioUrlCache: [String: URL] = [:] // 记录当前正在播放的音频唯一标识 var currentPlayingAudioKey: String?
2. 移除cellForRowAt中的预加载逻辑
绝对不要在单元格渲染方法中执行异步修改全局播放器的操作,把loadAudio的调用时机改到用户点击播放按钮时触发,避免无意义的请求和播放器被意外覆盖。
3. 重写播放逻辑,增加切歌处理
修改playMusic方法,实现点击判断、切歌、缓存复用的逻辑:
func playMusic(index: Int) { let targetPost = compPosts[index] let targetKey = targetPost.mp3 // 点击的是当前正在播放的音频,直接操作播放状态即可 if currentPlayingAudioKey == targetKey { if !isPlaying { audioPlayer.play() isPlaying = true } return } // 点击的是其他音频,先停止当前播放 if isPlaying { audioPlayer.pause() isPlaying = false } // 更新当前播放的音频标识 currentPlayingAudioKey = targetKey // 优先读缓存,避免重复请求 if let cachedUrl = audioUrlCache[targetKey] { audioPlayer = AVPlayer(playerItem: AVPlayerItem(url: cachedUrl)) audioPlayer.play() isPlaying = true return } // 无缓存则发起请求 loadAudio(post: targetPost) { [weak self] url in guard let self = self, // 异步请求返回时判断是否还是当前要播放的音频,避免请求回来时用户已经切歌 self.currentPlayingAudioKey == targetKey else { return } // 写入缓存 self.audioUrlCache[targetKey] = url self.audioPlayer = AVPlayer(playerItem: AVPlayerItem(url: url)) self.audioPlayer.play() self.isPlaying = true } }
4. 可选优化
可以在cellForRowAt中根据currentPlayingAudioKey同步更新单元格的播放/暂停按钮状态,避免滚动后单元格按钮显示和实际播放状态不一致;也可以监听AVPlayer的播放完成通知,自动重置播放状态。
内容的提问来源于stack exchange,提问作者EAO123
相关产品推荐
相关产品推荐

