Overview of Scenario-Based Solutions
소셜 엔터테인먼트
이커머스 라이브 방송
Audio/Video Call
원거리 실시간 조작
스마트 고객 서비스
AI 인터뷰





dependencies {// RTC Engine 라이트 버전 SDK는 TRTC 및 라이브 방송 재생 두가지 기능을 포함합니다implementation 'com.tencent.liteav:LiteAVSDK_TRTC:latest.release'// Chat SDK 추가하며 최신 버전 번호를 입력하는 것을 권장합니다implementation 'com.tencent.imsdk:imsdk-plus:Version number'// Quic 플러그인을 추가하려면 다음 줄의 주석을 취소하세요(주의: 플러그인 버전 번호와 Chat 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이 31이거나 대상 기기가 Android 12및 그 이상 시스템 버전과 관련된 경우, 블루투스 기능을 정상적으로 사용하기 위해 코드에서 android.permission.BLUETOOTH_CONNECT 권한을 동적으로 신청해야 합니다. 자세한 내용은 블루투스 권한를 참조하십시오.-keep class com.tencent.** { *; }

// 이벤트 리스너의 추가V2TIMManager.getInstance().addIMSDKListener(imSdkListener);// Chat SDK 초기화하며 이 인터페이스를 호출한 후 즉시 로그인 인터페이스를 호출할 수 있습니다V2TIMManager.getInstance().initSDK(context, sdkAppID, null);// SDK 초기화 후 연결 상태, 로그인 티켓 만료 등 일부 이벤트가 발생할 수 있습니다private V2TIMSDKListener imSdkListener = new V2TIMSDKListener() {@Overridepublic void onConnecting() {Log.d(TAG, "Chat SDK가 텐센트 클라우드 서버에 연결 중입니다");}@Overridepublic void onConnectSuccess() {Log.d(TAG, "Chat SDK가 Tencent Cloud 서버에 성공적으로 연결되었습니다");}};// 이벤트 리스너의 제거V2TIMManager.getInstance().removeIMSDKListener(imSdkListener);// Chat SDK 초기화의 해제V2TIMManager.getInstance().unInitSDK();
// RTC Engine SDK 인스턴스의 생성(싱글톤 모드)TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(context);// 이벤트 리스너의 설정mTRTCCloud.setListener(trtcSdkListener);// SDK의 다양한 이벤트 알림(예: 오류 코드, 경고 코드, 오디오/비디오 상태 매개변수 등)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);}};// 이벤트 리스너의 제거mTRTCCloud.setListener(null);// RTC Engine SDK 인스턴스(싱글톤 모드)를 파기합니다.TRTCCloud.destroySharedInstance();
// 로그인: userID는 사용자 자체 정의 가능하고 userSig는 단계1 참조하여 생성 및 획득합니다V2TIMManager.getInstance().login(userID, userSig, new V2TIMCallback() {@Overridepublic void onSuccess() {Log.i("imsdk", "success");}@Overridepublic void onError(int code, String desc) {// 다음 오류 코드가 반환되면 UserSig가 만료되었음을 의미하므로 새로 발급된 UserSig를 사용하여 다시 로그인하십시오.// 1. ERR_USER_SIG_EXPIRED(6206)// 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED(70001)// 주의: 다른 오류 코드의 경우 여기에서 로그인 인터페이스를 호출하지 마십시오. Chat SDK 로그인이 무한 루프에 빠지는 것을 방지하기 위한 것입니다Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);}});
// 로그아웃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) {// 그룹 생성 성공}@Overridepublic void onError(int code, String desc) {// 그룹 생성 실패}});// 그룹 생성 알림을 감시합니다V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupCreated(String groupID) {// 그룹 생성 콜백하고 groupID는 새로 생성된 그룹의 ID입니다}});
GROUP_TYPE_AVCHATROOM을 선택해야 합니다.V2TIMManager.getInstance().joinGroup(groupID, message, new V2TIMCallback() {@Overridepublic void onSuccess() {// 그룹 가입 성공}@Overridepublic void onError(int code, String desc) {// 그룹 가입 실패}});// 그룹 가입 이벤트의 감시V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onMemberEnter(String groupID, List<V2TIMGroupMemberInfo> memberList) {// 누군가 그룹에 가입했습니다}});
private void enterRoom(String roomId, String userId) {TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();// 문자열 방 번호를 예로 들면 IM 그룹 번호와 일치하는 것이 좋습니다params.strRoomId = roomId;params.userId = userId;// 업무 백엔드에서 가져온 UserSigparams.userSig = getUserSig(userId);// 손님의 SDKAppID로 교체합니다params.sdkAppId = SDKAppID;// 음성 채팅 상호 작용 시나리오에서 방에 들어가려면 사용자 역할을 지정해야 합니다params.role = TRTCCloudDef.TRTCRoleAudience;// 음성 채팅 인터랙티브 방 입장 시나리오를 예로 들어mTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_VOICE_CHATROOM);}// 방 입장 결과 이벤트의 콜백@Overridepublic void onEnterRoom(long result) {if (result > 0) {// result는 방에 임장하는 데 소요된 시간(밀리초)을 나타냅니다.Log.d(TAG, "Enter room succeed");} else {// result는 방 입장 실패의 오류 코드를 나타냅니다Log.d(TAG, "Enter room failed");}}
roomId와 문자열 형식 strRoomId로 나뉘며 두 유형의 방은 서로 통하지 않으므로 방 번호 유형을 통일하는 것이 좋습니다.TRTC_APP_SCENE_VOICE_CHATROOM을 선택하는 것을 권장합니다V2TIMManager.getInstance().quitGroup(groupID, new V2TIMCallback() {@Overridepublic void onSuccess() {// 그룹 나가기 성공}@Overridepublic void onError(int code, String desc) {// 그룹 나가기 실패}});V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onMemberLeave(String groupID, V2TIMGroupMemberInfo member) {// 그룹 멤버 나가기의 콜백}});
dismissGroup을 호출하여 그룹을 해산할 수만 있습니다.private void exitRoom() {mTRTCCloud.stopLocalAudio();mTRTCCloud.exitRoom();}// 방 나가기 이벤트의 콜백@Overridepublic void onExitRoom(int reason) {if (reason == 0) {Log.d(TAG, "exitRoom을 호출하여 방에서 나가기");} else if (reason == 1) {Log.d(TAG, "서버에 의해 현재 방에서 나가게 됩니다");} else if (reason == 2) {Log.d(TAG, "현재 방 전체가 해체됩니다");}}
onExitRoom 콜백을 통해 알려줍니다.enterRoom을 다시 호출하거나 다른 음성/영상 SDK로 전환하려면 onExitRoom 콜백이 발생한 후 관련 작업을 수행하십시오. 그렇지 않으면 카메라, 마이크 장치가 점유됨 등 다양한 문제가 발생할 수 있습니다.V2TIMManager.getInstance().dismissGroup(groupID, new V2TIMCallback() {@Overridepublic void onSuccess() {// 그룹 해산 성공}@Overridepublic void onError(int code, String desc) {// 그룹 해산 실패}});V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {// 그룹 해산 콜백}});
DismissRoom(숫자 방 ID와 문자열 방 ID 구분)를 제공하며 이 인터페이스를 호출하여 방의 모든 사용자를 방에서 나가게 하고 방을 해산할 수 있습니다.exitRoom 인터페이스를 통해 방 내 모든 스트리머와 청취자의 퇴장을 완료시키고 퇴장 후 RTC Engine 방 라이프사이클 규칙에 따라 방이 자동으로 해산됩니다. 자세한 내용은 방에서 나가기를 참조하세요.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;// 좌석 상태는 세 가지 상태에 대응합니다public int status;// 좌석 금언 여부public boolean mute;// 좌석 점유돼었을 때 사용자 정보 저장합니다public String userId;@Overridepublic String toString() {return "TXSeatInfo{"+ "status=" + status+ ", mute=" + mute+ ", user='" + userId + '''+ '}';}}
// 청취자가 마이크 요청을 보내며 userId는 스트리머 Id이고 data는 시그널링 식별을 위한 json을 전달할 수 있습니다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 ");}});}// 스트리머가 마이크 사용의 요청을 수신하며 inviteID는 해당 요청 Id이고 inviter는 요청자 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);}});
// 마이크 사용의 요청을 동의합니다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 ");}});}// 마이크 사용의 요청 거절합니다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 ");}});}
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;// 마이크 요청 동의의 콜백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);}});// 청취자가 마이크를 켜서 사용하기 시작합니다private void takeSeat(int seatIndex) {// 마이크 순위 정보 인스턴스 생성하며 수정된 마이크 순위 정보를 저장합니다SeatInfo localInfo = mSeatInfoList.get(seatIndex);SeatInfo seatInfo = new SeatInfo();seatInfo.status = SeatInfo.STATUS_USED;seatInfo.mute = localInfo.mute;seatInfo.userId = mUserId;// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다Gson gson = new Gson();String json = gson.toJson(seatInfo, SeatInfo.class);HashMap<String, String> map = new HashMap<>();map.put("seat" + seatIndex, json);// 그룹 속성을 설정하고 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되고 마이크 연결이 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되고 TRTC 역할의 전환 및 스트리밍 시작합니다mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);}});}
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;// 스트리머 측에서 해당 인터페이스를 호출하여 그룹 속성에 저장된 마이크 순위 정보를 수정합니다.private void pickSeat(String userId, int seatIndex) {// 마이크 순위 정보 인스턴스 생성하며 수정된 마이크 순위 정보를 저장합니다SeatInfo localInfo = mSeatInfoList.get(seatIndex);SeatInfo seatInfo = new SeatInfo();seatInfo.status = SeatInfo.STATUS_USED;seatInfo.mute = localInfo.mute;seatInfo.userId = userId;// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다Gson gson = new Gson();String json = gson.toJson(seatInfo, SeatInfo.class);HashMap<String, String> map = new HashMap<>();map.put("seat" + seatIndex, json);// 그룹 속성을 설정하며 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되며 마이크 사용의 초대가 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되면 onGroupAttributeChanged 콜백 트리거됩니다}});}// 청취자 측에서 그룹 속성 변경 콜백을 수신하여 자체 정보에 매칭 성공 후 스트리밍 시작합니다V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// 로컬에 저장된 기존의 전체 마이크 순위 정보 리스트final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// groupAttributeMap에서 파싱한 최신 전체 마이크 순위 정보 리스트final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// 전체 마이크 순위 정보 리스트를 순회하며 새/구 마이크 정보를 비교합니다.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)) {// 자신의 정보에 매칭 성공 후, RTC Engine 역할 전환 및 스트리밍 시작합니다mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);} else {// 로컬 마이크 순위 목록을 업데이트하고 로컬 마이크 순위 뷰 렌더링합니다}}}}});
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;private void leaveSeat(int seatIndex) {// 마이크 순위 정보 인스턴스 생성하며 수정된 마이크 순위 정보를 저장합니다SeatInfo localInfo = mSeatInfoList.get(seatIndex);SeatInfo seatInfo = new SeatInfo();seatInfo.status = SeatInfo.STATUS_UNUSED;seatInfo.mute = localInfo.mute;seatInfo.userId = "";// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다Gson gson = new Gson();String json = gson.toJson(seatInfo, SeatInfo.class);HashMap<String, String> map = new HashMap<>();map.put("seat" + seatIndex, json);// 그룹 속성을 설정하고 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되고 마이크 끄기가 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되고 RTC Engine 역할 전환 및 스트리밍 중지합니다mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);mTRTCCloud.stopLocalAudio();}});}
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;// 스트리머 측에서 해당 인터페이스를 호출하여 그룹 속성에 저장된 마이크 순위 정보를 수정합니다.private void kickSeat(int seatIndex) {// 마이크 순위 정보 인스턴스 생성하며 수정된 마이크 순위 정보를 저장합니다SeatInfo localInfo = mSeatInfoList.get(seatIndex);SeatInfo seatInfo = new SeatInfo();seatInfo.status = SeatInfo.STATUS_UNUSED;seatInfo.mute = localInfo.mute;seatInfo.userId = "";// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다Gson gson = new Gson();String json = gson.toJson(seatInfo, SeatInfo.class);HashMap<String, String> map = new HashMap<>();map.put("seat" + seatIndex, json);// 그룹 속성을 설정하고 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되고 마이크 끄기 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되고 onGroupAttributeChanged 콜백 트리거됩니다}});}// 연결된 청취자 측에서 그룹 속성 변경 콜백을 수신하여 자체 정보에 매칭 성공 후 스트리밍 중지합니다V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// 로컬에 저장된 기존의 전체 마이크 순위 정보 리스트final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// groupAttributeMap에서 파싱한 최신 전체 마이크 순위 정보 리스트final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// 전체 마이크 순위 정보 리스트를 순회하며 새/구 마이크 정보를 비교합니다.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)) {// 자신의 정보에 매칭 성공 후 RTC Engine 역할을 전환하고 스트리밍을 중지합니다.mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);mTRTCCloud.stopLocalAudio();} else {// 로컬 마이크 순위 목록을 업데이트하고 로컬 마이크 순위 뷰 렌더링합니다}}}}});
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;// 스트리머 측에서 해당 인터페이스를 호출하여 그룹 속성에 저장된 마이크 순위 정보를 수정합니다.private void muteSeat(int seatIndex, boolean mute) {// 마이크 순위 정보 인스턴스 생성하며 수정된 마이크 순위 정보를 저장합니다SeatInfo localInfo = mSeatInfoList.get(seatIndex);SeatInfo seatInfo = new SeatInfo();seatInfo.status = localInfo.status;seatInfo.mute = mute;seatInfo.userId = localInfo.userId;// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다Gson gson = new Gson();String json = gson.toJson(seatInfo, SeatInfo.class);HashMap<String, String> map = new HashMap<>();map.put("seat" + seatIndex, json);// 그룹 속성을 설정하고 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되고 마이크 금언 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되고 onGroupAttributeChanged 콜백 트리거됩니다}});}// 연결된 청취자 측에서 그룹 속성 변경 콜백을 수신하여 자체 정보에 매칭 성공 후 스트리밍 일시 중지/재개합니다V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// 로컬에 저장된 기존의 전체 마이크 순위 정보 리스트final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// groupAttributeMap에서 파싱한 최신 전체 마이크 순위 정보 리스트final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// 전체 마이크 순위 정보 리스트를 순회하며 새/구 마이크 정보를 비교합니다.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)) {// 자신의 정보에 매칭 성공 후 로컬 스트리밍을 일시 중지/재개합니다.mTRTCCloud.muteLocalAudio(newInfo.mute);} else {// 로컬 마이크 순위 목록을 업데이트하고 로컬 마이크 순위 뷰 렌더링합니다}}}}});
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;// 스트리머 측에서 해당 인터페이스를 호출하여 그룹 속성에 저장된 마이크 순위 정보를 수정합니다.private void lockSeat(int seatIndex, boolean isLock) {// 마이크 순위 정보 인스턴스 생성하며 수정된 마이크 순위 정보를 저장합니다SeatInfo localInfo = mSeatInfoList.get(seatIndex);SeatInfo seatInfo = new SeatInfo();seatInfo.status = isLock ? SeatInfo.STATUS_LOCKED : SeatInfo.STATUS_UNUSED;seatInfo.mute = localInfo.mute;seatInfo.userId = "";// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다Gson gson = new Gson();String json = gson.toJson(seatInfo, SeatInfo.class);HashMap<String, String> map = new HashMap<>();map.put("seat" + seatIndex, json);// 그룹 속성을 설정하고 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되고 마이크 잠금이 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되고 onGroupAttributeChanged 콜백 트리거됩니다}});}// 청취자 측에서 그룹 속성 변경 콜백을 수신하여 해당 마이크 뷰가 업데이트됩니다V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupAttributeChanged(String groupID, Map<String, String> groupAttributeMap) {// 로컬에 저장된 기존의 전체 마이크 순위 정보 리스트final List<SeatInfo> oldSeatInfoList = mSeatInfoList;// groupAttributeMap에서 파싱한 최신 전체 마이크 순위 정보 리스트final List<SeatInfo> newSeatInfoList = getSeatListFromAttr(groupAttributeMap, seatSize);// 전체 마이크 순위 정보 리스트를 순회하며 새/구 마이크 정보를 비교합니다.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) {// 마이크 잠금의 해제} else if (oldInfo.status != newInfo.status && newInfo.status == SeatInfo.STATUS_LOCKED) {// 마이크 순위 잠그기}}}});
// 로컬에 저장된 전체 마이크 순위 정보의 목록private List<SeatInfo> mSeatInfoList;// 마이크 사용중의 스트리머가 해당 인터페이스를 호출하여 그룹 속성에 저장된 마이크 정보를 수정합니다.private void moveSeat(int dstIndex) {// 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;}}// 마이크 번호로 해당 마이크 순위를 받습니다SeatInfo srcSeatInfo = mSeatInfoList.get(srcIndex);SeatInfo dstSeatInfo = mSeatInfoList.get(dstIndex);// 마이크 위치 정보 인스턴스를 생성하고 수정된 소스 마이크 순위 정보를 저장합니다SeatInfo srcChangeInfo = new SeatInfo();srcChangeInfo.status = SeatInfo.STATUS_UNUSED;srcChangeInfo.mute = srcSeatInfo.mute;srcChangeInfo.userId = "";// 마이크 순위 정보 인스턴스를 생성하고 수정된 대상 마이크 순위 정보를 저장합니다SeatInfo dstChangeInfo = new SeatInfo();dstChangeInfo.status = SeatInfo.STATUS_USED;dstChangeInfo.mute = dstSeatInfo.mute;dstChangeInfo.userId = mUserId;// 마이크 순위 정보 객체를 JSON 형식으로 직렬화합니다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);// 그룹 속성을 설정하고 해당 그룹 속성이 이미 있으면 value 값을 업데이트하고, 없으면 해당 속성을 추가합니다.V2TIMManager.getGroupManager().setGroupAttributes(groupId, map, new V2TIMCallback() {@Overridepublic void onError(int code, String message) {// 그룹 속성의 수정이 실패되고 마이크 이동이 실패됩니다}@Overridepublic void onSuccess() {// 그룹 속성의 수정이 성공되며 마이크 이동이 성공됩니다}});}
muteRemoteAudio(userId, mute)을 추가로 호출하여 원격 사용자의 오디오 스트림을 구독하고 재생해야 합니다.// 자동 구독 모드(기본값)mTRTCCloud.setDefaultStreamRecvMode(true, true);// 수동 구독 모드(자체 정의)mTRTCCloud.setDefaultStreamRecvMode(false, false);
setDefaultStreamRecvMode은 방 입장 enterRoom 전에 호출해야만 적용됩니다.// 로컬 오디오의 수집 및 게시를 활성화합니다mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT)// 로컬 오디오의 수집 및 게시를 중지합니다mTRTCCloud.stopLocalAudio();
startLocalAudio는 마이크 사용 권한을 요청하며, stopLocalAudio는 마이크 사용 권한을 릴리스합니다.// 로컬 오디오 스트림 게시를 일시 중지합니다(마이크 끄기)mTRTCCloud.muteLocalAudio(true);// 로컬 오디오 스트림 게시를 재개합니다(마이크 켜기)mTRTCCloud.muteLocalAudio(false);// 특정 원격 사용자의 오디오 스트림 구독 및 재생을 일시 중지합니다mTRTCCloud.muteRemoteAudio(userId, true);// 특정 원격 사용자의 오디오 스트림 구독 및 재생을 재개합니다mTRTCCloud.muteRemoteAudio(userId, false);// 모든 원격 사용자의 오디오 스트림 구독 및 재생을 일시 중지합니다mTRTCCloud.muteAllRemoteAudio(true);// 모든 원격 사용자의 오디오 스트림 구독 및 재생을 재개합니다mTRTCCloud.muteAllRemoteAudio(false);
muteLocalAudio는 소프트웨어 측면에서 데이터 스트림을 일시 중지하거나 허용하기만 하면 되므로 효율적이고 원활합니다.자주 마이크를 켜고 끄는 시나리오에 더 적합합니다.// 로컬 오디오 수집 및 게시 시 음질을 설정합니다mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);// 오디오 스트리밍 중 동적으로 음질 설정합니다mTRTCCloud.setAudioQuality(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
// 음량 유형의 설정mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeAuto);
// 오디오 라우팅의 설정mTRTCCloud.setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
// 공개 탄막 메시지의 전송V2TIMManager.getInstance().sendGroupTextMessage(text, groupID, V2TIMMessage.V2TIM_PRIORITY_NORMAL, new V2TIMValueCallback<V2TIMMessage>() {@Overridepublic void onError(int i, String s) {// 탄막 메시지의 전송이 실패됩니다}@Overridepublic void onSuccess(V2TIMMessage v2TIMMessage) {// 탄막 메시지의 전송이 성공됩니다}});// 공개 탄막 메시지의 수신V2TIMManager.getInstance().addSimpleMsgListener(new V2TIMSimpleMsgListener() {@Overridepublic void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {Log.i(TAG, sender.getNickName + ": " + text);}});
// 볼륨 크기 콜백 활성화하고 방 입장 성공 후 즉시 활성화하는 것을 권장합니다// interval: 콜백 간격(ms); enable_vad: 인간 음성 감지 활성화 여부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는 로컬 사용자 및 원격 스트리밍 사용자를 포함한 모든 말하는 사용자의 볼륨 크기를 표시하는 데 사용됩니다// totalVolume는 원격 스트리밍 사용자 중 최대 볼륨 값을 피드백하는 데 사용됩니다...// 볼륨 크기에 따라 UI에 해당하는 음파 디스플레이를 표시합니다....}}
userVolumes는 한 배열리고 배열의 각 요소에서 userId가 자신일 경우 로컬 마이크에서 수집된 볼륨 크기를 나타내고 다른 경우는 원격 사용자의 볼륨 크기를 나타냅니다.// 배경 음악, 짧은 음향 효과 및 인간음성 이펙트를 설정하는 데 사용되는 관리 클래스 얻습니다TXAudioEffectManager mTXAudioEffectManager = mTRTCCloud.getAudioEffectManager();TXAudioEffectManager.AudioMusicParam param = new TXAudioEffectManager.AudioMusicParam(musicID, musicPath);// 음악을 원격으로 공유할지?(그렇지 않으면 로컬에서만 재생)param.publish = true;// 재생 중인 파일이 짧은 음향 효과 파일인지?param.isShortFile = false;// 배경 음악의 재생 시작합니다mTXAudioEffectManager.startPlayMusic(param);// 배경 음악의 재생 중지합니다mTXAudioEffectManager.stopPlayMusic(musicID);// 배경 음악의 재생을 일시 정지합니다mTXAudioEffectManager.pausePlayMusic(musicID);// 배경 음악의 재생 재개합니다mTXAudioEffectManager.resumePlayMusic(musicID);
musicPath를 통해 로컬 절대 경로 또는 URL 주소로 전달할 수 있습니다. MP3/AAC/M4A/WAV 형식을 지원합니다.// 특정 백그라운드 음악의 로컬 재생 볼륨 크기를 설정합니다mTXAudioEffectManager.setMusicPlayoutVolume(musicID, volume);// 특정 백그라운드 음악의 원격 재생 볼륨 크기를 설정합니다mTXAudioEffectManager.setMusicPublishVolume(musicID, volume);// 모든 백그라운드 음악의 로컬 볼륨 및 원격 볼륨 크기를 설정합니다mTXAudioEffectManager.setAllMusicVolume(volume);// 인간 음성의 수집 음량 크기를 설정합니다mTXAudioEffectManager.setVoiceCaptureVolume(volume);
muteLocalAudio(true) 아니고 setVoiceCaptureVolume(0)를 사용하세요.mTXAudioEffectManager.setMusicObserver(mCurPlayMusicId, new TXAudioEffectManager.TXMusicPlayObserver() {@Override// 배경 음악 재생을 시작합니다public void onStart(int id, int errCode) {// -4001: 경로 열기 실패// -4002: 디코딩 실패// -4003: URL 주소가 무효합니다// -4004: 재생 중지되지 않습니다if (errCode < 0) {// 재생 실패 후 다시 재생하기 전에 현재 음악 재생을 먼저 중지해야 합니다.mTXAudioEffectManager.stopPlayMusic(id);}}@Override// 배경 음악의 재생 진도public void onPlayProgress(int id, long curPtsMs, long durationMs) {// curPtsMS 현재 재생 시간(밀리초)// durationMs 현재 음악의 총 시간(밀리초)}@Override// 배경 음악 재생이 완료되었습니다public void onComplete(int id, int errCode) {// 약한 네트워크로 인해 재생 중간에 실패한 경우에도 이 콜백이 발생하며 이때 errCode < 0입니다// 중간에 재생을 일시 중지하거나 중단해도 onComplete 콜백이 발생하지 않습니다}});
setMusicObserver(musicId, null)을 수행하여 Observer를 완전히 릴리스할 수 있습니다.AudioMusicParam의 loopCount 매개변수를 사용하여 반복 재생 횟수를 설정합니다.private void startPlayMusic(int id, String path, int loopCount) {TXAudioEffectManager.AudioMusicParam param = new TXAudioEffectManager.AudioMusicParam(id, path);// 음악을 원격으로 공유할지?param.publish = true;// 재생 중인 파일이 짧은 음향 효과 파일인지?param.isShortFile = true;// 반복 재생 횟수를 설정하고 음수는 무한 반복을 의미합니다param.loopCount = loopCount < 0 ? Integer.MAX_VALUE : loopCount;mTRTCCloud.getAudioEffectManager().startPlayMusic(param);}
onComplete 콜백이 트리거되지 않으며 설정된 반복 횟수가 모두 재생된 후에야 해당 콜백이 트리거됩니다.onComplete을 통해 반복 재생을 구현하며, 일반적으로 리스트 반복 또는 단일 곡 반복에 사용됩니다.// 반복 재생 여부를 나타내는 멤버 변수입니다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) {// 여기서 반복 재생 리스트 음악의 ID, Path를 대체할 수 있습니다startPlayMusic(mId, mPath);}}}
private void startPublishMediaToRoom(String roomId, String userId) {// TRTCPublishTarget 객체 생성TRTCCloudDef.TRTCPublishTarget target = new TRTCCloudDef.TRTCPublishTarget();// 혼합 스트리밍 후 방으로 푸시백하기target.mode = TRTCCloudDef.TRTC_PublishMixStream_ToRoom;target.mixStreamIdentity.strRoomId = roomId;// 혼합 스트리밍 로봇의 userid는 방 내 다른 사용자의 userid와 중복될 수 없습니다target.mixStreamIdentity.userId = userId + MIX_ROBOT;// 트랜스코딩된 오디오 스트림의 인코딩 매개변수를 설정합니다(자체 정의 가능)TRTCCloudDef.TRTCStreamEncoderParam trtcStreamEncoderParam = new TRTCCloudDef.TRTCStreamEncoderParam();trtcStreamEncoderParam.audioEncodedChannelNum = 2;trtcStreamEncoderParam.audioEncodedKbps = 64;trtcStreamEncoderParam.audioEncodedCodecType = 2;trtcStreamEncoderParam.audioEncodedSampleRate = 48000;// 트랜스코딩된 비디오 스트림의 인코딩 매개변수를 설정합니다(순수 오디오 혼합 스트리밍은 무시해도 됩니다)trtcStreamEncoderParam.videoEncodedFPS = 15;trtcStreamEncoderParam.videoEncodedGOP = 3;trtcStreamEncoderParam.videoEncodedKbps = 30;trtcStreamEncoderParam.videoEncodedWidth = 64;trtcStreamEncoderParam.videoEncodedHeight = 64;// 오디오/비디오 혼합 스트리밍 매개변수를 설정합니다TRTCCloudDef.TRTCStreamMixingConfig trtcStreamMixingConfig = new TRTCCloudDef.TRTCStreamMixingConfig();// 기본적으로 빈 값을 입력하면 방의 모든 오디오가 믹싱됨을 의미합니다trtcStreamMixingConfig.audioMixUserList = null;// 비디오 혼합 스트리밍 템플릿을 설정합니다(오디오 혼합 스트리밍은 무시해도 됩니다)TRTCCloudDef.TRTCVideoLayout videoLayout = new TRTCCloudDef.TRTCVideoLayout();trtcStreamMixingConfig.videoLayoutList.add(videoLayout);// 혼합 스트리밍 푸시백 시작합니다mTRTCCloud.startPublishMediaStream(target, trtcStreamEncoderParam, trtcStreamMixingConfig);}
private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onStartPublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {// taskId: 요청이 성공하면 RTC Engine 백엔드는 콜백에서 이 작업의 taskId를 제공하며 이후 이 taskId를 updatePublishMediaStream 및 stopPublishMediaStream과 함께 사용하여 업데이트 및 중지할 수 있습니다.// code: 콜백 결과이고 0은 성공을 나타내며 그 외의 값은 실패를 나타냅니다.}@Overridepublic void onUpdatePublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {// 미디어 스트림 발행 인터페이스(updatePublishMediaStream)를 호출할 때 전달한 taskId가 이 콜백을 통해 다시 전달되며 이 콜백이 어떤 업데이트 요청에 속하는지 식별하는 데 사용됩니다.// code: 콜백 결과이고 0은 성공을 나타내며 그 외의 값은 실패를 나타냅니다.}@Overridepublic void onStopPublishMediaStream(String taskId, int code, String message, Bundle extraInfo) {// 미디어 스트림 발행 중지(stopPublishMediaStream)를 호출할 때 전달한 taskId가 이 콜백을 통해 다시 전달되며 이 콜백이 어떤 중지 요청에 속하는지 식별하는 데 사용됩니다.// code: 콜백 결과이고 0은 성공을 나타내며 그 외의 값은 실패를 나타냅니다.}}
startPublishMediaStream로 시작된 미디어 스트림을 업데이트합니다.// taskId: onStartPublishMediaStream 콜백의 작업 ID입니다// target: 예를 들어 발행된 CDN URL 추가 또는 삭제합니다// params: 생 측의 스트림 중단을 방지하기 위해 미디어 스트림 인코딩 출력 매개변수를 일치시키는 것을 권장합니다// config: 예를 들어 크로스 룸 PK와 같은 혼합 트랜스코딩에 참여하는 사용자 목록을 업데이트하는 것입니다mTRTCCloud.updatePublishMediaStream(taskId, target, trtcStreamEncoderParam, trtcStreamMixingConfig);
startPublishMediaStream로 시작된 미디어 스트림을 중지합니다.// taskId: onStartPublishMediaStream 콜백의 작업 ID입니다mTRTCCloud.stopPublishMediaStream(taskId);
startPublishMediaStream로 시작한 모든 미디어 스트림이 중지됩니다. 하나의 미디어 스트림만 시작했거나 손님을 통해 시작된 모든 미디어 스트림을 중지하려는 경우 이 방법을 사용하는 것이 좋습니다.onNetworkQuality를 통해 로컬 및 원격 사용자의 네트워크 품질을 실시간으로 모니터링할 수 있으며 이 콜백은 2초마다 한 번씩 발생합니다.private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onNetworkQuality(TRTCCloudDef.TRTCQuality localQuality,ArrayList<TRTCCloudDef.TRTCQuality> remoteQuality) {// localQuality userId가 비어 있으면 로컬 사용자의 네트워크 품질 평가 결과를 나타냅니다.// remoteQuality는 원격 사용자의 네트워크 품질 평가 결과를 나타내며 그 결과는 원격 및 로컬의 영향을 받습니다.switch (localQuality.quality) {case TRTCCloudDef.TRTC_QUALITY_Excellent:Log.i(TAG, "현재 네트워크 상태가 매우 좋습니다");break;case TRTCCloudDef.TRTC_QUALITY_Good:Log.i(TAG, "현재 네트워크 상태가 비교적 좋습니다");break;case TRTCCloudDef.TRTC_QUALITY_Poor:Log.i(TAG, "현재 네트워크 상태가 일반입니다");break;case TRTCCloudDef.TRTC_QUALITY_Bad:Log.i(TAG, "현재 네트워크 상태가 좋지 않습니다");break;case TRTCCloudDef.TRTC_QUALITY_Vbad:Log.i(TAG, "현재 네트워크 상태가 매우 나쁩니다");break;case TRTCCloudDef.TRTC_QUALITY_Down:Log.i(TAG, "현재 네트워크가 TRTC 최소 요구 사항을 충족하지 못합니다");break;default:Log.i(TAG, "정의되지 않음");break;}}}
TRTCCloudDef.TRTCParams mTRTCParams = new TRTCCloudDef.TRTCParams();mTRTCParams.sdkAppId = SDKAPPID;mTRTCParams.userId = mUserId;mTRTCParams.strRoomId = mRoomId;// 업무 백엔드에서 가져온 UserSigmTRTCParams.userSig = getUserSig();// 업무 백엔드에서 가져온 PrivateMapKeymTRTCParams.privateMapKey = getPrivateMapKey();mTRTCParams.role = TRTCCloudDef.TRTCRoleAudience;mTRTCCloud.enterRoom(mTRTCParams, TRTCCloudDef.TRTC_APP_SCENE_VOICE_CHATROOM);
// 업무 백엔드에서 가져온 최신 PrivateMapKey를 역할 전환 인터페이스로 전달합니다mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor, getPrivateMapKey());
열거형 | 값 | 설명 |
ERR_TRTC_INVALID_USER_SIG | -3320 | 방 입장 매개변수 userSig가 올바르지 않습니다. TRTCParams.userSig이 비어 있는지 확인하세요. |
ERR_TRTC_USER_SIG_CHECK_FAILED | -100018 | UserSig 검증에 실패했습니다. 매개변수 TRTCParams.userSig이 올바르게 입력되었거나 만료되었는지 확인하세요. |
열거형 | 값 | 설명 |
ERR_TRTC_CONNECT_SERVER_TIMEOUT | -3308 | 입장 요청이 시간 초과했습니다, 네트워크 연결이 끊겼는지 또는 VPN이 켜져 있는지 확인하세요. 4G로 전환하여 테스트할 수도 있습니다. |
ERR_TRTC_INVALID_SDK_APPID | -3317 | 입장 매개변수 sdkAppId가 잘못되었습니다. TRTCParams.sdkAppId이 비어 있는지 확인하세요. |
ERR_TRTC_INVALID_ROOM_ID | -3318 | 입장 매개변수 roomId가 잘못되었습니다. TRTCParams.roomId 또는 TRTCParams.strRoomId이 비어 있는지 확인하세요. roomId와 strRoomId는 혼용할 수 없습니다. |
ERR_TRTC_INVALID_USER_ID | -3319 | 입장 매개변수 userId가 올바르지 않습니다. TRTCParams.userId이 비어 있는지 확인하세요. |
ERR_TRTC_ENTER_ROOM_REFUSED | -3340 | 입장 요청이 거부되었습니다. enterRoom을 연속적으로 호출하여 동일한 ID의 방에 입장했는지 확인하세요. |
열거형 | 값 | 설명 |
ERR_MIC_START_FAIL | -1302 | Windows 또는 Mac 장치에서 마이크 구성 프로그램(드라이버)이 비정상일 경우, 마이크 열 수가 없습니다. 장치를 비활성화한 후 다시 활성화하거나, 기기를 재시작하거나, 구성 프로그램을 업데이트하세요. |
ERR_SPEAKER_START_FAIL | -1321 | 스피커 열기 실패했습니다. 예를 들어 Windows 또는 Mac 장치에서 스피커 구성 프로그램(드라이버)이 이상이 있는 경우, 장치를 비활성화한 후 다시 활성화하거나 기기를 재시작하거나 구성 프로그램을 업데이트하세요. |
ERR_MIC_OCCUPY | -1319 | 마이크가 사용 중입니다. 예를 들어 모바일 장치에서 통화 중일 때 마이크를 열 수가 없습니다. |
onConnectionLost 콜백을 수신한 후 로컬 마이크 순위 UI에 네트워크 끊김 표시를 표시하여 사용자에게 알릴 수 있으며 동시에 로컬에서 타이머를 시작합니다. 설정된 시간의 임계값을 초과해도 onConnectionRecovery 콜백을 수신하지 못하면 네트워크가 계속 끊긴 상태입니다. 이때 로컬에서 마이크 종료 및 퇴장 절차를 시작하며 동시에 팝업으로 사용자에게 방을 나갔음을 알리고 페이지를 파기합니다. 네트워크가 90초(기본값) 이상 끊긴 경우 타임아웃 퇴장이 트리거되며, RTC Engine 서버는 해당 사용자를 방에서 강제 퇴장시킵니다. 해당 사용자가 스트리머 역할인 경우 방 내 다른 사용자는 onRemoteUserLeaveRoom 콜백을 수신합니다.private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onConnectionLost() {// SDK와 클라우드 간의 연결이 끊어졌습니다.}@Overridepublic void onTryToReconnect() {// SDK가 클라우드에 재연결을 시도 중입니다.}@Overridepublic void onConnectionRecovery() {// SDK가 클라우드와의 연결이 복구되었습니다.}}
// 스트리머 구독을 통해 마이크 연결 청취자 상태를 감지합니다V2TIMManager.getInstance().subscribeUserStatus(userList, new V2TIMCallback() {@Overridepublic void onSuccess() {// 사용자 상태의 구독이 성공됩니다}@Overridepublic void onError(int code, String message) {// 사용자 상태의 구독이 실패됩니다}});// 스트리머가 마이크 해제 청취자 상태를 구독하는 것을 취소합니다V2TIMManager.getInstance().unsubscribeUserStatus(userList, new V2TIMCallback() {@Overridepublic void onSuccess() {// 사용자 상태의 구독을 취소하는 것이 성공됩니다}@Overridepublic void onError(int code, String message) {// 사용지 상태의 구독을 취소하는 것이 실패됩니다}});// 사용자 상태 변경의 알림 및 처리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) {오프라인 상태에서 마이크 끄기를 수행합니다kickSeat(getSeatIndexFromUserId(userId));}}}});

subscribeUserStatus 호출 시 오류가 발생합니다.https://trtc.tencentcloudapi.com/?Action=RemoveUser&SdkAppId=1400000001&RoomId=1234&UserIds.0=test1&UserIds.1=test2<공개 요청 매개변수>
onExitRoom() 콜백을 받으며 reason 값은 1입니다. 이때 해당 콜백에서 마이크 끄기, Chat 그룹 퇴장 등의 작업을 처리할 수 있습니다.// RTC Engine 방 퇴장 이벤트의 콜백@Overridepublic void onExitRoom(int reason) {if (reason == 0) {Log.d(TAG, "exitRoom을 호출하여 방에서 나가기");} else {// reason 1: 서버에 의해 현재 방에서 나가게 됩니다// reason 2: 현재 방이 완전히 해산됩니다Log.d(TAG, "서버에 의해 방에서 나가게 되거나 현재 방이 해산됩니다");// 마이크 끄기leaveSeat(seatIndex);// IM 그룹 나가기quitGroup(groupID, new V2TIMCallback() {});}}
https://xxxxxx/v4/group_open_http_svc/destroy_group?sdkappid=88888888&identifier=admin&usersig=xxx&random=99999999&contenttype=json
onGroupDismissed() 콜백을 받게 됩니다. 이때 해당 콜백에서 RTC Engine 방 나가기 등의 작업을 처리할 수 있습니다.// 그룹 해산 콜백V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {@Overridepublic void onGroupDismissed(String groupID, V2TIMGroupMemberInfo opUser) {// RTC Engine 방 나가기mTRTCCloud.stopLocalAudio();mTRTCCloud.exitRoom();}});
exitRoom()을 호출하여 퇴장을 완료하면 RTC Engine 방이 자동으로 해산됩니다. 물론 서버 인터페이스 DismissRoom(정수형 방 번호) 또는 DismissRoomByStrRoomId(문자열 방 번호)를 호출하여 RTC Engine 방을 강제로 해산할 수도 있습니다.
V2TIMMessageListGetOption option = new V2TIMMessageListGetOption();option.setGetType(V2TIMMessageListGetOption.V2TIM_GET_CLOUD_OLDER_MSG); // 클라우드의 이전 메시지 가져오기option.setGetTimeBegin(1640966400); // 2022-01-01 00:00:00부터 시작option.setGetTimePeriod(1 * 24 * 60 * 60); // 하루 동안의 메시지 가져오기option.setCount(Integer.MAX_VALUE); // 시간 범위 내 모든 메시지 가져오기option.setGroupID(#you group id#); // 그룹 채팅 메시지 가져오기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) 콜백을 통해 해당 스트리머의 무음 상태를 해제합니다.private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onUserAudioAvailable(String userId, boolean available) {if (available) {// 해당 스트리머의 무음 상태를 해제합니다}}}
V2TIMManager.getGroupManager().getGroupAttributes(groupID, null, new V2TIMValueCallback<Map<String, String>>() {@Overridepublic void onError(int i, String s) {// 그룹 속성 가져오기가 실패됩니다}@Overridepublic void onSuccess(Map<String, String> attrMap) {// 그룹 속성 가져오기가 성공되며 스트리머 무음 상태를 저장하는 키를 muteStatus로 가정합니다String muteStatus = attrMap.get("muteStatus");// muteStatus를 분석하여 각 마이크를 사용하는 스트리머의 무음 상태를 가져옵니다}});
mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeVOIP);
BLUETOOTH 권한이 구성되어야 하며, Android 12및 그의 이상 시스템은 최소한 BLUETOOTH_CONNECT 권한이 구성되어야 하고 코드에서 동적으로 권한을 신청해야 합니다.<!--일반 권한: 기본 블루투스 연결 권한--><uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/><!--일반 권한: 블루투스 관리, 스캔 권한--><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /><!--런타임 권한: Android 12 블루투스 권한 블루투스 장치 검색--><uses-permission android:name="android.permission.BLUETOOTH_SCAN" /><!--런타임 권한: Android 12 블루투스 권한 현재 장치를 다른 블루투스 장치에서 감지할 수 있도록 합니다--><uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /><!--런타임 권한: Android 12 블루투스 권한 페어링된 블루투스 장치와 통신 또는 현재 휴대폰 블루투스 켜짐 여부를 확인합니다--><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
private List<String> permissionList = new ArrayList<>();protected void initPermission() {// Android SDK 버전을 판단하고 Android 12 이상으로 되어야 합니다.if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {// 필요한 경우 동적으로 요청할 권한을 추가합니다permissionList.add(Manifest.permission.BLUETOOTH_SCAN);permissionList.add(Manifest.permission.BLUETOOTH_ADVERTISE);permissionList.add(Manifest.permission.BLUETOOTH_CONNECT);}if (permissionList.size() != 0) {// 동적으로 권한 요청ActivityCompat.requestPermissions(this, permissionList.toArray(new String[0]), REQ_PERMISSION_CODE);}}
startPlayMusic를 사용하여 배경 음악을 재생할 때, 음악 리소스 경로 매개변수 path에 Android 개발의 assets/raw 등 애플리케이션 리소스 파일을 저장하는 디렉터리의 파일 경로를 전달할 수 없습니다. 이러한 디렉터리의 파일은 APK로 패키징되어 설치 후 휴대폰 파일 시스템에 압축 해제되지 않기 때문입니다. 현재는 네트워크 리소스 URL, Android 장치 외부 저장소 및 애플리케이션 개인 디렉터리에 있는 리소스 파일의 절대 경로만 전달할 수 있습니다.public static void copyAssetsToFile(Context context, String name) {// 애플리케이션 자체 디렉터리의 files 디렉터리String savePath = ContextCompat.getExternalFilesDirs(context, null)[0].getAbsolutePath();// 애플리케이션 자체 디렉터리의 cache 디렉터리// String savePath = getApplication().getExternalCacheDir().getAbsolutePath();// 애플리케이션의 개인 저장 디렉터리의 files 디렉터리// String savePath = getApplication().getFilesDir().getAbsolutePath();String filename = savePath + "/" + name;File dir = new File(savePath);// 디렉터리가 존재하지 않으면 이 디렉터리를 생성하세요.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".) 이 속성은 targetSdkVersion이 29(Android 10)인 애플리케이션에서만 유효하며 더 높은 버전의 targetSdkVersion을 가진 애플리케이션의 경우 여전히 애플리케이션의 개인 또는 외부 저장 경로를 사용하는 것을 권장합니다.MANAGE_EXTERNAL_STORAGE 권한을 신청해야 합니다.<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);}
피드백