

git clone https://github.com/Tencent-RTC/video-chat-solution.git
video-chat-solution/blob/main/android/app/src/main/java/io/trtc/uikit/demo/debug/GenerateTestUserSig.java 文件,将开通服务时获取到的 SDKAppID 和 SDKSecretKey 填入其中:
video-chat-solution/android/app/src/main/java/io/trtc/uikit/demo/debug/GenerateTestUserSig.java 文件中。
video-chat-solution/tools/import_users_to_video_chat.py 脚本(需填入您的 AppID 和 SecretKey)。该脚本将自动注册 6 个推荐测试账号,登录后即可在“交友”模块中看到他们。

推荐用户列表 | 用户主页 | 消息列表 | 个人设置界面 |
![]() | ![]() | ![]() | ![]() |
登录账号 “Richard” | 关注推荐用户“林小雨” | 与“林小雨”视频通话 | 通话余额不足 | 与“林小雨”聊天 |
![]() | ![]() | ![]() | ![]() | ![]() |
登录账号 “林小雨” | 进入设置界面 | 调整美颜 | 接听 “Richard” 的通话 | 与 “Richard”聊天 |
![]() | ![]() | ![]() | ![]() | ![]() |
选择“联系人”栏目 | 搜索目标用户并添加好友 | 在“联系人”选择该用户进行聊天互动 | ||
![]() | ![]() | ![]() | ![]() | ![]() |
video-chat 和 tuikit 组件复制到您的工程目录下:
settings.gradle 中添加以下代码,完成功能接入。// 引入上层应用模块include ':app'// 引入 1v1 视频交友界面模块include ':video-chat'// 引入美颜功能模块include ':tebeautykit'project(':tebeautykit').projectDir = file("./tuikit/tebeautykit")// 引入 IM 组件公共模块include ':timcommon'project(':timcommon').projectDir = file("./tuikit/timcommon")// 引入会话功能模块 (基础功能模块)include ':tuiconversation'project(':tuiconversation').projectDir = file("./tuikit/tuiconversation")// 引入聊天功能模块 (基础功能模块)include ':tuichat'project(':tuichat').projectDir = file("./tuikit/tuichat")// 引入关系链功能模块 (基础功能模块)include ':tuicontact'project(':tuicontact').projectDir = file("./tuikit/tuicontact")// 引入搜索功能模块(需要购买旗舰版或企业版套餐)include ':tuisearch'project(':tuisearch').projectDir = file("./tuikit/tuisearch")
import io.trtc.tuikit.atomicxcore.api.CompletionHandlerimport io.trtc.tuikit.atomicxcore.api.login.LoginStoreLoginStore.shared.login(context,sdkAppID, // Int,控制台获取userID, // StringuserSig, // String,控制台或服务端生成object : CompletionHandler {override fun onSuccess() {// 登录成功,可跳转会话列表或聊天页}override fun onFailure(code: Int, desc: String) {// 登录失败,可弹框报错}})

import com.tencent.qcloud.tuikit.videochat.page.meet.MeetPageclass MeetPageFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)addMeetPage()}private fun addMeetPage() {val meetPage = MeetPage(requireContext())val container = findViewById<FrameLayout>(R.id.meet_container)container.addView(meetPage, MATCH_PARENT, MATCH_PARENT)}}
setPageUsers 输入推荐用户列表,并监听上拉刷新(onLoadMoreRequested)在尾部追加用户数据、下拉刷新回调(onRefreshRequested)全量更新用户数据。import com.tencent.qcloud.tuikit.videochat.page.meet.MeetPagemeetPage.setListener(object : MeetPage.Listener {// 下拉刷新时触发该回调override fun onRefreshRequested() {// 全量更新用户列表fetchUsers { users, hasMore ->// 输入用户列表meetPage.setPageUsers(users, hasMore)}}// 上拉刷新时触发该回调。override fun onLoadMoreRequested() {// 尾部追加新用户列表fetchNextPage { users, hasMore ->// 输入用户列表meetPage.setPageUsers(users, hasMore)}}})
fun setPageUsers(users: List<V2TIMUserFullInfo>, hasMore: Boolean)
参数 | 类型 | 说明 |
users | ||
hasMore | Boolean | 表示是否还有下一页: true:还有下一页,滚到底会继续触发 onLoadMoreRequested(); false:已无更多数据,滚到底不再触发回调。 |
setLayoutTemplate 选择您喜欢的布局。import com.tencent.qcloud.tuikit.videochat.page.meet.MeetPageclass MeetPageFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)addMeetPage()}private fun addMeetPage() {val meetPage = MeetPage(requireContext())// 设置布局meetPage.setLayoutTemplate(MeetPage.Template.LIST) // 设置布局为列表布局(默认)// meetPage.setLayoutTemplate(MeetPage.Template.GRID) // 设置布局为卡片布局val container = findViewById<FrameLayout>(R.id.meet_container)container.addView(meetPage, MATCH_PARENT, MATCH_PARENT)}}
fun setLayoutTemplate(template: MeetPage.Template)
参数 | 类型 | 说明 |
template | MeetPage.Template | 布局的类型: MeetPage.Template.LIST :列表布局。 MeetPage.Template.GRID :卡片布局。 |
ConversationPage 对象加入到自己的布局中即可。会话列表会从数据库中读取最近聊天记录,当用户点击聊天记录时,ConversationPage 会默认跳转到聊天界面。
val conversationPage = ConversationPage()// R.id.container 为您的布局中容纳会话列表 Fragment 的 FrameLayoutsupportFragmentManager.beginTransaction().add(R.id.container, conversationPage).commit()

Bundle param = new Bundle();param.putInt(TUIConstants.TUIChat.CHAT_TYPE, V2TIMConversation.V2TIM_C2C);val userId = "jack" // 对方的 UserIdparam.putString(TUIConstants.TUIChat.CHAT_ID, userId);TUICore.startActivity("TUIC2CChatActivity", param);

// 通过 AtomicxCore SDK 的通话模块 CallStore.calls 发起视频通话。CallStore.shared.calls(list, mediaType, params, object : CompletionHandler {override fun onFailure(code: Int, desc: String) {completion?.onFailure(code, desc)}override fun onSuccess() {completion?.onSuccess()// 通话成功,拉起 VideoCallPage 通话界面val intent = Intent(context, VideoCallPage::class.java)intent.flags = Intent.FLAG_ACTIVITY_NEW_TASKcontext.startActivity(intent)}})
模块 | 功能描述 |
通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。 | |
通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。 | |
音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 |
video-chat-solution/android/app/src/main/java/io/trtc/uikit/demo/debug/GenerateTestUserSig.java 中的 LicenseURL、LicenseKey 是否正确。获取可参考 License 指引。
模块 | 说明 |
登录管理类,用于处理用户登录、登出及用户信息管理等业务逻辑。 LoginStore 提供了一套完整的登录管理 API,包括用户登录、登出、设置个人信息等功能。 通过该类,可以管理用户的登录状态和用户资料。 | |
通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。 | |
通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。 | |
音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 |
问题 | 说明 |
如何判断 “发起通话” | actionType = 1 且 cmd = 'videoCall' 或 'audioCall' |
如何判断 “已接听” | actionType = 3 |
如何判断 “通话已结束”,“通话时长是多少” | actionType = 1 且 cmd = 'hangup',通话时长取 call_end 字段,单位:秒 |
如何判断 “取消通话” | actionType = 2 |
如何判断 “拒绝通话” | actionType = 4 |
如何判断对端忙线产生的 “拒绝通话” | actionType = 4 且 cmd = "line_busy" |
如何被叫判断 “超时未接听” | actionType = 5 |
tuikit/TUIChat/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/CallModel.java 源码,修改 getContentForSimplifyAppearance 函数完成自定义通话状态消息上屏。/*** 定制 1v1 通话状态消息上屏文案** 修改位置:CallModel.java → getContentForSimplifyAppearance()* 只需修改 participantType == CALL_PARTICIPANT_TYPE_C2C 分支内的文案即可。** 以下字段在方法内可直接使用:* - protocolType:通话协议类型(发起/接听/拒绝/取消/挂断/超时/忙线)* - participantRole:当前用户角色(主叫 CALLER / 被叫 CALLEE)* - streamMediaType:媒体类型(语音 VOICE / 视频 VIDEO)* - duration:通话时长(秒)*/// =================== 示例:定制 1v1 通话上屏文案 ===================// 在 getContentForSimplifyAppearance() 方法中,// 找到 if (participantType == CALL_PARTICIPANT_TYPE_C2C) 分支,修改如下:if (participantType == CALL_PARTICIPANT_TYPE_C2C) {// 判断媒体类型,用于拼接文案前缀val mediaPrefix = if (streamMediaType == CALL_STREAM_MEDIA_TYPE_VIDEO) {"[视频通话]" // 填写您的自定义视频通话文案} else {"[语音通话]" // 填写您的自定义语音通话文案}val isCaller = (participantRole == CALL_PARTICIPANT_ROLE_CALLER)// display 为显示的通话状态上屏消息display = when (protocolType) {// 通话被拒绝(actionType = 4,不含 line_busy)CALL_PROTOCOL_TYPE_REJECT -> {if (isCaller) {"$mediaPrefix 对方已拒绝"} else {"$mediaPrefix 已拒绝"}}// 通话已取消(actionType = 2,主叫取消)CALL_PROTOCOL_TYPE_CANCEL -> {if (isCaller) {"$mediaPrefix 已取消"} else {"$mediaPrefix 对方已取消"}}// 通话结束(actionType = 1,cmd = "hangup")// duration 字段为通话时长,单位:秒CALL_PROTOCOL_TYPE_HANGUP -> {val formattedDuration = DateTimeUtil.formatSecondsTo00(duration)"$mediaPrefix 通话时长 $formattedDuration"}// 超时未接听(actionType = 5)CALL_PROTOCOL_TYPE_TIMEOUT -> {if (isCaller) {"$mediaPrefix 对方无应答"} else {"$mediaPrefix 未接听"}}// 忙线拒绝(actionType = 4,顶层 JSON 包含 "line_busy" 字段)CALL_PROTOCOL_TYPE_LINE_BUSY -> {if (isCaller) {"$mediaPrefix 对方忙线"} else {"$mediaPrefix 忙线未接听"}}// 发起通话(actionType = 1,cmd = "videoCall" 或 "audioCall")CALL_PROTOCOL_TYPE_SEND -> {"$mediaPrefix 发起通话"}// 已接听(actionType = 3)CALL_PROTOCOL_TYPE_ACCEPT -> {"$mediaPrefix 已接听"}else -> {context.getString(R.string.invalid_command)}}}
// 设置消息监听器V2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener)/*** 收到新消息* @param msg 消息*/override fun onRecvNewMessage(msg: V2TIMMessage) {// 获取信令信息,非信令消息返回 nullval signalingInfo = V2TIMManager.getSignalingManager().getSignalingInfo(msg)?: return// 只处理 1v1 单聊(groupID 为空表示单聊,非空表示群聊)if (!signalingInfo.groupID.isNullOrEmpty()) {return}// 解析信令的 data 字段(JSON 格式)val gson = Gson()val jsonData: Map<String, Any> = try {gson.fromJson(signalingInfo.data, object : TypeToken<Map<String, Any>>() {}.type)} catch (e: Exception) {return}// 校验 businessID,确认是通话信令消息val businessId = jsonData["businessID"] as? Stringif (businessId != "av_call" && businessId != "tuikit_calling") {return}// 提取 data 子对象中的 cmdval dataMap = jsonData["data"] as? Map<*, *>val cmd = dataMap?.get("cmd") as? String// 根据 actionType 判断通话状态when (signalingInfo.actionType) {// ========== actionType = 1:发起邀请 ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_INVITE -> {when (cmd) {// 发起视频通话"videoCall" -> {val mediaType = "video"Log.i(TAG, "发起通话 | 类型: $mediaType, 主叫: ${msg.sender}")}// 发起语音通话"audioCall" -> {val mediaType = "audio"Log.i(TAG, "发起通话 | 类型: $mediaType, 主叫: ${msg.sender}")}// 挂断(通话结束)"hangup" -> {// 通话时长从顶层 call_end 字段获取,单位:秒val duration = jsonData["call_end"]?.toString()?.toDoubleOrNull()?.toInt() ?: 0Log.i(TAG, "通话结束 | 时长: ${duration}秒")}}}// ========== actionType = 2:取消通话(主叫取消) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_CANCEL_INVITE -> {Log.i(TAG, "通话已取消 | 主叫: ${msg.sender}")}// ========== actionType = 3:接听通话(被叫接受) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_ACCEPT_INVITE -> {Log.i(TAG, "通话已接听")}// ========== actionType = 4:拒绝通话(被叫拒绝) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_REJECT_INVITE -> {// 判断是否为忙线拒绝:顶层 JSON 包含 "line_busy" 字段if (jsonData.containsKey("line_busy")) {Log.i(TAG, "对方忙线拒绝")} else {Log.i(TAG, "通话被拒绝")}}// ========== actionType = 5:超时未接听 ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_INVITE_TIMEOUT -> {Log.i(TAG, "超时未接听")}}}

文档反馈