MPMediaQuery search for Artists, Albums, and Songs
Asked Answered
L

4

11

How can I search the iPod Library in the same manner as the iOS Music application? I want to make general queries that return results for each Artists, Albums, and Songs. For instance, if I search Kenny Chesney I want the songs query to return all Kenny Chesney songs (and any songs titles or albums that contain Kenny Chesney in them.) When I make this query and a predicate for each property (song title, album title, artist name), an empty array returns.

Here is a bit of code that may give you a better idea of what I am attempting to accomplish:

MPMediaPropertyPredicate *songPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyTitle
                              comparisonType:MPMediaPredicateComparisonContains];

MPMediaPropertyPredicate *albumPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyAlbumTitle
                              comparisonType:MPMediaPredicateComparisonContains];

MPMediaPropertyPredicate *artistPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyArtist
                              comparisonType:MPMediaPredicateComparisonContains];

MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
[songsQuery addFilterPredicate:songNamePredicate];
[songsQuery addFilterPredicate:artistNamePredicate];
[songsQuery addFilterPredicate:albumNamePredicate];

NSLog(@"%@", [songsQuery items]);

I have this working by running the query with each predicate separately but this seems very inefficient!

Laddy answered 12/11, 2012 at 22:40 Comment(0)
L
16

Combining your predicates this way makes it like "AND" relationship. It means that you are querying for a song that has title, album and name all are matching the search text.

To achive what you are trying to do, you should run 3 queries and combining the results in a proper way.

If you run:

MPMediaPropertyPredicate *artistPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyArtist
                              comparisonType:MPMediaPredicateComparisonContains];

NSSet *predicates = [NSSet setWithObjects: artistPredicate, nil];

MPMediaQuery *songsQuery =  [[MPMediaQuery alloc] initWithFilterPredicates: predicates];

NSLog(@"%@", [songsQuery items]);

This will return you with artists matching your search. The same you should do for songs and albums.

If you this this is slow, you may retrieve all the media at once and filter it manually:

MPMediaQuery *everything = [[MPMediaQuery alloc] init];

NSLog(@"Logging items from a generic query...");
NSArray *itemsFromGenericQuery = [everything items];
for (MPMediaItem *song in itemsFromGenericQuery) {
    NSString *songTitle = [song valueForProperty: MPMediaItemPropertyTitle];
    /* Filter found songs here manually */
}
Lodicule answered 6/2, 2013 at 12:31 Comment(3)
just mention that if you do 3 queries separately you can end up with duplicate items. eg. if album is named like artist, etc.Preheat
@relikd Yep this is where I am. I have three different queries that add to three arrays - but there are duplicates - which is obviously a problem.Prostyle
you can check before merging them with if ([arr indexOfObject:song] == NSNotFound) { [arr addObject:song]; }. But this isn't very efficient especially on large arrays. This is because indexOfObject:sends a isEqual: message to ALL items before adding the new one.Preheat
P
11

this works for me:

MPMediaQuery *searchQuery = [[MPMediaQuery alloc] init];
NSPredicate *test = [NSPredicate predicateWithFormat:@"title contains[cd] %@ OR albumTitle contains[cd] %@ OR artist contains[cd] %@", searchString, searchString, searchString];
NSArray *filteredArray = [[searchQuery items] filteredArrayUsingPredicate:test];

//NSLog(@"%@", [filteredArray valueForKeyPath:@"title"]);

for (MPMediaItem *song in filteredArray) 
{
    [arrayOfSongItems addObject:song];
}

I basically filter after getting all items from media query. I don't think my solution is better than filtering the query at the first place, but definitely better than searching three times separately. Or iterating through the array multiple times.

Preheat answered 6/2, 2013 at 15:26 Comment(3)
How do you manage to group the results into MPMediaItemCollections so that rather than returning 50 songs belonging to 3 albums, you return just the 3 albums?Gullah
do you mean an array with 3 albums where each album contains the tracks? OR do you mean just one random item of each album?Preheat
The second one, but would like an opinion on the first too. The only way I've found to do it is to loop through the filtered array and remove duplicate MPMediaItemPropertyAlbumPersistentIDs. Although I like your approach, I've found it to be expensive; although it sounds a bit tedious, making individual MPMediaQuerys for Artists, Title and Album Title and summing the filtered items into one array worked out faster for me (with a music library of 4000+).Gullah
T
2

There are two approaches here:

  1. Use multiple MPMediaPropertyPredicates to get everything using queries. Using this approach, you will need to do multiple queries and aggregate the results yourself. NSSet is your friend!

  2. Get everything (or close to it) from the library and then do your search or filter after that.

Access to the music library can be VERY slow on older devices or versions of the OS. In my own experience implementing something very similar to what you are describing I got the best results with approach 2. Once I had, well, just about everything I then iterated over those items using a scatter/gather approach (my needs did not allow the use of NSPredicate for this). This was actually much more performant than approach 1 for me, though I know that with iOS 6 that gap has closed somewhat.

Tuba answered 13/2, 2013 at 5:34 Comment(0)
I
2

Swift 3+ solution for filtering items after running the query, similar to Mohammed Habib's answer:

let searchString = “string to search”
let query = MPMediaQuery()

let filteredItems = query.items?.filter { item -> Bool in
    let properties = [item.songTitle,
                      item.artist,
                      item.albumTitle]

    let lowercasedProperties = properties.compactMap { $0.lowercased() }

    return lowercasedProperties.contains(searchString.lowercased())
}

print(filteredItems ?? [])
Imbed answered 6/8, 2018 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.