The music player object also sends notifications when the item being played changes. This occurs when a new song starts playing, or one finishes playing. The notification is different than the one your controller is currentlyobserving, so you’ll need to create another notification handler and register to observe the additional notification. Start by adding a prototype for your new function to
the private @interface DDViewController () section at the beginning of the DDViewController.m file:
- (void)playingItemDidChangeNotification:(NSNotification*)notification;
Near the -playbackStateDidChangeNotification: method, add your new notification handler:
- (void)playingItemDidChangeNotification:(NSNotification*)notification
{
MPMediaItem *nowPlaying = music.nowPlayingItem;
MPMediaItemArtwork *artwork = [nowPlaying valueForProperty:MPMediaItemPropertyArtwork]; UIImage *albumImage = [artwork imageWithSize:_albumView.bounds.size];
if (albumImage==nil)
albumImage = [UIImage imageNamed:@"noartwork"];
_albumView.image = albumImage;
_songLabel.text = [nowPlaying valueForProperty:MPMediaItemPropertyTitle];
_albumLabel.text = [nowPlaying valueForProperty:MPMediaItemPropertyAlbumTitle];
_artistLabel.text = [nowPlaying valueForProperty:MPMediaItemPropertyArtist];
}
The method gets the nowPlayingItem property object. Rather than have a bunch of fixed properties (like typical objects), the MPMediaItem object contains a variable number of property values that you request via a key. A key is a fixed value—typically a string object—that identifies the value you’re interested in.
The first thing you ask for is the MPMediaItemPropertyArtwork value. This value will be a MPMediaItemArtwork object that encapsulates the album artwork for the song. You then request a UIImage object, optimized for the sizeof your image view.
The thing to remember about media metadata is that there are no guarantees. Any song in the iPod library might have values for title, artist, and artwork. Or, it might not have any of those values. Or, it might have a title andartist, but no artwork, or artwork and no title. The bottom line is, be prepared for the case where something you ask for isn’t available.
In this app, you test to see if MPMediaItemArtwork declined to return a displayable image (albumImage==nil). In that case, replace the image with a resource image named “noartwork.”
For that statement to work, you’ll need to add the noartwork.png and noartwork@2x.png files to your project. Select the Images.xcassets item in the navigator. Find the Learn iOS Development Projects ➤ Ch 9 ➤ DrumDub (Resources) folder and drag the noartwork.png and noartwork@2x.png files into the asset catalog.
The last three statements repeat this process, obtaining the title, album title, and artist name for the item. In this code you don’t have to worry about missing values. If an item doesn’t have an album name—for example,requesting MPMediaItemPropertyAlbumTitle—the media item will return a nil value. It just so happens that setting a UILabel’s text property to nil blanks the view—which is exactly what you want to happen if there’s no albumname.
The last step is observing the item changed notifications. Find the -musicPlayer property getter method. Find the code that observes the playback state changes, and insert this new statement:
[notificationCenter addObserver:self selector:@selector(playingItemDidChangeNotification:)
name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:music];
Now whenever a new song starts playing, your
controller will receive a “playing item did change” notification and display that information to the user. Give it a try.
Run your app, select a song, and start it playing. The song information and artwork display, as shown in Figure 9-11. If you let the song play to its end, the information disappears again.
Figure 9-11. Album artwork and song metadata
The only thing I don’t like about this interface is that the artwork view and the three label views are filled with the placeholder information when the app launches. Fix that in the Main.storyboard file by clearing the text property ofthe three metadata labels, and setting the image view’s initial image to noartwork.png.
Make Some Noise
So far, you’ve essentially created a (minimal) iPod app. That’s an impressive feat, but it isn’t the only way to add sound to your app. You may want to add sound effects to actions, or play music files that you’ve bundled. Maybeyou want to play live audio streams from a network data source. Those are all easy to do, even easier than playing songs from the iPod library—which was pretty easy.
I’ll get the easy part out of the way first. To play and control almost any kind of audio data your app has access to:
1. Create an AVAudioPlayer object.
2. Initialize the player with the source of the audio data, typically a URL to a
resource file.
3. Send it a -play message
And just like the MPMusicPlayerController, the AVAudioPlayer object takes care of all of the details, including notifying your delegate when it’s done.
So you might be thinking that it won’t take more than a dozen lines of code and some buttons to finish this app. Sadly, you would be mistaken.
Living in a Larger World
What makes playing audio in this app complicated is not the code to play your sounds. The complication lies in the nature of iOS devices, and the environment they exist in.
Consider an iPhone. It’s a telephone and videophone; audio is used to indicate incoming calls and play the audio stream from the caller. It’s a music player; you can play your favorite music or audiobook, or stream Internetradio, even while using other apps. It’s an alarm clock; timers can remind you of things to do any time of the day or night. It’s a game console; games are full of sounds, sound effects, and ambient music. It’s a pager; messages, notifications, and alerts can occur for countless reasons, interrupting your work (or play) at a moment’s notice. It’s also a video player, TV, answering machine, GPS navigator, movie editor, Dictaphone, and digitalassistant.
All of these audio sources share a single output. To do that effectively—creating a pleasant experience for the user—all of these competing audio sources have to cooperate. Game sounds and music playback have to stop when a telephone call arrives. Background music needs to temporarily lower its volume, if the user is expected to hear a reminder or recorded message. iOS refers to these as interruptions.
Adding to this complexity, iOS devices have many different ways of producing sound. There’s the built-in speakers, the head phone jack, wireless Bluetooth devices, AirPlay, and the dock connecter. iOS calls these audio routes. Audio can be directed to any one of these, and switched to a different one at any time (called a route change). Audio playback must be aware of this and your app may need to react to those changes. For example, Apple recommends that unplugging the headphones should cause music playback to pause, but game sound effects should continue playing.
And just to add one more dash of complication, most iOS devices have a ring/silence switch. Audio that’s intended as an alert, alarm, embellishment, or sound effect should play only when the ring switch is in its normal position. More deliberate audio, like movies and audiobooks, should play normally, even when the silence switch is engaged.
Taken together, your app needs to
n Decide the intent and purpose of each source of audio in your app
n Declare this purpose, so iOS can adjust its behavior to accommodate your audio
n Observe interruptions and audio route changes, and take appropriate action
The good news is that not every audio-endowed app you write has to do all of these things. In fact, if you only use the iPod music player or only play incidental sounds using AVAudioPlayer objects, you probably don’t have todo anything at all. Both of these classes will “do the right thing.”
For an app like DrumDub, however, that wants to manage its own music playback while mixing
in additional sound effects, all of these steps need to be taken. So before you start adding sound effects to your app, lay some of the groundwork.
Configuring Your Audio Session
You communicate your intent—describe the kinds of sounds your app will make and how those will affect other audio sources—to iOS through an audio session. Every iOS app gets a generic audio session, pre-configured witha basic set of behaviors.
That’s why, if you only play music through a music player controller, you don’t have to do anything special; the default audio session is just fine.
DrumDub needs to both playback and mix audio. This is unusual, so it will need to reconfigure its audio session. Apps that only play audio can typically configure their audio session once and leave it.
In your DDAppDelegate.m file, you’ll find the implementation for your app’s delegate object. One of the messages sent to your app’s delegate is the -application:didFinishLaunchingWithOptions: message. As the name implies, it’ssent immediately after your app has loaded, initialized, and is about to start running. It’s the perfect place to put code that needs to run just once, and run before anything else gets underway. Add the following code (in bold) tothe beginning of that method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
AVAudioSession *audioSession = [AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionMixWithOthers error:NULL];
An audio session has a category and a set of options. There are seven different categories to choose from,aslisted in Table 9-1.
Table 9-1. Audio session categories
Session Categories App description
AVAudioSessionCategoryAmbient Plays “background” audio or non-essential sound effects. The app will work just fine without them. App audio mixes with other audio (the iPod) playing at the same time. The ring/silence switchsilences the app’s audio.
AVAudioSessionCategorySoloAmbient Plays non-essential audio that does not mix with other audio; other audio sources are silenced when the app plays. The ring/ silence switch silences the app’s audio.
AVAudioSessionCategoryPlayback Plays music or other essential sounds. In other words, audio is the principle purpose of the app and it wouldn’t work without it. The ring/silence switch does not silence its audio.
AVAudioSessionCategoryRecord Records audio.
AVAudioSessionCategoryPlayAndRecord Plays and records audio.
AVAudioSessionCategoryAudioProcessing Performs audio processing (using the hardware audio codecs), while neither playing nor recording.
AVAudioSessionCategoryMultiRoute Needs to output audio to multiple routes simultaneously. A slideshow app might play music through a dock connector, while simultaneously sending audio prompts through the headphones.
The default category is AVAudioSessionCategorySoloAmbient. For DrumDub, you’ve decided that audio is its raison d’être—its reason to exist. Use the -setCategory:withOptions:error: message to change its category toAVAudioSessionCategoryPlayback. Now your app’s audio won’t be silenced
by the ring/silence switch.
You can also fine-tune the category with a number of category-specific options. The only option for the playback category is AVAudioSessionCategoryOptionMixWithOthers. If set, this option allows audio played with yourAVAudioPlayer objects to “mix” with other audio playing at the same time. This is exactly what you want for DrumDub. Without this option, playing a sound would stop other audio sources.
All of these symbols are defined in the AVFoundation framework, so you’ll need to #import the AVFoundation.h header to get them. Add this statement above all the other #import statements in DDAppDelegate.m:
#import <AVFoundation/AVFoundation.h>
See, that wasn’t too hard. In fact, there was a lot more explanation than code.
With your audio session correctly configured, you can now add (mix in) sound effects with your music.
Playing Audio Files
You’re finally at the heart of your app’s design: playing sounds. You’re going to have four buttons, each playing a different sound. To implement this, you will need:
n Four button objects
n Four images
n Four AVAudioPlayer objects
n Four sampled sound files
n And an action method to play a sound
It will be easier to build the interface once you have added the resources and defined the action method, so start there. Find your Learn iOS Development Projects ➤ Ch 9 ➤ DrumDub (Resources) folder and locate the 12 files in this table:
Sound Sample
|
Button Image
|
Retina Display Image
|
snare.m4vbass.m4v tambourine.m4vmaraca.m4v
|
snare.pngbass.pngtambourine.pngmaraca.png
|
Begin by adding the button image files.
Select the Images.xcassets asset catalog item. In it, you’ll see the noartwork resource you added earlier. Drag the eight instrument image files (two each of snare, bass, tambourine, andmaraca) into the asset catalog’s group list, as shown in Figure 9-12.
Figure 9-12. Adding image resources
While you’re here, select the AppIcon group of the asset catalog and drag the appropriate app icon image files into it, as you’ve done for earlier projects. If you’re building the iPhone version of this project, that would be theDDIconIPhone@2x, DDIconSpotlight, and DDIconSpotlight@2x files. If you’re building
an iPad version, add the DDIconIPad, DDIconIPad@2x, DDIconSpotlight, and DDIconSpotlight@2x files.
The four sound files (bass.m4a, maraca.m4a, snare.m4a, and tambourine.m4a) will also become resource files, but they’re not the kind of resources managed by an asset catalog. You can add any kind of file you want directly to aproject, and have that file included as a resource in your app’s bundle.
For the sake of neatness, begin by creating a new group for these resource files. Control+click/ right-click on the DrumDub group (not the project) in the navigator and choose the New Group command, as shown on the leftin Figure 9-13.
Figure 9-13. Adding non-image resources
Name the group Sounds, as shown in the middle of Figure 9-13. Locate the four sound sample files in the Finder and drag them into the group, as shown on the right of Figure 9-13. If you miss and add the items to the DrumDubgroup instead, select them in the navigator and drag them into the Sounds group. You can always reorganize your project items as you please.
After dropping your items into the navigator, Xcode presents some options that determine how the items will be added to your project, as shown in Figure 9-14. Make sure the Copy items into destination group’s folder (ifneeded) option is checked. This option copies the new items into your app’s project folder. The second option (Create groups for any added folders) only applies when adding folders full of resource files.
Figure 9-14. Add project file options
Finally, make sure the DrumDub target is checked, as shown in Figure 9-14. This option makes these items members of the DrumDub app target, which means they’ll be included as resource files in your finished app. (If youforget to check this, you can later change the target membership of any item using the file inspector.) Click Finish and Xcode will copy the sound sample files into your project folder, add them to the project navigator, andinclude them in the DrumDub app target. These files are now ready to be used in your app.




No comments:
Post a Comment