产品动态
产品近期公告
关于 TRTC Live 正式上线的公告
关于TRTC Conference 正式版上线的公告
Conference 商业化版本即将推出
关于多人音视频 Conference 开启内测公告
关于音视频通话 Call 正式版上线的公告
关于腾讯云音视频终端 SDK 播放升级及新增授权校验的公告
关于 TRTC 应用订阅套餐服务上线的相关说明
接听前效果 | 接听后效果 |
![]() | ![]() |




Keychain Access - Certificate Assistant - Request a Certificate From a Certificate Authority)。
*.certSigningRequest 文件。*.certSigningRequest文件。
voip_services.cer,系统会将其导入钥匙串中。P12 文件。P12文件时,请务必为其设置密码。




Podfile 文件中添加 pod 'TUICore' 依赖。pod 'TUICore'
Podfile 文件所在的目录,然后执行以下命令安装组件。pod install --repo-update
import PushKitimport ImSDK_Plusimport LiveCommunicationKitclass VoIPPushManager: NSObject {private let pushRegistry: PKPushRegistryprivate var certificateID: Int = 0private var voipToken: Data? // 保存 Token,等待 TUILogin 成功后上报// 存储当前通话的 UUIDprivate 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 = selfreturn manager}()override init() {pushRegistry = PKPushRegistry(queue: DispatchQueue.main)super.init()pushRegistry.delegate = selfpushRegistry.desiredPushTypes = [.voIP]// 监听 TUILogin 成功的通知NotificationCenter.default.addObserver(self,selector: #selector(onTUILoginSuccess),name: NSNotification.Name("TUILoginSuccess"),object: nil)}deinit {NotificationCenter.default.removeObserver(self)}// 设置证书 IDfunc setCertificateID(_ certificateID: Int) {self.certificateID = certificateID}// TUILogin 成功后上报 Token@objc private func onTUILoginSuccess() {// 收到 TUILogin 成功通知,开始上报 VoIP TokenuploadVoIPToken()}// 上报 VoIP Token 到 Chat 后台private func uploadVoIPToken() {guard let token = voipToken else {// VoIP Token 尚未获取return}guard certificateID != 0 else {// 证书 ID 未设置return}let config = V2TIMVOIPConfig()config.certificateID = certificateIDconfig.token = tokenV2TIMManager.sharedInstance().setVOIP(config: config, succ: {// VoIP Token 上报成功}, fail: { code, desc in// VoIP Token 上报失败})}}
AppDelegate 中初始化并设置证书 IDclass AppDelegate: UIResponder, UIApplicationDelegate {private lazy var voipPushManager: VoIPPushManager = {let manager = VoIPPushManager()manager.setCertificateID(1234) // 替换为您在 Chat 控制台获取的证书 IDreturn manager}()func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {// 触发 voipPushManager 的初始化_ = voipPushManagerreturn true}}
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 }// 保存 tokenvoipToken = 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 已失效}}
TUILoginSuccess 通知,VoIPPushManager 监听到该通知后会自动上报 VoIP Token。import TUICoreimport AtomicXCore// 步骤1:登录 Chat SDK(AtomicXCore 的 LoginStore)LoginStore.shared.login(sdkAppID: sdkAppID,userID: userID,userSig: userSig) { result inswitch result {case .success:// Chat SDK 登录成功// 步骤2:登录 TUICoreTUILogin.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):// Chat SDK 登录失败}}
extension VoIPPushManager: PKPushRegistryDelegate {// 收到 VoIP 推送func pushRegistry(_ registry: PKPushRegistry,didReceiveIncomingPushWith payload: PKPushPayload,for type: PKPushType) {guard type == .voIP else { return }// 必须在 5 秒内向系统报告来电,否则会 crashshowIncomingCall(with: payload) // 步骤 7}}
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 = falseif 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 inswitch result {case .success:// 接听成功,可以在这里跳转到通话页面case .failure(let error):}}} else if action is EndConversationAction {// 用户点击"挂断",调用 CallStore 挂断通话CallStore.shared.hangup { result inswitch result {case .success:case .failure(let error):}}}// 必须调用 fulfill() 通知系统操作已完成action.fulfill()}}
Conversation.Update 的以下参数,自定义来电界面显示的来电者信息、通话类型等内容:参数 | 类型 | 是否必需 | 说明 |
members | [Handle] | 必需 | 来电者信息数组,支持多人通话场景。 每个 Handle 包含: type:通常使用 .generic value:来电者唯一标识(建议使用用户 ID) displayName:显示在来电界面的名称 |
capabilities | Conversation.Capabilities | 必需 | 通话类型: .video:视频通话(显示视频图标) .playingTones:语音通话(显示语音图标) |
AtomicXCore 的 CallStore 已经默认使用 VoIP 推送,您不需要手动设置 offlinePushInfo 参数。CallStore 内部会自动配置以下推送信息:import 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 inswitch result {case .success:// 通话发起成功// 此时可以跳转到您自己的通话页面case .failure(let error):// 通话失败处理}}
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 inguard 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):// 通话结束,返回或更新 UIself.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 = callIdcallVC.mediaType = mediaTypecallVC.userData = userDatacallVC.modalPresentationStyle = .fullScreenpresent(callVC, animated: true)}private func dismissCallViewController() {// 关闭通话页面dismiss(animated: true)}}
didReceiveIncomingPushWith 回调中立即调用 reportNewIncomingConversation 方法。Conversation.Update 来自定义来电通知的内容,包括:displayName)capabilities:.video 或 .playingTones)文档反馈