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?
Related
I'm having a problem with AudioServicesPlaySystemSound in my iOS app.
I have a method defined to play a system sound. When I call the method from viewDidLoad, I can hear the sound play, but when I call it from a button handler, I do not hear the sound play.
Here's the code from my view controller:
SystemSoundID startSound = 0;
-(void)playStartSound
{
AudioServicesPlaySystemSound(startSound);
}
-(void)viewDidLoad
{
NSURL *urlStart = [[NSBundle mainBundle] URLForResource:#"Beep" withExtension:#"wav"];
AudioServicesCreateSystemSoundID((CFURLRef)urlStart, &startSound);
[self playStartSound];
[super viewDidLoad];
}
- (IBAction)start:(id)sender
{
[self playStartSound];
}
The IBAction method is associated with a button. When I select the button, the playStartSound method gets called, but the sound is not audible.
The system sound is not disposed of with AudioServicesDisposeSystemSoundID until the dealloc method is called.
I've tried calling AudioServicesCreateSystemSoundID from within playStartSound, but it didn't make any difference.
The app also uses AV Foundation, Media Player, Core Media, Core Video, and Core Location. I'm wondering if one of these might be interfering with AudioToolbox?
UPDATE: I just put together a simple app that just contains the above code and a button and the sound plays just fine when I select the button.
So then I removed all the video capture code from my controller and now the audio plays fine.
This was not working because I had an active video capture session in my application.
AudioServicesPlaySystemSound will not play when there is an active video capture session.
NSString *soundPath = [[NSBundle mainBundle] pathForResource:#"7777" ofType:#"mp3"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)([NSURL fileURLWithPath: soundPath]), &soundID);
AudioServicesPlaySystemSound (soundID);
Hey try like that it will play
I have searched for this answer and have not found it.
I have music that plays in the background when my iPhone app is launched. But I want a button so that users can mute the music. There are sound effects within the app also so sliding the mute button on the side of the device won't cut it.
This is the current code I have for the AVAudioPlayer.
- (void)viewDidLoad{
#if TARGET_IPHONE_SIMULATOR
//here code for use when execute in simulator
#else
//in real iphone
NSString *path = [[NSBundle mainBundle] pathForResource:#"FUNKYMUSIC" ofType:#"mp3"];
AVAudioPlayer *TheAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
TheAudio.delegate = self;
[TheAudio play];
TheAudio.numberOfLoops = -1;
#endif
}
Can anyone help me out the code needed for a simple button to simply stop the music and start it again.
Thanks in advanced.
Put this code in the viewcontroller.h file:
-(IBAction) btnStop:(id)sender;
Put this code in the viewcontroller.m file:
-(IBAction) btnStop:(id)sender {
[TheAudio stop];
//Whatever else you want to do when the audio is stopped
}
In the Interface builder, connect a button to this action, so when it is clicked this action will be called.
That should make the music stop.
Its easier to display code in an answer:
-(IBAction) playerPlay:(id)sender {
if([player isPlaying]) {
[player stop];
}
if(![player isPlaying]) {
[player play];
}
}
I'll explain:
The [player isPlaying] method checks to see if the audio is playing. If the audio is playing, everything in the brackets is executed (in this situation, the audio stops playing).
Because of the "!" in ![player isPlaying], the method is made the opposite of what it usually is. This means that if the player is NOT playing, everything in the brackets is executed (in this situation, the audio starts playing).
All of this is enclosed in the IBAction, so that it is executed when the button is clicked.
For future reference, the correct format for an If statement in Objective-C is:
if(thing to check for) {
things that happen if the thing that is check for is correct;
}
The word "then" is never actually used, but it is the same thing and whatever is in the brackets.
Hope this helps!
Playing sound for click sound when date is selected in the date picker. Now i need to set my setting module for control ON and OFF using UISWitch.
How to set my control of sound to uiswitch .
-(void) switchforSounds:(id)sender
{
if ([sender isOn])
{
[Appdelegate.infoObj playback];
}
else
{
}
}
-(void) playback
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"click" ofType:#"caf"];
if (path)
{
NSURL *url = [NSURL fileURLWithPath: path];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID ((CFURLRef)url, &soundID);
AudioServicesPlaySystemSound(soundID);
}
}
Nice and easy:
if(myUISwitch.on){
[self playback];
}
If that wasn't what you were looking for, I can always revise it.
if the question is how to bind the switch in IB to your function
-(void) switchforSounds:(id)sender the answer is
declare it like this:
- (IBAction) switchforSounds:(id) sender in your .h and .m file respectively.
right click on the switch in IB, you will see all events it can respond to. Choose changeValue event. Click in the hook near it and drag to the File's Owner element in your IB'c controller file. When you leave the mouse it's supposed to show you all IBAction selectors you've defined in your controller class. Choose switchforSounds.
Done.
Hopefully i understood your question right.
Alright I have two problems. I'm using AVAudioPlayer to play a simple audio file in .caf format. What I'm trying to do is have a play and pause button. Here is a sample of a simple IBAction to play the audio from a button.
- (IBAction) clear {
NSString *path = [[NSBundle mainBundle] pathForResource:#"clear" ofType:#"caf"];
AVAudioPlayer* myAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
myAudio.delegate = self;
myAudio.volume = 1.0;
myAudio.numberOfLoops = 0;
[myAudio play];
}
So when I go ahead and create another IBAction with the method...
- (IBAction) stopaudio {
[myAudio stop];
audioPlayer.currentTime = 0;
}
I compile and xcode tells me that myAudio in the stopaudio IBAction is not defined. How do I fix it so I'm able to access myAudio in another IBAction?
My Second question is that I'm using NavigationController to setup a TableView, when I make a selection it takes me to a UIView with a back button to go back to the TableView. What I'm trying to do is to kill any audio that I have playing in the UIView when I go back to the TableView. The code above is part of the code that I'm going to have in the UIView.
Also one quick question. Lets say I have an audio file that is 1 minute long. Clicking play again will overlap a new instance of that audio before the old instance is done playing. How can I check to see if myAudio is playing and not allow it to be played again until myAudio is finished playing?
Thank You!
First of all, you should read the documentation. It will remove almost all of your questions. Nevertheless, to have access to your instance of AVAudioPlayer from all methods of your class, you should define it in your .h file. To stop your player on returning to the previous screen, call [myAudio stop] in your viewWillDisappear:(BOOL)animated method. About your last question. If you define myAudio in your .h file, you will be able to check if it is playing now. smth like
- (IBAction) clear {
if(![myAudio isPlaying]){
[myAudio play];
}
}
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];
}