




dependencies {// TRTC Lite Edition SDK, including 2 features, TRTC and live streaming playback.implementation 'com.tencent.liteav:LiteAVSDK_TRTC:latest.release'// Add the Chat SDK. It's recommended to enter the latest version number.implementation 'com.tencent.imsdk:imsdk-plus:Version number'// If you need to add the Quic plugin, uncomment the next line (Note: the plugin version number should be identical to the Chat SDK version number).// implementation 'com.tencent.imsdk:timquic-plugin:Version number'}
defaultConfig {ndk {abiFilters "armeabi-v7a", "arm64-v8a"}}
<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" />
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.-keep class com.tencent.** { *; }

// Add event listener.V2TIMManager.getInstance().addIMSDKListener(imSdkListener);// Initialize the Chat SDK. After calling this API, you can immediately call the login 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() {@Overridepublic void onConnecting() {Log.d(TAG,"Chat SDK is connecting to Tencent Cloud Virtual Machine (CVM)");}@Overridepublic void onConnectSuccess() {Log.d(TAG, "Chat SDK has successfully connected to Tencent CVM");}};// Remove event listener.V2TIMManager.getInstance().removeIMSDKListener(imSdkListener);// Deinitialize the Chat SDK.V2TIMManager.getInstance().unInitSDK();
// Create an RTC Engine SDK instance (singleton mode)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() {@Overridepublic void onError(int errCode, String errMsg, Bundle extraInfo) {Log.d(TAG, errCode + errMsg);}@Overridepublic void onWarning(int warningCode, String warningMsg, Bundle extraInfo) {Log.d(TAG, warningCode + warningMsg);}};// Remove event listener.mTRTCCloud.setListener(null);// Terminate the RTC Engine SDK instance (singleton mode)TRTCCloud.destroySharedInstance();
// 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() {@Overridepublic void onSuccess() {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {// If the following error codes are returned, it means that an expired UserSig is in use. You need to generate a new one for login again.// 1. ERR_USER_SIG_EXPIRED (6206)// 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED (70001)// Note: For other error codes, do not call the login API here to avoid the Chat SDK entering an infinite loop.Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
// Log out.V2TIMManager.getInstance().logout(new V2TIMCallback() {@Overridepublic void onSuccess() {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
V2TIMManager.getInstance().createGroup(V2TIMManager.GROUP_TYPE_AVCHATROOM, groupID, groupName, new V2TIMValueCallback<String>() {@Overridepublic void onSuccess(String s) {// Group created successfully.}@Overridepublic void onError(int code, String desc) {// Group creation failed.}});// Listen for group creation notifications.V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupCreated(String groupID) {// Group creation callback. GroupID is the ID of the newly created group.}});
GROUP_TYPE_AVCHATROOM.V2TIMManager.getInstance().joinGroup(groupID, message, new V2TIMCallback() {@Overridepublic void onSuccess() {// Successfully joined the group.}@Overridepublic void onError(int code, String desc) {// Failed to join the group.}});// Listen for the event of joining a group.V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onMemberEnter(String groupID, List<V2TIMGroupMemberInfo> memberList) {// Someone joined the group.}});
private void enterRoom(String roomId, String userId) {TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();// Using a string-type room ID for example, it is recommended to keep it consistent with the Chat 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 room entry 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.@Overridepublic 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 in the case of room entry failure.Log.d(TAG, "Enter room failed");}}
roomId and string type strRoomId. Rooms of different types are not interconnected. It is advisable to unify the room ID type.TRTC_APP_SCENE_VOICE_CHATROOM.V2TIMManager.getInstance().quitGroup(groupID, new V2TIMCallback() {@Overridepublic void onSuccess() {// Successfully exited the group.}@Overridepublic void onError(int code, String desc) {// Failed to exit the group.}});V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onMemberLeave(String groupID, V2TIMGroupMemberInfo member) {// Group member leaving callback.}});
dismissGroup.private void exitRoom() {mTRTCCloud.stopLocalAudio();mTRTCCloud.exitRoom();}// Event callback for exiting the room.@Overridepublic 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.");}}
onExitRoom callback notification to inform you.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.V2TIMManager.getInstance().dismissGroup(groupID, new V2TIMCallback() {@Overridepublic void onSuccess() {// Group dissolved successfully.}@Overridepublic void onError(int code, String desc) {// Failed to dissolve the group.}});V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {// Group dissolving callback.}});
DismissRoom (distinguish between numeric room ID and string room ID). You can call this API to remove all users from the room and dissolve the room.exitRoom of each client. After room exit, the room will be automatically dissolved according to RTC Engine room lifecycle rules. For details, see Exit the Room.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. Three corresponding statuses are available.public int status;// Whether the seat is muted.public boolean mute;// When the seat is occupied, the user information is stored.public String userId;@Overridepublic String toString() {return "TXSeatInfo{"+ "status=" + status+ ", mute=" + mute+ ", user='" + userId + '\\''+ '}';}}
// 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() {@Overridepublic void onError(int i, String s) {Log.e(TAG, "sendInvitation error " + i);}@Overridepublic 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() {@Overridepublic void onReceiveNewInvitation(String inviteID, String inviter,String groupId, List<String> inviteeList, String data) {Log.i(TAG, "received invitation: " + inviteID + " from " + inviter);}});
// Agree to the request to speak.private void acceptInvitation(String inviteID, String data) {V2TIMManager.getSignalingManager().accept(inviteID, data, new V2TIMCallback() {@Overridepublic void onError(int i, String s) {Log.e(TAG, "acceptInvitation error " + i);}@Overridepublic 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() {@Overridepublic void onError(int i, String s) {Log.e(TAG, "rejectInvitation error " + i);}@Overridepublic void onSuccess() {Log.i(TAG, "rejectInvitation success ");}});}
// Locally saved full list of seats.private List<SeatInfo> mSeatInfoList;// Callback for agreeing to the request to speak.V2TIMManager.getSignalingManager().addSignalingListener(new V2TIMSignalingListener() {@Overridepublic 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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to become a speaker.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Switch the TRTC role and start streaming.mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);}});}
// 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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to invite a listener to speak.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Trigger onGroupAttributeChanged callback.}});}// Audience receives group attribute change callback. Audience starts streaming after successfully matching own information.V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// Last locally saved full seat information list.final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// The most recent full seat information list parsed in groupAttributeMap.final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// Iterate through the full seat information list. 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)) {// Personal user information matched successfully. Switch the TRTC role and start streaming.mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);} else {// Update the local seat list. Render local seat view.}}}}});
// 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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to leave the seat.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Switch the TRTC role and stop streaming.mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);mTRTCCloud.stopLocalAudio();}});}
// 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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to become a speaker.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Trigger onGroupAttributeChanged callback.}});}// Mic-connecting audience receives group attribute change callback. It stops streaming after successfully matching own information.V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// Last locally saved full seat information list.final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// The most recent full seat information list parsed in groupAttributeMap.final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// Iterate through the full seat information list. 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)) {// Its own information matched successfully. Switch the TRTC role and stop streaming.mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);mTRTCCloud.stopLocalAudio();} else {// Update the local seat list. Render local seat view.}}}}});
// 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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to mute seats.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Trigger 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() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// Last locally saved full seat information list.final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// The most recent full seat information list parsed in groupAttributeMap.final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// Iterate through the full seat information list. 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)) {// Its own information matched successfully. Pause/ restore local streaming.mTRTCCloud.muteLocalAudio(newInfo.mute);} else {// Update the local seat list. Render local seat view.}}}}});
// 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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to lock seats.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Trigger onGroupAttributeChanged callback.}});}// The audience receives the group attribute change callback. Update the corresponding seat view.V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// Last locally saved full seat information list.final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// The most recent full seat information list parsed in groupAttributeMap.final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// Iterate through the full seat information list. 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.}}}});
// 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 the seat ID.SeatInfo srcSeatInfo = mSeatInfoList.get(srcIndex);SeatInfo dstSeatInfo = mSeatInfoList.get(dstIndex);// Create a seat information instance. Store the modified source seat information.SeatInfo srcChangeInfo = new SeatInfo();srcChangeInfo.status = SeatInfo.STATUS_UNUSED;srcChangeInfo.mute = srcSeatInfo.mute;srcChangeInfo.userId = "";// Create a seat information instance. Store the modified target seat information.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 group attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to move seats.}@Overridepublic void onSuccess() {// Group attributes modified successfully. Seats moved successfully.}});}
muteRemoteAudio(userId, mute) additionally is required to subscribe and play remote users' audio streams.// Automatic subscription mode (default).mTRTCCloud.setDefaultStreamRecvMode(true, true);// Manual subscription mode (custom).mTRTCCloud.setDefaultStreamRecvMode(false, false);
setDefaultStreamRecvMode before entering the room enterRoom to ensure to take effect.// Enable local audio capture and publishing.mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT)// Stop local audio capture and publishing.mTRTCCloud.stopLocalAudio();
startLocalAudio requests mic permissions, and stopLocalAudio releases them.// 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);
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.// 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);
// Set volume type.mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeAuto);
// Set audio routing.mTRTCCloud.setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
// Send public screen bullet screen messages.V2TIMManager.getInstance().sendGroupTextMessage(text, groupID, V2TIMMessage.V2TIM_PRIORITY_NORMAL, new V2TIMValueCallback<V2TIMMessage>() {@Overridepublic void onError(int i, String s) {// Failed to send bullet screen messages.}@Overridepublic void onSuccess(V2TIMMessage v2TIMMessage) {// Bullet screen messages sent successfully.}});// Receive public screen bullet screen messages.V2TIMManager.getInstance().addSimpleMsgListener(new V2TIMSimpleMsgListener() {@Overridepublic void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {Log.i(TAG, sender.getNickName + ": " + text);}});
// 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 handle 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....// Display sound waves on the UI based on volume levels....}}
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.// 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);
// 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);
setVoiceCaptureVolume(0) to replace muteLocalAudio(true).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) {// Stop the current music first before replaying after playback failure.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 (in 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.}});
setMusicObserver(musicId, null) after playback is finished to completely release the Observer.AudioMusicParam's loopCount parameter to set the number of loop playbacks.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. A negative number means an infinite loop.param.loopCount = loopCount < 0 ? Integer.MAX_VALUE : loopCount;mTRTCCloud.getAudioEffectManager().startPlayMusic(param);}
onComplete callback after each loop playback. It will only be triggered after all the set loop counts have been played.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;}@Overridepublic void onStart(int i, int i1) {}@Overridepublic void onPlayProgress(int i, long l, long l1) {}@Overridepublic void onComplete(int i, int i1) {mTXAudioEffectManager.stopPlayMusic(i);if (i1 >= 0 && loopPlay) {// Here, you can replace the ID and Path of the music in the loop playlist.startPlayMusic(mId, mPath);}}}
private void startPublishMediaToRoom(String roomId, String userId) {// Create a TRTCPublishTarget object.TRTCCloudDef.TRTCPublishTarget target = new TRTCCloudDef.TRTCPublishTarget();// After mixing, the streams are relayed back to the room.target.mode = TRTCCloudDef.TRTC_PublishMixStream_ToRoom;target.mixStreamIdentity.strRoomId = roomId;// Mixed-stream robot's user ID, which should not be a duplicate of the user ID of any other user in the room.target.mixStreamIdentity.userId = userId + MIX_ROBOT;// Set the encoding parameters of the transcoded audio streams (which 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 streams (which can be ignored for pure audio stream mixing).trtcStreamEncoderParam.videoEncodedFPS = 15;trtcStreamEncoderParam.videoEncodedGOP = 3;trtcStreamEncoderParam.videoEncodedKbps = 30;trtcStreamEncoderParam.videoEncodedWidth = 64;trtcStreamEncoderParam.videoEncodedHeight = 64;// Set audio stream 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 a video stream mixing template (which can be ignored for pure audio stream mixing).TRTCCloudDef.TRTCVideoLayout videoLayout = new TRTCCloudDef.TRTCVideoLayout();trtcStreamMixingConfig.videoLayoutList.add(videoLayout);// Start pushing back mixed streams.mTRTCCloud.startPublishMediaStream(target, trtcStreamEncoderParam, trtcStreamMixingConfig);}
private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onStartPublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {// taskId: When the request is successful, TRTC backend will provide you 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.}@Overridepublic void onUpdatePublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {// When you call the media stream publishing 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.}@Overridepublic void onStopPublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {// When you call the media stream publishing stopping 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.}}
// 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 PK.mTRTCCloud.updatePublishMediaStream(taskId, target, trtcStreamEncoderParam, trtcStreamMixingConfig);
// taskId: Task ID returned by the onStartPublishMediaStream callback.mTRTCCloud.stopPublishMediaStream(taskId);
startPublishMediaStream. If you have only initiated one media stream or want to stop all media streams initiated by you, this method is recommended.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 {@Overridepublic 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;}}}
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);
// Pass in the latest PrivateMapKey obtained from the backend into the role switching API.mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor, getPrivateMapKey());
onError callback. For details, see Error Code Table.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. |
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 denied. Check if enterRoom is called consecutively to enter a room with the same ID. |
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. |
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 seat 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 RTC Engine server side 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 {@Overridepublic void onConnectionLost() {// The connection between the SDK and the cloud has been disconnected.}@Overridepublic void onTryToReconnect() {// The SDK is attempting to reconnect to the cloud.}@Overridepublic void onConnectionRecovery() {// The connection between the SDK and the cloud has been restored.}}
// Anchor subscribes to the connection status of mic-connecting audiences.V2TIMManager.getInstance().subscribeUserStatus(userList, new V2TIMCallback() {@Overridepublic void onSuccess() {// Subscription of user status succeeded.}@Overridepublic 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() {@Overridepublic void onSuccess() {// Unsubscription of user status succeeded.}@Overridepublic void onError(int code, String message) {// Unsubscription of user status failed.}});// User status change notification and processing.V2TIMManager.getInstance().addIMSDKListener(new V2TIMSDKListener() {@Overridepublic 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));}}}});

subscribeUserStatus is called.https://trtc.tencentcloudapi.com/?Action=RemoveUser&SdkAppId=1400000001&RoomId=1234&UserIds.0=test1&UserIds.1=test2&<Common request parameters>
onExitRoom() callback with reason set to 1. You can handle leaving the seat and exiting the Chat group in this callback.// Exit TRTC room event callback.@Overridepublic 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, "Being removed from the room by the server, or the current room having been dissolved.");// Leave the seat.leaveSeat(seatIndex);// Exit the Chat group.quitGroup(groupID, new V2TIMCallback() {});}}
https://xxxxxx/v4/group_open_http_svc/destroy_group?sdkappid=88888888&identifier=admin&usersig=xxx&random=99999999&contenttype=json
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() {@Overridepublic void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {// Exit the TRTC room.mTRTCCloud.stopLocalAudio();mTRTCCloud.exitRoom();}});
exitRoom() to complete room exit, the RTC Engine room will be automatically dissolved. Of course, you can also call the server-side API DismissRoom (room ID in integer type) or DismissRoomByStrRoomId (string room ID) to mandatorily dissolve the RTC Engine room.
V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // Pull earlier existing messages from the cloud.option.setGetTimeBegin(1640966400); // Starting from 2022-01-01 00:00:00.option.setGetTimePeriod(1 * 24 * 60 * 60); // Pull messages of 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>>() {@Overridepublic void onSuccess(List<V2TIMMessage> v2TIMMessages) {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
onUserAudioAvailable(userId, true) callback.private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onUserAudioAvailable(String userId, boolean available) {if (available) {// Unmute the corresponding anchors.}}}
V2TIMManager.getGroupManager().getGroupAttributes(groupID, null, new V2TIMValueCallback<Map<String, String>>() {@Overridepublic void onError(int i, String s) {// Failed to obtain group attributes.}@Overridepublic void onSuccess(Map<String, String> attrMap) {// Successfully obtained group attributes. Assume the key for storing anchor mute status is muteStatus.String muteStatus = attrMap.get("muteStatus");// Parse muteStatus, and obtain the mute status of each on-mic anchor.}});
mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeVOIP);
BLUETOOTH permission, and Android 12 or later systems require at least the BLUETOOTH_CONNECT permission and need to request permissions dynamically in the code.<!--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" />
private List<String> permissionList = new ArrayList<>();protected void initPermission() {// Check if the Android SDK version is Android 12 or 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);}}
startPlayMusic is used to play background music, the music resource path parameter path does not support the passing of the path of files in directories for storing application resource files such as assets/raw for Android development. This is because files in these directories are bundled into the APK and are not extracted to the mobile's file system after installation. Currently, only network resource URLs and absolute paths to resource files as external storage of Android devices, and resource files in the application's private directory are supported.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();}}
/data/user/0/<package_name>/files/<file_name>./storage/emulated/0/Android/data/<package_name>/files/<file_name>./storage/emulated/0/Android/data/<package_name>/cache/<file_name>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.MANAGE_EXTERNAL_STORAGE permission:<manifest ...><!-- This is the permission itself --><uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><application ...>...</application></manifest>
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 modelActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);}
Feedback