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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:02:27