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.

Другие вопросы по тегам