CallSDK IOS 接⼊⽂档

JJCallKit 对接文档

场景说明

使用 JJCallKit 对接,可以将电话功能(外呼、挂断等)集成到您自己的业务系统中,在您自己的业务系统使用此功能。

集成方式灵活

  • 使用自带 UI:Demo 提供了完整的通话界面(拨号、通话中、通话记录等),可以直接使用,快速集成
  • 完全自定义 UI:只使用底层的 Service 层能力,完全按照自己的 UI 设计实现界面

您可以根据项目需求选择合适的集成方式。


0、环境版本等限制

0.1 系统版本要求

项目 最低版本要求 说明
iOS 系统版本 iOS 12.4+ 支持 iPhone
Xcode 版本 Xcode 15.0+ 建议使用最新稳定版本
Swift 版本 Swift 5.0+ 推荐使用 Swift 5.9+
macOS 开发环境 macOS 12.0+ 用于 Xcode 开发

0.2 Framework 依赖

依赖项 说明
JJCallKit.framework 核心 VoIP 框架,基于 PJSIP 实现
架构支持 arm64(真机)、arm64(模拟器)、x86_64(模拟器)
Bitcode 不支持 Bitcode

0.3 权限要求

权限 Info.plist 键值 说明
麦克风权限 NSMicrophoneUsageDescription 必需,用于语音通话
网络权限 系统默认允许 用于 SIP 连接和 WebSocket 通信
后台音频 可选配置 如需后台通话功能,需配置 UIBackgroundModes 中的 audio

0.4 已知限制

  1. 后台保活:如需后台通话,需要配置相应的后台模式,并且 iOS 系统可能会限制后台运行时间。

1、如何将 JJCallKit.framework 集成到新项目中

1.1 准备工作

在开始集成之前,请确保您已准备好以下文件:

  • JJCallKit.framework - 核心 VoIP 框架文件
  • JJPhoneDemo 工程源码(如需要集成 Demo UI,可选)

1.2 添加 Framework 到项目

  1. JJCallKit.framework 拖拽到 Xcode 项目导航器中
  2. 在 Target 的 "General" -> "Frameworks, Libraries, and Embedded Content" 中:
    • 添加 JJCallKit.framework,设置为 "Embed & Sign"
    • 添加系统框架:CoreAudio.frameworkAudioToolbox.frameworkAVFoundation.framework(设置为 "Do Not Embed"

1.3 配置 Build Settings

在 Target 的 "Build Settings" 中配置以下项:

Framework Search Paths

$(PROJECT_DIR)

(或 Framework 所在的具体路径)

Header Search Paths

$(PROJECT_DIR)/JJCallKit.framework/Headers

Preprocessor Macros(添加以下定义)

PJ_IS_LITTLE_ENDIAN=1
PJ_IS_BIG_ENDIAN=0

1.4 配置 Bridging Header(Swift 项目必需)

  1. 创建头文件:YourProjectName-Bridging-Header.h
  2. 添加内容:
    #import <JJCallKit/VoIPManager.h>
    
  3. "Build Settings" -> "Objective-C Bridging Header" 中设置路径:
    YourProjectName/YourProjectName-Bridging-Header.h
    

1.5 配置权限(必需)

  1. 麦克风权限(必需)

Info.plist 中添加:

<key>NSMicrophoneUsageDescription</key>
<string>用于语音通话</string>
  1. 后台音频(可选,如需后台通话)

Info.plist 中添加:

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>

2、Demo 部分如何集成到新项目中

根据需求选择合适的方案,将对应文件拖入项目中:

2.1 方案一:只带 Service 层

Service/
  └── PhoneService.swift
Config/
  └── PhoneConfig.swift

2.2 方案二:带完整 UI

Service/
  └── PhoneService.swift
Config/
  └── PhoneConfig.swift
Model/
  └── Call.swift
UI/
  ├── DialViewController.swift
  ├── ActiveCallViewController.swift
  ├── CallSettingsViewController.swift
  └── Components/
      ├── DialKeyboardView.swift
      └── ActiveCallDialKeyboard.swift
Extension/
  └── UIColor+Extension.swift
JJPhoneSDK.swift

3、JJCallKit.framework 各个接口用法说明

3.1 获取单例

let manager = VoIPManager.sharedManager()

3.2 初始化和登录

初始化 SDK

let result = manager.initial()
// result == 0 表示成功,负数表示失败

登录

// 密码和已加密密码二选一,都填写的话,使用已加密密码
manager.loginWithAccount(
    account: "6000@useasy",          // 账号(格式:用户名@域名)
    password: "your_password",     // 密码
    environment: .production,      // 使用生产环境
    passwordPk: "secret_password",  // 已加密密码
    success: { result in
        // result: NSDictionary,包含登录结果信息
        print("登录成功")
    },
    failure: { code, message in
        // code: 错误码
        // message: 错误描述
        print("登录失败: \(code), \(message ?? "")")
    }
)

参数说明

  • account: 登录账号(格式:用户名@域名,如:6000@useasy)
  • password: 登录密码(可选,如果提供了 passwordPk 则可以不填)
  • passwordPk: 可选的已加密密码,传 nil 表示使用 password
  • success: 成功回调,返回登录结果字典(NSDictionary)
  • failure: 失败回调,返回错误码和错误信息

重新注册 SIP

仅在 loginWithAccount 已成功后可用。会先断开当前 SIP 注册,再使用缓存的 SIP 信息(域名、端口、账号、密码)重新注册。

manager.reregisterSIPWithSuccess(
    success: {
        print("重新注册 SIP 成功")
    },
    failure: { code, message in
        print("重新注册失败: \(code), \(message ?? "")")
    }
)

参数说明

  • success: 重新注册成功回调(收到 200 时调用)
  • failure: 失败回调,返回 SIP 状态码和描述

登出

manager.logout()

3.3 通话控制

发起呼叫

manager.makeCall("13800138000",
                 success: { callId in
    print("呼叫成功,callId: \(callId)")
}, failure: { code, message in
    print("呼叫失败: \(code), \(message ?? "")")
})

参数说明

  • callNumber: 被叫号码
  • success: 成功回调,返回callId
  • failure: 失败回调,返回错误码和错误信息

发起呼叫(带自定义参数)

// 
manager.makeCall("13800138000",
                 userData: ["key1": "value1"],
                 success: { callId in
    print("呼叫成功,callId: \(callId)")
}, failure: { code, message in
    print("呼叫失败: \(code), \(message ?? "")")
})

参数说明

  • callNumber: 被叫号码
  • userData: 自定义数据字典(可选),将作为JSON字符串,编码后长度需小于255字节。如果为nil,则行为与普通 makeCall 方法相同
  • success: 成功回调,返回callId
  • failure: 失败回调,返回错误码和错误信息

挂断通话

manager.hangupCall()

检查是否可以拨打

if manager.canMakeCall() {
    // 可以拨打电话
}

3.4 通话中控制

静音控制

manager.openMute(true)   // true=开启静音,false=关闭静音

参数说明

  • open: YES/true 开启静音,NO/false 关闭静音

免提控制

manager.openLoudSpeaker(true)  // true=开启免提,false=关闭免提

参数说明

  • open: YES/true 开启扬声器,NO/false 关闭扬声器

查询静音状态

if manager.isMuted {
    // 当前已静音
    print("当前已静音")
} else {
    // 当前未静音
    print("当前未静音")
}

说明:查询当前通话的静音状态。如果没有正在进行的通话,返回 false

查询扬声器状态

if manager.isLoudSpeakerOn {
    // 当前使用扬声器
    print("当前使用扬声器")
} else {
    // 当前使用听筒或其他音频输出设备
    print("当前使用听筒")
}

说明:查询当前音频输出设备状态。通过查询 AVAudioSession 的实际输出端口来判断。

发送 DTMF 信号

manager.sendDTMF("1")  // 参数:0-9, *, #

参数说明

  • dtmfNumber: DTMF号码(0-9, *, #)

3.5 外显号码相关(可选)

获取外显策略配置

manager.getCallerStrategyWithSuccess({ config in
    // config: 包含 mobile, agentNumber, numbers, numberSelect 等字段
    print("外显策略: \(config)")
}, failure: { code, message in
    print("获取失败: \(code), \(message ?? "")")
})

参数说明

  • success: 成功回调,返回包含以下字段的字典:mobile, agentNumber, numbers, numberSelect, selectNumber, numberGroup, sipNumber, callerStrategy
  • failure: 失败回调,返回错误码和错误信息

获取外显号码列表

manager.getDisplayNumberListWithSuccess({ numberList in
    // numberList: 数组,每个元素包含 id, status, number, province, city
    for number in numberList {
        print("号码: \(number)")
    }
}, failure: { code, message in
    print("获取失败: \(code), \(message ?? "")")
})

参数说明

  • success: 成功回调,返回外显号码数组(每个字典包含 id、status、number、province、city)
  • failure: 失败回调,返回错误码和错误信息

设置外显号码

// 方式1:指定单个号码
manager.updateAgentDisplayNumberWithSelectNumber("02112345678", 
                                                 numberGroup: nil,
                                                 success: {
    print("设置成功")
}, failure: { code, message in
    print("设置失败: \(code), \(message ?? "")")
})

// 方式2:指定号码组
manager.updateAgentDisplayNumberWithSelectNumber(nil,
                                                 numberGroup: "group_id",
                                                 success: {
    print("设置成功")
}, failure: { code, message in
    print("设置失败: \(code), \(message ?? "")")
})

参数说明

  • selectNumber: 指定的外显号码(可选,如果指定了 numberGroup 则此参数可为 nil)
  • numberGroup: 指定的外显号码组ID(可选,如果指定了 selectNumber 则此参数可为 nil)
  • success: 成功回调
  • failure: 失败回调,返回错误码和错误信息

注意:必须指定 selectNumbernumberGroup 之一。

3.6 状态通知

Framework 通过 NSNotificationCenter 发送状态通知,需监听这些通知来更新 UI。

通知常量列表

连接相关通知

通知名称 说明 UserInfo
kSIPConnectedNotification SIP 连接成功 -
kSIPConnectFailedNotification SIP 连接失败 kSIPReasonDescriptionKey
kSIPKickedOutNotification 被踢下线(453) -
kSIPDisconnectedNotification SIP 断开连接 kSIPReasonKey, kSIPReasonDescriptionKey, kSIPIsPassiveKey, kSIPTimestampKey

呼叫相关通知

通知名称 说明 UserInfo
kSIPCallCallingNotification 正在呼叫 -
kSIPCallConnectingNotification 振铃中 -
kSIPCallConfirmNotification 呼叫接通 -
kSIPCallDisconnectNotification 呼叫挂断 kSIPReasonKey, kSIPReasonDescriptionKey, kSIPHangupTypeKey, kSIPTimestampKey, kSIPVoIPStatusCodeKey
kSIPCallRetryNotification 呼叫重试 -
kSocketCallStatusNotification 通话状态通知 kSocketCallIdKey, kSocketCallTypeKey, kSocketCallStateKey, kSocketCallStateNameKey, kSocketCustomerNumberKey, kSocketDisNumberKey, kSocketEventDataKey

UserInfo Keys 说明

SIP 通知相关 Keys

  • kSIPReasonKey: SIP 错误码
  • kSIPReasonDescriptionKey: SIP 错误描述
  • kSIPIsPassiveKey: 是否被动断开
  • kSIPTimestampKey: 时间戳
  • kSIPHangupTypeKey: 挂断类型(主叫挂断/被叫挂断)
  • kSIPVoIPStatusCodeKey: VoIPStatusCode 错误码(当 SIP 状态码映射到 VoIPStatusCode 时使用)

通话状态通知相关 Keys

  • kSocketCallIdKey: 通话ID(String)
  • kSocketCallTypeKey: 通话类型(String),值为 callin(呼入)或 callout(呼出)
  • kSocketCallStateKey: 通话状态(Int),1=忙碌,2=呼叫中,3=振铃等
  • kSocketCallStateNameKey: 通话状态名称(String),如"呼叫中"、"振铃"等
  • kSocketCustomerNumberKey: 客户号码(String)
  • kSocketDisNumberKey: 外显号码(String)
  • kSocketEventDataKey: 完整的事件数据字典(NSDictionary),包含所有通话相关信息

3.7 错误码说明

错误码分类

错误码范围 分类 说明
0 成功 操作成功
-1 ~ -99 初始化错误 SDK 初始化相关错误
-100 ~ -199 参数验证错误 输入参数验证失败
-200 ~ -299 登录流程错误 登录、认证、SIP 注册相关错误
-300 ~ -399 通话相关错误 呼叫、通话控制相关错误
-400 ~ -499 网络错误 网络连接、超时相关错误
-2000 ~ -2099 HTTP 请求错误 HTTP 接口调用错误
-2100 ~ -2199 WebSocket 错误 WebSocket 连接相关错误
-4000 ~ -4099 外显号码错误 外显号码配置相关错误
-9999 未知错误 未定义的错误

常见错误码列表

初始化错误(-1 ~ -99)

  • -1:SDK 初始化失败
  • -2:SDK 未初始化
  • -3:PJSIP 创建失败
  • -4:PJSIP 初始化失败
  • -5:传输层创建失败(UDP/TCP)
  • -6:PJSIP 启动失败

参数验证错误(-100 ~ -199)

  • -100:账号无效
  • -101:密码无效
  • -104:账号格式错误
  • -105:账号为空
  • -106:密码为空
  • -107:服务器 URL 为空
  • -108:Token 为空

登录流程错误(-200 ~ -299)

  • -200:登录请求失败
  • -201:登录认证失败
  • -202:获取登录配置失败
  • -203:Socket 连接失败
  • -205:获取 SIP 配置失败
  • -206:SIP 配置信息不完整
  • -207:SIP 注册失败(通用,详细 SIP 状态码在错误消息中)
  • -209:SIP 注册被拒绝 (403)
  • -210:账号已被冻结
  • -211:坐席已停用
  • -212:账户未找到
  • -213:坐席未找到
  • -215:SIP 注册未找到 (404)
  • -216:SIP 注册服务器错误 (5xx)
  • -217:获取公钥失败

通话相关错误(-300 ~ -399)

  • -300:呼叫失败(通用,详细 SIP 状态码在错误消息中)
  • -301:通话权限被拒绝
  • -302:未登录无法呼叫
  • -303:呼叫号码为空
  • -304:呼叫被拒绝 (403)
  • -305:号码不存在 (404)
  • -306:用户忙线 (486)
  • -307:请求已终止 (487)
  • -308:呼叫超时 (408)
  • -309:暂时不可用 (480)

网络错误(-400 ~ -499)

  • -401:网络超时

HTTP 请求错误(-2000 ~ -2099)

  • -2001:无效 URL
  • -2007:HTTP 错误 (4xx, 5xx,通用)
  • -2008:空响应数据
  • -2009:JSON 解析失败

WebSocket 错误(-2100 ~ -2199)

  • -2100:无效的 WebSocket URL
  • -2101:WebSocket 连接失败

外显号码错误(-4000 ~ -4099)

  • -4001:坐席 ID 为空
  • -4002:获取外显号码配置失败

获取错误描述

let description = VoIPManager.errorDescriptionForCode(errorCode)
print("错误描述: \(description)")

使用示例

manager.loginWithAccount(
    account: "6000@useasy",
    password: "password",
    environment: .production,
    passwordPk: nil,
    success: { result in
        print("登录成功")
    },
    failure: { code, message in
        let description = VoIPManager.errorDescriptionForCode(code)
        print("登录失败: \(code), 描述: \(description), 详情: \(message ?? "")")
    }
)

获取 SIP 状态码描述

let description = VoIPManager.sipStatusDescription(403)  // 返回 "403 Forbidden - 禁止访问"

常见 SIP 状态码

  • 100:Trying - 正在尝试
  • 180:Ringing - 振铃中
  • 200:OK - 成功
  • 403:Forbidden - 禁止访问
  • 404:Not Found - 未找到
  • 408:Request Timeout - 请求超时
  • 480:Temporarily Unavailable - 暂时不可用
  • 486:Busy Here - 用户忙线
  • 487:Request Terminated - 请求已终止
  • 500:Internal Server Error - 服务器内部错误
  • 503:Service Unavailable - 服务不可用

错误处理建议

  1. 初始化错误:检查 SDK 是否正确集成,确保在调用其他方法前先初始化
  2. 参数验证错误:检查输入参数是否为空、格式是否正确
  3. 登录流程错误:检查账号密码、网络连接、服务器配置
  4. 通话相关错误:确保已登录且 SIP 连接成功,检查被叫号码格式
  5. 网络错误:检查网络连接状态,可能需要重试
  6. 未知错误:查看详细错误消息,联系技术支持

3.8 其他方法

刷新音频设备

manager.refreshAudioDevice()  // 通常在通话建立后调用

重置重试次数

manager.resetRetryNumber()

清理状态(高级)

// 清理通话状态
manager.cleanupCompleteCallState()

// 清理 SDK(需重新初始化)
manager.cleanupSDK()

3.9 属性说明

日志消息回调

manager.messageHandler = { message in
    print("日志: \(message)")
}

初始化状态

if manager.isSDKInitialized {
    print("SDK 已初始化")
}

3.10 日志配置(可选)

// 设置日志级别(0=错误,1=一般,2=全量)
VoIPManager.setLogLevel(2)

// 设置日志文件路径(nil 使用默认路径:Documents/VoIPLogs/voip.log)
VoIPManager.setLogFilePath(nil)

// 启用文件日志
VoIPManager.setFileLoggingEnabled(true)

4、注意事项

4.1 重要提示

初始化顺序

  1. 必须先初始化 SDK 再登录

    let result = VoIPManager.sharedManager().initial()
    if result != 0 {
        // 初始化失败,不能继续
        return
    }
    // 初始化成功后再登录
    
  2. 初始化是线程安全的,多次调用不会重复初始化

  3. 音频会话配置:Framework 内部已处理 AVAudioSession,应用层无需额外配置,但需确保不会冲突

登录流程

  1. 登录是异步过程loginWithAccount 返回成功仅表示请求已发出,真实连接结果需监听通知
    // 正确示例:通过通知获取真实连接状态
    NotificationCenter.default.addObserver(
        forName: NSNotification.Name(kSIPConnectedNotification),
        object: nil,
        queue: .main
    ) { _ in
        print("真正连接成功")
    }
    

通话状态通知(kSocketCallStatusNotification)对接说明

适用场景
当您的业务需要根据服务端推送的通话状态事件来展示来电/去电界面、更新通话状态或做统计时,需要监听本通知。例如:WebSocket/Socket.IO 侧推送了“来电”“振铃”“接通”等事件,Framework 会转发为一条 kSocketCallStatusNotification,您在该通知的回调里解析数据并跳转到通话页或更新 UI。

通知基本信息

项目 说明
通知名称常量 kSocketCallStatusNotification(在 VoIPManager.h 中声明,Swift 项目需通过 Bridging Header 引用)
触发时机 服务端通过 WebSocket 下发通话状态事件时,由 JJCallKit 内部转发
发送方式 NotificationCenter.default,无 object 过滤

UserInfo 字段说明

Key 常量 类型 说明
kSocketCallIdKey String 通话唯一 ID,可用于关联同一通呼叫的多次状态推送
kSocketCallTypeKey String 通话方向:callin(呼入)、callout(呼出)
kSocketCallStateKey Int 通话状态码(如 1=忙碌,2=呼叫中,3=振铃等),具体取值以服务端约定为准
kSocketCallStateNameKey String 状态名称(如「呼叫中」「振铃」),便于展示或日志
kSocketCustomerNumberKey String 客户号码(主叫/被叫号码,视业务含义使用)
kSocketDisNumberKey String 外显号码
kSocketEventDataKey NSDictionary 服务端下发的完整事件体,如需更多字段可从此字典解析

对接步骤

  1. 注册监听
    在应用启动时(如 AppDelegate.application(_:didFinishLaunchingWithOptions:))或全局单例中调用一次,向 NotificationCenter 添加对 kSocketCallStatusNotification 的观察,并指定处理方法(如 handleSocketCallStatusNotification)。

  2. 实现处理方法
    在观察者中实现一个带 Notification 参数的方法(如 handleSocketCallStatusNotification(_:)),从 notification.userInfo 中按上表取出所需字段。

  3. 过滤与业务处理
    根据 kSocketCallTypeKey 过滤 callin / callout(若只关心来电可只处理 callin);再根据 callIdcallState 等决定是弹出新通话页、更新当前页状态还是仅打点统计。UI 更新务必放在主线程(如 DispatchQueue.main.async)。

  4. 去重与重复弹窗
    同一次通话可能收到多条状态推送,建议用 callId 或业务侧会话 ID 做去重,避免同一通话重复弹出界面。

完整示例代码

// 1. 在 AppDelegate 或全局位置注册监听(应用启动时调用一次)
func setupSocketCallStatusNotification() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleSocketCallStatusNotification(_:)),
        name: NSNotification.Name(kSocketCallStatusNotification),
        object: nil
    )
}

// 2. 处理方法:解析 userInfo,区分呼入/呼出并更新 UI
@objc private func handleSocketCallStatusNotification(_ notification: Notification) {
    guard let userInfo = notification.userInfo else { return }

    // 通话方向:callin=呼入,callout=呼出
    guard let callType = userInfo[kSocketCallTypeKey] as? String else { return }
    guard callType == "callin" || callType == "callout" else { return }

    let callId = userInfo[kSocketCallIdKey] as? String ?? ""
    let customerNumber = userInfo[kSocketCustomerNumberKey] as? String ?? ""
    let callState = (userInfo[kSocketCallStateKey] as? Int) ?? 0
    let callStateName = userInfo[kSocketCallStateNameKey] as? String ?? ""
    let disNumber = userInfo[kSocketDisNumberKey] as? String ?? ""

    // 可选:使用完整事件数据
    if let eventData = userInfo[kSocketEventDataKey] as? [String: Any] {
        // 按服务端字段进一步解析
    }

    // 必须在主线程更新 UI
    DispatchQueue.main.async {
        self.showCallPage(callId: callId,
                         customerNumber: customerNumber,
                         callType: callType,
                         callState: callState,
                         callStateName: callStateName,
                         disNumber: disNumber)
    }
}

注意事项

  • Bridging Header:Swift 项目需在 Bridging Header 中 #import <JJCallKit/VoIPManager.h>,否则无法使用 kSocketCallStatusNotificationkSocketCallTypeKey 等常量。
  • 生命周期:建议在 AppDelegate 或长期存在的对象上注册;若在 ViewController 中注册,需在 deinit 中调用 NotificationCenter.default.removeObserver,避免重复注册或野指针。
  • 后台/前台:该通知可能在应用后台或从后台切到前台时发送,若需在后台也弹出来电界面,请结合 UIApplication 状态与业务需求处理。
  • 主线程:通知回调可能不在主线程,界面跳转、弹窗、更新 UI 等请统一放到 DispatchQueue.main.async 中执行。

通话相关

  1. 拨打电话前检查状态

    if manager.canMakeCall() {
        manager.makeCall("13800138000")
    }
    
  2. 通话接通后确保音频会话激活

    NotificationCenter.default.addObserver(
        forName: NSNotification.Name(kSIPCallConfirmNotification),
        object: nil,
        queue: .main
    ) { _ in
        // 通话接通后,确保音频会话正确配置
        PhoneService.shared.ensureAudioSessionForCall()
    }
    
  3. 通话中操作需在接通后进行:静音、免提、DTMF 等操作应在通话接通后使用

  4. 监听通话状态通知

    当需要根据服务器推送的通话状态事件来显示通话页面时,可以监听 kSocketCallStatusNotification 通知:

    NotificationCenter.default.addObserver(
        forName: NSNotification.Name(kSocketCallStatusNotification),
        object: nil,
        queue: .main
    ) { notification in
        guard let userInfo = notification.userInfo else { return }
        
        // 获取通话类型
        guard let callType = userInfo[kSocketCallTypeKey] as? String else { return }
        
        // 处理来电(callin)或呼出(callout)
        guard callType == "callin" || callType == "callout" else { return }
        
        // 获取通话信息
        let callId = userInfo[kSocketCallIdKey] as? String ?? ""
        let customerNumber = userInfo[kSocketCustomerNumberKey] as? String ?? ""
        let callState = userInfo[kSocketCallStateKey] as? Int ?? 0
        let callStateName = userInfo[kSocketCallStateNameKey] as? String ?? ""
        
        // 根据通知信息显示通话页面
        // 例如:创建并显示通话界面
        let call = Call()
        call.id = callId
        call.phoneNumber = customerNumber
        call.callType = callType == "callin" ? .incoming : .outgoing
        
        // 根据 callState 设置状态
        if callState == 1 || callState == 2 {
            call.status = .connecting  // 振铃中
        } else if callState == 3 {
            call.status = .connected   // 已接通
        } else {
            call.status = .calling     // 默认正在呼叫
        }
        
        // 显示通话页面
        let callVC = ActiveCallViewController()
        callVC.call = call
        navigationController?.pushViewController(callVC, animated: true)
    }
    

    使用场景

    • 当收到服务器推送的通话状态事件时,自动弹出通话页面
    • 适用于需要根据服务器推送的事件来更新 UI 的场景
    • 可以获取完整的通话信息,包括客户号码、外显号码、通话状态等

    注意事项

    • 通知可能在任何时候发送,包括应用在后台时
    • 建议在 AppDelegate 或全局位置监听此通知
    • 需要检查是否已经显示了通话页面,避免重复弹出

4.2 常见问题排查

问题 1:编译错误 "Framework not found"

解决方案

  • 检查 Framework Search Paths 配置是否正确
  • 确认 Framework 文件确实存在于指定路径
  • 尝试 Clean Build Folder(Cmd + Shift + K),然后重新编译

问题 2:运行时错误 "dyld: Library not loaded"

解决方案

  • 确认 Framework 的 Embed 设置已设置为 "Embed & Sign"
  • 检查 Framework 的签名是否有效

问题 3:找不到头文件

解决方案

  • 检查 Header Search Paths 配置是否正确
  • 确认 Bridging Header 路径配置正确
  • 检查 Bridging Header 文件中的 import 语句是否正确

问题 4:Swift 中无法使用 Framework 类

解决方案

  • 确认 Bridging Header 已正确配置
  • 检查 Bridging Header 文件是否已添加到 Target 的编译源中
  • 确认 Framework 的 Headers 文件夹存在且包含 VoIPManager.h

问题 5:通话无声音

现象:能拨通电话但听不到声音

排查步骤

  1. 检查麦克风权限是否已授予
    • 设置 → 隐私 → 麦克风 → 检查应用权限
  2. 检查系统音量是否已调高
  3. 检查是否开启了静音模式(iPhone 侧边开关)
  4. 通话接通后确保音频会话激活:
    PhoneService.shared.ensureAudioSessionForCall()
    
  5. 检查音频路由是否正确(听筒/免提)
  6. 查看日志文件,检查音频设备初始化是否成功

问题 6:呼叫失败

现象:呼叫立即失败或无法接通

排查步骤

  1. 检查 SIP 连接状态:监听 kSIPConnectedNotification 确认已连接
  2. 检查号码格式是否正确
  3. 查看通知中的错误信息:
    NotificationCenter.default.addObserver(
        forName: NSNotification.Name(kSIPCallDisconnectNotification),
        object: nil,
        queue: .main
    ) { notification in
        if let code = notification.userInfo?[kSIPReasonKey] as? Int {
            print("呼叫失败,错误码: \(code)")
        }
    }
    

常见 SIP 错误码

  • 403 Forbidden:呼叫被拒绝
  • 404 Not Found:号码不存在
  • 486 Busy Here:用户忙线
  • 487 Request Terminated:请求已取消

VoIPStatusCode 映射文档

本文档供对接方使用,列出 VoIPManager 中通知方法所包含或返回的 VoIPStatusCode。


一、通知 (Notifications)

1. kSIPConnectedNotification(SIP连接成功)

  • 补充说明:无 VoIPStatusCode

2. kSIPConnectFailedNotification(SIP连接失败)

  • kSIPVoIPStatusCodeKey
    • VoIPStatusCodeInvalidAccount (-100):参数校验失败
    • VoIPStatusCodeSIPRegisterFailed (-207):SIP注册失败
  • 补充说明:其他SIP错误码

3. kSIPKickedOutNotification(被踢下线 453)

  • 补充说明:无 VoIPStatusCode

4. kSIPDisconnectedNotification(SIP断开连接)

  • 补充说明:无 VoIPStatusCode

5. kSIPCallCallingNotification(正在呼叫)

  • 补充说明:无 VoIPStatusCode

6. kSIPCallConnectingNotification(振铃中)

  • 补充说明:无 VoIPStatusCode

7. kSIPCallConfirmNotification(呼叫接通)

  • 补充说明:无 VoIPStatusCode

8. kSIPCallDisconnectNotification(呼叫挂断)

  • kSIPVoIPStatusCodeKey
    • VoIPStatusCodeCallForbidden (-304):403 Forbidden
    • VoIPStatusCodeCallNotFound (-305):404 Not Found
    • VoIPStatusCodeCallBusy (-306):486 Busy Here
    • VoIPStatusCodeCallTerminated (-307):487 Request Terminated
    • VoIPStatusCodeCallTimeout (-308):408 Request Timeout
    • VoIPStatusCodeCallUnavailable (-309):480 Temporarily Unavailable
  • 补充说明:其他挂断原因

9. kSIPCallRetryNotification(呼叫重试)

  • 补充说明:当前未发送此通知

10. kSocketCallStatusNotification(通话状态通知)

  • 补充说明:无 VoIPStatusCode

二、方法 (Methods)

1. initial

  • 返回值VoIPStatusCode
  • 可能值
    • VoIPStatusCodeSuccess (0)
    • VoIPStatusCodeSDKInitFailed (-1)

2. loginWithAccount:password:environment:passwordPk:success:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeEmptyAccount (-105)
    • VoIPStatusCodeEmptyPassword (-106)
    • VoIPStatusCodeAccountFormatError (-104)
    • VoIPStatusCodeLoginRequestFailed (-200):业务错误码不在 VoIPStatusCode 范围内时
    • VoIPStatusCodeNetworkTimeout (-401)
    • VoIPStatusCodeHTTPInvalidURL (-2001)
    • VoIPStatusCodeHTTPError (-2007)
    • VoIPStatusCodeHTTPEmptyResponseData (-2008)
    • VoIPStatusCodeHTTPJSONParseFailed (-2009)
    • VoIPStatusCodeSIPConfigIncomplete (-206)
    • 其他任意有效 VoIPStatusCode(业务错误码在范围内时透传)

3. reregisterSIPWithSuccess:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeSDKInitFailed (-1)
    • VoIPStatusCodeSIPConfigIncomplete (-206)
    • VoIPStatusCodeInvalidAccount (-100)
    • VoIPStatusCodeSIPRegisterFailed (-207)
    • SIP 状态码 4xx/5xx(非 VoIPStatusCode,直接透传)

4. makeCall:success:failure: / makeCall:userData:success:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeSDKNotInitialized (-2)
    • VoIPStatusCodeCallEmptyNumber (-303)
    • VoIPStatusCodeCallNotLoggedIn (-302)
    • VoIPStatusCodeCallForbidden (-304):黑名单
    • VoIPStatusCodeCallFailed (-300) 失败或外呼验证失败
    • VoIPStatusCodeCallPermissionDenied (-301):麦克风权限被拒绝
    • 外呼验证返回的 code(可能为业务错误码)

5. getCallerStrategyWithSuccess:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeEmptyAgentId (-4001)
    • VoIPStatusCodeDisplayNumberConfigFailed (-4002)
    • VoIPStatusCodeNetworkTimeout (-401)
    • VoIPStatusCodeHTTPInvalidURL (-2001)
    • VoIPStatusCodeHTTPError (-2007)
    • VoIPStatusCodeSIPConfigIncomplete (-206)

6. getDisplayNumberListWithSuccess:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeDisplayNumberConfigFailed (-4002)
    • VoIPStatusCodeNetworkTimeout (-401)
    • VoIPStatusCodeHTTPInvalidURL (-2001)
    • VoIPStatusCodeHTTPError (-2007)
    • VoIPStatusCodeSIPConfigIncomplete (-206)

7. getDisplayNumberGroupListWithSuccess:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeDisplayNumberConfigFailed (-4002)
    • VoIPStatusCodeNetworkTimeout (-401)
    • VoIPStatusCodeHTTPInvalidURL (-2001)
    • VoIPStatusCodeHTTPError (-2007)
    • VoIPStatusCodeSIPConfigIncomplete (-206)

8. updateAgentDisplayNumberWithSelectNumber:numberGroup:success:failure:

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeEmptyAgentId (-4001)
    • VoIPStatusCodeDisplayNumberConfigFailed (-4002)
    • VoIPStatusCodeNetworkTimeout (-401)
    • VoIPStatusCodeHTTPInvalidURL (-2001)
    • VoIPStatusCodeHTTPError (-2007)
    • VoIPStatusCodeSIPConfigIncomplete (-206)

9. loginSIPWithToken:baseURL:success:failure:(内部/公开)

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeEmptyToken (-108)
    • VoIPStatusCodeEmptyBaseURL (-107)
    • VoIPStatusCodeNetworkTimeout (-401)
    • VoIPStatusCodeHTTPInvalidURL (-2001)
    • VoIPStatusCodeHTTPError (-2007)
    • VoIPStatusCodeHTTPEmptyResponseData (-2008)
    • VoIPStatusCodeHTTPJSONParseFailed (-2009)
    • VoIPStatusCodeSIPConfigIncomplete (-206)
    • VoIPStatusCodeSDKInitFailed (-1)
    • VoIPStatusCodeLoginAuthFailed (-201):401
    • VoIPStatusCodeSIPRegisterForbidden (-209):403
    • VoIPStatusCodeSIPRegisterNotFound (-215):404
    • VoIPStatusCodeSIPRegisterServerError (-216):5xx
    • VoIPStatusCodeSIPRegisterFailed (-207):默认通用错误
    • VoIPStatusCodeInvalidAccount (-100)

10. loginSIPWithConfig:success:failure:(内部)

  • failure 回调(NSInteger code, NSString *message)
  • code 可能值
    • VoIPStatusCodeSIPConfigIncomplete (-206)
    • VoIPStatusCodeSDKInitFailed (-1)
    • VoIPStatusCodeLoginAuthFailed (-201):401
    • VoIPStatusCodeSIPRegisterForbidden (-209):403
    • VoIPStatusCodeSIPRegisterNotFound (-215):404
    • VoIPStatusCodeNetworkTimeout (-401):408
    • VoIPStatusCodeSIPRegisterServerError (-216):5xx
    • VoIPStatusCodeSIPRegisterFailed (-207):默认通用错误
    • VoIPStatusCodeInvalidAccount (-100)
Demo+SDK.zip
11.6 MB
版本号更新时间核心更新
1.0.426.1.191. 增加呼叫前黑名单校验2. 增加风控呼叫提示
1.0.326.1.121. 振铃音优化2. 修复优化passwordpk验证逻辑
1.0.212.29支持加密密码登录
1.0.112.23错误码问题新增静音和扬声器查询方法外呼支持返回callid外呼支持传自定义参数处理Headers头文件过多问题外呼挂断,标明主叫挂断还是被叫挂断
1.0.012.15
2026-03-12