7

I have an app that uses CoreBluetooth background modes. When a certain event happens I need to play an alarm sound. Everything works fine in the foreground and all bluetooth functionality works fine in the background. I also have it working where it schedules UILocalNotification's in the background to sound the alarm, however I don't like the lack of volume control with these so want to play the alarm sound using AVAudioPlayer.

I've added the background mode audio to my .plist file but can't get the sound to play properly.

I am using a singleton class for the alarm and initialise the sound like this:

NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                    pathForResource:soundName
                                    ofType:@"caf"]];

player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
player.numberOfLoops = -1;
[player setVolume:1.0];

I start the sound like this:

-(void)startAlert
{
    playing = YES;
    [player play];
    if (vibrate)
        [self vibratePattern];
}

and use this for the vibration:

-(void)vibratePattern
{
    if (vibrate && playing) {
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
        [self performSelector:@selector(vibratePattern) withObject:nil afterDelay:0.75];
    }
}

The vibration works fine in the background, but no sound. If I use Systemsound to play the sound like this, it plays fine (But no volume control):

AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &_sound);
AudioServicesPlaySystemSound(_sound);

So what could be the reason why the AVAudioPlayer is not playing the sound file?

Thanks

EDIT -------

The sound will play if it's already playing when the app is backgrounded. However making it start to play whilst backgrounded is not working.

Darren
  • 10,182
  • 20
  • 95
  • 162
  • Not sure if this is your answer, but setting the sound of the player does not set the global volume level. So could it be that your volume is turned down? Apple have made changes in iOS7 which doesn't easily allow you to set the global volume. A trick was to create an instance of MPMusicPlayerController and set the volume programmatically. This has since been deprecated. – MDB983 Mar 23 '14 at 16:04
  • I'm not sure it's a vole issue as the sound plays fine when the app is in the foreground. More importantly, I need the alarm to sound when the phone's silent switch is enabled and Local Notifications don't do this. – Darren Mar 24 '14 at 08:44
  • I did. Didn't work. It seems if the sound is already playing then it carries on in the background. If I start playing it in the background it doesn't play – Darren Apr 01 '14 at 15:42

5 Answers5

15

add a key named Required background modes in property list (.plist) file ..

as following picture..

enter image description here may you get help..

and add following code in

AppDelegate.h

#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

AppDelegate.m

in application didFinishLaunchingWithOptions

[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

UInt32 size = sizeof(CFStringRef);
CFStringRef route;
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &size, &route);
NSLog(@"route = %@", route);

If you want changes as per events you have to add following code in AppDelegate.m

- (void)remoteControlReceivedWithEvent:(UIEvent *)theEvent {

    if (theEvent.type == UIEventTypeRemoteControl)  {
        switch(theEvent.subtype)        {
            case UIEventSubtypeRemoteControlPlay:
                [[NSNotificationCenter defaultCenter] postNotificationName:@"TogglePlayPause" object:nil];
                break;
            case UIEventSubtypeRemoteControlPause:
                [[NSNotificationCenter defaultCenter] postNotificationName:@"TogglePlayPause" object:nil];
                break;
            case UIEventSubtypeRemoteControlStop:
                break;
            case UIEventSubtypeRemoteControlTogglePlayPause:
                [[NSNotificationCenter defaultCenter] postNotificationName:@"TogglePlayPause" object:nil];
                break;
            default:
                return;
        }
    }
}

based on notification have to work on it..

code.tutsplus.com provides a tutorial.

for HandsetBluetooth you have to add following code in AppDelegate

UInt32 size = sizeof(CFStringRef);
    CFStringRef route;
    AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &size, &route);
    NSLog(@"route = %@", route);
    NSString *routeString=[NSString stringWithFormat:@"%@",route];
    if([routeString isEqualToString:@"HeadsetBT"]){
        UInt32 allowBluetoothInput = 1;
        AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,sizeof (allowBluetoothInput),&allowBluetoothInput);
    }
Ashish Kakkad
  • 23,586
  • 12
  • 103
  • 136
  • 1
    Unfortunately, I still get no sound playing when backgrounded. The vibration still happens, but no found :( – Darren Apr 01 '14 at 13:42
  • 1
    Thanks. I shall award your answer with the bounty as it's the most detailed. – Darren Apr 02 '14 at 16:35
  • Yes It started working, but I didn't change anything! I'm really puzzled. – Darren Apr 03 '14 at 06:31
  • Thanks, but no, the app uses a BTLe device to wake it up then just plays an alarm through the speakers – Darren Apr 03 '14 at 09:43
  • 1
    Yeh I have all that working. The last piece was for the phone to play an alarm sound. I originally used local notifications but wanted it to work when in silent mode too. – Darren Apr 03 '14 at 14:44
  • @Darren Hey, can you please let me know how you got it to work. I am facing the same issue, can't initiate the audio from background. – drink_android Oct 02 '14 at 19:42
  • 1
    @drink_android I ended up playing my alert sound on zero volume as soon as my app is in the state it might be needed. This way it keeps `playing` when in background and all that's needed is the volume set to 1.0 for it to be audible, – Darren Oct 02 '14 at 21:03
  • @Darren Thanks, but doesn't that affect the battery life of the phone? did you analyze it? – drink_android Oct 02 '14 at 21:20
  • I've not analysed it, but as no sound is playing I'd have thought it would be pretty minimal – Darren Oct 02 '14 at 22:01
  • @Darren so I got it to work with the volume thing, but it doesn't work in the use case where you go to some other audio app like Music, play and pause the audio in that app and come back to home screen and use your BLE device to play the sound. Did you test that use case? – drink_android Oct 02 '14 at 23:10
  • No I didn't test that and wouldn't expect my app to be used in that way. But if you find a way around that I'd love to know – Darren Oct 03 '14 at 06:53
  • Excellent Answer. saved a lot of my time – Mohit Dec 31 '14 at 11:21
8

Apart from plist settings you have to modify app delegate.

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}

Also in your controller write the following code.

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
Rahul Mathur
  • 872
  • 1
  • 7
  • 20
  • I still only get vibrate and no sound in the background. I think it's something to do with the fact i'm starting the sound while in the Background. – Darren Mar 27 '14 at 13:23
  • Ok, then try to make the object of background player in app delegate and pass the audioPlayer delegate to app delegate. It might be possible that your audiPlayer object would be getting released somwhere when you popped out or navigated out of the screen where you have made its object and its delegate. – Rahul Mathur Mar 28 '14 at 04:28
  • Also check if you are passing the correct url in your audio player by checking player.duration property. – Rahul Mathur Mar 28 '14 at 04:29
  • The vibration is played by the same object, and that works fine. URL is fine too as it plays in foreground. – Darren Mar 28 '14 at 07:39
4

Maybe you should make your app's audio session higher than others.

Besides the process to setting background mode, you must set AudioSession correctly.

Sometimes just doing this is not enough

[[AVAudioSession sharedInstance] setActive:YES error:&activationErr];

because in Help document there is a discussion about setActive

Discussion
If another active audio session has higher priority than yours (for example, a phone call), and neither audio session allows mixing, attempting to activate your audio session fails. Deactivating your session will fail if any associated audio objects (such as queues, converters, players, or recorders) are currently running.

So, setActive:withOptions:error: is needed. Just like this

[audioSession setCategory :AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error]  

That is you must make your app's audio session higher than others.

passwind
  • 149
  • 5
  • Thanks! My issue was solved using `withOptions:AVAudioSessionCategoryOptionMixWithOthers` . Thank you so much again! :) – devdc Jul 29 '15 at 11:26
1

By default, AVAudioPlayer uses the AVAudioSessionCategorySoloAmbient category, which is silenced by the ringer switch. The AVAudioSessionCategoryPlayback category is more appropriate for your situation, since it is not silenced by the switch, and will continue to play in the background:

NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                    pathForResource:soundName
                                    ofType:@"caf"]];

player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[player setCategory:AVAudioSessionCategoryPlayback error:nil];
player.numberOfLoops = -1;
[player setVolume:1.0];
Austin
  • 5,625
  • 1
  • 29
  • 43
  • Hi, `AVAudioPlayer` doesn't have a `setCategory` method, but `AVAudioSession` does. I try with this `[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];` but still no sound when in the background. – Darren Mar 27 '14 at 08:50
  • It seems if the alarm is already playing, I can background the app and still hear it, but it doesn't play if I initiate it from the background? – Darren Mar 27 '14 at 08:59
0

This (hopefully) may be as simple as retaining the audio. If you could check where you have set the player property that it is strong.

@property (strong, nonatomic) AVAudioPlayer *player;

I hope this helps,

Cheers, Jim

Jim Tierney
  • 4,078
  • 3
  • 27
  • 48