Android
혼합 스트리밍의 전환 및 푸시백 업무 프로세스
본 섹션에서는 음성 채팅방의 몇 가지 일반적인 업무 프로세스를 요약하여 전체 시나리오 구현 프로세스를 더 잘 이해할 수 있도록 도와줍니다.
아래 그림은 방 생성, 입장, 퇴장, 해산 등의 구현 프로세스를 포함한 방 관리 프로세스를 보여줍니다.


아래 그림은 방주인이 마이크를 관리하는 프로세스를 보여줍니다. 마이크 사용 초청, 마이크 사용 강제 끄기, 마이크 금언등의 구현 프로세스를 포함합니다.


아래 그림은 청취자가 마이크를 관리하는 프로세스를 보여줍니다. 마이크 자동 사용, 마이크 자동 끄기, 마이크 순위 이동 등의 구현 프로세스를 포함합니다.


접수 준비
단계1: 서비스 개통
1. 먼저 콘솔에 로그인하여 애플리케이션을 생성해야 합니다. RTC Engine 애플리케이션을 생성한 후 Chat 애플리케이션을 생성하세요.


설명:
두 개의 애플리케이션을 생성하고 각각 테스트 환경과 프로덕션 환경에 적용할 것을 권장하며 1년 동안 각 계정(UIN)당 매월 10,000분의 무료 사용 시간이 제공합니다.
RTC Engine 월정액 요금제는 체험판(기본), 라이트, 스탠다드, 프로페셔널로 구분되며, 다양한 부가 기능 서비스를 사용할 수 있습니다. 자세한 내용은 버전 기능 및 월정액 요금제 설명을 참조하세요.
2. 애플리케이션 생성이 완료되면 애플리케이션 관리-애플리케이션 개요 항목에서 해당 애플리케이션의 기본 정보를 확인할 수 있습니다. 이후 사용을 위해 SDKAppID와 SDKSecretKey를 안전하게 보관해야 하며 키 유출로 인한 트래픽 도용되지 않도록 주의해야 합니다.


단계2: SDK 임포트하기
RTC Engine SDK 와 Chat SDK는 mavenCentral 저장소에 릴리스되었으며 gradle을 구성하여 자동 다운로드 및 업데이트가 가능합니다.
1. dependencies에 적합한 버전의 SDK 종속성을 추가합니다.
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'}
설명:
추천하는 자동 로드 방식 외에도 SDK를 다운로드하고 수동으로 임포트할 수도 있습니다. 자세한 내용은 RTC Engine SDK 수동 통합 및 Chat SDK 수동 통합을 참조하세요.
Quic 플러그인은 axp-Quic 멀티플렉싱 전송 프로토콜을 제공하며 약한 네트워크 환경에서도 더 우수한 성능을 발휘합니다. 네트워크 패킷 손실률이 70%에 달하는 조건에서도 서비스를 제공할 수 있습니다. 이 기능은 프로페셔널, 프로페셔널 플러스 및 엔터프라이즈 버전 사용자에게만 제공되며, 프로페셔널, 프로페셔널 플러스 또는 엔터프라이즈 버전을 구매한 후 사용할 수 있습니다. 기능의 정상적인 사용을 위해 터미널 SDK를 7.7.5282 및 그의 이상 버전으로 업데이트하세요.
2. defaultConfig에서 앱이 사용하는 CPU 아키텍처를 지정합니다.
defaultConfig {ndk {abiFilters "armeabi-v7a", "arm64-v8a"}}
설명:
RTC Engine SDK는 armeabi/armeabi-v7a/arm64-v8a 아키텍처를 지원하며 에뮬레이터 전용의 x86/x86_64 아키텍처도 지원합니다.
Chat SDK는 armeabi-v7a/arm64-v8a/x86/x86_64 아키텍처를 지원하며 설치 패키지 크기를 줄이기 위해 일부 아키텍처의 SO 파일만 패키징할 수 있습니다.
단계 3: 엔지니어링 구성
1. AndroidManifest.xml에서 앱 권한을 구성하며 음성 채팅 시나리오에서 RTC Engine SDK 및 Chat SDK에는 다음 권한이 필요합니다.
<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" />
주의:
RTC Engine SDK에는 내장된 권한 요청 로직이 없으므로 해당 권한과 기능을 직접 선언해야 하며 일부 권한(예: 저장, 녹음 등)은 운전 시 동적으로 요청해야 합니다.
Android 프로젝트의
targetSdkVersion이 31이거나 대상 기기가 Android 12및 그 이상 시스템 버전과 관련된 경우, 블루투스 기능을 정상적으로 사용하기 위해 코드에서 android.permission.BLUETOOTH_CONNECT 권한을 동적으로 신청해야 합니다. 자세한 내용은 블루투스 권한를 참조하십시오.2. SDK 내부에서 Java의 리플렉션 기능을 사용하기 때문에 proguard-rules.pro 파일에서 RTC Engine SDK 관련 클래스를 난독화 제외 목록에 추가해야 합니다.
-keep class com.tencent.** { *; }
접속 과정
단계1: 인증 자격 증명의 생성
UserSig는 텐센트 실시간 통신 서비스에서 설계한 보안 서명이고 악의적인 공격자가 클라우드 서비스 사용 권한을 도용하는 것을 방지하기 위한 목적입니다. RTC Engine 및 Chat 서비스는 모두 이 보안 메커니즘을 채택하고 있으며 RTC Engine은 방 입장 시 인증을 수행하고 Chat은 로그인 시 인증을 수행합니다.
디버깅 및 테스트 단계: 클라이언트 예시 코드와 콘솔에서 가져오기 두 가지 방법으로 UserSig를 계산 및 생성할 수 있으며, 디버깅 및 테스트 용도로만 사용됩니다.
정식 운영 단계: 클라이언트가 역공학으로 키가 유출되는 것을 방지하기 위해 보안 등급이 더 높은 서버 UserSig 계산 방식의 사용을 권장합니다.
구체적인 구현 프로세스는 다음과 같습니다.
1. 손님의 App은 SDK 초기화 함수를 호출하기 전에 먼저 서버에 UserSig를 요청해야 합니다.
2. 손님의 서버는 SDKAppID와 UserID에 따라 UserSig를 계산합니다.
3. 서버는 계산된 UserSig를 손님의 App에 반환합니다.
4. 손님의 App은 획득한 UserSig를 특정 API를 통해 SDK에 전달합니다.
5. SDK는 SDKAppID + UserID + UserSig를 텐센트 클라우드 서버에 제출하여 검증합니다.
6. 텐센트 클라우드는 UserSig를 검증하여 합법성을 확인합니다.
7. 검증이 통과되면 Chat SDK에 인스턴트 메시징 서비스를 제공하고 RTC Engine SDK에 실시간 음성/영상 서비스를 제공합니다.


주의:
디버깅 단계의 로컬 UserSig 계산 방식은 온라인 환경에 적용하는 것을 권장하지 않으며, 역공학으로 인해 키가 유출될 수 있기 때문입니다.
여러 언어 버전(Java/Go/PHP/Node.js/Python/C#/C++)의 UserSig 서버 계산원 코드를 제공하며 자세한 내용은 서버에서 UserSig 계산을 참조하세요.
단계 2: 초기화 및 리스닝
시퀀스 다이어그램

1. Chat SDK 초기화 및 이벤트 리스너의 추가
// 이벤트 리스너의 추가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();
설명:
앱의 라이프사이클이 SDK 라이프사이클과 일치하는 경우, 앱을 종료하기 전에 초기화 해제를 수행하지 않아도 됩니다. 특정 화면에 진입한 후에만 SDK를 초기화하고 화면을 나간 후 더 이상 사용하지 않는 경우, SDK를 초기화 해제할 수 있습니다.
2. RTC Engine SDK 인스턴스의 생성 및 이벤트 리스너의 설정
// 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();
설명:
단계3: 로그인 및 로그아웃
Chat SDK 초기화 후, 계정 인증 및 기능 사용 권한을 얻기 위해 SDK 로그인 인터페이스를 호출해야 합니다. 따라서 다른 기능을 사용하기 전에 반드시 로그인이 성공했는지 확인하세요. 그렇지 않으면 기능이 비정상적으로 작동하거나 사용할 수 없을 수 있습니다. RTC Engine 오디오/비디오 서비스만 필요한 경우 이 단계를 무시해도 됩니다.
시퀀스 다이어그램

1. 로그인.
// 로그인: 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);}});
2. 로그아웃
// 로그아웃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);}});
설명:
Chat SDK의 라이프사이클이 애플리케이션의 라이프사이클과 일치하는 경우, 애플리케이션을 종료하기 전에 로그아웃하지 않아도 됩니다. 특정 화면에 진입한 후에만 Chat SDK를 사용하고 화면을 나간 후에는 더 이상 사용하지 않는 경우, 로그아웃을 수행하고 Chat SDK를 초기화 해제할 수 있습니다.
단계4: 방 관리
시퀀스 다이어그램

1. 방 생성.
스트리머(방주인)는 방송을 시작할 때 방을 생성해야 하며 여기서 '방' 의 개념은 Chat의 '그룹'에 해당합니다. 이 예시는 클라이언트에서 채팅 그룹을 생성하는 방식만 보여주며 실제로는 서버에서 그룹 생성할 수도 있습니다
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입니다}});
주의:
음성 채팅방 시나리오에서 Chat 그룹을 생성하려면 라이브방송 그룹 유형
GROUP_TYPE_AVCHATROOM을 선택해야 합니다.RTC Engine에는 방 생성 API가 없으며 사용자가 입장하려는 방이 존재하지 않을 경우 백엔드에서 자동으로 방을 생성합니다.
2. 방 입장.
Chat 그룹 가입.
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) {// 누군가 그룹에 가입했습니다}});
RTC Engine 방에 가입합니다.
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");}}
주의:
RTC Engine 방 번호는 정수형
roomId와 문자열 형식 strRoomId로 나뉘며 두 유형의 방은 서로 통하지 않으므로 방 번호 유형을 통일하는 것이 좋습니다.음성 채팅 인터랙티브 시나리오에서 방에 입장 시 사용자 역할(스트리머/시청자)을 지정해야 하며 스트리머만 스트리밍 권한이 있습니다. 지정하지 않으면 기본적으로 스트리머 역할로 설정됩니다.
음성 채팅 인터랙티브 방 입장 시나리오에서는
TRTC_APP_SCENE_VOICE_CHATROOM을 선택하는 것을 권장합니다3. 방에서 나가기
Chat 그룹 나가기.
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) {// 그룹 멤버 나가기의 콜백}});
주의:
라이브 방송 그룹(AVChatRoom)에서는 그룹 주인이 그룹에서 나갈 수 없으며
dismissGroup을 호출하여 그룹을 해산할 수만 있습니다.RTC Engine 방에서 나가기.
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, "현재 방 전체가 해체됩니다");}}
주의:
SDK가 점유한 모든 리소스가 릴리스된 후, SDK는
onExitRoom 콜백을 통해 알려줍니다.enterRoom을 다시 호출하거나 다른 음성/영상 SDK로 전환하려면 onExitRoom 콜백이 발생한 후 관련 작업을 수행하십시오. 그렇지 않으면 카메라, 마이크 장치가 점유됨 등 다양한 문제가 발생할 수 있습니다.4. 방 해산하기.
Chat 그룹 해산.
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) {// 그룹 해산 콜백}});
RTC Engine 방 해산
서버 측 해산: RTC Engine은 서버 측 방 해산 API
DismissRoom(숫자 방 ID와 문자열 방 ID 구분)를 제공하며 이 인터페이스를 호출하여 방의 모든 사용자를 방에서 나가게 하고 방을 해산할 수 있습니다.클라이언트 측 해산: 각 클라이언트의 방 나가기
exitRoom 인터페이스를 통해 방 내 모든 스트리머와 청취자의 퇴장을 완료시키고 퇴장 후 RTC Engine 방 라이프사이클 규칙에 따라 방이 자동으로 해산됩니다. 자세한 내용은 방에서 나가기를 참조하세요.경고:
라이브 방송 작업이 종료되면 방 해산 API를 호출하여 방이 해산되도록 하여 청취자가 의외로 방에 들어가서 원치 않는 비용이 발생하는 것을 방지하는 것을 권장합니다.
단계5: 마이크 순위 관리
시퀀스 다이어그램

먼저, 마이크 순위 정보를 저장하기 위한 JavaBean을 생성할 수 있습니다.
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 + '''+ '}';}}
1. 자동으로 마이크 사용.
자동으로 마이크의 사용을 켜는 것은 방송실에 있는 청취자가 방주인 또는 관리자에게 마이크의 사용 켜기 신청을 보내고 동의한다는 시그널링을 받은 후 마이크를 켜서 사용하는 것을 의미합니다. 자유 마이크 모드인 경우 시그널링 요청 부분은 무시할 수 있습니다.
청취자가 마이크 사용 켜기 요청을 보냅니다.
// 청취자가 마이크 요청을 보내며 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);}});}
2. 마이크의 사용을 초대합니다.
스트리머가 마이크의 사용을 초대합니다(청취자의 동의 필요없이).직접 그룹 속성을 수정하여 저장된 마이크 순위 정보를 변경하며, 해당 청취자는 그룹 속성 변경 콜백을 수신한 후 userId 매칭에 성공하면 RTC Engine 역할을 전환하고 스트리밍을 시작할 수 있습니다. 마이크 사용 초대 모드인 경우, 자동으로 마이크를 켜기 구현 로직을 참조하고 시그널링의 발신자와 수신자를 바꾸기만 하면 됩니다.
// 로컬에 저장된 전체 마이크 순위 정보의 목록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 {// 로컬 마이크 순위 목록을 업데이트하고 로컬 마이크 순위 뷰 렌더링합니다}}}}});
3. 자동으로 마이크 끄기.
연결된 청취자는 그룹 속성을 수정하는 방식으로 마이크 순위 정보를 재설정할 수 있으며 다른 사용자는 그룹 속성 변경 콜백을 받고 로컬 마이크 정보가 업데이트됩니다.
// 로컬에 저장된 전체 마이크 순위 정보의 목록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();}});}
4. 강제로 마이크 끄기.
스트리머가 사용자의 마이크 사용을 끄게 합니다. 그룹 속성에 저장된 마이크 순위 정보를 직접 수정하여 저장하고 해당 연결된 청취자는 그룹 속성 변경 콜백을 받은 후 userId와 일치하면 RTC Engine 역할을 전환하고 스트리밍을 중지합니다.
// 로컬에 저장된 전체 마이크 순위 정보의 목록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 {// 로컬 마이크 순위 목록을 업데이트하고 로컬 마이크 순위 뷰 렌더링합니다}}}}});
5. 마이크 금언.
스트리머가 특정 마이크 순위를 금언/해제하고 그룹 속성에 저장된 마이크 순위 정보를 직접 수정하며 해당 연결된 청취자는 그룹 속성 변경 콜백을 수신한 후 userId 매칭에 성공하면 로컬 스트리밍을 일시 중지/재개합니다.
// 로컬에 저장된 전체 마이크 순위 정보의 목록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 {// 로컬 마이크 순위 목록을 업데이트하고 로컬 마이크 순위 뷰 렌더링합니다}}}}});
6. 마이크 순위 잠그기.
스트리머가 특정 마이크를 잠그거나 잠금 해제합니다. 그룹 속성에 저장된 마이크 순위 정보를 직접 수정하며 청취자는 그룹 속성 변경 콜백을 수신한 후 해당 마이크 뷰가 업데이트됩니다.
// 로컬에 저장된 전체 마이크 순위 정보의 목록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) {// 마이크 순위 잠그기}}}});
7. 마이크 순위의 이동.
마이크 사용중의 스트리머가 마이크 순위를 이동하면 그룹 속성에 저장된 소스 및 대상 마이크 정보를 각각 수정해야 하며 청취자는 그룹 속성 변경 콜백을 수신한 후 해당 마이크 뷰가 업데이트됩니다.
// 로컬에 저장된 전체 마이크 순위 정보의 목록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() {// 그룹 속성의 수정이 성공되며 마이크 이동이 성공됩니다}});}
단계6: 오디오 관리
시퀀스 다이어그램

1. 구독 모드.
RTC Engine SDK는 기본적으로 오디오 스트림을 자동으로 구독하는 로직을 사용하며, 사용자가 방에 입장하면 원격 사용자의 소리가 자동으로 재생됩니다. 수동으로 오디오 스트림을 구독해야 하는 경우,
muteRemoteAudio(userId, mute)을 추가로 호출하여 원격 사용자의 오디오 스트림을 구독하고 재생해야 합니다.// 자동 구독 모드(기본값)mTRTCCloud.setDefaultStreamRecvMode(true, true);// 수동 구독 모드(자체 정의)mTRTCCloud.setDefaultStreamRecvMode(false, false);
주의:
구독 모드 설정
setDefaultStreamRecvMode은 방 입장 enterRoom 전에 호출해야만 적용됩니다.2. 수집 및 게시.
// 로컬 오디오의 수집 및 게시를 활성화합니다mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT)// 로컬 오디오의 수집 및 게시를 중지합니다mTRTCCloud.stopLocalAudio();
설명:
startLocalAudio는 마이크 사용 권한을 요청하며, stopLocalAudio는 마이크 사용 권한을 릴리스합니다.3. 마이크 끄기와 켜기.
// 로컬 오디오 스트림 게시를 일시 중지합니다(마이크 끄기)mTRTCCloud.muteLocalAudio(true);// 로컬 오디오 스트림 게시를 재개합니다(마이크 켜기)mTRTCCloud.muteLocalAudio(false);// 특정 원격 사용자의 오디오 스트림 구독 및 재생을 일시 중지합니다mTRTCCloud.muteRemoteAudio(userId, true);// 특정 원격 사용자의 오디오 스트림 구독 및 재생을 재개합니다mTRTCCloud.muteRemoteAudio(userId, false);// 모든 원격 사용자의 오디오 스트림 구독 및 재생을 일시 중지합니다mTRTCCloud.muteAllRemoteAudio(true);// 모든 원격 사용자의 오디오 스트림 구독 및 재생을 재개합니다mTRTCCloud.muteAllRemoteAudio(false);
설명:
반면에,
muteLocalAudio는 소프트웨어 측면에서 데이터 스트림을 일시 중지하거나 허용하기만 하면 되므로 효율적이고 원활합니다.자주 마이크를 켜고 끄는 시나리오에 더 적합합니다.4.
음질 및 음량 유형
.음질의 설정
// 로컬 오디오 수집 및 게시 시 음질을 설정합니다mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);// 오디오 스트리밍 중 동적으로 음질 설정합니다mTRTCCloud.setAudioQuality(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
설명:
RTC Engine 사전 설정된 음질은 세 가지 등급(Speech/Default/Music)으로 나뉘며 각각 다른 오디오 매개변수에 해당합니다. 자세한 내용은 TRTCAudioQuality을 참조하세요.
음량 유형의 설정
RTC Engine 각 음질 등급에는 기본 음량 유형이 설정되어 있으며 음량 유형을 강제로 지정하려면 다음 인터페이스를 사용할 수 있습니다.
// 음량 유형의 설정mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeAuto);
설명:
RTC Engine 음량 유형은 세 가지 등급(VOIP/Auto/Media)으로 나뉘며 각각 다른 음량 채널에 해당합니다. 자세한 내용은 TRTCSystemVolumeType을 참조하세요.
오디오 라우팅의 설정
휴대폰과 같은 모바일 장치에는 일반적으로 스피커와 수화기 두 가지 재생 위치가 있습니다. 오디오 라우팅을 강제로 지정하려면 다음 인터페이스를 사용할 수 있습니다.
// 오디오 라우팅의 설정mTRTCCloud.setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
설명:
RTC Engine 오디오 라우팅은 두 가지 유형(Speaker/Earpiece)으로 나뉘며, 각각 다른 발음 위치에 해당합니다. 자세한 내용은 TRTCAudioRoute을 참조하세요.
고급 기능
탄막 메시지 인터랙티브
음성 채팅 라이브 방송에는 일반적으로 텍스트 형태의 탄막 메시지 인터랙티브가 있으며, 여기서는 Chat을 통해 그룹 채팅 일반 텍스트 메시지를 보내고 받는 방식으로 구현할 수 있습니다.
// 공개 탄막 메시지의 전송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);}});
볼륨 크기 콜백
RTC Engine 정해진 주기로 마이크에 연결된 스트리머의 음량 크기를 콜백할 수 있으며 일반적으로 음파나 음량을 표시하거나 발언 중인 스트리머를 알리는 데 사용됩니다.
// 볼륨 크기 콜백 활성화하고 방 입장 성공 후 즉시 활성화하는 것을 권장합니다// 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가 자신일 경우 로컬 마이크에서 수집된 볼륨 크기를 나타내고 다른 경우는 원격 사용자의 볼륨 크기를 나타냅니다.음악 및 음향 효과의 재생
배경 음악 및 음향 효과 재생은 음성 채팅방 시나리오에서 자주 사용되는 것입니다. 아래에서는 일반적으로 사용되는 배경 음악 관련 인터페이스의 사용 방법과 주의 사항에 대해 설명합니다.
1. 재생 시작/중지/일시 정지/재개.
// 배경 음악, 짧은 음향 효과 및 인간음성 이펙트를 설정하는 데 사용되는 관리 클래스 얻습니다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);
주의:
RTC Engine 여러 음악을 동시에 재생할 수 있으며 musicID로 고유하게 식별됩니다. 동시에 한 곡만 재생하려면 새 음악을 재생하기 전에 다른 음악을 중지하거나 동일한 musicID를 사용하여 다른 음악을 재생할 수 있습니다. 이 경우 SDK는 기존 음악을 먼저 중지한 후 새 음악을 재생합니다.
RTC Engine 로컬 및 네트워크 오디오 파일 재생을 지원하며
musicPath를 통해 로컬 절대 경로 또는 URL 주소로 전달할 수 있습니다. MP3/AAC/M4A/WAV 형식을 지원합니다.2. 음악 및 인간 음성 볼륨 비율을 조정합니다.
// 특정 백그라운드 음악의 로컬 재생 볼륨 크기를 설정합니다mTXAudioEffectManager.setMusicPlayoutVolume(musicID, volume);// 특정 백그라운드 음악의 원격 재생 볼륨 크기를 설정합니다mTXAudioEffectManager.setMusicPublishVolume(musicID, volume);// 모든 백그라운드 음악의 로컬 볼륨 및 원격 볼륨 크기를 설정합니다mTXAudioEffectManager.setAllMusicVolume(volume);// 인간 음성의 수집 음량 크기를 설정합니다mTXAudioEffectManager.setVoiceCaptureVolume(volume);
주의:
볼륨 값 volume의 정상 범위는 0-100이며 기본값은 60입니다. 최대 150까지 설정할 수 있지만 폭음 위험이 있습니다.
배경 음악이 인간 음성보다 큰 경우 음악 재생 볼륨을 적절히 낮추고 인간 음성의 수집 볼륨을 높일 수 있습니다.
마이크 끄지만 배경 음악 여전히 재생하기:
muteLocalAudio(true) 아니고 setVoiceCaptureVolume(0)를 사용하세요.3. 음악 재생 이벤트 콜백을 설정합니다.
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 콜백이 발생하지 않습니다}});
주의:
배경 음악 재생 전에 재생 이벤트 콜백을 설정하여 재생 진행 상황을 인지할 수 있도록 이 인터페이스를 사용하십시오.
MusicId를 재사용할 필요가 없는 경우 재생 완료 후
setMusicObserver(musicId, null)을 수행하여 Observer를 완전히 릴리스할 수 있습니다.4. 배경 음악 및 음향 효과를 반복 재생합니다
방안 1:
AudioMusicParam의 loopCount 매개변수를 사용하여 반복 재생 횟수를 설정합니다.값의 범위는 0부터 임의의 양의 정수입니다. 기본값은 0이고 0은 음악을 한 번 재생함을 의미하고, 1은 음악을 두 번 재생함을 의미하며 이와 같이 반복됩니다.
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);}
주의:
방안 1은 매번 반복 재생이 끝나도
onComplete 콜백이 트리거되지 않으며 설정된 반복 횟수가 모두 재생된 후에야 해당 콜백이 트리거됩니다.방안 2: "배경 음악이 이미 재생 완료됨" 이벤트 콜백
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);}}}
혼합 스트리밍 전환 및 푸시백
1. 혼합 스트리밍 푸시백 RTC Engine 방.
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);}
2. 이벤트 콜백 및 업데이트 작업을 중지합니다
작업 결과 이벤트의 콜백.
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은 성공을 나타내며 그 외의 값은 실패를 나타냅니다.}}
미디어 스트림 발행의 업데이트
이 인터페이스는 RTC Engine 서버에 명령을 보내고
startPublishMediaStream로 시작된 미디어 스트림을 업데이트합니다.// taskId: onStartPublishMediaStream 콜백의 작업 ID입니다// target: 예를 들어 발행된 CDN URL 추가 또는 삭제합니다// params: 생 측의 스트림 중단을 방지하기 위해 미디어 스트림 인코딩 출력 매개변수를 일치시키는 것을 권장합니다// config: 예를 들어 크로스 룸 PK와 같은 혼합 트랜스코딩에 참여하는 사용자 목록을 업데이트하는 것입니다mTRTCCloud.updatePublishMediaStream(taskId, target, trtcStreamEncoderParam, trtcStreamMixingConfig);
주의:
동일한 작업은 순수 오디오, 오디오/비디오, 순수 비디오 간의 전환을 지원하지 않습니다.
미디어 스트림 게시를 중지합니다
이 인터페이스는 RTC Engine 서버에 명령을 보내고
startPublishMediaStream로 시작된 미디어 스트림을 중지합니다.// taskId: onStartPublishMediaStream 콜백의 작업 ID입니다mTRTCCloud.stopPublishMediaStream(taskId);
주의:
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;}}}
고급 권한의 관리
RTC Engine 고급 권한 관리는 다양한 방에 대해 서로 다른 입장 권한을 설정할 수 있습니다.예를 들어 고급 VIP 방과 같은 경우. 또한 청취자가 마이크 사용 권한을 관리하는 데 사용할 수 있습니다.예를 들어 유령 마이크를 처리하는 경우.
단계3: 방 입장 검증 및 마이크 연결 검증 PrivateMapKey
입장 검증
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());
이상 처리
고장및 오류 처리
UserSig 관련
열거형 | 값 | 설명 |
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의 방에 입장했는지 확인하세요. |
장치 관련
장치 관련 오류를 감지할 수 있으며 관련 오류 발생 시 UI에서 사용자에게 알립니다.
열거형 | 값 | 설명 |
ERR_MIC_START_FAIL | -1302 | Windows 또는 Mac 장치에서 마이크 구성 프로그램(드라이버)이 비정상일 경우, 마이크 열 수가 없습니다. 장치를 비활성화한 후 다시 활성화하거나, 기기를 재시작하거나, 구성 프로그램을 업데이트하세요. |
ERR_SPEAKER_START_FAIL | -1321 | 스피커 열기 실패했습니다. 예를 들어 Windows 또는 Mac 장치에서 스피커 구성 프로그램(드라이버)이 이상이 있는 경우, 장치를 비활성화한 후 다시 활성화하거나 기기를 재시작하거나 구성 프로그램을 업데이트하세요. |
ERR_MIC_OCCUPY | -1319 | 마이크가 사용 중입니다. 예를 들어 모바일 장치에서 통화 중일 때 마이크를 열 수가 없습니다. |
비정상 종료 처리
1. 네트워크 끊김 감지 및 타임아웃 퇴장.
다음 콜백을 통해 Real-Time Communication Engine (RTC Engine)의 네트워크 연결 끊김 및 재연결 이벤트 알림을 모니터링할 수 있습니다.
onConnectionLost 콜백을 수신한 후 로컬 마이크 순위 UI에 네트워크 끊김 표시를 표시하여 사용자에게 알릴 수 있으며 동시에 로컬에서 타이머를 시작합니다. 설정된 시간의 임계값을 초과해도 onConnectionRecovery 콜백을 수신하지 못하면 네트워크가 계속 끊긴 상태입니다. 이때 로컬에서 마이크 종료 및 퇴장 절차를 시작하며 동시에 팝업으로 사용자에게 방을 나갔음을 알리고 페이지를 파기합니다. 네트워크가 90초(기본값) 이상 끊긴 경우 타임아웃 퇴장이 트리거되며, RTC Engine 서버는 해당 사용자를 방에서 강제 퇴장시킵니다. 해당 사용자가 스트리머 역할인 경우 방 내 다른 사용자는 onRemoteUserLeaveRoom 콜백을 수신합니다.private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onConnectionLost() {// SDK와 클라우드 간의 연결이 끊어졌습니다.}@Overridepublic void onTryToReconnect() {// SDK가 클라우드에 재연결을 시도 중입니다.}@Overridepublic void onConnectionRecovery() {// SDK가 클라우드와의 연결이 복구되었습니다.}}
2. 오프라인 상태에서 자동으로 마이크의 사용을 종료합니다.
Chat 사용자의 일반 상태는 온라인(ONLINE), 오프라인(OFFLINE), 미로그인(UNLOGGED)으로 구분되며 오프라인 상태는 일반적으로 사용자가 프로세스를 강제 종료하거나 네트워크가 비정상적으로 중단된 경우에 발생합니다. 스트리머 구독을 통해 마이크 연결된 청취자 상태를 모니터링하여 오프라인 상태의 마이크 연결 청취자를 감지하고 강제 퇴장시킬 수 있습니다.
// 스트리머 구독을 통해 마이크 연결 청취자 상태를 감지합니다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));}}}});

주의:
사용자 상태를 구독하는 것은 프로 버전 패키지로 업그레이드해야 합니다. 자세한 내용은 기본 서비스 상세 정보을 참조하세요.
사용자 상태를 구독하는 것은 사전에 Chat 콘솔에서 사용자 상태 조회 및 상태 변경 알림 구성을 활성화해야 합니다. 활성화되지 않은 경우
subscribeUserStatus 호출 시 오류가 발생합니다.서버에서 사용자 나가게 시키고 방을 해산합니다.
1. 서버에서 사용자 나가게 시킵니다.
먼저 RTC Engine 서버에서 사용자 나가게 시키는 인터페이스 RemoveUser(정수형 방 번호) 또는 RemoveUserByStrRoomId(문자열 방 번호)를 호출하여 대상 사용자를 RTC Engine 방에서 나가게 시킵니다. 입력 예시는 다음과 같습니다.
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() {});}}
2. 서버에서 방을 해산합니다.
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 방을 강제로 해산할 수도 있습니다.방 입장하여 라이브 방송의 이전 메시지를 확인합니다
AVChatRoom은 기본적으로 라이브 방송의 이전 메시지를 저장하지 않고 새 사용자가 라이브 방송에 입장한 후에는 입장 후 사용자가 보낸 메시지만 볼 수 있습니다. 새로 입장한 사용자의 체험을 최적화하기 위해 콘솔에서 라이브방송 그룹 사용자가 입장 전의 메시지를 구성할 수 있으며, 그림과 같습니다.

라이브방송 그룹 사용자가 입장 전의 메시지를 가져오는 것은 다른 그룹의 이전 메시지를 가져오는 것과 동일하며, 코드 예시는 하기와 같습니다.
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);}});
주의:
이 기능은 프리미엄 버전 사용자만 이용할 수 있으며, 그룹 생성 후 24시간 이내에 최대 20개의 이전의 메시지만 지원합니다.
방 입장 시 스트리머의 무음 상태를 감지합니다
방안 1: 입장 시 모든 스트리머를 기본적으로 무음상태로 설정한 후
onUserAudioAvailable(userId, true) 콜백을 통해 해당 스트리머의 무음 상태를 해제합니다.private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onUserAudioAvailable(String userId, boolean available) {if (available) {// 해당 스트리머의 무음 상태를 해제합니다}}}
방안 2: 스트리머의 뮤음 상태를 RTC Engine 그룹 속성에 저장하고 청취자가 방에 입장하면 전체 그룹 속성을 가져와서 마이크 사용중의 스트리머의 무음 상태를 파악합니다.
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를 분석하여 각 마이크를 사용하는 스트리머의 무음 상태를 가져옵니다}});
블루투스 헤드폰의 오디오 입력 및 출력 문제
휴대폰이 블루투스 헤드폰에 성공적으로 연결되었지만 RTC Engine 앱의 오디오 입력 또는 출력은 여전히 휴대폰 마이크 또는 스피커를 사용하고 있습니다.
1. 오디오 출력이 정상적이고 블루투스 헤드폰을 사용하는 데 오디오 입력만 여전히 휴대폰 마이크를 사용하는 경우, 볼륨 유형 설정을 확인하세요. 통화 볼륨 모드에서만 블루투스 헤드폰의 마이크를 통해 소리를 수집할 수 있습니다. 자세한 내용은 오디오 관리 - 음질 및 볼륨 유형을 참조하세요.
mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeVOIP);
2. 오디오의 입력 및 출력 모두 블루투스 헤드폰을 사용할 수 없는 경우, 앱 권한에서 블루투스 권한이 구성되었는지 확인하십시오. Android 장치의 경우 Android 12 미만 시스템은 최소한
BLUETOOTH 권한이 구성되어야 하며, Android 12및 그의 이상 시스템은 최소한 BLUETOOTH_CONNECT 권한이 구성되어야 하고 코드에서 동적으로 권한을 신청해야 합니다.AndroidManifest.xml에서 블루투스 권한을 구성하고 Android 12 이하 시스템과의 호환성을 위해 다음과 같이 선언하는 것을 권장합니다.
<!--일반 권한: 기본 블루투스 연결 권한--><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" />
Android 12및 그의 이상 시스템에 추가된 블루투스 세분화 권한에 대한 동적 요청 방법은 다음과 같습니다.
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);}}
음악 재생을 지원하는 리소스 경로 문제
RTC Engine SDK API
startPlayMusic를 사용하여 배경 음악을 재생할 때, 음악 리소스 경로 매개변수 path에 Android 개발의 assets/raw 등 애플리케이션 리소스 파일을 저장하는 디렉터리의 파일 경로를 전달할 수 없습니다. 이러한 디렉터리의 파일은 APK로 패키징되어 설치 후 휴대폰 파일 시스템에 압축 해제되지 않기 때문입니다. 현재는 네트워크 리소스 URL, Android 장치 외부 저장소 및 애플리케이션 개인 디렉터리에 있는 리소스 파일의 절대 경로만 전달할 수 있습니다.assets 디렉터리에 있는 리소스 파일을 미리서 복사해서 장치 외부 저장소 또는 애플리케이션 개인 디렉터리에 넣는 방법으로 이 문제를 해결할 수 있습니다. 예시 코드는 다음과 같습니다.
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();}}
애플리케이션 개인 저장 files 디렉터리 경로:
/data/user/0/<package_name>/files/<file_name>애플리케이션 외부 저장 files 디렉터리 경로:
/storage/emulated/0/Android/data/<package_name>/files/<file_name>애플리케이션 외부 저장 cache 디렉터리 경로:
/storage/emulated/0/Android/data/<package_name>/cache/<file_name>주의:
전달한 경로가 애플리케이션 자체의 특정 디렉터리가 아닌 다른 외부 저장 경로인 경우, Android 10 및 그의 이상 기기에서 리소스 액세스가 거부될 수 있습니다. 이는 Google이 도입한 새로운 저장소 관리 시스템인 분할 저장소 때문입니다. AndroidManifest.xml 파일의 <application> 태그 내에 다음 코드를 추가하여 일시적으로 이 문제를 회피할 수 있습니다(
android:requestLegacyExternalStorage="true".) 이 속성은 targetSdkVersion이 29(Android 10)인 애플리케이션에서만 유효하며 더 높은 버전의 targetSdkVersion을 가진 애플리케이션의 경우 여전히 애플리케이션의 개인 또는 외부 저장 경로를 사용하는 것을 권장합니다.RTC Engine SDK 11.5 미 그의 이상 버전의 Content Provider 컴포넌트의 Content URI를 사용하여 Android 기기의 로컬 음악 리소스를 재생합니다.
Android 11 및 HarmonyOS 3.0 이상 시스템에서 외부 저장 디렉터리의 리소스 파일에 액세스할 수 없는 경우
MANAGE_EXTERNAL_STORAGE 권한을 신청해야 합니다.먼저, 앱의 AndroidManifest 파일에 다음 항목을 추가해야 합니다.
<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);}