AVAudioPlayer makes easy to playback the audio data from local files and memory. It is built on top of Core Audio’s C-based Audio Queue Service. It provides all core functions we can find in Audio Queue Service.

Things that AVAudioPlayer can’t do:

  1. Play audio from a network stream
  2. Access the raw audio sample
  3. Require very low latency

Create AVAudioPlayer

The AVAudioPlayer can be created by NSData or NSURL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Initializer for AVAudioPlayer
- (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;
- (nullable instancetype)initWithData:(NSData *)data error:(NSError **)outError;

// Example:
// A function to create an AVAudioPlayer with local audio file name and its extension
- (AVAudioPlayer *)playerFileName:(NSString *)name withExtension:(NSString *)extension {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:name withExtension:extension];
NSError *err;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&err];

// Configure the player
if (player) {
player.numberOfLoops = -1; // infinite loop
player.enableRate = YES;
[player prepareToPlay];
} else {
NSLog(@"%@", [err localizedDescription]);
}

return player;
}

Controlling Playback

AVAudioPlayer provides ways for us to modify the player’s volume, panning, playback rate, number of looping and preform audio metering.
Calling play() will play a sound asynchronously. It will implicitly call the prepareToPlay() if the audio player is not already prepared to play. We can call the prepareToPlay() method after we created a audio player to minimize the lag between calling the play() method. Calling stop() method will stop the playback and undo the setup from prepareToPlay().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* set the audio player’s stereo pan position, value from -1.0 to 1.0. A value of -1.0 is full left, 0.0 is center, 1.0 is full right. */
@property float pan;

/* set the audio player’s volume, value from 0.0 to 1.0. */
@property float volume;

/* set the audio player’s playback rate. Default value is 1.0, value 0.5 is half-speed playback, 2.0 is double-speed playback. To set playback rate, we need to enable rate adjustment. */
@property float rate;
/* default is NO */
@property BOOL enableRate;

/* set number of times a sound will return to the beginning to repeat playback. Default value is 0, play once, a value of 1 plays twice, a negative value will play infinitely */
@property NSInteger numberOfLoops;

- (BOOL)prepareToPlay;
- (void)play;
- (BOOL)playAtTime:(NSTimeInterval)time;
- (void)pause;
- (void)stop;

Audio Level Metering

AVAudioPlayer doesn’t meter the audio-level by default. We can turn it on and get average sound decibel and peak decibel.

1
2
3
4
5
6
7
/* default is not enabled */
@property(getter=isMeteringEnabled) BOOL meteringEnabled;

/* Refreshes the average and peak power values for all channels of an audio player. For best results, call this function in CADisplayLink */
- (void)updateMeters;
- (float)peakPowerForChannel:(NSUInteger)channelNumber;
- (float)averagePowerForChannel:(NSUInteger)channelNumber;

Configure AVAudioSession

In order to make our app playback sound when we lock the screen or we turn our cellphones to silent mode, we need to choose the correct AVAudioSessionCategory to AVAudioSession.
AVAudioSession is a global singleton instance. We can configure it after app launched. There are several categories. https://developer.apple.com/documentation/avfoundation/avaudiosession/audio_session_categories

Category Allows Mixing Audio Input Audio Output When Screen Lock When Silent
Ambient yes no yes off off
Solo Ambient no no yes off off
Playback optional no yes on on
Record no yes no on on
Play and Record optional yes yes on on
Multi-Route no yes yes on on
1
2
3
4
5
6
7
8
9
10
11
12
// Configure AVAudioSession
AVAudioSession *session = [AVAudioSession sharedInstance];

NSError *error;
// use AVAudioSessionCategoryPlayback to make the app playback sound when screen locked and silent turned on
if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category Error: %@", [error localizedDescription]);
}

if (![session setActive:YES error:&error]) {
NSLog(@"Activation Error: %@", [error localizedDescription]);
}

In order to make the app play in the background, we also need change the info.plist file. Add following code to the file.

1
2
3
4
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>

Adding this setting specifies that the application is now allowed to play audio in the background.

Handle Interruption

When we got a call or alarm, our app will automatically pause the sound, but it will not play the sound after the interruption. We can listen the AVAudioSessionInterruptionNotification notification and code the behavior we want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// add notification for audio player controller
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleInterruption:)
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];

- (void)handleInterruption:(NSNotification *)notification
{
AVAudioSessionInterruptionType type = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
// do something when interruption began
}else if (type == AVAudioSessionInterruptionTypeEnded) {
// do something when interruption ended
}
}

Handle Route Change

When we switch the audio output route, iOS will send a route change notification. Suppose we want to stop the playback when users unplug their headphone, we can check the notification’s userInfo dictionary for the route change reason and for a description of the previous audio route. If previous portType is AVAudioSessionPortHeadphones, we stop the playback.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Note This is not calling from main thread
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];

- (void)handleRouteChange:(NSNotification *)notification {

AVAudioSessionRouteChangeReason reason = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *preRoute = notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
NSString *portType = [[preRoute.outputs firstObject] portType];
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
// stop the playback
}
}
}

AVAudioPlayer Delegate

1
2
3
4
// Called when audioplayer finished playing.
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
// Called when an audio player encounters a decoding error during playback.
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error;

Here is the link for the test app: AVAudioPlayer Test App

ScreenshotScreenshot

AVFoundation Developer Guide