6

I am trying to start playing a sound from a background task via an AVAudioPlayer that is instantiated then, so here's what I've got.

For readability I cut out all user selected values.

- (void)restartHandler {
    bg = 0;
    bg = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:bg];
    }];
    tim = [NSTimer timerWithTimeInterval:.1 target:self selector:@selector(upd) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:tim forMode:NSRunLoopCommonModes];
}

- (void)upd {
    if ([[NSDate date] timeIntervalSinceDate:startDate] >= difference) {
        [self playSoundFile];
        [tim invalidate];
        [[UIApplication sharedApplication] endBackgroundTask:bg];
    }
}

- (void)playSoundFile {

    NSError *sessionError = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&sessionError];


    // new player object
    _player = [[AVQueuePlayer alloc] init];
    [_player insertItem:[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:@"Manamana" withExtension:@"m4a"]] afterItem:nil];

    [_player setVolume:.7];
    [_player play];

    NSLog(@"Testing");
}

Explanation: bg, tim, startDate, difference and _player are globally declared variables. I call - (void)restartHandler from a user-fired method and inside it start a 10Hz repeating timer for - (void)upd. When a pre-set difference is reached, - (void)playSoundFile gets called and initiates and starts the player. The testing NSLog at the end of the method gets called.

Now the strange thing is if I call - (void)playSoundFile when the app is active, everything works out just fine, but if I start it from the background task it just won't play any sound.

Edit

So I tried using different threads at runtime and as it seems, if the Player is not instantiated on the Main Thread this same problem will appear. I also tried using AVPlayer and AVAudioPlayer where the AVAudioPlayer's .playing property did return YES and still no sound was playing.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Finn Gaida
  • 4,000
  • 4
  • 20
  • 30

5 Answers5

13

From what I've learned after writing an player App, it seems that you can not start playing audio when your App is already in background for longer than X seconds, even if you have configured everything right.

To fix it, you have to use background task wisely. The most important thing is that you must NOT call endBackgroundTask immediately after playSoundFile. Delay it for about 5-10 seconds.

Here is how I managed doing it for iOS6 and iOS7.

1, Add audio UIBackgroundModes in plist.

2, Set audio session category:

3, Create an AVQueuePlayer in the main thread and enqueue an audio asset before the App enter background.

*For continues playing in background (like playing a playlist)*

4, Make sure the audio queue in AVQueuePlayer never become empty, otherwise your App will be suspended when it finishes playing the last asset.

5, If 5 seconds is not enough for you to get the next asset you can employ background task to delay the suspend. When the asset is ready you should call endBackgroundTask after 10 seconds.

Jay Zhao
  • 946
  • 10
  • 24
  • 1
    Thank you sir! This is genius. I am not sure if Apple will accept my current solution of continuously looping an .m4a file that holds an empty sound wave until the player should start playing, but it works now. – Finn Gaida Dec 15 '13 at 17:46
  • 1
    Did you guys submit this to the App Store without being banned ? – Joel Dean Feb 23 '16 at 23:59
0

include your playSoundFile call in the below, this should always run it in main thread

dispatch_async(dispatch_get_main_queue(), ^{

});

add this if to your - (void) playSoundFile to check if player is created, if not, then create it

if(!_player) {
    _player = [[AVQueuePlayer alloc] init];
}
MuhammadBassio
  • 1,590
  • 10
  • 13
  • mh, if I use this the whole player is first created when I go back into the app and make it active... – Finn Gaida Dec 08 '13 at 21:27
  • mh well yeah, but the _player will be created whether there is one or not, but the handler simply only gets called when the app's active again – Finn Gaida Dec 09 '13 at 15:22
  • If the app is active again, _player won't be created again as it won't be nil, correct me if I misunderstood your comment. – MuhammadBassio Dec 09 '13 at 16:24
  • No what I mean is everything I write inside the dipatch_async will only be callen when I go back into the app... – Finn Gaida Dec 15 '13 at 13:43
0

Seems to me as if you do not have the appropriate background mode set? Xcode screenshot

Also, looks like another user had the same issue and fixed it with an Apple Docs post. Link to Post

You may need to just do the step to set the audio session as active:

[audioSession setActive:YES error:&activationError];

Hope it helps!

Community
  • 1
  • 1
werm098
  • 159
  • 1
  • 11
  • that is a nice idea, but I already did so and it didn't help. I am planning on trying all with a new project soon – Finn Gaida Dec 13 '13 at 07:07
0

If you need to start playing a sound in the background (without a trick such as keeping on playing at volume zero until you need the sound):

  • Set Background mode Audio in your p.list;
  • Set AudioSession category to AVAudioSessionCategoryPlayback
  • Set AudioSession to active
  • When your backgrounded app becomes running, start a background task before playing the sound (apparently when the remaining background time is less than 10 seconds, the sound is not played)

This now works for me.

Georges
  • 307
  • 3
  • 13
0

What fixed it for me was to start playing some audio just as application quits, so I added 1sec blank audio in applicationWillResignActive in the AppDelegate

func applicationWillResignActive(_ application: UIApplication) {

        self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
            self.endBackgroundUpdateTask(true)
        })


        let secSilence = URL(fileURLWithPath: Bundle.main.path(forResource: "1secSilence", ofType: "mp3")!)

        do {
            audioPlayer = try AVAudioPlayer(contentsOf: secSilence)
        }
        catch {
            print("Error in loading sound file")
        }

        audioPlayer.prepareToPlay()
        audioPlayer.play()



}
Joseph Williamson
  • 771
  • 1
  • 6
  • 18