Tag: mpmediaquery

Back in June Apple released its own music streaming service, Apple Music, and to this date there is no API that allows developers to easily take advantage of the user’s Apple Music library. Sure, MPMediaPickerController and MPMusicPlayerController work great with very few lines of code, but the player doesn’t work in the background and that’s a very big limitation.
AVFoundation’s AVPlayer and AVAudioPlayer are two very powerful options when it comes to audio and video playback and they work in the background, but they both have a problem: they can’t play directly the MPMediaItems that the MPMediaPickerController returns.
Before Apple Music (and iTunes Match), every MPMediaItem used to represent a single song that was physically stored on the user’s device, so the quickest option was to access its MPMediaItemPropertyAssetURL and use it for playback. Today, there is a range of different situations and the AssetURL isn’t always available.

Song origin/location

AssetURL

iTunes (Win/Mac OSX transfer)

YES

iTunes (downloaded via iOS app)

YES

iTunes Match (stored on device)

YES

iTunes Match (stored on iCloud)

NO

Apple Music (stored on device)

NO

Apple Music (stored on iCloud)

NO

As expected, an iTunes Match or Apple Music song that is only in the cloud doesn’t have a local AssetURL, but Apple Music songs aren’t available even if the user decided to save them for offline use. Since there is no support for Apple Music, if we want to let the user pick a song to be played with AVAudioPlayer, we need to be able to hide the ones that don’t have a valid AssetURL. By default MPMediaPickerController shows every song in the library (including cloud items), so the only option is to create our own song picker using MPMediaQuery. This class doesn’t allow to filter directly the songs based on their AssetURL, but once we have the complete list we can cycle through the array and remove the ones who don’t have a valid URL:

// Get all the songs from the library
MPMediaQuery *query = [MPMediaQuery songsQuery];
// Filter out cloud items
[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithBool:NO] forProperty:MPMediaItemPropertyIsCloudItem]];
NSArray *items = [query items];
NSMutableArray *itemsWithAssetURL = [NSMutableArray array];
// For every song stored locally
for (MPMediaItem *item in items)
{
// If the song has an assetURL
if ([item valueForProperty:MPMediaItemPropertyAssetURL]) {
// Add it to the array of valid songs
[itemsWithAssetURL addObject:item];
}
}

This does the trick, but if the user’s library contains thousands of songs (as it does most of the times) it’s probably best to perform this away from the main thread and/or to limit the number of items to check in the first place.