Play NSSound two times - objective-c

I want to play a NSSound two times except its shorter than 10sec then i want to wait until the 10sec are done and then start the second one.
But I already have a problem playing the same NSSound twice.
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"MessageArrived" ofType:#"wav"];
NSSound *sound = [[NSSound alloc] initWithContentsOfFile:resourcePath byReference:YES];
[sound play] //It plays the fist time
[sound play] //I get the following Error and it doesn't play a secont time
/*malloc: *** auto malloc[498]: error: GC operation on unregistered thread. Thread registered implicitly. Break on auto_zone_thread_registration_error() to debug.*/
Can someone tell me whats the reason for this error?
How can I handle this?
Is there another way i have to do this?

So after a loot of searching and trying to fix it I got the solution for my problem...
NSSound *sound = [NSSound soundNamed:#"MessageArrived"];
BOOL res = [sound play];
NSLog(#"%d", res);
[NSThread detachNewThreadSelector:#selector(playSecondSound:) toTarget:self withObject:sound];
-(void)playSecondSound:(NSSound*)sound
{
[NSThread sleepForTimeInterval:10-[sound duration]];
[sound stop];
BOOL res = [sound play];
NSLog(#"%d", res);
}
I found out that even when the sound has finished I have to call [sound stop] before starting the second.

The sound is already playing when you call the second play method, obviously a bug in garbage collection as the second play should be ignored, but it will not be an issue with what you want to achieve.
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"MessageArrived" ofType:#"wav"];
NSSound *sound = [[NSSound alloc] initWithContentsOfFile:resourcePath byReference:YES];
[sound play];
//Will play the second time after 10.0 seconds have passed.
NSInteger wait = ([sound duration] < 10.0) ? 10.0 - [sound duration] : 0.0;
[sound performSelector:#selector(play) withObject:nil afterDelay:wait];

Related

Objective-C exception handling

I'm trying to figure out how exception handling works in Objective-C. So I'm forcing an exception by performing a selector (boom) which does not exist. When I run it, the application crushes, the exception is not handled properly. Can somebody show me the proper way of handling exceptions in objective-c?
#try{
[self performSelector:#selector(boom)];
}
#catch (NSException *ex) {
NSLog(#"Error");
}
... And can somebody also show me how to handle exception for the code below. I'm using one instance variable for four different sound effects. After playing, I set the variable to nil. For some reason, the code below crushes the app at times, so I will like to handle that exception. Thanks.
- (void) playSound:(NSUInteger) number withDelay:(NSTimeInterval) delay
{
if(sound == nil)
{
NSURL* url;
switch (number)
{
case 1: url = [[NSBundle mainBundle] URLForResource:#"Shuffle" withExtension:#"caf"]; break;
case 3: url = [[NSBundle mainBundle] URLForResource:#"Clap" withExtension:#"caf"]; break;
case 4: url = [[NSBundle mainBundle] URLForResource:#"Glass" withExtension:#"caf"]; break;
case 5: url = [[NSBundle mainBundle] URLForResource:#"Knock" withExtension:#"caf"]; break;
default: break;
}
if (url != nil)
{
sound = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[sound setCurrentTime:0];
[sound prepareToPlay];
[sound setVolume:1.0];
[sound performSelector:#selector(play) withObject:nil afterDelay: delay];
}
}
}
Exception Handling will not solve your problem here,
If you call - (void) playSound:(NSUInteger) number withDelay:(NSTimeInterval) delay from button press events. Multiple button presses would lead to this kind of crashes. Because you are using the same AVAudioPlayer variable *sound and that same object is used to play sounds after delays.A call from other button press could be initiating the sound player, while another one tries to play the sound.
If these are short sound clips (less than 30sec), Without using AVAudioPlayer , you better use AudioServicesPlaySystemSound,
You can write a method to delay the start of play, This will let you play multiple sound clips at the same time without any problem.
NSString *path = [[NSBundle mainBundle] pathForResource:#"Shuffle" ofType:#"caf"];
NSURL *url = [NSURL fileURLWithPath:path];
SystemSoundID soundFileObject;
AudioServicesCreateSystemSoundID ((__bridge_retained CFURLRef) url, &soundFileObject);
AudioServicesPlaySystemSound (soundFileObject);

iOS AVAudioPlayer double sound playing issue with Xcode 4.2 and ARC

Ive been beating my head up against the all trying to figure out a weird sound issue in my program. I will post the code below. As a brief description I have a function that gets passed a variable and it takes that and decides which category of sound it should play, within that function I have multiple sounds of reach category so I use an arc4random statement then run that through a switch statement. What keeps happening is that it will play two sounds from the case statement instead of just one, most times if I call it twice with my button push the second time it will play the sound from the first time and a new sound, other times it plays the same same over the top of each other but with a slight delay. I put in a breakpoint in the switch and when it double plays it only goes through the switch once which is really confusing. One thing to note is right before this I do play another sound but it uses a separate AVAudioplayer and path variable so that shouldn't be an issue as it never plays the second sound as the other sound I'm playing. I'm only calling the function once when I press the button so I'm not sure why it does this. I have tried putting the *path and *avaudioplayer variables inside the function but it won't play at all. Searching here it seems as though arc deallocs it before it gets a chance to play it. I ended up trying to put it at the top of my .m file as a global variable then just set the actual path and play the sound within the switch. The sound will play but it plays twice.. Hopefully someone can help me out. I tried putting the avaudioplayer definition as a property as well and it does the same thing. Here is my code snippet. And thanks in advance ...
in the .m file
// Just below my synthesize statements
NSString *path;
AVAudioPlayer *theSound
// My code that I call when the button is pressed
[self playVoice:#"buyVoice"];
// The playVoice function
- (void)playVoice:(NSString*)voiceNum;
if ([voiceNum isEqualToString:#"buyVoice"]) // Bought an Item Time for a voice
{
// play coin sound
[self coinSound];
// play random buy phrase and coin sound
int phraseNumber = 0;
phraseNumber = arc4random() %(3);
switch (phraseNumber)
{
case 0:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy1" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
case 1:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy2" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
case 2:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy3" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
case 3:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy4" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
}
}
set this code before u play the Audio sound
This will reset the player to the beginning:
[theSound setCurrentTime:0.0];
[theSound play];
try this

AVAudioPlayer Won't Play Repeatedly

I have a relatively simple app that plays a sound using AVAudioPlayer, like this:
NSURL *file3 = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/ball_bounce.aiff", [[NSBundle mainBundle] resourcePath]]];
player3 = [[AVAudioPlayer alloc] initWithContentsOfURL:file3 error:nil];
[player3 prepareToPlay];
to later be called to play, like this:
[player3 play];
But this sound is called in multiple places (collisions with a bouncing ball), many times. Sometimes, the ball hits two things in quick enough succession where the sound from the first bounce is still playing when the ball collides a second time. This second bounce then does not produce a sound. How can I make it play a sound every time it collides, even if it involves paying a sound that overlaps an already playing sound?
Any help is greatly appreciated.
One way to solve this problem would to be create a new instance of AVAudioPlayer if the previous one is already playing:
if([player3 isPlaying]) {
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[player3 url]];
[newPlayer setDelegate:self];
[newPlayer play];
}
Then, in the delegate method audioPlayerDidFinishPlaying:successfully:, you should send the player the -release message:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if(player != player3) {
[player release];
}
}
This is just the way it has to be, as you do need a new source buffer for each concurrent sound playing. :(
It is an old thread but maybe someone can benefit from this answer. Creating AVAudioPlayer repeatedly can be a burden for the app and can creates lags. In this case you can use SystemSound.
NSString *pathToMySound = [[NSBundle mainBundle] pathForResource:#"yourMusic" ofType:#"mp3"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)([NSURL fileURLWithPath: pathToMySound]), &soundID);
AudioServicesPlaySystemSound(soundID);
Downside of this solution, you can't adjust the volume.

Waiting until another sound is finished before playing a new one

I am developing an iPhone application that plays sounds. I do not want interrupt or play another sound, if a sound is already playing, regardless of where it was "spawned" from to play. My code is as follows:
if(distance <= 10)
{
NSString *soundPath = [[NSBundle mainBundle] pathForResource:#"sound" ofType:#"wav"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((CFURLRef)[NSURL fileURLWithPath: soundPath], &soundID);
AudioServicesPlaySystemSound(soundID);
}
Simply, I do not want to play a new sound if one is already playing.
I do not want to queue another sound to play if one is already playing.
Is this possible?
I suggest you use the AVAudioPlayer API rather than AudioServicesPlaySystemSound. It has a delegate which you can use, which defines a method -audioPlayerDidFinishPlaying:successfully: which will tell you when your sound finishes playing. The AVPlayer class also has a -playing method which you can check to see if a sound is currently playing.
So, in other words, do this instead:
NSError *error;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundPath error:&error];
if(!player)
[self doSomethingWithError:error];
[player setDelegate:self];
[player play];

problem in playing next song in the avaudioplayer

my delegate method looks like this. after the first song is played it goes into this method and plays the second song , however when the second song is done playing it stops. it does not go into the delegate method.i need to play all the songs continuously. i am not sure, why. can someone help me.
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)p successfully:(BOOL)flag
{
if (flag == NO)
NSLog(#"Playback finished unsuccessfully");
else
{
//[player stop];
index++;
NSLog(#"%d",index);
path=[[NSBundle mainBundle] pathForResource:[songlist objectAtIndex:index] ofType:#"mp3"];
[player initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[songlabel2 setTitle:[songlist objectAtIndex:index]];
[endtime setText:[NSString stringWithFormat:#"%.2f",[player duration]/100]];
[player play];
}
}
Since you’re not defining a local variable called player, I’m assuming that player is an instance variable that was used to play the first song, and you’ve created it with
player = [[AVAudioPlayer alloc] initWithContentsOfURL:firstSongURL error:&error];
or something similar, and you’ve set the delegate. In your audioPlayerDidFinishPlaying: successfully: method, you have
[player initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
which means you’re sending -initWithContentsOfURL:error: again to the same instance. Do not call an initialiser twice on a given object. The results are unpredictable and there is potential for memory leaking. You should release the previous instance of AVAudioPlayer, e.g.
[player release];
and then create a new instance with +alloc and -initWithContentsOfURL:error: again, just like you did with the first song, and set the appropriate delegate:
player = [[AVAudioPlayer alloc] initWithContentsOfURL:secondSongURL error:&error];
player.delegate = self;
[player play];