Many of the best tutorials on the web are how to do simple things. Application development in the 21st century is often taking a number of building blocks and creating something one block at a time. Hopefully this tutorial helps add another building block to your iOS application development arsenal.
All we want to accomplish is to play a simple sound in our app – we’re not streaming music here or layering the sound of exploding zombies, just the straightforward chime or “jet” sound when sending an e-mail on the iPhone. Okay, less talk more do.
You might want to go ahead and clone the example repository from GitHub, we have some sample files for you to work with.
1 |
git clone https://github.com/iachievedit/ding |
Get an MP3 file, say ding.mp3
that you can find in the folder ding/assets
. We’re going convert it to a CAF (Core Audio Format) file with afconvert
. Core Audio is the “native” format for iOS. To get the highest quality possible we want to review the source audio quality and ensure our conversion takes full advantage of what we have. To do so, run the file through afinfo
first:
1 2 3 4 5 6 7 |
$ afinfo ding.mp3 File: ding.mp3 File type ID: MPG3 Num Tracks: 1 ---- Data format: 2 ch, 44100 Hz, '.mp3' (0x00000000) 0 bits/channel, 0 bytes/packet, 1152 frames/packet, 0 bytes/frame no channel layout. |
Note the 44100 Hz. This is the sample rate of the audio contained in the file. Let’s convert to CAF and retain our sample rate:
1 |
$ afconvert -f caff -d LEI32@44100 ding.mp3 ding.caf |
The “magic string” LEI32@44100
is read as “Little Endian Integer 32-bit at 44.1 kilohertz”. The resulting CAF file will be added to your XCode project. The mp3 file can be kept around as the source reference.
The remaining instructions assume you’ve created an XCode project that looks like the one you can clone from GitHub. We called our application ding, and it’s a basic single-view application with a Play Sound button in the middle of the view. Using XCode we created an IBAction
such that when you pressed the button it calls a method on the ViewController
. Once wiring up the button and view controller has been completed, proceed with importing the sound file into your project.
Using the Finder, drag and drop the file into your XCode project. We typically place audio assets under Supporting Files. If you have a large number of audio assets you may consider creating a group for them. Ensure that Copy items into destination groups’ folder is selected and ensure that the file is added to your project target. If it is not added to the target it won’t be a part of your application bundle, thus when you go to ask iOS to play the file it will silently (no pun intended) fail.
In our sample application found at Github you will see a single method on the view controller:
[objc]
– (IBAction)playSound:(id)sender {
}
[/objc]
This is where we’ll play our sound, but we first need to add the requisite header to the project.
At the top of the view controller, go ahead and add
[objc]
#import <AVFoundation/AVFoundation.h>
[/objc]
This imports the AV (audio/visual, audio/video, whatever) headers. You should not have to add any additional libraries to a default project.
Before we present the final project, let’s look at a method that will not work. Why show you something that won’t work? Well, trust us, you’re going to do this soon or later, and then forget about the incorrect implementation and scratch your head when your sound won’t play.
[objc]
– (IBAction)playSound:(id)sender {
NSLog(@"playSound");
NSString* path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"ding.caf"];
NSError* error;
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
[player play];
}
[/objc]
Your log will print playSound
, but you won’t hear a thing. Why? Notice that your playSound:
method allocates and initializes an AVAudioPlayer
, and then calls the play
method on that object. Do you think iOS stops what its doing and plays the sound, and then resumes? Hardly. That play
method queues a routine up to play the sound while the OS keeps on moving. Unfortunately, by the time it gets around to playing, your code has moved on and lost the reference to the player
object, so ARC (automatic reference counting) is going to release it. The end result? No sound.
Well, let’s fix that, by creating us an AVAudioPlayer
that sticks around. Our view controller will now have a strong reference to an AVAudioPlayer
, and will also serve as an AVAudioPlayer
delegate, so we can receive updates about the playback of our sound, if we choose to do something when the playback completes (or errors). At the top of the ViewController.m
file:
[objc]
@interface ViewController () <AVAudioPlayerDelegate>
@property (nonatomic, strong) AVAudioPlayer* player;
@end
[/objc]
Now, in our playSound:
method:
[objc]
– (IBAction)playSound:(id)sender {
NSString* path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"ding.caf"];
NSError* error;
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
self.player.delegate = self;
[self.player play];
}
[/objc]
To receive information about playback completion, go ahead and implement (in the ViewController
of course, which is your AVAudioPlayerDelegate
):
[objc]
– (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if (flag) {
NSLog(@"audioPlayerDidFinishPlaying successfully");
}
}
[/objc]
Note that there are more delegate methods, and you should review the AVAudioPlayerDelegate
documentation.
And that’s really about it for playing a simple sound! Run your app and press the Play Sound button and you should get a nice little ding!
In the XCode project on Github you will notice the following at the top of the ViewController
file:
[objc]
// Comment this line out to do a test run with an incorrect implementation
// that causes the AVAudioPlayer to be lost to ARC
#define CORRECT_IMPLEMENTATION
[/objc]
If you comment out the line #define CORRECT_IMPLEMENTATION
you’ll experience the joys of getting no sound when you press the Play Sound. Give it a try as a reminder that your AVAudioPlayer
object needs to be retained by your view controller.