如何在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
相关产品推荐
相关产品推荐

