Я пытаюсь воспроизвести аудиофайл и управлять его воспроизведением через Центр удаленного управления, доступный на экране блокировки.
Если я сделаю следующее:
- Начать воспроизведение
- Приостановить воспроизведение
- Блокировка устройства
- Начать воспроизведение с заблокированного экрана (MPRemoteCommandCenter)
В этом случае невозможно приостановить воспроизведение с заблокированного экрана. Кнопка мерцает и ничего не происходит.
Как я могу это исправить?
Более подробная информация ниже:
- Похоже, что при попытке приостановить звук AVAudioPlayer возвращает false для audioPlayer.isPlaying.
- Это происходит на iOS13.1 на моих iPhoneXR, iPhoneSE и iPhone8. У меня нет других устройств, с которыми можно было бы тестировать
- Журналы показывают, что 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
на экране блокировки. Последующие нажатия сбрасывают звук в исходное положение паузы, а затем работают в обычном режиме.
Обновлять
- Замена AVAudioPlayer на AVPlayer, похоже, решает проблему полностью! Я вполне уверен, что сейчас это ошибка в AVAudioPlayer.
- Чтобы переключиться на AVPlayer, необходимо выполнить этот общедоступный файл diff.
- Я использовал один из своих билетов с вопросами для разработчиков и отправил этот вопрос в Apple. Я отправлю ответ, когда получу от них ответ.
Обновление 2
- Служба поддержки Apple Dev подтвердила, что по состоянию на 5 декабря 2019 года обходного пути решения этой проблемы не существует. Я отправил вопрос на feedbackassistant.apple.com и обновлю этот ответ, когда что-то изменится.