tccc_user_call_sdk
效果展示

安装
flutter pub add tccc_user_call_sdk
平台要求
- Flutter >= 3.7.0(Dart >= 2.19.6)
- Android minSdk 21
- iOS 13.0
权限
使用前, 请完成权限申请。SDK 不会自己弹权限,也不要抄插件里的前台 <service>。
【Android】
不要加 CAMERA 或蓝牙权限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
【iOS】
编辑 Info.plist。麦克风用于通话;NSCameraUsageDescription 是 TXLiteAVSDK_Professional 的审核要求(本 SDK 不做视频)。不要加蓝牙用途说明。
<key>NSMicrophoneUsageDescription</key>
<string>用于语音通话</string>
<key>NSCameraUsageDescription</key>
<string>音频通话 SDK 链接了相机能力,本应用仅用于语音通话</string>
<key>UIBackgroundModes</key>
<array><string>audio</string></array>
上架
插件已内置 iOS PrivacyInfo.xcprivacy(CocoaPods resource_bundles)。宿主仍须在 App Store Connect / Google Play 填写本应用的隐私问卷。
iOS App Store
Info.plist必须有麦克风、相机用途说明,以及UIBackgroundModes = audio。缺NSCameraUsageDescription会被 ITMS-90683 拒绝。- 不要开启
NSAllowsArbitraryLoads。 - 默认会向腾讯云上报通话诊断日志(userId、网络与信令摘要)。可在上架前调用
setLogReportEnabled(false);App Privacy 问卷仍须按实际采集填写。 - 出口合规(
ITSAppUsesNonExemptEncryption)由宿主 App 自行声明。
Google Play
targetSdk34+(以当前 Play 政策为准)。- Play Console 声明 Foreground service types:Microphone 与 Media playback(service 在插件里,不要抄到宿主)。
- 运行时申请
RECORD_AUDIO;Android 13+ 再申请POST_NOTIFICATIONS(否则麦克风前台服务无法拉起)。 - 不要声明
CAMERA。 - 16 KB 页对齐由
tencent_rtc_sdk原生 so 决定,不是本插件 Java/Kotlin 层。
快速开始
含 UI 集成
为方便您的使用,减少开发成本,SDK 内置一套通话 UI 界面,使用方式如下所示:
-
注册 navigatorKey。
import 'package:flutter/material.dart'; import 'package:tccc_user_call_sdk/tccc_user_call_sdk.dart'; import 'package:tccc_user_call_sdk/ui.dart'; final navigatorKey = GlobalKey<NavigatorState>(); void main() { WidgetsFlutterBinding.ensureInitialized(); // navigatorKey 的全局注册 TcccCallUI.install(navigatorKey: navigatorKey); // 把 navigatorKey 配置进 MaterialApp runApp(MaterialApp(navigatorKey: navigatorKey, home: const HomePage())); } -
监听 events 并发起通话。
final userCallInstance = TcccUserCall.instance; // 先订阅 ready,再 init userCallInstance.events.listen((event) async { switch (event.type) { case 'ready': // 发起呼叫(只传 channelId) final started = await userCallInstance.startAudioCall( channelId, showUI: true, // 默认值为 true ); final session = started.data!; session.events.listen((event) { if (event.type == 'ended') { userCallInstance.unInit(); } }); case 'connecting': case 'connected': case 'disconnected': case 'error': break; default: break; } }); -
创建用户。
userCallInstance.createUser( sdkAppId: sdkAppId, userId: userId, userSig: userSig, ); -
初始化。
userCallInstance.init();
无 UI 集成
您可以基于 TcccUserCall 和 Session 的接口和事件回调,自实现通话页面。
-
监听 events 并发起通话。
final userCallInstance = TcccUserCall.instance; // 先订阅 ready,再 init userCallInstance.events.listen((event) async { switch (event.type) { case 'ready': // 发起呼叫(传 showUI: false) final started = await userCallInstance.startAudioCall( channelId, showUI: false, ); final session = started.data; session.events.listen((event) { switch (event.type) { case 'warning': // (event as TcccWarningEvent).code / .serverType.trtc → TRTC 警告码文档 break; case 'newDTMF': // (event as TcccNewDTMFEvent).tone / originator / duration break; case 'muted': case 'unmuted': // muted / unmuted break; case 'error': // 可见下方错误码表格 break; case 'networkquality': // 当前值也在 status.uplinkQuality / downlinkQuality break; case 'disconnected': case 'connecting': case 'connected': break; case 'loginExpired': break; case 'onRoomEntered': break; case 'progress': // (event as TcccProgressEvent).response.statusCode / reasonPhrase break; case 'accepted': // SIP 200。(event as TcccAcceptedEvent).response.statusCode break; case 'confirmed': // 当对方已接听并且本地协议栈确认后(发送了 ack 信令)会触发该事件。无事件对象参数。 break; case 'ended': case 'failed': // (event as TcccEndedEvent / TcccFailedEvent).cause / .originator user.unInit(); break; default: break; } }); case 'connecting': case 'connected': case 'disconnected': // connecting.attempts;disconnected.error / .code / .reason break; case 'error': // 可见下方错误码表格 break; default: break; } }); -
创建用户。
userCallInstance.createUser( sdkAppId: sdkAppId, userId: userId, userSig: userSig, ); -
初始化。
await userCallInstance.init(); -
通话中操作。
// 以下是通话中需要使用的方法, 请按需调用 await session.muteAudio(true); await session.setAudioRoute(TcccAudioRoute.speakerphone); await session.sendDTMFTone('1'); await session.terminate();
方法及事件说明
TcccUserCall 方法
TcccUserCall.instance 是单例。
createUser
创建 user。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sdkAppId | int | 是 | 腾讯云联络中心 SDKAppId |
| userId | String | 是 | 不能为空、不能含 @ |
| userSig | String | 是 | 业务侧后台签发 |
| userClientData | String | 否 | CreateUserSig 带了 ClientData 时必填且一致;否则不要指定 |
返回值: TcccResult
final userCallInstance = TcccUserCall.instance;
final created = userCallInstance.createUser(
sdkAppId: 1400000000,
userId: userId,
userSig: userSig,
);
init
初始化 SDK。
返回值: TcccOutcome
final userCallInstance = TcccUserCall.instance;
final ready = userCallInstance.init();
updateUserSig
替换已绑定的 userSig。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userSig | String | 是 | 业务侧后台签发的新 userSig |
| userClientData | String? | 否 | 同时替换时传入;不传则保持原值。CreateUserSig 带了 ClientData 时须与签发一致 |
返回值: TcccOutcome
final userCallInstance = TcccUserCall.instance;
userCallInstance.updateUserSig(userSig);
unInit
反初始化 SDK,并清掉 createUser 接口创建的 user 的身份。通话结束后或页面销毁时调用。
返回值: TcccOutcome
final userCallInstance = TcccUserCall.instance;
userCallInstance.unInit();
startAudioCall
发起音频呼叫。
说明:
请在 ready event 触发之后调用。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| audioChannelId | String | 是 | 渠道 ID |
| showUI | bool | 否 | 是否弹出内置通话UI。默认 true |
| peerDisplayName | String? | 否 | 对端展示名(内置 UI) |
| peerSubtitle | String? | 否 | 对端子标题(内置 UI) |
| ringback | TcccRingback | 否 | 默认 builtIn;none 则不播放内置回铃 |
返回值: TcccResult
final userCallInstance = TcccUserCall.instance;
final started = await userCallInstance.startAudioCall(channelId);
final session = started.data;
TcccUserCall 事件
| 事件名 | 类型 | 触发时机 |
|---|---|---|
| connecting / connected / disconnected | TcccConnectingEvent / TcccConnectedEvent / TcccDisconnectedEvent | 首次 init 为 connecting → connected → ready |
| ready | TcccReadyEvent | 第一次 init 成功,只一次 |
| error | TcccErrorEvent | createUser / init / 占座失败 |
| warning | TcccWarningEvent | 仅 -11001(未安装内置 UI 仍外呼) |
Session 方法
startAudioCall 成功后返回的会话实例。
terminate
挂断当前通话。已接通发挂断,未接通则取消。
返回值: TcccOutcome
await session.terminate();
muteAudio
静音 / 取消静音本地麦克风。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| mute | bool | 是 | true 静音,false 取消静音 |
返回值: TcccOutcome
await session.muteAudio(true);
await session.muteAudio(false);
sendDTMFTone
发送单个 DTMF(IVR 按键)。连续调用会排队。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| tone | String | 是 | 单个字符:0–9、#、*、A–D |
| duration | int? | 否 | INFO Duration=,毫秒。默认 100,夹取 70–6000 |
| interToneGap | int? | 否 | 与 Web 相同:队列间隔为 duration + interToneGap。默认 500,下限 50 |
返回值: TcccOutcome
await session.sendDTMFTone('1');
await session.sendDTMFTone('#', duration: 160);
// await session.sendDTMFTone('1', duration: 160, interToneGap: 500);
setAudioRoute
切换听筒 / 扬声器。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| route | TcccAudioRoute | 是 | earpiece 听筒;speakerphone 扬声器 |
返回值: TcccOutcome
await session.setAudioRoute(TcccAudioRoute.speakerphone);
// await session.setAudioRoute(TcccAudioRoute.earpiece);
Session 事件
会话事件, 明细如下表所示.
| 事件名 | 类型 | 触发时机 |
|---|---|---|
| progress | TcccProgressEvent | 18x。originator、response.statusCode / reasonPhrase |
| warning | TcccWarningEvent | 警告,通话继续。 |
| newDTMF | TcccNewDTMFEvent | DTMF 发送成功。 |
| muted / unmuted | TcccMutedEvent / TcccUnmutedEvent | 本地静音变化。audio;当前值也在 isMuted().audio |
| error | TcccErrorEvent | 进房后错误等(通话继续或已接通后的非终态错误)。进房后 TRTC onError 的 serverType 为 trtc,其余为 tccc |
| networkquality | TcccNetworkQualityEvent | 质量变化。uplinkNetworkQuality / downlinkNetworkQuality;当前值也在 status |
| connecting / connected / disconnected | TcccConnectingEvent / TcccConnectedEvent / TcccDisconnectedEvent | TRTC。serverType 为 trtc。 |
| loginExpired | TcccLoginExpiredEvent | 换票失败。 |
| onRoomEntered | TcccOnRoomEnteredEvent | TRTC 进房成功。纯 IVR 不发 |
| accepted | TcccAcceptedEvent | SIP 200。originator、response.statusCode / reasonPhrase |
| confirmed | TcccConfirmedEvent | INVITE 2xx 已 ACK。Web demo 此时切到通话中。originator 主叫 ACK 为 local。可通话仍看 status.state == accepted |
| ended | TcccEndedEvent | 已接通后结束。cause / originator / 可选 failure。同时完成 session.ended |
| failed | TcccFailedEvent | 未接通失败(拒接、取消、刷票失败、TRTC 踢房等)。cause / originator。同时完成 session.ended。不另发 fatal error |
示例代码:
session.events.listen((event) {
switch (event.type) {
case 'ended':
case 'failed':
break;
default:
break;
}
});
结束原因 ended.cause
| 值(cause / cause.value) | 含义 |
|---|---|
| terminated / Terminated | 已接通后挂断。originator.local 本地,remote 对端 BYE。ended.success == true |
| canceled / Canceled | 未接通取消。originator.local 本地,remote 对端 |
| busy / Busy | 486、600 |
| rejected / Rejected | 403、603 |
| notFound / Not Found | 404、604 |
| unavailable / Unavailable | 480、410、408、430 |
| noAnswer / No Answer | TcccConfig.noAnswerTimeout:第一次 18x 后仍无 200。默认不设上限 |
| expires / Expires | Session Timer 超时 |
| requestTimeout / Request Timeout | 信令事务超时(不是 SIP 408) |
| connectionError / Connection Error | WSS / 网络 |
| sipFailureCode / SIP Failure Code | 其它 SIP 失败 |
| internalError / Internal Error | 内部错误(含 18x 无媒体 20002) |
| authenticationError / Authentication Error | userSig / 换票失败;SIP 401、407 |
| webRtcError / WebRTC Error | TRTC 进房失败或踢房(Rtc.KickedOut 走 failed) |
| addressIncomplete / Address Incomplete | 484、424 |
| redirected / Redirected | 300、301、302、305、380 |
| incompatibleSdp / Incompatible SDP | 488、606 |
| dialogError / Dialog Error | Dialog 错误 |
| userDeniedMediaAccess / User Denied Media Access | 麦克风被拒 |
| rtpTimeout / RTP Timeout | 媒体超时 |
错误码
| 错误码 | 错误码类型 | 来源 | 触发时机 |
|---|---|---|---|
| -11010 | ErrorCode.User.instanceExists | 返回值 | 未 unInit 再 createUser |
| -11004 | ErrorCode.User.notReady | 返回值 | 未 createUser / 未 init |
| -11002 | ErrorCode.User.invalidParam | 返回值 | sdkAppId / userSig / channelId 空或非法 |
| 20003 | ErrorCode.User.invalidUserId | 返回值 | userId 或 channelId 非法 |
| -11003 | ErrorCode.User.disconnected | 返回值或 ended | init 握手失败;或通话中重连耗尽 |
| -10004 | ErrorCode.User.cannotReset | 返回值 | 通话中 unInit |
| -11005 | ErrorCode.User.callInProgress | 仅返回值 | 已有一路呼叫 |
| -11006 | ErrorCode.Session.invalidState | 仅返回值 | 已结束的通话上再操作 |
| -11007 | ErrorCode.Session.endedBeforeMilestone | 里程碑 Future | 通话已结束,该 sent / ringing / accepted 没走到 |
| -10002 | ErrorCode.User.invalidUserSig | ended | 空 userSig / 空 jwt / sdkLogin bizCode -2014 / 二次 401 |
| -10003 | ErrorCode.Cgi.bizError | ended | sdkLogin HTTP 成功但 errorCode != 0(-2014 除外)。failure.detail 有 bizCode / httpStatus / requestId |
| -10001 | ErrorCode.Cgi.error | ended | 鉴权网络失败或 HTTP 4xx/5xx。failure.detail 有 bizCode / httpStatus / requestId |
| 20002 | ErrorCode.Session.answerFailure | ended | 18x 无可用媒体 |
| -3301 | ErrorCode.Rtc.joinRoomFailed | ended | 进房失败 |
| -3325 | ErrorCode.Rtc.kickedOut | ended(failed) | 被踢 / 房间解散 |
| 100–699 | SIP 状态(failure.sipStatusCode 同值) | ended | 对端 SIP。忙/拒/找不到看 ended.cause,不要按码硬编码 |
| 20006 | ErrorCode.Dtmf.invalidState | 仅返回值 | 未接通发 DTMF |
| 20005 | ErrorCode.Dtmf.invalidParam | 返回值 + session.events error | DTMF 字符非法 |
| -11008 | ErrorCode.Dtmf.queueFull | 仅返回值 | DTMF 队列满 |
| 20004 | ErrorCode.Dtmf.sendFailed | 返回值 + session.events error | DTMF INFO 发送抛错,或队列 drain 时已无 InviteSession |
| 20007 | ErrorCode.Dtmf.timeout | 返回值 + session.events error | DTMF INFO 超时或 408 |
| 20008 | ErrorCode.Dtmf.transportError | 返回值 + session.events error | DTMF INFO 传输层失败 |
| 20009 | ErrorCode.Dtmf.dialogError | 返回值 + session.events error | DTMF INFO 481 或无 dialog |
| 20010 | ErrorCode.Dtmf.responseError | 返回值 + session.events error | DTMF INFO 对端 4xx/5xx(非 408/481) |
| -1332 | ErrorCode.Rtc.audioRouteFail | 仅返回值 | setAudioRoute 异常 |
| -13xx / -33xx | ErrorCode.Rtc.*(除 -3301 / -3325) | session.events error | 进房后 TRTC onError 原样,通话继续。查 TRTC 错误码 |
Libraries
- tccc_user_call_sdk
- L0 public barrel for the tccc_user_call_sdk SDK.
- ui
- L1 call UI barrel. Apps that only need the business API should import
package:tccc_user_call_sdk/tccc_user_call_sdk.dartinstead.