Playing 2 sounds with AVAudioPlayer without sleep(); - avaudioplayer

I have this 2 sounds playing in a scrollview using AVAudioPlayer.
The problem is that the sleep() is creating lags and I need an alternative to playing two sounds one after the other more easily without the sleep function.
PS: the name of the file is given by the page in the scrollView in which the user is.
if( nextPage!=6 ) {
[scrMain scrollRectToVisible:CGRectMake(0, nextPage*250, scrMain.frame.size.width, scrMain.frame.size.height) animated:YES];
pgCtr.currentPage=nextPage;
NSString *path;
NSError *error;
path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"pas%02i",nextPage+1] ofType:#"m4a"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
{
player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
player.volume = 0.5f;
[player prepareToPlay];
[player setNumberOfLoops:0];
[player play];
}
sleep(1);
NSString *path2;
NSError *error2;
path2 = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"passun%02i",nextPage+1] ofType:#"m4a"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path2])
{
player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path2] error:&error2];
player.volume = 0.5f;
[player prepareToPlay];
[player setNumberOfLoops:0];
[player play];
}
How can I do this?

Related

Repeating Audio in WatchKit (AVAudioPlayer?)?

I am wanting to loop a local audio file in my Apple Watch App. Currently I am using AVAudioPlayerNode and AVAudioEngine which works well but I cannot figure out how to loop the sound.
I noticed that I can use AVAudioPlayer, which has the handy "numberOfLoops" but, for some reason AVAudioPlayer is not working on the watch. I have no idea why.
Here is my current code to play a sound:
_audioPlayer = [[AVAudioPlayerNode alloc] init];
_audioEngine = [[AVAudioEngine alloc] init];
[_audioEngine attachNode:_audioPlayer];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_audioEngine connect:_audioPlayer to:_audioEngine.mainMixerNode format:stereoFormat];
if (!_audioEngine.isRunning) {
NSError* error;
[_audioEngine startAndReturnError:&error];
}
NSError *error;
NSBundle* appBundle = [NSBundle mainBundle];
NSURL *url = [NSURL fileURLWithPath:[appBundle pathForResource:#"FILE_NAME" ofType:#"mp3"]];
AVAudioFile *asset = [[AVAudioFile alloc] initForReading:url error:&error];
[_audioPlayer scheduleFile:asset atTime:nil completionHandler:nil];
[_audioPlayer play];
Here is the code i've tried to use for AVAudioPlayer, but does not work:
NSError *audioError;
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FILE_NAME" ofType:#"mp3"]] error:&audioError];
player.numberOfLoops = MAXFLOAT;
player.delegate = self;
[player play];
I am using WatchKit 5.0(+).
You can loop your AVAudioFile by recursively scheduling it:
__block __weak void (^weakSheduleFile)(void);
void (^scheduleFile)(void);
weakSheduleFile = scheduleFile = ^{ [self->_audioPlayer scheduleFile:asset atTime:nil completionHandler:weakSheduleFile]; };
scheduleFile();
I'm not sure if this will be a seamless loop. If it's not, you can try always having two files scheduled:
scheduleFile();
scheduleFile();
If your audio file fits into memory, you could schedule playback as an AVAudioBuffer with the AVAudioPlayerNodeBufferLoops option (N.B. only tested on simulator!):
AVAudioFormat *outputFormat = [_audioPlayer outputFormatForBus:0];
__block AVAudioPCMBuffer *srcBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:asset.processingFormat frameCapacity:(AVAudioFrameCount)asset.length];
if (![asset readIntoBuffer:srcBuffer error:&error]) {
NSLog(#"Read error: %#", error);
abort();
}
AVAudioPCMBuffer *dstBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)asset.length];
AVAudioConverter *converter = [[AVAudioConverter alloc] initFromFormat:srcBuffer.format toFormat:dstBuffer.format];
AVAudioConverterOutputStatus status = [converter convertToBuffer:dstBuffer error:&error withInputFromBlock:^AVAudioBuffer * _Nullable(AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus * _Nonnull outStatus) {
if (srcBuffer) {
AVAudioBuffer *result = srcBuffer;
srcBuffer = NULL;
*outStatus = AVAudioConverterInputStatus_HaveData;
return result;
} else {
*outStatus = AVAudioConverterInputStatus_EndOfStream;
return NULL;
}
}];
assert(status != AVAudioConverterOutputStatus_Error);
[_audioPlayer scheduleBuffer:dstBuffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[_audioPlayer play];

Loading an audio sample from the documents folder

I'm trying to have an app play audio files added via itunes file-sharing. I've managed the app to retrieve the app's sandbox folder's content, but I'm not able to load such files into a C4Sample by specifying the complete path.
NSString *documentsFolder;
NSString *clickAudioPath;
C4Sample *clicksample;
-(void)setup {
// Retrieve the app's Documents folder
documentsFolder = [self applicationDocumentsDirectory];
clickAudioPath = [NSString stringWithFormat:#"%#/click.mp3", documentsFolder];
// Add test click audio
clicksample = [C4Sample sampleNamed:clickAudioPath];
[clicksample prepareToPlay];
[clicksample play];
}
// Get Documents folder
- (NSString *) applicationDocumentsDirectory{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
The above code doesn't play any sound, although I've doubled checked that clicksample actually refers to an existing file. How can I specify a complete path instead of just a name to load the audio?
Add a new method as below.
-(id) initWithURL:(NSURL *) soundFileURL
{
self = [super init];
if(self != nil) {
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
self.enableRate = YES;
self.player.delegate = self;
[self setup];
}
return self;
}

AVAudioPlayer sounds garbled on iPhone without headphones

I have 23 music files (10-20 seconds in length) that I want to play when the user selects them and presses the play button. It works find on my iPad and on the simulators, with or without earphones. But on the iPhone device without earphones, around 80% of the files sound garbled, the volume is low, and some are barely legible. I can plug the earphones in, and they sound fine. As soon as I unplug the earphones, the sounds are garbled again.
Since some of the files always play fine, I converted all the original mp3's that I was using into the AAC format with a sample rate of 44100. But the same files play fine, while most are distorted when playing on the iphone device.
I've tried several settings for AVAudioSession category: playback, playAndRecord, ambient. I've tried storing the files in core data and using initWithData instead of initWithContentsOfURL. Can anyone suggest what else I might try? Or how I might get clues into why I'm having this problem?
Here is my code. There are 3 relevant files. The ViewController that has a button to play a selected music file, an AVPlaybackSoundController that has all the audio logic, and a Singleton that has an array with the filenames.
My ViewController creates an AVPlaybackSoundController through the Nib.
IBOutlet AVPlaybackSoundController *avPlaybackSoundController;
#property (nonatomic, retain) AVPlaybackSoundController *avPlaybackSoundController;
- (IBAction) playButtonPressed:(id)the_sender
{
self.currentRhythmSampleIndex = arc4random() % [[CommonData sharedInstance].rhythmSampleArray count];
[self.avPlaybackSoundController playMusic:self.currentRhythmSampleIndex];
self.playBtn.hidden=YES;
self.pauseBtn.hidden=NO;
}
AVPlaybackSoundController.m
- (void) awakeFromNib
{
[self initAudioSession:AVAudioSessionCategoryPlayback];
[self initMusicPlayer];
}
- (void) initAudioSession:(NSString *const)audioSessionCategory
{
NSError* audio_session_error = nil;
BOOL is_success = YES;
is_success = [[AVAudioSession sharedInstance] setCategory:audioSessionCategory error:&audio_session_error];
if(!is_success || audio_session_error){
NSLog(#"Error setting Audio Session category: %#", [audio_session_error localizedDescription]);
}
[[AVAudioSession sharedInstance] setDelegate:self];
audio_session_error = nil;
is_success = [[AVAudioSession sharedInstance] setActive:YES error:&audio_session_error];
if(!is_success || audio_session_error){
NSLog(#"Error setting Audio Session active: %#", [audio_session_error
localizedDescription]);
}
}
- (void) initMusicPlayer
{
NSError* file_error = nil;
NSURL* file_url = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"musicFile1" ofType:#"m4a"]
isDirectory:NO];
avMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:file_url
error:&file_error];
if(!file_url || file_error){
NSLog(#"Error loading music file: %#", [file_error
localizedDescription]);
}
self.avMusicPlayer.delegate = self;
self.avMusicPlayer.numberOfLoops = 0xFF;
[file_url release];
}
-(void) playMusic:(NSUInteger)rhythmSampleIdx
{
CommonData *glob = [CommonData sharedInstance];
if (rhythmSampleIdx > [glob.rhythmSampleArray count])
return; // bug if we hit here
// if we were already playing it, just resume
if ((self.avMusicPlayer) && (rhythmSampleIdx == self.currentRhythmSampleIndex)){
[self.avMusicPlayer play];
}
else{ // re-init player with new rhythm
if (self.avMusicPlayer){
[self.avMusicPlayer stop];
[self.avMusicPlayer setCurrentTime:0.0];
}
NSError* error = nil;
RhythmSample *rhythmSample = [glob.rhythmSampleArray objectAtIndex:rhythmSampleIdx];
NSString* rhythm_filename = [self getRhythmFileName:rhythmSampleIdx];
NSURL* file_url = [[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle]
pathForResource:rhythm_filename ofType:#"m4a"] isDirectory:NO]autorelease];
self.avMusicPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:file_url error:&error]autorelease];
[self.avMusicPlayer prepareToPlay]; // shouldn't be needed since we are playing immediately
if(error){
NSLog(#"Error loading music file: %#", [error localizedDescription]);
}else{
self.avMusicPlayer.delegate = self;
self.avMusicPlayer.numberOfLoops = 0xFF; // repeat infinitely
self.currentRhythmSampleIndex = rhythmSampleIdx;
[self.avMusicPlayer play];
}
}
}
I finally decided to obtain the sound files from a different source. The new sound files play fine. I still have no theory as to why the old sound files would play with earphones, but not without them. I really dislike finding solutions without understanding why they work.

Loading database at start up slows down the app

i m not sure how to describe this as i m new with all the developing and i m really looking forward to an answer from you guys. I know you can be very busy but please try to HELP me!
Here it goes. I have an app that loads a very large database (although it only has 100 entries it contains HiRes images (100MB) ).
At start up a tableview presents the rows -records (using only 3 attributes from the database). However it seems that the WHOLE database (including the images) is loaded at start up!
IS THERE A WAY TO ONLY LOAD THE 3 attributes (something like "select") when the app starts and then when the user moves to didselectrowatindexpath load the rest of the record?
Because i have NO IDEA where to look or what to do i would appreciate some coding help!
here is the code i m using:
#pragma mark -
#pragma mark App support
- (NSFetchedResultsController *)resetFetchedResultsController:(NSPredicate *)predicate cached:(BOOL)cached
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Records" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *partDescriptor = [[NSSortDescriptor alloc] initWithKey:#"displayOrder" ascending:YES];
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: partDescriptor, nameDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
if (predicate != nil)
[fetchRequest setPredicate:predicate];
NSString *cacheName = nil;
if (cached)
cacheName = #"Root";
NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:cacheName] autorelease];
aFetchedResultsController.delegate = self;
[fetchRequest release];
[partDescriptor release];
[nameDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return aFetchedResultsController;
}
- (void)showRecords:(Records *)records animated:(BOOL)animated {
.
RecordsDetailViewController *detailViewController = [[RecordsDetailViewController alloc] initWithStyle:UITableViewStyleGrouped];
detailViewController.records = records;
[self.navigationController pushViewController:detailViewController animated:animated];
[detailViewController release];
}
#pragma mark -
#pragma mark Table view methods
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.backgroundColor = [UIColor lightGrayColor];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSInteger count = [[fetchedResultsController sections] count];
if (count == 0) {
count = 1;
}
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger numberOfRows = 0;
if ([[fetchedResultsController sections] count] > 0) {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
numberOfRows = [sectionInfo numberOfObjects];
}
return numberOfRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *RecordCellIdentifier = #"RecordCellIdentifier";
RecordTableViewCell *recordCell = (RecordTableViewCell *)[tableView dequeueReusableCellWithIdentifier:RecordCellIdentifier];
if (recordCell == nil) {
recordCell = [[[RecordTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:RecordCellIdentifier] autorelease];
recordCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
[self configureCell:recordCell atIndexPath:indexPath];
return recordCell;
}
- (void)configureCell:(RecordTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// Configure the cell
Records *records = (Records *)[fetchedResultsController objectAtIndexPath:indexPath];
cell.records = records;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.searchDisplayController.isActive)
[self.tableView reloadData];
Records *records = (Records *)[fetchedResultsController objectAtIndexPath:indexPath];
[self showRecords:records animated:YES];
}
//this is from the RecordTableViewCell.m to show you the attributes i m using:
#pragma mark -
#pragma mark Record set accessor
- (void)setRecord:(Record *)newRecord {
if (newRecord != record) {
[record release];
record = [newRecord retain];
}
imageView.image = [UIImage imageNamed:#"icon.png"];
nameLabel.text = record.name;
overviewLabel.text = record.overview;
partLabel.text = record.part;
}
thanks again...
I would separate the large file from the metadata, because I like to have the freedom to manage these expensive resources seParately. Then I could store them differently, eg filesystem, or http server. This allows me to cache them or send them to remote locAtions proactively to reduce the download times
The rest of the table can then fit in less blocks in the database so less disk access is needed. Many databases do this internally anyway, eg postgresql
You can the just refer to the heavy resource by Id
Ok here it goes. I ve gave up the idea of loading separately the attributes that i need at start up.
What i ve done AND NOW WORKS FLAWLESSLY is to do create RELATIONSHIPS in my model. Images are now loading only when called!
This solution was in my mind but because i had already populated my database it was difficult for me to repeat this step.
However i m glad that i did!
NOW it works as it should!!
Happy Developer!!

How to load UIImagePickerController at viewLoad

This seems like a really simple idea... I have a button that says "load a picture". When that button is clicked I want to remove the current view, load a new one, open the image picker, get the selected picture, and display it in the view that I just loaded. I have read several documents that say UIImagePicker does not work in viewDidLoad, and found one thread that suggested using awakeFromNib instead but even that does not work... here is code. Please help.
In MenuViewController
-(IBAction)loadPictureButtonPressed
{
UIView *parent = [self.view superview];
if(self.loadPictureViewController.view.superview == nil)
{
if(self.loadPictureViewController == nil)
{
LoadPictureViewController *picController = [[LoadPictureViewController alloc]
initWithNibName:#"LoadPictureView" bundle: nil];
self.loadPictureViewController = picController;
[picController release];
}
}
[parent addSubview:self.loadPictureViewController.view];
[self.view removeFromSuperview];
}
In LoadPictureViewController
-(void)awakeFromNib
{
if([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypePhotoLibrary])
{
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:picker animated:YES];
[picker release];
}
else
{
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Error!"
message:#"Device does not support photo library";
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[alert release];
}
}
Again I have tried placing this code in both viewDidLoad and awakeFromNib. Please help
I simply set an NSTimer in viewDidLoad with a timer of 0 to select the method where I load it.
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(camera) userInfo:nil repeats:NO];
Instead of NSTimer, you probably want to use the following in -viewDidLoad,
[self performSelector:#selector(camera) withObject:nil afterDelay:0];

Resources