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

如何在Swift中为AVQueuePlayer设置NowPlaying属性

解决AVQueuePlayer切换曲目时更新锁屏元数据的问题

核心思路

通过监听播放器的播放项变化(或单首曲目播放结束事件),在切换时同步更新MPNowPlayingInfoCenter的元数据信息,实现锁屏界面的内容刷新。


方案1:通过KVO监听当前播放项变化(适合非static类)

如果你的播放器管理类是实例类,用KVO监听currentItem属性变化是最全面的方式,能覆盖自动切歌和手动切歌的场景。

1. 添加KVO监听

在初始化或playQueue方法中,给queuePlayer添加监听:

static func playQueue() {
    // 原有创建队列、启动播放的代码...
    
    // 先移除旧监听,避免重复添加
    queuePlayer?.removeObserver(self, forKeyPath: "currentItem")
    // 添加currentItem变化监听
    queuePlayer?.addObserver(self, forKeyPath: "currentItem", options: [.new], context: nil)
    
    // 初始化第一首歌的元数据
    if let firstItem = queuePlayer?.currentItem, 
       let firstIdx = lofiSongs.firstIndex(of: firstItem),
       let firstSong = songs[firstIdx] {
        updateNowPlayingMetadata(with: firstSong)
    }
}

2. 实现KVO回调方法

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard keyPath == "currentItem", 
          let player = object as? AVQueuePlayer,
          let currentItem = player.currentItem,
          let currentIdx = lofiSongs.firstIndex(of: currentItem),
          let currentSong = songs[currentIdx] else {
        return
    }
    // 更新元数据
    updateNowPlayingMetadata(with: currentSong)
}

3. 实现元数据更新方法

private func updateNowPlayingMetadata(with song: Song) {
    var nowPlayingInfo = [String: Any]()
    
    // 设置歌曲标题
    nowPlayingInfo[MPMediaItemPropertyTitle] = song.title
    // 设置歌手(如有)
    if let artist = song.artist {
        nowPlayingInfo[MPMediaItemPropertyArtist] = artist
    }
    
    // 异步下载并设置封面图片
    if let artworkURL = song.artworkURL {
        URLSession.shared.dataTask(with: artworkURL) { data, _, error in
            guard let data = data, let image = UIImage(data: data), error == nil else { return }
            
            let artwork = MPMediaItemArtwork(boundsSize: image.size) { size in
                return image
            }
            nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
            
            // 回到主线程更新信息
            DispatchQueue.main.async {
                MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
            }
        }.resume()
    } else {
        // 无封面时直接更新现有字段
        DispatchQueue.main.async {
            MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
        }
    }
}

4. 移除监听避免内存泄漏

deinit {
    queuePlayer?.removeObserver(self, forKeyPath: "currentItem")
}

方案2:通过通知监听曲目播放结束(适合static类)

如果你的播放器管理是static类,用AVPlayerItemDidPlayToEndTime通知更合适:

1. 添加通知监听

static func playQueue() {
    // 原有创建队列、启动播放的代码...
    
    // 先移除旧通知
    NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
    // 给每个播放项添加结束监听
    lofiSongs.forEach { item in
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(onTrackFinished(_:)),
            name: .AVPlayerItemDidPlayToEndTime,
            object: item
        )
    }
    
    // 初始化第一首歌的元数据
    if let firstItem = queuePlayer?.currentItem, 
       let firstIdx = lofiSongs.firstIndex(of: firstItem),
       let firstSong = songs[firstIdx] {
        updateNowPlayingMetadata(with: firstSong)
    }
}

2. 实现通知回调

@objc private static func onTrackFinished(_ notification: Notification) {
    guard let finishedItem = notification.object as? AVPlayerItem,
          let finishedIdx = lofiSongs.firstIndex(of: finishedItem) else {
        return
    }
    // 计算下一首索引(支持循环播放)
    let nextIdx = (finishedIdx + 1) % songs.count
    let nextSong = songs[nextIdx]
    updateNowPlayingMetadata(with: nextSong)
}

3. 元数据更新方法同方案1

直接复用方案1中的updateNowPlayingMetadata方法即可。


额外注意事项

  • 确保App开启后台播放权限:在Info.plist中添加UIBackgroundModes字段,包含audio值。
  • 如果需要支持手动切歌(上一曲/下一曲按钮),在对应的命令处理逻辑中,也要主动调用updateNowPlayingMetadata方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 11:55:20