MPNowPlayingInfoCenter теперьPlayingInfo не обновляется в конце дорожки

У меня есть метод, который изменяет звуковую дорожку, воспроизводимую моим приложением 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?


person Hélène Martin    schedule 19.01.2016    source источник
comment
Кто-нибудь еще испытал что-нибудь подобное?   -  person Hélène Martin    schedule 03.02.2016
comment
Я размышляю здесь, но MPNowPlayingInfoCenter должен предположительно отправлять изменения nowPlayingInfo в любой системный процесс, управляющий экраном блокировки. Так что я бы совсем не удивился вашему отладочному выводу... метод setNowPlayingInfo эффективно завершается асинхронно.   -  person Reuben Scratton    schedule 04.02.2016
comment
Когда трек меняется автоматически, setTrackNumber определенно вызывается каждый раз? Даже если приложение работает в фоновом режиме?   -  person Reuben Scratton    schedule 04.02.2016
comment
@ReubenScratton - согласен, что настройка nowPlayingInfo, скорее всего, выполняется асинхронно, поэтому отпечатки не так уж полезны. Но я ожидаю, что набор будет завершен в какой-то момент! Когда телефон показывает экран блокировки, я знаю, что всегда вызывается setTrackNumber, потому что мои отладочные отпечатки отображаются на консоли.   -  person Hélène Martin    schedule 04.02.2016
comment
@HélèneMartin Не уверен, что это связано. Просто интересно, открываете ли вы фоновые режимы в возможностях?   -  person Allen    schedule 04.02.2016
comment
@Allen - да, у меня включен фоновый звук, и он отлично работает!   -  person Hélène Martin    schedule 04.02.2016
comment
Просто чтобы уточнить немного больше понимания; что происходит, когда на заблокированном экране пользователь нажимает кнопку паузы? Также при просмотре экрана блокировки в момент воспроизведения новой дорожки (автоматически после окончания воспроизведения дорожки) что отображается на экране блокировки? следующий трек продолжает играть?   -  person MDB983    schedule 07.02.2016
comment
Все кнопки на экране блокировки работают как положено. Например, кнопка паузы правильно приостанавливает воспроизводимую в данный момент дорожку. Когда новая дорожка воспроизводится автоматически, звук меняется правильно, но на экране блокировки отображается информация о предыдущей дорожке.   -  person Hélène Martin    schedule 08.02.2016
comment
@HélèneMartin, посмотри на мой ответ...   -  person Tom el Safadi    schedule 08.02.2016


Ответы (6)


Проблема в том, что nowPlayingInfo обновляется в двух местах одновременно при автоматическом изменении трека: в методе setTrackNumber, который запускается AVPlayerItemDidPlayToEndTimeNotification, и в методе playerTimeJumped, который запускается AVPlayerItemTimeJumpedNotification.

Это вызывает состояние гонки. Более подробная информация предоставлена ​​сотрудником Apple здесь.

Проблему можно решить, сохраняя локальный словарь nowPlayingInfo, который обновляется по мере необходимости, и всегда устанавливая MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo из него, а не устанавливая отдельные значения.

person Hélène Martin    schedule 12.02.2016

Мой коллега предполагает, что для обновления справочной информации необходимы некоторые реализации. Возможно, вы можете проверить и проверить некоторые из этих требований в вашем контроллере представления:

    //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")
        }
    }
person Allen    schedule 04.02.2016
comment
Я делаю №2. # 3 не должен быть напрямую связан, поскольку он связан с событиями удаленного управления, но да, я правильно обрабатываю события удаленного управления. #1 и self.becomeFirstResponder() Я не совсем понимаю. Я думаю, это для того, чтобы представление могло получать события удаленного управления? Метод setTrackNumber находится в классе, обертывающем AVPlayer, и я использую его синглтон во всех своих представлениях. - person Hélène Martin; 04.02.2016
comment
@HélèneMartin Да, я понимаю, что ты имеешь в виду. Вот что я подумал сначала; однако после того, как я проверю Руководство по обработке событий для iOS от Apple (которое также содержит тему для цепочек ответчиков). Я обнаружил, что тема Предоставление информации о том, что сейчас играет, находится в разделе События удаленного управления developer.apple.com/library/ios/documentation/EventHandling/ Поэтому я думаю, что возможно обновление фоновой информации служат для дистанционного управления в своей концепции дизайна. - person Allen; 04.02.2016
comment
@HélèneMartin Также, если вы тестируете его в симуляторе iOS, всегда включайте ключ MPNowPlayingInfoPropertyPlaybackRate в свой словарь nowPlayingInfo в соответствии с документом. - person Allen; 04.02.2016

Не могли бы вы попробовать этот код? Это сработало для моего примера...

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.")
}

Здесь вы также пытаетесь сделать его активным, как и выше. Это старая версия, которая может не работать...

person Tom el Safadi    schedule 08.02.2016
comment
Я устанавливаю категорию сеанса и делаю ее активной. Обратите внимание, что смена дорожки, автоматически запускаемая в конце предыдущей дорожки, никогда не обновляет nowPlayingInfo правильно, даже если приложение находится на переднем плане. - person Hélène Martin; 09.02.2016

Во-первых, обязательно включите фоновые режимы для вашего приложения в файле .plist. Это позволит вашему приложению использовать фоновые задачи и запускать код обновления, пока оно заблокировано.

Во-вторых, я бы позволил функции обновления вызываться функцией делегата AVAudioPlayer audioPlayerDidFinishPlaying:successfully:, если вы хотите обновить ее в нужное время. Также вы можете попробовать зарегистрироваться для получения уведомления о завершении в качестве альтернативы.

person rikola    schedule 10.02.2016
comment
Фоновые режимы включены и работают корректно. Я использую AVPlayer, а вызов setTrackNumber запускается AVPlayerItemDidPlayToEndTimeNotification. Как вы можете видеть в моей версии, проблема в том, что два разных уведомления могут иногда запускать обновление nowPlayingInfo одновременно. - person Hélène Martin; 11.02.2016

Я не могу комментировать приведенный выше ответ, но при использовании MPRemoteCommandCenter нет необходимости вызывать -[UIApplication beginReceivingRemoteControlEvents] или -[UIResponder becomeFirstResponder] для обработки удаленных событий. Приведенный выше ответ относится к более старой реализации, которая больше не рекомендуется.

Рекомендуется установить как можно больше ключей в словаре nowPlayingInfo. MPMediaItemPropertyPlaybackDuration, MPNowPlayingInfoPropertyPlaybackRate и MPNowPlayingInfoPropertyElapsedPlaybackTime могут влиять на обновление MPNowPlayingInfoCenter.

person xianwenx    schedule 07.02.2016
comment
Это правильно - я использую новую реализацию для событий удаленного управления, и они работают правильно. Я установил все nowPlayingInfo ключи, о которых у меня есть информация, включая все те, которые вы упомянули. - person Hélène Martin; 09.02.2016

Вот мой случай:

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 _ { }

Оно работает

person dengApro    schedule 11.05.2020