feat(live-room): 무료방 입장 전면 광고를 추가한다

This commit is contained in:
2026-04-21 14:11:24 +09:00
parent 30b3dcdce6
commit f6a94e0f7c
3 changed files with 104 additions and 0 deletions

View File

@@ -75,6 +75,7 @@ android {
// release용 ad unit id는 배포 전 실제 값으로 교체한다. // release용 ad unit id는 배포 전 실제 값으로 교체한다.
buildConfigField 'String', 'YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID', '"R-M-19140295-1"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID', '"R-M-19140295-1"'
buildConfigField 'String', 'YANDEX_INTERSTITIAL_LIVE_ROOM_AD_UNIT_ID', '"R-M-19140295-2"'
buildConfigField 'String', 'BASE_URL', '"https://api.sodalive.net"' buildConfigField 'String', 'BASE_URL', '"https://api.sodalive.net"'
buildConfigField 'String', 'AGORA_API_BASE_URL', '"https://api.agora.io/api/speech-to-speech-translation/v2/"' buildConfigField 'String', 'AGORA_API_BASE_URL', '"https://api.agora.io/api/speech-to-speech-translation/v2/"'
buildConfigField 'String', 'AGORA_CUSTOMER_ID', '"de5dd9ea151f4a43ba1ad8411817b169"' buildConfigField 'String', 'AGORA_CUSTOMER_ID', '"de5dd9ea151f4a43ba1ad8411817b169"'
@@ -106,6 +107,7 @@ android {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID', '"R-M-19140297-1"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID', '"R-M-19140297-1"'
buildConfigField 'String', 'YANDEX_INTERSTITIAL_LIVE_ROOM_AD_UNIT_ID', '"R-M-19140297-2"'
buildConfigField 'String', 'BASE_URL', '"https://test-api.sodalive.net"' buildConfigField 'String', 'BASE_URL', '"https://test-api.sodalive.net"'
buildConfigField 'String', 'AGORA_API_BASE_URL', '"https://api.agora.io/api/speech-to-speech-translation/v2/"' buildConfigField 'String', 'AGORA_API_BASE_URL', '"https://api.agora.io/api/speech-to-speech-translation/v2/"'
buildConfigField 'String', 'AGORA_CUSTOMER_ID', '"de5dd9ea151f4a43ba1ad8411817b169"' buildConfigField 'String', 'AGORA_CUSTOMER_ID', '"de5dd9ea151f4a43ba1ad8411817b169"'

View File

@@ -82,6 +82,15 @@ import io.agora.rtm.RtmEventListener
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import com.yandex.mobile.ads.common.AdError
import com.yandex.mobile.ads.common.AdRequestConfiguration
import com.yandex.mobile.ads.common.AdRequestError
import com.yandex.mobile.ads.common.ImpressionData
import com.yandex.mobile.ads.interstitial.InterstitialAd
import com.yandex.mobile.ads.interstitial.InterstitialAdEventListener
import com.yandex.mobile.ads.interstitial.InterstitialAdLoadListener
import com.yandex.mobile.ads.interstitial.InterstitialAdLoader
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.agora.Agora import kr.co.vividnext.sodalive.agora.Agora
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
@@ -172,6 +181,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private var isSpeaker = false private var isSpeaker = false
private var hasKnownHostAbsence = false private var hasKnownHostAbsence = false
private var isFreeRoomEntryInterstitialEligible = false
private var hasRequestedFreeRoomEntryInterstitialLoad = false
private var hasConsumedFreeRoomEntryInterstitialAttempt = false
private var isLiveRoomJoinCompleted = false
private var freeRoomEntryInterstitialAdLoader: InterstitialAdLoader? = null
private var freeRoomEntryInterstitialAd: InterstitialAd? = null
private var isCapturePrivacyMuted = false private var isCapturePrivacyMuted = false
private var isScreenRecordingActive = false private var isScreenRecordingActive = false
@@ -216,6 +232,35 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val content: String val content: String
) )
private val freeRoomEntryInterstitialAdLoadListener = object : InterstitialAdLoadListener {
override fun onAdLoaded(interstitialAd: InterstitialAd) {
clearFreeRoomEntryInterstitialAd()
freeRoomEntryInterstitialAd = interstitialAd
maybeShowFreeRoomEntryInterstitial()
}
override fun onAdFailedToLoad(error: AdRequestError) {
Logger.e("Free room interstitial failed to load: ${error.description}")
}
}
private val freeRoomEntryInterstitialAdEventListener = object : InterstitialAdEventListener {
override fun onAdShown() = Unit
override fun onAdFailedToShow(adError: AdError) {
Logger.e("Free room interstitial failed to show: ${adError.description}")
clearFreeRoomEntryInterstitialAd()
}
override fun onAdDismissed() {
clearFreeRoomEntryInterstitialAd()
}
override fun onAdClicked() = Unit
override fun onAdImpression(impressionData: ImpressionData?) = Unit
}
// region 채팅 금지 // region 채팅 금지
private var isNoChatting = false private var isNoChatting = false
private var isChatFrozen = false private var isChatFrozen = false
@@ -454,6 +499,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
override fun onDestroy() { override fun onDestroy() {
// 액티비티 종료 전에 강제 음소거 상태를 원복한다. // 액티비티 종료 전에 강제 음소거 상태를 원복한다.
clearCapturePrivacyMuteState() clearCapturePrivacyMuteState()
releaseFreeRoomEntryInterstitial()
cropper.cleanup() cropper.cleanup()
hideKeyboard { hideKeyboard {
viewModel.quitRoom(roomId) { viewModel.quitRoom(roomId) {
@@ -1141,6 +1187,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
viewModel.roomInfoLiveData.observe(this) { response -> viewModel.roomInfoLiveData.observe(this) { response ->
syncFreeRoomEntryInterstitial(response)
updateV2vAvailability(response) updateV2vAvailability(response)
binding.ivShield.visibility = if (response.isAdult) { binding.ivShield.visibility = if (response.isAdult) {
View.VISIBLE View.VISIBLE
@@ -1510,6 +1557,58 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
} }
private fun syncFreeRoomEntryInterstitial(roomInfo: GetRoomInfoResponse) {
isFreeRoomEntryInterstitialEligible = roomInfo.isFreeRoom
if (!isFreeRoomEntryInterstitialEligible || hasRequestedFreeRoomEntryInterstitialLoad) {
return
}
val adUnitId = BuildConfig.YANDEX_INTERSTITIAL_LIVE_ROOM_AD_UNIT_ID
if (adUnitId.isBlank()) {
Logger.e("Free room interstitial blocked: ad unit id is blank.")
return
}
hasRequestedFreeRoomEntryInterstitialLoad = true
freeRoomEntryInterstitialAdLoader = InterstitialAdLoader(this).apply {
setAdLoadListener(freeRoomEntryInterstitialAdLoadListener)
}
freeRoomEntryInterstitialAdLoader?.loadAd(
AdRequestConfiguration.Builder(adUnitId).build()
)
}
private fun maybeShowFreeRoomEntryInterstitial() {
if (
!isFreeRoomEntryInterstitialEligible ||
!isLiveRoomJoinCompleted ||
hasConsumedFreeRoomEntryInterstitialAttempt ||
!isForeground ||
isFinishing ||
isDestroyed
) {
return
}
val interstitialAd = freeRoomEntryInterstitialAd ?: return
hasConsumedFreeRoomEntryInterstitialAttempt = true
interstitialAd.setAdEventListener(freeRoomEntryInterstitialAdEventListener)
interstitialAd.show(this)
}
private fun clearFreeRoomEntryInterstitialAd() {
freeRoomEntryInterstitialAd?.setAdEventListener(null)
freeRoomEntryInterstitialAd = null
}
private fun releaseFreeRoomEntryInterstitial() {
freeRoomEntryInterstitialAdLoader?.setAdLoadListener(null)
freeRoomEntryInterstitialAdLoader = null
clearFreeRoomEntryInterstitialAd()
}
private fun hideKeyboard(onAfterExecute: () -> Unit) { private fun hideKeyboard(onAfterExecute: () -> Unit) {
handler.postDelayed({ handler.postDelayed({
imm.hideSoftInputFromWindow( imm.hideSoftInputFromWindow(
@@ -2908,6 +3007,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
channelName = roomInfo.channelName, channelName = roomInfo.channelName,
rtmChannelJoinSuccess = { rtmChannelJoinSuccess = {
isRtmJoined = true isRtmJoined = true
isLiveRoomJoinCompleted = true
// 두 채널 모두 연결 시 키보드 트릭 후 dismiss, 아니면 즉시 dismiss // 두 채널 모두 연결 시 키보드 트릭 후 dismiss, 아니면 즉시 dismiss
if (!tryForceLayoutRefresh()) { if (!tryForceLayoutRefresh()) {
handler.post { loadingDialog.dismiss() } handler.post { loadingDialog.dismiss() }
@@ -2936,6 +3036,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
setHeartButtonPosition() setHeartButtonPosition()
startPeriodicPlaybackValidation() startPeriodicPlaybackValidation()
maybeShowFreeRoomEntryInterstitial()
}, },
rtmChannelJoinFail = { rtmChannelJoinFail = {
agoraConnectFail() agoraConnectFail()

View File

@@ -29,6 +29,7 @@ data class GetRoomInfoResponse(
@SerializedName("isActiveRoulette") val isActiveRoulette: Boolean, @SerializedName("isActiveRoulette") val isActiveRoulette: Boolean,
@SerializedName("isCaptureRecordingAvailable") val isCaptureRecordingAvailable: Boolean = false, @SerializedName("isCaptureRecordingAvailable") val isCaptureRecordingAvailable: Boolean = false,
@SerializedName("isChatFrozen") val isChatFrozen: Boolean = false, @SerializedName("isChatFrozen") val isChatFrozen: Boolean = false,
@SerializedName("isFreeRoom") val isFreeRoom: Boolean,
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean, @SerializedName("isPrivateRoom") val isPrivateRoom: Boolean,
@SerializedName("password") val password: String? = null @SerializedName("password") val password: String? = null
) )