Tuesday, 6 May 2014

Observing the Playing Item [Sweet, Sweet Music]

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 isnt 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 dont have to worry about missing values. If an item doesnt have an album name—for example,requesting MPMediaItemPropertyAlbumTitle—the media item will return a nil value. It just so happens that setting a UILabels text property to nil blanks the view—which is exactly what you want to happen if theres 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 dont 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 views initial image to noartwork.png.


Make Some Noise

So far, you’ve essentially created a (minimal) iPod app. Thats an impressive feat, but it isnt 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 its done.

So you might be thinking that it wont 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. Its a telephone and videophone; audio is used to indicate incoming calls and play the audio stream from the caller. Its a music player; you can play your favorite music or audiobook, or stream Internetradio, even while using other apps. Its an alarm clock; timers can remind you of things to do any time of the day or night. Its a game console; games are full of sounds, sound effects, and ambient music. Its a pager; messages, notifications, and alerts can occur for countless reasons, interrupting your work (or play) at a moments notice. Its 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. Theres 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 thats 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 dont 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. 

Thats why, if you only play music through a music player controller, you dont 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 apps delegate object. One of the messages sent to your apps delegate is the -application:didFinishLaunchingWithOptions: message. As the name implies, itssent immediately after your app has loaded, initialized, and is about to start running. Its 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 apps 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 apps audio.
AVAudioSessionCategoryPlayback                 Plays music or other essential sounds. In other words, audio is the principle purpose of the app and it wouldnt 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 slideshoapmight play music through a dock connector, while simultaneouslsending 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 apps audio wont 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 wasnt 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 apps 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 catalogs group list, as shown in Figure 9-12.


Figure 9-12. Adding image resources

While you’re here, select the AppIcogroup of the asset catalog and drathe appropriate app icoimage files into itas you’vdone for earlier projects. If you’re building the iPhone version of thiproject, 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, antambourine.m4a) will also become resourcfiles, but they’re not the kind of resources managed by an asset catalog. You can add any kind of filyou want directly to aproject, and have that file included as a resource in your apps 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 groups folder (ifneeded) option is checked. This option copies the new items into your apps 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