场景说明
使用 CallSDK 对接,可以将电话功能(外呼、挂断等)集成到您自己的业务系统中,在您自己的业务系统使用此功能。
集成方式灵活:
- 使用自带 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(真机) |
| 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> #import <JJCallKit/pj/log.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, // 使用生产环境
success: { result in
// result: NSDictionary,包含登录结果信息
print("登录成功")
},
failure: { code, message in
// code: 错误码
// message: 错误描述
print("登录失败: \(code), \(message ?? "")")
}
)
登出
manager.logout()
3.3 通话控制
发起呼叫
manager.makeCall("13800138000") // 被叫号码
挂断通话
manager.hangupCall()
检查是否可以拨打
if manager.canMakeCall() {
// 可以拨打电话
}
3.4 通话中控制
静音控制
manager.openMute(true) // true=开启静音,false=关闭静音
免提控制
manager.openLoudSpeaker(true) // true=开启免提,false=关闭免提
发送 DTMF 信号
manager.sendDTMF("1") // 参数:0-9, *, #
3.5 外显号码相关(可选)
获取外显策略配置
manager.getCallerStrategyWithSuccess({ config in
// config: 包含 mobile, agentNumber, numbers, numberSelect 等字段
print("外显策略: \(config)")
}, failure: { code, message in
print("获取失败: \(code), \(message ?? "")")
})
获取外显号码列表
manager.getDisplayNumberListWithSuccess({ numberList in
// numberList: 数组,每个元素包含 id, status, number, province, city
for number in numberList {
print("号码: \(number)")
}
}, failure: { code, message in
print("获取失败: \(code), \(message ?? "")")
})
设置外显号码
// 方式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 ?? "")")
})
3.6 状态通知
Framework 通过 NSNotificationCenter 发送状态通知,需监听这些通知来更新 UI。
通知常量列表
| 通知名称 | 说明 | UserInfo |
|---|---|---|
kSIPConnectedNotification |
SIP 连接成功 | - |
kSIPConnectFailedNotification |
SIP 连接失败 | kSIPReasonDescriptionKey |
kSIPDisconnectedNotification |
SIP 断开连接 | - |
kSIPKickedOutNotification |
被踢下线(453) | - |
kSIPCallCallingNotification |
正在呼叫 | - |
kSIPCallConnectingNotification |
振铃中 | - |
kSIPCallConfirmNotification |
呼叫接通 | - |
kSIPCallDisconnectNotification |
呼叫挂断 | - |
kSIPCallRetryNotification |
呼叫重试 | - |
3.7 错误码说明
错误码分类
- 0:成功
- 1:已登录
- -1 ~ -99:初始化错误
- -100 ~ -199:参数验证错误
- -200 ~ -299:登录流程错误
- -300 ~ -399:通话相关错误
- -400 ~ -499:网络错误
- -9999:未知错误
获取错误描述
let description = VoIPManager.errorDescriptionForCode(errorCode)
获取 SIP 状态码描述
let description = VoIPManager.sipStatusDescription(403) // 返回 "403 Forbidden - 禁止访问"
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("真正连接成功") }
通话相关
-
拨打电话前检查状态
if manager.canMakeCall() { manager.makeCall("13800138000") } -
通话接通后确保音频会话激活
NotificationCenter.default.addObserver( forName: NSNotification.Name(kSIPCallConfirmNotification), object: nil, queue: .main ) { _ in // 通话接通后,确保音频会话正确配置 PhoneService.shared.ensureAudioSessionForCall() } -
通话中操作需在接通后进行:静音、免提、DTMF 等操作应在通话接通后使用
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:请求已取消