即时通信 IM 接入离线推送功能

这篇档将指导你如何基于 TIMPush 服务实现 VoIP 推送功能,适用于使用 AtomicXCore 进行无 UI 集成的场景。

相关产品
即时通信 IM 产品页,含版本规格、价格与购买入口。

一、集成效果

按照这篇档接入 Apple LiveCommunicationKit 后,你的 App 将瞬间拥有微信同款的原生通话能力,在进程关闭或锁屏状态下,也能无差别唤醒系统级全屏接听界面:

接听前效果 接听后效果
 

三、开通服务

进入 IM 控制台 > 插件市场,单击立即购买或免费试用。

注意:购买及免费试用规则详见 计费说明。

五、步骤1:申请 VoIP Push 证书

在申请 VoIP Push 证书之前,请先登录 苹果开发者中心 网站开启 App 的远程推送功能。当你的 AppID 具备了 Push Notification 能力后,按照如下步骤申请并配置 VoIP Push 证书:

1. 登录 苹果开发者中心 网站,单击 Certificates, IDs & Profiles 选项卡,进入 Certificates, Identifiers & Profiles 页面。

2. 单击 Certificates 右侧的+。

3. 在 Create a New Certificate 选项卡中,选择 VoIP Services Certificate,并单击 Continue。

4. 在 Select an App ID for your VoIP Service Certificate 选项卡中,选择你当前的 App 的 BundleID,并单击 Continue。

5. 紧接着,系统提示我们需要一个 Certificate Signing Request(CSR)。

6. 我们接下来制作 CSR 文件。首先在 Mac 上打开钥匙串访问工具(Keychain Access),在菜单中选择钥匙串访问 > 证书助理 > 从证书颁发机构请求证书 (Keychain Access – Certificate Assistant – Request a Certificate From a Certificate Authority)。

7. 输入用户电子邮件地址(你的邮箱)、常用名称(你的名称或公司名),选择存储到磁盘,单击继续,系统将生成一个 *.certSigningRequest 文件。

返回上述步骤 5 中 Apple Developer 网站刚才的页面,单击 Choose File 上传生成的*.certSigningRequest 文件。

8. 单击 Continue 后生成证书,点击 Download 下载对应的证书到本地。

9. 双击打开刚才下载的 voip_services.cer,系统会将其导入钥匙串中。

10. 打开钥匙串应用,在登录 > 我的证书,右键导出刚创建的 VoIP Services 的 P12 文件。

注意:说明:

注意:保存 P12文件时,请务必为其设置密码。

六、步骤2:上传证书到 IM 控制台

打开 IM 控制台,选择你创建的 IM 应用,并按照如下步骤上传证书:

1. 选择你的 IM 应用,进入应用的接入设置。

2. 在厂商配置中,切换到 iOS, 点击添加证书按钮,然后在悬浮页面中选择证书类型,上传 iOS 证书(p12),设置证书密码,单击确认。

注意:说明:

3. 上传完成后,记录不同环境下的证书 ID。

注意:说明:

注意:开发环境和生产环境下的证书 ID 要严格区分,请根据实际环境填写。

八、步骤1:完成工程配置

1. 如下,请在你工程的 Capability 中添加 Push Notification 能力。

2. 如下,请你在工程 Capability 的 Background Modes 中,开启 Voice over IP 选项。

九、步骤2: 添加依赖

1. 添加 Pod 依赖:在你项目的 Podfile 文件中添加 pod 'TUICore' 依赖。

pod 'TUICore'

2. 安装组件:在终端进入到 Podfile 文件所在的目录,然后执行以下命令安装组件。

pod install –repo-update

十、步骤3:初始化 PushKit 并设置证书 ID

1. 创建一个类来管理 VoIP 推送,初始化 PushKit 用于接收 VoIP 推送通知。

import PushKitimport ImSDK_Plusimport LiveCommunicationKitclass VoIPPushManager: NSObject { private let pushRegistry: PKPushRegistry private var certificateID: Int = 0 private var voipToken: Data? // 保存 Token,等待 TUILogin 成功后上报 // 存储当前通话的 UUID private var currentCallUUID: UUID? // ConversationManager 用于展示系统来电界面(步骤 7) private lazy var conversationManager: ConversationManager = { let configuration = ConversationManager.Configuration( ringtoneName: "phone_ringing.mp3", // 来电铃声文件名 iconTemplateImageData: nil, // 来电图标(可选) supportsVideo: true // 是否支持视频通话 ) let manager = ConversationManager(configuration: configuration) manager.delegate = self return manager }() override init() { pushRegistry = PKPushRegistry(queue: DispatchQueue.main) super.init() pushRegistry.delegate = self pushRegistry.desiredPushTypes = [.voIP] // 监听 TUILogin 成功的通知 NotificationCenter.default.addObserver( self, selector: #selector(onTUILoginSuccess), name: NSNotification.Name("TUILoginSuccess"), object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } // 设置证书 ID func setCertificateID(_ certificateID: Int) { self.certificateID = certificateID } // TUILogin 成功后上报 Token @objc private func onTUILoginSuccess() { // 收到 TUILogin 成功通知,开始上报 VoIP Token uploadVoIPToken() } // 上报 VoIP Token 到 IM 后台 private func uploadVoIPToken() { guard let token = voipToken else { // VoIP Token 尚未获取 return } guard certificateID != 0 else { // 证书 ID 未设置 return } let config = V2TIMVOIPConfig() config.certificateID = certificateID config.token = token V2TIMManager.sharedInstance().setVOIP(config: config, succ: { // VoIP Token 上报成功 }, fail: { code, desc in // VoIP Token 上报失败 }) }}

2. 在 AppDelegate 中初始化并设置证书 ID

class AppDelegate: UIResponder, UIApplicationDelegate { private lazy var voipPushManager: VoIPPushManager = { let manager = VoIPPushManager() manager.setCertificateID(1234) // 替换为你在 IM 控制台获取的证书 ID return manager }() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 触发 voipPushManager 的初始化 _ = voipPushManager return true }}

十一、步骤4:获取并保存 VoIP Token

实现 PKPushRegistryDelegate 代理方法,在获取到 VoIP Token 后先保存,等待 TUILogin 成功后再上报。

import PushKitimport ImSDK_Plusextension VoIPPushManager: PKPushRegistryDelegate { // 获取到 VoIP Token 后保存(不立即上报) func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { guard type == .voIP else { return } // 保存 token voipToken = pushCredentials.token // 将 token 转换为字符串(用于日志) let tokenString = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined() // VoIP Token 已获取: // 如果 TUILogin 已经完成,立即上报;否则等待 TUILoginSuccess 通知 uploadVoIPToken() } // Token 失效 func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { // VoIP Token 已失效 }}

注意:VoIP Token 的上报必须在 IM SDK 登录成功后才能进行,因此不能在获取 Token 时立即上报。

十二、步骤5:Token 上报

在用户登录时,需要先登录 IM SDK,再登录 TUICore。TUICore 登录成功后会发送 TUILoginSuccess 通知,VoIPPushManager 监听到该通知后会自动上报 VoIP Token。

import TUICoreimport AtomicXCore// 步骤1:登录 IM SDK(AtomicXCore 的 LoginStore)LoginStore.shared.login( sdkAppID: sdkAppID, userID: userID, userSig: userSig) { result in switch result { case .success: // IM SDK 登录成功 // 步骤2:登录 TUICore TUILogin.login( sdkAppID, userID: userID, userSig: userSig ) { // TUICore 登录成功 // 发送通知,触发 VoIP Token 上报 NotificationCenter.default.post( name: NSNotification.Name("TUILoginSuccess"), object: nil ) } fail: { code, message in // TUICore 登录失败 } case .failure(let error): // IM SDK 登录失败 }}

十三、步骤6:接收 VoIP 推送通知

extension VoIPPushManager: PKPushRegistryDelegate { // 收到 VoIP 推送 func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { guard type == .voIP else { return } // 必须在 5 秒内向系统报告来电,否则会 crash showIncomingCall(with: payload) // 步骤 7 }}

十四、步骤7:展示来电通知

使用 LiveCommunicationKit 向系统报告来电,系统自动展示原生来电界面

import LiveCommunicationKitextension VoIPPushManager { // 向系统报告来电 private func showIncomingCall(with payload: PKPushPayload) { // 解析推送数据 let payloadDict = payload.dictionaryPayload // 1. 获取来电者名称(必需字段) guard let callerName = payloadDict["voip_caller_name"] as? String else { // 缺少 voip_caller_name 字段 return } // 2. 解析 ext 字段获取通话类型(音频/视频) var isVideoCall = false if let extString = payloadDict["ext"] as? String, let extData = extString.data(using: .utf8), let extDic = try? JSONSerialization.jsonObject(with: extData) as? [String: Any], let voipExtDic = extDic["voip_ext"] as? [String: String], let mediaType = voipExtDic["voip_media_type"] { isVideoCall = (mediaType == "video") } // 创建来电通知内容 let uuid = UUID() currentCallUUID = uuid // 保存 UUID,用于后续操作 var update = Conversation.Update( members: [Handle(type: .generic, value: callerName, displayName: callerName)] ) update.capabilities = isVideoCall ? .video : .playingTones // 向系统报告来电(系统自动展示原生来电界面) Task { do { try await conversationManager.reportNewIncomingConversation(uuid: uuid, update: update) // 报告成功 } catch { // 报告失败 } } }}// MARK: – ConversationManagerDelegateextension VoIPPushManager: ConversationManagerDelegate { func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) { if action is JoinConversationAction { // 用户点击"接听",调用 CallStore 接听通话 CallStore.shared.accept { result in switch result { case .success: // 接听成功,可以在这里跳转到通话页面 case .failure(let error): } } } else if action is EndConversationAction { // 用户点击"挂断",调用 CallStore 挂断通话 CallStore.shared.hangup { result in switch result { case .success: case .failure(let error): } } } // 必须调用 fulfill() 通知系统操作已完成 action.fulfill() }}

你可以通过配置 Conversation.Update 的以下参数,自定义来电界面显示的来电者信息、通话类型等内容:

参数 类型 是否必需 说明
members [Handle] 必需 来电者信息数组,支持多人通话场景。每个 Handle 包含:type:通常使用 .genericvalue:来电者唯一标识(建议使用用户 ID)displayName:显示在来电界面的名称
capabilities Conversation.Capabilities 必需 通话类型:.video:视频通话(显示视频图标).playingTones:语音通话(显示语音图标)

注意:说明:

十六、发起通话

AtomicXCore 的 CallStore 已经默认使用 VoIP 推送,你不需要手动设置 offlinePushInfo 参数。CallStore 内部会自动配置以下推送信息:

·推送类型:.voIP

·推送标题:当前用户 ID

·推送描述:"You have a new call"

·iOS 铃声:phone_ringing.mp3

Swiftimport AtomicXCore// 构造通话参数(不需要设置 offlinePushInfo)var params = CallParams()params.timeout = 30 // 超时时间(秒)params.userData = "自定义数据" // 扩展信息(可选)// 发起通话let userIdList = ["被叫用户的 userID"]let mediaType = CallMediaType.video // .video 视频通话,.audio 语音通话CallStore.shared.calls( participantIds: userIdList, callMediaType: mediaType, params: params) { result in switch result { case .success: // 通话发起成功 // 此时可以跳转到你自己的通话页面 case .failure(let error): // 通话失败处理 }}

注意:说明:

注意:VoIP 推送降级策略

注意:当使用 VoIP 推送时,后台会智能判断证书类型,保证用户仍然能收到推送通知:

十七、接听通话

当被叫用户离线时收到 VoIP 推送并点击接听,系统会唤醒你的 App。此时需要监听通话事件并跳转到你自己实现的通话页面。

import AtomicXCoreimport Combineclass YourViewController: UIViewController { private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() // 监听来电事件 CallStore.shared.callEventPublisher .receive(on: DispatchQueue.main) .sink { [weak self] event in guard let self = self else { return } switch event { case .onCallReceived(let callId, let mediaType, let userData): // 收到来电,跳转到你自己的通话页面 self.showCallViewController(callId: callId, mediaType: mediaType, userData: userData) case .onCallEnded(let callId, let mediaType, let reason, let userId): // 通话结束,返回或更新 UI self.dismissCallViewController() case .onCallStarted(let callId, let mediaType): // 通话已开始 break } } .store(in: &cancellables) } private func showCallViewController(callId: String, mediaType: CallMediaType, userData: String) { // 跳转到你自己实现的通话页面 let callVC = YourCallViewController() callVC.callId = callId callVC.mediaType = mediaType callVC.userData = userData callVC.modalPresentationStyle = .fullScreen present(callVC, animated: true) } private func dismissCallViewController() { // 关闭通话页面 dismiss(animated: true) }}

十九、获取不到 VoIP Push,如何处理?

1. 检查登录顺序:必须先登录 IM SDK,再登录 TUICore,否则 VoIP Token 无法上报。参考 步骤 5。

2. 首先检查下 App 的运行环境和证书的环境是否一致,证书 ID 是否匹配,如果不一致,无法收到推送。

3. 请确认当前你登录的账号是否处于离线状态:按 home 键切后台、登录后主动杀进程退出。VoIP Push 目前只支持离线状态下的推送。

4. 检查 完成工程配置 是否正确。

5. 尝试重启测试手机来清除系统缓存和内存(很重要)。

6. IM 控制台提供 推送排查 工具,帮助你快速排查推送问题,使用方式详见:Push 排查工具。(很重要)

20、应用在收到 VoIP 推送后 crash 怎么办?

这通常是因为没有在 5 秒内向系统发送展示通知的请求。请确保在 didReceiveIncomingPushWith 回调中立即调用 reportNewIncomingConversation 方法。

21、如何自定义来电通知的内容?

你可以通过 Conversation.Update 来自定义来电通知的内容,包括:

·来电者信息(displayName)

·通话类型(capabilities:.video 或 .playingTones)

·其他自定义信息

具体用法请参考 Apple LiveCommunicationKit 产品文档。

22、交流与反馈

如果你在使用过程中,有什么建议或者意见,可以 联系我们,感谢你的反馈。

赞 (0)
上一篇 4天前
下一篇 4天前