Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

How to Detect System Sound Completion?

ZariannaZarianna Posts: 17Registered Users
edited December 2009 in iOS SDK Development
Hello all,

I'm quite new to the iPhone platform and even more new to the concept of Audio Services.

I am able to play a system sound but if the play function is called many times, the sound will restart and try to play again. I'd like to have the play function wait until the first sound is finished before playing again.

I've looked over the iPhone Dev Reference Guide on Playing System Sound and they outline a function that will keep track if a sound is completed but I am not sure how to implement this. There is a function called "AudioServicesSystemSoundCompletionProc" which is invoked as a callback. I've looked everywhere for an example how to use this function but I have not been able to find it it any sample code (neither metronome, GLPaint nor BubbleLevel) and if anyone out there can point me how I could use this function, I would greatly appreciate it.

I have added a listener with AudioServicesSystemSoundCompleted. I am unclear how to use this in conjunction with the "AudioServicesSystemSoundCompletionProc" method if at all. :x

Any help is appreciated.
Post edited by Zarianna on

Replies

  • smashersmasher Posts: 3,859Registered Users @ @ @ @ @
    edited September 2008
    To tell when a sound is finished playing, do something like this:
    -(void)playAndRelease{
    	AudioServicesAddSystemSoundCompletion (_soundID,NULL,NULL,
    		completionCallback,
    		(void*) self);
    	AudioServicesPlaySystemSound(_soundID);
    }
    
    static void completionCallback (SystemSoundID  mySSID, void* myself) {
    	//NSLog(@"completion Callback");
    	AudioServicesRemoveSystemSoundCompletion (mySSID);
    	
    	[(SoundEffect*)myself release];
    }
    

    You're wrting the function "completionCallback", and then passing it and the current object into "AudioServicesAddSystemSoundCompletion" .

    When the sound is done, it will call your function (a regular C function, not an objective-C method) with your object as a parameter. You can then cast that object back to whatever you want, and call methods on it. I'm catsing it back to a (SoundEffect*) , one of my custom classes.
    TinyCo is Hiring Mobile Game Programmers (C++)
    http://jobvite.com/m?3Ho5wgwr
  • sclydesclyde Posts: 13Registered Users
    edited October 2008
    smasher wrote: »
    To tell when a sound is finished playing, do something like this:
    -(void)playAndRelease{
    	AudioServicesAddSystemSoundCompletion (_soundID,NULL,NULL,
    		completionCallback,
    		(void*) self);
    	AudioServicesPlaySystemSound(_soundID);
    }
    
    static void completionCallback (SystemSoundID  mySSID, void* myself) {
    	//NSLog(@"completion Callback");
    	AudioServicesRemoveSystemSoundCompletion (mySSID);
    	
    	[(SoundEffect*)myself release];
    }
    

    You're wrting the function "completionCallback", and then passing it and the current object into "AudioServicesAddSystemSoundCompletion" .

    When the sound is done, it will call your function (a regular C function, not an objective-C method) with your object as a parameter. You can then cast that object back to whatever you want, and call methods on it. I'm catsing it back to a (SoundEffect*) , one of my custom classes.

    I'm using this callback almost exactly as written above and it is never reaching the callback. Is it a problem that my sounds are very short? Some are less than a second anywhere up to about 3 seconds.

    Basically I'm trying to play a collection of sounds all in a row one after the other - but they all play at the same time, so I want to use something to wait until the end of each sound to play the next - I figured this callback might do the job, but it seems to never get called for me.

    Any thoughts?
  • sclydesclyde Posts: 13Registered Users
    edited October 2008
    Just wanted to give this a quick bump - anyone have any ideas? I can't think of anything that would be causing the callback to not be hit.

    Thanks for any help.
  • smashersmasher Posts: 3,859Registered Users @ @ @ @ @
    edited October 2008
    sclyde wrote: »
    Just wanted to give this a quick bump - anyone have any ideas? I can't think of anything that would be causing the callback to not be hit.

    Thanks for any help.

    I'll look at your code if you post it. Did you try an NSLog inside your callback function, to see if it's being called at all?
    TinyCo is Hiring Mobile Game Programmers (C++)
    http://jobvite.com/m?3Ho5wgwr
  • sclydesclyde Posts: 13Registered Users
    edited October 2008
    I'll have to post my code when I get home. I don't have any NSLogs in there, but I had break points galore, none of which ever got hit - and of course what it was supposed to do never happened.
  • sclydesclyde Posts: 13Registered Users
    edited October 2008
    Ok, I kept forgetting to post my code - here it is still not working:
    void MyAudioServicesSystemSoundCompletionProc(SystemSoundID ssID, void *clientData) {
    	[((MyAppDelegate*)clientData) setSoundPlaying:NO];
    }
    
    - (void)playWavNamed:(NSString*)audioFile {
    	AudioServicesDisposeSystemSoundID(lastSoundID);
    	[self setSoundPlaying:YES]; // self happens to be MyAppDelegate
    	
    	SystemSoundID soundID;
    	NSString *filePath = [[NSBundle mainBundle] pathForResource:audioFile ofType:@"wav" inDirectory:@""];
    	NSURL *fileUrl = [NSURL fileURLWithPath:filePath isDirectory:NO];
    	AudioServicesCreateSystemSoundID((CFURLRef)fileUrl, &soundID);
    	AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, MyAudioServicesSystemSoundCompletionProc, self);
    	AudioServicesPlaySystemSound(soundID);
    	[self setLastSoundID:soundID];
    }
    

    The playWavNamed method is called in a loop, which is this:
    NSMutableArray *sounds = [appDelegate getSounds];
    				
    				for (Sound *sound in sounds)
    				{
    					[appDelegate playSound:sound];
    					
    					while ([appDelegate soundPlaying]) {}
    				}
    

    The idea is to wait until each sound is done playing before continuing the loop into playing the next sound. All this currently does it get locked up in the while loop all the while MyAudioServicesSystemSoundCompletionProc never gets called - so soundPlaying is always true.
  • smashersmasher Posts: 3,859Registered Users @ @ @ @ @
    edited November 2008
    Your "While" loop is the problem there - you can't hog the processor like that; that will prevent the systemSoundCompletionProc from being called - it'll also keep any touch events from being called.

    I'd get rid of the while loop AND the for loop, and change your systemSoundCompletionProc - instead of calling setSoundPlaying:NO, write a method that plays the next sound, and call that instead. (You'll need an int to keep tack of which sound is next, etc.

    Hope that helps.
    TinyCo is Hiring Mobile Game Programmers (C++)
    http://jobvite.com/m?3Ho5wgwr
  • Lukapple80Lukapple80 Posts: 65Registered Users
    edited December 2009
    Hi!
    I've same problem. Trying to make sound queue with audio toolbox.

    soundQueue.h :
    @interface soundQueue : NSObject {
    	NSMutableArray *sndQueue; 
    ...
    -(void)initWithArray:(NSMutableArray *)arraySound;
    -(void)addSoundToQueue:(NSString*)snd;
    -(NSString*)getSoundFromQueue; // get next sound from queue
    -(void)playQueue;
    
    

    then I try to play queue of sounds, one sound after another...

    soundQueue.m:
    
    void MyAudioServicesSystemSoundCompletionProc(SystemSoundID ssID, void *clientData)
    {
    	soundQueue* pSoundQueue = clientData;
    //I DON'T KNOW HOW TO START NEXT SONG FROM HERE, How can I call "playQueue" from this procedure ?
    //Something like:
      [pSoundQueue playQueue]; // ???
    }
    
    
    
    -(void)playSnd:(NSString*) snd
    {
    	NSString *path = [[NSBundle mainBundle] pathForResource:snd ofType:@"wav"];
    	SystemSoundID soundID;
    	AudioServicesCreateSystemSoundID((CFURLRef)[NSURL fileURLWithPath:path], &soundID);
    	
    	AudioServicesAddSystemSoundCompletion (soundID,NULL,NULL,MyAudioServicesSystemSoundCompletionProc,self);
    	AudioServicesPlaySystemSound (soundID);	
    	
    }
    
    -(void)playQueue
    {
    	NSString *snd = [self getSoundFromQueue];
    	if (snd != nil)
    	{
    		[self playSnd:snd];
    	}
    }
    

    Please help.
  • smashersmasher Posts: 3,859Registered Users @ @ @ @ @
    edited December 2009
    That looks like it should work - you're passing self into AudioServicesAddSystemSoundCompletion; "self" is the sound queue, right?

    Then you get it back as clientData when the sound is complete in MyAudioServicesSystemSoundCompletionProc and you call playQueue. You should probably also destroy the system sounds ID there.

    Everything looks shipshape - what's not working? Put NSlogs in MyAudioServicesSystemSoundCompletionProc and playQueue and see if they're being run.
    TinyCo is Hiring Mobile Game Programmers (C++)
    http://jobvite.com/m?3Ho5wgwr
  • Lukapple80Lukapple80 Posts: 65Registered Users
    edited December 2009
    Hi,

    OSStatus AudioServicesAddSystemSoundCompletion (
       SystemSoundID                           inSystemSoundID,
       CFRunLoopRef                            inRunLoop,
       CFStringRef                             inRunLoopMode,
       AudioServicesSystemSoundCompletionProc  inCompletionRoutine,
    [B] void                                    *inClientData[/B]
    );
    

    I want to pass whole object into "inClientData"...
    Sounds are stored in soundQueue.h instance variable:
    NSMutableArray *sndQueue;
    

    then I want to call object method, that removes&returns next song from sound queue (NSMutableArray *sndQueue).
    It hangs here:
    [pSoundQueue playQueue];
    

    I'll post logs when I'm back from work.

    Regards,
    L
  • Lukapple80Lukapple80 Posts: 65Registered Users
    edited December 2009
    Here is my class.

    soundQueue.h:
    #import <Foundation/Foundation.h>
    
    @interface soundQueue : NSObject {
    	NSMutableArray *sndQueue;
    }
    @property(nonatomic, retain) NSMutableArray *sndQueue;
    
    
    -(id)init;
    
    -(void)initWithArray:(NSMutableArray *)arraySound;
    
    -(void)addSoundToQueue:(NSString*)snd;
    
    -(NSString*)getSoundFromQueue;
    
    -(void)playQueue;
    
    @end
    

    soundQueue.m:
    #import "soundQueue.h"
    #import <AudioToolbox/AudioToolbox.h>
    
    
    @implementation soundQueue
    @synthesize sndQueue;
    
    -(id)init
    {
    	if (self = [super init])
    	{
    		sndQueue = [[NSMutableArray alloc] init];
    	}
    	return self;
    }
    
    -(void)initWithArray:(NSMutableArray *)arraySound
    {	
    	[self init];
    	for (NSString* snd in arraySound) 
    	{
    		[self addSoundToQueue:snd];
    	}
    }
    
    -(void)addSoundToQueue:(NSString*)snd
    {
    	[sndQueue addObject:snd];
    }
    
    -(NSString*)getSoundFromQueue
    {
    	NSString* snd;
    	snd = [sndQueue objectAtIndex:0]; //1st sound
    	[sndQueue removeObjectAtIndex:0]; 
    	return snd;
    }
    
    void MyAudioServicesSystemSoundCompletionProc(SystemSoundID ssID, void *clientData)
    {
    	//cleanup
    	AudioServicesDisposeSystemSoundID(ssID);
    	soundQueue* pSoundQueue = clientData;
    	[B][pSoundQueue playQueue];[/B] //it crashes here: EXC_BAD_ACCESS
    }
    
    -(void)playQueue
    {
        NSString *nextSound = [self getSoundFromQueue];
    	if (nextSound != NULL) { 
    		NSString *path = [[NSBundle mainBundle] pathForResource:nextSound ofType:@"wav"];
    		SystemSoundID soundID;
    		AudioServicesCreateSystemSoundID((CFURLRef)[NSURL fileURLWithPath:path], &soundID);
    	
    		AudioServicesAddSystemSoundCompletion (soundID,NULL,NULL,MyAudioServicesSystemSoundCompletionProc, /*(void*)*/ self);
    		AudioServicesPlaySystemSound (soundID);	
    	}
    	
    }
    
    @end
    
    
  • smashersmasher Posts: 3,859Registered Users @ @ @ @ @
    edited December 2009
    I can't spot the error yet. Can you show the block of code when you create your soundQueue? You don't release it, do you?

    PS objectAtIndex won't return nil when there's no more objects, it'll cause an error. Check the length of the array before accessing object 0.

    PPS - your initWithArray method is strange - it shares a name with another method of a different type, which causes a warning, and any init method should return self. Right now you probably init twice, right?

    Here's a better initWithArray:
    -(id)initWithArray:(NSMutableArray *)arraySound
    {	
    	self = [self init];
    	if (self!=nil){
    		[sndQueue addObjectsFromArray:arraySound];
    	}
    	
    	return self;
    }
    

    When I make that change your code works for me - except for the exception when the queue runs dry (see PS above).
    TinyCo is Hiring Mobile Game Programmers (C++)
    http://jobvite.com/m?3Ho5wgwr
  • Lukapple80Lukapple80 Posts: 65Registered Users
    edited December 2009
    Thanks a lot for your help! it works like a charm now. I released object too soon.
Sign In or Register to comment.