Search code examples
iosswiftmpmusicplayercontrollermpmediaitemuibackgroundtask

MPMusicPlayerController.systemMusicPlayer setQueue difficulties


I am having difficulty with my music app. The idea is to tap a button and play more songs from the artist that is currently playing. When I am IN the app and I hit next song it works fine. However if I let the song end naturally the app would play a random song. So I added logic to say if the songs remaining time is 0 then play next song using media players func because I know that works. This solved it except if the app is in the background. I tried to keep the app alive if it is in the background but I guess that is not working.

Every time I think I have solved something it the issue comes back.

What I expect to happen is lock on an Artist, close app to background and when the song ends play another song from Artist.

What actually happens is when I lock the artist and close app to background is the song will end and sometimes it will play the right song. Sometimes it will not. HOWEVER when it plays the wrong song and I open the app back up it ends the currently (Wrong) playing song and starts playing a song by the proper artist

to be clear, I think it’s unwise to need logic that runs when the song time remaining is 0 to skip to next song but for whatever reason I need it

I have set the Capabilities to background fetch and Audio Airplay and picture in picture

I have tried to only post relevant code in order..

I have a property

let mediaPlayer = MPMusicPlayerController.systemMusicPlayer
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid

In my ViewDidLoad

try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try? AVAudioSession.sharedInstance().setActive(true)


DispatchQueue.main.async {
  self.clearSongInfo()
  MediaManager.shared.getAllSongs { (songs) in
    guard let theSongs = songs else {
      return
    }
    self.mediaPlayer.nowPlayingItem = nil

    self.newSongs = theSongs.filter({ (item) -> Bool in
      return !MediaManager.shared.playedSongs.contains(item)
    })
    self.aSongIsInChamber = true
    self.mediaPlayer.setQueue(with: MPMediaItemCollection(items: self.newSongs.shuffled())
    )
    self.mediaPlayer.shuffleMode = .off
    self.mediaPlayer.repeatMode = .none

  }
  NotificationCenter.default.addObserver(self, selector: #selector(self.songChanged(_:)), name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange, object: self.mediaPlayer)
  self.mediaPlayer.beginGeneratingPlaybackNotifications()
}

I have a func that updates the played time and remaining time and in that I have

if Int(self.songProgressSlider.maximumValue - self.songProgressSlider.value) < 1 {
  print("song ended naturally, skipped")
  mediaPlayer.prepareToPlay(completionHandler: { (error) in
    DispatchQueue.main.async {
      self.mediaPlayer.skipToNextItem()
    }
  })
}

When I play the music I have a bool isPlaying

If it is then the time is running and I have this

let app = UIApplication.shared
  var task: UIBackgroundTaskIdentifier?
  task  = app.beginBackgroundTask {
    app.endBackgroundTask(task!)
  }

I have that to keep the timer on in the background so it will play the next song when the time remaining is 0

Now to lock onto an Artist I have the following code that executes on button tap

let artistPredicate: MPMediaPropertyPredicate  = MPMediaPropertyPredicate(value: nowPlaying.artist, forProperty: MPMediaItemPropertyArtist)
      let query: MPMediaQuery = MPMediaQuery.artists()
      let musicPlayerController: MPMusicPlayerController = MPMusicPlayerController.systemMusicPlayer

      query.addFilterPredicate(artistPredicate)
      musicPlayerController.setQueue(with: query)

Solution

  • I find that setting the queue does not "take" until you say prepareToPlay (literally or implicitly by saying play). The current edition of my book says:

    My experience is that the player can behave in unexpected ways if you don't ask it to play, or at least prepareToPlay, immediately after setting the queue. Apparently the queue does not actually take effect until you do that.

    You have also said (in a comment):

    ideally I would like the song to finish first and then go onto the queue I have

    Okay, but then perhaps what you want here is the (new in iOS 11) append feature that allows you modify the queue without replacing it entirely.