Search code examples
iosswiftmpmusicplayercontroller

How to know when MPMusicPlayerController changes playing item naturally


I am using the MPMusicPlayerController to create a music player in my app. I have got it all working great except for one small issue:

When the songs changes naturally - one song finishes and the next starts from the set queue - the notification MPMusicPlayerControllerNowPlayingItemDidChange doesn't appear to be called.

At the moment I am utilising both the MPMusicPlayerControllerNowPlayingItemDidChange and MPMusicPlayerControllerPlaybackStateDidChange notifications. These cover playing, pausing, shuffle, repeat, next, previous etc. When the notifications are hit I then refresh the screen based on the MPMusicPlayerController to show the new song, artist or different button icons required. Neither of these are called though when a song finishes and the next one automatically starts playing - this means that the title and artist of the previous song is left until the user reloads the screen or interacts with the audio controls which is not good user experience.

Short of regularly checking whether the current name matches the playing name I don't know how to update this in the normal flow of the app.

NotificationCenter.default.addObserver(
  forName: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange,
  object: musicPlayerController,
  queue: nil) { _ in
    // Update view
}

Solution

  • The answer to this turns out to be very simple but also difficult to spot if you're not looking in the right place.

    Before we add our observers we need to begin generating the playback notifications:

    musicPlayerController.beginGeneratingPlaybackNotifications()
    
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(refreshView),
                                           name: .MPMusicPlayerControllerPlaybackStateDidChange,
                                           object: musicPlayerController)
    
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(refreshView),
                                           name: .MPMusicPlayerControllerNowPlayingItemDidChange,
                                           object: musicPlayerController)
    

    We also need to remember to end generating them when we leave (deallocate) the view:

    deinit {
        NotificationCenter.default.removeObserver(self, name: .MPMusicPlayerControllerPlaybackStateDidChange, object: nil)
        NotificationCenter.default.removeObserver(self, name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)
        musicPlayerController.endGeneratingPlaybackNotifications()
    }
    

    The confusion came from the musicMediaPlayer returning a number of notifications even without this which didn't point to the fact we weren't observing all the notifications that were being fired.

    Note: It is worth noting that as of the time of writing this it was in discussion whether there was a need to manually remove observers - I have included it here for answer completeness.