iOS通知控制中心多音频同时播放异常问题求助
你遇到的控制中心同时播放多个音频的问题,核心原因是MPRemoteCommandCenter的命令被重复绑定,且切换音频时未彻底清理旧的播放会话与NowPlaying信息。每次进入ViewController时都调用addTarget绑定命令处理闭包,会导致同一个命令触发多个处理逻辑;同时返回ViewController后播放新音频时,旧的播放状态和NowPlaying信息没有被重置,控制中心依然会关联旧的音频会话。
下面是具体的修复步骤:
1. 避免重复绑定Remote Command Target
每次ViewController出现时直接调用addTarget会导致同一个命令绑定多个处理闭包,切换页面后旧的闭包依然生效,触发旧的AVPlayer实例播放。我们需要在合适的时机移除旧的target,再添加新的绑定:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 先移除旧的target,避免重复绑定 MPRemoteCommandCenter.shared().playCommand.removeTarget(self) MPRemoteCommandCenter.shared().pauseCommand.removeTarget(self) // 重新设置命令绑定 setupRemoteCommands() } private func setupRemoteCommands() { // Play命令处理 MPRemoteCommandCenter.shared().playCommand.addTarget { [weak self] _ in guard let self = self else { return .commandFailed } // 移除多余的replaceCurrentItem操作,避免重置当前播放项 self.player.isPlaying ? self.player.pause() : self.player.play() self.updatePlayButtonState() self.updateInfo() return .success } // Pause命令处理 MPRemoteCommandCenter.shared().pauseCommand.addTarget { [weak self] _ in guard let self = self else { return .commandFailed } self.player.isPlaying ? self.player.pause() : self.player.play() self.updatePlayButtonState() self.updateInfo() return .success } } private func updatePlayButtonState() { let imageName = player.isPlaying ? "pause" : "play" btnAudioPlay.setImage(#imageLiteral(resourceName: imageName), for: .normal) } // 页面销毁时确保移除所有target deinit { MPRemoteCommandCenter.shared().playCommand.removeTarget(self) MPRemoteCommandCenter.shared().pauseCommand.removeTarget(self) }
这里用[weak self]避免循环引用,同时把按钮状态更新抽成单独方法,让代码更清晰。另外移除了原代码中多余的replaceCurrentItem(with: currentItem)——这个操作会重新加载当前播放项,容易导致状态混乱。
2. 切换音频时彻底重置播放状态
当返回ViewController准备播放新音频时,需要清空旧的播放会话和NowPlaying信息,确保控制中心只关联当前音频:
func playNewAudio(with audioURL: URL, title: String, subtitle: String) { // 暂停当前播放 if player.isPlaying { player.pause() } // 创建新的播放项并替换 let newPlayerItem = AVPlayerItem(url: audioURL) player.replaceCurrentItem(with: newPlayerItem) // 更新音频标题信息 self.titleForAudioPlayer = title self.subTilteForAudioPlayer = subtitle // 先清空旧的NowPlaying信息,再设置新内容 MPNowPlayingInfoCenter.default().nowPlayingInfo = nil updateInfo() // 激活音频会话(确保播放权限) do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) } catch { print("音频会话激活失败: \(error.localizedDescription)") } }
3. 优化updateInfo方法的安全性
原代码中强制解包player.currentItem?.asset.duration容易导致崩溃,添加可选绑定增加代码健壮性:
private func updateInfo() { guard let currentItem = player.currentItem else { // 没有播放项时清空NowPlaying信息 MPNowPlayingInfoCenter.default().nowPlayingInfo = nil return } let appIcon = UIApplication.shared.icon! let artwork = MPMediaItemArtwork(boundsSize: appIcon.size) { _ in appIcon } let duration = Float(CMTimeGetSeconds(currentItem.asset.duration)) let elapsedTime = Float(CMTimeGetSeconds(player.currentTime())) let nowPlayingInfo: [String: Any] = [ MPMediaItemPropertyAlbumTitle: self.subTilteForAudioPlayer, MPMediaItemPropertyTitle: self.titleForAudioPlayer, MPMediaItemPropertyArtwork: artwork, MPMediaItemPropertyPlaybackDuration: duration, MPNowPlayingInfoPropertyElapsedPlaybackTime: elapsedTime ] MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo }
4. 离开页面时清理播放状态
在ViewController消失时,暂停播放、清空NowPlaying信息并停用音频会话,彻底切断旧播放会话与控制中心的关联:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if player.isPlaying { player.pause() } // 清空控制中心信息 MPNowPlayingInfoCenter.default().nowPlayingInfo = nil // 停用音频会话,通知其他应用可以接管音频 do { try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) } catch { print("音频会话停用失败: \(error.localizedDescription)") } }
这些修改主要解决了重复绑定命令处理闭包、播放状态未彻底重置、NowPlaying信息残留三个核心问题,确保每次切换音频时只有当前的AVPlayer实例被控制中心管理,不会出现旧音频同时播放的情况。
内容的提问来源于stack exchange,提问作者AshWinee Dhakad

