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 已知限制
- 后台保活:如需后台通话,需要配置相应的后台模式,并且 iOS 系统可能会限制后台运行时间。
1、如何将 JJCallKit.framework 集成到新项目中
1.1 准备工作
在开始集成之前,请确保您已准备好以下文件:
JJCallKit.framework- 核心 VoIP 框架文件JJPhoneDemo工程源码(如需要集成 Demo UI,可选)
1.2 添加 Framework 到项目
- 将
JJCallKit.framework拖拽到 Xcode 项目导航器中 - 在 Target 的 "General" -> "Frameworks, Libraries, and Embedded Content" 中:
- 添加
JJCallKit.framework,设置为 "Embed & Sign" - 添加系统框架:
CoreAudio.framework、AudioToolbox.framework、AVFoundation.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 项目必需)
- 创建头文件:
YourProjectName-Bridging-Header.h - 添加内容:
#import <JJCallKit/VoIPManager.h> - 在 "Build Settings" -> "Objective-C Bridging Header" 中设置路径:
YourProjectName/YourProjectName-Bridging-Header.h
1.5 配置权限(必需)
- 麦克风权限(必需)
在 Info.plist 中添加:
<key>NSMicrophoneUsageDescription</key>
<string>用于语音通话</string>
- 后台音频(可选,如需后台通话)
在 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 表示使用 passwordsuccess: 成功回调,返回登录结果字典(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: 成功回调,返回callIdfailure: 失败回调,返回错误码和错误信息
发起呼叫(带自定义参数)
//
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: 成功回调,返回callIdfailure: 失败回调,返回错误码和错误信息
挂断通话
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, callerStrategyfailure: 失败回调,返回错误码和错误信息
获取外显号码列表
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: 失败回调,返回错误码和错误信息
注意:必须指定 selectNumber 或 numberGroup 之一。
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 - 服务不可用
错误处理建议
- 初始化错误:检查 SDK 是否正确集成,确保在调用其他方法前先初始化
- 参数验证错误:检查输入参数是否为空、格式是否正确
- 登录流程错误:检查账号密码、网络连接、服务器配置
- 通话相关错误:确保已登录且 SIP 连接成功,检查被叫号码格式
- 网络错误:检查网络连接状态,可能需要重试
- 未知错误:查看详细错误消息,联系技术支持
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 重要提示
初始化顺序
-
必须先初始化 SDK 再登录
let result = VoIPManager.sharedManager().initial() if result != 0 { // 初始化失败,不能继续 return } // 初始化成功后再登录 -
初始化是线程安全的,多次调用不会重复初始化
-
音频会话配置:Framework 内部已处理 AVAudioSession,应用层无需额外配置,但需确保不会冲突
登录流程
- 登录是异步过程:
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 | 服务端下发的完整事件体,如需更多字段可从此字典解析 |
对接步骤
-
注册监听
在应用启动时(如AppDelegate.application(_:didFinishLaunchingWithOptions:))或全局单例中调用一次,向NotificationCenter添加对kSocketCallStatusNotification的观察,并指定处理方法(如handleSocketCallStatusNotification)。 -
实现处理方法
在观察者中实现一个带Notification参数的方法(如handleSocketCallStatusNotification(_:)),从notification.userInfo中按上表取出所需字段。 -
过滤与业务处理
根据kSocketCallTypeKey过滤callin/callout(若只关心来电可只处理callin);再根据callId、callState等决定是弹出新通话页、更新当前页状态还是仅打点统计。UI 更新务必放在主线程(如DispatchQueue.main.async)。 -
去重与重复弹窗
同一次通话可能收到多条状态推送,建议用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>,否则无法使用kSocketCallStatusNotification、kSocketCallTypeKey等常量。 - 生命周期:建议在 AppDelegate 或长期存在的对象上注册;若在 ViewController 中注册,需在
deinit中调用NotificationCenter.default.removeObserver,避免重复注册或野指针。 - 后台/前台:该通知可能在应用后台或从后台切到前台时发送,若需在后台也弹出来电界面,请结合 UIApplication 状态与业务需求处理。
- 主线程:通知回调可能不在主线程,界面跳转、弹窗、更新 UI 等请统一放到
DispatchQueue.main.async中执行。
通话相关
-
拨打电话前检查状态
if manager.canMakeCall() { manager.makeCall("13800138000") } -
通话接通后确保音频会话激活
NotificationCenter.default.addObserver( forName: NSNotification.Name(kSIPCallConfirmNotification), object: nil, queue: .main ) { _ in // 通话接通后,确保音频会话正确配置 PhoneService.shared.ensureAudioSessionForCall() } -
通话中操作需在接通后进行:静音、免提、DTMF 等操作应在通话接通后使用
-
监听通话状态通知
当需要根据服务器推送的通话状态事件来显示通话页面时,可以监听
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:通话无声音
现象:能拨通电话但听不到声音
排查步骤:
- 检查麦克风权限是否已授予
- 设置 → 隐私 → 麦克风 → 检查应用权限
- 检查系统音量是否已调高
- 检查是否开启了静音模式(iPhone 侧边开关)
- 通话接通后确保音频会话激活:
PhoneService.shared.ensureAudioSessionForCall() - 检查音频路由是否正确(听筒/免提)
- 查看日志文件,检查音频设备初始化是否成功
问题 6:呼叫失败
现象:呼叫立即失败或无法接通
排查步骤:
- 检查 SIP 连接状态:监听
kSIPConnectedNotification确认已连接 - 检查号码格式是否正确
- 查看通知中的错误信息:
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 ForbiddenVoIPStatusCodeCallNotFound(-305):404 Not FoundVoIPStatusCodeCallBusy(-306):486 Busy HereVoIPStatusCodeCallTerminated(-307):487 Request TerminatedVoIPStatusCodeCallTimeout(-308):408 Request TimeoutVoIPStatusCodeCallUnavailable(-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):401VoIPStatusCodeSIPRegisterForbidden(-209):403VoIPStatusCodeSIPRegisterNotFound(-215):404VoIPStatusCodeSIPRegisterServerError(-216):5xxVoIPStatusCodeSIPRegisterFailed(-207):默认通用错误VoIPStatusCodeInvalidAccount(-100)
10. loginSIPWithConfig:success:failure:(内部)
- failure 回调:
(NSInteger code, NSString *message) - code 可能值:
VoIPStatusCodeSIPConfigIncomplete(-206)VoIPStatusCodeSDKInitFailed(-1)VoIPStatusCodeLoginAuthFailed(-201):401VoIPStatusCodeSIPRegisterForbidden(-209):403VoIPStatusCodeSIPRegisterNotFound(-215):404VoIPStatusCodeNetworkTimeout(-401):408VoIPStatusCodeSIPRegisterServerError(-216):5xxVoIPStatusCodeSIPRegisterFailed(-207):默认通用错误VoIPStatusCodeInvalidAccount(-100)
| 版本号 | 更新时间 | 核心更新 |
| 1.0.4 | 26.1.19 | 1. 增加呼叫前黑名单校验2. 增加风控呼叫提示 |
| 1.0.3 | 26.1.12 | 1. 振铃音优化2. 修复优化passwordpk验证逻辑 |
| 1.0.2 | 12.29 | 支持加密密码登录 |
| 1.0.1 | 12.23 | 错误码问题新增静音和扬声器查询方法外呼支持返回callid外呼支持传自定义参数处理Headers头文件过多问题外呼挂断,标明主叫挂断还是被叫挂断 |
| 1.0.0 | 12.15 |