关于AVQueuePlayer的项目更新:封装音频播放器的播放列表修改需求
Hey there! 我刚好在项目里做过类似的AVQueuePlayer封装,针对你提到的播放列表更新、添加/移除项目的需求,这里有几个实用的方案可以参考,每个方案都有对应的代码示例和适用场景:
基于AVQueuePlayer的音频播放列表操作方案
首先得明确:AVQueuePlayer的items属性是只读的,没法直接修改现有队列,所以我们得通过一些间接方式来实现列表的动态管理。
方案1:替换整个队列(最直接的原生实现)
这是苹果官方推荐的基础方式,核心思路是每次修改列表时,生成新的AVPlayerItem数组,然后用setItems(_:)方法替换整个队列。如果需要保留播放进度,记得提前保存当前播放时间。
class AudioPlayerManager { private var queuePlayer: AVQueuePlayer? private var savedPlaybackTime: Double = 0.0 // 更新整个播放列表 func updatePlaylist(with newItems: [AVPlayerItem]) { // 保存当前播放进度 if let currentItem = queuePlayer?.currentItem { savedPlaybackTime = currentItem.currentTime().seconds } // 替换队列 queuePlayer = AVQueuePlayer(items: newItems) // 恢复播放进度(如果有) if savedPlaybackTime > 0, !newItems.isEmpty { queuePlayer?.seek(to: CMTime(seconds: savedPlaybackTime, preferredTimescale: 1)) } } // 添加单个项目到列表末尾 func addItem(_ item: AVPlayerItem) { guard let player = queuePlayer else { queuePlayer = AVQueuePlayer(items: [item]) return } var currentItems = player.items currentItems.append(item) player.setItems(currentItems) } // 移除指定项目 func removeItem(_ item: AVPlayerItem) { guard let player = queuePlayer else { return } var currentItems = player.items guard let index = currentItems.firstIndex(of: item) else { return } currentItems.remove(at: index) // 如果移除的是当前正在播放的项目,替换后自动播放下一个 if item == player.currentItem { player.setItems(currentItems) player.play() } else { player.setItems(currentItems) } } }
优缺点:
- 优点:实现简单,完全贴合AVQueuePlayer的原生设计,不需要额外的状态管理。
- 缺点:每次修改都要重建整个队列,当列表很大时可能有轻微的性能开销;如果正在播放的项目被替换,需要手动处理进度恢复。
方案2:动态队列管理(适合频繁修改的场景)
这种方式会自己维护一个数据源列表,通过监听AVPlayerItemDidPlayToEndTime通知,动态向AVQueuePlayer中添加下一个项目,避免每次都替换整个队列。
class AudioPlayerManager { private var queuePlayer: AVQueuePlayer? private var playlist: [AVPlayerItem] = [] private var currentTrackIndex: Int = 0 private var playbackObserver: Any? init() { setupPlaybackObserver() } private func setupPlaybackObserver() { playbackObserver = NotificationCenter.default.addObserver( forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main ) { [weak self] notification in guard let self = self, let playedItem = notification.object as? AVPlayerItem else { return } // 当当前队列的最后一个项目播放完毕,自动添加下一个 if playedItem == self.queuePlayer?.items.last { self.currentTrackIndex += 1 if self.currentTrackIndex < self.playlist.count { self.queuePlayer?.insert(self.playlist[self.currentTrackIndex], after: nil) } } } } // 更新播放列表 func updatePlaylist(with newItems: [AVPlayerItem]) { playlist = newItems currentTrackIndex = 0 queuePlayer?.pause() // 先只添加第一个项目到队列 queuePlayer = AVQueuePlayer(items: newItems.prefix(1).map { $0 }) if !newItems.isEmpty { queuePlayer?.play() } } // 添加项目到列表 func addItem(_ item: AVPlayerItem) { playlist.append(item) // 如果当前队列的项目已经全部播放/添加,直接把新项加入队列 if let player = queuePlayer, currentTrackIndex >= player.items.count - 1 { player.insert(item, after: nil) } } // 移除指定项目 func removeItem(_ item: AVPlayerItem) { guard let index = playlist.firstIndex(of: item) else { return } playlist.remove(at: index) // 调整索引和队列 if index < currentTrackIndex { currentTrackIndex -= 1 } else if index == currentTrackIndex { // 移除当前播放项,切换到下一个 queuePlayer?.pause() currentTrackIndex += 1 if currentTrackIndex < playlist.count { queuePlayer = AVQueuePlayer(items: [playlist[currentTrackIndex]]) queuePlayer?.play() } else { queuePlayer = nil } } // 从队列中移除已存在的项 queuePlayer?.remove(item) } deinit { if let observer = playbackObserver { NotificationCenter.default.removeObserver(observer) } } }
优缺点:
- 优点:不需要频繁替换整个队列,内存和性能更优,适合频繁添加/移除项目的场景。
- 缺点:需要自己维护播放索引和队列的同步,逻辑相对复杂,要处理各种边界情况(比如移除当前播放项、移除队列中的项等)。
方案3:完全自定义播放逻辑(摆脱AVQueuePlayer限制)
如果AVQueuePlayer的限制太多,你可以直接使用AVPlayer,自己完全控制播放列表的切换逻辑,这种方式灵活度最高。
class AudioPlayerManager { private var player: AVPlayer? private var playlist: [AVPlayerItem] = [] private var currentTrackIndex: Int = -1 private var playbackObserver: Any? init() { setupPlaybackObserver() } private func setupPlaybackObserver() { playbackObserver = NotificationCenter.default.addObserver( forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main ) { [weak self] _ in self?.playNext() } } // 更新播放列表 func updatePlaylist(with newItems: [AVPlayerItem]) { playlist = newItems currentTrackIndex = -1 player?.pause() playNext() } // 添加项目到列表 func addItem(_ item: AVPlayerItem) { playlist.append(item) } // 移除指定项目 func removeItem(_ item: AVPlayerItem) { guard let index = playlist.firstIndex(of: item) else { return } playlist.remove(at: index) if index < currentTrackIndex { currentTrackIndex -= 1 } else if index == currentTrackIndex { player?.pause() playNext() } } // 播放下一首 func playNext() { currentTrackIndex += 1 if currentTrackIndex < playlist.count { player = AVPlayer(playerItem: playlist[currentTrackIndex]) player?.play() } else { player = nil currentTrackIndex = -1 } } // 播放上一首 func playPrevious() { guard currentTrackIndex > 0 else { return } currentTrackIndex -= 1 player = AVPlayer(playerItem: playlist[currentTrackIndex]) player?.play() } deinit { if let observer = playbackObserver { NotificationCenter.default.removeObserver(observer) } } }
优缺点:
- 优点:完全自定义,没有AVQueuePlayer的任何限制,支持循环播放、随机播放等复杂逻辑,灵活度拉满。
- 缺点:需要自己处理所有播放逻辑,代码量更大,适合对播放控制有高度定制需求的场景。
总结选择建议
- 如果你的需求比较简单,只是偶尔更新播放列表,方案1是最省心的选择;
- 如果需要频繁添加/移除项目,追求更好的性能,方案2会更合适;
- 如果需要完全控制播放流程(比如自定义循环、随机播放逻辑),方案3是最佳选择。
内容的提问来源于stack exchange,提问作者iOSGeek
相关产品推荐
相关产品推荐

