I have gone through the iOS CallKit documentation, the below is the code provided by Apple under the section 'Making Outgoing Calls'. When I try and call the function startOutGoingCall() nothing happens i.e. I don't see any outgoing call UI.
Could someone please suggest me how I could trigger a native outgoing call UI.
func startOutGoingCall(){
let uuid = UUID()
let handle = CXHandle(type: .emailAddress, value: "jappleseed#apple.com")
let startCallAction = CXStartCallAction(call: uuid)
startCallAction.destination = handle
let transaction = CXTransaction(action: startCallAction)
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
} else {
print("Requested transaction successfully")
}
}
}
EDIT:
Added the delegate method from my code
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
logMessage(messageText: "provider:performStartCallAction:")
/*
* Configure the audio session, but do not start call audio here, since it must be done once
* the audio session has been activated by the system after having its priority elevated.
*/
localMedia?.audioController.configureAudioSession(.videoChatSpeaker)
callKitProvider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
performRoomConnect(uuid: action.callUUID, roomName: action.handle.value) { (success) in
if (success) {
provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date())
action.fulfill()
} else {
action.fail()
}
}
}
There is no outgoing UI you get from CallKit. When you make an outgoing call, your app is open, and therefore, it is your app that should show the UI.
Related
I'm working on a voip app now and want to support holding.
But when a second call comes and I hold my current call. Switching to my first call I hear no sound at all.
The way to hear it is to navigate from callKit native screen to my app and hence I can hear the voice.
func configureAudioSession() {
_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: .videoChat, options: AVAudioSession.CategoryOptions.mixWithOthers)
_ = try? AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none)
_ = try? AVAudioSession.sharedInstance().setMode(AVAudioSession.Mode.voiceChat)
}
func startAudio() {
print("Starting audio")
do {
_ = try AVAudioSession.sharedInstance().setActive(true)
} catch {
}
}
func stopAudio() {
print("Stopping audio")
do {
_ = try AVAudioSession.sharedInstance().setActive(false)
} catch {
}
}
For supporting holding, you dont have to start/stop audio session, instead you can use CXSetHeldCallAction provided by Callkit itself. Here, is the code for hold that i use.
let callKitCallController = CXCallController()
func performHoldAction(isOnHold:Bool, uuid:UUID) {
let holdCallAction = CXSetHeldCallAction(call: uuid, onHold: isOnHold)
let transaction = CXTransaction(action: holdCallAction)
callKitCallController.request(transaction) { error in
if let error = error {
CPrint("holdCallAction transaction request failed: \(error.localizedDescription).")
return
}
CPrint("holdCallAction transaction request successful")
}
}
Once system puts the call on hold(by above method OR due to other incoming call accepting or any other reason), then in CXProviderDelegate, the method func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) gives you the callback for the detail.
Here, system/callkit itself interacts with audio, you dont have to explicitly start or stop audio for holding.
Note: Make sure that you given supportsHolding to true for the CXCallUpdate that you gave for new call.
I'm having a little confusion in regards to showing my UI when opening the app from an incoming background video call. I am successfully causing iOS to summon the default "incoming video call" interface when the app is in the background, but after the call is answered, my app isn't being woken up properly?
When I receive the call push, I setup a CXProvider and notify CallKit of an incoming call:
func handleIncomingCallFromBackground() {
//These properties are parsed from the push payload before this method is triggered
guard let callingUser = callingUser, roomName != nil else {
print("Unexpected nil caller and roomname (background)")
return
}
let callHandleTitle = "\(callingUser.first_name) \(callingUser.surname)"
let configuration = CXProviderConfiguration.default
let callKitProvider = CXProvider(configuration: configuration)
callKitProvider.setDelegate(self, queue: nil)
let callHandle = CXHandle(type: .generic, value: callHandleTitle)
self.callHandle = callHandle
let callUpdate = CXCallUpdate.default
callUpdate.remoteHandle = callHandle
let callUUID = UUID()
self.callUUID = callUUID
callKitProvider.reportNewIncomingCall(with: callUUID, update: callUpdate) { error in
if error != nil {
self.resetTwilioObjects()
}
}
}
I respond to the answer call delegate method for the CXProvider, in which I get an access token for the video call from the server, send a response to the server to alert the caller that we've accepted the call, and perform a segue to our own video call controller (that's all jobsVC.showVideoCallVC() does) which handles connecting the call through a Twilio room etc. Code below.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let customer = callingUser, let callHandle = self.callHandle, let uuid = callUUID, let roomName = roomName else {
resetTwilioObjects()
return
}
self.twilioAPI = TwilioAPI()
self.twilioAPI!.accessToken(room: roomName) { (success, accessToken) in
switch success{
case true:
guard let token = accessToken else {
return
}
let customerID = customer.userID
DispatchQueue.global().asyncAfter(deadline: .now() , execute: {
self.twilioAPI!.postResponse(customerID: customerID, status: 1) { (success) in
if let success = success, !success {
self.resetTwilioObjects()
}
}
})
guard let jobsVC = P_ChildHomeJobsVC.instance else {
print("Jobs VC unexpectedly nil")
return
}
//All this method does is perform a segue, the parameters are stored for later.
jobsVC.showVideoCallVC(callingUser: customer, callHandle: callHandle, callKitProvider: provider, callUUID: uuid, twilioAccessToken: token, roomName: roomName)
action.fulfill()
default:
action.fail()
self.resetTwilioObjects()
}
}
}
Depending on whether or not the device is locked, I get differing behaviour:
If the device is locked, upon hitting my app icon to open the app, I get the latest app screenshot show up with a green bar at the top instead of the UI.
If the device is unlocked, upon hitting my app icon to open the app nothing happens at all - it stays on the iOS interface.
According to my logs, the segue is actually being performed correctly and the inner workings of the video call controller are even being fired, but shortly after I get the applicationWillResignActive: delegate call and everything stops.
What's odd (or maybe not) is that if the device is locked whilst the app is still in the foreground, everything works as expected: the app is correctly woken up and the updated UI is shown. I noticed that I still get the applicationWillResignActive: call, but immediately get applicationDidBecomeActive: after that.
Does anyone have any suggestions or hints as to what I might be doing wrong?
In my app i'm using CallKit for incoming call. There is no outgoing call feature in the app. Everything is fine but when the receiver or dailer ends the call it shows the CallKit UI with call back option. I don't want to show callback option, how can I do it?
My code for ending the call
func end(call: SpeakerboxCall) {
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction()
transaction.addAction(endCallAction)
requestTransaction(transaction, action: "endCall")
}
private func requestTransaction(_ transaction: CXTransaction, action:
String = "") {
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
} else {
print("Requested transaction \(action) successfully")
}
}
}
I have solved it. I was force quitting the CallKit where the transaction is not correctly completing.
AppDelegate.shared.providerDelegate?.provider.reportCall(with: call.uuid, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
We need to set endedAt to nil and reason to remoteEnded
Close All Call (Swift4)
func performEndCallAction() {
for call in self.cxCallController.callObserver.calls {
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction(action: endCallAction)
cxCallController.request(transaction) { error in
if let error = error {
NSLog("EndCallAction transaction request failed: \(error.localizedDescription).")
return
}
NSLog("EndCallAction transaction request successful")
}
}
}
I'm working on an app for a hands free BLE device for changing music and answering phone calls. Everything I'm reading says this can't be done, but figured I'd ask and see if maybe there was a change now with CallKit.
I'm able to see call events using CXCallObserverDelegate, but am unable to answer phone calls with the CXAnswerCallAction.
Here is my current code, I'm using a delay for testing purposes. Eventually the action would be triggered by an action from the BLE device:
self.currentCallUUID = call.uuid
if let callUUID = self.currentCallUUID
{
let answerAction: CXAnswerCallAction = CXAnswerCallAction(call: callUUID)
let transaction: CXTransaction = CXTransaction(action: answerAction)
let when = DispatchTime.now() + 2
DispatchQueue.main.asyncAfter(deadline: when) {
print("answering call")
self.callController.request(transaction, completion: { error in
if (error != nil)
{
print("did answer")
answerAction.fulfill()
}
else
{
print("answer failed: \(String(describing: error?.localizedDescription))")
}
})
}
}
I am integrating the new CallKit API with my VOIP app.
As shown in the example app: https://developer.apple.com/library/content/samplecode/Speakerbox/Introduction/Intro.html
I am configuring the audio session:
- (void) configureAudioSession
{
// Configure the audio session
AVAudioSession *sessionInstance = [AVAudioSession sharedInstance];
// we are going to play and record so we pick that category
NSError *error = nil;
[sessionInstance setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
if (error) {
NSLog(#"error setting audio category %#",error);
}
// set the mode to voice chat
[sessionInstance setMode:AVAudioSessionModeVoiceChat error:&error];
if (error) {
NSLog(#"error setting audio mode %#",error);
}
NSLog(#"setupAudioSession");
return;
}
in my CXAnswerCallAction:
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("Provider - CXAnswerCallAction")
// get the active call
guard let call = self.softphone.getCallForCallId(self.currentCallId) else {
action.fail()
return
}
/*
Configure the audio session, but do not start call audio here, since it must be done once
the audio session has been activated by the system after having its priority elevated.
*/
self.softphone.configureAudioSession()
// Trigger the call to be answered via the underlying network service.
call.answer()
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
According to the documentation, didActivate should be called back by the callkit:
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Provider - Received \(#function)")
// Start call audio media, now that the audio session has been activated after having its priority boosted.
}
For some reasons, it's not called back after the first VOIP call. The subsequent calls seem to receive the callback and they work fine.
How to fix this?
I've fixed this problem by setting call audio first then call "reportNewIncomingCall" method. Sample code is given below:
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
DispatchQueue.global().sync {
_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.mixWithOthers)
_ = try? AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.none)
if hasVideo == true {
_ = try? AVAudioSession.sharedInstance().setMode(AVAudioSessionModeVideoChat)
} else {
_ = try? AVAudioSession.sharedInstance().setMode(AVAudioSessionModeVoiceChat)
}
do {
_ = try AVAudioSession.sharedInstance().setActive(true)
} catch (let error){
print("audio session error: \(error)")
}
}
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
}
completion?(error as? NSError)
}
}