How do I loop a sound, while some condition, using the AVAudioPlayer framework here? - objective-c

EDIT : trying to implement Adam B's answer now...
I have a crashSound.wav file that I have put in my Supporting Files folder of my xCode project
I'm now trying to make it play inside a while loop, but the documentation isn't very clear as to how exactly I can do that. I know I have to create a delegate object somewhere (I guess my Game class) and get notifications as to whether stopButtonPressed is false and whether the file has finsihed playing so that it can loop and play again while the stopButtonPressed condition is false.. and I know I shouldn't be doing that by calling the [crashSound play] method but I'm not sure how to do it.. Any help?
#interface Game()
{
// code...
AVAudioPlayer *crashSound;
// code...
}
#end
#implementation Game
- (id) init
{
// code...
NSURL *crashSoundFile = [[NSURL alloc] initWithString:#"crashSound" ];
crashSound = [[AVAudioPlayer alloc] initWithContentsOfURL:crashSoundFile error:NULL];
// code...
}
-(void) play // this is my "main" method that will be called once the playButton is pressed
{
while(!self.stopButonPressed)
{
[crashSound play];
}
}
#end

You're structuring your code wrong for how most multimedia libraries would work (including AVPlayer, for example).
Instead of having a while loop, you would start the sound playing, and check if your condition is true each time it completes. You may need to trigger the "next" thing based on a timer or in that callback when the sound completes, instead of having the loop.
See this question and good answer for an example of how to setup AVPlayer and the notification function playerItemDidReachEnd:: Looping a video with AVFoundation AVPlayer?

Related

NSSpeechRecognizer and .delegate=self; Problems

I've run into an issue with this little Objective-C project I'm doing and it's proving to be a bit of a roadblock. I'm playing around with Apple's NSSpeechRecognizer software on El Capitan, and I'm trying to get this guy running properly so that when the riddle I give it is posed to the user, the user can respond with a word to "do something cool". As it stands right now, the delegate method:
-(void) speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(NSString *)command { ... }`
is never even called, even though it appears the recognition icon is correctly detecting the answer to the riddle.
The problem is that your main function has a loop that is continually checking whether the speech has been recognizing. You are not giving NSSpeechRecognizer a chance to actually deliver any messages to you.
Your app needs to let the main "run loop" run, so it can deliver messages. Normally, in an OS X app, your main would just call NSApplicationMain, which does this for you.
Your code is effectively this:
#interface RecognizerDelegate : NSObject <NSSpeechRecognizerDelegate>
#property (nonatomic) NSSpeechRecognizer *recognizer;
#property (nonatomic) BOOL didRecognize;
#end
#implementation RecognizerDelegate
- (id)init
{
if ((self = [super init])) {
self.didRecognize = NO;
self.recognizer = [[NSSpeechRecognizer alloc] init];
self.recognizer.listensInForegroundOnly = NO;
self.recognizer.blocksOtherRecognizers = YES;
self.recognizer.delegate = self;
self.recognizer.commands = #[ #"hello" ];
[self.recognizer startListening];
}
return self;
}
- (void)speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(NSString *)command
{
self.didRecognize = YES;
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool
{
RecognizerDelegate *recognizerDelegate = [[RecognizerDelegate alloc] init];
while (recognizerDelegate.didRecognize == NO) {
// do nothing
}
NSLog(#"Recognized!");
}
return 0;
}
That while loop is doing nothing useful, just running your CPU in a loop and wasting time and energy. You are not letting any other code in NSSpeechSynthesizer, or any of the system frameworks like Foundation or AppKit, get the chance to do anything. So, nothing happens.
To fix this in the short term: you can let the main run loop run for a little while in each pass through the loop. This code would let the system run for a second, then would return to your code, so you could check again:
while (recognizerDelegate.didRecognize == NO) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
The longer-term fix would be to move your code out of main and to structure it like a real OS X app. Instead of using a loop to poll a condition like recognizerDelegate.didRecognize, you would just trigger the "next thing" directly from delegate methods like -speechRecognizer:didRecognizeCommand:, or you would use things like NSTimer to run code periodically.
For more details, see the Apple doc Cocoa Application Competencies for OS X, specifically the "Main Event Loop" section.
I had the same problem using NSSpeechRecognizer. The callback function:
func speechRecognizer(_ sender: NSSpeechRecognizer,
didRecognizeCommand command: String) {}
...was never called, even though everything appeared to be working.
There were three things I changed to get the code working.
1) I had to enable the entitlement in my "sandboxed" mode application to allow for microphone use.
... I also did these other two things, as well.
2) I added the "Privacy - Microphone Usage Description" in the info.pList, and set the string value to "I want to listen to you speak"
3) I added the "Privacy - Speech Recognition Usage Description" in the info.pList, and set the string value to "I want to write down what you say"

AVAudioPlayer Number Of Loops only taking effect after being played through once

I'm a little stumped by this weird occurrence:
I have a UIButton, which once tapped either sets a loop for an audio player, or resets it to 0 (no loop). Here is the method -
-(void)changeLoopValueForPlay:(int)tag toValue:(bool)value{
AVAudioPlayer *av = [self.playerArray objectAtIndex:tag];
if(value){
[av setNumberOfLoops:100];
[av prepareToPlay];
}
else{
[av setNumberOfLoops:0];
}
}
Now for some reason, the loop will only take effect after the player plays through the audio one time, meaning that the looping value doesn't take affect immediately, but the "numberOfLoops" value of the player is in fact set to 100 when I check its value before playing. I'm assuming this has something to do with the initialization or loading of the player, but I don't re-initialize it between those two plays (one without loop, the other with). Any idea why this is happening? If you want to see any other code please let me know.
This fixed the problem, however I feel as if this is a work-around instead of a direct solution. What I did is just create a new AVAudioPlayer with the numberOfLoops value set to whatever it is I wanted and replace that player with the existing player, instead of changing the value of the already existing player.
I workaround the issue by abandoning numberOfLoops altogether and doing my own logic instead.
First, set the delegate of the AVAudioPlayer:
self.audioPlayer.delegate = self;
Next, implement -audioPlayerDidFinishPlaying:successfully: of the delegate:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag
{
if(flag && <#(bool)i_want_to_repeat_playing#>)
{
[self.audioPlayer play];
}
}
Just replace <#(bool)i_want_to_repeat_playing#> with your desired logic, e.g., check if a counter has reached a certain threshold.

Where does the main-loop go when creating an iOS app?

I am writing an iOS app for the iPhone in Xcode and I have created some classes as well as their methods inside their respective .h and .m files (that's two classes so basically I have two pairs of .h & .m files)
I now I want to start writing my main loop that will be executed whenever the user hits on the play button, but where exactly do I do that ?
Do I do that in ViewController.m ? e.g. inside this method :
- (IBAction)playPressed:(UIButton *)sender
{
// main loop executed in here ?
// or simply message to the main loop to start executing is *sent* from here ?
}
I read about a similar question in here and someone was suggesting AppDelegate. Now would that be AppDelegate.m or AppDelegate.h ? And if that's the case do I just start writing code or do I include everything inside something like :
int main(int argc, char **argv)
{
....
}
in the Appdelegate file?
I tried to simply start instantiating classes and declaring generic methods (not belonging to any particular class that is..) in a game.m file I created and I get a initializer element is not a compile-time constant warning as soon as I try instantiating anything
Any help? Coming from c++ it would really help me to clarify once and for all in which file exactly to write my main loop and whether I should wrap it in some kind of an int main() function..
thanks!
PS :
Just in case it makes any difference, my ViewController will only consist of a play button that would start the execution of my main loop whenever its pressed, and a stop button that would terminate the execution of the main loop
I have created their respective methods in ViewController.m :
- (IBAction)playPressed:(UIButton *)sender
{
//
}
- (IBAction)stopPressed:(UIButton *)sender
{
// ??
}
which are for the time being empty :)
The programming methodoly on iOS is different from the C++ methodoly.
In C++ , indeed , you would have to make an infinite loop and get the touches , draw everything , etc at each frame.
Until the player presses "exit" and you break the loop.
On iOS , things are done differently:
You already have a main.m file in which you have a main function.
That starts up the app delegate. That app delegate tells you when the app finished launching , goes to background , comes in foreground , etc.
When the app finished launching , you go to your first actual screen.
There , you ADD subviews. You don't draw them at each frame. That is done automatically for you once you have added the view to a parent view.
The programming on iOS is based on events. You don't have to check for touches and see if the
touch location is on a button and then call the method of that button.
Instead , you set a callback method for the button and it's called automatically for you once the button is pressed.
Of course , you first need to alloc the button and add it to a parent view.
Once you get used to this event based programming model , you will for sure like it.
At the start it may seam very different and maybe it doesn't make sense to you , but don't worry.
Comming from a C++ background is surely a good start.
Cheers,
George
EDIT: In that case , I can give more specific info:
So , you go from the AppDelegate in your first screen. Let's call it MainAppScreen.
Now , you need to add those 2 buttons and set selectors ( callback methods ) for them. I can see you already did that.
Now :
- (IBAction)playPressed:(UIButton *)sender
{
running = TRUE;
[self performSelectorInBackground:#selector(myLoop) withObject:nil];
}
- (IBAction)stopPressed:(UIButton *)sender
{
running = FALSE;
}
- (void) myLoop
{
while(running)
{
//this is your loop. You can code in here.
}
}
Where running is an instance variable in the MainAppScreen class.
Hope this helps.
Cheers!
Every iOS app, as well as every executable file has an entry point - this is the main(). You can't have more than one entry points of an executable.And if you look closely into the project you will see that there is an automatically generated main.m file in the Supporting Files group in Xcode's navigator, which looks like this:
#import <UIKit/UIKit.h>
#import "MyAppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
}
}
What you want to do is not clear enough, but it's a good start reading about the structure and the lifecycle of iOS apps, objective-c syntax, get familiar with the UIKit and at least some of the frameworks Apple provide.
You don't have a main in iOS apps (well, technically you do have a main, but you don't have to worry about writing it). That's all handled for you. The runloop is all done for you too. All you have to do is create your button and then tell it (via addTarget method) which method to run when it gets pressed.
Update:
This is pseudo(ish) code for what you'd need to do....
[startButton addTarget:#selector(startPressed:)];
[stopButton addTarget:#selector(stopPressed:)];
-(void)startPressed {
backgroundThread = [[NSThread alloc] initWithWhateverYouWantToRun];
[backgroundThread start];
}
-(void)stopPressed {
[backgroundThread stop];
}
In your background thread, if you want to update the UI, you would call sendMessageOnMainThread (or something similar - can't remember the exact details at the moment!)

AVPlayer class events

Are there any delegate methods in AVPlayer class? I need to handle interruptions such as phone call etc. AVAudioPlayer supports. If AVPlayer doesn't support it, how to stream audio with AVAudioPlayer?
AVPlayer doesn't have the methods you want but you can use AVAudioSession object instead
1) Select AVAudioSession object (for example [AVAudioSession sharedInstance])
2) Set it active by calling setActive:error: method
3) Set its delegate (class implementing AVAudioSessionDelegate protocol)
4) Implement delegate's methods such as
-(void)beginInterruption;
-(void)endInterruptionWithFlags:(NSUInteger)flags;
-(void)endInterruption;
EDIT
I don't see any delegates available in AVPlayer class
So how to stream audio with AVAudioPlayer? Because we don't know how you need to stream it, and most important from where, providind some inspiration
see related questions:
stopping an AVAudioPlayer
Reusing an AVAudioPlayer for a different sound
avaudioplayer playingsong
Streaming with an AVAudioplayer
http://blog.guvenergokce.com/avaudioplayer-on-iphone-simulator/57/
http://www.iphonedevsdk.com/forum/iphone-sdk-development/15991-sample-code-avaudioplayer.html
and tutorial
http://mobileorchard.com/easy-audio-playback-with-avaudioplayer/
AVAudioPlayerDelegate Protocol Reference http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVAudioPlayerDelegateProtocolReference/Reference/Reference.html#//apple_ref/doc/uid/TP40008068
Responding to Sound Playback Completion
– audioPlayerDidFinishPlaying:successfully:
Responding to an Audio Decoding Error
– audioPlayerDecodeErrorDidOccur:error:
Handling Audio Interruptions
– audioPlayerBeginInterruption:
– audioPlayerEndInterruption:
– audioPlayerEndInterruption:withFlags:
I don't think AVPlayer will get you there. Take a look at AVAudioPlayerDelegate, The audioPlayerBeginInterruption would be the delegate method you are looking for.
Here's a sample of code I use for AVAudioPlayer (I'm assuming you already know how to build your url):
// Instantiates the AVAudioPlayer object, initializing it with the sound
NSError * errAV = nil;
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfUrl: mUrl error: &errAV];
if (newPlayer == nil) {
NSString * msg = [[NSString alloc] initWithFormat:#"An internal error has occured: %#", [errAV localizedDescription]];
UIAlertView *uiav = [[UIAlertView alloc] initWithTitle:#"Play Sound"
message:msg delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[uiav show];
[uiav release];
[msg release];
} else {
self.appSoundPlayer = newPlayer;
[newPlayer release];
// "Preparing to play" attaches to the audio hardware and ensures that playback
// starts quickly when the user taps Play
[appSoundPlayer prepareToPlay];
[appSoundPlayer setVolume: 1.0];
[appSoundPlayer setDelegate: self];
[appSoundPlayer play];
}
Even when using AVAudioPlayer, you can initialize an Audio Session, where in you can specify the kind of playback (or recording, for that matter) you will be doing, and a callback for handling interruptions like phone calls.
Have a look at AudioSessionInitialize() and it's third parameter, a callback function for handling interruptions. In your callback, you can handle both the start and end of an interruption.
The salient different here, between using an AudioSession and relying on the AVAudioPlayer callbacks, is that the former occurs at a lower level, perhaps before the latter's delegate methods are called. So with the AudioSession callback, you have finer control, I think, but then you have to do more, perhaps, depending on the complexity of your app's audio setup.
It has been a long while since the question was posted. However, for the sake of completion, I would like to add: AVPlayer can be used to handle interruptions by adding a TimeObserver as follows:
When initialising the AVPlayer:
AVPlayer *_aplayer;
id _aplayerObserver;
_aplayer = [[AVPlayer alloc] initWithURL:mediaURL];
_aplayerObserver = [_aplayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:NULL usingBlock:^(CMTime time)
{
if (((time.value/time.timescale) >= (_aplayer.currentItem.asset.duration.value/_aplayer.currentItem.asset.duration.timescale))
{
// media file played to its end
// you can add here code that should run after the media file is completed,
// thus mimicing AVAudioPlayer's audioPlayerDidFinishPlaying event
}
else
{
if (_aplayer.rate == 0)
// audio player was interrupted
}
}
If you choose this solution, please take note of what addPeriodicTimeObserverForInterval's documentation says:
You must retain the returned value [i.e. _aplayerObserver] as long as you want the time observer to be invoked by the player. Each invocation of this method should be paired with a corresponding call to removeTimeObserver:.

Is there any way i can group three classes?

What I want to do is I have many classes first of all, they all have the same music throughout, as i said in the app delegates bool application did finish launching method. But in my last 3 classes, I want different music, fair enough, I put these lines of code:
[(Smart2AppDelegate *)[UIApplication sharedApplication].delegate pauseAudioPlayer];
[(Smart2AppDelegate *)[UIApplication sharedApplication].delegate newAudioPlayer];
And in my app delegate:
-(void)newAudioPlayer {
NSString *music = [[NSBundle mainBundle]
pathForResource:#"win" ofType:#"m4a"];
audio.delegate = self;
self.audio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
[audio play];
audio.numberOfLoops = -1;
}
-(void)pauseAudioPlayer {
[audio pause];
}
So it works, whenever I go to that view, it changes music, lets call that view, view x. Now, from that view x I can go to and from only to 2 other views, e.g. I can go to the info page and there is a back button that leads back to that view x, and the same with a prize page. But when I go back to the view x, the music starts from the beginning, when in these three classes, i want them to all loop and not go from beginning because it sounds akward. The reason is simple it is because I put it in the viewDidLoad. But how can I do this, I was thinking of a way to actually group classes and put in the avaudioplayer method in there.
Here you have a possible approach:
Refactor out the music+sounds mechanisms into a separate class, i.e. something like Smart2AudioPlayer.
Public the necessary methods: play, pause, resume so you can use the audio player from anywhere.
In each viewDidLoad method, call the audio player and pass along a parameter to indicate who is the sender (who wants the sound to be played) and a preference indicating whether you wish to continue playing the current group song (see bellow) or start it all over again.
Implement the necessary logic in your audio player to allow certain groups of classes to be associated together. This way, when you are playing a song from a class that belongs to a group, and another class of the same group asks for the music to be played, you won't start the song again, you simply do nothing.
Hope this helps