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

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.media:media:1.7.0"
implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

View File

@ -12,10 +12,11 @@ import kr.co.vividnext.sodalive.databinding.ItemAuditionApplicantBinding
class AuditionApplicantListAdapter( class AuditionApplicantListAdapter(
private val onClickVote: (Int) -> Unit, private val onClickVote: (Int) -> Unit,
private val onClickPlayOrPause: (Int) -> Unit private val onClickPlayOrPause: (Int, Long, String) -> Unit
) : ListAdapter<GetAuditionRoleApplicantItem, AuditionApplicantListAdapter.ViewHolder>(DiffCallback()) { ) : ListAdapter<GetAuditionRoleApplicantItem, AuditionApplicantListAdapter.ViewHolder>(DiffCallback()) {
private var currentPlayingIndex: Int = -1 private var currentPlayingIndex: Int = -1
private var isPlaying: Boolean = false
class DiffCallback : DiffUtil.ItemCallback<GetAuditionRoleApplicantItem>() { class DiffCallback : DiffUtil.ItemCallback<GetAuditionRoleApplicantItem>() {
override fun areItemsTheSame( override fun areItemsTheSame(
@ -29,16 +30,18 @@ class AuditionApplicantListAdapter(
oldItem: GetAuditionRoleApplicantItem, oldItem: GetAuditionRoleApplicantItem,
newItem: GetAuditionRoleApplicantItem newItem: GetAuditionRoleApplicantItem
): Boolean { ): Boolean {
return oldItem == newItem && oldItem.voteCount == newItem.voteCount return oldItem == newItem
} }
} }
inner class ViewHolder( inner class ViewHolder(
private val binding: ItemAuditionApplicantBinding private val binding: ItemAuditionApplicantBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetAuditionRoleApplicantItem, position: Int) { fun bind(item: GetAuditionRoleApplicantItem) {
binding.ivVote.setOnClickListener { onClickVote(position) } binding.ivVote.setOnClickListener { onClickVote(bindingAdapterPosition) }
binding.ivPlayOrPause.setOnClickListener { onClickPlayOrPause(position) } binding.ivPlayOrPause.setOnClickListener {
onClickPlayOrPause(bindingAdapterPosition, item.applicantId, item.voiceUrl)
}
binding.tvNickname.text = item.nickname binding.tvNickname.text = item.nickname
binding.tvCountVote.text = item.voteCount.toString() binding.tvCountVote.text = item.voteCount.toString()
@ -49,7 +52,7 @@ class AuditionApplicantListAdapter(
} }
binding.ivPlayOrPause.setImageResource( binding.ivPlayOrPause.setImageResource(
if (position == currentPlayingIndex) { if (bindingAdapterPosition == currentPlayingIndex && isPlaying) {
R.drawable.ic_audition_pause R.drawable.ic_audition_pause
} else { } else {
R.drawable.ic_audition_play R.drawable.ic_audition_play
@ -67,10 +70,12 @@ class AuditionApplicantListAdapter(
) )
override fun onBindViewHolder(holder: ViewHolder, position: Int) { 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 val previousIndex = currentPlayingIndex
currentPlayingIndex = newIndex currentPlayingIndex = newIndex
notifyItemChanged(previousIndex) 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.R
import kr.co.vividnext.sodalive.audition.applicant.ApplicationMethodDialog import kr.co.vividnext.sodalive.audition.applicant.ApplicationMethodDialog
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantListAdapter 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.audition.applicant.AuditionApplyDialogFragment
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog 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?) { override fun onCreate(savedInstanceState: Bundle?) {
auditionRoleId = intent.getLongExtra(Constants.EXTRA_AUDITION_ROLE_ID, 0) auditionRoleId = intent.getLongExtra(Constants.EXTRA_AUDITION_ROLE_ID, 0)
@ -87,10 +90,26 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mediaPlayerManager = AuditionApplicantMediaPlayerManager(
this
) { position, isPlaying ->
adapter.updatePlayingIndex(position, isPlaying)
}
bindData() bindData()
viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId) viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId)
} }
override fun onPause() {
mediaPlayerManager.pauseContent()
super.onPause()
}
override fun onDestroy() {
mediaPlayerManager.stopContent()
super.onDestroy()
}
override fun setupView() { override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater) loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.setOnClickListener { finish() } binding.toolbar.tvBack.setOnClickListener { finish() }
@ -133,15 +152,19 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
} }
binding.tvSortNewest.setOnClickListener { binding.tvSortNewest.setOnClickListener {
mediaPlayerManager.stopContent()
viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.NEWEST) viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.NEWEST)
} }
binding.tvSortLikes.setOnClickListener { binding.tvSortLikes.setOnClickListener {
mediaPlayerManager.stopContent()
viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.LIKES) viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.LIKES)
} }
adapter = AuditionApplicantListAdapter( adapter = AuditionApplicantListAdapter(
onClickPlayOrPause = {}, onClickPlayOrPause = { position, applicantId, voiceUrl ->
mediaPlayerManager.toggleContent(position, applicantId, voiceUrl)
},
onClickVote = { onClickVote = {
if (isShowNotifyVote) { if (isShowNotifyVote) {
SodaDialog( SodaDialog(

View File

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