Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to restart recording after answering incoming phone call

I have added an observer for the interrupt notification when recording audio.

This works fine when performing an outgoing-call, getting an incoming call and not answering, Siri, etc..

Now my app is running in the background with the red bar at the top of the screen, and continuing the recording in the states described above is not a problem.

But when I actually answer an incoming-call. I get another AVAudioSessionInterruptionTypeBegan notification and then when I stop the call, I never get a notification AVAudioSessionInterruptionTypeEnded type.

I have tried using the CTCallCenter to detect when a call has started, but I am unable to restart the recording from that callback.

Does anyone know how to get the interrupt mechanism to work with an incoming call that is actually getting answered?

This is (part of) the code I am using;

CFNotificationCenterAddObserver(
                CFNotificationCenterGetLocalCenter(),
                this,
                &handle_interrupt,
                (__bridge CFStringRef) AVAudioSessionInterruptionNotification,
                NULL,
                CFNotificationSuspensionBehaviorDeliverImmediately );

...

static void handle_interrupt( CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo )
{
    au_recorder *recorder = static_cast<au_recorder*>( observer );

    NSNumber* interruptionType = [( ( __bridge  NSDictionary* ) userInfo ) objectForKey:AVAudioSessionInterruptionTypeKey];
    switch ( [interruptionType unsignedIntegerValue] )
    {
        case AVAudioSessionInterruptionTypeBegan:
        {
            // pause recorder without stopping recording (already done by OS)
            recorder->pause();
            break;
        }
        case AVAudioSessionInterruptionTypeEnded:
        {
            NSNumber* interruptionOption = [( ( __bridge  NSDictionary* ) userInfo ) objectForKey:AVAudioSessionInterruptionOptionKey];

            if ( interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume )
            {
                recorder->resume();
            }
            break;
        }
    }
}

I have tried binding the notification to either the AppDelegate, a UIViewController and a separate class, but that doesn't appear to help.

Edit This is what I tried using the CTCallCenter, but this is very flaky. When recorder->resume() is called from the callback, it either works, crashes violently or doesn't do anything at all until the app is put back in the foreground again manually.

callCenter = [[CTCallCenter alloc] init];
callCenter.callEventHandler = ^(CTCall *call)
{
   if ([call.callState isEqualToString:CTCallStateDisconnected])
   {
      recorder->resume();
   }
};
like image 235
Thizzer Avatar asked Sep 04 '17 14:09

Thizzer


People also ask

Where is call recording setting?

Automatic Call Recorder On devices with Android 10 or higher, you'll need to turn on the accessibility service. Tap the Turn On button. At the Accessibility settings screen, tap the entry for Call Recorder under Downloaded apps and turn on the switch for Use Call Recorder.

How do I turn on call recording when closing?

Call Recordings To initiate a Call Recording, open your Phone Settings and toggle on the Call Recording option. The first time you do this, you'll have to agree to some terms and conditions. Once you've enabled the option, any call you make will be recorded.


1 Answers

UPDATE
If you hackily wait for a second or so, you can restart the recording from your callEventHandler (although you haven't described your violent crashes, it works fine for me):

if ([call.callState isEqualToString:CTCallStateDisconnected])
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        self.recorder->resume();
    });
}

This is all without a background task or changing to an exclusive audio session. The delay works around the fact that the call ending notification comes in before Phone.app deactivates its audio session & 1 second seems to be small enough to fall into some kind of background descheduling grace period. I've lost count of how many implementation details are assumed in this, so maybe you'd like to read on to the

Solution that seems to be backed by documentation:

N.B While there's a link to "suggestive" documentation for this solution, it was mostly guided by these two errors popping up and their accompanying comments in the header files:

AVAudioSessionErrorCodeCannotInterruptOthers
   The app's audio session is non-mixable and trying to go active while in the background.
   This is allowed only when the app is the NowPlaying app.

AVAudioSessionErrorInsufficientPriority
   The app was not allowed to set the audio category because another app (Phone, etc.) is controlling it.

You haven't shown how your AVAudioSession is configured, but the fact that you're not getting an AVAudioSessionInterruptionTypeEnded notification suggests you're not using an exclusive audio session (i.e. you're setting "mix with others"):

[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];

The obsoleted CTCallCenter and newer CXCallObserver callbacks seem to happen too early, before the interruption ends, and maybe with some further work you could find a way to run a background task that restarts the recording a little while after the call ends (my initial attempts at this failed).

So right now, the only way I know to restart the recording after receiving a phone call is:

Use an exclusive audio session (this gives you an end interruption):

if (!([session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]
      && [session setActive:YES error:&error])) {
    NSLog(@"Audio session error: %@", error);
}

and integrate with "Now Playing" (not joking, this is a structural part of the solution):

MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand addTargetWithHandler:^(MPRemoteCommandEvent *event) {
    NSLog(@"PLAY");
    return MPRemoteCommandHandlerStatusSuccess;
}];

[commandCenter.pauseCommand addTargetWithHandler:^(MPRemoteCommandEvent *event) {
    NSLog(@"PAUSE");
    return MPRemoteCommandHandlerStatusSuccess;
}];
// isn't this mutually exclusive with mwo? or maybe that's remote control keys
// not seeing anything. maybe it needs actual playback?
NSDictionary* nowPlaying = @{
    MPMediaItemPropertyTitle: @"foo",
    MPMediaItemPropertyArtist: @"Me",
    MPMediaItemPropertyAlbumTitle: @"brown album",
};

[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlaying;

Pieces of this solution were cherry picked from the Audio Guidelines for User-Controlled Playback and Recording Apps documentation.

p.s. maybe the lock screen integration or non-exclusive audio session are deal breakers for you, but there are other situations where you'll never get an end interruption, e.g. launching another exclusive app, like Music (although this may not be an issue with mixWithOthers, so maybe you should go with the code-smell delay solution.

like image 93
Rhythmic Fistman Avatar answered Oct 06 '22 21:10

Rhythmic Fistman