제품 업데이트
Tencent Cloud 오디오/비디오 단말 SDK 재생 업그레이드 및 권한 부여 인증 추가
TRTC 월간 구독 패키지 출시 관련 안내
Before Answering | After Answering |
![]() | ![]() |





*.certSigningRequest file.*.certSigningRequest.
voip_services.cer to import it into Keychain.P12 file.P12 file.




pod 'TUICore' to your Podfile.pod 'TUICore'
pod install --repo-update
import PushKitimport ImSDK_Plusimport LiveCommunicationKitclass VoIPPushManager: NSObject {private let pushRegistry: PKPushRegistryprivate var certificateID: Int = 0private var voipToken: Data? // Store token, report after TUILogin succeeds// Store the current call UUIDprivate var currentCallUUID: UUID?// ConversationManager for displaying the system call interface (Step 7)private lazy var conversationManager: ConversationManager = {let configuration = ConversationManager.Configuration(ringtoneName: "phone_ringing.mp3", // Incoming call ringtone file nameiconTemplateImageData: nil, // Incoming call icon (optional)supportsVideo: true // Whether video calls are supported)let manager = ConversationManager(configuration: configuration)manager.delegate = selfreturn manager}()override init() {pushRegistry = PKPushRegistry(queue: DispatchQueue.main)super.init()pushRegistry.delegate = selfpushRegistry.desiredPushTypes = [.voIP]// Listen for TUILogin success notificationNotificationCenter.default.addObserver(self,selector: #selector(onTUILoginSuccess),name: NSNotification.Name("TUILoginSuccess"),object: nil)}deinit {NotificationCenter.default.removeObserver(self)}// Set certificate IDfunc setCertificateID(_ certificateID: Int) {self.certificateID = certificateID}// Report Token after TUILogin succeeds@objc private func onTUILoginSuccess() {// Received TUILogin success notification, start reporting VoIP TokenuploadVoIPToken()}// Report VoIP Token to Chat backendprivate func uploadVoIPToken() {guard let token = voipToken else {// VoIP Token not yet obtainedreturn}guard certificateID != 0 else {// Certificate ID not setreturn}let config = V2TIMVOIPConfig()config.certificateID = certificateIDconfig.token = tokenV2TIMManager.sharedInstance().setVOIP(config: config, succ: {// VoIP Token reported successfully}, fail: { code, desc in// VoIP Token report failed})}}
AppDelegate:class AppDelegate: UIResponder, UIApplicationDelegate {private lazy var voipPushManager: VoIPPushManager = {let manager = VoIPPushManager()manager.setCertificateID(1234) // Replace with the certificate ID obtained from the Chat Consolereturn manager}()func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {// Trigger the initialization of voipPushManager_ = voipPushManagerreturn true}}
PKPushRegistryDelegate protocol. After obtaining the VoIP Token, save it first and report it only after TUILogin succeeds.import PushKitimport ImSDK_Plusextension VoIPPushManager: PKPushRegistryDelegate {// Save VoIP Token after obtaining (do not report immediately)func pushRegistry(_ registry: PKPushRegistry,didUpdate pushCredentials: PKPushCredentials,for type: PKPushType) {guard type == .voIP else { return }// Save tokenvoipToken = pushCredentials.token// Convert token to string (for logging)let tokenString = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()// VoIP Token obtained:// If TUILogin is already completed, report immediately; otherwise, wait for TUILoginSuccess notificationuploadVoIPToken()}// Token invalidatedfunc pushRegistry(_ registry: PKPushRegistry,didInvalidatePushTokenFor type: PKPushType) {// VoIP Token invalidated}}
TUILoginSuccess notification is sent, and VoIPPushManager will automatically report the VoIP Token.import TUICoreimport AtomicXCore// Step 1: Log in to Chat SDK (AtomicXCore -- LoginStore)LoginStore.shared.login(sdkAppID: sdkAppID,userID: userID,userSig: userSig) { result inswitch result {case .success:// Chat SDK login succeeded// Step 2: Log in to TUICoreTUILogin.login(sdkAppID,userID: userID,userSig: userSig) {// TUICore login succeeded// Send notification to trigger VoIP Token reportingNotificationCenter.default.post(name: NSNotification.Name("TUILoginSuccess"),object: nil)} fail: { code, message in// TUICore login failed}case .failure(let error):// Chat SDK login failed}}
extension VoIPPushManager: PKPushRegistryDelegate {// Receive VoIP Pushfunc pushRegistry(_ registry: PKPushRegistry,didReceiveIncomingPushWith payload: PKPushPayload,for type: PKPushType) {guard type == .voIP else { return }// You must report the incoming call to the system within 5 seconds, or the app will crashshowIncomingCall(with: payload) // Step 7}}
import LiveCommunicationKitextension VoIPPushManager {// Report incoming call to the systemprivate func showIncomingCall(with payload: PKPushPayload) {// Parse push datalet payloadDict = payload.dictionaryPayload// 1. Get caller name (required field)guard let callerName = payloadDict["voip_caller_name"] as? String else {// Missing voip_caller_name fieldreturn}// 2. Parse ext field to get call type (audio/video)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")}// Create incoming call notification contentlet uuid = UUID()currentCallUUID = uuid // Save UUID for subsequent operationsvar update = Conversation.Update(members: [Handle(type: .generic, value: callerName, displayName: callerName)])update.capabilities = isVideoCall ? .video : .playingTones// Report incoming call to the system (system automatically displays native incoming call interface)Task {do {try await conversationManager.reportNewIncomingConversation(uuid: uuid, update: update)// Report succeeded} catch {// Report failed}}}}// MARK: - ConversationManagerDelegateextension VoIPPushManager: ConversationManagerDelegate {func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) {if action is JoinConversationAction {// User taps "Answer", call CallStore to accept the callCallStore.shared.accept { result inswitch result {case .success:// Answer succeeded, navigate to your call page herecase .failure(let error):}}} else if action is EndConversationAction {// User taps "Hang Up", call CallStore to end the callCallStore.shared.hangup { result inswitch result {case .success:case .failure(let error):}}}// You must call fulfill() to notify the system that the operation is completeaction.fulfill()}}
Conversation.Update:Parameter | Type | Required | Description |
members | [Handle] | Required | Array of caller information, supports multi-party call scenarios. Each Handle includes: type: usually .generic value: unique identifier of the caller (recommended to use user ID) displayName: name displayed on the incoming call interface |
capabilities | Conversation.Capabilities | Required | Call type: .video: video call (shows video icon) .playingTones: audio call (shows audio icon) |
CallStore uses VoIP Push by default, so you do not need to manually set the offlinePushInfo parameter. CallStore will automatically set the following push information.import AtomicXCore// Construct call parameters (no need to set offlinePushInfo)var params = CallParams()params.timeout = 30 // Timeout in secondsparams.userData = "custom data" // Extension info (optional)// Create a calllet userIdList = ["callee's userID"]let mediaType = CallMediaType.video // .video for video call, .audio for audio callCallStore.shared.calls(participantIds: userIdList,callMediaType: mediaType,params: params) { result inswitch result {case .success:// Call initiated successfully// Navigate to your call pagecase .failure(let error):// Handle call failure}}
import AtomicXCoreimport Combineclass YourViewController: UIViewController {private var cancellables = Set<AnyCancellable>()override func viewDidLoad() {super.viewDidLoad()// Listen for incoming call eventsCallStore.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):// Incoming call received, navigate to your call pageself.showCallViewController(callId: callId, mediaType: mediaType, userData: userData)case .onCallEnded(let callId, let mediaType, let reason, let userId):// Call ended, update or close UIself.dismissCallViewController()case .onCallStarted(let callId, let mediaType):// Call startedbreak}}.store(in: &cancellables)}private func showCallViewController(callId: String, mediaType: CallMediaType, userData: String) {// Navigate to your call pagelet callVC = YourCallViewController()callVC.callId = callIdcallVC.mediaType = mediaTypecallVC.userData = userDatacallVC.modalPresentationStyle = .fullScreenpresent(callVC, animated: true)}private func dismissCallViewController() {// Close call pagedismiss(animated: true)}}
피드백