ios - AVAudioPlayer 'forgets' 由 MPRemoteCommand 触发时播放

标签 ios swift avaudioplayer mpremotecommandcenter

我正在尝试播放音频文件并通过锁定屏幕上可用的远程命令中心控制它的播放。

如果我执行以下操作:

  • 开始播放
  • 暂停播放
  • 锁具
  • 从锁屏开始播放 (MPRemoteCommandCenter)

  • 然后就不可能从锁屏暂停播放。按钮闪烁,没有任何 react 。

    我怎样才能解决这个问题?

    更多详情如下:
  • 似乎在尝试暂停音频时,AVAudioPlayer 为 audioPlayer.isPlaying 返回“false”。
  • 这发生在我的 iPhoneXR、iPhoneSE 和 iPhone8 上的 iOS13.1 上。我没有其他设备可以测试
  • 日志表明 AVAudioPlayer.isPlaying 在开始播放时最初返回 true,但随后返回 false。播放器的 currentTime 也出现在播放开始的时间附近。
  • 我的整个 View Controller 低于(~100 行)。这是重现问题所需的最低限度。
  • Github here 上也提供了演示该错误的示例项目。 .

  • 导入 UIKit
    导入媒体播放器
    class ViewController: UIViewController {
        @IBOutlet weak var playPauseButton: UIButton!
        @IBAction func playPauseButtonTap(_ sender: Any) {
            if self.audioPlayer.isPlaying {
                pause()
            } else {
                play()
            }
        }
    
        private var audioPlayer: AVAudioPlayer!
        private var hasPlayed = false
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let fileUrl = Bundle.main.url(forResource: "temp/intro", withExtension: ".mp3")
            try! self.audioPlayer = AVAudioPlayer(contentsOf: fileUrl!)
            let audioSession = AVAudioSession.sharedInstance()
            do { // play on speakers if headphones not plugged in
                try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
            } catch let error as NSError {
                print("Override headphones failed, probably because none are available: \(error.localizedDescription)")
            }
            do {
                try audioSession.setCategory(.playback, mode: .spokenAudio)
                try audioSession.setActive(true)
            } catch let error as NSError {
                print("Warning: Setting audio category to .playback|.spokenAudio failed: \(error.localizedDescription)")
            }
            playPauseButton.setTitle("Play", for: .normal)
        }
    
        func play() {
            playPauseButton.setTitle("Pause", for: .normal)
            self.audioPlayer.play()
            if(!hasPlayed){
                self.setupRemoteTransportControls()
                self.hasPlayed = true
            }
        }
    
        func pause() {
            playPauseButton.setTitle("Play", for: .normal)
            self.audioPlayer.pause()
        }
    
        // MARK: Remote Transport Protocol
        @objc private func handlePlay(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
            print(".......................")
            print(self.audioPlayer.currentTime)
            let address = Unmanaged.passUnretained(self.audioPlayer).toOpaque()
            print("\(address) not playing: \(!self.audioPlayer.isPlaying)")
            guard !self.audioPlayer.isPlaying else { return .commandFailed }
            print("attempting to play")
            let success = self.audioPlayer.play()
            print("play() invoked with success \(success)")
            print("now playing \(self.audioPlayer.isPlaying)")
            return success ? .success : .commandFailed
        }
    
        @objc private func handlePause(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
            print(".......................")
            print(self.audioPlayer.currentTime)
            let address = Unmanaged.passUnretained(self.audioPlayer).toOpaque()
            print("\(address) playing: \(self.audioPlayer.isPlaying)")
            guard self.audioPlayer.isPlaying else { return .commandFailed }
            print("attempting to pause")
            self.pause()
            print("pause() invoked")
            return .success
        }
    
        private func setupRemoteTransportControls() {
            let commandCenter = MPRemoteCommandCenter.shared()
            commandCenter.playCommand.addTarget(self, action: #selector(self.handlePlay))
            commandCenter.pauseCommand.addTarget(self, action: #selector(self.handlePause))
    
            var nowPlayingInfo = [String : Any]()
            nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = "Major title"
            nowPlayingInfo[MPMediaItemPropertyTitle] = "Minor Title"
            nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = self.audioPlayer.currentTime
            nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = self.audioPlayer.duration
            nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = self.audioPlayer.rate
            MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
        }
    }
    

    这会记录以下内容(添加了我的//注释):
    .......................
    1.438140589569161 // audio was paused here
    0x0000000283361cc0 not playing: true // player correctly says its not playing
    attempting to play // so it'll start to play
    play() invoked with success true // play() successfully invoked
    now playing true // and the player correctly reports it's playing
    .......................
    1.4954875283446711 // The player thinks it's being playing for about half a second
    0x0000000283361cc0 playing: false // and has now paused??? WTF?
    .......................
    1.4954875283446711 // but there's definitely sound coming from the speakers. It has **NOT** paused. 
    0x0000000283361cc0 playing: false // yet it thinks it's paused?
    // note that the memory addresses are the same. This seems to be the same player. ='(
    

    我不知所措。帮助我 StackOverflow——你是我唯一的希望。

    编辑:我也试过

    总是返回 .success
    @objc private func handlePlay(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
        guard !self.audioPlayer.isPlaying else { return .success }
        self.audioPlayer.play()
        return .success
    }
    
    @objc private func handlePause(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
        print(self.audioPlayer.isPlaying)
        guard self.audioPlayer.isPlaying else { return .success }
        self.pause()
        return .success
    }
    

    忽略 audioPlayer 状态并按照远程命令中心的说明进行操作
    @objc private func handlePlay(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
        self.audioPlayer.play()
        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = self.audioPlayer.currentTime
        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = self.audioPlayer.rate
        return .success
    }
    
    @objc private func handlePause(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
        self.audioPlayer.pause()
        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = self.audioPlayer.currentTime
        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = self.audioPlayer.rate
        return .success
    }
    

    这两种情况都会导致错误第一次持续存在 pause在锁定屏幕上点击。后续点击将音频重置到原始暂停位置,然后正常工作。

    更新
  • 用 AVPlayer 替换 AVAudioPlayer 似乎可以让问题完全消失!我现在有理由确定这是 AVAudioPlayer 中的一个错误。
  • 切换到 AVPlayer 的必要步骤是 in this public diff
  • 我已经使用了我的一张开发者问题票并将这个问题提交给了 Apple。当我收到他们的回复时,我会发布一个答案。

  • 更新 2
  • Apple Dev Support 确认,截至 2019 年 12 月 5 日,此问题尚无已知的解决方法。我已向feedbackassistant.apple.com 提交了一个问题,并将在发生变化时更新此答案。
  • 最佳答案

    这确实是 AVAudioPlayer 中的一个错误。
    如果您不想切换到 AVPlayer,另一种解决方法是简单地检查是否在暂停前播放,如果没有,请在暂停前调用 play。它不漂亮,但它有效:

    if (!self.player.isPlaying) [self.player play];
    [self.player pause];
    

    关于ios - AVAudioPlayer 'forgets' 由 MPRemoteCommand 触发时播放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58965461/

    相关文章:

    ios - dSYM 未通过 CLI 命令上传

    xcode - 如何让物体无限旋转

    objective-c - 音乐音阶:循环播放不同音符的功能

    swift - ARKit 1.5 – 垂直物体检测

    ios - 为什么我的多 channel 映射不能正常工作?

    objective-c - 为什么这个声音循环不会停止?

    ios - 代码行后的数字百分比是多少表示使用 Instrument?

    ios - 当约束改变时如何改变 UITableViewCell 的高度?

    jquery - IOS HTML5 应用程序 - Jquery slider 也允许单击操作

    swift - 连接到 Pusher ChatKit [Swift]