5

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

如果我执行以下操作:

  • 开始播放
  • 暂停播放
  • 锁定装置
  • 从锁屏开始播放 (MPRemoteCommandCenter)

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

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

更多详情如下:

  • 似乎在尝试暂停音频时,AVAudioPlayer 为 audioPlayer.isPlaying 返回“false”。
  • 这发生在我的 iPhoneXR、iPhoneSE 和 iPhone8 上的 iOS13.1 上。我没有其他设备可以测试
  • 日志表明 AVAudioPlayer.isPlaying 在开始播放时最初返回 true,但随后返回 false。播放器的 currentTime 也似乎停留在播放开始的时间附近。
  • 我的整个视图控制器都在下面(~100 行)。这是重现问题的最低要求。
  • 演示错误的这个示例项目也可以在 Github 上找到

导入 UIKit 导入 MediaPlayer

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 的必要步骤在这个公共差异中
  • 我使用了我的一张开发人员问题票并将这个问题提交给 Apple。当我收到他们的回复时,我会发布答案。

更新 2

  • Apple 开发支持确认,截至 2019 年 12 月 5 日,此问题没有已知的解决方法。我已向 feedbackassistant.apple.com 提交了一个问题,并会在发生变化时更新此答案。
4

1 回答 1

1

This is indeed a bug in AVAudioPlayer.

Another workaround if you dont want to switch to AVPlayer is to simply check if playing before pausing and if not, call play just before pause. It's not pretty but it works:

if (!self.player.isPlaying) [self.player play];
[self.player pause];
于 2021-02-24T09:52:48.740 回答