AVAudioPlayer Freezes the UI when new sound is played - ios

I am new to iOS and I was searching fix for this issue since morning and didn't find the solution yet, I have more than 6000 sound files online which i want to play using AVAudioPlayer when user click on the cell, so i implemented it in didSelectRowAtIndexPath:
like this :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#", [soundsArray objectAtIndex:(int)indexPath.row]];
NSData *soundData = [NSData dataWithContentsOfURL:url];
_audioPlayer = [[AVAudioPlayer alloc] initWithData:soundData error:NULL];
[_audioPlayer play];
}
the problem is, whenever i click on any row, the whole UI freezes till the time it takes to load the complete sound file from internet,
for larger files , it takes longer.
I tried with AVPlayer and it is working fine, but I need AVAudioPlayer becouse it seems easy to me to work with
-(void)AVAudioPlayerDidFinishPlaying:(AVAudioPlayer *)Player successfully:(BOOL)flag
thats why I want to implement it with AVAudioPlayer
I hope you understood my problem,
Sorry for my bad English.

Never use
NSData *soundData = [NSData dataWithContentsOfURL:url];
this will block your UI.
You need to use NSURLSession to download data asynchronously.

Related

Xcode pre-load many audio files into one AVAudioPLayer?

I managed to load an audio file onto a player and play it via the following method:
- (void) play:(NSString*)loop{
NSString* resourcePath = [[NSBundle mainBundle] pathForResource:loop
ofType:#"mp3"];
player = [[AVAudioPlayer alloc] initWithContentsOfURL:
[NSURL fileURLWithPath:resourcePath]];
player.delegate = self;
[player play];
player.numberOfLoops = 1;
player.currentTime = 0;
player.volume = 1.0;
}
}
(I removed a couple of lines in this post but the method itself works great)
The problem here is, that the sound only gets loaded when the method play() is being called which results in a little delay in between the button click that calls the method and the player actually playing it.
I have about 200 sounds in my app and one way would be to load each sound in its own AVAudioPLayer in the viewDidLoad, but I doubt that this is considered good code.
On Android, I am using the SoundPool for this concern and it works great but I don't think there is an equivalent for this on iOS.
I now managed to do it via AudioServicesPlaySystemSound()
I use a method that loads all the sounds in a giant array
- (void)prepSounds{
NSString *soundPath =
[[NSBundle mainBundle] pathForResource:#"bl3"
ofType:#"mp3"];
NSURL *soundPathURL = [NSURL fileURLWithPath:soundPath];
AudioServicesCreateSystemSoundID(
(__bridge CFURLRef)soundPathURL, &soundID[0]);
AudioServicesPlaySystemSound(soundID[0]);
}
where soundID[] is an array of SystemSoundID declared in the header.
Now, I can just easily fire the sound via AudioServicesPlaySystemSound(soundID[0]) where 0 relates to the first sound I loaded. The latency is close to 0 (I would guess 5ms)
However, this only works for sounds no longer than about 30 seconds.
If you need any assistance with that, just comment here, I'm glad to help.

How to stop streaming mp3 file in UIWebview

I want to stop streaming mp3 file in UIWebview without reload page.
So I don't want reload page to stop. It's not best way. Does anyone suggest an idea for me ?
Thanks in advance !
You simply load blank page to stop streaming mp3 file.
Like this :
[self.yrwebContent loadRequest:NSURLRequestFromString(#"about:blank")];
UPDATE: Another solution, play sound using AVAudioPlayer instead
NSURL *url = [NSURL fileURLWithPath:self.sFilePath];
NSData *yrSound = [NSData dataWithContentsOfURL:url];
AVAudioPlayer *SoundPlayer = [[AVAudioPlayer alloc] initWithData:yrSound error:NULL];
SoundPlayer.numberOfLoops = 0;
[SoundPlayer play];
Then, you stop it by using :
[SoundPlayer stop];

Start playing SoundCloud audio stream in iOS by using a pre-buffer

Using the following code example from SoundCloud Developers page, the AVAudioPlayer will start playing after the SCRequest response has been received. Depending on the size of the requested file, this might take some time.Does the iOS SoundCloud API offer a pre-buffer solution, so that it would be possible to start playing audio before all data has been received or do I need to implement a own solution with help of NSURLConnection in order to achieve this?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *track = [self.tracks objectAtIndex:indexPath.row];
NSString *streamURL = [track objectForKey:#"stream_url"];
SCAccount *account = [SCSoundCloud account];
[SCRequest performMethod:SCRequestMethodGET
onResource:[NSURL URLWithString:streamURL]
usingParameters:nil
withAccount:account
sendingProgressHandler:nil
responseHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSError *playerError;
player = [[AVAudioPlayer alloc] initWithData:data error:&playerError];
[player prepareToPlay];
[player play];
}];
}
To stream tracks from SoundCloud, all you need to do is pass the URL to AVPlayer with the client id:
NSString *urlString = [NSString stringWithFormat:#"%#?client_id=%#", track.streamURL, self.clientId];
player = [[AVPlayer alloc] initWithURL:[NSURL URLWithString:urlString]];
[player play];
It will take a bit of extra work to make it a progressive download. Hope that helps.
afaik there is no pre-buffer solution at the moment. If you want to contribute to our SoundCloud API we'd love to review a Pull Request from you regarding this feature.
This will probably affect CocoaSoundCloudAPI and OAuth2Client.
Happy Coding!

AVAudioPlayer working fine in 5.0, issues in 4.0-4.3.5

I have an app which plays and controls music across different ViewControllers. To do this, I created two instances of AVAudioPLayer in the app delegate's DidFinishLaunchingMethod:
NSString *path = [[NSBundle mainBundle] pathForResource:#"minnie1" ofType:#"mp3"];
NSURL *path1 = [[NSURL alloc] initFileURLWithPath:path];
menuLoop =[[AVAudioPlayer alloc] initWithContentsOfURL:path1 error:NULL];
[menuLoop setDelegate:self];
menuLoop.numberOfLoops = -1;
[menuLoop play];
NSString *path2 = [[NSBundle mainBundle] pathForResource:#"minnie2" ofType:#"mp3"];
NSURL *path3 = [[NSURL alloc] initFileURLWithPath:path2];
gameLoop=[[AVAudioPlayer alloc] initWithContentsOfURL:path3 error:NULL];
gameLoop.numberOfLoops = -1;
[gameLoop setDelegate:self];
[gameLoop prepareToPlay];
After this I call it in various viewControllers, to stop or restart using code like:
- (IBAction)playGameLoop{
NSLog(#"Begin playGameLoop");
FPAppDelegate *app = (FPAppDelegate *)[[UIApplication sharedApplication] delegate];
if ([FPAVManager audioEnabled] == NO){
//DO NOTHING
}
else {
if (app.gameLoop.playing ==YES ) {
//DO NOTHING
}
else { [app.gameLoop play];
NSLog(#"End playGameLoop");
}
}
The audio files play fine the first time and they stop when asked to stop. Though, on iOS4 devices, they won't start replaying when called again.
Thanks!
From the docs:
Calling this method [stop] or allowing a sound to finish playing,
undoes the setup performed upon calling the play or prepareToPlay
methods.
The stop method does not reset the value of the currentTime property
to 0. In other words, if you call stop during playback and then call
play, playback resumes at the point where it left off.
Where you are looping the sounds, I would expect to be able to call stop and then call play. But I have fuzzy recollection of running into this myself. I found that calling pause, rather than stop, was a better solution. Especially since I could then call play again to resume play. Calling stop always seemed to require calling prepareToPlay again before calling play, at least in my experience.
It's possible that there were some changes in the API implemetation between iOS4.x and iOS5, too. You should check the API diffs on the Developer web site to be sure.

iPhone AVAudioPlayer app freeze on first play

I'm using AVAudioPlayer from iOS SDK for playing short sounds on each click in tableView rows.
I have manually made #selector on button in each row that fires up method playSound:(id)receiver {}. From receiver I get sound url so I can play it up.
This method looks like that:
- (void)playSound:(id)sender {
[audioPlayer prepareToPlay];
UIButton *audioButton = (UIButton *)sender;
[audioButton setImage:[UIImage imageNamed:#"sound_preview.png"] forState:UIControlStateNormal];
NSString *soundUrl = [[listOfItems objectForKey:[NSString stringWithFormat:#"%i",currentPlayingIndex]] objectForKey:#"sound_url"];
//here I get mp3 file from http url via NSRequest in NSData
NSData *soundData = [sharedAppSettingsController getSoundUrl:defaultDictionaryID uri:soundUrl];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithData:soundData error:&error];
audioPlayer.numberOfLoops = 0;
if (error) {
NSLog(#"Error: %#",[error description]);
}
else {
audioPlayer.delegate = self;
[audioPlayer play];
}
}
Everything works fine except for the first play of some sound. The application freezes for about 2 seconds and than sound is played. Second and every other sound play works just right after click on sound button.
I wonder why there is that about 2 seconds freeze on first play when application starts?
From your code snippet, audioPlayer must be an ivar, right?
At the top of the method, you invoke -prepareToPlay on the existing audioPlayer instance (which could be nil, at least on the first pass).
Later in the method, you replace this existing audio player with a brand new AVAudioPlayer instance. The previous -prepareToPlay is wasted. And you're leaking memory with each new AVAudioPlayer.
Instead of caching sound data or URLs, I would try creating a cache of AVAudioPlayer objects, one for each sound. In your -playSound: method, get a reference to the appropriate audio player for the table row, and -play.
You can use -tableView:cellForRowAtIndexPath: as the appropriate point to get the AVAudioPlayer instance for that row, maybe lazily creating the instance and caching it there as well.
You can try -tableView:willDisplayCell:forRowAtIndexPath: as the point where you would invoke -prepareToPlay on the row's AVAudioPlayer instance.
Or you could just do the prepare in -tableView:cellForRowAtIndexPath:. Experiment and see which works best.
Check whether you are getting data asynchronously in function..
NSData *soundData = [sharedAppSettingsController getSoundUrl:defaultDictionaryID uri:soundUrl];
If you are getting asynchronously the execution will be blocked until it will get data.
If your audio is less than 30 seconds long in length and is in linear PCM or IMA4 format, and is packaged as a .caf, .wav, or .aiff you can use system sounds:
Import the AudioToolbox Framework
In your .h file create this variable:
SystemSoundID mySound;
In your .m file implement it in your init method:
-(id)init{
if (self) {
//Get path of VICTORY.WAV <-- the sound file in your bundle
NSString* soundPath = [[NSBundle mainBundle] pathForResource:#"VICTORY" ofType:#"WAV"];
//If the file is in the bundle
if (soundPath) {
//Create a file URL with this path
NSURL* soundURL = [NSURL fileURLWithPath:soundPath];
//Register sound file located at that URL as a system sound
OSStatus err = AudioServicesCreateSystemSoundID((CFURLRef)soundURL, &mySound);
if (err != kAudioServicesNoError) {
NSLog(#"Could not load %#, error code: %ld", soundURL, err);
}
}
}
return self;
}
In your IBAction method you call the sound with this:
AudioServicesPlaySystemSound(mySound);
This works for me, plays the sound pretty damn close to when the button is pressed. Hope this helps you.
This sometimes happens in the simulator for me too. Everything seems to work fine on the device. Have you tested on actual hardware?

Resources