
SDKAppID and SDKSecretKey. These will be used in a later step (Configure and Run the Demo).
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 file, and enter the SDKAppID and SDKSecretKey obtained when enabling the service:
video-chat-solution/android/app/src/main/java/io/trtc/uikit/demo/debug/GenerateTestUserSig.java file.
video-chat-solution/tools/import_users_to_video_chat.py script (you will need to enter your AppID and SecretKey). This script will automatically register 6 recommended test accounts. Once logged in, you will be able to see them in the "Social" module.

Meet List | User Profile | Message List | Personal Settings |
![]() | ![]() | ![]() | ![]() |
Log in to account "Richard" | Follow the recommended user "Luna" | Start a video call with "Luna" | Insufficient balance for the call | Chat with "Luna" |
![]() | ![]() | ![]() | ![]() | ![]() |
Log in to Luna's account "VideoChatchenkexin" | Enter the Settings page | Adjust beauty filters | Answer the call from "Richard" | Chat with "Richard" |
![]() | ![]() | ![]() | ![]() | ![]() |
Select the "Contacts" tab | Search for the target user and add them as a friend | Select the user in "Contacts" to start a chat | ||
![]() | ![]() | ![]() | ![]() | ![]() |

// Include the app moduleinclude ':app'// Include the 1v1 video social UI moduleinclude ':video-chat'// Include the beauty filter moduleinclude ':tebeautykit'project(':tebeautykit').projectDir = file("./tuikit/tebeautykit")// Include the IM common moduleinclude ':timcommon'project(':timcommon').projectDir = file("./tuikit/timcommon")// Include the conversation module (core module)include ':tuiconversation'project(':tuiconversation').projectDir = file("./tuikit/tuiconversation")// Include the chat module (core module)include ':tuichat'project(':tuichat').projectDir = file("./tuikit/tuichat")// Include the contact module (core module)include ':tuicontact'project(':tuicontact').projectDir = file("./tuikit/tuicontact")// Include the search module (requires the Ultimate or Enterprise Edition plan)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, obtained from the consoleuserID, // StringuserSig, // String, generated in the console or by your serverobject : CompletionHandler {override fun onSuccess() {// Login successful. You can navigate to the conversation list or chat page.}override fun onFailure(code: Int, desc: String) {// Login failed. You can show an error prompt.}})
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)}}
onLoadMoreRequested) to append user data to the end of the list, and the pull-down-to-refresh callback (onRefreshRequested) to fully update the user data.import com.tencent.qcloud.tuikit.videochat.page.meet.MeetPagemeetPage.setListener(object : MeetPage.Listener {// Triggered upon a pull-down-to-refresh actionoverride fun onRefreshRequested() {// Fully update the user listfetchUsers { users, hasMore ->// Pass in the user listmeetPage.setPageUsers(users, hasMore)}}// Triggered upon a pull-up-to-load-more actionoverride fun onLoadMoreRequested() {// Append new users to the end of the listfetchNextPage { users, hasMore ->// Pass in the user listmeetPage.setPageUsers(users, hasMore)}}})
setPageUsers:fun setPageUsers(users: List<V2TIMUserFullInfo>, hasMore: Boolean)
Parameter | Type | Description |
users | The user list. V2TIMUserFullInfo is an object containing essential user information such as ID, nickname, avatar, and gender. | |
hasMore | Boolean | Indicates whether there is a next page: true: There is a next page. Scrolling to the bottom will continue to trigger onLoadMoreRequested(). false: No more data is available. Scrolling to the bottom will no longer trigger the callback. |
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())// Set the layoutmeetPage.setLayoutTemplate(MeetPage.Template.LIST) // Set to list layout (default)// meetPage.setLayoutTemplate(MeetPage.Template.GRID) // Set to grid layoutval container = findViewById<FrameLayout>(R.id.meet_container)container.addView(meetPage, MATCH_PARENT, MATCH_PARENT)}}
setLayoutTemplate:fun setLayoutTemplate(template: MeetPage.Template)
Parameter | Type | Description |
template | MeetPage.Template | The layout type : MeetPage.Template.LIST :List layout. MeetPage.Template.GRID :Grid layout. |
ConversationPage object and add it to your layout. The conversation list automatically reads recent chat records from the database. When a user taps on a chat record, the ConversationPage navigates to the chat page by default.
val conversationPage = ConversationPage()// R.id.container is the FrameLayout in your layout that hosts the conversation list FragmentsupportFragmentManager.beginTransaction().add(R.id.container, conversationPage).commit()

Bundle param = new Bundle();param.putInt(TUIConstants.TUIChat.CHAT_TYPE, V2TIMConversation.V2TIM_C2C);val userId = "jack" // The peer's UserIdparam.putString(TUIConstants.TUIChat.CHAT_ID, userId);TUICore.startActivity("TUIC2CChatActivity", param);

// Initiate a video call via CallStore.calls of the AtomicxCore SDK calling module.CallStore.shared.calls(list, mediaType, params, object : CompletionHandler {override fun onFailure(code: Int, desc: String) {completion?.onFailure(code, desc)}override fun onSuccess() {completion?.onSuccess()// Call succeeded. Navigate to the VideoCallPage UI.val intent = Intent(context, VideoCallPage::class.java)intent.flags = Intent.FLAG_ACTIVITY_NEW_TASKcontext.startActivity(intent)}})
Module | Description |
Core component of the call view. Automatically listens to CallStore data to render the video, while providing UI customization options such as layout switching, and avatar/icon configuration. | |
Call lifecycle management: making, answering, rejecting, and hanging up calls. Retrieves real-time audio/video status of participants, call duration, call logs, and other data. | |
Audio/video device control: microphone (on/off / volume), camera (on/off / switch / quality), and screen sharing, along with real-time monitoring of device status. |
Feature | Description | Integration Guide |
Call Billing | We provide highly accurate server-side call status callbacks. You can listen for the server-side callbacks for call start and call end to implement call billing functionality. | |
Integrate Beauty Effects | We provide two beauty effect solutions: Basic Beauty (built-in) and Advanced Beauty (requires additional integration and payment). You can choose the solution that best fits your needs. | |
Integrate Voice Effects | Implementation methods and application scenarios for TRTC voice effects, helping developers create a rich and diverse interactive audio experience for users. | |
Integrate AI Real-Time Translation | An essential feature for globalizing your app, helping you break down language barriers for your users. |
video-chat-solution/android/app/src/main/java/io/trtc/uikit/demo/debug/GenerateTestUserSig.java are correct. For how to obtain them, please refer to the License Guide.
Module | Description |
The login management class used to handle business logic such as user login, logout, and user information management. LoginStore provides a complete set of login management APIs, including functions for logging in, logging out, and setting personal information. You can use this class to manage user login states and profiles. | |
Core component for the call view. It automatically listens to CallStore data to handle video rendering, and provides UI customization capabilities such as layout switching, and avatar/icon configurations. | |
Call lifecycle management: making, answering, rejecting, and hanging up calls. It obtains real-time data including the audio/video status of call participants, call duration, and call logs. | |
Audio/video device control: microphone (on/off/volume), camera (on/off/switch/quality), and screen sharing, along with real-time monitoring of device status. |
Scenario | Judgment Logic |
Identify "Call Initiated" | actionType = 1 and cmd = 'videoCall' or 'audioCall' |
Identify "Call Answered" | actionType = 3 |
Identify "Call Ended" & "Call Duration" | actionType = 1 and cmd = 'hangup'. The duration is obtained from the call_end field (in seconds). |
Identify "Call Canceled" | actionType = 2 |
Identify "Call Rejected" | actionType = 4 |
Identify "Call Rejected due to Line Busy" | actionType = 4 and cmd = "line_busy" |
Identify "Call Timeout" (unanswered) | actionType = 5 |
tuikit/TUIChat/tuichat/src/main/java/com/tencent/qcloud/tuikit/tuichat/bean/CallModel.java and modify the getContentForSimplifyAppearance function to customize how call status messages are displayed on the UI./*** Customize the display text for 1v1 call status messages** Modification location: CallModel.java -> getContentForSimplifyAppearance()* You only need to modify the text inside the `participantType == CALL_PARTICIPANT_TYPE_C2C` branch.** The following fields can be used directly within the method:* - protocolType: Call protocol type (Initiated/Answered/Rejected/Canceled/Hung up/Timeout/Line Busy)* - participantRole: Current user role (CALLER / CALLEE)* - streamMediaType: Media type (VOICE / VIDEO)* - duration: Call duration (in seconds)*/// =================== Example: Customizing 1v1 Call Display Text ===================// In the getContentForSimplifyAppearance() method,// locate the `if (participantType == CALL_PARTICIPANT_TYPE_C2C)` branch and modify it as follows:if (participantType == CALL_PARTICIPANT_TYPE_C2C) {// Determine the media type to append the text prefixval mediaPrefix = if (streamMediaType == CALL_STREAM_MEDIA_TYPE_VIDEO) {"[Video Call]" // Enter your custom text for video calls} else {"[Voice Call]" // Enter your custom text for voice calls}val isCaller = (participantRole == CALL_PARTICIPANT_ROLE_CALLER)// `display` is the call status message shown on the UIdisplay = when (protocolType) {// Call rejected (actionType = 4, excluding line_busy)CALL_PROTOCOL_TYPE_REJECT -> {if (isCaller) {"$mediaPrefix The peer rejected the call"} else {"$mediaPrefix Call rejected"}}// Call canceled (actionType = 2, canceled by caller)CALL_PROTOCOL_TYPE_CANCEL -> {if (isCaller) {"$mediaPrefix Call canceled"} else {"$mediaPrefix The peer canceled the call"}}// Call ended (actionType = 1, cmd = "hangup")// The `duration` field indicates the call duration in secondsCALL_PROTOCOL_TYPE_HANGUP -> {val formattedDuration = DateTimeUtil.formatSecondsTo00(duration)"$mediaPrefix Call duration: $formattedDuration"}// Timeout/Unanswered (actionType = 5)CALL_PROTOCOL_TYPE_TIMEOUT -> {if (isCaller) {"$mediaPrefix No response from the peer"} else {"$mediaPrefix Unanswered"}}// Rejected due to line busy (actionType = 4, top-level JSON contains the "line_busy" field)CALL_PROTOCOL_TYPE_LINE_BUSY -> {if (isCaller) {"$mediaPrefix The peer is busy"} else {"$mediaPrefix Line busy"}}// Call initiated (actionType = 1, cmd = "videoCall" or "audioCall")CALL_PROTOCOL_TYPE_SEND -> {"$mediaPrefix Call initiated"}// Call answered (actionType = 3)CALL_PROTOCOL_TYPE_ACCEPT -> {"$mediaPrefix Call answered"}else -> {context.getString(R.string.invalid_command)}}}
// Set the message listenerV2TIMManager.getMessageManager().addAdvancedMsgListener(advancedMsgListener)/*** Received a new message* @param msg The message object*/override fun onRecvNewMessage(msg: V2TIMMessage) {// Get signaling information. Returns null for non-signaling messagesval signalingInfo = V2TIMManager.getSignalingManager().getSignalingInfo(msg)?: return// Only process 1v1 chats (an empty groupID indicates a 1v1 chat; non-empty indicates a group chat)if (!signalingInfo.groupID.isNullOrEmpty()) {return}// Parse the data field of the signaling message (JSON format)val gson = Gson()val jsonData: Map<String, Any> = try {gson.fromJson(signalingInfo.data, object : TypeToken<Map<String, Any>>() {}.type)} catch (e: Exception) {return}// Verify businessID to confirm it is a call signaling messageval businessId = jsonData["businessID"] as? Stringif (businessId != "av_call" && businessId != "tuikit_calling") {return}// Extract `cmd` from the nested `data` objectval dataMap = jsonData["data"] as? Map<*, *>val cmd = dataMap?.get("cmd") as? String// Determine the call status based on actionTypewhen (signalingInfo.actionType) {// ========== actionType = 1: Initiate Invitation ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_INVITE -> {when (cmd) {// Initiate a video call"videoCall" -> {val mediaType = "video"Log.i(TAG, "Call initiated | Type: $mediaType, Caller: ${msg.sender}")}// Initiate a voice call"audioCall" -> {val mediaType = "audio"Log.i(TAG, "Call initiated | Type: $mediaType, Caller: ${msg.sender}")}// Hang up (Call ended)"hangup" -> {// Call duration is obtained from the top-level `call_end` field (in seconds)val duration = jsonData["call_end"]?.toString()?.toDoubleOrNull()?.toInt() ?: 0Log.i(TAG, "Call ended | Duration: ${duration}s")}}}// ========== actionType = 2: Cancel Call (Canceled by Caller) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_CANCEL_INVITE -> {Log.i(TAG, "Call canceled | Caller: ${msg.sender}")}// ========== actionType = 3: Answer Call (Accepted by Callee) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_ACCEPT_INVITE -> {Log.i(TAG, "Call answered")}// ========== actionType = 4: Reject Call (Rejected by Callee) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_REJECT_INVITE -> {// Determine if it was rejected due to line busy: the top-level JSON contains a "line_busy" fieldif (jsonData.containsKey("line_busy")) {Log.i(TAG, "Rejected: The peer is busy")} else {Log.i(TAG, "Call rejected")}}// ========== actionType = 5: Timeout (Unanswered) ==========V2TIMSignalingInfo.SIGNALING_ACTION_TYPE_INVITE_TIMEOUT -> {Log.i(TAG, "Call timeout (Unanswered)")}}}

Apakah halaman ini membantu?
Anda juga dapat Menghubungi Penjualan atau Mengirimkan Tiket untuk meminta bantuan.
masukan