AVAudioPlayer causing memory leak - objective-c

Not sure if this is a simulator issue or I am missing something, the following code gives me a memory leak although theAudio is released as in dealloc. I am playing a .wav file and the sound will change depending on the value of an integer. The App runs ok but 16 bit leak is flagged whwn I test this in xcode
Any suggestion is greatly appreciated:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface Water : UIViewController
<AVAudioPlayerDelegate>
{
}
#property (nonatomic,retain) AVAudioPlayer *theAudio;
-(IBAction) goButMenu: (id) sender;
-(IBAction) goDrinkMenu: (id) sender;
-(IBAction) playwater;
#end
#implementation Water
#synthesize theAudio;
-(IBAction) goDrinkMenu: (id) sender{
ButDrinkMenu *butdrinkmenu1 = [[ButDrinkMenu alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:butdrinkmenu1 animated:YES];
}
-(void) viewDidLoad {
if (BoyOrGirl == 1) {
NSString *path = [[NSBundle mainBundle] pathForResource:#"Applause" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
else {
NSString *path = [[NSBundle mainBundle] pathForResource:#"bongo" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
}
-(IBAction) playwater{
if (BoyOrGirl == 1) {
NSString *path = [[NSBundle mainBundle] pathForResource:#"Applause" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
else {
NSString *path = [[NSBundle mainBundle] pathForResource:#"bongo" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
}

Definitely test on the device directly; testing for leaks with the simulator may return inaccurate results.
That said, there are several memory-related concerns with this code sample. You may wish to include your dealloc method here as that may offer some insight, but here's a problem (that appears in several of your allocations):
Your AVAudioPlayer isn't being assigned to your property, nor is it being released in scope. Ideally a player allocation would look more like:
AVAudioPlayer *myPlayer = [[AVAudioPlayer alloc] initWithContents....
[myPlayer setDelegate:self];
[self setTheAudio:myPlayer];
[myPlayer release];
As opposed to how you've implemented it, which assigns the player directly the instance variable:
theAudio = [[AVAudioPlayer alloc] initWith...
Because you assign the player directly to your instance variable, your player isn't being retained. This is likely to result in either premature release and a dangling pointer if you try and balance your calls properly, or it will result in leaking.

Related

Objective-C How to play a sound when a cell's button is tapped?

I have a button on each cell and while the button is tapped I want to play different audio file from array for each cell.
I don't know how to implement an array of sounds/audio files for my table view cells.
I have a button outlet and action on my UITableViewCell. But I am not sure how to initialise my AVaudioplayer in tableview.m specifically in:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell22";
Below I've inserted an image of the project I want to achieve.
image1
You can try like this:
Import AVFoundation.framework
Place this code in didSelectRowAtIndexPath method
NSString *soundPath = [[NSBundle mainBundle] pathForResource:#"your sound name" ofType:#"mp3"];
NSURL *soundUrl = [NSURL fileURLWithPath:soundPath];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
[player play];
Note: Add your audio files to project and then take the names into one array. Then you can use your audio names based on indexPath.
If you want to play sound on button click which is in tableViewCell then add tag as indexPath to that button and add action to button like this in cellForRowAtIndexPath method
button.tag = indexPath.row;
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
and paste the same code in button action. but use sender.tag instead of indexPath.row
-(void)buttonPressed:(UIButton *)sender
{
NSString *soundName = [yourArray objectAtIndex:sender.tag];
NSString *soundPath = [[NSBundle mainBundle] pathForResource:soundName ofType:#"mp3"];
NSURL *soundUrl = [NSURL fileURLWithPath:soundPath];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil]; [player play];
}
check now i updated code :its playing sound
1.create property in .h or .m file for ur AVAudioPlayer . like
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
declare <AVAudioPlayerDelegate> in .m file and
#synthesize audioPlayer;
3.implement this code in :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath{
yourSoundArray=[NSArray arrayWithObjects:#"sss",#"sss",#"sss",#"sss",#"sss",#"sss", nil];//dont include formate type
NSString *audioPath = [[NSBundle mainBundle] pathForResource: [yourSoundArray objectAtIndex:indexPath.row] ofType:#"mp3"];
NSLog(#"%#",audioPath);
NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
NSError *audioError = [[NSError alloc] init];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:&audioError];
audioPlayer.delegate=self;
if (!audioError) {
NSLog(#"playing!");
audioPlayer play];
}
else {
NSLog(#"Error!");
}
4.
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
NSLog(#"%d",playing the audio);
}
happy coding its working please check :
Mate You need to use two things mostly
1st import AVFounfation.framwork
2nd Do code id "didSelectRowAtIndexPath"
for more details
Xcode 6 - Press button to play sound
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *selectedCell=[tableView cellForRowAtIndexPath:indexPath];
NSLog(#"%#",selectedCell.textLabel.text);
AVAudioPlayer *audioPlayer101;
if ([_detailPhrases isEqualToString:#"Basics"]){
NSArray *soundArray = [NSArray arrayWithObjects:#"cellTapSound", #"Second", nil];
NSString *audioPath = [[NSBundle mainBundle] pathForResource: [soundArray objectAtIndex:indexPath.row] ofType:#".mp3"];
NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
NSError *audioError = [[NSError alloc] init];
audioPlayer101 = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:&audioError];
if (!audioError) {
[audioPlayer101 play];
NSLog(#"playing!");
}
else {
NSLog(#"Error!");
}
}
}

AVAudioPlayer iOS no sound

Seems to be similar questions asked but none of the solutions seem to work for me. Cant for the life of me understand why my code doesnt work so here it is.
I dont get any errors but also get no sound.
#interface MainApp ()
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
-(void)playSoundFX:(NSString*) soundFile;
#end
#implementation MainApp
- (void)viewDidLoad {
[super viewDidLoad];
// Play sound
[self playSoundFX:#“sound.mp3"];
}
// Play sound
- (void)playSoundFX:(NSString*)soundFile {
// Setting up path to file url
NSBundle* bundle = [NSBundle mainBundle];
NSString* bundleDirectory = (NSString*)[bundle bundlePath];
NSURL *url = [NSURL fileURLWithPath:[bundleDirectory stringByAppendingPathComponent:soundFile]];
// Make an audioPlayer object and play file
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[audioPlayer setVolume:100.0f];
if (audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[audioPlayer play];
}
#end
*********vvvvvvvvv Amended Code that works vvvvvvvvv**********
#interface MainApp ()
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
-(void)playSoundFX:(NSString*) soundFile;
#end
#implementation MainApp
- (void)viewDidLoad {
[super viewDidLoad];
// Play sound
[self playSoundFX:#“sound.mp3"];
}
// Play sound
- (void)playSoundFX:(NSString*)soundFile {
// Setting up path to file url
NSBundle* bundle = [NSBundle mainBundle];
NSString* bundleDirectory = (NSString*)[bundle bundlePath];
NSURL *url = [NSURL fileURLWithPath:[bundleDirectory stringByAppendingPathComponent:soundFile]];
// Make an audioPlayer object and play file
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[self.audioPlayer setVolume:100.0f];
if (self.audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[self.audioPlayer play];
}
#end
I am execute your code on my iPhone. It is work properly.Please Select a device and run your code. I hope it will execute properly with sound.
You got to segregate the functionalities..
Loading audio file in viewDidLoad and play it on play action, some thing like this
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#interface ViewController ()
{
AVAudioPlayer *_audioPlayer;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Construct URL to sound file
NSString *path = [NSString stringWithFormat:#"%#/sound.mp3", [[NSBundle mainBundle] resourcePath]];
NSURL *soundUrl = [NSURL fileURLWithPath:path];
// Create audio player object and initialize with URL to sound
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)playSoundTapped:(id)sender
{
// When button is tapped, play sound
[_audioPlayer play];
}

Sound stops working in soundboard (Xcode)

In my soundboard app, after some time the sound will stop working until the app is closed and opened again. Can't figure out what is wrong! here is my code:
In the .h file :
(imported files here)
#interface MainView : UIView {
}
- (IBAction)pushButton2:(id)sender;
#end
In the .m file:
(imported files here)
#implementation MainView
- (IBAction)pushButton2:(id)sender {
NSString *path = [[NSBundle mainBundle] pathForResource:#"sound1" ofType:#"mp3"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
#end
I'm not sure it would cause the behavior you're seeing, but you're definitely leaking the AVAudioPlayer each time the button is pressed. Additionally, I would just load it once (say, in viewDidLoad), rather than on each button press. Perhaps something like:
#interface MainView : UIView
{
AVAudioPlayer* audioPlayer;
}
- (IBAction)pushButton2:(id)sender;
#end
#implementation MainView
- (void) viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"sound1" ofType:#"mp3"];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
audioPlayer.delegate = self;
}
- (void) viewDidUnload
{
[audioPlayer release], audioPlayer = nil;
}
- (IBAction)pushButton2:(id)sender
{
[audioPlayer play];
}
#end

Why is this object release not OK, should i be releasing it?

You'll have to forgive me because i'm still fairly new to Obj-C but i'm quite confused..
I have this little sound board app with 12 buttons.. each calling the same IBAction..
When the user taps the button i'm calling alloc init on the player variable (which is declared in the interface part of the class)
This works all fine and dandy:
#pragma mark - IBActions
-(IBAction)userDidTapButton:(id)sender {
[player stop];
NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"clip" ofType:#"mp3"]];
player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];
[player setNumberOfLoops:-1];
[player play];
}
#pragma mark - Cleanup
- (void)dealloc {
[player release];
[super dealloc];
}
However this feels like when i'm calling alloc init repeatedly i'm leaving memory dangling (because i'm assigning the player pointer to a new variable without releasing the old one..)
To remedy this I tried adding this at the top of the IBAction:
-(IBAction)userDidTapButton:(id)sender {
[player stop];
[player release];
... etc ...
This works the first time i click the button (which seems strange to me as it's effectively a null pointer because it hasn't been allocated and initialised (right?)) but when i tap the button again it throws a EXC_BAD_ACCESS signal..
Why?
I allocated the memory should't I be freeing it too?
How am i supposed to free it?
Thanks in adavance!
So I'll walk you through how I would do it and why.
In your .h file declare the player ivar with a property like this
// .h
#interface MyClass : UIViewController
#property (nonatomic, retain) AVAudioPlayer *audioPlayer;
// method signatures
#end
I named it audioPlayer just to be more explicit (this is personal preference).
In your implementation file you need to synthesize this ivar like this
// .m
#implementation MyClass
#synthesize audioPlayer = _audioPlayer;
// Do some stuff
#end
This will create the backing ivar and the getter and setter with the signatures - (void)setAudioPlayer:(AVAudioPlayer *)audioPlayer and - (AVAudioPlayer *)audioPlayer; but in the background they will be manipulating the ivar _audioPlayer.
You mentioned in a reply that you come from Ruby this can be likened to something like this attr_accessor :audio_player but in Objective-C it creates setters and getters than can deal with memory management depending on whether you pass in assign/retain/copy into the #property line.
This is how Apple does it in most of their examples and it means that it is clearer when you are accessing the ivar directly or going through a getter/setter.
I would now change your -(IBAction)userDidTapButton:(id)sender to look like this
-(IBAction)userDidTapButton:(id)sender
{
[self.audioPlayer stop];
NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"clip" ofType:#"mp3"]];
AVAudioPlayer *tmpPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];;
self.audioPlayer = tmpPlayer;
[tmpPlayer release]; tmpPlayer = nil;
[self.audioPlayer setNumberOfLoops:-1];
[self.audioPlayer play];
}
I have used the getters/setters anytime I have interacted with the audioPlayer ivar. This means that the memory management is taken care of each time I set the ivar (e.g. it releases the old player and retains the new). The reason this is using the getters/setters is because of the self.audioPlayer which will be compiled to the appropriate call like this:
self.audioPlayer; // compiled to -> [self audioPlayer];
self.audioPlayer = tmpPlayer; // compiled to -> [self setAudioPlayer:tmpPlayer];
Now to tidy up and make the - (void)dealloc; method correct we should use the ivar directly without going through the getter/setters so I have to use the _audioPlayer ivar that we synthesized like this:
#pragma mark - Cleanup
- (void)dealloc
{
[_audioPlayer release];
[super dealloc];
}
I sometimes get these weird problems too. A good habit to get into for Objective-C code is the same pattern with all allocated objects: call alloc init, do something (including retain it), then release. I find if you do that all in the same method things go predictably.
So, in your case, try the following:
-(IBAction)userDidTapButton:(id)sender {
[myPlayer stop];
[myPlayer release];
NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"clip" ofType:#"mp3"]];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];
[player setNumberOfLoops:-1];
[player play];
myPlayer = [player retain];
[player release];
}
where myPlayer is an instance variable in your class.
You should probable release the previous player before you create a new one, or just reuse the one you have previously created. So after [player stop]; add [player release]; player = nil; the = nil; is so you can then safely send release in you dealloc methods. You should also probable add a [player stop]; before you [player release]; in you dealloc method. You may also want to keep a AVAudioPlayer instance for each button if there are not too many.
However this feels like when i'm calling alloc init repeatedly i'm leaving memory danglin
Yes, you are. You should release the old player before allocing the new one.
-(IBAction)userDidTapButton:(id)sender {
[player stop];
NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"clip" ofType:#"mp3"]];
[player release]; // <<<=== this needs to be here
player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];
[player setNumberOfLoops:-1];
[player play];
}
However, it is better to create a property for the player to take care of all of this:
#interface MyClass : WhateverSuperClass
{
#private
// other ivars
AVAudioPlayer* player
}
#property (retain) AVAudioPlayer* player;
// other methods
#end
#implementation MyClass
#synthesize player;
// other stuff
-(IBAction)userDidTapButton:(id)sender {
[[self player] stop];
NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"clip" ofType:#"mp3"]];
[self setPlayer: [[[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil] autorelease]];
[[self player] setNumberOfLoops:-1];
[[self player] play];
}
- (void)dealloc
{
[player release];
[super dealloc];
}

sound should start again when over

i've got a sound which is started when the app starts, it's 30 seconds long. how can I repeat the song when it's over no matter in which viewcontroller i am?
my code:
-(void)playBgMusic {
NSString *path = [[NSBundle mainBundle] pathForResource:#"bgmusic" ofType:#"aif"];
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play]; }
-(id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self)
{
[self playBgMusic];
}
return self; }
Try
theAudio.numberOfLoops = -1
Look at Apple's Documentation for more info.
The owner of the audioPlayer instance should be the appDelegate then to let it play wherever the user is in the app.