CallKit框架相关计算,微信语音成效落到实处

> 日了狗,本篇文章仅供参考吧,大天朝已经不让用callkit了了了了~

作者简介:龚宇华,声网 Agora.io 首席 iOS 研发工程师,负责 iOS 端移动应用产品设计和技术架构。

CallKit是iOS 10中的新特性,当你的应用中有语音通话类的功能的时候,如果集成了CallKit框架的内容,在程序杀死,或者不活跃的时候,可以直接在锁屏状态接听,效果与系统来电一样,可以说是比较方便。

因为篇幅太长,所以把这个分成了三部分,希望读者不要打我。。
上一篇文章讲了PushKit的集成和CallKit打电话,那么这篇就来讲讲如何接电话和挂电话还有其他的一些操作。。
需要继续完善上一篇文章的代码哦。。
iOS CallKit与PushKit的集成(一)

> 前提:

iOS版本的微信已经支持了系统层级的打电话体验,并且可以直接写入系统的通话记录,点击对应的通话记录又可以直接弹到微信里面直接拨打对应人的语音或者是视频通话。这,其实在体验上已经和系统层级的电话体验差不多了。捣鼓了好几天,算是调通了,总结了一波,同样的,如果你是来寻找代码,那么直接拉到最后!

简介

CallKit 是苹果在 iOS10 中推出的,专为 VoIP 通话场景设计的系统框架,在 iOS 上为 VoIP 通话提供了系统级的支持。

在 iOS10 以前,VoIP 场景的体验存在很多局限。比如没有专门的来电呼叫通知方式,App 在后台接收到来电呼叫后,只能使用一般的系统通知方式提示用户。如果用户关掉了通知权限,就会错过来电。VoIP 通话本身也很容易被打断。比如用户在通话过程中打开了另一个使用音频设备的应用,或者接到了一个运营商电话,VoIP 通话就会被打断。

为了改善 VoIP 通话的用户体验问题,CallKit 框架在系统层面把 VoIP 通话提高到了和运营商通话一样的级别。当 App 收到来电呼叫后,可以通过 CallKit 把 VoIP 通话注册给系统,让系统使用和运营商电话一样的界面提示用户。在通话过程中,app 的音视频权限也变成和运营商电话一样,不会被其他应用打断。VoIP 通话过程中接到运营商电话时,在界面上由用户自己选择是否挂起 /挂断当前的 VoIP 通话。

另外,使用了 CallKit 框架的 VoIP 通话也会和运营商电话一样出现在系统的电话记录中。用户可以直接在通讯录和电话记录中发起新的 VoIP 呼叫。

因此,一个有 VoIP 通话场景的应用应该尽快集成 CallKit,以大幅提高用户体验和使用便捷性。

下面我们就来看下 CallKit 的使用方法,并且把它集成到一个使用 Agora SDK 的视频通话应用中。

首先语音通讯类的应用应该都会用到PushKit,在程序杀死的情况同样能唤醒程序,处理推送,我用过的也就是在杀死的情况下能够接听来电,这里简单介绍一下PushKit的使用,注意与普通的APNs推送区分开就好了,还有其他的证书制作相关的之后有时间会写一篇证书相关的,这里不在赘述。

接电话

首先来讲一下如果接电话,来到ProviderDelegate中,添加方法:

func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((Error?) -> Void)?) {
        //准备向系统报告一个 call update 事件,它包含了所有的来电相关的元数据。
        let update = self.callUpdate(handle: handle, hasVideo: hasVideo)
        //调用 CXProvider 的reportIcomingCall(with:update:completion:)方法通知系统有来电。
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            if error == nil {
                //completion 回调会在系统处理来电时调用。如果没有任何错误,你就创建一个 Call 实例,将它添加到 CallManager 的通话列表。
                let call = Call(uuid: uuid, handle: handle)
                self.callManager.add(call: call)
            }
            //调用 completion,如果它不为空的话。
            completion?(error)
        }
 }

这个方法需要在所有接电话的地方手动调用,需要根据自己的业务逻辑来判断。还有就是不要忘了iOS的版本兼容哦。。

在ProviderDelegate中实现系统接电话的代理:

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        //从 callManager 中获得一个引用,UUID 指定为要接听的动画的 UUID。
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        //设置通话要用的 audio session 是 App 的责任。系统会以一个较高的优先级来激活这个 session。
        configureAudioSession()
        //通过调用 answer,你会表明这个通话现在激活
        call.answer()
        //在这里添加自己App接电话的逻辑

        //在处理一个 CXAction 时,重要的一点是,要么你拒绝它(fail),要么满足它(fullfill)。如果处理过程中没有发生错误,你可以调用 fullfill() 表示成功。
        action.fulfill()
    }

回到AppDelegate中,找到之前写的PushKit收到推送的代理方法,在里面添加:

 func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        guard type == .voIP else {
            log.info("Callkit& pushRegistry didReceiveIncomingPush But Not VoIP")
            return
        }
        log.info("Callkit& pushRegistry didReceiveIncomingPush")
        //别忘了在这里加上你们自己接电话的逻辑,比如连接聊天服务器啥的,不然这个电话打不通的
        if let uuidString = payload.dictionaryPayload["UUID"] as? String,
            let handle = payload.dictionaryPayload["handle"] as? String,
            let hasVideo = payload.dictionaryPayload["hasVideo"] as? Bool,
            let uuid = UUID(uuidString: uuidString)
        {
            if #available(iOS 10.0, *) {
                ProviderDelegate.shared.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: { (error) in
                    if let e = error {
                        log.info("CallKit& displayIncomingCall Error (e)")
                    }
                })
            } else {
                // Fallback on earlier versions
            }
        }
    }

至此,CallKit接电话的逻辑完成了,你只需要在合适的地方调用reportIncomingCall就可以调出系统的通话页面了。

> 原理:

Callkit:是iOS 10以后才推出的框架,个人理解就是苹果把原本系统层面的电话UI以Api的形式开放出来,让开发者可以集成到自己的项目中,着重强调一下这里的Callkit是系统层级的UI框架,其本身并没有通话功能!我们需要告知这个框架我们的操作打电话,然后通过UI交互,Callkit给我们回调的是用户的操作,比方说End,Start,Answer等,我们需要根据对应的操作在App里面完成通话的逻辑(联想类比一下iPhone和电信提供商的关系,其实真正的通讯是电信提供商去完成的)。

Pushkit:是iOS 8以后推出的一个推送方式,如果你用过静默推送,那么和这个Pushkit非常相似,简单的说,区别于传统的推送方式,Pushkit不会弹出顶部的那个提示条,而是直接进入App里面的回调,再配合上Callkit,那么就能实现类似于系统层级的打电话体验!

通话记录回拨:这个应该算是一个坑吧,后面代码中补充相关逻辑。

CallKit 基本类介绍

CallKit 最重要的类有两个,CXProviderCXCallController。这两个类是 CallKit 框架的核心。

//1、导入PushKit框架
#import <PushKit/PushKit.h>

//2、注册通知
- (void)pushKitInit
{
    self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:nil];
    self.pushRegistry.delegate = self;
    // Initiate registration.
    self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
}

//3、处理收到的Token,此Token应该是后台服务器使用的
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type
{
    //处理token字符串就因人而异了
    NSString *token = [NSString stringWithFormat:@"%@",credentials.token];
    NSString *tokenString = [[[token stringByReplacingOccurrencesOfString:@"<" withString:@""]
                  stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"VoIP Token:%@",tokenString);
}

//4、处理收到的推送通知,这里是主要的应用逻辑代码了,在下面的函数中
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type{}

挂电话

来到CallKitManager中,添加方法:

func end(call: Call) {
        //先创建一个 CXEndCallAction。将通话的 UUID 传递给构造函数,以便在后面可以识别通话。
        let endCallAction = CXEndCallAction(call: call.uuid)
        //然后将 action 封装成 CXTransaction,以便发送给系统。
        let transaction = CXTransaction(action: endCallAction)
        requestTransaction(transaction)
}

来到ProviderDelegate中,实现系统代理:

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        //从 callManager 获得一个 call 对象。
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        //当 call 即将结束时,停止这次通话的音频处理。
        stopAudio()
        //调用 end() 方法修改本次通话的状态,以允许其他类和新的状态交互。
        call.end()
       //在这里添加自己App挂断电话的逻辑
        //将 action 标记为 fulfill。
        action.fulfill()
        //当你不再需要这个通话时,可以让 callManager 回收它。
        callManager.remove(call: call)
 }

添加完之后,只需要在你自己App挂断电话的地方调用:

if #available(iOS 10.0, *) {
        if let call = CallKitManager.shared.calls.first { //因为我们这里不支持群通话,所以一次只有一个call
               CallKitManager.shared.end(call: call)
        }
}

就可以了。。这里的CallKitManager.shared.calls保存了所有CallKit的通话。是咱们自己写的工具类哦,忘了的话自己翻翻上篇文章。

至此,CallKit挂电话的逻辑结束。。

> 准备:

公司是用的leanCloud作为推送的三方,如果大家在集成上述两个框架并且实现Voip 类型的通话的时候,若是用的三方推送,请首先用工单的方式提问是否支持Voip类型的推送,我们这边原来使用的是传统的方式导出dev.p12 和 dis.P12。上传之后正常的推送是没有问题的,但是Voip类型的就是发不过来,后来更换了文档中 Token Authentication的方式进行验证,这种方式确实比原来的方式更方便了一点,友情提示,这里面的key文件貌似只能下载一次,请谨慎保存!

本文由星彩网app下载发布于计算机编程,转载请注明出处:CallKit框架相关计算,微信语音成效落到实处

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。