For receiving audio in PushtoTalk, channelManager(_:didActivate:) not called when app receives first push after backgrounding

I'm implementing the PushToTalk framework and have encountered an issue where channelManager(_:didActivate:) is not called under specific circumstances.

What works:

  • App is in foreground, receives PTT push → didActivate is called ✅
  • App receives audio in foreground, then is backgrounded → subsequent pushes trigger didActivate ✅

What doesn't work:

  • App is launched, user joins channel, then immediately backgrounds
  • PTT push arrives while app is backgrounded
  • incomingPushResult is called, I return .activeRemoteParticipant(participant)
  • The system UI shows the speaker name correctly
  • However, didActivate is never called
  • Audio data arrives via WebSocket but cannot be played (no audio session)

Setup:

  • Channel joined successfully before backgrounding
  • UIBackgroundModes includes push-to-talk
  • No manual audio session activation (setActive) anywhere in my code
  • AVAudioEngine setup only happens inside didActivate delegate method
  • Issue persists even after channel restoration via channelDescriptor(restoredChannelUUID:)

Question: Is this expected behavior or a bug? If expected, what's the correct approach to handle incoming PTT audio when the app is backgrounded and hasn't received audio while in the foreground yet?

UIBackgroundModes includes push-to-talk

Did you also include "audio"? You actually need both. Strictly speaking, "push-to-talk" allows you to receive PTT pushes while "audio" allows your audio session to activate in the background.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I am having the same issue, channelManager(_:didActivate:) is not called in background. audio is included in UIBackgroundModes. So, it seems the issue in PTT framework itself. I am testing on debug mode, but that shouldn't be a reason, right?

I am having the same issue, channelManager(_:didActivate:) is not called in the background. audio is included in UIBackgroundModes. So, it seems the issue is in the PTT framework itself.

No, not necessarily. What audio API are you using and, most importantly, do you EVER activate the AudioSession yourself? The PTT framework is built around the same infrastructure as CallKit, which also means that you MUST rely on it for session activation instead of activating the audio session yourself. CallKit's speakerbox sample is a good starting point for how this should work.

I am testing on debug mode, but that shouldn't be a reason, right?

No and, in fact, testing with the debugger tends to make this work "better" than it "should"[1], not worse.

[1] Notably, the debugger disables normal app suspension, so your app ends up being awake in the background in ways that would not normally occur.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

No, I am not activating or changing anything in AudioSession. I have pretty straightforward setup; recording an audio using AVAudioEngine and sending to the server (which is running locally for now). And when PTT notification arrives saving url to audio file in incomingPushResult, then downloading and playing that sound using AVAudioPlayer when didActivate is executed. It's working fine on foreground, but when app goes to background incomingPushResult is called as expected but didActivate is not for some reason. Am I missing anything in my setup?

I tried to activate AudiSession in incomingPushResult method and then didActivate was called in background even though AudioSession threw an error.

And when PTT notification arrives, saving the URL to the audio file in incomingPushResult, then downloading and playing that sound using AVAudioPlayer when didActivate is executed. It's working fine on the foreground, but when the app goes to the background, incomingPushResult is called as expected, but didActivate is not for some reason. Am I missing anything in my setup?

Yes. The issue here is that you can't use AVAudioPlayer, as it does its own AudioSession activation. More specifically:

It's working fine on the foreground.

It works in the foreground app because the foreground app has "inherent" access to the audio system and can do whatever it wants.

However:

when the app goes to the background, incomingPushResult is called as expected, but didActivate is not for some reason.

...what happens in the background is that the same activation attempt then occurs and either fails entirely or succeeds (partially) but is then disrupted by callservicesd's own activation. Unfortunately, you'll ultimately end up in one of two failure modes, depending on exactly how things play out (the details are timing and configuration dependent):

  1. Activation fails entirely, preventing playback.

  2. You are able to self-activate, but the session that activates is NOT a valid "phone" session, which means it's not interruptible by the "rest" of the system. In particular, this means that any incoming call will immediately interrupt your session without really providing any way to "recover".

I tried to activate AudiSession in the incomingPushResult method, and then didActivate was called in the background even though AudioSession threw an error.

Don't do this. If you work hard enough, you can force yourself into #2 above, degrading your user experience for now’s real benefit. Stick to a lower-level API like AVAudioEngine, and this will work fine.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

For receiving audio in PushtoTalk, channelManager(_:didActivate:) not called when app receives first push after backgrounding
 
 
Q