CallKit can reactivate sound after swapping call - ios

I'm developing CallKit application, I have a problem, Call Holding is failing to restart audio when "swapping" calls on the CallKit screen until user returns to in-app call screen. I can bypass this by updating:
supportsHolding = false
but I can I solve this problem, whatsapp for example can do this correctly!
p.s. I'm using webrtc to make a call!
thanks!
EDIT:
This is code of provider:
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
guard let call = conductor!.callWithUUID(uuid: action.callUUID) else {
WebRtcConductor.debug("\(self.TAG) đź”´ failed to perform HeldAction: uuid: \(action.uuid), calluiid: \(action.callUUID)")
action.fail()
return
}
setIsHeld(call: call, isHeld: action.isOnHold)
action.fulfill()
}
the setIsHeld function simply do:
audioTrack.isEnabled = enabled
If I use "mute" button of callkit screen, all works fine, but if I have 2 active calls, when I swipe from webrtc call to normal call, CXSetHeldCallAction is called and audio track did disabled, If I swipe again to webrtc call, audio track is enabled but i do not hear nothing, if I return to main app screen, audio works fine again!

Actually, there is a limitation in the Google WebRTC Library which leads to the described problem when implementing a CallKit integration which supports swapping calls.
The WebRTC Issue 8126 is known for over a year now, but not yet integrated into the WebRTC master branch. However, you can find the necessary code changes to fix this problem in the original ticket.
However, as a workarround, you can trigger the system notification which is subscribed by WebRTC internally.
Post a AVAudioSessionInterruptionType.ended Notification in the "didActivate audioSession" method of the CallKit Provider:
var userInfo = Dictionary<AnyHashable, Any>()
let interrupttioEndedRaw = AVAudioSessionInterruptionType.ended.rawValue
userInfo[AVAudioSessionInterruptionTypeKey] = interrupttioEndedRaw
NotificationCenter.default.post(name: NSNotification.Name.AVAudioSessionInterruption, object: self, userInfo: userInfo)
PS: Stare the ticket to improve chances of a merge ;-)

Had the same issue. If I have 1 active call, then new calls is incoming, I tap hold&accept. New call works, but after using Swap in CallKit audio stops working.
Found that provider:performSetHeldCallAction: method from CXProviderDelegate protocol is the spot where you can actually deactivate/activate audio for Swap calls via CallKit native interface.
In my case I used the audioController.deactivateAudioSession() method for the call was putting in OnHold.
But found that the same method provider:performSetHeldCallAction: was fired for other call that is being put active (from OnHold state), when tap Swap button via CallKit.
So you just need to deactivate/activate audio respectively to call's state (either hold or not).
In common way it should look this way:
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
// Retrieve the Call instance corresponding to the action's call UUID
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// Update the Call's underlying hold state.
call.isOnHold = action.isOnHold
// Stop or start audio in response to holding or unholding the call.
if call.isOnHold {
stopAudio()
} else {
startAudio()
}
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
P.S. It looks like you should have some class that responds for Audio session. It should implement kind of activate audio session / deactivate audio session.

Related

How to ignore iOS VoIP notification if call has already ended?

We're developing a video calling application and rely on APNS VoIP notifications. Due to our design it sometimes happens that the VoIP notification arrives to the device when the call has already ended or the recipient has declined it (missed call for example).
The problem with that approach is that iOS requires you to report all incoming VoIP notifications in some way - either that there's new incoming call or the current call has been updated.
Is there any way to ignore the unnecessary/redundant VoIP notification? The current approach I came up with is really nasty i.e. first I report new unknown incoming call and then immediately I report its end. This causes the native call UI to be shown for a brief moment.
private var provider: CXProvider?
private var uuid = UUID()
//...
func ignorePushNotification() {
self.provider?.reportNewIncomingCall(with: self.uuid, update: CXCallUpdate(),
completion: { error in
// ignore
})
self.provider?.reportCall(with: self.uuid, endedAt: nil, reason: reason)
}
Unfortunately, there isn't a better way to ignore a VoIP Push. But I suggest you to improve the code as follows.
func ignorePushNotification() {
provider?.reportNewIncomingCall(
with: self.uuid,
update: CXCallUpdate(),
completion: { error in
self.provider?.reportCall(with: self.uuid, endedAt: nil, reason: .failed)
})
}
Given the asynchronous nature of CallKit, if you don't do that, it could happen that the end of the call executes before the reportNewIncomingCall. It's probably very rare but it could happen.

Get a callback from AKPlayer at a user specified time

I’m trying to get a callback at a given point in an AKPlayer’s file playback (currently, just before the end). I see the Apple docs on addBoundaryTimeObserver(), which would work, but it doesn’t seem to be accessible from AKPlayer (I guess an AVAudioPlayerNode vs AVPlayer thing). Any suggestions? I see a few callbacks in AVAudioPlayerNode… maybe I could determine the buffer based on the desired time and use dataConsumed?
The goal is to trigger another event just before the file finishes playing (there is a callback on completion, but obviously that's too late).
If anybody has done something similar, or knows of something similar (a gist, etc), that would be great.
There's an AudioKit playground called AKPlaygroundLoop that shows you how to call an arbitrary handler periodically, based on CADisplayLink. In your handler you could check the AKPlayer's currentTime and if it's close to the end (say 1 second before) you could trigger whatever event you want.
This is a rough outline:
var player: AKPlayer!
var loop: AKPlaygroundLoop!
func play() {
// ...
playgroundLoop = AKPlaygroundLoop(frequency: 10.0, handler: myHandler)
}
func myHandler() {
if player.currentTime >= player.duration - 1.0 {
// trigger some event
}
}
See also this answer for advice on how to synchronize events with AudioKit.

Callkit incoming call ui

I am using call kit framework for calling and please help me
how to remove call kit ui when incoming call occurs during app in foreground and call in process, i am getting call kit ui in background.
You can check for app state before reporting a new incoming call to CXProvider. If you do not wish to use system incoming call screen when your app is in foreground, then give the if statement not to report new incoming call screen if the app is in foreground.
Example:
let state = UIApplication.shared.applicationState
if state == .background {
// background
provider.reportNewIncomingCall(with: UUID(uuidString: call.callUUID)!, update: callUpdate) { error in /* */ }
}

NotificationCenter stops working when the screen is locked

I'm having trouble with an app I'm building, the app objective is to play audio files, it works by requesting an audio file from a public API, playing it and wait until it ends, after it ends it requests another audio and starts over.
Here's a shortened version of the code that does this, I omitted the error checking part for simplicity
func requestEnded(audioSource: String) {
let url = URL(string: "https://example.com/audios/" + audioSource)
audio = AVPlayer(url: url!)
NotificationCenter.default.addObserver(self,selector: #selector(MediaItem.finishedPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: audio?.currentItem)
audio?.play()
}
#objc func finishedPlaying(_ notification: NSNotification) {
print("Audio ended")
callAPI()
}
func callAPI() {
// do all the http request stuff
requestEnded(audioSource: "x.m4a")
}
// callAPI() is called when the app is initialized
It works well when the screen is unlocked. When I lock the phone the current audio keeps playing but when it ends finishedPlaying() never gets called (the print statement is not shown on the console).
How can I make it so the app would know the audio ended and trigger another one all while locked?, In the android version I got around the screenlock problem by using a partial wakelock which made it run normally even with the screen off.
It has to be done this way because the API decides the audio on realtime and it's all done on the backend so no way to buffer more than one audio without breaking the requirements of the app
What are my options here?, any help is appreciated.

CTCallCenter in Swift

I'm trying to use CTCallCenter in Swift, however it always displays error.
I suppose it may cause in how to use closure but actually I don't familiar about it.
Does anybody have idea to resolve this issue?
Here is my code
import CoreTelephony
class ViewController: UIViewController{
var callCenter:CTCallCenter = CTCallCenter()
override func viewDidLoad() {
callCenter.callEventHandler(call:CTCall) -> Void in{
//will get CTcall status here
}
}
}
There are three errors.
1, Braced block of statements is an unused closure
2, Expected expression
3, Consecutive statements on a line must be separated by ";".
I tried to change as it indicated but any ways are not correct.
Thanks in Advance!
I got this working using the following code:
import CoreTelephony
class SomeClass: UIViewController {
private var callCenter = CTCallCenter()
override func viewDidLoad() {
super.viewDidLoad()
callCenter.callEventHandler = { (call:CTCall!) in
switch call.callState {
case CTCallStateConnected:
println("CTCallStateConnected")
self.callConnected()
case CTCallStateDisconnected:
println("CTCallStateDisconnected")
self.callDisconnected()
default:
//Not concerned with CTCallStateDialing or CTCallStateIncoming
break
}
}
}
func callConnected(){
// Do something when call connects
}
func callDisconnected() {
// Do something when call disconnects
}
}
Hope it helps.
From the Apple documentation :
Responding to Cellular Call Events
Dispatched when a call changes state.
Declaration:
var callEventHandler: ((CTCall!) -> Void)!
Discussion:
This property’s block object is dispatched on the default priority global dispatch queue when a call changes state. To handle such call events, define a handler block in your application and assign it to this property. You must implement the handler block to support being invoked from any context.
If your application is active when a call event takes place, the system dispatches the event to your handler immediately. However, call events can also take place while your application is suspended. While it is suspended, your application does not receive call events. When your application resumes the active state, it receives a single call event for each call that changed state—no matter how many state changes the call experienced while your application was suspended. The single call event sent to your handler, upon your application returning to the active state, describes the call’s state at that time.
For example, suppose your application changes from the active to the suspended state while a call is in the connected state. Suppose also that while your application is suspended, the call disconnects. When your application returns to the active state, you get a cellular call event indicating that the call is disconnected.
Here is a more complex example. Suppose your application changes from the active to the suspended state after the user has initiated a call but before it connects (that is, your application suspends while the call is in the dialing state). Suppose further that, while your application is suspended, the call changes first to the connected state and then to the disconnected state. When your application returns to the active state, you get a single cellular call event indicating that the call is disconnected.
May be now you can understand how to declare that.

Resources