user2640633
user2640633

Reputation:

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)

Upvotes: 2

Views: 2617

Answers (2)

matt
matt

Reputation: 535140

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.

Upvotes: 0

user2640633
user2640633

Reputation:

I found a solution that isn't 100% what I wanted, but at this point I'll take it.

On the IBAction where I lock onto an artist I was originally just

self.mediaPlayer.prepareToPlay { error in
  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)
}

What I have done to fix my issue is added

musicPlayerController.play()

after I set the queue.

The downside to this approach is that if I am midway through a song and I tap the button to lock the artist the app immediately ends the now playing song and goes to songs by that artist and I would rather the song finish playing and THEN go the songs I want.

If anybody can think of a way to use play() AND let the song ends naturally ill mark that answer as correct...

NOTE I never wanted to use the logic that checks how long is left in a song and I never wanted to use background code. So I got rid of that and it still works with new idea.

Upvotes: 0

Related Questions