오디션 지원자 콘텐츠 재생/정지 기능 추가

This commit is contained in:
klaus 2025-01-03 11:30:42 +09:00
parent 92b201a6fa
commit b0a97ab941
5 changed files with 144 additions and 10 deletions

View File

@ -81,6 +81,7 @@ dependencies {
implementation "androidx.media:media:1.7.0"
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

View File

@ -12,10 +12,11 @@ import kr.co.vividnext.sodalive.databinding.ItemAuditionApplicantBinding
class AuditionApplicantListAdapter(
private val onClickVote: (Int) -> Unit,
private val onClickPlayOrPause: (Int) -> Unit
private val onClickPlayOrPause: (Int, Long, String) -> Unit
) : ListAdapter<GetAuditionRoleApplicantItem, AuditionApplicantListAdapter.ViewHolder>(DiffCallback()) {
private var currentPlayingIndex: Int = -1
private var isPlaying: Boolean = false
class DiffCallback : DiffUtil.ItemCallback<GetAuditionRoleApplicantItem>() {
override fun areItemsTheSame(
@ -29,16 +30,18 @@ class AuditionApplicantListAdapter(
oldItem: GetAuditionRoleApplicantItem,
newItem: GetAuditionRoleApplicantItem
): Boolean {
return oldItem == newItem && oldItem.voteCount == newItem.voteCount
return oldItem == newItem
}
}
inner class ViewHolder(
private val binding: ItemAuditionApplicantBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetAuditionRoleApplicantItem, position: Int) {
binding.ivVote.setOnClickListener { onClickVote(position) }
binding.ivPlayOrPause.setOnClickListener { onClickPlayOrPause(position) }
fun bind(item: GetAuditionRoleApplicantItem) {
binding.ivVote.setOnClickListener { onClickVote(bindingAdapterPosition) }
binding.ivPlayOrPause.setOnClickListener {
onClickPlayOrPause(bindingAdapterPosition, item.applicantId, item.voiceUrl)
}
binding.tvNickname.text = item.nickname
binding.tvCountVote.text = item.voteCount.toString()
@ -49,7 +52,7 @@ class AuditionApplicantListAdapter(
}
binding.ivPlayOrPause.setImageResource(
if (position == currentPlayingIndex) {
if (bindingAdapterPosition == currentPlayingIndex && isPlaying) {
R.drawable.ic_audition_pause
} else {
R.drawable.ic_audition_play
@ -67,10 +70,12 @@ class AuditionApplicantListAdapter(
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position), position)
holder.bind(getItem(position))
}
fun updatePlayingIndex(newIndex: Int) {
fun updatePlayingIndex(newIndex: Int, isPlaying: Boolean) {
this.isPlaying = isPlaying
val previousIndex = currentPlayingIndex
currentPlayingIndex = newIndex
notifyItemChanged(previousIndex)

View File

@ -0,0 +1,106 @@
package kr.co.vividnext.sodalive.audition.applicant
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.net.Uri
import android.widget.Toast
import com.orhanobut.logger.Logger
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
import java.io.IOException
class AuditionApplicantMediaPlayerManager(
private val context: Context,
private val updateUI: (Int, Boolean) -> Unit
) {
private var mediaPlayer: MediaPlayer? = null
private var currentPlayingApplicantId: Long = -1
private var currentPlayingPosition: Int = -1
fun pauseContent() {
mediaPlayer?.pause()
updateUI(currentPlayingPosition, false)
}
private fun resumeContent() {
pauseAudioContentService()
mediaPlayer?.start()
updateUI(currentPlayingPosition, true)
}
fun stopContent() {
mediaPlayer?.let {
it.stop()
it.release()
mediaPlayer = null
}
currentPlayingApplicantId = -1
currentPlayingPosition = -1
updateUI(currentPlayingPosition, false)
}
fun toggleContent(position: Int, applicantId: Long, voiceUrl: String) {
if (currentPlayingApplicantId == applicantId && currentPlayingPosition == position) {
if (mediaPlayer?.isPlaying == true) {
pauseContent()
} else {
resumeContent()
}
} else {
playContent(position, applicantId, voiceUrl)
}
}
private fun playContent(position: Int, applicantId: Long, voiceUrl: String) {
pauseAudioContentService()
stopContent()
currentPlayingPosition = position
currentPlayingApplicantId = applicantId
mediaPlayer = MediaPlayer().apply {
setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
)
try {
setDataSource(context, Uri.parse(voiceUrl))
prepareAsync() // 비동기적으로 준비
setOnPreparedListener {
start()
updateUI(currentPlayingPosition, true)
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(context, "콘텐츠를 재생하지 못했습니다.\n다시 시도해 주세요", Toast.LENGTH_SHORT).show()
}
setOnCompletionListener {
updateUI(currentPlayingPosition, false)
currentPlayingApplicantId = -1
}
}
}
private fun pauseAudioContentService() {
context.startService(
Intent(
context,
AudioContentPlayService::class.java
).apply {
action = AudioContentPlayService.MusicAction.PAUSE.name
}
)
context.startService(
Intent(context, AudioContentPlayerService::class.java).apply {
action = "STOP_SERVICE"
}
)
}
}

View File

@ -16,6 +16,7 @@ import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audition.applicant.ApplicationMethodDialog
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantListAdapter
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantMediaPlayerManager
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplyDialogFragment
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
@ -72,6 +73,8 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
}
}
private lateinit var mediaPlayerManager: AuditionApplicantMediaPlayerManager
override fun onCreate(savedInstanceState: Bundle?) {
auditionRoleId = intent.getLongExtra(Constants.EXTRA_AUDITION_ROLE_ID, 0)
@ -87,10 +90,26 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
super.onCreate(savedInstanceState)
mediaPlayerManager = AuditionApplicantMediaPlayerManager(
this
) { position, isPlaying ->
adapter.updatePlayingIndex(position, isPlaying)
}
bindData()
viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId)
}
override fun onPause() {
mediaPlayerManager.pauseContent()
super.onPause()
}
override fun onDestroy() {
mediaPlayerManager.stopContent()
super.onDestroy()
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.setOnClickListener { finish() }
@ -133,15 +152,19 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
}
binding.tvSortNewest.setOnClickListener {
mediaPlayerManager.stopContent()
viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.NEWEST)
}
binding.tvSortLikes.setOnClickListener {
mediaPlayerManager.stopContent()
viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.LIKES)
}
adapter = AuditionApplicantListAdapter(
onClickPlayOrPause = {},
onClickPlayOrPause = { position, applicantId, voiceUrl ->
mediaPlayerManager.toggleContent(position, applicantId, voiceUrl)
},
onClickVote = {
if (isShowNotifyVote) {
SodaDialog(

View File

@ -5,7 +5,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="13.3dp"
android:paddingTop="18.7dp"
tools:background="@color/black">
<ImageView