Question

I am trying to add a tap-sound to a UIButton to play when it is pushed. So far, I've tried two methods, but none worked out quite as well as I hoped.

  1. I have tried using simple SystemSounds, but they are an ugly solution, as the user have no (or rather very limited) control over the audio volume.

  2. I also tried AVAudioPlayer in the following form (called in the ViewController's init method):

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient 
                                           error:nil];
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
    [[AVAudioSession sharedInstance] setDelegate:self];
    
    NSString *filePath = [[NSBundle mainBundle] 
                         pathForResource: @"button_pushed"
                                  ofType: @"wav"];
    NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: filePath];
    self.buttonPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
                                                               error: nil];
    [buttonPlayer prepareToPlay];
    [buttonPlayer setDelegate: self];
    

This second method seemed to work a lot better than the first one; especially when I called sound with the following statement:

    if (self.clickPlayer.playing) {
        [self.clickPlayer stop];
        self.clickPlayer.currentTime = 0;
        [self.clickPlayer play];
    }
    else {
        [self.clickPlayer play];
    }

The above ensured that I got a very fast response when I pushed the button repeatedly. Fast response time, by the way, is essential in my app, and unfortunately, this is where this second methods fails.

If I let the application idle for about 10 seconds, loading the first sound takes a noticeable delay (then, it becomes fast and responsive again). Not only that, it also delays the button from being pushed with it. While this delay is very tiny (probably the fraction of a second), unfortunately it does effect the usability of my application. It is as if the sound was becoming deallocated automatically and needed to be initialized or put into the memory every time I did not use it for a while.

Is there a workaround to always keep the sound available and avoid the delay or another, possibly simple, method to implement button sounds?

A few more info: 1. I use ARC 2. I do not use IB 3. Currently, there are no audio delegate methods implemented 4. The ability to play audio in the background from other applications is kind of important 5. The delay occurred on an iPhone 5; so, I can only imagine how long it would take on less powerful devices...

Was it helpful?

Solution

I managed to solve this problem by creating a "blank" player that continuously fires off a tiny .wav file with no sound, therefore keeping the AVPlayer alive. This is probably not the most elegant solution, but it does work, while it does not seem to affect the performance of the program. Here's what you need to do:

  1. Create a silent .wav file of 0.1 seconds long (the smaller the better).

  2. Initialize a player for it:

    NSString *filePath = [[NSBundle mainBundle] pathForResource: @"silence"
                                                         ofType: @"wav"];
    NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: filePath];
    silencePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
                                                           error: nil];
    [silencePlayer prepareToPlay];
    [silencePlayer setDelegate: self];
    
  3. Then make it fire every second or so:

    timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
                                             target:self
                                           selector:@selector(repeatSilence)
                                           userInfo:nil
                                            repeats:YES];
    
  4. Finally, create the called method (- (void) repeatSilence { } ) to play the sound:

    if (self.silencePlayer.playing) {
        [self.silencePlayer stop];
        self.silencePlayer.currentTime = 0;
        [self.silencePlayer play];
    }
    else {
        [self.silencePlayer play];
    }
    

OTHER TIPS

Not entirely sure, but wouldn't it go faster if you make an NSData of the path and change [[AVAudioPlayer alloc] initWithContentsOfURL to [[AVAudioPlayer alloc] initWithData?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top