CallKit is a new iOS 10 framework that allows VoIP applications to integrate with the iPhone UI. Your application does not need to integrate with CallKit in order to use the Client SDK. If your application does not integrate with CallKit, you may disregard this article.
All Client SDK API changes are backwards compatible such that pre-existing applications will continue to work as they did with previous Client SDK release.
With CallKit, iOS manages the AudioSession independently of the call signalling, requiring two small changes to the Client SDK API. The call flow below shows the how your application can use the Client SDK API to become a CallKit application.
In order to the CallKit framework can identify calls, your application need to use UUIDs. It is useful to create the new category for the CSCall class and add the method which generates and returns UUID.
#import
@interface CSCall (Additions)
@property (nonatomic) NSUUID *UUID;
@end
#import "CSCall+Additions.h"
#import
@implementation CSCall (Additions)
- (NSUUID *)UUID {
NSUUID* uuid = objc_getAssociatedObject(self, @selector(UUID));
if (!uuid) {
uuid = [NSUUID UUID];
self.UUID = uuid;
}
return uuid;
}
- (void)setUUID:(NSUUID *)UUID {
objc_setAssociatedObject(self, @selector(UUID), UUID,
OBJC_ASSOCIATION_RETAIN);
}
@end
Then you are ready to report the incoming call UUID to CallKit framework. Your application should configure the audio session prior to reporting the call to ensure the audio path is set for the incoming call.
- (void)configureAudioSession {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError* err;
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err];
if (err) {
NSLog(@"%s Error setting audio category %@", __PRETTY_FUNCTION__, err);
}
[audioSession setMode:AVAudioSessionModeVoiceChat error:&err];
if (err) {
NSLog(@"%s Error setting audio Mode %@", __PRETTY_FUNCTION__, err);
}
}
- (void)callService:(CSCallService *)callService
didReceiveIncomingCall:(CSCall *)call {
CXHandle *callHandle = [[CXHandle alloc]
initWithType:CXHandleTypeGeneric value:call.remoteNumber];
CXCallUpdate *cxCallUpdate = [[CXCallUpdate alloc] init];
cxCallUpdate.remoteHandle = callHandle;
cxCallUpdate.supportsDTMF = call.sendDigitCapability.allowed;
cxCallUpdate.supportsUngrouping = NO;
cxCallUpdate.supportsGrouping = !call.isConference;
cxCallUpdate.supportsDTMF = YES;
cxCallUpdate.supportsHolding = YES;
cxCallUpdate.supportsGrouping = YES;
cxCallUpdate.localizedCallerName = call.remoteNumber;
[self configureAudioSession];
[self.callKitProvider reportNewIncomingCallWithUUID:call.UUID
update:cxCallUpdate completion:^(NSError * _Nullable error) {
if (!error)
{
NSLog(@"reportNewIncomingCall Call is reported successfully:%@",
error);
}
else
{
NSLog(@"reportNewIncomingCall error:%@", error);
}
}
];
}
Then, if user answers the call, your application will receieve the provider:performAnswerCallAction: message. You need to wait for the provider:didActivateAudioSession: message before accept Client SDK call.
- (void)provider:(CXProvider *)provider
performAnswerCallAction:(CXAnswerCallAction *)action {
CSCall *call = [self callWithUUID:action.callUUID];
if (call) {
[action fulfill];
@synchronized (self) {
self.waitingForActivation = ^{
[call accept];
};
}
}
else {
[action fail];
}
}
When the provider:didActivateAudioSession: message is received from CallKit provider, send the audioSessionDidBecomeActive: message to Client SDK instance then accept the Client SDK call.
- (void)provider:(CXProvider *)provider
didActivateAudioSession:(AVAudioSession *)audioSession {
NSLog(@"%s", __PRETTY_FUNCTION__);
[self.client audioSessionDidBecomeActive:YES];
@synchronized (self) {
if (self.waitingForActivation) {
self.waitingForActivation();
self.waitingForActivation = nil;
}
}
}
When user ends a call using native iOS dialog, the provider:performEndCallAction: message is received from CallKit provider. Call [call end] then wait for call end from Client SDK before calling [action fulfilled].
- (void)provider:(CXProvider *)provider
performEndCallAction:(CXEndCallAction *)action {
CSCall *call = [self callWithUUID:action.callUUID];
NSLog(@"%s", __PRETTY_FUNCTION__);
if (call) {
[call end];
#define kMaxWaitForCallEnd 20 /* 2 seconds */
NSUInteger count = 0;
while (call.state != CSCallStateEnded &&
call.state != CSCallStateEnding) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
count++;
if (count >= kMaxWaitForCallEnd) {
break;
}
#undef kMaxWaitForCallEnd
}
[action fulfill];
}
else
{
[action fail];
}
}
Then the provider:didDeactivateAudioSession: message is received from CallKit provider, Send the audioSessionDidBecomeActive: message to the Client SDK instance.
- (void)provider:(CXProvider *)provider
didDeactivateAudioSession:(AVAudioSession *)audioSession {
NSLog(@"%s", __PRETTY_FUNCTION__);
[self.client audioSessionDidBecomeActive:NO];
}