Group calls in Callkit - ios

I've developed a VoIP app. I'm integrating now with callkit framework. Everything works well except conference.
The situation is the following:
1.) I make a call.
2.) Put first call on hold and make another.
3.) I click Conference button to merge both calls.
If I manually remove hold for first call, automatically second call is on hold.
I've read about CXSetGroupCallAction, but there is no match documentation.
Can someone help me?
Thanks.

Call perform merge call action
let call1UUID = UUID(uuidString: call1.callUUID)!
let call2UUID = UUID(uuidString: call2.callUUID)!
let mergeCallAction = CXSetGroupCallAction(call: call1UUID, callUUIDToGroupWith: call2UUID)
let transaction = CXTransaction()
transaction.addAction(mergeCallAction)
callController.request(transaction) { (_) in
}
This will call provider delegate:
func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
// perform merge call here where you merge ports of two call audio i/o
action.fulfill()
}

Related

How to set timeout for CallKit incoming call UI

I'm developing Video call feature in my app and using CallKit to be the incoming call UI. And i found an edge case like:
User A: call user B
User B:
The app is in terminated state. And CallKit incoming UI shows for user B.
User B doesn't notice (since silent mode) and let the incoming UI keep showing
User A doesn't end the call; or for some reasons, user A lost internet or quit the app (therefore my server doesn't send Cancel command via VoIP notification) so there's no way for user B to end the incoming UI until user B touch Cancel or Answer
So is there any way to set a timeout for an incoming UI of CallKit? For example: If i set the timeout is 60 seconds then the incoming UI just shows in 60 seconds the auto dismiss.
Here is my code to show the incoming UI:
let update = CXCallUpdate()
update.localizedCallerName = callerName
update.remoteHandle = CXHandle(type: .phoneNumber, value: myID)
update.hasVideo = true
self.provider.reportNewIncomingCall(with: uuid, update: update) { [weak self] error in
guard let self = self else { return }
if error == nil {
// Store my calls
let call = Call(uuid: uuid, handle: handle)
self.callKitManager.add(call: call)
}
}
Any help would be greatly appreciated. Thanks.
There's no way to set a timeout using the CallKit API. You just have to implement it by yourself.
In the class where you handle all the call logic, you should add something like the following:
private func startRingingTimer(for call: Call)
{
let vTimer = Timer(
timeInterval: 60,
repeats: false,
block: { [weak self] _ in
self?.ringingDidTimeout(for: call)
})
vTimer.tolerance = 0.5
RunLoop.current.add(vTimer, forMode: .common)
ringingTimer = vTimer
}
private func ringingDidTimeout(for call: Call)
{
...
self.provider.reportCall(with: call.uuid, endedAt: nil, reason: .unanswered)
...
}
Then you should call startRingingTimer(for: call) as soon as you successfully reported a new incoming call; and, of course, you have to invalidate the timer if the user answers the call.

CallKit: Resume an on hold VoIP call after GSM call disconnects

I'm experiencing an issue where I'm unable to resume an on hold VoIP call after a GSM call is disconnected by the calling person.
Scenario one that works fine:
I answer an incoming VoIP call
I get a GSM call, press 'hold and accept', which results in provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) being called, allowing me to put the VoIP call on hold
I hang up the GSM call which results in provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) being called, allowing me to un-hold the VoIP call
Scenario two:
I answer an incoming VoIP call
I get a GSM call, press 'hold and accept', which results in provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) being called, allowing me to put the VoIP call on hold
The caller hangs up the GSM call, but provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) is not called
In scenario two only provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) is called after the caller hangs up the GSM call.
How can we resume a VoIP call after a GSM is disconnected by the caller if provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) is not called?
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction)
{
print("Callkit: provider(_ provider: CXProvider, perform action: CXSetHeldCallAction): \(action.callUUID)")
guard var call = callManager.call(withUUID: action.callUUID) else {
action.fail()
return
}
call.isOnHold = action.isOnHold
call.isMuted = call.isOnHold
audioRouter.audioDevice.isEnabled = !call.isOnHold
action.fulfill()
}
After A GSM call is ended, applicationDidBecomeActive will be called when returning to the app. At this point we can detect if a call is on hold and resume it. Other solutions welcome.
I ran into this today. After some Googling, I was unable to find a solution. So strange, I thought, that nobody has solved this. I was able to find a solution inspired by #Simon, but I think a bit cleaner.
In your CallViewController, AppDelegate, or similar, set yourself as the CXCallObserver's delegate.
CXCallController.shared.callObserver.setDelegate(self, queue: .main)
Then later, you want to detect if there is exactly one active call, it's "yours" (whatever that means for you. For me it's stored in self.call.id) and that it's on hold. If so, you can request an update to "unhold" it, and your code elsewhere that handles func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) will get invoked, and hopefully your app handles it correctly.
//MARK: - CXCallObserverDelegate
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
guard let activeCall = callObserver.calls.first,
1 == callObserver.calls.count,
activeCall.uuid == self.call.id,
true == activeCall.isOnHold else {
return
}
let transaction = CXTransaction(action: CXSetHeldCallAction(call: activeCall.uuid, onHold: false))
CXCallController.shared.request(transaction) { error in
if let error = error {
//handle it?
}
}
}
When you get an incoming call, from say, WhatsApp, and your app is put on hold, CXCallObserver.shared.calls will return two calls. One will be "yours" on hold, and the other will be "the WhatsApp call" not on hold. Once the WhatsApp call is terminated on the other end, CXCallObserver.shared.calls will only return one call, yours, but it will still be on hold. This seems like a fairly egregious bug in CallKit. But what do I know?

Calling screen dismiss on accept the twilio voice call even app is active in swift 4

I’m using Twilio’s Programmable Voice in one of the projects. My primary requirement is to place VoIP class between mobile devices. I am able to place calls from one device to another,but when i accept the call at that time calling screen is dismiss automatically and call continue in background. In this case user do not have an option for disconnect call or any other action related to call because screen is dismissed.
Here is the screen that i have created for call when app is in foreground.
Calling placed success fully but on receiver accept it will dismiss the custom screen.So that user do not have any option to disconnect call or any other action related to call.
If any issue in code or any thing related to call kit setting i need to configure or any other issue ? Please help.
As per my knowledge this is default behaviour of call kit framework. On accept button click it will dismiss the screen when app is in foreground. If you wants to achieve same like whats app then you need to create a custom screen for that.Below code I did to resolve this issue:
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction)
{
NSLog("provider:performAnswerCallAction:")
// TwilioVoice.configureAudioSession()
let vc = loadVC(strStoryboardId: SB_CHAT, strVCId: idVoiceCallVC) as! VoiceCallVC
vc.callername = name
vc.userPhoto = userphoto
APP_DELEGATE.appNavigation?.pushViewController(vc, animated: true)
assert(action.callUUID == self.callInvite?.uuid)
TwilioVoice.isAudioEnabled = false
self.performAnswerVoiceCall(uuid: action.callUUID)
{ (success) in
if (success)
{
action.fulfill()
}
else
{
action.fail()
}
}
action.fulfill()
}
You just need to add your custom screen display code in this delegate method of call kit framework.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {}
Thanks.

Hold callkit call when incoming cellular call

I have a problem (but not really) with callkit.
I implemented callkit in my app and it works great. I can get a second call to my app and callkit offeres me options to End&Accept, Decline or Hold&Accept. Same goes if I am in a cellular (gsm) call and I get a call on my app. But when I am in app call (on callkit) and get a cellular(gsm) call I only get 2 options: Decline or End&Accept.
Any idea why? Or how I can get all 3 options?
static var providerConfiguration: CXProviderConfiguration {
var providerConfiguration: CXProviderConfiguration
providerConfiguration = CXProviderConfiguration(localizedName: "app name")
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.maximumCallGroups = 3
providerConfiguration.supportedHandleTypes = [.phoneNumber]
return providerConfiguration
}
I have implemented:
providerDidReset,
CXStartCallAction,
CXAnswerCallAction,
CXEndCallAction,
CXSetHeldCallAction,
CXSetMutedCallAction,
timedOutPerforming action,
didActivate audioSession,
didDeactivate audioSession.
In my app delegate I have function that checks useractivity. I put breakpoints in all of the functions but nothing gets called before the view for incoming cellular (gsm) call is shown.
I googled but couldn't find the solution. As far as I can see, callkit is working perfectly.
I struggled with this for outgoing calls. For outgoing calls, make sure you call this method for the call once it is answered by the remote side:
[self.provider reportOutgoingCallWithUUID:currentCall.uuid connectedAtDate:[NSDate date]];
If you do not, the call is stuck "connecting" from CallKit's perspective and I have found that the native incoming call UI for other calls will not provide the "send to voicemail" and "hold and accept" options for incoming calls while another call is "connecting".
I struggled with this for a bit today until I figured that part out. I also am calling:
[self.provider reportOutgoingCallWithUUID:currentCall.uuid startedConnectingAtDate:[NSDate date]];
from within:
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action
Not sure if that part is necessary but I'm doing it because that's what the Speakerbox demo does. Kind of, they do it in a callback... I just do it immediately.
While you were sending CXCallUpdate object to CallKit before calling, make sure you kept supportsHolding value as true.
My CXCallUpdate looks something like below:
let callHandle = CXHandle(type: .phoneNumber, value: handle)
let callUpdate = CXCallUpdate()
if userName != nil{
callUpdate.localizedCallerName = userName;
}
callUpdate.remoteHandle = callHandle
callUpdate.supportsDTMF = true
callUpdate.supportsHolding = true
callUpdate.supportsGrouping = false
callUpdate.supportsUngrouping = false
callUpdate.hasVideo = false
Meaning of above different properties:
localizedCallerName = If you want to show name of user on system's call screen, otherwise phone number/email based on type of handle will be shown
supportsDTMF = On system's main screen, if you want to allow keypad numbers to be typed while call is running, if you make it false, keypad option get disabled.
supportsHolding = If you want your call to be held, when some other call get triggered, keep this property true
supportsGrouping = If you want to allow conference calling(Merge call option enabled in calling screen), then keep this one true
supportsUngrouping = Inverse of last one, After call getting merged(conference call), should allow it to ungroup or not.
hasVideo = If you support video call, the system will automatically start camera for you.
#Redssie, let me know if any further help related to Callkit required.

How to keep native UI after accept a call using Callkit

I'm developing an iOS voip app using Callkit and Linphone. When I receive a incoming call, system shows the native phone UI to user accept or decline de call, whe the user taps accept button the call starts but de phone UI dissapear.
How can I keep native phone UI after user accept the call, like whatsapp do?
Also, how can I show the native phone UI when start a outgoing call?
Here's my providerDelegate code:
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
update.hasVideo = hasVideo
// Report the incoming call to the system
provider.reportNewIncomingCall(with: uuid, update: update) { error in
/*
Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
*/
if error == nil {
print("calling")
}
}
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
let update = CXCallUpdate()
update.remoteHandle = action.handle
provider.reportOutgoingCall(with: action.uuid, startedConnectingAt: Date())
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "callStart"), object: self, userInfo: ["uuid":action.uuid])
action.fulfill(withDateStarted: Date())
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "callStart"), object: self, userInfo: ["uuid":action.uuid])
// ACCEPT CALL ON SIP MANAGER
if let voiceCallManager = AppDelegate.voiceCallManager {
voiceCallManager.acceptCall()
}
action.fulfill(withDateConnected: Date())
}
You can't keep native UI after accept the incoming call. And Whatsapp use their own UI, that is similar to native UI.
When you have iPhone locked and you accept an incoming call it won't show you APP UI. But if iPhone is unlocked and you accept an incoming call iPhone will open your app, and you must show your phone UI.
And for outgoing calls you can't show native phone UI, it will show if you receive an call.
Therefor, you need a custom phone UI for outgoing and established calls.

Resources