Tutorial: Playing Audio with AVAudioPlayer

Screenshot of IDZAudioPlayerTutorial App

Screenshot of IDZAudioPlayerTutorial App


The past few tutorials dealt with compiling the open source Ogg and Vorbis libraries. In the next few tutorials I will demonstrate how to play Ogg Vorbis files using these libraries.

Before attempting to play an unsupported file format like Ogg Vorbis, it make sense to first understand how a supported file format can be played, so I will start off by showing how to play supported audio formats using AVAudioPlayer.

Since the UI for this tutorial is a little busy and this is not, after all, a tutorial about using Interface Builder I will provide a starting point project with the UI already constructed: IDZAudioPlayerTutorial0.zip

Creating an instance of AVAudioPlayer

To use AVAudioPlayer you need to import AVFoundation.h. Edit IDZViewController.h and add the following line:

#import <AVFoundation/AVFoundation.h>

Next open IDZViewController.m.

Add a property to the class extension to hold your a reference to your player instance:

@interface IDZViewController ()
@property (nonatomic, strong) AVAudioPlayer* player;
@end

Keeping private properties and methods avoids the interface in your header file become cluttered with details a user of the class need not know.

Add a synthesize statement for this property:

@synthesize player = mPlayer;

In Xcode 4.5 you do not need to add a synthesize statement, but I still like to do it to explicitly give the property and instance variable distinct names.

The downloaded project contains a file Rondo_Alla_Turka_Short.aiff. Add the following code to create a player loaded with this file.

- (void)viewDidLoad
{
    [super viewDidLoad]; 
    // Do any additional setup after loading the view, typically from a nib.  
    NSURL* url = [[NSBundle mainBundle] URLForResource:@"Rondo_Alla_Turka_Short" withExtension:@"aiff"];
    NSAssert(url, @"URL is valid."); 
    NSError* error = nil;
    self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&amp;error];
    if(!self.player)
    {
        NSLog(@"Error creating player: %@", error);
    }
}

Also add the following line somewhere in viewDidUnload:

    self.player = nil;

Finally update the play, pause and stop methods as shown:

- (IBAction)play:(id)sender {
    IDZTrace();
    [self.player play];
}

- (IBAction)pause:(id)sender {
    IDZTrace();
    [self.player pause];
}

- (IBAction)stop:(id)sender {
    IDZTrace();
    [self.player stop];
}

If you compile and run the project you should not be able to tap the Play button and the music will start playing.

Initializing the UI Controls

The duration and numberOfChannels will not change during playback so they can be set just after the player has been created. This is also a good time to set the minimumValue and maximumValue of the currentTimeSlider. To do this add the following code to the end of viewDidLoad:

    self.durationLabel.text = [NSString
        stringWithFormat:@&quot;%.02fs&quot;,self.player.duration];
    self.numberOfChannelsLabel.text = [NSString 
        stringWithFormat:@&quot;%d&quot;, self.player.numberOfChannels];
    self.currentTimeSlider.minimumValue = 0.0f;
    self.currentTimeSlider.maximumValue = self.player.duration;

Updating the Controls During Playback

A timer can be used to periodically update the display when the audio is playing. The timer should be started when the user taps the Play button.

Add a property to the class extension for the timer:

@interface IDZViewController ()
@property (nonatomic, strong) AVAudioPlayer* player;
@property (nonatomic, strong) NSTimer* timer;
@end 

Also add a synthesize statement near the top of the implementation (you can skip this on Xcode 4.5):

@synthesize timer = mTimer;

Now modify your play method:

- (IBAction)play:(id)sender 
{
    IDZTrace();
    [self.player play];
    self.timer = [NSTimer 
        scheduledTimerWithTimeInterval:0.1 
        target:self selector:@selector(timerFired:) 
        userInfo:nil repeats:YES];               
}

Lines 5-8 create a timer that will call a method timerFired every 0.1 seconds.

Implement timerFired:

- (void)timerFired:(NSTimer*)timer
{
    [self updateDisplay];
}

You’ll implement updateDisplay a little later. Let’s finish with the timer first.

Declare a stopTimer method in the class extension:

- (void)stopTimer;

Add the method to the implementation:

- (void)stopTimer
{
    [self.timer invalidate];
    self.timer = nil;
}

Line 3 stops the timer from firing and line 4 signals to ARC that the timer is no longer needed.

The timer must be stopped when:

  • the user presses pause,
  • the user presses stop,
  • playback stops at the end of file, or
  • playback stops due to an error.

The first two cases can easily dealt with by modifying the pause and stop methods:

- (IBAction)pause:(id)sender {
    IDZTrace();
    [self.player pause];
    [self stopTimer];
    [self updateDisplay];
}

- (IBAction)stop:(id)sender {
    IDZTrace();
    [self.player stop];
    [self stopTimer];
    [self updateDisplay];
}

The latter two cases can be dealt with using delegation and the AVAudioPlayerDelegate protocol.

The AVAudioPlayerDelegate Protocol.

To begin signal to the compiler that your view controller will be implementing the AVAudioPlayerDelegate protocol, open IDZViewController.h and add the protocol to the interface definition.

@interface IDZViewController : UIViewController&lt;AVAudioPlayerDelegate&gt;

Back in IDZViewController.m add the following method declarations to the class extension:

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player 
        successfully:(BOOL)flag;
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player 
        error:(NSError *)error;

And add implementations:

#pragma mark - AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    NSLog(@&quot;%s successfully=%@&quot;, __PRETTY_FUNCTION__, flag ? @&quot;YES&quot;  : @&quot;NO&quot;);
    [self stopTimer];
    [self updateDisplay];
}

- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
{
    NSLog(@&quot;%s error=%@&quot;, __PRETTY_FUNCTION__, error);
    [self stopTimer];
    [self updateDisplay];
}

We’re now finished with the timer, time to turn our attention back the updateDisplay method.

Updating the UI Controls

Add a declaration to the class extension:

- (void)updateDisplay
- (void)updateSliderLabels

The implementation is straightforward:

#pragma mark - Display Update
- (void)updateDisplay
{
    NSTimeInterval currentTime = self.player.currentTime;
    NSString* currentTimeString = [NSString stringWithFormat:@"%.02f", currentTime];
    
    self.currentTimeSlider.value = currentTime;
    [self updateSliderLabels];
    
    self.currentTimeLabel.text = currentTimeString;
    self.playingLabel.text = self.player.playing ? @"YES" : @"NO";
    self.deviceCurrentTimeLabel.text = [NSString stringWithFormat:@"%.02f", self.player.deviceCurrentTime];
}

- (void)updateSliderLabels
{
    NSTimeInterval currentTime = self.currentTimeSlider.value;
    NSString* currentTimeString = [NSString stringWithFormat:@"%.02f", currentTime];
    
    self.elapsedTimeLabel.text =  currentTimeString;
    self.remainingTimeLabel.text = [NSString stringWithFormat:@"%.02f", self.player.duration - currentTime];
}

The reason for using a separate method to update the slider labels is that when you implement seeking you will want to be able to update them separately from other display elements.

You should now be able to compile and run the project, and all the UI controls should update as the audio is playing.

Implementing Seeking

A seek feature can be added by completing the implementation of two methods that were included in the skeleton project.

The currentTimeSliderValueChanged method is connected in the XIB file to the Value Changed action for the horizontal slider. Add the following lines:

- (IBAction)currentTimeSliderValueChanged:(id)sender
{
    if(self.timer)
        [self stopTimer];
    [self updateSliderLabels];
}

This code stops the display update timer so that the user’s dragging is not being contradicted by the current position and also updates the slider labels so the user knows where they are dragging. You could also stop playback if you wanted to, but I prefer letting it continue until the user releases the slider.

When the user releases the slider, a Touch Up Inside action is generated. In the skeleton project this action is connected to the currentTimeSliderTouchUpInside: method. The following shows the completed implementation:

- (IBAction)currentTimeSliderTouchUpInside:(id)sender
{
    [self.player stop];
    self.player.currentTime = self.currentTimeSlider.value;
    [self.player prepareToPlay];
    [self play:self];
}

This stops playback, sets the player’s currentTime property to the value of the slider, and restarts playback from that position.

Download the Source Code

You can download the complete source code for the project here: IDZAudioPlayerTutorial1.zip


About idz

A professional software engineer dabbling in iOS app development.
This entry was posted in Tutorial and tagged , . Bookmark the permalink.

13 Responses to Tutorial: Playing Audio with AVAudioPlayer

  1. dalexsoto says:

    Totally waiting for your next tutorial on implementing a custom AVPlayer 🙂 hope you will make it soon

    Thanks

    Alex

    • idz says:

      Hi Alex, thanks for the feedback. I really am hoping to write up the custom AVAudioPlayer soon. I am traveling at the moment and am finding it difficult to get time to sit down and write. Also, sometimes my access to internet connections is not so good.
      Cheers,
      idz

  2. dalexsoto says:

    Heeeyy!!! Hello IDZ long time no reading from you, Happy new year!! hope you are ok and my best wishes to you in this brand new year!!

    Now that we survived the mayan apocalypse I hope you get time to make that custom AVPlayer post you talked about 🙂

    Cheers.

    Alex

    • idz says:

      ¡Feliz Año Nuevo, Alex!

      Yes, life has really got in the way of blogging in the last few months. For the few weeks I was at home, during the holidays, we were moving and now I am traveling again.

      Luckily here I have good internet connections and some quiet places to work, so hopefully I will do some blogging soon.

      Thanks for your encouragement, I hope you have a great 2013 too.

      idz

  3. macca says:

    Hi Idz

    Your post are very informative and helpful.

    I already own an app which uses open source code from vlc to convert the video and audio, but I am looking to better the code as some videos which play on iphone, don’t play on ipad.

    I am looking for a team of experts to build me an app to support video and audio for ios5 and ios6 and support all devices. The app needs to support the “Open In…” feature (I have the code for this).

    The issue I am finding with using vlc open source code is that it doesnt support ios6 properly and keeps crashing when users open the app. Also I am finding certain videos that play well on iPhone, the same video doesnt play the sound on iPad 2,3 and mini)

    I have another developer currently working on the vlc issue but then I came across your comments and was wondering if you would be interested in developing a stable media player…?

    You can contact me at malcolmflinn@gmail.com

    Thanks

  4. Pingback: Développement iOS | Pearltrees

  5. gamiyol says:

    Great zone for developers, continue the good work! Start an amazing Audio Player for IOS instantly by using IOS MUSIC PLAYER CODE SCRIPT – [REDACTED BY iOS Developer Zone].

  6. BreizhReloaded says:

    Hi Idz,

    Thx for your work, it really helped me. But now I have an issue trying to play a ogg file on a 64 bit device. My current workaround is to change the architectures of my app project from armv7 and arm64 to armv7. Where could I be wrong?

    • BreizhReloaded says:

      Here is a sample of what I get: “Undefined symbols for architecture arm64” for functions ov_time_seek, ov_clear, ov_read, ov_time_total, ov_open_callbacks and ov_info. It seems that Ogg.framework and Vorbis.framework I found on your GitHub are not compiled for arm64: “missing required architecture arm64”.

  7. aparna says:

    please tell me, how can we add numberofloops in IDZAudioplayer ?
    Thanks in Advance

Leave a Reply