tencent cloud

Feedback

Last updated: 2024-07-18 14:26:14

    Business Process

    This section summarizes some common business processes in voice chat rooms to help you better understand the entire scenario implementation process.
    Room management process
    Room owner seat management process
    Audience seat management process
    The following figure shows the room management process, including the creation, joining, exiting, and dissolvement of rooms.
    
    
    
    The following figure shows the room owner seat management process, including inviting a listener to speak, removing a speaker, and muting a seat.
    
    
    
    The following figure shows the audience seat management process, including becoming a speaker, become a listener, and moving a seat.
    
    
    

    Integration Preparation

    Step 1. Activating the service.

    Voice chat room scenarios usually require dependencies on two paid PaaS services from Tencent Cloud, Instant Messaging (IM) and Tencent Real-Time Communication (TRTC) for construction.
    1. First, you need to log in to the Tencent Real-Time Communication (TRTC) console to create an application. At this time, in the Instant Messaging (IM) console, an IM experience edition application with the same SDKAppID as the current TRTC application will be automatically created. The account and authentication system of the two can be reused. Subsequently, you can choose to upgrade the TRTC or IM application version as needed. For example, advanced versions can unlock more value-added feature services.
    
    
    
    Note:
    It is recommended to create two applications for testing and production environments, respectively. Each Tencent Cloud account (UIN) is given 10,000 minutes of free duration every month for one year.
    TRTC offers monthly subscription plans including the experience edition (default), basic edition, and professional edition. Different value-added feature services can be unlocked. For details, see Version Features and Monthly Subscription Plan Instructions.
    2. After an application is created, you can see the basic information of the application in the Application Management - Application Overview section. It is important to keep the SDKAppID and SDKSecretKey safe for later use and to avoid key leakage that could lead to traffic theft.
    
    
    

    Step 2: Importing SDK.

    The TRTC SDK and IM SDK have been released to the mavenCentral repository. You can configure Gradle to download and update automatically.
    1. Add the dependency for the appropriate version of the SDK in dependencies.
    dependencies {
    // TRTC SDK Simplified Edition. It has two features of the TRTC and live streaming playback.
    implementation 'com.tencent.liteav:LiteAVSDK_TRTC:latest.release'
    // Add IM SDK. Entering the latest Version No. is recommended.
    implementation 'com.tencent.imsdk:imsdk-plus:Version number'
    
    // If you need to add the Quic plugin, uncomment the next line (Note: Version No. of the plugin needs to be the same as that of the IM SDK).
    // implementation 'com.tencent.imsdk:timquic-plugin:Version number'
    }
    Note:
    Besides the recommended automatic loading method, you can also choose to download the SDK and manually import it. For details, see Manually Integrating the TRTC SDK and Manually Integrating IM SDK.
    Quic plugin offers axp-quic Multiplexing Transmission Protocol, providing better resistance to poor networks. Even with a packet loss rate of 70%, it still can offer services. Available only for Flagship users. For non-Flagship users, purchase the Flagship package before use, and see Pricing Instructions. To ensure proper functionality, update Terminal SDK to version 7.7.5282 or above.
    2. Specify the CPU architecture used by the app in defaultConfig.
    defaultConfig {
    ndk {
    abiFilters "armeabi-v7a", "arm64-v8a"
    }
    }
    Note:
    The TRTC SDK supports architectures including armeabi, armeabi-v7a and arm64-v8a. Additionally, it supports architectures for simulators including x86 and x86_64.
    The IM SDK supports architectures including armeabi-v7a, arm64-v8a, x86, and x86_64. To reduce the size of the installer package, you can choose to package SO files for only a subset of these architectures.

    Step 3: Project configuration.

    1. To configure app permissions in AndroidManifest.xml, for voice chat scenarios, both the TRTC SDK and IM SDK require the following permissions:
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    Note:
    The TRTC SDK does not have built-in permission request logic. You need to declare the corresponding permissions and features yourself. Some permissions (such as storage and recording), also require runtime dynamic requests.
    If the Android project's targetSdkVersion is 31 or higher, or if the target device runs Android 12 or a newer version, the official requirement is to dynamically request android.permission.BLUETOOTH_CONNECT permission in the code to use the Bluetooth feature properly. For more information, see Bluetooth Permissions.
    2. Since we use Java's reflection features inside the SDK, you need to add relevant TRTC SDK classes to the non-obfuscation list in the proguard-rules.pro file:
    -keep class com.tencent.** { *; }

    Integration Process

    Step 1: Generate authentication credentials.

    UserSig is a security protection signature designed by Tencent Cloud. Its purpose is to prevent malicious attackers from misappropriating your cloud service usage rights. Tencent Cloud's Tencent Real-Time Communication (TRTC) and Instant Messaging (IM) services both implement this security mechanism. TRTC authentication when entering a room, and IM authentication when logging in.
    Debugging Stage: UserSig can be generated through two methods for debugging and testing purposes only: client sample code and console access.
    Formal Operation Stage: It is recommended to use a higher security level server computation for generating UserSig. This is to prevent key leakage due to client reverse engineering.
    The specific implementation process is as follows:
    1. Before calling the SDK's initialization function, your app must first request UserSig from your server.
    2. Your server computes the UserSig based on the SDKAppID and UserID.
    3. The server returns the computed UserSig to your app.
    4. Your app passes the obtained UserSig into the SDK through a specific API.
    5. The SDK submits the SDKAppID + UserID + UserSig to Tencent Cloud CVM for verification.
    6. Tencent Cloud verifies the UserSig and confirms its validity.
    7. Once the verification is passed, it will provide instant communication services to the IM SDK and Tencent Real-Time Communication (TRTC) services to the TRTC SDK.
    
    
    
    Note:
    The local computation method of UserSig during the debugging stage is not recommended for application in an online environment. It is prone to reverse engineering, leading to key leakage.
    We provide server computation source code for UserSig in multiple programming languages (Java/GO/PHP/Nodejs/Python/C#/C++). For details, see Server Computation of UserSig.

    Step 2: Initialization and listening.

    Sequence diagram

    
    
    
    1. Initialize the IM SDK and add event listeners.
    // Add event listener.
    V2TIMManager.getInstance().addIMSDKListener(imSdkListener);
    // Initialize the IM SDK. After calling this API, you can immediately call the log-in API.
    V2TIMManager.getInstance().initSDK(context, sdkAppID, null);
    
    // After the SDK is initialized, it will trigger various events, such as connection status and expiration of log-in credentials.
    private V2TIMSDKListener imSdkListener = new V2TIMSDKListener() {
    @Override
    public void onConnecting() {
    Log.d(TAG, "IM SDK is connecting to Tencent cloud service");
    }
    
    @Override
    public void onConnectSuccess() {
    Log.d(TAG, "IM SDK has successfully connected to Tencent cloud service");
    }
    };
    
    // Remove event listener.
    V2TIMManager.getInstance().removeIMSDKListener(imSdkListener);
    // Deinitialize the IM SDK.
    V2TIMManager.getInstance().unInitSDK();
    Note:
    If your application's lifecycle is consistent with the SDK's lifecycle, you do not need to deinitialize before exiting the application. If you only initialize the SDK after entering a specific interface and no longer use it after exiting, you may deinitialize the SDK.
    2. Create TRTC SDK instances and set event listeners.
    // Create TRTC SDK instance (Single Instance Pattern).
    TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(context);
    // Set event listeners.
    mTRTCCloud.setListener(trtcSdkListener);
    
    // Notifications from various SDK events (e.g., error codes, warning codes, audio and video status parameters, etc.).
    private TRTCCloudListener trtcSdkListener = new TRTCCloudListener() {
    @Override
    public void onError(int errCode, String errMsg, Bundle extraInfo) {
    Log.d(TAG, errCode + errMsg);
    }
    @Override
    public void onWarning(int warningCode, String warningMsg, Bundle extraInfo) {
    Log.d(TAG, warningCode + warningMsg);
    }
    };
    
    // Remove event listener.
    mTRTCCloud.setListener(null);
    // Terminate TRTC SDK instance (Singleton Pattern).
    TRTCCloud.destroySharedInstance();
    Note:
    It is recommended to listen to SDK event notifications. Perform log printing and handling for some common errors. For details, see Error Code Table.

    Step 3: Log in and log out.

    After initializing the IM SDK, you need to call the SDK log-in API to authenticate your account identity and have permissions to use features. Before using any other features, ensure you are successfully logged in, or you might encounter feature malfunctions or unavailability. If you only need to use TRTC's audio and video services, you can skip this step.

    Sequence diagram

    
    
    
    1. Log in.
    // Log in: userID can be defined by the user and userSig can be generated as per Step 1.
    V2TIMManager.getInstance().login(userID, userSig, new V2TIMCallback() {
    @Override
    public void onSuccess() {
    Log.i("imsdk", "success");
    }
    @Override
    public void onError(int code, String desc) {
    // The following error codes mean an expired UserSig, and you need to generate a new one for log-in again.
    // 1. ERR_USER_SIG_EXPIRED(6206)
    // 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED(70001)
    // Note: Do not call the log-in API in case of other error codes. Otherwise, the IM SDK may enter an infinite loop of log-in.
    Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);
    }
    });
    2. Log out.
    // Log out.
    V2TIMManager.getInstance().logout(new V2TIMCallback() {
    @Override
    public void onSuccess() {
    Log.i("imsdk", "success");
    }
    @Override
    public void onError(int code, String desc) {
    Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);
    }
    });
    Note:
    If your application's lifecycle matches the IM SDK's lifecycle, logging out before exiting the application is not necessary. However, if you only use the IM SDK after entering a specific interface and stop using it after exiting, you can log out and deinitialize the IM SDK.

    Step 4: Room management.

    Sequence diagram

    
    
    
    1. Create a room.
    When the anchor (room owner) starts live streaming, a room needs to be created. The concept of "room" here corresponds to "group" in IM. This example only shows how to create an IM group on the client, but it is also possible to create a group on the server.
    V2TIMManager.getInstance().createGroup(V2TIMManager.GROUP_TYPE_AVCHATROOM, groupID, groupName, new V2TIMValueCallback<String>() {
    @Override
    public void onSuccess(String s) {
    // Group creation successful
    }
    
    @Override
    public void onError(int code, String desc) {
    // Group creation failed
    }
    });
    
    // Listen for group creation notifications.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupCreated(String groupID) {
    // Group creation callback. groupID is the ID of the newly created group.
    }
    });
    Note:
    For voice chat room scenarios, when creating IM groups, you need to choose the type of live streaming groups: GROUP_TYPE_AVCHATROOM.
    TRTC does not have a room-creation API, so when a user attempts to join a room that does not exist, the backend automatically creates a room.
    2. Join a room.
    Join IM group.
    V2TIMManager.getInstance().joinGroup(groupID, message, new V2TIMCallback() {
    @Override
    public void onSuccess() {
    // Successfully joined the group.
    }
    
    @Override
    public void onError(int code, String desc) {
    // Failed to join the group.
    }
    });
    
    // Listen for the event of joining a group.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onMemberEnter(String groupID, List<V2TIMGroupMemberInfo> memberList) {
    // Someone joined the group.
    }
    });
    Join a TRTC room.
    private void enterRoom(String roomId, String userId) {
    TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();
    // Using a string as the room ID for example, it is recommended to keep it consistent with the IM group ID.
    params.strRoomId = roomId;
    params.userId = userId;
    // UserSig obtained from the business backend.
    params.userSig = getUserSig(userId);
    // Replace with your SDKAppID.
    params.sdkAppId = SDKAppID;
    // For entering a room in voice chat interaction scenarios, specify the user's role.
    params.role = TRTCCloudDef.TRTCRoleAudience;
    // Use room entry in voice chat interaction scenarios as an example.
    mTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_VOICE_CHATROOM);
    }
    
    // Event callback for the result of entering the room.
    @Override
    public void onEnterRoom(long result) {
    if (result > 0) {
    // result indicates the time taken (in milliseconds) to join the room.
    Log.d(TAG, "Enter room succeed");
    } else {
    // result indicates the error code when you fail to enter the room.
    Log.d(TAG, "Enter room failed");
    }
    }
    Note:
    TRTC room IDs are divided into integer type roomId and string type strRoomId. The rooms of these two types are not interconnected. It is recommended to unify the room ID type.
    When entering a room in voice chat interaction scenarios, it is necessary to specify the user's role (anchor/audience). Only anchors have permissions to push streams. If not specified, the default role is anchor.
    For voice chat interaction room-entering scenarios, it is recommended to use TRTC_APP_SCENE_VOICE_CHATROOM.
    3. Exit the room.
    Exit the IM group.
    V2TIMManager.getInstance().quitGroup(groupID, new V2TIMCallback() {
    @Override
    public void onSuccess() {
    // Exiting the group successful.
    }
    
    @Override
    public void onError(int code, String desc) {
    // Exiting the group failed.
    }
    });
    
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onMemberLeave(String groupID, V2TIMGroupMemberInfo member) {
    // Group member leave callback.
    }
    });
    Note:
    In a live streaming group (AVChatRoom), the group owner cannot exit the group. The owner can only dissolve the group by calling dismissGroup.
    Exit the TRTC room.
    private void exitRoom() {
    mTRTCCloud.stopLocalAudio();
    mTRTCCloud.exitRoom();
    }
    
    // Event callback for exiting the room.
    @Override
    public void onExitRoom(int reason) {
    if (reason == 0) {
    Log.d(TAG, "Actively call exitRoom to exit the room.");
    } else if (reason == 1) {
    Log.d(TAG, "Removed from the current room by the server.");
    } else if (reason == 2) {
    Log.d(TAG, "The current room has been dissolved.");
    }
    }
    Note:
    After all resources occupied by the SDK are released, the SDK will throw the onExitRoom callback notification to inform you.
    If you want to call enterRoom again or switch to another audio/video SDK, wait for the onExitRoom callback before proceeding. Otherwise, you may encounter exceptions such as the camera or microphone being forcefully occupied.
    4. Dissolve the room.
    Dissolve the IM group.
    This example only shows the client method of dissolving an IM group. It can also be done through server dissolves the group.
    V2TIMManager.getInstance().dismissGroup(groupID, new V2TIMCallback() {
    @Override
    public void onSuccess() {
    // Dissolving group successful.
    }
    
    @Override
    public void onError(int code, String desc) {
    // Dissolving the group failed.
    }
    });
    
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {
    // Group dissolved callback.
    }
    });
    Dissolve TRTC room.
    Server dissolvement: TRTC provides the Server dissolves the room API DismissRoom (differentiating between numeric room ID and string room ID). You can call this API to remove all users from the room and dissolve the room.
    Client dissolvement: Through the room exit exitRoom API of each client, all the anchors and audiences in the room can be completed of room exit. After room exit, according to TRTC room lifecycle rules, the room will automatically be dissolved. For details, see Exit Room.
    Warning:
    It is recommended that after the end of live streaming, you call the room dissolvement API to ensure the room is dissolved. This will prevent audiences from accidentally entering the room and incurring unexpected charges.

    Step 5: Seat management.

    Sequence diagram

    
    
    
    First, we can create a JavaBean to save seat information.
    public class SeatInfo implements Serializable {
    public static final transient int STATUS_UNUSED = 0;
    public static final transient int STATUS_USED = 1;
    public static final transient int STATUS_LOCKED = 2;
    
    // The status of seats. There are three corresponding statuses.
    public int status;
    // Whether the seat is muted.
    public boolean mute;
    // When the seat is occupied, the user information is stored.
    public String userId;
    
    @Override
    public String toString() {
    return "TXSeatInfo{"
    + "status=" + status
    + ", mute=" + mute
    + ", user='" + userId + '\\''
    + '}';
    }
    }
    1. Become a speaker.
    Becoming a speaker refers to off-mic audience sending a request to speak to the room owner or administrator. The audience can speak once the approval signaling is received. In a free-speaking mode, the signaling request part can be skipped.
    Audience sends a request to speak.
    // Audience sends a request to speak. userId is the Anchor ID, and data can pass in a JSON identifying the signaling.
    private String sendInvitation(String userId, String data) {
    return V2TIMManager.getSignalingManager().invite(userId, data, true, null, 0, new V2TIMCallback() {
    @Override
    public void onError(int i, String s) {
    Log.e(TAG, "sendInvitation error " + i);
    }
    
    @Override
    public void onSuccess() {
    Log.i(TAG, "sendInvitation success ");
    }
    });
    }
    
    // Anchor receives the request to speak. inviteID is the request ID, and inviter is the requester ID.
    V2TIMManager.getSignalingManager().addSignalingListener(new V2TIMSignalingListener() {
    @Override
    public void onReceiveNewInvitation(String inviteID, String inviter,
    String groupId, List<String> inviteeList, String data) {
    Log.i(TAG, "received invitation: " + inviteID + " from " + inviter);
    }
    });
    Anchor processes the request to speak.
    // Agree to the request to speak.
    private void acceptInvitation(String inviteID, String data) {
    V2TIMManager.getSignalingManager().accept(inviteID, data, new V2TIMCallback() {
    @Override
    public void onError(int i, String s) {
    Log.e(TAG, "acceptInvitation error " + i);
    }
    
    @Override
    public void onSuccess() {
    Log.i(TAG, "acceptInvitation success ");
    }
    });
    }
    
    // Reject the request to speak.
    private void rejectInvitation(String inviteID, String data) {
    V2TIMManager.getSignalingManager().reject(inviteID, data, new V2TIMCallback() {
    @Override
    public void onError(int i, String s) {
    Log.e(TAG, "rejectInvitation error " + i);
    }
    
    @Override
    public void onSuccess() {
    Log.i(TAG, "rejectInvitation success ");
    }
    });
    }
    Audience to speak.
    If the anchor agrees to the audience's request to speak, the audience can add seat information by modifying group attributes. Other users will receive a callback for the change in group attributes. Update the local seat information.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    // Callback for agreeing to the request to speak.
    V2TIMManager.getSignalingManager().addSignalingListener(new V2TIMSignalingListener() {
    @Override
    public void onInviteeAccepted(String inviteID, String invitee, String data) {
    Log.i(TAG, "received accept invitation: " + inviteID + " from " + invitee);
    takeSeat(seatIndex);
    }
    });
    
    // Audience begins to speak.
    private void takeSeat(int seatIndex) {
    // Create a seat information instance. Store the modified seat information.
    SeatInfo localInfo = mSeatInfoList.get(seatIndex);
    SeatInfo seatInfo = new SeatInfo();
    seatInfo.status = SeatInfo.STATUS_USED;
    seatInfo.mute = localInfo.mute;
    seatInfo.userId = mUserId;
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    String json = gson.toJson(seatInfo, SeatInfo.class);
    HashMap<String, String> map = new HashMap<>();
    map.put("seat" + seatIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to become a speaker.
    }
    
    @Override
    public void onSuccess() {
    // Successfully modified group attributes. Switch TRTC role and start streaming.
    mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);
    mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
    }
    });
    }
    2. Invite a listener to speak.
    Anchor invites a listener to speak (without the need for audience's consent). Directly modify group attributes saved for seats, and corresponding audiences will receive a callback for the change in group attributes. After matching the userId successfully, they can switch TRTC role and start streaming. In an invite-to-speak mode, see the implementation logic of becoming a speaker. Just switch the sender and receiver of the signaling.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    // Anchor calls this API to modify the seat information saved in group attributes.
    private void pickSeat(String userId, int seatIndex) {
    // Create a seat information instance. Store the modified seat information.
    SeatInfo localInfo = mSeatInfoList.get(seatIndex);
    SeatInfo seatInfo = new SeatInfo();
    seatInfo.status = SeatInfo.STATUS_USED;
    seatInfo.mute = localInfo.mute;
    seatInfo.userId = userId;
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    String json = gson.toJson(seatInfo, SeatInfo.class);
    HashMap<String, String> map = new HashMap<>();
    map.put("seat" + seatIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to be invited to the microphone.
    }
    
    @Override
    public void onSuccess() {
    // Successfully modified group attributes and it triggers onGroupAttributeChanged callback.
    }
    });
    }
    
    // Audience receives group attribute change callback. Audience starts streaming after successfully matching own information.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {
    // Last locally saved full list of seats.
    final List<SeatInfo> oldSeatInfoList = mSeatInfoList;
    // The most recent full list of seats parsed from groupAttributeMap.
    final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);
    // Iterate through the full list of seats. Compare old and new seat information.
    for (int i = 0; i < seatSize; i++) {
    SeatInfo oldInfo = oldSeatInfoList.get(i);
    SeatInfo newInfo = newSeatInfoList.get(i);
    if (oldInfo.status != newInfo.status && newInfo.status == SeatInfo.STATUS_USED) {
    if (newInfo.userId.equals(mUserId)) {
    // Match own information successfully. Switch TRTC role and start streaming.
    mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);
    mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
    } else {
    // Update local seat list. Render local seat view.
    }
    }
    }
    }
    });
    3. Become a listener.
    Mic-connecting audiences can reset seat information by modifying group attributes. Other users will receive a group attribute change callback. Update local seat information.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    private void leaveSeat(int seatIndex) {
    // Create a seat information instance. Store the modified seat information.
    SeatInfo localInfo = mSeatInfoList.get(seatIndex);
    SeatInfo seatInfo = new SeatInfo();
    seatInfo.status = SeatInfo.STATUS_UNUSED;
    seatInfo.mute = localInfo.mute;
    seatInfo.userId = "";
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    String json = gson.toJson(seatInfo, SeatInfo.class);
    HashMap<String, String> map = new HashMap<>();
    map.put("seat" + seatIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to become a listener.
    }
    
    @Override
    public void onSuccess() {
    // Successfully modified group attributes. Switch TRTC role and stop streaming.
    mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);
    mTRTCCloud.stopLocalAudio();
    }
    });
    }
    4. Remove a speaker.
    Anchor removes a speaker. Directly modify the seat information saved in group attributes. Corresponding mic-connecting audience receives group attribute change callback. After successfully matching userId, they switch TRTC role and stop streaming.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    // Anchor calls this API to modify the seat information saved in group attributes.
    private void kickSeat(int seatIndex) {
    // Create a seat information instance. Store the modified seat information.
    SeatInfo localInfo = mSeatInfoList.get(seatIndex);
    SeatInfo seatInfo = new SeatInfo();
    seatInfo.status = SeatInfo.STATUS_UNUSED;
    seatInfo.mute = localInfo.mute;
    seatInfo.userId = "";
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    String json = gson.toJson(seatInfo, SeatInfo.class);
    HashMap<String, String> map = new HashMap<>();
    map.put("seat" + seatIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to remove the speaker.
    }
    
    @Override
    public void onSuccess() {
    // Successfully modified group attributes and it triggers onGroupAttributeChanged callback.
    }
    });
    }
    
    // Mic-connecting audience receives group attribute change callback. It stops streaming after successfully matching own information.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {
    // Last locally saved full list of seats.
    final List<SeatInfo> oldSeatInfoList = mSeatInfoList;
    // The most recent full list of seats parsed from groupAttributeMap.
    final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);
    // Iterate through the full list of seats. Compare old and new seat information.
    for (int i = 0; i < seatSize; i++) {
    SeatInfo oldInfo = oldSeatInfoList.get(i);
    SeatInfo newInfo = newSeatInfoList.get(i);
    if (oldInfo.status != newInfo.status && newInfo.status == SeatInfo.STATUS_UNUSED) {
    if (oldInfo.userId.equals(mUserId)) {
    // Match own information successfully. Switch TRTC role and stop streaming.
    mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);
    mTRTCCloud.stopLocalAudio();
    } else {
    // Update local seat list. Render local seat view.
    }
    }
    }
    }
    });
    5. Mute a seat.
    Anchor mutes/unmutes a specific seat. Directly modify the seat information saved in group attributes. Corresponding mic-connecting audience receives group attribute change callback. After successfully matching userId, they pause/resume local streaming.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    // Anchor calls this API to modify the seat information saved in group attributes.
    private void muteSeat(int seatIndex, boolean mute) {
    // Create a seat information instance. Store the modified seat information.
    SeatInfo localInfo = mSeatInfoList.get(seatIndex);
    SeatInfo seatInfo = new SeatInfo();
    seatInfo.status = localInfo.status;
    seatInfo.mute = mute;
    seatInfo.userId = localInfo.userId;
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    String json = gson.toJson(seatInfo, SeatInfo.class);
    HashMap<String, String> map = new HashMap<>();
    map.put("seat" + seatIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to mute the seat.
    }
    
    @Override
    public void onSuccess() {
    // Successfully modified group attributes and it triggers onGroupAttributeChanged callback.
    }
    });
    }
    
    // The mic-connecting audience receives the group attribute change callback. The audience pauses/resumes streaming after successfully matching own information.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {
    // Last locally saved full list of seats.
    final List<SeatInfo> oldSeatInfoList = mSeatInfoList;
    // The most recent full list of seats parsed from groupAttributeMap.
    final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);
    // Iterate through the full list of seats. Compare old and new seat information.
    for (int i = 0; i < seatSize; i++) {
    SeatInfo oldInfo = oldSeatInfoList.get(i);
    SeatInfo newInfo = newSeatInfoList.get(i);
    if (oldInfo.mute != newInfo.mute) {
    if (oldInfo.userId.equals(mUserId)) {
    // Match own information successfully. Pause/resume local streaming.
    mTRTCCloud.muteLocalAudio(newInfo.mute);
    } else {
    // Update local seat list. Render local seat view.
    }
    }
    }
    }
    });
    6. Lock a seat.
    Anchor locks/unlocks a seat by directly modifying the seat information saved in group attributes. Audience updates the corresponding seat view after receiving the group attribute change callback.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    // Anchor calls this API to modify the seat information saved in group attributes.
    private void lockSeat(int seatIndex, boolean isLock) {
    // Create a seat information instance. Store the modified seat information.
    SeatInfo localInfo = mSeatInfoList.get(seatIndex);
    SeatInfo seatInfo = new SeatInfo();
    seatInfo.status = isLock ? SeatInfo.STATUS_LOCKED : SeatInfo.STATUS_UNUSED;
    seatInfo.mute = localInfo.mute;
    seatInfo.userId = "";
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    String json = gson.toJson(seatInfo, SeatInfo.class);
    HashMap<String, String> map = new HashMap<>();
    map.put("seat" + seatIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to lock the seat.
    }
    
    @Override
    public void onSuccess() {
    // Successfully modified group attributes and it triggers onGroupAttributeChanged callback.
    }
    });
    }
    
    // The audience receives the group attribute change callback. Update the corresponding seat view.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {
    // Last locally saved full list of seats.
    final List<SeatInfo> oldSeatInfoList = mSeatInfoList;
    // The most recent full list of seats parsed from groupAttributeMap.
    final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);
    // Iterate through the full list of seats. Compare old and new seat information.
    for (int i = 0; i < seatSize; i++) {
    SeatInfo oldInfo = oldSeatInfoList.get(i);
    SeatInfo newInfo = newSeatInfoList.get(i);
    if (oldInfo.status == SeatInfo.STATUS_LOCKED && newInfo.status == SeatInfo.STATUS_UNUSED) {
    // Unlock a seat.
    } else if (oldInfo.status != newInfo.status && newInfo.status == SeatInfo.STATUS_LOCKED) {
    // Lock a seat.
    }
    }
    }
    });
    7. Move a seat.
    On-mic anchor moves a seat by necessarily and separately modifying the source and target seat information saved in group attributes. Audience updates the corresponding seat view after receiving the group attribute change callback.
    // Locally saved full list of seats.
    private List<SeatInfo> mSeatInfoList;
    
    // On-mic anchor calls this API to modify the seat information saved in group attributes.
    private void moveSeat(int dstIndex) {
    // Obtain the source seat ID by userId.
    int srcIndex = -1;
    for (int i = 0; i < mSeatInfoList.size(); i++) {
    SeatInfo seatInfo = mSeatInfoList.get(i);
    if (seatInfo != null && mUserId.equals(seatInfo.userId)) {
    srcIndex = i;
    break;
    }
    }
    // Obtain the corresponding seat information by its ID.
    SeatInfo srcSeatInfo = mSeatInfoList.get(srcIndex);
    SeatInfo dstSeatInfo = mSeatInfoList.get(dstIndex);
    // Create a seat information instance to store the modified source seat data.
    SeatInfo srcChangeInfo = new SeatInfo();
    srcChangeInfo.status = SeatInfo.STATUS_UNUSED;
    srcChangeInfo.mute = srcSeatInfo.mute;
    srcChangeInfo.userId = "";
    // Create a seat information instance to store the modified target seat data.
    SeatInfo dstChangeInfo = new SeatInfo();
    dstChangeInfo.status = SeatInfo.STATUS_USED;
    dstChangeInfo.mute = dstSeatInfo.mute;
    dstChangeInfo.userId = mUserId;
    
    // Serialize the seat information object into JSON format.
    Gson gson = new Gson();
    HashMap<String, String> map = new HashMap<>();
    String json = gson.toJson(srcChangeInfo, SeatInfo.class);
    map.put("seat" + srcIndex, json);
    json = gson.toJson(dstChangeInfo, SeatInfo.class);
    map.put("seat" + dstIndex, json);
    // Set group attributes. If the group attribute already exists, its value is updated. Otherwise, the attribute is added.
    V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {
    @Override
    public void onError(int code, String message) {
    // Failed to modify group attributes. Failed to move the seat.
    }
    
    @Override
    public void onSuccess() {
    // Modify group attributes successfully. Move the seat successfully.
    }
    });
    }

    Step 6: Audio management.

    Sequence diagram

    
    
    
    1. Subscription mode.
    By default, the TRTC SDK uses an automatic subscription mode for audio streams. When users enter the room, the system will automatically play remote users' voice. If manual subscription to audio streams is needed, calling muteRemoteAudio(userId, mute) to subscribe to and play remote users' audio streams is required.
    // Automatic subscription mode (default).
    mTRTCCloud.setDefaultStreamRecvMode(true, true);
    
    // Manual subscription mode (custom).
    mTRTCCloud.setDefaultStreamRecvMode(false, false);
    Note:
    Set the subscription mode setDefaultStreamRecvMode before entering the room enterRoom to ensure to take effect.
    2. Capture and publish.
    // Enable local audio capture and publishing.
    mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT)
    
    // Stop local audio capture and publishing.
    mTRTCCloud.stopLocalAudio();
    Note:
    startLocalAudio requests mic permissions, and stopLocalAudio releases them.
    3. Mute and unmute.
    // Pause publishing local audio streams (mute).
    mTRTCCloud.muteLocalAudio(true);
    // Resume publishing local audio streams (unmute).
    mTRTCCloud.muteLocalAudio(false);
    
    // Pause the subscription and playback of a specific remote user's audio streams.
    mTRTCCloud.muteRemoteAudio(userId, true);
    // Resume the subscription and playback of a specific remote user's audio streams.
    mTRTCCloud.muteRemoteAudio(userId, false);
    
    // Pause the subscription and playback of all remote users' audio streams.
    mTRTCCloud.muteAllRemoteAudio(true);
    // Resume the subscription and playback of all remote users' audio streams.
    mTRTCCloud.muteAllRemoteAudio(false);
    Note:
    In comparison, muteLocalAudio only requires a pause or release of the data stream at the software level, thus it is more efficient and smoother. And it is better suited for scenarios that require frequent muting and unmuting.
    4.
    Audio quality and volume type
    Audio quality setting
    // Set audio quality during local audio capture and publishing.
    mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
    
    // Dynamically set audio quality during audio streaming.
    mTRTCCloud.setAudioQuality(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
    Note:
    TRTC's preset audio quality is divided into three levels (Speech/Default/Music), each corresponding to different audio parameters. See TRTCAudioQuality for details.
    Volume type setting.
    Each TRTC audio quality level corresponds to a default volume type. If you need to forcibly specify a volume type, you can use the following API.
    // Set volume type.
    mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeAuto);
    Note:
    TRTC volume types are divided into three levels (VOIP/Auto/Media), each corresponding to different volume channels. See TRTCSystemVolumeType for details.
    Audio routing setting.
    Mobile devices such as smartphones usually have two playback locations: the speaker and the earpiece. If you need to forcibly specify the audio routing, you can use the following API.
    // Set audio routing.
    mTRTCCloud.setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
    Note:
    TRTC audio routing is divided into two types (Speaker/Earpiece), each corresponding to a different sound emission location. See TRTCAudioRoute for details.

    Advanced Features

    Bullet screen message interaction.

    Voice chat live streaming rooms usually have text-based bullet screen message interactions. This can be achieved through the sending and receiving of group chat regular text messages via IM.
    // Send public screen bullet screen messages.
    V2TIMManager.getInstance().sendGroupTextMessage(text, groupID, V2TIMMessage.V2TIM_PRIORITY_NORMAL, new V2TIMValueCallback<V2TIMMessage>() {
    @Override
    public void onError(int i, String s) {
    // Failed to send bullet screen messages.
    }
    
    @Override
    public void onSuccess(V2TIMMessage v2TIMMessage) {
    // Successfully sent bullet screen messages.
    }
    });
    
    // Receive public screen bullet screen messages.
    V2TIMManager.getInstance().addSimpleMsgListener(new V2TIMSimpleMsgListener() {
    @Override
    public void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {
    Log.i(TAG, sender.getNickName + ": " + text);
    }
    });

    Volume level callback.

    TRTC can callback the volume levels of the on-mic anchor at a fixed frequency. It is usually used to display sound waves and indicate the speaking anchor.
    // Enable volume level callback. It is recommended to be enabled immediately after successful room entry.
    // interval: Callback interval (ms). enable_vad: Whether to enable voice detection.
    mTRTCCloud.enableAudioVolumeEvaluation(int interval, boolean enable_vad);
    
    private class TRTCCloudImplListener extends TRTCCloudListener {
    public void onUserVoiceVolume(ArrayList<TRTCCloudDef.TRTCVolumeInfo> userVolumes, int totalVolume) {
    super.onUserVoiceVolume(userVolumes, totalVolume);
    // userVolumes is used to hold the volume levels of all speaking users, including both local users and remote streaming users.
    // totalVolume is used to report the maximum volume value among remote streaming users.
    ...
    // Adjust the corresponding visual representation of sound waves on the UI based on volume levels.
    ...
    }
    }
    Note:
    Voice detection only provides local voice detection results. The user's role must be an anchor to make it convenient to remind users to turn on their mics.
    userVolumes is an array. For each element in the array, when userId is oneself, it indicates the volume captured by the local microphone; when userId is others, it indicates the volume of remote users.

    Music and sound effect playback.

    Playing background music and sound effects is a high-frequency demand in voice chat room scenarios. Below, we will explain the use of and precautions for commonly used background music APIs.
    1. Start/stop/pause/resume playback.
    // Obtain the management class for configuring background music, short sound effects, and voice special effects.
    TXAudioEffectManager mTXAudioEffectManager = mTRTCCloud.getAudioEffectManager();
    
    TXAudioEffectManager.AudioMusicParam param = new TXAudioEffectManager.AudioMusicParam(musicID, musicPath);
    // Whether to publish the music to remote (otherwise play locally only).
    param.publish = true;
    // Whether the playback is from a short sound effect file.
    param.isShortFile = false;
    
    // Start background music playback.
    mTXAudioEffectManager.startPlayMusic(param);
    // Stop background music playback.
    mTXAudioEffectManager.stopPlayMusic(musicID);
    // Pause background music playback.
    mTXAudioEffectManager.pausePlayMusic(musicID);
    // Resume background music playback.
    mTXAudioEffectManager.resumePlayMusic(musicID);
    Note:
    TRTC supports playing multiple pieces of music simultaneously, each identified uniquely by a musicID. If you want to play only one piece of music at a time, be sure to stop other music before starting playback, or you can use the same musicID to play different music. In this way, the SDK will stop the old music first, and then play the new one.
    TRTC supports playing both local and online audio files, by passing in a local absolute path or URL address through musicPath. MP3/AAC/M4A/WAV formats are supported.
    2. Adjust the ratio of music and voice volume.
    // Set the local playback volume of a piece of background music.
    mTXAudioEffectManager.setMusicPlayoutVolume(musicID, volume);
    // Set the remote playback volume of a specific background music.
    mTXAudioEffectManager.setMusicPublishVolume(musicID, volume);
    // Set the local and remote volume of all background music.
    mTXAudioEffectManager.setAllMusicVolume(volume);
    // Set the volume of voice capture.
    mTXAudioEffectManager.setVoiceCaptureVolume(volume);
    Note:
    Volume value's normal range is 0-100, with a default of 60 and a maximum setting of 150, but there is a risk of audio clipping.
    If background music is overwhelming vocals, consider lowering the music playback volume and increasing the voice capture volume.
    Mute microphone without muting background music: Use setVoiceCaptureVolume(0) to replace muteLocalAudio(true).
    3. Set music playback event callback.
    mTXAudioEffectManager.setMusicObserver(mCurPlayMusicId, new TXAudioEffectManager.TXMusicPlayObserver() {
    @Override
    // Background music starts playing.
    public void onStart(int id, int errCode) {
    // -4001: Path opening failed.
    // -4002: Decoding failed.
    // -4003: Invalid URL address.
    // -4004: Playback not stopped.
    if (errCode < 0) {
    // Before replaying after playback failure, you must first stop the current music.
    mTXAudioEffectManager.stopPlayMusic(id);
    }
    }
    @Override
    // The playback progress of background music.
    public void onPlayProgress(int id, long curPtsMs, long durationMs) {
    // curPtsMS current playback duration (in milliseconds).
    // durationMs: Total duration of the current music (milliseconds).
    }
    @Override
    // Background music has finished playing.
    public void onComplete(int id, int errCode) {
    // Playback failure due to weak network during playback will also throw this callback. In this case, errCode < 0.
    // Pausing or stopping playback midway will not trigger the onComplete callback.
    }
    });
    Note:
    Use this API to set the playback event callback before playing background music to monitor the progress of the music;
    If the MusicId does not need to be reused, you can execute setMusicObserver(musicId, null) after playback is finished to completely release the Observer.
    4. Loop playback of background music and sound effects.
    Solution 1: Use the AudioMusicParam's loopCount parameter to set the number of loop playbacks.
    The value range is from 0 to any positive integer. The default value is 0. 0 means play the music once; 1 means play the music twice; and so on.
    private void startPlayMusic(int id, String path, int loopCount) {
    TXAudioEffectManager.AudioMusicParam param = new TXAudioEffectManager.AudioMusicParam(id, path);
    // Whether to publish music to the remote.
    param.publish = true;
    // Whether the playback is from a short sound effect file.
    param.isShortFile = true;
    // Set the number of loop playbacks. Negative number means an infinite loop.
    param.loopCount = loopCount < 0 ? Integer.MAX_VALUE : loopCount;
    mTRTCCloud.getAudioEffectManager().startPlayMusic(param);
    }
    Note:
    Solution 1 will not trigger the onComplete callback after each loop playback. It will only be triggered after all the set loop counts have been played.
    Solution 2: Implement loop playback through the "Background music has finished playing" event callback onComplete. It is usually used for list loop or single track loop.
    // The member variable used for indicating whether to loop playback.
    private boolean loopPlay;
    
    private void startPlayMusic(int id, String path) {
    TXAudioEffectManager.AudioMusicParam param = new TXAudioEffectManager.AudioMusicParam(id, path);
    mTXAudioEffectManager.setMusicObserver(id, new MusicPlayObserver(id, path));
    mTXAudioEffectManager.startPlayMusic(param);
    }
    
    private class MusicPlayObserver implements TXAudioEffectManager.TXMusicPlayObserver {
    private final int mId;
    private final String mPath;
    public MusicPlayObserver(int id, String path) {
    mId = id;
    mPath = path;
    }
    
    @Override
    public void onStart(int i, int i1) {
    
    }
    
    @Override
    public void onPlayProgress(int i, long l, long l1) {
    
    }
    
    @Override
    public void onComplete(int i, int i1) {
    mTXAudioEffectManager.stopPlayMusic(i);
    if (i1 >= 0 && loopPlay) {
    // Here, you can replace the ID or Path of the music in the loop playlist.
    startPlayMusic(mId, mPath);
    }
    }
    }

    Mixed stream relay and push back.

    1. Live streaming CDN with mixed stream relay.
    private void startPublishMediaToCDN(String streamName) {
    // Expiration time of the streaming URL. By default, it is one day.
    long txTime = (System.currentTimeMillis() / 1000) + (24 * 60 * 60);
    // LIVE_URL_KEY authentication key. Obtain from the streaming URL configuration page in the console.
    String secretParam = UrlHelper.getSafeUrl(LIVE_URL_KEY, streamName, txTime);
    
    // The target URLs for media stream publication.
    TRTCCloudDef.TRTCPublishTarget target = new TRTCCloudDef.TRTCPublishTarget();
    // Publish to CDN after mixing.
    target.mode = TRTCCloudDef.TRTC_PublishMixStream_ToCdn;
    TRTCCloudDef.TRTCPublishCdnUrl cdnUrl = new TRTCCloudDef.TRTCPublishCdnUrl();
    // Streaming URL must include parameters. Otherwise, streaming fails.
    cdnUrl.rtmpUrl = "rtmp://" + PUSH_DOMAIN + "/live/" + streamName + "?" + secretParam;
    // True means Tencent CSS push URLs, and false means third-party services.
    cdnUrl.isInternalLine = true;
    // Multiple CDN push URLs can be added.
    target.cdnUrlList.add(cdnUrl);
    
    // Set the encoding parameters of the transcoded audio stream (can be customized).
    TRTCCloudDef.TRTCStreamEncoderParam trtcStreamEncoderParam = new TRTCCloudDef.TRTCStreamEncoderParam();
    trtcStreamEncoderParam.audioEncodedChannelNum = 1;
    trtcStreamEncoderParam.audioEncodedKbps = 50;
    trtcStreamEncoderParam.audioEncodedCodecType = 0;
    trtcStreamEncoderParam.audioEncodedSampleRate = 48000;
    
    // Set the encoding parameters of the transcoded video stream (must be filled in if mixing black frames, and can be ignored for pure audio mix stream).
    trtcStreamEncoderParam.videoEncodedFPS = 15;
    trtcStreamEncoderParam.videoEncodedGOP = 3;
    trtcStreamEncoderParam.videoEncodedKbps = 30;
    trtcStreamEncoderParam.videoEncodedWidth = 64;
    trtcStreamEncoderParam.videoEncodedHeight = 64;
    
    // Configuration parameters for media stream transcoding.
    TRTCCloudDef.TRTCStreamMixingConfig trtcStreamMixingConfig = new TRTCCloudDef.TRTCStreamMixingConfig();
    // By default, leave this field empty. It indicates that all audio in the room will be mixed.
    trtcStreamMixingConfig.audioMixUserList = null;
    
    // Must have TRTCVideoLayout parameters if mixing black frames (can be ignored for pure audio mix stream).
    TRTCCloudDef.TRTCVideoLayout videoLayout = new TRTCCloudDef.TRTCVideoLayout();
    trtcStreamMixingConfig.videoLayoutList.add(videoLayout);
    
    // Start mixing and relaying mixed streams.
    mTRTCCloud.startPublishMediaStream(target, trtcStreamEncoderParam, trtcStreamMixingConfig);
    }
    2. Push the mixed stream back to the TRTC room.
    private void startPublishMediaToRoom(String roomId, String userId) {
    // Create TRTCPublishTarget object.
    TRTCCloudDef.TRTCPublishTarget target = new TRTCCloudDef.TRTCPublishTarget();
    // After mixing, the stream is relayed back to the room.
    target.mode = TRTCCloudDef.TRTC_PublishMixStream_ToRoom;
    target.mixStreamIdentity.strRoomId = roomId;
    // Mixed stream robot's userid, must not duplicate with other users' userid in the room.
    target.mixStreamIdentity.userId = userId + MIX_ROBOT;
    
    // Set the encoding parameters of the transcoded audio stream (can be customized).
    TRTCCloudDef.TRTCStreamEncoderParam trtcStreamEncoderParam = new TRTCCloudDef.TRTCStreamEncoderParam();
    trtcStreamEncoderParam.audioEncodedChannelNum = 2;
    trtcStreamEncoderParam.audioEncodedKbps = 64;
    trtcStreamEncoderParam.audioEncodedCodecType = 2;
    trtcStreamEncoderParam.audioEncodedSampleRate = 48000;
    
    // Set the encoding parameters of the transcoded video stream (can be ignored for pure audio mix stream).
    trtcStreamEncoderParam.videoEncodedFPS = 15;
    trtcStreamEncoderParam.videoEncodedGOP = 3;
    trtcStreamEncoderParam.videoEncodedKbps = 30;
    trtcStreamEncoderParam.videoEncodedWidth = 64;
    trtcStreamEncoderParam.videoEncodedHeight = 64;
    
    // Set audio mixing parameters.
    TRTCCloudDef.TRTCStreamMixingConfig trtcStreamMixingConfig = new TRTCCloudDef.TRTCStreamMixingConfig();
    // By default, leave this field empty. It indicates that all audio in the room will be mixed.
    trtcStreamMixingConfig.audioMixUserList = null;
    
    // Configure video mixing template (can be ignored for pure audio mix stream).
    TRTCCloudDef.TRTCVideoLayout videoLayout = new TRTCCloudDef.TRTCVideoLayout();
    trtcStreamMixingConfig.videoLayoutList.add(videoLayout);
    
    // Start mixing and pushing back.
    mTRTCCloud.startPublishMediaStream(target, trtcStreamEncoderParam, trtcStreamMixingConfig);
    }
    3. Event callback and update stop task.
    Task result event callback.
    private class TRTCCloudImplListener extends TRTCCloudListener {
    @Override
    public void onStartPublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {
    // taskId: When the request is successful, TRTC backend will provide the taskId of this task in the callback. You can later use this taskId with updatePublishMediaStream and stopPublishMediaStream to update and stop.
    // code: Callback result. 0 means success and other values mean failure.
    }
    
    @Override
    public void onUpdatePublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {
    // When you call the publish media stream API (updatePublishMediaStream), the taskId you provide will be returned to you through this callback. It is used to identify which update request the callback belongs to.
    // code: Callback result. 0 means success and other values mean failure.
    }
    
    @Override
    public void onStopPublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {
    // When you call the stop publishing media stream API (stopPublishMediaStream), the taskId you provide will be returned to you through this callback. It is used to identify which stop request the callback belongs to.
    // code: Callback result. 0 means success and other values mean failure.
    }
    }
    Update the published media stream.
    This API sends a command to the TRTC server to update the media stream initiated by startPublishMediaStream.
    // taskId: Task ID returned by the onStartPublishMediaStream callback.
    // target: For example, add or remove the published CDN URLs.
    // params: It is recommended to maintain consistency in the encoding output parameters for the media stream to avoid interruptions during playback.
    // config: Update the list of users involved in mix stream transcoding, such as cross-room competition.
    mTRTCCloud.updatePublishMediaStream(taskId, target, trtcStreamEncoderParam, trtcStreamMixingConfig);
    Note:
    Switching between audio only, audio and video, and video only is not supported within the same task.
    Stop publishing media stream.
    This API sends a command to the TRTC server to stop the media stream initiated by startPublishMediaStream.
    // taskId: Task ID returned by the onStartPublishMediaStream callback.
    mTRTCCloud.stopPublishMediaStream(taskId);
    Note:
    If taskId is filled with an empty string, it will stop all media streams initiated by the user through startPublishMediaStream. If you have only initiated one media stream or want to stop all media streams initiated by you, this method is recommended.

    Real-time network quality callback

    You can listen to onNetworkQuality to real-time monitor the network quality of both local and remote users. This callback is thrown every 2 seconds.
    private class TRTCCloudImplListener extends TRTCCloudListener {
    @Override
    public void onNetworkQuality(TRTCCloudDef.TRTCQuality localQuality,
    ArrayList<TRTCCloudDef.TRTCQuality> remoteQuality) {
    // localQuality userId is empty. It represents the local user's network quality evaluation result.
    // remoteQuality represents the remote user's network quality evaluation result. The result is affected by both remote and local factors.
    switch (localQuality.quality) {
    case TRTCCloudDef.TRTC_QUALITY_Excellent:
    Log.i(TAG, "The current network is excellent.");
    break;
    case TRTCCloudDef.TRTC_QUALITY_Good:
    Log.i(TAG, "The current network is good.");
    break;
    case TRTCCloudDef.TRTC_QUALITY_Poor:
    Log.i(TAG, "The current network is moderate.");
    break;
    case TRTCCloudDef.TRTC_QUALITY_Bad:
    Log.i(TAG, "The current network is poor.");
    break;
    case TRTCCloudDef.TRTC_QUALITY_Vbad:
    Log.i(TAG, "The current network is very poor.");
    break;
    case TRTCCloudDef.TRTC_QUALITY_Down:
    Log.i(TAG, "The current network does not meet the minimum requirements of TRTC.");
    break;
    default:
    Log.i(TAG, "Undefined");
    break;
    }
    }
    }

    Advanced permission control

    TRTC advanced permission control can be used to set different entry permissions for different rooms, such as advanced VIP rooms. It can also be used to control the permission for the audience to speak, such as handling ghost microphones.
    Step 1: Enable the Advanced Permission Control Switch in the TRTC console application's feature configuration page.
    Step 2: Generate privateMapKey on the backend. For sample code, see privateMapKey computation source code.
    Step 3: Room entry verification & speaking permission verification with PrivateMapKey.
    Room entry verification
    TRTCCloudDef.TRTCParams mTRTCParams = new TRTCCloudDef.TRTCParams();
    mTRTCParams.sdkAppId = SDKAPPID;
    mTRTCParams.userId = mUserId;
    mTRTCParams.strRoomId = mRoomId;
    // UserSig obtained from the business backend.
    mTRTCParams.userSig = getUserSig();
    // PrivateMapKey obtained from the backend.
    mTRTCParams.privateMapKey = getPrivateMapKey();
    mTRTCParams.role = TRTCCloudDef.TRTCRoleAudience;
    mTRTCCloud.enterRoom(mTRTCParams, TRTCCloudDef.TRTC_APP_SCENE_VOICE_CHATROOM);
    Speaking permission verification
    // Pass in the latest PrivateMapKey obtained from the backend into the role switching API.
    mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor, getPrivateMapKey());

    Exception Handling

    Exception error handling

    When the TRTC SDK encounters an unrecoverable error, the error will be thrown in the onError callback. For details, see Error Code Table.
    UserSig related
    UserSig verification failure will lead to room-entering failure. You can use the UserSig tool for verification.
    Enumeration
    Value
    Description
    ERR_TRTC_INVALID_USER_SIG
    -3320
    Room entry parameter userSig is incorrect. Check if TRTCParams.userSig is empty.
    ERR_TRTC_USER_SIG_CHECK_FAILED
    -100018
    UserSig verification failed. Check if the parameter TRTCParams.userSig is filled in correctly or has expired.
    Room entry and exit related
    If failed to enter the room, you should first verify the correctness of the room entry parameters. It is essential that the room entry and exit APIs are called in a paired manner. This means that, even in the event of a failed room entry, the room exit API must still be called.
    Enumeration
    Value
    Description
    ERR_TRTC_CONNECT_SERVER_TIMEOUT
    -3308
    Room entry request timed out. Check if your internet connection is lost or if a VPN is enabled. You may also attempt to switch to 4G for testing.
    ERR_TRTC_INVALID_SDK_APPID
    -3317
    Room entry parameter sdkAppId is incorrect. Check if TRTCParams.sdkAppId is empty.
    ERR_TRTC_INVALID_ROOM_ID
    -3318
    Room entry parameter roomId is incorrect. Check if TRTCParams.roomId or TRTCParams.strRoomId is empty. Note that roomId and strRoomId cannot be used interchangeably.
    ERR_TRTC_INVALID_USER_ID
    -3319
    Room entry parameter userId is incorrect. Check if TRTCParams.userId is empty.
    ERR_TRTC_ENTER_ROOM_REFUSED
    -3340
    Room entry request is denied. Check if enterRoom is called consecutively to enter rooms with the same ID.
    Device related
    Errors for relevant monitoring devices. Prompt the user via UI in case of relevant errors.
    Enumeration
    Value
    Description
    ERR_MIC_START_FAIL
    -1302
    Failed to open the mic. For example, if there is an exception for the mic's configuration program (driver) on a Windows or macOS device, you should try disabling then re-enabling the device, restarting the machine, or updating the configuration program.
    ERR_SPEAKER_START_FAIL
    -1321
    Failed to open the speaker. For example, if there is an exception for the speaker's configuration program (driver) on a Windows or macOS device, you should try disabling then re-enabling the device, restarting the machine, or updating the configuration program.
    ERR_MIC_OCCUPY
    -1319
    The mic is occupied. This occurs when, for example, the user is currently having a call on the mobile device.

    Exception exit handling.

    1. Network disconnection detection and timeout room exit.
    You can listen for TRTC disconnection and reconnection events through the following callback notifications.
    Upon receiving the onConnectionLost callback, display a network disconnection icon on the local seat UI to notify the user. Simultaneously, initiate a local timer. If the onConnectionRecovery callback is not received after exceeding the set time threshold, it means the network remains disconnected. Then, locally initiate leaving the seatmic and room exit process. Pop up a window to inform the user that they have exited the room and the page will be closed. If the disconnection exceeds 90 seconds (default), a timeout room-exit will be triggered, and the TRTC server will remove the user from the room. If the user has an anchor role, other users in the room will receive the onRemoteUserLeaveRoom callback.
    private class TRTCCloudImplListener extends TRTCCloudListener {
    @Override
    public void onConnectionLost() {
    // The connection between the SDK and the cloud has been disconnected.
    }
    
    @Override
    public void onTryToReconnect() {
    // The SDK is attempting to reconnect to the cloud.
    }
    
    @Override
    public void onConnectionRecovery() {
    // The connection between the SDK and the cloud has been restored.
    }
    }
    2. Automatically remove an offline-status user.
    The regular statuses of IM users include online (ONLINE), offline (OFFLINE), and not logged in (UNLOGINED). The offline status typically results from the user force-stopping the process or experiencing an abnormal network disruption. You may use the feature of anchors subscribing to the connection status of mic-connecting audiences to detect offline mic-connecting audiences. And then you may remove them.
    // Anchor subscribes to the connection status of mic-connecting audiences.
    V2TIMManager.getInstance().subscribeUserStatus(userList, new V2TIMCallback() {
    @Override
    public void onSuccess() {
    // Subscription of user status succeeded.
    }
    
    @Override
    public void onError(int code, String message) {
    // Subscription of user status failed.
    }
    });
    
    // Anchor unsubscribes from the connection status of audiences leaving the seat.
    V2TIMManager.getInstance().unsubscribeUserStatus(userList, new V2TIMCallback() {
    @Override
    public void onSuccess() {
    // Unsubscription of user status succeeded.
    }
    
    @Override
    public void onError(int code, String message) {
    // Failed to unsubscription of user status.
    }
    });
    
    // User status change notification and processing.
    V2TIMManager.getInstance().addIMSDKListener(new V2TIMSDKListener() {
    @Override
    public void onUserStatusChanged(List<V2TIMUserStatus> userStatusList) {
    for (V2TIMUserStatus userStatus : userStatusList) {
    final String userId = userStatus.getUserID();
    int status = userStatus.getStatusType();
    if (status == V2TIMUserStatus.V2TIM_USER_STATUS_OFFLINE) {
    // Remove an offline-status user.
    kickSeat(getSeatIndexFromUserId(userId));
    }
    }
    }
    });
    
    
    
    Note:
    User-status subscription needs to be upgraded to the advanced package. For details, see Basic Service Details.
    User-status subscription needs User status query and status change notification configuration to be enabled in Instant Messaging (IM) console in advance. Failure to enable will result in an error when calling subscribeUserStatus.

    Server removes users from and dissolve the room.

    1. Server removes users.
    First, call the TRTC server user-removing API RemoveUser (for integer room IDs) or RemoveUserByStrRoomId (for string room IDs) to remove the target user from the TRTC room. The input example is as follows:
    https://trtc.tencentcloudapi.com/?Action=RemoveUser
    &SdkAppId=1400000001
    &RoomId=1234
    &UserIds.0=test1
    &UserIds.1=test2
    &<Common request parameters>
    After removing the user successfully, the target user will receive the onExitRoom() callback on the client, with the reason value being 1. At this moment, you can handle leaving the seat and exiting the IM group in this callback.
    // Exit TRTC room event callback.
    @Override
    public void onExitRoom(int reason) {
    if (reason == 0) {
    Log.d(TAG, "Actively call exitRoom to exit the room.");
    } else {
    // reason 1: Removed from the current room by the server.
    // reason 2: The current room is dissolved.
    Log.d(TAG, "Removed from the room by the server, or the current room has been dissolved.");
    // Leave the seat.
    leaveSeat(seatIndex);
    // Exit IM group.
    quitGroup(groupID, new V2TIMCallback() {});
    }
    }
    2. Server dissolves the room.
    First, call the IM server group dissolvement API destroy_group to dissolve the target group. The example request URL is as follows:
    https://xxxxxx/v4/group_open_http_svc/destroy_group?sdkappid=88888888&identifier=admin&usersig=xxx&random=99999999&contenttype=json
    After the group is dissolved, all members within the target group will receive the onGroupDismissed() callback on clients. At this point, you can handle operations such as exiting the TRTC room in this callback.
    // Group dissolved callback.
    V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
    @Override
    public void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {
    // Exit TRTC room.
    mTRTCCloud.stopLocalAudio();
    mTRTCCloud.exitRoom();
    }
    });
    Note:
    When all users in the room have completed exiting by calling exitRoom(), the TRTC room will be automatically dissolved. Of course, you can also mandatorily dissolve the TRTC room by calling the server API DismissRoom (for integer room IDs) or DismissRoomByStrRoomId (for string room IDs).

    View live streaming room's historical messages upon room entry.

    By default, using AVChatRoom does not store live streaming room's historical messages. Therefore, when new users enter the live streaming room, they can only see messages sent after their entry. To optimize the experience for new users joining the group, you can configure the number of messages new live streaming group users can pull before joining the group in the console, as shown in the figure:
    
    
    
    Pulling historical messages before joining the live streaming group for live group users is the same as pulling historical messages for other groups, as shown in the sample code:
    V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();
    option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // Pull earlier existing messages from the cloud.
    option.setGetTimeBegin(1640966400); // Starting from midnight January 1, 2022.
    option.setGetTimePeriod(1 * 24 * 60 * 60); // Pull messages from a 24-hour period.
    option.setCount(Integer.MAX_VALUE); // Return all messages within the time range.
    option.setGroupID(#your group id#); // Pull messages for the group chat.
    V2TIMManager.getMessageManager().getHistoryMessageList(option, new V2TIMValueCallback<List<V2TIMMessage>>() {
    @Override
    public void onSuccess(List<V2TIMMessage> v2TIMMessages) {
    Log.i("imsdk", "success");
    }
    
    @Override
    public void onError(int code, String desc) {
    Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);
    }
    });
    Note:
    This feature is only available to advanced users. It only supports pulling up to 20 historical messages within 24 hours from the group.

    Enter the room to sense the mute status of on-mic anchors.

    Solution 1: Default all anchors to mute status upon room entry, then unmute the corresponding anchors based on the onUserAudioAvailable(userId, true) callback.
    private class TRTCCloudImplListener extends TRTCCloudListener {
    @Override
    public void onUserAudioAvailable(String userId, boolean available) {
    if (available) {
    // Unmute the corresponding anchors.
    }
    }
    }
    Solution 2: Store the mute status of the anchors in the IM group attributes. Audiences entering the room obtain all group attributes to parse the mute status of on-mic anchors.
    V2TIMManager.getGroupManager().getGroupAttributes(groupID, null, new V2TIMValueCallback<Map<String, String>>() {
    @Override
    public void onError(int i, String s) {
    // Failed to obtain the group attributes.
    }
    
    @Override
    public void onSuccess(Map<String, String> attrMap) {
    // Successfully obtained group attributes. It is assumed that the key used to store the mute status of the anchors is muteStatus.
    String muteStatus = attrMap.get("muteStatus");
    // Parse muteStatus, and obtain the mute status of each on-mic anchor.
    }
    });

    Issues with audio input and output of Bluetooth headphones.

    The mobile phone has successfully connected to the Bluetooth headphones, but the audio input or output of the TRTC application still uses the mobile phone's microphone or speaker.
    1. If the audio output is working fine with the Bluetooth headphones, but only the audio input is still using the mobile phone's microphone, check the settings for the audio volume type. Only under the call volume mode does it support capturing audio through the microphone on the Bluetooth headphones. For details, see Audio Management - Audio Quality and Volume Type.
    mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeVOIP);
    2. If both audio input and output fail to use the Bluetooth headphones, check if the app has been granted Bluetooth permissions. Devices running on systems below Android 12 require at least the BLUETOOTH permission. Devices running on Android 12 and above require at least the BLUETOOTH_CONNECT permission and also the permissions to be requested dynamically in the code.
    To configure Bluetooth permissions in the AndroidManifest.xml for compatibility with systems below Android 12, it is recommended to declare permissions as follows:
    <!--Normal Permission: basic Bluetooth connection permissions-->
    <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
    <!--Normal Permission: Bluetooth management and scan permissions-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
    
    <!--Runtime Permission: Android 12 Bluetooth permissions for discovering Bluetooth devices-->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <!--Runtime Permission: Android 12 Bluetooth permissions for making the current device discoverable by other Bluetooth devices-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    <!--Runtime Permission: Android 12 Bluetooth permissions for communicating with paired Bluetooth devices or checking if Bluetooth is enabled on the current mobile phone-->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    For Android 12 and above systems, the method to dynamically request the newly added fine-grained Bluetooth permissions, is as follows:
    private List<String> permissionList = new ArrayList<>();
    
    protected void initPermission() {
    // Check if the Android SDK version is Android 12 and later.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    // Add the required permissions to be requested dynamically according to the needs.
    permissionList.add(Manifest.permission.BLUETOOTH_SCAN);
    permissionList.add(Manifest.permission.BLUETOOTH_ADVERTISE);
    permissionList.add(Manifest.permission.BLUETOOTH_CONNECT);
    }
    if (permissionList.size() != 0) {
    // Dynamically request permissions.
    ActivityCompat.requestPermissions(this, permissionList.toArray(new String[0]), REQ_PERMISSION_CODE);
    }
    }

    Issues with supported resource paths for playing music.

    When using the TRTC SDK API startPlayMusic to play background music, the music resource path parameter path does not support for file paths from directories such as assets or raw used in Android development for storing application resources. This is because files in these directories are bundled into the APK and are not extracted to the device's file system after installation. Currently, only absolute paths to network resources URLs, external storage on Android devices, and files in the application's private directory are supported.
    You can work around this issue by copying resource files from the assets directory to either the device external storage or the application private directory beforehand. Sample code is as follows:
    public static void copyAssetsToFile(Context context, String name) {
    // The files directory under the application's directory.
    String savePath = ContextCompat.getExternalFilesDirs(context, null)[0].getAbsolutePath();
    // The cache directory under the application's directory.
    // String savePath = getApplication().getExternalCacheDir().getAbsolutePath();
    // The files directory under the application's private storage directory.
    // String savePath = getApplication().getFilesDir().getAbsolutePath();
    String filename = savePath + "/" + name;
    File dir = new File(savePath);
    // Create the directory if it does not exist.
    if (!dir.exists()) {
    dir.mkdir();
    }
    try {
    if (!(new File(filename)).exists()) {
    InputStream is = context.getResources().getAssets().open(name);
    FileOutputStream fos = new FileOutputStream(filename);
    byte[] buffer = new byte[1024];
    int count = 0;
    while ((count = is.read(buffer)) > 0) {
    fos.write(buffer, 0, count);
    }
    fos.close();
    is.close();
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    Application private storage files directory path:/data/user/0/<package_name>/files/<file_name>.
    Application external storage files directory path:/storage/emulated/0/Android/data/<package_name>/files/<file_name>.
    Application external storage cache directory path:/storage/emulated/0/Android/data/<package_name>/cache/<file_name>.
    Note:
    If you provide a path that is an external storage path outside of the application's specific directories, on Android 10 and above devices, you may face denial of access to the resource. This is due to Google introducing Partition Storage, a new storage management system. You can temporarily bypass this by adding the following code inside the <application> tag in the AndroidManifest.xml file: android:requestLegacyExternalStorage="true". This attribute only takes effect on applications with targetSdkVersion 29 (Android 10), and applications with a higher version targetSdkVersion are still recommended to use the application's private or external storage paths.
    For TRTC SDK v11.5 and later, playback of local music resources on Android devices via Content URI from Content Provider components is supported.
    On Android 11 and HarmonyOS 3.0 or later, if you cannot access resource files in the external storage directory, you need to request the MANAGE_EXTERNAL_STORAGE permission:
    First, you need to add the following entry in your application's AndroidManifest file.
    <manifest ...>
    <!-- This is the permission itself -->
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    
    <application ...>
    ...
    </application>
    </manifest>
    Then, guide users to manually grant this permission at the point in your application where it is needed.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    if (!Environment.isExternalStorageManager()) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
    Uri uri = Uri.fromParts("package", getPackageName(), null);
    intent.setData(uri);
    startActivity(intent);
    }
    } else {
    // For Android versions less than Android 11, you can use the old permissions model
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
    }
    
    Contact Us

    Contact our sales team or business advisors to help your business.

    Technical Support

    Open a ticket if you're looking for further assistance. Our Ticket is 7x24 avaliable.

    7x24 Phone Support