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

  • targetSdk 34+(以当前 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 界面,使用方式如下所示:

  1. 注册 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()));
    }
    
  2. 监听 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;
      }
    });
    
  3. 创建用户。

    userCallInstance.createUser(
      sdkAppId: sdkAppId,
      userId: userId,
      userSig: userSig,
    );
    
  4. 初始化。

    userCallInstance.init();
    

无 UI 集成

您可以基于 TcccUserCall 和 Session 的接口和事件回调,自实现通话页面。

  1. 监听 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;
      }
    });
    
  2. 创建用户。

    userCallInstance.createUser(
      sdkAppId: sdkAppId,
      userId: userId,
      userSig: userSig,
    );
    
  3. 初始化。

    await userCallInstance.init();
    
  4. 通话中操作。

    // 以下是通话中需要使用的方法, 请按需调用
    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.dart instead.