/dev/trouble
Eric Roller's Development Blog

Assembling a playlist queue to play in MPMusicPlayer

- Posted in iOS by

Here is a piece of code that looks quite reasonable, but it doesn't work. The plan is to assemble a playlist of multiple songs starting off with a list of their persistent IDs:

let persistentSongIDs : [MPMediaEntityPersistentID] = ...

let musicPlayer = MPMusicPlayerController.applicationQueuePlayer
var filterSet = Set<MPMediaPredicate>()

// THIS WILL NOT WORK FOR MULTIPLE SONGS:
for songID in persistentSongIDs {
    let predicate = MPMediaPropertyPredicate(value: songID,
            forProperty: MPMediaItemPropertyPersistentID)
    filterSet.insert(predicate)
}

if filterSet.isEmpty == false {
    let query = MPMediaQuery(filterPredicates: filterSet)
    let descriptor = MPMusicPlayerMediaItemQueueDescriptor(query: query)
    mediaPlayer.setQueue(with: descriptor)
    mediaPlayer.play()
}

So, why does it not work? We are creating search predicates with each of the persistent song IDs, surely this should give us a list of songs that we can play, right?

Well, no.

As soon as you have multiple songs, the song list will always be empty.

The reason for this is simple, if you think about it. What we are searching for is:

"persistentID == 5819395988480015566"
"persistentID == 9054558313999882624"
...
"persistentID == 8511246475365999992"

But all of these search predicates are combined, i.e. they all need to be true to find matches.

Let's rephrase this: This will never be true:

"persistentID == A" && "persistentID == B" && ...

So How Do We Fix This?

We need to search for this:

"persistentID == A" || "persistentID == B" || ...

But that cannot be expressed in a list of search predicates. The solution is to search for each ID in turn and assemble a playlist manually:

mySongs = [MPMediaItem]()

for songID in persistentSongIDs {
    let predicate = MPMediaPropertyPredicate(value: songID,
            forProperty: MPMediaItemPropertyPersistentID)
    let query = MPMediaQuery(filterPredicates: [predicate])
    if let song = query.items?.first {
        mySongs.append(song)
    }
}

if mySongs.isEmpty == false {
    // Let's play !!!
    musicPlayer.setQueue(with: MPMediaItemCollection(items: mySongs))
    musicPlayer.play()
}