live-room(agora): rtm version 1.5.3 -> 2.2.6

This commit is contained in:
2025-10-29 01:55:30 +09:00
parent f08c481807
commit 1ca6d068d0
4 changed files with 864 additions and 655 deletions

View File

@@ -32,6 +32,33 @@ android {
includeInBundle = false
}
packaging {
// JNI(.so) 관련
jniLibs {
// pickFirsts: 충돌 시 첫 파일만 채택
pickFirsts += ["**/libaosl.so"]
}
// 일반 리소스(META-INF 등) 관련
resources {
// pickFirsts: 충돌 시 첫 파일만 채택
pickFirsts += [
"META-INF/LICENSE.txt",
"META-INF/NOTICE*"
]
// 자주 쓰는 제외/병합 예시
excludes += [
"META-INF/DEPENDENCIES",
"META-INF/AL2.0",
"META-INF/LGPL2.1"
]
merges += [
"META-INF/services/**"
]
}
}
defaultConfig {
applicationId "kr.co.vividnext.sodalive"
minSdk 23
@@ -168,8 +195,8 @@ dependencies {
implementation "io.github.bootpay:android:4.4.3"
// agora
implementation "io.agora.rtc:voice-sdk:4.6.0"
implementation 'io.agora.rtm:rtm-sdk:1.5.3'
implementation "io.agora.rtc:voice-sdk:4.2.6"
implementation 'io.agora:agora-rtm:2.2.6'
// Glide
implementation 'com.github.bumptech.glide:glide:5.0.5'

View File

@@ -6,28 +6,31 @@ import io.agora.rtc2.Constants
import io.agora.rtc2.IRtcEngineEventHandler
import io.agora.rtc2.RtcEngine
import io.agora.rtm.ErrorInfo
import io.agora.rtm.GetOnlineUsersOptions
import io.agora.rtm.GetOnlineUsersResult
import io.agora.rtm.PublishOptions
import io.agora.rtm.ResultCallback
import io.agora.rtm.RtmChannel
import io.agora.rtm.RtmChannelListener
import io.agora.rtm.RtmClient
import io.agora.rtm.RtmClientListener
import io.agora.rtm.SendMessageOptions
import io.agora.rtm.RtmConfig
import io.agora.rtm.RtmConstants
import io.agora.rtm.RtmEventListener
import io.agora.rtm.SubscribeOptions
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.live.room.LiveRoomRequestType
import kotlin.concurrent.thread
class Agora(
private val uid: Long,
private val context: Context,
private val rtcEventHandler: IRtcEngineEventHandler,
private val rtmClientListener: RtmClientListener
private val rtmEventListener: RtmEventListener
) {
// RTM client instance
private var rtmClient: RtmClient? = null
// 상태 플래그: RTM 로그인 완료 여부
private var rtmLoggedIn: Boolean = false
// RTM channel instance
private var rtmChannel: RtmChannel? = null
private var rtcEngine: RtcEngine? = null
// 상태 플래그: RTM 로그인 진행 중 여부
private var rtmLoginInProgress: Boolean = false
init {
initAgoraEngine()
@@ -42,19 +45,23 @@ class Agora(
}
}
fun deInitAgoraEngine() {
fun deInitAgoraEngine(rtmEventListener: RtmEventListener) {
deInitRtcEngine()
deInitRtmChannelAndClient()
deInitRtmClient(rtmEventListener)
}
// region RtcEngine
private var rtcEngine: RtcEngine? = null
@Throws(Exception::class)
private fun initRtcEngine() {
Logger.e("initRtcEngine")
rtcEngine = RtcEngine.create(
context,
BuildConfig.AGORA_APP_ID,
rtcEventHandler
)
Logger.e("initRtcEngine - rtcEngine: ${rtcEngine != null}")
rtcEngine!!.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING)
rtcEngine!!.setAudioProfile(
@@ -66,6 +73,15 @@ class Agora(
}
fun joinRtcChannel(uid: Int, rtcToken: String, channelName: String) {
val state = rtcEngine?.connectionState
val isDisconnected = state == null || state == Constants.CONNECTION_STATE_DISCONNECTED
if (!isDisconnected) {
Logger.e("joinRtcChannel - skip (state=$state)")
return
}
Logger.e("joinRtcChannel - proceed (state=$state) uid=$uid channel=$channelName")
rtcEngine!!.joinChannel(
rtcToken,
channelName,
@@ -90,6 +106,10 @@ class Agora(
return rtcEngine!!.connectionState
}
fun isRtmLoggedIn(): Boolean {
return rtmLoggedIn
}
fun deInitRtcEngine() {
if (rtcEngine != null) {
rtcEngine!!.leaveChannel()
@@ -103,64 +123,179 @@ class Agora(
// endregion
// region RtmClient
private var rtmClient: RtmClient? = null
private var roomChannelName: String? = null
@Throws(Exception::class)
private fun initRtmClient() {
rtmClient = RtmClient.createInstance(
context,
BuildConfig.AGORA_APP_ID,
rtmClientListener
)
val rtmConfig = RtmConfig.Builder(BuildConfig.AGORA_APP_ID, uid.toString())
.eventListener(rtmEventListener)
.build()
rtmClient = RtmClient.create(rtmConfig)
}
fun createRtmChannelAndLogin(
uid: String,
fun rtmLogin(
rtmToken: String,
channelName: String,
rtmChannelListener: RtmChannelListener,
rtmChannelJoinSuccess: () -> Unit,
rtmChannelJoinFail: () -> Unit
) {
rtmChannel = rtmClient!!.createChannel(channelName, rtmChannelListener)
rtmClient!!.login(
rtmToken,
uid,
object : ResultCallback<Void> {
override fun onSuccess(p0: Void?) {
rtmChannel!!.join(object : ResultCallback<Void> {
override fun onSuccess(p0: Void?) {
Logger.e("rtmChannel join - onSuccess")
rtmChannelJoinSuccess()
}
// 이미 RTM 로그인 및 구독이 완료된 경우 재호출 방지
if (rtmLoggedIn && roomChannelName == channelName) {
Logger.e("rtmLogin - already logged in and subscribed. skip")
return
}
// 로그인 시도 중이면 재호출 방지
if (rtmLoginInProgress) {
Logger.e("rtmLogin - already in progress. skip")
return
}
override fun onFailure(p0: ErrorInfo?) {
roomChannelName = channelName
fun attemptLogin(attempt: Int) {
rtmClient!!.login(
rtmToken,
object : ResultCallback<Void> {
override fun onSuccess(p0: Void?) {
Logger.e("rtmClient login - success (attempt=$attempt)")
// 로그인 성공 후 두 채널 구독 시도
subscribeChannel(rtmChannelJoinSuccess, rtmChannelJoinFail)
}
override fun onFailure(p0: ErrorInfo?) {
Logger.e("rtmClient login - fail (attempt=$attempt), ${p0?.errorReason}")
if (attempt < 4) {
attemptLogin(attempt + 1)
} else {
rtmLoginInProgress = false
rtmChannelJoinFail()
}
})
}
}
)
}
override fun onFailure(p0: ErrorInfo?) {
}
}
)
rtmLoginInProgress = true
attemptLogin(1)
}
fun inputChat(message: String) {
val rtmMessage = rtmClient!!.createMessage()
rtmMessage.text = message
private fun subscribeChannel(
rtmChannelJoinSuccess: () -> Unit,
rtmChannelJoinFail: () -> Unit
) {
val targetRoom = roomChannelName
if (targetRoom == null) {
Logger.e("subscribeChannel - roomChannelName is null")
rtmChannelJoinFail()
return
}
rtmChannel!!.sendMessage(
rtmMessage,
object : ResultCallback<Void?> {
override fun onSuccess(p0: Void?) {
Logger.e("sendMessage - onSuccess")
}
var completed = false
var roomSubscribed = false
var inboxSubscribed = false
override fun onFailure(p0: ErrorInfo) {
Logger.e("sendMessage fail - ${p0.errorCode}")
Logger.e("sendMessage fail - ${p0.errorDescription}")
}
fun completeSuccessIfReady() {
if (!completed && roomSubscribed && inboxSubscribed) {
completed = true
rtmLoggedIn = true
rtmLoginInProgress = false
Logger.e("RTM subscribe - both channels subscribed")
rtmChannelJoinSuccess()
}
)
}
fun failOnce(reason: String?) {
if (!completed) {
completed = true
Logger.e("RTM subscribe failed: $reason")
rtmChannelJoinFail()
}
}
fun subscribeRoom(attempt: Int) {
val channelOptions = SubscribeOptions()
channelOptions.withMessage = true
channelOptions.withPresence = true
Logger.e("RTM subscribe(room: $targetRoom) attempt=$attempt")
rtmClient!!.subscribe(
targetRoom,
channelOptions,
object : ResultCallback<Void> {
override fun onSuccess(responseInfo: Void?) {
Logger.e("RTM subscribe(room) success at attempt=$attempt")
roomSubscribed = true
completeSuccessIfReady()
}
override fun onFailure(errorInfo: ErrorInfo?) {
Logger.e("RTM subscribe(room) failure at attempt=$attempt reason=${errorInfo?.errorReason}")
if (attempt < 4) {
subscribeRoom(attempt + 1)
} else {
failOnce("room subscribe failed after 3 retries (4 attempts)")
}
}
}
)
}
fun subscribeInbox(attempt: Int) {
val inboxChannel = "inbox_$uid"
val inboxChannelOptions = SubscribeOptions()
inboxChannelOptions.withMessage = true
Logger.e("RTM subscribe(inbox: $inboxChannel) attempt=$attempt")
rtmClient!!.subscribe(
inboxChannel,
inboxChannelOptions,
object : ResultCallback<Void> {
override fun onSuccess(responseInfo: Void?) {
Logger.e("RTM subscribe(inbox) success at attempt=$attempt")
inboxSubscribed = true
completeSuccessIfReady()
}
override fun onFailure(errorInfo: ErrorInfo?) {
Logger.e("RTM subscribe(inbox) failure at attempt=$attempt reason=${errorInfo?.errorReason}")
if (attempt < 4) {
subscribeInbox(attempt + 1)
} else {
failOnce("inbox subscribe failed after 3 retries (4 attempts)")
}
}
}
)
}
// 두 채널 구독을 병렬로 시도
subscribeRoom(1)
subscribeInbox(1)
}
fun inputChat(message: String, onFailure: () -> Unit) {
if (roomChannelName != null) {
val options = PublishOptions()
options.setChannelType(RtmConstants.RtmChannelType.MESSAGE)
rtmClient!!.publish(
roomChannelName!!,
message,
options,
object : ResultCallback<Void> {
override fun onSuccess(p0: Void?) {
Logger.e("sendMessage - onSuccess")
}
override fun onFailure(p0: ErrorInfo) {
Logger.e("sendMessage fail - ${p0.errorCode}")
Logger.e("sendMessage fail - ${p0.errorReason}")
}
}
)
} else {
Logger.e("inputChat - roomChannelName is null")
onFailure()
}
}
fun sendRawMessageToGroup(
@@ -168,23 +303,30 @@ class Agora(
onSuccess: (() -> Unit)? = null,
onFailure: (() -> Unit)? = null
) {
val message = rtmClient!!.createMessage()
message.rawMessage = rawMessage
rtmChannel!!.sendMessage(
message,
object : ResultCallback<Void?> {
override fun onSuccess(p0: Void?) {
Logger.e("sendMessage - onSuccess")
onSuccess?.invoke()
}
if (roomChannelName != null) {
val options = PublishOptions()
options.customType = "ByteArray"
rtmClient!!.publish(
roomChannelName!!,
rawMessage,
options,
object : ResultCallback<Void> {
override fun onSuccess(p0: Void?) {
Logger.e("sendMessage - onSuccess")
onSuccess?.invoke()
}
override fun onFailure(p0: ErrorInfo) {
Logger.e("sendMessage fail - ${p0.errorCode}")
Logger.e("sendMessage fail - ${p0.errorDescription}")
onFailure?.invoke()
override fun onFailure(p0: ErrorInfo) {
Logger.e("sendMessage fail - ${p0.errorCode}")
Logger.e("sendMessage fail - ${p0.errorReason}")
onFailure?.invoke()
}
}
}
)
)
} else {
Logger.e("inputChat - roomChannelName is null")
onFailure?.invoke()
}
}
fun sendRawMessageToPeer(
@@ -193,34 +335,71 @@ class Agora(
rawMessage: ByteArray? = null,
onSuccess: () -> Unit
) {
val option = SendMessageOptions()
if (roomChannelName != null) {
val message = rawMessage ?: requestType.toString().toByteArray()
val options = PublishOptions()
options.customType = "ByteArray"
rtmClient!!.publish(
"inbox_$receiverUid",
message,
options,
object : ResultCallback<Void> {
override fun onSuccess(p0: Void?) {
Logger.e("sendMessage - onSuccess")
onSuccess()
}
val message = rtmClient!!.createMessage()
message.rawMessage = rawMessage ?: requestType.toString().toByteArray()
override fun onFailure(p0: ErrorInfo) {
Logger.e("sendMessage fail - ${p0.errorCode}")
Logger.e("sendMessage fail - ${p0.errorReason}")
}
}
)
} else {
Logger.e("inputChat - roomChannelName is null")
}
}
rtmClient!!.sendMessageToPeer(
receiverUid,
message,
option,
object : ResultCallback<Void?> {
override fun onSuccess(aVoid: Void?) {
onSuccess()
fun deInitRtmClient(rtmEventListener: RtmEventListener) {
rtmClient?.removeEventListener(rtmEventListener)
rtmClient?.unsubscribe(roomChannelName, object : ResultCallback<Void> {
override fun onSuccess(responseInfo: Void?) {
Logger.e("RTM unsubscribe - $roomChannelName")
roomChannelName = null
}
override fun onFailure(errorInfo: ErrorInfo) {
Logger.e("RTM unsubscribe fail - ${errorInfo.errorCode}")
Logger.e("RTM unsubscribe fail - ${errorInfo.errorReason}")
}
})
rtmClient?.unsubscribe(
"inbox_${SharedPreferenceManager.userId}",
object : ResultCallback<Void> {
override fun onSuccess(responseInfo: Void?) {
Logger.e("RTM unsubscribe - inbox_${SharedPreferenceManager.userId}")
}
override fun onFailure(errorInfo: ErrorInfo) {
Logger.e("RTM unsubscribe fail - ${errorInfo.errorCode}")
Logger.e("RTM unsubscribe fail - ${errorInfo.errorReason}")
}
})
rtmClient?.logout(object : ResultCallback<Void> {
override fun onSuccess(responseInfo: Void?) {
Logger.e("RTM logout")
rtmClient = null
}
)
}
fun rtmChannelIsNull(): Boolean {
return rtmChannel == null
}
override fun onFailure(errorInfo: ErrorInfo) {
Logger.e("RTM logout fail - ${errorInfo.errorCode}")
Logger.e("RTM logout fail - ${errorInfo.errorReason}")
}
})
// 상태 리셋
rtmLoggedIn = false
rtmLoginInProgress = false
fun deInitRtmChannelAndClient() {
rtmChannel?.leave(null)
rtmChannel?.release()
rtmClient?.logout(null)
}
// endregion
}

View File

@@ -86,7 +86,7 @@
android:gravity="center"
android:paddingVertical="16dp"
android:text="취소"
android:textColor="@color/color_9970ff"
android:textColor="@color/color_3bb9f1"
android:textSize="18.3sp" />
<TextView