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
Related
I know very little about using background threads, but this seems to play my sound in the way that I need it to as follows:
1) I need this very short sound effect to play repeatedly even if the sound overlaps.
2) I need the sound to be played perfectly on time.
3) I need the loading of the sound to not affect the on-screen graphics by stuttering.
I am currently just trying out this method with one sound, but if successful, I will roll it out to other sound effects that need the same treatment. My question is this: Am I using the background thread properly? Will there be any sort of memory leaks?
Here's the code:
-(void) playAudio {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSString *path = [NSString stringWithFormat:#"%#/metronome.mp3", [[NSBundle mainBundle] resourcePath]];
NSURL *metronomeSound = [NSURL fileURLWithPath:path];
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:metronomeSound error:nil];
[_audioPlayer prepareToPlay];
[_audioPlayer play];
});
}
//handles collision detection
-(void) didBeginContact:(SKPhysicsContact *)contact {
uint32_t categoryA = contact.bodyA.categoryBitMask;
uint32_t categoryB = contact.bodyB.categoryBitMask;
if (categoryA == kLineCategory || categoryB == kLineCategory) {
NSLog(#"line contact");
[self playAudio];
}
}
I use the AVAudioPlayer and use it asynchronously and in background threads without any problems and no leaks as far as I can tell. However, I have implemented a singleton class that handles all the allocations and keeps an array of AVAudioPlayer instances that also play asynchronously as needed. If you need to play a sound repeatedly, you should allocate an AVAudioPlayer instance for every time you want to play it. In that case, latency will be negligible and you can even play the same sound simultaneously.
Concerning your strategy I think it needs some refinements, in particular if you want to prevent any delays. The main problem is always reading from disk, which is the slowest operation of all and your limiting step.
Thus, I would also implement an array of AVAudioPlayers each already initialized to play a specific sound, in particular if this sound is played often and repeatedly. You could remove those instances of players that are played less often from the array if memory starts to grow and reload them a few seconds before if you can tell which ones will be needed.
And one more thing... Don't forget to lock and unlock the array, if you are going to access it from multiple threads or better yet, create a GCD queue to handle all accesses to the array.
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.
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?
We are using a UInavigationController with one of the views being one that plays soundtracks.
We are using the AVfountation framework.
We navigate to audiolist(where music is played) on button click
-(IBAction)audioBtnClicked
{
audiolist *audio=[[audiolist alloc] initWithNibName:#"audiolist" bundle:nil];
[self.navigationController pushViewContrller:audio animated:YES];
[audiolist release];
}
When the user plays music and navigates away, the music continues playing
Problem: when the user navigates back to the list of songs and plays another track,
2 songs are playing at the same time.
We think that a new instance of audiolist is created everytime the user navigates back.
We would like to only have one instance of audiolist. How do we make the first instance of audiolist persistent and how do we refer back to it?
You have to do
[audio release];
and not
[audiolist release];
audiolist seems to be your class name. The naming conventions says that classes should be capital and camel-cased, so it should be AudioList. It'll make your code more readable.
To have one single shared instance of your AudioList, you could do this:
Add a class method to your header:
+ (AudioList *) sharedInstance;
then add this to your implementation file:
#implementation AudioList
static AudioList *gSharedInstance = nil;
+ (AudioList *) sharedInstance {
if (gSharedInstance == nil) {
gSharedInstance = [[AudioList alloc] init];
}
return gSharedInstance;
}
Now you can always access that instance with
[AudioList sharedInstance];
Cheers.
Create a singleton AudioManager class that handles all your music playback. When you navigate to that view controller, grab the shared instance of that AudioManager to do whatever you do in that view.
You don't want the view to persist as that would be bad separation of MVC.
Apple has some discussion of different design patterns and their objective-c implementations, including the singleton:
http://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html#//apple_ref/doc/uid/TP40002974-CH6-SW6
Trying to implement AVAudioplayer and get some metering data of the played music, but still getting value -160.
It looks easy to use, just enable Meter and then pickup data under a timer, but no results so far.
playerAV = [[AVAudioPlayer alloc] initWithContentsOfURL:outURL error:nil];
playerAV.delegate = self;
playerAV.meteringEnabled = YES;
[playerAV prepareToPlay];
[playerAV play];
NSLog(#"Peak left: %f Avg right: %f", [playerAV peakPowerForChannel:0],[playerAV averagePowerForChannel:1]);
any thoughts are welcome.
Are you calling updateMeters? The docs for both peakPowerForChannel: and averagePowerForChannel: say:
To obtain a current [...] value, you must call the updateMeters method before calling this method.
How do you know the url works, and the data at the end of it is the right format? Can you hear stuff coming from the speakers?
Are you somehow making two playerAV objects, one with the correct URL which plays, the other with a bad url which plays nothing, and is the one that the timer sees when it calls NSLog()?