iPhone dev -- performSelector:withObject:afterDelay or NSTimer? - iphone

To repeat a method call (or message send, I guess the appropriate term is) every x seconds, is it better to use an NSTimer (NSTimer's scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:) or to have the method recursively call itself at the end (using performSelector:withObject:afterDelay)? The latter doesn't use an object, but maybe its less clear/readable? Also, just to give you an idea of what I'm doing, its just a view with a label which counts down to 12:00 midnight, and when it gets to 0, it will blink the time (00:00:00) and play a beep sound forever.
Thanks.
Edit: also, what would be the best way to repeatedly play a SystemSoundID (forever) ?
Edit: I ended up using this to play the SystemSoundID forever:
// Utilities.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioServices.h>
static void soundCompleted(SystemSoundID soundID, void *myself);
#interface Utilities : NSObject {
}
+ (SystemSoundID)createSystemSoundIDFromFile:(NSString *)fileName ofType:(NSString *)type;
+ (void)playAndRepeatSystemSoundID:(SystemSoundID)soundID;
+ (void)stopPlayingAndDisposeSystemSoundID;
#end
// Utilities.m
#import "Utilities.h"
static BOOL play;
static void soundCompleted(SystemSoundID soundID, void *interval) {
if(play) {
[NSThread sleepForTimeInterval:(NSTimeInterval)interval];
AudioServicesPlaySystemSound(soundID);
} else {
AudioServicesRemoveSystemSoundCompletion(soundID);
AudioServicesDisposeSystemSoundID(soundID);
}
}
#implementation Utilities
+ (SystemSoundID)createSystemSoundIDFromFile:(NSString *)fileName ofType:(NSString *)type {
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:type];
SystemSoundID soundID;
NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
AudioServicesCreateSystemSoundID((CFURLRef)filePath, &soundID);
return soundID;
}
+ (void)playAndRepeatSystemSoundID:(SystemSoundID)soundID interval:(NSTimeInterval)interval {
play = YES
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL,
soundCompleted, (void *)interval);
AudioServicesPlaySystemSound(soundID);
}
+ (void)stopPlayingAndDisposeSystemSoundID {
play = NO
}
#end
Seems to work fine.. And for the label blinking I'll use an NSTimer I guess.

A timer is more suited to a strictly defined interval. You will lose accuracy if you have your function call itself with a delay because its not really synced to a time interval. There's always the time taken to run the actual method itself as well which puts the interval out.
Stick with an NSTimer, I'd say.

Just to add a bit to the other answers, the case for a recursive call would be when the call might take an unknown amount of time - say you are calling a web service repeatedly with small amounts of data until you are finished. Each call may take some unknown amount of time so you have the code do nothing until the web call returns, then the next batch is sent out until no more data remains to be sent and the code does not call itself again.

Since your application depends on time accuracy (i.e. it needs to execute once per second), the NSTimer would be better. It takes some time for the method itself to execute, and an NSTimer would be fine with that (as long as your method takes less than 1 second, if it's called every second).
To repeatedly play your sound, you can set a completion callback and replay the sound there:
SystemSoundID tickingSound;
...
AudioServicesAddSystemSoundCompletion(tickingSound, NULL, NULL, completionCallback, (void*) self);
...
static void completionCallback(SystemSoundID mySSID, void* myself) {
NSLog(#"completionCallback");
// You can use this when/if you want to remove the completion callback
//AudioServicesRemoveSystemSoundCompletion(mySSID);
// myself is the object that called set the callback, because we set it up that way above
// Cast it to whatever object that is (e.g. MyViewController, in this case)
[(MyViewController *)myself playSound:mySSID];
}

Related

Making a Mute Toggle Button Using NSUserDefaults and AVAudioPlayer

I've been working on this project for a while, but I've encountered a problem that I can't figure out.
First, I have a checkbox button that saves to NSDefaultUser as BOOL value. It simply saves the value YES when it's pressed once and it saves NO if pressed again and so on... This checkbox button works fine like a normal custom checkbox button would.
I would like to make an option to mute all of my sounds in my app by using this checkbox button.
I'm playing my sounds by calling method such as:
- (void)startMusic1
{
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/music1.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
AVAudioPlayer *audioPlayer;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = -1;
if (audioPlayer == nil)
NSLog(#"Error: %#", [error description]);
else
[audioPlayer play];
}
So for every sound I want to play in my app, I could repeat something like:
- (IBAction)playButton:(id)sender
{
NSUserDefaults *default = [NSUserDefaults standardUserDefaults];
if(![default boolForKey:#"isMuted"])
{
[self startMusic1];
}
}
But, this gets repetitive especially because I have to manage a lot of different sounds (and different buttons that play sounds) for my project, and it seems irrelevant to repeat these steps.
I tried making a new class with a subclass of AVAudioPlayer and messed around with -(BOOL)play method for a few days, but I couldn't manage to get the results I wanted. I researched and found posts like Disable in App sounds but this still wouldn't do it.
I'm pretty new to programming overall, so it'll be great if someone could enlighten me a little.
I tackle this (rightly or wrongly, but it works for me) by using a global model singleton. This is a class that effectively maintains state across the entire application.
The way to do that is with a class that has a shared static property like this:
//// Interface
#import <Foundation/Foundation.h>
#interface AudioModel
#property (nonatomic) BOOL playAudio;
+(id)sharedInstance;
#end
//// Implementation
#import "AudioModel.h"
#implementation AudioModel
// property for toggling audio on or off
#synthesize playAudio = _playAudio;
// singleton model variable
static AudioModel* audio = nil;
-(BOOL)playAudio {
return _playAudio;
}
-(void)setPlayAudio:(BOOL)playAudio {
_playAudio = playAudio;
}
// static
+(AudioModel*)sharedInstance {
if(audio == nil)
{
audio = [[AudioModel alloc]init];
}
return audio;
}
-(AudioModel*)init {
self = [super init];
if(self){
// set up default sounds on
// this may read from your stored value
_playAudio = YES;
}
}
#end
Then when you want to read or write to this globally available singleton model, you set a variable in your controller like this:
AudioModel *volumeControl = [AudioModel sharedInstance];
if(volumeControl.playAudio){
// method to play audio passing audio file name...
}
You could also have a reference to your AVAudioPlayer instance in this class, init it on creation of the shared instance and pass files to it to play when required.

How to perform operations when playing sound in iPhone?

I play a MP3 in my iPhone app using AVAudioPlayer; i need to perform some operations at certain times (say 30th seconds, 1 minute); is there a way to invoke callback functions based on mp3 playing time?
I believe the best solution is to start an NSTimer as you start the AVAudioPlayer playing. You could set the timer to fire every half second or so. Then each time your timer fires, look at the currentTime property on your audio player.
In order to do something at certain intervals, I'd suggest you kept an instance variable for the playback time from last time your timer callback was called. Then if you had passed the critical point between last callback and this, do your action.
So, in pseudocode, the timer callback:
Get the currentTime of your AVAudioPlayer
Check to see if currentTime is greater than criticalPoint
If yes, check to see if lastCurrentTime is less than criticalPoint
If yes to that too, do your action.
Set lastCurrentTime to currentTime
If you're able to use AVPlayer instead of AVAudioPlayer, you can set boundary or periodic time observers:
// File URL or URL of a media library item
AVPlayer *player = [[AVPlayer alloc] initWithURL:url];
CMTime time = CMTimeMakeWithSeconds(30.0, 600);
NSArray *times = [NSArray arrayWithObject:[NSValue valueWithCMTime:time]];
id playerObserver = [player addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
NSLog(#"Playback time is 30 seconds");
}];
[player play];
// remove the observer when you're done with the player:
[player removeTimeObserver:playerObserver];
AVPlayer documentation:
http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVPlayer_Class/Reference/Reference.html
I found this link describing a property property which seems to indicate you can get the current playback time.
If the sound is playing, currentTime is the offset of the current
playback position, measured in seconds from the start of the sound. If
the sound is not playing, currentTime is the offset of where playing
starts upon calling the play method, measured in seconds from the
start of the sound.
By setting this property you can seek to a specific point in a sound
file or implement audio fast-forward and rewind functions.
To check the time and perform your action you can simply query it:
if (avAudioPlayerObject.currentTime == 30.0) //You may need a more broad check. Double may not be able to exactly represent 30.0s
{
//Do Something
}
with multithreading your goal is simple, just do like this :
1 : in your main thread create a variable for storing time passed
2 : create new thread like "checkthread" that check each 30-20 sec(as you need)
3 : if the time passed is what you want do the callback
Yes Sure you can ...it's tricky i hope it works for you but it works for me ..
1- you play your mp3 file.
2- [self performSelector:#selector(Operation:) withObject:Object afterDelay:30];
then the function
-(void)Operation:(id)sender;
called; so you fired function after 30 second of mp3 file .. you can make many of function based on time you want..
3- there is other solution using timers
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(CheckTime:) userInfo:nil repeats:YES];
it will fire function called Check Time
-(void)CheckTime:(id)sender{
if (avAudioPlayerObject.currentTime == 30.0)
{
//Do Something
//Fire and function call such
[self performSelector:#selector(Operation:) withObject:Object]
}
}
then you can change time interval you want and repeats is for you to control repeat this action every 5 seconds or not..
Hope that helpful..
Thanks
i think ,you want to play different sound-files after 30sec then use this code :
1) all sound-files put in Array and then retrieve from document directory
2)then try this:
-(IBAction)play_sound
{
BackgroundPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:[Arr_tone_selected objectAtIndex:j]ofType:#"mp3"]]error:NULL];
BackgroundPlayer.delegate=self;
[BackgroundPlayer play];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[BackgroundPlayer stop];
j++;
[self performSelector:#selector(play_sound) withObject:Object afterDelay:30];
}

Sound playback delay (hiccup) when OpenAL is implemented on iphone

I implemented OpenAL code to my iphone game. When I starts the game, it runs for 1 sec and stalls for 2 sec then resumes (hiccup effect). I believe its delayed due to the sound files loading. What is the solution? Can anyone recommend any book, site or sources code (not the iphone reference, please)? Is there a loading process and where should I initialize the loading process? Would that help?
Below, I have included the related components of the OpenAL code that I have implemented. The sound file will be played and invoked by a "if" statement in the gameloop. The OpenALController class is for the sound sources and buffers creation and the InitOpenAL method is invoked in OpenALController. MyView is a customized subclass of UIView and connected to the main view (I didn't use the default view).
// MyView.m
// A customized UIView as the main view.
#import "OpenALSoundController.h"
- (void)startPlaying{
...
[self initializeValuables];
...
[self initializeTimer];
}
- (void)initializeTimer {
if (theTimer == nil) {
theTimer = [CADisplayLink displayLinkWithTarget:self selector:#selector)gameLoop)];
theTimer.frameInterval = 2;
[theTimer addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
}
}
- (void)gameLoop {
...
If something = true
// Play the sound
[[OpenALSoundController sharedSoundController] playSound1];
...
}
...
#end
// OpenALSoundController.h
#interface OpenALSoundController : NSObject {
...}
...
+ (OpenALSoundController*) sharedSoundController
...
#end
// OpenALSoundController.m
// Singleton accessor
{
static OpenALSoundController* shared_sound_controller;
#synchronized(self)
{
if(nil == shared_sound_controller)
{
shared_sound_controller = [[OpenALSoundController alloc] init];
}
return shared_sound_controller;
}
return shared_sound_controller;
}
- (void) initOpenAL{
...
file_url = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:#"fire" ofType:#"wav"]];
firePcmData = MyGetOpenALAudioDataAll((CFURLRef)file_url, &data_size, &al_format,&sample_rate);
alBufferData(fireOutputBuffer, al_format, firePcmData, data_size, sample_rate);
[file_url release];
...
alSourcei(outputSourceFire, AL_BUFFER, fireOutputBuffer);
...
}
You might be interested in Finch, an OpenAL sound engine for iOS. It’s very well suited to games. It’s usually better to reuse some already existing code than develop and maintain your own.
First its better to use mp3, as wav files are huge and loading from disk takes time. Mp3 files are smaller on disk, loaded into memory and decompressed there for playing. Try experimenting by reducing mp3 bitrate/encoding quality too.
Also you need to preload sounds to avoid hiccups, otherwise you will have a delay the first time a sound is played.

iphone, how to refresh the gui?

i'm coding a small app for the iphone (just for fun)
what i want:
if i press the "update" button:
send something to the server
parse the answer of the server
update the content of some labels on the screen
show the answer
play a system sound //using the audio toolbox
sleep some secods, just enough to finish the previos system sound call
do other stuff
end
actually, everything works but... for some reason, the label content is updated at the end of the method / callback / function that is called when pressed the "update" button.
what am i doing wrong?
thanks!
some code:
-(void) playASound: (NSString *) file {
//Get the filename of the sound file:
NSString *path = [NSString stringWithFormat:#"%#/%#",
[[NSBundle mainBundle] resourcePath],
file];
SystemSoundID soundID;
//Get a URL for the sound file
NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
AudioServicesCreateSystemSoundID((CFURLRef)filePath, &soundID);
//play the file
AudioServicesPlaySystemSound(soundID);
}
- (IBAction)update: (id) sender{
BOOL error=[self sendContent];
if ( error == NO ){
result.text=[self parseContent];
[self playSound:#"ready"];
sleep(4);
....
}
// here is updated the gui
}
The GUI is displayed by the runloop. The loop will not reach its next iteration until your method is done executing. Therefore, your method blocks the view from updating. Either use a background thread or a timer.
You don't want to use sleep there. Sleep stops the whole process from continuing that's why your label gets updated only at the end of the function. I'd recommend either using NSTimer or performSelector:withObject:afterDelay: (see here).
This is because you have blocked the main thread when you sleep. UI can only be drawn on the main thread.
A solution is to use [NSTimer scheduledTimerWithTimeInterval:X_SEC target:self selector:#selector(doOtherStuff) userInfo:nil repeats:NO] in place of sleep and do other stuff.
What about performing the [self sendContent] call in a separate thread?

Different instances of SystemSoundID playing on different streams !

First, thanks for the StackOverflow team, cause it's a very useful website, since i'm developping on iPhone.
Secondary, please excuse my language. I'm a frenchie and like every frenchies i'm very bad in english.
I've a very strange problem with my sounds in my iPhone program :
I implemented a class which play a short sound in aiff. Here it is :
#implementation SoundPlayer
-(id)initWithFile:(NSString*)file{
self = [super init];
NSString *soundPath = [[NSBundle mainBundle] pathForResource:file ofType:#"aiff"];
AudioServicesCreateSystemSoundID((CFURLRef)[NSURL fileURLWithPath: soundPath], &soundID);
return self;
}
-(void)play {
if(SOUND_ACTIVATED){
AudioServicesPlaySystemSound (soundID);
}
}
-(void)dealloc{
[super dealloc];
}
#end
It works quite good, but even if my instances are initialized the same way, they are not in the same audio stream !
I noticed that because when I push the volume+ and volume- buttons of the iPhone, in some cases it controls the main audio stream, in other cases it controls the ring volume.
If I put the main stream to volume 0, sound A won't be hearable, but sound B will be.
Did someone have a similar problem ? Do you have any idea ?
Thanks a lot.
Martin
Ok.
I found somehing that would be interesting to answer the problem.
There's a global function which initialize the audio context. It seem's that I don't use it the right way, but I think the problem comes from there.
// Initialize the Audio context
AudioSessionInitialize (
NULL, // 'NULL' to use the default (main) run loop
NULL, // 'NULL' to use the default run loop mode
NULL, // a reference to your interruption callbac
self // data to pass to your interruption listener callback
);
// What kind of sound will be played
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty (
kAudioSessionProperty_AudioCategory,
sizeof (sessionCategory),
&sessionCategory
);
In spite of these two functions, one sound is remaining on the ring stream, and that's really strange. Can someone help me ?