如何使用AVQueuePlayer播放歌曲?多曲目播放问题求助
问题:AVQueueManager实现单首/专辑播放的疑难
找不到易懂的AVQueuePlayer文档或示例,正尝试将原单轨播放的AudioManager改造为支持单首/整张专辑多曲目播放的版本,需要AVQueuePlayer的代码示例或清晰实现思路。
原单轨播放代码(AVAudioPlayer)
final class AudioManager: ObservableObject { var player: AVAudioPlayer? @Published var isPlaying: Bool = false func startPlayer(track: String) { guard let url = Bundle.main.url(forResource: track, withExtension: "mp3") else {return} do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) player = try AVAudioPlayer(contentsOf: url) player?.play() isPlaying = true } catch { print("Failed to initialize player: ", error) } } func playPause() { guard let player = player else { print("Instance of audio player not found") return } if player.isPlaying { player.pause() isPlaying = false } else { player.play() isPlaying = true } } func stop() { guard let player = player else { return } if player.isPlaying { player.stop() isPlaying = false } } }
尝试编写的AVQueueManager代码
import Foundation import AVKit final class AudioQueueManager: ObservableObject { var player: AVQueuePlayer? @Published var isPlaying: Bool = false @Published var someType: SomeType = .album(Album.data) func startPlayer(someType: SomeType) { switch someType { case .song(let song): let url = Bundle.main.url(forResource: song.track, withExtension: "mp3") do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) player = AVQueuePlayer(url: url!) player?.play() isPlaying = true } catch { print("Failed to initialize player: ", error) } case .album(let album): var items: [AVPlayerItem] = [] album.songs?.forEach({ albumsong in items.append(AVPlayerItem(url: Bundle.main.url(forResource: albumsong.track, withExtension: "mp3")!)) }) do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) player = AVQueuePlayer(items: items) player?.play() isPlaying = true } catch { print("Failed to initialize player: ", error) } } } func playPause() { guard let player = player else { print("Instance of audio player not found") return } if player.timeControlStatus == .playing { player.pause() isPlaying = false } else { player.play() isPlaying = true } } func stop() { guard let player = player else { return } player.removeAllItems() isPlaying = false } }
视图中启动播放器的代码
enum SomeType { case song(Song) case album(Album) } @EnvironmentObject var audioQueueManger: AudioQueueManager switch type { case .song(let song): audioQueueManager.stop() audioQueueManager.isPlaying = true audioQueueManager.startPlayer(someType: .song(song)) musicPlayerVm.addCurrentSong() case .album(let album): audioQueueManager.stop() audioQueueManager.isPlaying = true audioQueueManager.startPlayer(someType: .album(album)) }
遇到的具体问题
- 停止/启动新歌曲/专辑功能无效,找不到AVQueuePlayer对应
player.stop()的方法 - 快进10秒无法像AVAudioPlayer那样用
currentTime += 10实现,尝试player?.currentTime().value += 10但value不可变,且currentTime需要CMTime类型 - 单首歌曲播放正常,但播放专辑时切换到另一专辑会报错:
An AVPlayerItem cannot be associated with more than one instance of AVPlayer
解决方案
1. 修复停止功能与AVPlayerItem重复关联问题
AVQueuePlayer没有直接的stop方法,需销毁旧播放器实例彻底清理状态,避免AVPlayerItem被多个播放器关联:
func stop() { player?.pause() player?.removeAllItems() player = nil // 关键:销毁旧实例,断绝item关联 isPlaying = false // 可选:停用音频会话释放资源 do { try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) } catch { print("Failed to deactivate audio session: \(error)") } } // 优化startPlayer:先停止旧播放,统一处理播放项 init() { // 初始化时一次性设置音频会话 do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) } catch { print("Failed to set audio session category: \(error)") } } func startPlayer(someType: SomeType) { stop() // 先清理旧播放 var playerItems: [AVPlayerItem] = [] switch someType { case .song(let song): guard let url = Bundle.main.url(forResource: song.track, withExtension: "mp3") else { print("Song URL not found: \(song.track)") return } playerItems.append(AVPlayerItem(url: url)) case .album(let album): guard let songs = album.songs else { print("Album has no songs") return } for song in songs { guard let url = Bundle.main.url(forResource: song.track, withExtension: "mp3") else { print("Song URL not found: \(song.track)") continue } playerItems.append(AVPlayerItem(url: url)) } } guard !playerItems.isEmpty else { print("No valid player items to play") return } do { try AVAudioSession.sharedInstance().setActive(true) player = AVQueuePlayer(items: playerItems) player?.play() isPlaying = true } catch { print("Failed to initialize player: \(error)") } }
2. 实现快进10秒功能
AVQueuePlayer的currentTime为只读属性,需用seek(to:)方法结合CMTime计算目标位置:
func forward10Seconds() { guard let player = player, let currentItem = player.currentItem else { return } let currentTime = currentItem.currentTime() // 计算快进10秒后的CMTime let targetTime = CMTimeAdd(currentTime, CMTimeMakeWithSeconds(10, preferredTimescale: 1)) // 不超过当前歌曲时长则跳转,否则自动切下一首 if targetTime <= currentItem.duration { player.seek(to: targetTime) } else { player.advanceToNextItem() } }
3. 优化播放状态同步
添加KVO监听播放器状态,避免手动设置isPlaying出现不一致:
final class AudioQueueManager: ObservableObject { // ... 原有属性 ... override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "timeControlStatus", let player = object as? AVQueuePlayer { DispatchQueue.main.async { self.isPlaying = player.timeControlStatus == .playing } } } func startPlayer(someType: SomeType) { // ... 原有逻辑 ... player?.addObserver(self, forKeyPath: "timeControlStatus", options: [.new], context: nil) } deinit { player?.removeObserver(self, forKeyPath: "timeControlStatus") } }
添加监听后,无需手动在playPause或startPlayer中设置isPlaying,由播放器状态自动同步。
内容的提问来源于stack exchange,提问作者AdamLeet
相关产品推荐
相关产品推荐

