iPhone AVAudioPlayer app freeze on first play - ios

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?

Related

Simply Playing A Sound in iOS

I'm trying to play a sound when a button is clicked. First, I imported the AudioToolbox in the header file, then added the code into the IBAction that fires when the button is pressed, like so:
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)button1:(id)sender {
NSString *soundPath = [[NSBundle mainBundle] pathForResource:#"mySong" ofType:#"wav"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:soundPath], &soundID);
AudioServicesPlaySystemSound (soundID);
}
I even threw an NSLog in the method to make sure it's firing. It is. I also but in a foo path in the string to see if it would crash, it did. I made sure that I dragged and dropped the short sound file into the app as well. Tested my speakers, tested audio on the device simulator by going to youtube on the simulator, everything. I can't figure out what I'm doing wrong.
When I changed the code to this:
- (IBAction)button1:(id)sender {
AVAudioPlayer *testAudioPlayer;
// *** Implementation... ***
// Load the audio data
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"3" ofType:#"wav"];
NSData *sampleData = [[NSData alloc] initWithContentsOfFile:soundFilePath];
NSError *audioError = nil;
// Set up the audio player
testAudioPlayer = [[AVAudioPlayer alloc] initWithData:sampleData error:&audioError];
if(audioError != nil) {
NSLog(#"An audio error occurred: \"%#\"", audioError);
}
else {
[testAudioPlayer setNumberOfLoops: -1];
[testAudioPlayer play];
}}
I get the error: WARNING: 137: Error '!dat' trying to set the (null) audio devices' sample rate
Hi as per you question can you please check whether you audio file is present in you project folder.It might happen that u have forgotten to checkmark "copy item if needed" so it might not be there in your project file.
You can also play sounds provided in device. Visit the below link it will be helpful.
https://github.com/TUNER88/iOSSystemSoundsLibrary
I've tested your code and it working fine and not getting crashed. Here's what I did.
As said in #matt's comment, sound playback fails in the Simulator. I had this problem when trying to play back a video.
The funny part is that sound plays just fine for the same video inside the Photos app.

How to tell that AVAudioPlayer has finished playing

I'm developing an app that plays sounds using AVAudioPlayer, and I want to know when a sound has finished playing. I want to change an image when the sound stops playing
Here's the code I use to create the player:
NSURL* url = [[NSBundle mainBundle] URLForResource:#"LOLManFace1" withExtension:#"mp3"];
NSAssert(url, #"URL is valid.");
NSError* error = nil;
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&url];error;
if(!self.player)
{
NSLog(#"Error creating player: %#", error);
}
And here's the code I use to play the sound:
[self.player play];
How can I tell that playback has finished?
Thank you so much in advance!
Look at the AVAudioPlayerDelegate protocol which has a method to tell the delegate when audio playback has finished among other things, specifically what you are looking for is - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag, here is a reference
Hope it helps
Daniel
Long time ago and I am pretty sure you figured out at one point. If someone reads this post:
1) Your class should implement the AVAudioPlayerDelegate. (https://developer.apple.com/documentation/avfoundation/avaudioplayerdelegate)
One of the delegate methods is:
audioPlayerDidFinishPlaying:successfully:
2) Set delegate:
self.player.delegate = self

play 15 second mp3 ios

I have tried everything I can find to try and get my app to play an mp3. I have a few 2-3 second mp3s that play as sound effects that work, but I am trying to play a 15s mp3 without any luck.
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[[NSBundle mainBundle] URLForResource: #"test" withExtension: #"mp3"] error:&error];
if (error) {
NSLog(#"Error creating audio player: %#", [error userInfo]);
} else {
if([audioPlayer play] == FALSE) {
NSLog(#"Fail %#", error);
}
}
NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
selector: #selector(stopMusic) userInfo: nil repeats: NO];
I doubt the timer has anything to do with it but thought I would include it just in case. Basically my error checks are not getting triggered at all. It "should" be playing properly but no sound is coming out, and no errors are being displayed.
I have tried a few other things to try and get this working, and I have tried without the timer. I can't find a decent guide. I don't need a full blown media player, just some background music for an UIScreenViewController.
I have also tried the following code, which works good with short MP3s, not with the longer MP3 I want to play.
NSString *path = [[NSBundle mainBundle] pathForResource:#"bgmusic" ofType:#"mp3"];
NSURL *pathURL = [NSURL fileURLWithPath : path];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)pathURL, &audioEffect);
AudioServicesPlaySystemSound(audioEffect);
Please help, I have spent hours trying to sort this. It seems apple re-jig their audio every time they release an OS.
I did a little testing for you: It turned out that music files aren't added to target by default in Xcode 5. Therefore URLForResource:error: likely returns nil.
Try removing the mp3 from your project. Then add it again and this time make shure you add it to your target:
The other thing is: It seems like you're defining the AVAudioPlayer inside a method. You normally set up a #property for your audio player because otherwise its life is limited to the method - and the music stops playing after you reach the end of the method.

Creating AVAudioPlayer object much before playing the dynamic audio

Whenever i play an audio file like below, it is taking few seconds to start playing the audio. I have seen from some forums and understand that, AVAudioPlayer will take few seconds to start playing if we allocate the object there itself. I am thinking to allocate this object much earlier (may be Appdelegate itself), before when i want to play, so that when i want to play it, it will play immediately.
NSURL *audioPathURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:audioName ofType:#"wav"]];
audioP = [[AVAudioPlayer alloc]initWithContentsOfURL:audioPathURL error:NULL];
[appDelegate.audioP play];
but, i am passing audio url dynamically at run time, so i would like to know how can i allocate the object earlier and then be able to pass dynamic audio url path later?
Please note, my question is different from this question Slow start for AVAudioPlayer the first time a sound is played
In the existing question, they are telling about one audio file and play it when required. But, i am having many different audio file and url path is generated randomly and at run time to play, so i do not know what the actual url path to set and play. So, the answer mentioned here->Slow start for AVAudioPlayer the first time a sound is played
won't help for my question.
I can't use prepareToPlay, because audio path url is set at run time and not just one audio is being used for all the times to play, there will more than 20 audio file randomly chosen and set to play one at a time. So, i need the right answer for it, it is not a duplicate question.
Thank you!
For a single file here the solution:
in your file.h
#import <AVFoundation/AVFoundation.h>
#interface AudioPlayer : UIViewController <AVAudioPlayerDelegate> {
}
To use with button add - (IBAction)Sound:(id)sender; in your file.h
Now in your file.m
//this is a action but you can use inside a void to start auto
#implementation AudioPlayer {
SystemSoundID soundID;
}
- (IBAction)Sound:(id)sender {
AudioServicesDisposeSystemSoundID(soundID);
CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef soundFileURLRef;
//you can use here extension .mp3 / .wav / .aif / .ogg / .caf / and more
soundFileURLRef = CFBundleCopyResourceURL(mainBundle, (CFStringRef) #"sound" ,CFSTR ("mp3") , NULL);
AudioServicesCreateSystemSoundID(soundFileURLRef, &soundID);
AudioServicesPlaySystemSound(soundID);
}
If you want use your code to play audio from URL you can use this:
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#interface AudioPlayer : UIViewController <AVAudioPlayerDelegate> {
AVPlayer *mySound;
}
#property (strong, nonatomic) AVPlayer *mySound;
#end
And file.m
- (IBAction)playPause:(id)sender {
if (mySound.rate == 0){
NSURL *url = [NSURL URLWithString:#"http://link.mp3"];
mySound = [[AVPlayer alloc] initWithURL:url];
[mySound setAllowsExternalPlayback:YES];
[mySound setUsesExternalPlaybackWhileExternalScreenIsActive: YES];
[mySound play];
} else {
[mySound pause];
}
}
Hope this can help you!
One thing you can do to reduce the latency of the first load+play is to "spin up" the underlying audio system before you need it by loading and playing a dummy file. For example, in application:didFinishLaunchingWithOptions: add some code like this:
// this won't effect the problem you're seeing, but it's good practice to explicitly
// set your app's audio session category
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
NSURL *audioURL = [[NSBundle mainBundle] URLForResource:#"dummy.wav" withExtension:nil];
AVAudioPlayer *dplayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:nil];
// we don't want to hear it (alternatively, the audiofile you use for this purpose can be silence.)
dplayer.volume = 0.f;
[dplayer prepareToPlay];
[dplayer play];
[dplayer stop];
Once this code completes, subsequent instantiations and playback of AVAudioPlayer instances should have pretty low latency (certainly well under 1 second)

AudioServicesPlaySystemSound not playing except when I step over

I'm writing an alarm clock app and I need to have the user select the chime to wake up to. I have a UITableView with a list of my sounds and when the user taps a sound I want it to play.
However, my sounds do not play unless I step over the call to AudioServicesPlaySystemSound (or if I put a breakpoint on the call to AudioServicesDisposeSystemSoundID which triggers after I hear the sound).
I had a theory that this was a main thread issue so I used performSelectorOnMainThread:
[self performSelectorOnMainThread:#selector(playWakeUpSound)
withObject:nil
waitUntilDone:true];
but that didn't help. (The value of waitUntilDone doesn't seem to matter.)
This question was promising but I checked and I'm only calling my method once.
This is only a guess, since I don't know what is in playWakeUpSound. If you are using AVAudioPlayer within that call and ARC, it might be because it is getting released. Store the instance of AVAudioPlayer as a member of the class and it should play.
#interface MyClass
{
AVAudioPlayer *player;
}
#end
Then in the implementation:
#implementation MyClass
- (void)playWakeUpSound {
// Assuming name and extension is set somewhere.
NSURL* musicFile = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:name ofType:extension]];
NSError* error = nil;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:musicFile error:&error];
if (error) {
NSLog(#"%#", error.localizedDescription);
} else {
[player play];
}
}
#end

Resources