AVAudioPlayer works once - iphone

Trying to create a playlist of music files that are NOT part of the user's iphone library so I'm using AVAudioPlayer and creating the playlist functionality myself. It works on the first pass (meaning the first song is played). When the first song finishes and it goes to play the 2nd AVAudioPlayer crashes in prepareToPlay.
- (void) create {
Song *song;
NSArray *parts;
NSString *path;
NSString *name;
NSError *err;
if (currentIndex > [queue count])
currentIndex = 0;
if (currentIndex < 0)
currentIndex = ([queue count] - 1);
song = (Song *) [queue objectAtIndex:currentIndex];
name = song.fileName;
parts = [name componentsSeparatedByString:#"."];
path = [[NSBundle mainBundle] pathForResource:[parts objectAtIndex:0] ofType:[parts objectAtIndex:1]];
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &err];
[[AVAudioSession sharedInstance] setActive: YES error: &err];
appPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: url error: &err];
[url release];
[parts release];
[appPlayer prepareToPlay];
[appPlayer setVolume: 1.0];
[appPlayer setDelegate: self];
}
- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL) flag {
[appPlayer stop];
[appPlayer release];
appPlayer = nil;
[self next];
}
The call to [self next] just increases currentIndex and calls create again.
The only other thing (I know, the infamous "only other thing" statement) is that this is all going on inside a Singleton. There's lots of things that could cause the music to start and stop playing, change the queue, etc so I thought it would be best to wrap it all up in one spot.
Any thoughts?

I was able to track this down myself. I re-wrote the whole section to have multiple AVAudioPlayer objects but still had issues. Turns out the [parts release] line was the issue, it was causing an over-release situation in the autorelease routine.

One of the reason of having exception in prepareToPlay method is enabled "All Exceptions" breakpoint. Go to Breakpoint Navigator and disable it.

Related

AVAudioPlayer behaviour handling issue

I have been trying to learn AVAudioPlayer and it's behaviour.
So to start with I had written
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
player.numberOfLoops = -1;
[player play];
It did worked well as long as application did not enter background. So I searched again and found that I need to add entry in plist and also have written following code
NSError *activationError = nil;
if([[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&activationError] == NO)
NSLog(#"*** %# ***", activationError);
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
Now my sample application plays sound in background as well. Then I encountered another problem. If I put sleep before all this processing, take my application in background during the sleep and start playing music from some other app, in this case my application is not able to play sound. It is probably because it does not get the audio session which some other music application has taken.
Questions
1. I understand that there is a way to detect if any other sound is playing, but I could not find how do I stop it. There are methods for stopping standard app sound like iPod, but is there any generic method to stop the sound from any other application.
2. [player play]; gives NO as result which is failure. How do I find what failure has caused. I have implemented audioPlayerDecodeErrorDidOccur:error: method, but it is never called.
Please share your knowledge on this. I have already gone through most of the stack overflow questions regarding the same, but did not find anything useful to this particular problem.
-(void)loadPlayer
{
NSURL *audioFileLocationURL = [NSURL URLWithString:[[NSBundle mainBundle] URLForResource:#"01-YedhiYedhi[www.AtoZmp3.in]" withExtension:#"mp3"]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileLocationURL error:&error];
[audioPlayer setNumberOfLoops:-1];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
[[self volumeControl] setEnabled:NO];
[[self playPauseButton] setEnabled:NO];
[[self alertLabel] setText:#"Unable to load file"];
[[self alertLabel] setHidden:NO];
}
else
{
[[self alertLabel] setText:[NSString stringWithFormat:#"%# has loaded", str]];
[[self alertLabel] setHidden:NO];
//Make sure the system follows our playback status
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
//Load the audio into memory
[audioPlayer prepareToPlay];
}
}
if (!self.audioPlayer.playing) {
[self playAudio];
} else if (self.audioPlayer.playing) {
[self pauseAudio];
}
}
- (void)playAudio {
[audioPlayer play];
}

EXC_BAD_ACCESS after audioPlayerDidFinishPlaying: called

I have a class that I call to utilize AVAudioPlayer and everything works fine and dandy when it comes to playing the audio, but when the -audioPlayerDidFinishPlaying: is called my NSLog() command says that the player is released; the problem is that the app crashes moments later. I should mention that audioPlayer is an ivar in this class. Here is the code:
-(id) initWithFileName:(NSString *)sndFileName
{
[super init];
sndFileToPlay = [[NSString alloc] initWithString:sndFileName];
return self;
}
-(void)dealloc {
[audioPlayer release];
self.audioPlayer.delegate = nil;
self.audioPlayer = nil;
[super dealloc];
}
-(void)play
{
[self playSound:sndFileToPlay];
}
-(void)playSound:(NSString *)fileName
{
NSString *fname, *ext;
NSRange range = [fileName rangeOfString:#"."];
int location = range.location;
if( location > 0 )
{
fname = [fileName substringWithRange:NSMakeRange(0, location)];
ext = [fileName substringFromIndex:location+1];
[self playSound:fname :ext];
}
}
—
-(void)playSound:(NSString *)fileName :(NSString *)fileExt
{
NSBundle *mainBundle = [NSBundle mainBundle];
NSURL *fileURL = [NSURL fileURLWithPath:
[mainBundle pathForResource:fileName ofType:fileExt] isDirectory:NO];
if (fileURL != nil)
{
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
[audioPlayer setDelegate:self];
[audioPlayer play];
}
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
successfully:(BOOL)flag
{
NSLog(#"Releasing");
[audioPlayer release];
}
There are several things wrong with your code.
For one, in your dealloc:
[audioPlayer release];
self.audioPlayer.delegate = nil;
self.audioPlayer = nil;
You are releasing the audioPlayer, then, on the released (and maybe deallocated) player you set the delegate to nil and then the property, which releases it again. Remove the [audioPlayer release];.
In your audioPlayerDidFinishPlaying:successfully: you're releasing the player as well, but you haven't set the variable to nil. That might cause a crash since by the time you access this variable again a different object might be at that memory address. Use the property instead and do it like in your dealloc:
self.audioPlayer.delegate = nil;
self.audioPlayer = nil;
Then, in playSound:: (argh, non-named second argument !) you over-release fileURL. The -[NSURL fileURLWithPath:isDirectory:] returns an autoreleased object, you may not release it.
Last but maybe not least you leak sndFileToPlay, you need to release it in your dealloc method. And instead of sndFileToPlay = [[NSString alloc] initWithString:sndFileName]; simply do sndFileToPlay = [sndFileName copy];.
You might want to read up on Objective-C memory management. It's not hard once you know the three or four rules-of-thumb.
You should clean up your code. If playSound is called several times, you are leaking AVAudioPlayer.
In your dealloc, you should put [audioPlayer release] after the two lines beneath.
Turn on NSZombieEnabled to debug, and make sure that the audioPlayer is not released when didFinish is called.

MemoryLeak problem in following case

I am getting a memory leak when i click the play button....
I am testing with that "Leak" tool under "Run and performance tool"....on simulator
I am getting that leak when i click the play button first time.....
Here is my code....
-(IBAction)play
{
[self setPlayer];
[self playme];
}
-(IBAction)stop
{
[self stopme];
[self releasePlayer];
}
-(void)setPlayer
{
NSURL *file = [[NSURL alloc] initFileURLWithPath:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"shut up.mp3"]];
NSError *err = nil;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
[file release];
player.numberOfLoops = -1;
[player prepareToPlay];
player.volume=1.0;
}
-(void)playme
{
if (!isPlaying)
{
[player play];
isPlaying=YES;
}
}
-(void)stopme
{
if (isPlaying)
{
[player stop];
isPlaying=NO;
}
}
-(void)releasePlayer
{
if(!isPlaying)
{
[player release];
player=nil;
}
isPlaying=NO;
}
I think, the below statement is the source of memory leak,
player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
Here is the SO posts which has discussed the same issue.
AVAudioPlayer memory leak
AVAudioPlayer memory leak
AVAudioPlayer Memory Leak - Media Player Framework
Here is the blog post
AVAudioPlayer Memory Leak
EDITED:
As per the blog tutorial your code must be look like below.
-(void)setPlayer
{
NSURL *file = [[NSURL alloc] initFileURLWithPath:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"shut up.mp3"]];
NSError *err = nil;
NSData *data = [NSData dataWithContentsOfFile:file];
AVAudioPlayer *player = [AVAudioPlayer alloc];
if([player initWithData:audioData error:NULL])
{
player.numberOfLoops = -1;
[player prepareToPlay];
player.volume=1.0;
[player autorelease];
}
else
{
[player release];
player = nil;
}
[file release];
}
The leak-free version stores the pointer returned by alloc, rather than the pointer returned by initWithData:error:. That way, whatever happens, the player can still be released.
The blog post in Jhaliya's answer describes a leak that's specific to the situation when your player can't init the audio, for example when it can't find the file.
The real problem with your code is that you only release the player if the user explicitly stops the audio. If the audio plays through to the end, you have a player instance with a retainCount of 1. Then if the user hits play again, you create a new player and assign it to the player variable, leaking the old one.
The easiest solution to this is to make player a retained property:
#property(nonatomic,retain)AVAudioPlayer *player;
Then, instead of assigning to the ivar directly, use the mutator to set the player, which will implicitly release the previously set instance, if there is one:
[self setPlayer:[[[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err] autorelease];
And don't forget to release it in your dealloc:
-(void)dealloc {
[player release];
[super dealloc];
}

stopping an AVAudioPlayer

here is my code
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"95" ofType:#"wav"];
NSError *activationError = nil;
NSError *audioPlayerInitError = nil;
[[AVAudioSession sharedInstance] setActive: YES error:&activationError];
NSURL *newURL = [NSURL fileURLWithPath:soundFilePath];
musicPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:newURL error:&audioPlayerInitError];
if (musicPlayer1) {
[musicPlayer1 stop];
[musicPlayer1 release];
musicPlayer1 = nil;
}
else {
[musicPlayer1 prepareToPlay];
[musicPlayer1 setVolume:.8];
[musicPlayer1 setNumberOfLoops:-1]; // -1 means play indefintely
[musicPlayer1 setDelegate: self];
[musicPlayer1 play];
}
}
I am rying to start and stop the AVAudioPlay with the same button (musicPlayer1 is in the header). Now when i touch the button it does not play anything. Help please?
You're creating a new instance of AVAudioPlayer each time. You need to hold onto the reference to the object somewhere, and refer back to that. Add a field to your view controller class:
#private AVAudioPlayer *currentAudio;
And then in this method, make the following changes:
if (currentAudio) {
[currentAudio stop];
[currentAudio release];
currentAudio = nil;
} else {
// what you already have goes here
}

Sounds randomly interrupted when starting to play

I'm calling playSoundFromBundle from the code below to play sounds (aif files). I have a sound that does a single click and then a fading sound. Both sounds are in the same file. Sometimes I get two clicks and then the fade. Meaning, click, click...fade. A single click isn't what should play. I'm guessing the sound starts (click sound), gets interrupted and then restarts (full sound...click/fade) because of other processing that may be going on. It seems random when it will occur. I put the sound on its own thread to try and avoid the double clicking. Is there anything else I can do to ensure the sound plays correctly?
- (void) playSoundFromBundleThreaded:(NSArray*)arr{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *fileName = (NSString*)[arr objectAtIndex:0];
NSString *fileExt = (NSString*)[arr objectAtIndex:1];
NSError *err;
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource: fileName ofType: fileExt inDirectory:#"/"]] error: &err];
self.audioPlayer = newPlayer;
self.audioPlayer.numberOfLoops = 0;
self.audioPlayer.volume = .5;
if (self.audioPlayer == nil)
{
NSLog(#"Problem initializing Sound - %#", [err description]);
}
else
{
[self.audioPlayer play];
}
[newPlayer release];
[pool release];
}
- (void) playSoundFromBundle:(NSString*)fileName fileExtension:(NSString*)fileExt{
NSArray *arr = [NSArray arrayWithObjects:fileName, fileExt, nil];
[NSThread detachNewThreadSelector: #selector(playSoundFromBundleThreaded:) toTarget:self withObject:arr];
}
No idea if this would work, but check out the Audio Session stuff. It's intended to manage things like whether the playing of other sounds (such as by the music player) will interrupt audio from your application.