iOS10适配之 CallKit

作者: foolishBoy | 来源:发表于2016-11-07 18:16 被阅读6135次

iOS10来了,iOS程序员们又有的忙了。

公司产品的核心功能是VoIP语/视频通话,为了与时俱进,就要适配iOS最新的CallKit。关于CallKit的介绍我就不详述了,大家可以去看看iOS开发文档WWDC或者直接Google。

总的来说,CallKit有三大优势:

1.提供系统通话界面,这一点在锁屏时体验最明显。

2.VoIP通话权限提升到系统级别,即不是随便被系统电话打断,而是可以选择拒接。

3.支持系统通讯记录沉淀与唤起。

从这三点“升级”可以看出苹果是非常看中VoIP的市场,现在我们可以像打系统电话一样使用VoIP了。

那么,我就开门见山的介绍一些API的使用吧。

CXProvider

The CXProvider class provides a programmatic interface to an object that represents a telephony provider. A CXProvider object is responsible for reporting out-of-band notifications that occur to the system.

我们首先要初始化一个单例的provider。其方法是

- (instancetype)initWithConfiguration:(CXProviderConfiguration *)configuration

这里的CXProviderConfiguration很重要,很多我们显式看到的信息都是在这里面配置好的。

@interface CXProviderConfiguration : NSObject <NSCopying>

//系统来电页面显示的app名称和系统通讯记录的信息
@property (nonatomic, readonly, copy) NSString *localizedName; 

//来电铃声
@property (nonatomic, strong, nullable) NSString *ringtoneSound;

//锁屏接听时,系统界面右下角的app图标,要求40 x 40大小
@property (nonatomic, copy, nullable) NSData *iconTemplateImageData; 

//最大通话组
@property (nonatomic) NSUInteger maximumCallGroups; // Default 2

//是否支持视频
@property (nonatomic) BOOL supportsVideo; // Default NO

//支持的Handle类型
@property (nonatomic, copy) NSSet<NSNumber *> *supportedHandleTypes;

@end

我们初始化provider之后还要设置它代理,以便执行CXProviderDelegate的方法。其方法是:

- (void)setDelegate:(nullable id<CXProviderDelegate>)delegate queue:(nullable dispatch_queue_t)queue;

queue一般直接指定为nil,即在main线程执行callback。

完成初始化之后,provider 就可以为我们服务了,这时候来了一个VoIP电话,那么它应该报告系统,好让系统按照它的配置弹出一个系统来电界面。其方法是:

- (void)reportNewIncomingCallWithUUID:(NSUUID *)UUID update:(CXCallUpdate *)update completion:(void (^)(NSError *_Nullable error))completion;

其中UUID是每次随机生成的,标记一次通话;CXCallUpdate有点类似CXConfiguration,也是一些配置信息。

@interface CXCallUpdate : NSObject <NSCopying>

//通话对方的Handle 信息
@property (nonatomic, copy, nullable) CXHandle *remoteHandle;

//对方的名字,可以设置为app注册的昵称
@property (nonatomic, copy, nullable) NSString *localizedCallerName;

//通话过程中再来电,是否支持保留并接听
@property (nonatomic) BOOL supportsHolding;

//是否支持键盘拨号
@property (nonatomic) BOOL supportsDTMF;

//本次通话是否有视频
@property (nonatomic) BOOL hasVideo;

@end

这些配置信息会影响锁屏时的接听界面上的按钮状态以及多个通话的选择界面。如果执行成功,completion中的error为nil, 否则,不会弹出系统界面。

由于非本地人为(文章最后解释)的因素导致的通话结束,需要报告系统通话结束的时间和原因。其方法是:

- (void)reportCallWithUUID:(NSUUID *)UUID endedAtDate:(nullable NSDate *)dateEnded reason:(CXCallEndedReason)endedReason;

如果dateEnded为nil,则认为结束时间是现在。

我们还可以动态更改provider的配置信息CXCallUpdate,比如作为拨打方,开始没有地方配置通话的界面,就可以在通话开始时更新这些配置信息。 其方法是:

- (void)reportCallWithUUID:(NSUUID *)UUID updated:(CXCallUpdate *)update;

作为拨打方,我们还可以报告通话的状态,以便让系统知道我们app的VoIP真正的通话开始时间。

通话连接时:

- (void)reportOutgoingCallWithUUID:(NSUUID *)UUID startedConnectingAtDate:(nullable NSDate *)dateStartedConnecting;

通话连接上:

- (void)reportOutgoingCallWithUUID:(NSUUID *)UUID connectedAtDate:(nullable NSDate *)dateConnected;

CXCallController

The CXCallController class provides the programmatic interface for interacting with and observing calls.

初始化:

- (instancetype)initWithQueue:(dispatch_queue_t)queue

queue也是指定执行callback的线程,默认是main线程。

在开始或结束一次通话时,需要提交action事务请求,这些事务会交给上面的provider执行。

- (void)requestTransaction:(CXTransaction *)transaction completion:(void (^)(NSError *_Nullable error))completion;

Transaction可以通过三种方法添加Action:

- (instancetype)initWithActions:(NSArray<CXAction *> *)actions
- (instancetype)initWithAction:(CXAction *)action;
- (void)addAction:(CXAction *)action;

CXAction是CXCallAction的基类,常见的CXCallAction有:

CXCallAction Subclass Description
CXAnswerCallAction Answers an incoming call
CXStartCallAction Initiates an outgoing call
CXEndCallAction Ends a call
CXSetHeldCallAction Places a call on hold or removes a call from hold
CXSetGroupCallAction Groups a call with another call or removes a call from a group.
CXSetMutedCallAction Mutes or unmutes a call
CXPlayDTMFCallAction Plays a DTMF (dual tone multi frequency) tone sequence on a call

CXProviderDelegate

The CXProviderDelegate protocol defines methods that are called by a CXProvider object when a provider begins or reset, when a transaction is requested, when an action is performed, and when an audio session changes its activation state.

当拨打方成功发起一个通话后,会触发

- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action;

当接听方成功接听一个电话时,会触发

- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action;

当接听方拒接电话或者双方结束通话时,会触发

- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action;

当点击系统通话界面的Mute按钮时,会触发

- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action;

流程图

一个简单经典的CallKit 通话流程如下图:


CallKit经典通话流程

苹果官方现在还没有给出Callkit的完整文档,所以都是自己摸索,难免有很多坑。

  • 无声

刚开始做的时候,会偶然碰到无声的情况,这个时候发现可以在VoIP通话成功后直接结束系统的通话界面就有声音了。然后就这么很傻叉地做了,而且发现imo一开始也是这么做的。不过,这样肯定会带来问题,最简单的就是系统通话纪录的时长显示不对,因为它是按照callkit上报的开始和结束时间算的,这样毫无理由地结束当然显示错误。QQ最先写了一篇文章,讲到无声的处理方法是

在流程开始前setCategory为PlayAndRecord

突然发现自己的代码里也写了这句话,由于以前的代码逻辑就会处理这种音频问题,所以怀疑是冲突了,反正现在不是很懂,感觉小复杂,去掉就可以了。

  • 如何在系统通讯录中增加选项

既然可以沉淀到系统通话纪录中,就应该可以在通话纪录中直接呼出。那么长按系统通讯录中的“呼叫”如何显示我们自己的app名称呢?就像图中的Whatsup和SpeakerBox一样。


通话选项

其实这依赖于CXProviderConfiguration的一个配置项:

configuration.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypePhoneNumber)];

为了支持安装app就生效,可以在AppDelegate.m的didFinishLaunchingWithOptions方法中去做这个配置。

  • 如何从系统通讯中直接呼出

上面解决了选项问题,那么为什么点击了app的名字没有任何反应呢?
这需要在AppDelegate.m的continueUserActivity方法中响应。

INInteraction *interaction = userActivity.interaction;
INIntent *intent = interaction.intent;
    
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"])
{
    INPerson *person = [(INStartAudioCallIntent *)intent contacts][0];
    CXHandle *handle = [[CXHandle alloc] initWithType:(CXHandleType)person.personHandle.type value:person.personHandle.value];
        
    [[CallKitManager sharedInstance] startCallAction:handle isVideo:NO];
    return YES;
} else if([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
    INPerson *person = [(INStartVideoCallIntent *)intent contacts][0];
    CXHandle *handle = [[CXHandle alloc] initWithType:(CXHandleType)person.personHandle.type value:person.personHandle.value];
        
    [[CallKitManager sharedInstance] startCallAction:handle isVideo:YES];
    return YES;
}

另外,在reportNewIncomingCallWithUUID:update:completion:时要指定remoteHandle为对方的Handle。

  • 何种方式结束

上面的介绍,我们知道结束通话可以有两种方法:

//1
- (void)reportCallWithUUID:(NSUUID *)UUID endedAtDate:(nullable NSDate *)dateEnded reason:(CXCallEndedReason)endedReason;
//2
requestTransaction:CXEndCallAction

那么它们有什么区别,该选择哪个呢?

这个问题我在stackoverflow上提问了,答案我觉得很清楚,在此感谢这位@user102008解惑!

You do requestTransactionwith a CXEndCallAction when the user actively chooses to end the call from your app's UI. You do
reportCallWithUUID:endedAtDate:reason:
when it ended not due to user action (i.e. not due to
provider:performEndCallAction:). If you take a look at the allowed
CXCallEndedReasons (failed, remote ended, unanswered, answered elsewhere, and declined elsewhere), they are all reasons not due to the user's action.

相关文章

网友评论

  • 韩大熊宝要姓张:大概的问题找到了:
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .generic, value: handle)//使用通话记录这个打不开App
    update.hasVideo = hasVideo
    其中的type 只要选择.phoneNumber 就可以了,不过新的问题又出现了: 我使用phoneNumber在打电话的时候不支持使用中文显示通讯记录中的名字,大神有解决方法吗? 先谢谢了。
    foolishBoy:对,我的文章里好像是这样写的吧?
    韩大熊宝要姓张:@foolishBoy 额。谢谢回复,找到原因了。
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
    update.hasVideo = hasVideo
    update.localizedCallerName = handle --> 这里写上本次通话的名称,通话记录里面就会显示对应的名字。 phoneNumber 这个属性写上了 就可以跳转了。 这样就实现了中文的记录,点击还能跳转到app里面。
    foolishBoy:@韩大熊宝要姓张 不支持中文显示通讯录的名字,那是什么样子的呢?可以截图看看吗
  • 韩大熊宝要姓张:大神, “ 需要在AppDelegate.m的continueUserActivity方法中响应” 这个方法我再 Appdelegate里面实现了,不过我在通话记录中点击对应的记录,没有任何反应呢,方法AppDelegate.m的continueUserActivity也没有任何回调啊,请问大神这是什么情况呢?
  • liuyun333:你好,大神,我们现在项目需求也要搞这个,而且时间周期不长,没有太多时间研究这个,可否方便发份demo,万分感谢
    foolishBoy:@liuyun333 demo在github上有很多的,你想找个直接拿去改改就能用在你们项目上的估计没有
  • Rakuyo:您好,我现在在为项目接入CallKit。遇到了无声的这个问题。按照QQ的在发起的时候设置了setCategory为PlayAndRecord。但是依然有概率无声,找不到规律。想请教下关于这方面的经验。是除了在这个地方PlayAndRecord之外其他的地方都不要设置这个吗?还有没有其他的地方会导致无声呢?
    Rakuyo:最终我解决了这个问题,我把这个问题提到了stackoverflow上,解决方案也在这里。虽然我不知道为什么这么做就可以了。.....Orz。

    https://stackoverflow.com/questions/48070163/callkitno-sound-when-i-use-webrtc/48092916#48092916
  • 聪zero:如果不要留下通话记录,应该怎么设置
    聪zero:@foolishBoy 试了下,不行
    聪zero:@foolishBoy 为啥,如果不设置会不会有什么隐患
    foolishBoy:@聪zero 不要设置CXCallUpdate的remoteHandle应该就可以了
  • 0271fb6f797c:收到消息,调起电话界面的时候 ,一直不会出现界面 Provider did reset
    Stopping audio ,请问知道是哪的问题吗?
    0271fb6f797c:您好, 我把错误改过来了,是我的问题,现在,我有一个新的错误,就是在手机通话过程中,手机退到后台后,点击电池栏的通话绿条,不会跳进应用里面,而是进入了拨打电话的那个界面,这个问题我就真不知道是哪的毛病了,请问您遇到过吗?
    0271fb6f797c:@foolishBoy 是在调起接听电话界面的时候的错误
    foolishBoy:不会出现界面?? “ Provider did reset Stopping audio”这是哪里的提示?
  • Allenli:亲 如何 像360等一样,在系统设置页中,显示的名称是 “应用名称—具体功能” ???
    foolishBoy:@Allenli 嗯,解决就好:stuck_out_tongue:
    Allenli:@foolishBoy 知道原因了 在设置中有多callKit的target后 可以自己设置显示名称,以区别不同的target的作用
    foolishBoy:@Allenli 没太明白你的意思,可以详细一点吗
  • Matt_Yuen:您好,我想问一下,视频通话相关内容,可以使用CallKit吗?比如视频通话沉淀到系统通话记录等,期待您的答复。
    foolishBoy:@Matt_Yuen 系统只有语音来电的页面吧,没有视频来电页面啊!只是页面上方的文字可以区别音频还是视频的
    Matt_Yuen:@foolishBoy 我试了一下,就是把‘CXCallUpdate’的‘hasVideo’设置YES,沉淀的记录就是视频的,即会走‘[userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]’这个分支;过,既使我这样设置了,来电仍然只是系统的语音通话的来电界面,还是说只能是系统语音通话来电的界面,求解答,只是想了解CallKit对于视频应该怎么处理
    foolishBoy:@Matt_Yuen 首先 Callkit就是iOS10新增的服务于语音视频通话的功能;沉淀到系统通话记录只需要设置好CXHandle的type和value就好了
  • 浮生0若梦:你好,NSUserActivity实例下面并没有找到interaction这个属性
    foolishBoy:@浮生0若梦 #import <Intents/Intents.h> 另外看看项目的链接库里有没有Intent库,没有要手动加入
    浮生0若梦:@foolishBoy 导入了,也没有点出来这个属性
    foolishBoy:@浮生0若梦 需要导入Intent库
  • 98fb1c598953:执行[callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
    if (error) {
    NSLog(@"Error requesting transaction: %@",error);
    } else {
    NSLog(@"Requested transaction successfully");
    }
    的时候报错:Error requesting transaction: Error Domain=com.apple.CallKit.error.requesttransaction Code=1 "(null)"
    不知有没有遇到过这个问题?
    foolishBoy:@Chant丶凌大昌 应该是transaction定义有问题吧 贴出来看看
    98fb1c598953:@foolishBoy 是CXStartCallAction
    foolishBoy:@Chant丶凌大昌 你的transcation是什么样呢?
  • CNMD_LJ:你好,你有没有遇到过这个问题“ CXTransaction with a CXEndCallAction, the transaction fails with error code Error Domain=com.apple.CallKit.error.requesttransaction Code=4 "(null)“ ,我挂断的时候一直出现,找不到原因
    foolishBoy:@塔格木洛洛 可能是你挂断时用的的uuid不对,不是来电时对应的那个,你检查看看

本文标题:iOS10适配之 CallKit

本文链接:https://www.haomeiwen.com/subject/tkvouttx.html