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'}
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 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() {@Overridepublic void onConnecting() {Log.d(TAG, "IM SDK is connecting to Tencent cloud service");}@Overridepublic 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();
// 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() {@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 TRTC SDK instance (Singleton Pattern).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) {// 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);}});
// 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 creation successful}@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 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.@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 when you fail to enter the room.Log.d(TAG, "Enter room failed");}}
roomId
and string type strRoomId
. The rooms of these two types are not interconnected. It is recommended to unify the room ID type.TRTC_APP_SCENE_VOICE_CHATROOM
.V2TIMManager.getInstance().quitGroup(groupID, new V2TIMCallback() {@Overridepublic void onSuccess() {// Exiting the group successful.}@Overridepublic void onError(int code, String desc) {// Exiting the group failed.}});V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onMemberLeave(String groupID, V2TIMGroupMemberInfo member) {// Group member leave 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() {// Dissolving group successful.}@Overridepublic void onError(int code, String desc) {// Dissolving the group failed.}});V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {// Group dissolved callback.}});
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.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.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;@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 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() {// Successfully modified group attributes. Switch 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 attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to be invited to the microphone.}@Overridepublic 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() {@Overridepublic 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.}}}}});
// 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() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to become a listener.}@Overridepublic void onSuccess() {// Successfully modified group attributes. Switch 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 attribute is added.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to remove the speaker.}@Overridepublic 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() {@Overridepublic 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.}}}}});
// 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() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to mute the seat.}@Overridepublic 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() {@Overridepublic 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.}}}}});
// 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() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to lock the seat.}@Overridepublic 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() {@Overridepublic 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.}}}});
// 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() {@Overridepublic void onError(int code, String message) {// Failed to modify group attributes. Failed to move the seat.}@Overridepublic void onSuccess() {// Modify group attributes successfully. Move the seat successfully.}});}
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);
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) {// Successfully sent bullet screen messages.}});// 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 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....}}
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);
musicPath
. MP3/AAC/M4A/WAV formats are supported.// 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) {// 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.}});
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. 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 or Path of the music in the loop playlist.startPlayMusic(mId, mPath);}}}
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);}
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);}
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 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 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.}@Overridepublic 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.}}
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);
startPublishMediaStream
.// 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 is denied. Check if enterRoom is called consecutively to enter rooms 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 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 {@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) {// Failed to unsubscription of user status.}});// 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
.https://trtc.tencentcloudapi.com/?Action=RemoveUser&SdkAppId=1400000001&RoomId=1234&UserIds.0=test1&UserIds.1=test2&<Common request parameters>
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.@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, "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() {});}}
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 TRTC room.mTRTCCloud.stopLocalAudio();mTRTCCloud.exitRoom();}});
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).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>>() {@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 the group attributes.}@Overridepublic 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.}});
mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeVOIP);
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.<!--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 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);}}
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.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);}
Was this page helpful?