12

I am trying to play an MP3 via AVAudioPlayer which I thought to be fairly simple. Unfortunately, it's not quite working. Here is all I did:

  • For the sake of testing, I created a new iOS application (Single View) in Xcode.
  • I added the AVFoundation framework to the project as well as the #import <AVFoundation/AVFoundation.h> to the ViewController.m

  • I added an MP3 File to the Apps 'Documents' folder.

  • I changed the ViewControllers viewDidLoad: to the following:

Code:

- (void)viewDidLoad
{
    [super viewDidLoad];        

    NSString* recorderFilePath = [NSString stringWithFormat:@"%@/MySound.mp3", [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]];    

    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:recorderFilePath] error:nil];
    audioPlayer.numberOfLoops = 1;

    [audioPlayer play];

    //[NSThread sleepForTimeInterval:20];
}

Unfortunately, the audio obviously stops right after it starts playing. If I uncomment the sleepForTimeInterval it plays for 20 seconds and stops afterwards. This problem occurs only when compiling with ARC, otherwise, it works flawlessly.

Philipp Schlösser
  • 5,179
  • 2
  • 38
  • 52
  • You don't need to start another thread to play audio. Have you tried providing an error pointer to see if anything's being logged? – isaac Oct 07 '11 at 21:09
  • Yes, I passed an NSError to initWithContensOfURL. It stays nil, though... So I don't get any errors, it just stops playing immediately. – Philipp Schlösser Oct 07 '11 at 21:12
  • 3
    Do you compile with [ARC](http://clang.llvm.org/docs/AutomaticReferenceCounting.html)? Seams to be the only reasonable way i can think of that would cause the player to be released and dealloced. – Mattias Wadman Oct 07 '11 at 21:40
  • Good thinking. I will investigate this further. Due to the NDA, I will have to go the the Dev Forum in that case, I guess... – Philipp Schlösser Oct 07 '11 at 22:02
  • Ok! so ARC is not release yet? i haven't used it myself. But after iOS 5 is released you can comment if ARC was the cause of the problem? – Mattias Wadman Oct 07 '11 at 22:09
  • Thanks again, Mattias. I solved it now and once 5.0 is out, I'll provide further information. This is kind of odd... – Philipp Schlösser Oct 07 '11 at 23:17
  • 3
    ARC isn't under NDA. (learned that here) Just to ask how you could have solved it or where the problem was: After viewDidLoad is done nothing is pointing to your audioPlayer so ARC releases it. You probably made an ivar/property for it. Could that work? – yinkou Oct 12 '11 at 17:31
  • 1
    Yes, that works. Proper answer is on it's way :) – Philipp Schlösser Oct 12 '11 at 17:36

3 Answers3

7

The problem is that when compiling with ARC you need to make sure to keep a reference to instances that you want to keep alive as the compiler will automatically fix "unbalanced" alloc by inserting release calls (at least conceptually, read Mikes Ash blog post for more details). You can solve this by assigning the instance to a property or a instance variable.

In Phlibbo case the code will be transformed into:

- (void)viewDidLoad
{
    [super viewDidLoad];        
    NSString* recorderFilePath = [NSString stringWithFormat:@"%@/MySound.mp3", [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]];    
    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:recorderFilePath] error:nil];
    audioPlayer.numberOfLoops = 1;
    [audioPlayer play];
    [audioPlayer release]; // inserted by ARC
}

And the AVAudioPlayer it will stop playing immediately as it gets deallocated when no reference is left.

I haven't used ARC myself and have just read about it briefly. Please comment on my answer if you know more about this and I will update it with more information.

More ARC information:
Transitioning to ARC Release Notes
LLVM Automatic Reference Counting

Mattias Wadman
  • 11,172
  • 2
  • 42
  • 57
  • I don't use ARC and I tried to play short sounds the same way you write. I hear only the first sound (and it gives thanks to a bug with AVAudioPlayer with the first time playing some sound). So at best your code cuts the first seconds of an audio, at worst it stops playing before sound will be stopped by itself. – Gargo Apr 27 '12 at 09:10
  • @Gargo: As, Mattias writes in his post, the code explains why the player didn't work in my case, it is intentionally faulty. You should look at an AVAudioPlayer tutorial, if you have no experience with it. – Philipp Schlösser Apr 27 '12 at 14:16
  • @Gargo if you included the `[audioPlayer release]; // inserted by ARC` line you will most probably get a very short sound or no sound at all (i think AVAudioPlayer uses threads?) as the player gets released directly. And if you don't include that line you will get sound but also leak one `AVAudioPlayer` instance each time. Note also that `autorelease` wont work. You need to wait until the sound is done playing (using the delegate protocol) or another option is to reuse the same player. Hope that helps. – Mattias Wadman Apr 27 '12 at 15:51
3

If you need multiple AVAudioPlayers playing at the same time, create a NSMutableDictionary. Set the key as the filename. Remove from Dictionary via Delegate Callback like so:

-(void)playSound:(NSString*)soundNum {


    NSString* path = [[NSBundle mainBundle]
                      pathForResource:soundNum ofType:@"m4a"];
    NSURL* url = [NSURL fileURLWithPath:path];

    NSError *error = nil;
    AVAudioPlayer *audioPlayer =[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];

    audioPlayer.delegate = self;

    if (_dictPlayers == nil)
        _dictPlayers = [NSMutableDictionary dictionary];
    [_dictPlayers setObject:audioPlayer forKey:[[audioPlayer.url path] lastPathComponent]];
    [audioPlayer play];

}

-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {        
   [_dictPlayers removeObjectForKey:[[player.url path] lastPathComponent]];
}
jaysquared.com
  • 216
  • 1
  • 3
  • This is the best solution to play sounds simultaneously. If you use AVAudioPlayer as a property than the object will release when you will play the next sound and some people are using AudioServicesPlaySystemSound but apple are rejecting apps because it will still play when the device is muted and there's no api to check if the device is muted. – iC7Zi Oct 29 '15 at 04:16
3

Use the AVAudioPlayer as an ivar in the header file with strong:

@property (strong,nonatomic) AVAudioPlayer *audioPlayer
Pang
  • 9,564
  • 146
  • 81
  • 122
fraktal
  • 71
  • 2