feat(ads): 라이브와 콘텐츠 상세 광고 지면을 추가한다
This commit is contained in:
@@ -28,6 +28,18 @@ import coil.transform.RoundedCornersTransformation
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.google.gson.Gson
|
||||
import com.orhanobut.logger.Logger
|
||||
import com.yandex.mobile.ads.banner.BannerAdSize
|
||||
import com.yandex.mobile.ads.common.AdError
|
||||
import com.yandex.mobile.ads.common.AdRequest
|
||||
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.audio_content.AudioContentPlayService
|
||||
import kr.co.vividnext.sodalive.audio_content.PurchaseOption
|
||||
@@ -60,6 +72,7 @@ import kr.co.vividnext.sodalive.mypage.recent.db.RecentContent
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
import org.koin.android.ext.android.inject
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@UnstableApi
|
||||
class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBinding>(
|
||||
@@ -88,6 +101,42 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
private lateinit var orderType: OrderType
|
||||
private lateinit var imm: InputMethodManager
|
||||
|
||||
private var audioContentPlayInterstitialAdLoader: InterstitialAdLoader? = null
|
||||
private var audioContentPlayInterstitialAd: InterstitialAd? = null
|
||||
private var pendingAudioContentPlayAction: (() -> Unit)? = null
|
||||
private var hasConsumedAudioContentPlayInterstitialAttempt = false
|
||||
private var isAudioContentPlaying = false
|
||||
private var isAudioContentInterstitialEligible = false
|
||||
private var audioContentStartPlaybackAction: (() -> Unit)? = null
|
||||
|
||||
private val audioContentPlayInterstitialAdLoadListener = object : InterstitialAdLoadListener {
|
||||
override fun onAdLoaded(interstitialAd: InterstitialAd) {
|
||||
clearAudioContentPlayInterstitialAd()
|
||||
audioContentPlayInterstitialAd = interstitialAd
|
||||
}
|
||||
|
||||
override fun onAdFailedToLoad(error: AdRequestError) {
|
||||
Logger.e("Audio content interstitial failed to load: ${error.description}")
|
||||
}
|
||||
}
|
||||
|
||||
private val audioContentPlayInterstitialAdEventListener = object : InterstitialAdEventListener {
|
||||
override fun onAdShown() = Unit
|
||||
|
||||
override fun onAdFailedToShow(adError: AdError) {
|
||||
Logger.e("Audio content interstitial failed to show: ${adError.description}")
|
||||
continuePendingAudioContentPlayAction()
|
||||
}
|
||||
|
||||
override fun onAdDismissed() {
|
||||
continuePendingAudioContentPlayAction()
|
||||
}
|
||||
|
||||
override fun onAdClicked() = Unit
|
||||
|
||||
override fun onAdImpression(impressionData: ImpressionData?) = Unit
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
@@ -105,6 +154,9 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
binding.rlPreviewAlert.visibility = View.GONE
|
||||
|
||||
audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
hasConsumedAudioContentPlayInterstitialAttempt = false
|
||||
releaseAudioContentPlayInterstitial()
|
||||
setupAudioContentPlayInterstitial()
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
@@ -315,9 +367,125 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
dialog.show(screenWidth - 26.7f.dpToPx().toInt())
|
||||
}
|
||||
|
||||
setupAudioContentDetailInlineBanner()
|
||||
setupAudioContentPlayInterstitial()
|
||||
setupBuyerList()
|
||||
}
|
||||
|
||||
private fun setupAudioContentDetailInlineBanner() {
|
||||
binding.yandexInlineBannerView.post {
|
||||
val density = resources.displayMetrics.density
|
||||
val adWidthPixels = binding.yandexInlineBannerView.width.takeIf { it > 0 } ?: screenWidth
|
||||
val adWidthDp = (adWidthPixels / density).roundToInt()
|
||||
val maxAdHeightDp = 90
|
||||
|
||||
binding.yandexInlineBannerView.apply {
|
||||
setAdUnitId(BuildConfig.YANDEX_INLINE_BANNER_AUDIO_CONTENT_DETAIL_AD_UNIT_ID)
|
||||
setAdSize(
|
||||
BannerAdSize.inlineSize(
|
||||
this@AudioContentDetailActivity,
|
||||
adWidthDp,
|
||||
maxAdHeightDp
|
||||
)
|
||||
)
|
||||
loadAd(AdRequest.Builder().build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAudioContentPlayInterstitial() {
|
||||
val adUnitId = BuildConfig.YANDEX_INTERSTITIAL_AUDIO_CONTENT_PLAY_AD_UNIT_ID
|
||||
if (adUnitId.isBlank()) {
|
||||
Logger.e("Audio content interstitial blocked: ad unit id is blank.")
|
||||
return
|
||||
}
|
||||
|
||||
audioContentPlayInterstitialAdLoader = InterstitialAdLoader(this).apply {
|
||||
setAdLoadListener(audioContentPlayInterstitialAdLoadListener)
|
||||
}
|
||||
audioContentPlayInterstitialAdLoader?.loadAd(
|
||||
AdRequestConfiguration.Builder(adUnitId).build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun playAudioContentWithInterstitialIfAvailable(playAction: () -> Unit) {
|
||||
if (hasConsumedAudioContentPlayInterstitialAttempt || isFinishing || isDestroyed) {
|
||||
playAction()
|
||||
return
|
||||
}
|
||||
|
||||
val interstitialAd = audioContentPlayInterstitialAd ?: run {
|
||||
playAction()
|
||||
return
|
||||
}
|
||||
|
||||
hasConsumedAudioContentPlayInterstitialAttempt = true
|
||||
pendingAudioContentPlayAction = playAction
|
||||
interstitialAd.setAdEventListener(audioContentPlayInterstitialAdEventListener)
|
||||
runCatching {
|
||||
interstitialAd.show(this)
|
||||
}.onFailure {
|
||||
Logger.e("Audio content interstitial failed to show: ${it.message}")
|
||||
continuePendingAudioContentPlayAction()
|
||||
}
|
||||
}
|
||||
|
||||
private fun continuePendingAudioContentPlayAction() {
|
||||
val playAction = pendingAudioContentPlayAction
|
||||
pendingAudioContentPlayAction = null
|
||||
clearAudioContentPlayInterstitialAd()
|
||||
if (isFinishing || isDestroyed) {
|
||||
return
|
||||
}
|
||||
playAction?.invoke()
|
||||
}
|
||||
|
||||
private fun clearAudioContentPlayInterstitialAd() {
|
||||
audioContentPlayInterstitialAd?.setAdEventListener(null)
|
||||
audioContentPlayInterstitialAd = null
|
||||
}
|
||||
|
||||
private fun releaseAudioContentPlayInterstitial() {
|
||||
audioContentPlayInterstitialAdLoader?.setAdLoadListener(null)
|
||||
audioContentPlayInterstitialAdLoader = null
|
||||
pendingAudioContentPlayAction = null
|
||||
clearAudioContentPlayInterstitialAd()
|
||||
}
|
||||
|
||||
private fun pauseAudioContentPlayback() {
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
action = AudioContentPlayService.MusicAction.PAUSE.name
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateAudioContentPlayOrPauseControls() {
|
||||
val startPlaybackAction = audioContentStartPlaybackAction ?: return
|
||||
|
||||
if (isAudioContentPlaying) {
|
||||
binding.ivPlayOrPause.visibility = View.VISIBLE
|
||||
binding.llPreview.visibility = View.GONE
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_pause)
|
||||
binding.ivPlayOrPause.setOnClickListener { pauseAudioContentPlayback() }
|
||||
binding.llPreview.setOnClickListener(null)
|
||||
return
|
||||
}
|
||||
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play)
|
||||
|
||||
val startPlaybackClickListener = View.OnClickListener {
|
||||
if (isAudioContentInterstitialEligible) {
|
||||
playAudioContentWithInterstitialIfAvailable(startPlaybackAction)
|
||||
} else {
|
||||
startPlaybackAction()
|
||||
}
|
||||
}
|
||||
|
||||
binding.ivPlayOrPause.setOnClickListener(startPlaybackClickListener)
|
||||
binding.llPreview.setOnClickListener(startPlaybackClickListener)
|
||||
}
|
||||
|
||||
private fun setupBuyerList() {
|
||||
val recyclerView = binding.rvBuyer
|
||||
contentBuyerAdapter = AudioContentBuyerAdapter()
|
||||
@@ -775,10 +943,13 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
R.string.screen_audio_content_detail_total_duration_format,
|
||||
response.duration
|
||||
)
|
||||
audioContentStartPlaybackAction = null
|
||||
isAudioContentInterstitialEligible = false
|
||||
|
||||
isAlertPreview = response.creator.creatorId != SharedPreferenceManager.userId &&
|
||||
!response.existOrdered &&
|
||||
response.price > 0
|
||||
isAudioContentInterstitialEligible = response.price <= 0 || isAlertPreview
|
||||
|
||||
if (
|
||||
response.creator.creatorId != SharedPreferenceManager.userId && !response.existOrdered &&
|
||||
@@ -798,7 +969,7 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
binding.ivPlayOrPause.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
val playClickAction = View.OnClickListener {
|
||||
val playAudioContentAction: () -> Unit = {
|
||||
startService(
|
||||
Intent(
|
||||
applicationContext,
|
||||
@@ -842,9 +1013,10 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
)
|
||||
}
|
||||
|
||||
audioContentStartPlaybackAction = playAudioContentAction
|
||||
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play)
|
||||
binding.ivPlayOrPause.setOnClickListener(playClickAction)
|
||||
binding.llPreview.setOnClickListener(playClickAction)
|
||||
updateAudioContentPlayOrPauseControls()
|
||||
|
||||
if (!isAlertPreview) {
|
||||
binding.ivSeekForward10.visibility = View.VISIBLE
|
||||
@@ -873,6 +1045,8 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
}
|
||||
}
|
||||
} else if (response.releaseDate == null) {
|
||||
audioContentStartPlaybackAction = null
|
||||
isAudioContentInterstitialEligible = false
|
||||
binding.llPreviewNo.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
@@ -1166,7 +1340,7 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
} else {
|
||||
contentOrder(audioContent, orderType)
|
||||
}
|
||||
},
|
||||
}
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
@@ -1193,6 +1367,12 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
}, 100)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
binding.yandexInlineBannerView.destroy()
|
||||
releaseAudioContentPlayInterstitial()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
inner class AudioContentReceiver : BroadcastReceiver() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
@@ -1222,12 +1402,12 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
viewModel.isLoading.value = isLoading == true
|
||||
|
||||
if (this@AudioContentDetailActivity.audioContentId == contentId) {
|
||||
isAudioContentPlaying = isPlaying == true
|
||||
runOnUiThread {
|
||||
if (changeUi != null && changeUi) {
|
||||
if (isPlaying != null && isPlaying) {
|
||||
binding.ivPlayOrPause.visibility = View.VISIBLE
|
||||
binding.llPreview.visibility = View.GONE
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_pause)
|
||||
} else {
|
||||
if (isAlertPreview) {
|
||||
binding.ivPlayOrPause.visibility = View.GONE
|
||||
@@ -1235,9 +1415,10 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
|
||||
} else {
|
||||
binding.ivPlayOrPause.visibility = View.VISIBLE
|
||||
binding.llPreview.visibility = View.GONE
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play)
|
||||
}
|
||||
}
|
||||
|
||||
updateAudioContentPlayOrPauseControls()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,12 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.yandex.mobile.ads.banner.BannerAdSize
|
||||
import com.yandex.mobile.ads.common.AdRequest
|
||||
import com.zhpan.bannerview.BaseBannerAdapter
|
||||
import com.zhpan.indicator.enums.IndicatorSlideMode
|
||||
import com.zhpan.indicator.enums.IndicatorStyle
|
||||
import kr.co.vividnext.sodalive.BuildConfig
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
@@ -131,6 +134,7 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding.yandexInlineBannerView.destroy()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@@ -158,9 +162,31 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
|
||||
setupRecommendChannel()
|
||||
setupLatestFinishedLiveChannel()
|
||||
setupLiveReplay()
|
||||
setupLiveTabInlineBanner()
|
||||
setupLiveReservation()
|
||||
}
|
||||
|
||||
private fun setupLiveTabInlineBanner() {
|
||||
binding.yandexInlineBannerView.post {
|
||||
val density = resources.displayMetrics.density
|
||||
val adWidthPixels = binding.yandexInlineBannerView.width.takeIf { it > 0 } ?: screenWidth
|
||||
val adWidthDp = (adWidthPixels / density).roundToInt()
|
||||
val maxAdHeightDp = 90
|
||||
|
||||
binding.yandexInlineBannerView.apply {
|
||||
setAdUnitId(BuildConfig.YANDEX_INLINE_BANNER_LIVE_TAB_AD_UNIT_ID)
|
||||
setAdSize(
|
||||
BannerAdSize.inlineSize(
|
||||
requireContext(),
|
||||
adWidthDp,
|
||||
maxAdHeightDp
|
||||
)
|
||||
)
|
||||
loadAd(AdRequest.Builder().build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderMakeLiveByRole(role: String) {
|
||||
if (role == MemberRole.CREATOR.name) {
|
||||
binding.llMakeLive.visibility = View.VISIBLE
|
||||
|
||||
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.live.room.detail
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -20,6 +19,9 @@ import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.yandex.mobile.ads.banner.BannerAdSize
|
||||
import com.yandex.mobile.ads.common.AdRequest
|
||||
import kr.co.vividnext.sodalive.BuildConfig
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
@@ -35,6 +37,7 @@ import org.koin.android.ext.android.inject
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import androidx.core.net.toUri
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class LiveRoomDetailFragment(
|
||||
private val roomId: Long,
|
||||
@@ -79,11 +82,39 @@ class LiveRoomDetailFragment(
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
setupAdapter()
|
||||
setupLiveRoomDetailInlineBanner()
|
||||
bindData()
|
||||
binding.ivClose.setOnClickListener { dismiss() }
|
||||
viewModel.getDetail(roomId) { dismiss() }
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding.yandexInlineBannerView.destroy()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupLiveRoomDetailInlineBanner() {
|
||||
binding.yandexInlineBannerView.post {
|
||||
val density = resources.displayMetrics.density
|
||||
val screenWidth = resources.displayMetrics.widthPixels
|
||||
val adWidthPixels = binding.yandexInlineBannerView.width.takeIf { it > 0 } ?: screenWidth
|
||||
val adWidthDp = (adWidthPixels / density).roundToInt()
|
||||
val maxAdHeightDp = 90
|
||||
|
||||
binding.yandexInlineBannerView.apply {
|
||||
setAdUnitId(BuildConfig.YANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_ID)
|
||||
setAdSize(
|
||||
BannerAdSize.inlineSize(
|
||||
requireContext(),
|
||||
adWidthDp,
|
||||
maxAdHeightDp
|
||||
)
|
||||
)
|
||||
loadAd(AdRequest.Builder().build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAdapter() {
|
||||
val recyclerView = binding.rvParticipate
|
||||
adapter = LiveRoomDetailAdapter {}
|
||||
@@ -384,7 +415,7 @@ class LiveRoomDetailFragment(
|
||||
viewModel.shareRoomLink(
|
||||
response.roomId,
|
||||
response.isPrivateRoom,
|
||||
response.password,
|
||||
response.password
|
||||
) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
|
||||
@@ -396,6 +396,14 @@
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.yandex.mobile.ads.banner.BannerAdView
|
||||
android:id="@+id/yandex_inline_banner_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxHeight="90dp"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="13.3dp" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -143,11 +143,19 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.yandex.mobile.ads.banner.BannerAdView
|
||||
android:id="@+id/yandex_inline_banner_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:maxHeight="90dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_replay_live"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -187,6 +187,14 @@
|
||||
app:drawableStartCompat="@drawable/ic_live_detail_bottom" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.yandex.mobile.ads.banner.BannerAdView
|
||||
android:id="@+id/yandex_inline_banner_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxHeight="90dp"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="13.3dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
|
||||
Reference in New Issue
Block a user