I'm currently working on the chapter for
More iPhone 3 Development on accessing the iPod Library. I thought I'd throw out another bit of teaser code in the form of a category.
MPMediaItemCollection is the class that is used to specify queues of songs to be played. Unfortunately, it's an immutable collection object, and there is no mutable counterpart to it. That makes it kind of a pain when you have to add, delete, or insert items into a collection, or when you want to join multiple collections together.
This category adds a bunch of methods to
MPMediaItemCollection that make it easier to manipulate play queues. It doesn't make
MPMediaItemCollection mutable, but it does the next best thing. It makes it easier to create new collections based on existing collections. There are methods that create new collections by inserting or deleting songs from an existing collection.
I plan to add a few additional methods for sorting and re-ordering collections for the final version of this category, so look for an update to this in the next few days.
Note: I fixed a couple of leaks after the initial posting
MPMediaItemCollection-Utils.h#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>
@interface MPMediaItemCollection(Utils)
- (MPMediaItem *)firstMediaItem;
- (MPMediaItem *)lastMediaItem;
- (MPMediaItem *)mediaItemAtIndex:(NSUInteger)index;
- (MPMediaItem *)mediaItemAfterItem:(MPMediaItem *)compare;
- (NSString *)titleForMediaItemAtIndex:(NSUInteger)index;
- (BOOL)containsItem:(MPMediaItem *)compare;
- (MPMediaItemCollection *)collectionByAppendingCollection:(MPMediaItemCollection *)otherCollection;
- (MPMediaItemCollection *)collectionByAppendingMediaItems:(NSArray *)items;
- (MPMediaItemCollection *)collectionByAppendingMediaItem:(MPMediaItem *)item;
- (MPMediaItemCollection *)collectionByDeletingMediaItems:(NSArray *)itemsToRemove;
- (MPMediaItemCollection *)collectionByDeletingMediaItem:(MPMediaItem *)itemToRemove;
- (MPMediaItemCollection *)collectionByDeletingMediaItemAtIndex:(NSUInteger)index;
- (MPMediaItemCollection *)collectionByDeletingMediaItemsFromIndex:(NSUInteger)from toIndex:(NSUInteger)to;
@end
MPMediaItemCollection-Utils.m#import "Simple_PlayerViewController.h"
#import "MPMediaItemCollection-Utils.h"
@implementation Simple_PlayerViewController
@synthesize titleSearch;
@synthesize playPauseButton;
@synthesize tableView;
@synthesize player;
@synthesize collection;
@synthesize nowPlaying;
#pragma mark -
- (IBAction)doTitleSearch {
if ([titleSearch.text length] == 0)
return;
MPMediaPropertyPredicate *titlePredicate =
[MPMediaPropertyPredicate predicateWithValue: titleSearch.text
forProperty: MPMediaItemPropertyTitle
comparisonType:MPMediaPredicateComparisonContains];
MPMediaQuery *query = [[MPMediaQuery alloc] initWithFilterPredicates:[NSSet setWithObject:titlePredicate]];
if ([[query items] count] > 0) {
if (collection)
self.collection = [collection collectionByAppendingMediaItems:[query items]];
else
self.collection = [MPMediaItemCollection collectionWithItems:[query items]];
collectionModified = YES;
[self.tableView reloadData];
}
[query release];
titleSearch.text = @"";
[titleSearch resignFirstResponder];
}
- (IBAction)showMediaPicker {
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
picker.delegate = self;
[picker setAllowsPickingMultipleItems:YES];
picker.prompt = NSLocalizedString(@"Select items to play", @"Select items to play");
[self presentModalViewController:picker animated:YES];
[picker release];
}
- (IBAction)backgroundClick {
[titleSearch resignFirstResponder];
}
- (IBAction)previousTrack {
[player skipToPreviousItem];
}
- (IBAction)nextTrack {
[player skipToNextItem];
}
- (IBAction)playOrPause {
if (player.playbackState == MPMusicPlaybackStatePlaying) {
[player pause];
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"play.png"] forState:UIControlStateNormal];
}
else {
[player play];
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"pause.png"] forState:UIControlStateNormal];
}
[self.tableView reloadData];
}
- (IBAction)removeTrack:(id)sender {
NSUInteger index = [sender tag];
MPMediaItem *itemToDelete = [collection mediaItemAtIndex:index];
if ([itemToDelete isEqual:nowPlaying])
[player skipToNextItem];
MPMediaItemCollection *newCollection = [collection collectionByDeletingMediaItemAtIndex:index];
self.collection = newCollection;
collectionModified = YES;
NSUInteger indices[] = {0, index};
NSIndexPath *deletePath = [NSIndexPath indexPathWithIndexes:indices length:2];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:deletePath] withRowAnimation:UITableViewRowAnimationFade];
}
#pragma mark -
- (void)viewDidLoad {
MPMusicPlayerController *thePlayer = [MPMusicPlayerController iPodMusicPlayer];
self.player = thePlayer;
[thePlayer release];
if (player.playbackState == MPMusicPlaybackStatePlaying) {
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"pause.png"] forState:UIControlStateNormal];
MPMediaItemCollection *newCollection = [MPMediaItemCollection collectionWithItems:[NSArray arrayWithObject:[player nowPlayingItem]]];
self.collection = newCollection;
self.nowPlaying = [player nowPlayingItem];
}
else {
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"play.png"] forState:UIControlStateNormal];
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver: self
selector: @selector (nowPlayingItemChanged:)
name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object: player];
[player beginGeneratingPlaybackNotifications];
}
- (void)viewDidUnload {
self.titleSearch = nil;
self.playPauseButton = nil;
self.tableView = nil;
[super viewDidUnload];
}
- (void)dealloc {
[titleSearch release];
[playPauseButton release];
[tableView release];
[player release];
[collection release];
[super dealloc];
}
#pragma mark -
#pragma mark Media Picker Delegate Methods
- (void) mediaPicker: (MPMediaPickerController *) mediaPicker
didPickMediaItems: (MPMediaItemCollection *) theCollection {
[self dismissModalViewControllerAnimated: YES];
if (collection == nil){
self.collection = theCollection;
[player setQueueWithItemCollection:collection];
[player setNowPlayingItem:[collection firstMediaItem]];
self.nowPlaying = [collection firstMediaItem];
[player play];
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"pause.png"] forState:UIControlStateNormal];
}
else {
self.collection = [collection collectionByAppendingCollection:theCollection];
}
collectionModified = YES;
[self.tableView reloadData];
}
- (void) mediaPickerDidCancel: (MPMediaPickerController *) mediaPicker {
[self dismissModalViewControllerAnimated: YES];
}
#pragma mark -
#pragma mark Player Notification Methods
- (void)nowPlayingItemChanged:(NSNotification *)notification {
if (![collection containsItem:[player nowPlayingItem]]) {
if (collectionModified) {
[player play];
[player setQueueWithItemCollection:collection];
[player setNowPlayingItem:[collection mediaItemAfterItem:nowPlaying]];
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"pause.png"] forState:UIControlStateNormal];
}
else if ([player nowPlayingItem] != nil) {
MPMediaItem *nowPlayingItem = [player nowPlayingItem];
self.collection = [collection collectionByAppendingMediaItem:nowPlayingItem];
}
}
self.nowPlaying = [player nowPlayingItem];
if (nowPlaying == nil)
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"play.png"] forState:UIControlStateNormal];
collectionModified = NO;
[self.tableView reloadData];
}
#pragma mark -
#pragma mark Table View Methods
- (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section {
return [collection count];
}
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"Music Queue Cell";
UITableViewCell *cell = [theTableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
UIButton *removeButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *removeImage = [UIImage imageNamed:@"remove.png"];
[removeButton setBackgroundImage:removeImage forState:UIControlStateNormal];
[removeButton setFrame:CGRectMake(0.0, 0.0, removeImage.size.width, removeImage.size.height)];
[removeButton addTarget:self action:@selector(removeTrack:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryView = removeButton;
}
cell.textLabel.text = [collection titleForMediaItemAtIndex:[indexPath row]];
if ([nowPlaying isEqual:[collection mediaItemAtIndex:[indexPath row]]]) {
cell.textLabel.font = [UIFont boldSystemFontOfSize:21.0];
if (player.playbackState == MPMusicPlaybackStatePlaying)
cell.imageView.image = [UIImage imageNamed:@"play_small.png"];
else
cell.imageView.image = [UIImage imageNamed:@"pause_small.png"];
}
else {
cell.textLabel.font = [UIFont systemFontOfSize:21.0];
cell.imageView.image = [UIImage imageNamed:@"empty.png"];
}
cell.accessoryView.tag = [indexPath row];
return cell;
}
- (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MPMediaItem *selected = [collection mediaItemAtIndex:[indexPath row]];
if (collectionModified) {
[player setQueueWithItemCollection:collection];
}
[player setNowPlayingItem:selected];
[player play];
[playPauseButton setBackgroundImage:[UIImage imageNamed:@"pause.png"] forState:UIControlStateNormal];
[self.tableView reloadData];
}
- (CGFloat)tableView:(UITableView *)theTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 34;
}
@end