MPNowPlayingInfoCenter nowPlayingInfo не обновляется в конце дорожки
У меня есть метод, который изменяет звуковую дорожку, воспроизводимую моим приложением AVPlayer
а также устанавливает MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
для нового трека:
func setTrackNumber(trackNum: Int) {
self.trackNum = trackNum
player.replaceCurrentItemWithPlayerItem(tracks[trackNum])
var nowPlayingInfo: [String: AnyObject] = [ : ]
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = tracks[trackNum].albumTitle
nowPlayingInfo[MPMediaItemPropertyTitle] = "Track \(trackNum)"
...
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = nowPlayingInfo
print("Now playing local: \(nowPlayingInfo)")
print("Now playing lock screen: \(MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo)")
}
Я называю этот метод, когда пользователь явно выбирает альбом или трек и когда трек заканчивается и автоматически начинается следующий. Экран блокировки правильно показывает метаданные дорожки, когда пользователь устанавливает альбом или дорожку, но НЕ, когда дорожка заканчивается и автоматически устанавливается следующая.
Я добавил операторы печати, чтобы убедиться, что я правильно заполнил nowPlayingInfo
толковый словарь. Как и ожидалось, два оператора печати печатают одинаковое содержание словаря, когда этот метод вызывается для инициируемого пользователем изменения альбома или дорожки. Однако в случае, когда метод вызывается после автоматической смены дорожки, локальный nowPlayingInfo
переменная показывает новый trackNum
в то время как MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
показывает предыдущий trackNum
:
Now playing local: ["title": Track 9, "albumTitle": Test Album, ...]
Now playing set: Optional(["title": Track 8, "albumTitle": Test Album, ...]
Я обнаружил, что когда я устанавливаю точку останова на линии, которая устанавливает MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
в nowPlayingInfo
, затем номер дорожки корректно обновляется на экране блокировки. Добавление sleep(1)
сразу после этой строки также обеспечивается правильное обновление дорожки на экране блокировки.
Я подтвердил, что nowPlayingInfo
всегда устанавливается из основной очереди. Я пытался явно запустить этот код в основной очереди или в другой очереди без изменений в поведении.
Что мешает мне измениться на MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
? Как я могу убедиться, что настройки MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
всегда обновляет информацию экрана блокировки?
РЕДАКТИРОВАТЬ
Пройдя код в N-й раз, подумав "параллелизм", я нашел виновника. Я не знаю, почему я не стал подозревать об этом раньше:
func playerTimeJumped() {
let currentTime = currentItem().currentTime()
dispatch_async(dispatch_get_main_queue()) {
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(currentTime)
}
}
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "playerTimeJumped",
name: AVPlayerItemTimeJumpedNotification,
object: nil)
Этот код обновляет время экрана блокировки, истекшее, когда пользователь чистит или пропускает вперед / назад. Если я закомментирую это, то nowPlayingInfo
обновление от setTrackNumber
работает как положено при любых условиях.
Пересмотренные вопросы: как эти два фрагмента кода взаимодействуют, когда они оба выполняются в главной очереди? Есть ли способ, которым я могу сделать nowPlayingInfo
обновлять на AVPlayerItemTimeJumpedNotification
учитывая, что при вызове произойдет скачок setTrackNumber
?
5 ответов
Проблема в том, что nowPlayingInfo
обновляется в двух местах одновременно, когда дорожка автоматически изменяется: в setTrackNumber
метод, который вызывается AVPlayerItemDidPlayToEndTimeNotification
и в playerTimeJumped
метод, который вызывается AVPlayerItemTimeJumpedNotification
,
Это вызывает состояние гонки. Более подробная информация предоставлена сотрудником Apple здесь.
Проблема может быть решена путем сохранения местного nowPlayingInfo
словарь, который обновляется по мере необходимости и всегда настройки MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
от этого вместо установки отдельных значений.
Не могли бы вы попробовать этот код? Это сработало для моего примера...
override func viewDidLoad() {
super.viewDidLoad()
if NSClassFromString("MPNowPlayingInfoCenter") != nil {
let albumArt = MPMediaItemArtwork(image: image) // any image
var songInfo: NSMutableDictionary = [
MPMediaItemPropertyTitle: "Whatever",
MPMediaItemPropertyArtist: "Whatever",
MPMediaItemPropertyArtwork: albumArt
]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = songInfo as [NSObject : AnyObject]
}
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
try! AVAudioSession.sharedInstance().setActive(true)
}
Объяснение: Вы должны проверить, активен ли MPNowPlayingInfo в какой-то момент, когда он уходит в фоновый режим. Если он находится в фоновом режиме, то вы должны сделать его активным, что делает эту строку кода:
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
try! AVAudioSession.sharedInstance().setActive(true)
Напишите мне, если это сработало...
редактировать
Если это не работает, вы также можете попробовать этот код, но приведенный выше код является более современным решением.
if (AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)) {
println("Receiving remote control events")
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
} else {
println("Audio Session error.")
}
Здесь вы также пытаетесь сделать его активным, как и выше. Это старая версия, которая может не работать...
Прежде всего, обязательно включите фоновые режимы для вашего приложения в файле.plist. Это позволит вашему приложению использовать фоновые задачи и запускать код обновления, пока он заблокирован.
Во-вторых, я бы позволил функции обновления вызываться функцией делегата AVAudioPlayer. audioPlayerDidFinishPlaying:successfully:
если вы хотите обновить его в нужное время. Также вы можете попробовать зарегистрироваться для уведомления о том, что оно закончено в качестве альтернативы.
Для обновления фоновой информации мой коллега предполагает, что некоторые реализации необходимы. Может быть, вы можете проверить и проверить некоторые из этих требований в вашем контроллере представления:
//1: Set true for canBecomeFirstResponder func
override func canBecomeFirstResponder() -> Bool {
return true
}
//2: Set view controller becomeFirstResponder & allow to receive remote control events
override func viewDidLoad() {
super.viewDidLoad()
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
....
}
//3: Implement actions after did receive events from remote control
override func remoteControlReceivedWithEvent(event: UIEvent?) {
guard let event = event else {
return
}
switch event.subtype {
case .RemoteControlPlay:
....
break
case .RemoteControlPause:
....
break
case .RemoteControlStop:
....
break
default:
print("default action")
}
}
Вот мой случай:
В AVAudioSession
имеет значение.
не этот:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playback, options: AVAudioSession.CategoryOptions.mixWithOthers)
audioSession.requestRecordPermission({ (isGranted: Bool) in })
try AVAudioSession.sharedInstance().setActive(true, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
} catch {
}
Не работает
но
do {
//keep alive audio at background
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
} catch _ { }
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ { }
Оно работает
Я не могу прокомментировать приведенный выше ответ, но при использовании MPRemoteCommandCenter не нужно звонить -[UIApplication beginReceivingRemoteControlEvents]
ни -[UIResponder becomeFirstResponder]
обрабатывать удаленные события. Приведенный выше ответ относится к более старой реализации, которая больше не рекомендуется.
Рекомендуется установить как можно больше ключей в словаре nowPlayingInfo. MPMediaItemPropertyPlaybackDuration
, MPNowPlayingInfoPropertyPlaybackRate
, а также MPNowPlayingInfoPropertyElapsedPlaybackTime
может влиять на обновления MPNowPlayingInfoCenter.