1

I want to play background music in my game (ideally i'd like it to fade in / out but I want it playing first)

I have looked at various answers on SO and my code seems to look correct but for some reason the file isnt playing, in fact it crashes (with no useful output) on the AVAudioPlayer *player line. If I continue it crashes again on the [player play] line but error is 0.

Updated... again

GameViewController.h

@property (nonatomic, retain) AVAudioPlayer *player;

GameViewController.m

@synthesize player; // the player object

ViewDidLoad:

NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: @"bgtrack" ofType: @"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];

player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error: nil]; // breaks here
[player prepareToPlay];

I am including

#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

Updates:

As per comments I tried copying / pasting the code directly from the Apple Dev center. I am using the latest XCode 4 and iOS 6.1 simulator.

When it crashes I get no information bar the fact it hits a breakpoint. The file is included in the bundle

Chris
  • 26,744
  • 48
  • 193
  • 345

2 Answers2

3

You need to store a strong reference to player (perhaps in an ivar of your view controller or something). If you just put that code into the -viewDidLoad method or something without making a strong reference to it, what'll happen is that the player will be deallocated before it has a chance to start playing. For example:

@interface MyViewController : UIViewController

@end

@implementation MyViewController
{
    AVAudioPlayer* player;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:@"YourMP3FileName" ofType:@"mp3"];
    NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
    NSError *error;
    player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:&error];
    player.numberOfLoops = -1; //infinite
    player.currentTime = 139;// 2:19;

    [player play];
}

@end

That works fine for me, but if player is not an ivar of the view controller I get no sound.

Alternately, I whipped up the following class that will work as a "one-shot" AVAudioPlayer. This obviously presents a problem for cases where numberOfLoops is -1 (infinite) since that means it will never stop playing or go away, but it might make things slightly easier in the case where you want something to play once and then go away without needing to keep a reference to it around.

OneShotAVAudioPlayer.h

#import <AVFoundation/AVFoundation.h>

@interface OneShotAVAudioPlayer : AVAudioPlayer
@end

OneShotAVAudioPlayer.m

#import "OneShotAVAudioPlayer.h"
#import <AVFoundation/AVFoundation.h>
#import <objc/runtime.h>

@interface OneShotAVAudioPlayer () <AVAudioPlayerDelegate>
@property(weak) id<AVAudioPlayerDelegate> p_exogenousDelegate;
@end

@implementation OneShotAVAudioPlayer

static void * const OneShotAVAudioPlayerKey = (void*)&OneShotAVAudioPlayerKey;

- (id)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError
{
    if (self = [super initWithContentsOfURL:url error:outError])
    {
        // Retain ourself
        objc_setAssociatedObject(self, OneShotAVAudioPlayerKey, self, OBJC_ASSOCIATION_RETAIN);
        [super setDelegate: self];
    }
    return self;
}

- (id)initWithData:(NSData *)data error:(NSError **)outError;
{
    if (self = [super initWithData:data error:outError])
    {
                    // Retain ourself
        objc_setAssociatedObject(self, OneShotAVAudioPlayerKey, self, OBJC_ASSOCIATION_RETAIN);
        [super setDelegate: self];
    }
    return self;
}

- (void)setDelegate:(id<AVAudioPlayerDelegate>)delegate
{
    self.p_exogenousDelegate = delegate;
}

- (id<AVAudioPlayerDelegate>)delegate
{
    return self.p_exogenousDelegate;
}

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    @try
    {
        if ([self.p_exogenousDelegate respondsToSelector: _cmd])
            [self.p_exogenousDelegate audioPlayerDidFinishPlaying:player successfully:flag];
    }
    @finally
    {
        // Make a strong ref so we stay alive through the scope of this function
        typeof(self) keepAlive = self;
        // Give up the self retain
        objc_setAssociatedObject(keepAlive, OneShotAVAudioPlayerKey, nil, OBJC_ASSOCIATION_RETAIN);
        // Push in the "real" (outside) delegate, cause our job is done here.
        [super setDelegate: self.p_exogenousDelegate];
    }
}

- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
{
    @try
    {
        if ([self.p_exogenousDelegate respondsToSelector: _cmd])
            [self.p_exogenousDelegate audioPlayerDecodeErrorDidOccur:player error:error];
    }
    @finally
    {
        // Make a strong ref so we stay alive through the scope of this function
        typeof(self) keepAlive = self;
        // Give up the self retain
        objc_setAssociatedObject(keepAlive, OneShotAVAudioPlayerKey, nil, OBJC_ASSOCIATION_RETAIN);
        // Push in the "real" (outside) delegate, cause our job is done here.
        [super setDelegate: self.p_exogenousDelegate];
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    BOOL retVal = [super respondsToSelector: aSelector];
    if (!retVal)
    {
        struct objc_method_description method = protocol_getMethodDescription(@protocol(AVAudioPlayerDelegate), aSelector, YES, YES);
        if (method.name)
        {
            retVal = [self.p_exogenousDelegate respondsToSelector: aSelector];
        }
    }
    return retVal;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    id retVal = [super forwardingTargetForSelector:aSelector];
    if (!retVal)
    {
        struct objc_method_description method = protocol_getMethodDescription(@protocol(AVAudioPlayerDelegate), aSelector, YES, YES);
        if (method.name && [self.p_exogenousDelegate respondsToSelector: aSelector])
        {
            retVal = self.p_exogenousDelegate;
        }
    }
    return retVal;
}

- (void)setNumberOfLoops:(NSInteger)numberOfLoops
{
    if (numberOfLoops < 0)
    {
        NSLog(@"Warning! You have set an infinite loop count for an instance of %@ (%p). This means the instance will effectively be leaked.", NSStringFromClass([self class]), self);
    }
    [super setNumberOfLoops: numberOfLoops];
}

@end

Posted here as a gist.

ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • Thanks, this is a good suggestion but it still crashes when I do your suggestion. I seem to get no output or reason why it happened though? – Chris Aug 24 '13 at 13:18
  • What is the backtrace of the exception/crash? (Just edit the question -- it's impossible to read a backtrack in an SO comment) – ipmcc Aug 24 '13 at 13:48
  • I get no exception information which is strange, it just halts execution and says nothing more. – Chris Aug 25 '13 at 10:46
0

Promote the player ivar to a strong property and log the [error description] if player is nil.

If it is crashing something definitely is wrong. Did you add an exception breakpoint?

Rob van der Veer
  • 1,148
  • 1
  • 7
  • 20
  • I have currently defined it in the `@interface` like so `@property (strong, nonatomic) AVAudioPlayer *BackgroundMusicPlayer;` but still doesnt work. No error message :/ – Chris Aug 25 '13 at 10:43
  • Ive got a breakpoint for all exceptions but I dont get any information as to what actually happened :/ – Chris Aug 25 '13 at 10:46
  • As i commented on top, post your code, try the sample code, change the mp3, check the volume, do a little homework. – Rob van der Veer Aug 25 '13 at 11:33