CallSDK IOS 接⼊⽂档

JJCallKit 对接文档

场景说明

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

集成方式灵活

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

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


环境版本等限制

系统版本要求

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

Framework 依赖

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

权限要求

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

已知限制

  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: 失败回调,返回错误码和错误信息

登出

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 呼叫重试 -

UserInfo Keys 说明

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

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("真正连接成功")
    }
    

通话相关

  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.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:请求已取消

Demo+SDK(1).zip
11.4 MB
版本号更新时间核心更新
1.0.212.29支持加密密码登录
1.0.112.23错误码问题新增静音和扬声器查询方法外呼支持返回callid外呼支持传自定义参数处理Headers头文件过多问题外呼挂断,标明主叫挂断还是被叫挂断
1.0.012.15
2025-12-29