In version 3.0 of the iPhone SDK, Apple gave us access to the iPod library on the iPhone and and easy-to-use interface to control music playback. In this post, I want to give an overview of these APIs and how I used them in the development of Songtext.
In contrast,
The music player uses notifications to inform you about changes of:
There is one other related notification that is sent by the iPod media library when the contents of the library change, e.g. when you sync your device with iTunes. You must listen to this notification if your app creates its playlists that need to be updated after library changes. To do so, register yourself as an observer for
The notification handlers are where you update your UI in response to changes in the player’s state:
The media picker also has a multiple selection mode that can be enabled by setting its
Getting started: MPMusicPlayerController
You need to addMediaPlayer.framework
to your target in Xcode and #import
. To control music playback, we use an instance of MPMusicPlayerController
. There are two types of music players. The iPodMusicPlayer
is a reference to the music player instance used by the iPod app. Any settings you change, such as the shuffle or repeat modes, will be changed in the iPod app, too. If the iPod is playing when your application starts, the music will continue playing and you can access the current song and skip back and forward through the currently active playlist. When your app quits, the music will continue playing. I imagine this mode is very handy for most utility apps that try to improve your music listening experience by interacting with the iPod. In contrast,
applicationMusicPlayer
gives you a music player whose settings you can change independently of the iPod app. This is probably the way to go if your app is a game and you want to give the user the ability to choose the background music from their library. In Songtext, we’ll use iPodMusicPlayer
because we want to know which song is playing when our app launches:
@property (nonatomic, retain) MPMusicPlayerController *musicPlayer; ... self.musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
- the current song (
MPMusicPlayerControllerNowPlayingItemDidChangeNotification
), - the play/paused/stopped state (
MPMusicPlayerControllerPlaybackStateDidChangeNotification
), or - the volume (
MPMusicPlayerControllerVolumeDidChangeNotification
).
viewDidLoad
. We want to receive all 3 notifications:
// Register for music player notifications NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(handleNowPlayingItemChanged:) name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.musicPlayer]; [notificationCenter addObserver:self selector:@selector(handlePlaybackStateChanged:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer]; [notificationCenter addObserver:self selector:@selector(handleExternalVolumeChanged:) name:MPMusicPlayerControllerVolumeDidChangeNotification object:self.musicPlayer]; [self.musicPlayer beginGeneratingPlaybackNotifications];
MPMediaLibraryDidChangeNotification
notifications and call:
[[MPMediaLibrary defaultMediaLibrary] beginGeneratingLibraryChangeNotifications]
// When the now playing item changes, update song info labels and artwork display. - (void)handleNowPlayingItemChanged:(id)notification { // Ask the music player for the current song. MPMediaItem *currentItem = self.musicPlayer.nowPlayingItem; // Display the artist, album, and song name for the now-playing media item. // These are all UILabels. self.songLabel.text = [currentItem valueForProperty:MPMediaItemPropertyTitle]; self.artistLabel.text = [currentItem valueForProperty:MPMediaItemPropertyArtist]; self.albumLabel.text = [currentItem valueForProperty:MPMediaItemPropertyAlbumTitle]; // Display album artwork. self.artworkImageView is a UIImageView. CGSize artworkImageViewSize = self.artworkImageView.bounds.size; MPMediaItemArtwork *artwork = [currentItem valueForProperty:MPMediaItemPropertyArtwork]; if (artwork != nil) { self.artworkImageView.image = [artwork imageWithSize:artworkImageViewSize]; } else { self.artworkImageView.image = nil; } } // When the playback state changes, set the play/pause button appropriately. - (void)handlePlaybackStateChanged:(id)notification { MPMusicPlaybackState playbackState = self.musicPlayer.playbackState; if (playbackState == MPMusicPlaybackStatePaused || playbackState == MPMusicPlaybackStateStopped) { [self.playPauseButton setTitle:@"Play" forState:UIControlStateNormal]; } else if (playbackState == MPMusicPlaybackStatePlaying) { [self.playPauseButton setTitle:@"Pause" forState:UIControlStateNormal]; } } // When the volume changes, sync the volume slider - (void)handleExternalVolumeChanged:(id)notification { // self.volumeSlider is a UISlider used to display music volume. // self.musicPlayer.volume ranges from 0.0 to 1.0. [self.volumeSlider setValue:self.musicPlayer.volume animated:YES]; }
Accessing song metadata: MPMediaItem
A song is represented by an instance of theMPMediaItem
class. As you see in the code above, we access the song metadata with the valueForProperty:
method. Available properties include pretty much all the data that is available in iTunes: title, artist, album title and artist, genre, composer, duration, track/disc number, album artwork, rating, lyrics, last played date, and play and skip counts. The complete list of properties is available in the documentation. That’s a huge amount of data for all kinds of interesting statistics or organizing apps. I am sure we will see a lot of those on the App Store in the coming months. No write access to iPod library
Unfortunately, we won’t see apps that require write access to the iPod library anytime soon. The entireMPMediaItem
API is read-only at the moment. That is also the reason why Songtext cannot write the lyrics it downloads into the song files themselves to make them available outside the application. I hope Apple gives us write access in a future version of the SDK. Unreliable access to lyrics
WhileMPMediaItem
provides access to the lyrics of a song stored in the iTunes library, I found this to be unreliable. Songtext checks if a song already has lyrics attached and if so, does not try to download them from the web. Unfortunately, this does not work all the time as sometimes, -[MPMediaItem valueForProperty:MPMediaItemPropertyLyrics]
returns nil
even if lyrics are present. I was not able to reproduce the exact conditions under which this error occurs. So beware if you rely on this to work properly. A song selection UI: MPMediaPickerController
Similar to the built-in image picker, Apple provides a complete user interface to select songs from the media library. All we need to do is create an instance ofMPMediaPickerController
, present it to the user as a modal view controller and implement the MPMediaPickerControllerDelegate
protocol:
// MusicPlayerDemoViewController.h @interface MusicPlayerDemoViewController : UIViewController <MPMediaPickerControllerDelegate> { ... } ... // This action should open the media picker - (IBAction)openMediaPicker:(id)sender; @end // MusicPlayerDemoViewController.m - (IBAction)openMediaPicker:(id)sender { MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic]; mediaPicker.delegate = self; mediaPicker.allowsPickingMultipleItems = NO; // this is the default [self presentModalViewController:mediaPicker animated:YES]; [mediaPicker release]; } // Media picker delegate methods - (void)mediaPicker: (MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection { // We need to dismiss the picker [self dismissModalViewControllerAnimated:YES]; // Assign the selected item(s) to the music player and start playback. [self.musicPlayer stop]; [self.musicPlayer setQueueWithItemCollection:mediaItemCollection]; [self.musicPlayer play]; } - (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker { // User did not select anything // We need to dismiss the picker [self dismissModalViewControllerAnimated:YES]; }
Limitations of the media picker
This is all great and simple. The media picker has one huge drawback, though: it does not provide the context from which a song was picked. It is impossible to tell whether the user selected a song from a certain playlist, from an album, or from the Songs tab. All you get back is the single MPMediaItem the user tapped. Therefore, we cannot construct a play queue in the way the iPod does, depending on the context from which the user picked the song. The Next/Previous buttons will not work as expected anymore, and the user would have to pick another song each time the current one ends (the player will just stop at the end of the song). I think this can be very confusing for the user because your app functions differently, depending on whether the user selected a song in the iPod or directly in your app. And that is the reason why Songtext does not have the capability of selecting a new song from inside the app at the moment. Let’s hope Apple improves the media picker in the future.The media picker also has a multiple selection mode that can be enabled by setting its
allowsPickingMultipleItems
property to YES
. This mode works like the editing of the On-The-Go playlist in the iPod app and can also be quite confusing for the user in my opinion. Querying the iPod library with MPMediaQuery
If you want to build a custom media picker UI or select songs programmatically without user interaction, you can do so by building queries withMPMediaQuery
and MPMediaPropertyPredicate
. I will cover these classes in a future post.
thanks :)
ReplyDeleteI would like to be able to use the pause button with a remote control to fade out instead of pausing?
ReplyDelete