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

关于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:36:29