diff --git a/app/build.gradle b/app/build.gradle index 7e68bed..a58b601 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt index 00b3d08..de11457 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt @@ -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(DiffCallback()) { private var currentPlayingIndex: Int = -1 + private var isPlaying: Boolean = false class DiffCallback : DiffUtil.ItemCallback() { 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) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantMediaPlayerManager.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantMediaPlayerManager.kt new file mode 100644 index 0000000..11ccd13 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantMediaPlayerManager.kt @@ -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" + } + ) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt index a484546..38d040b 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt @@ -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 + 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 + mediaPlayerManager.toggleContent(position, applicantId, voiceUrl) + }, onClickVote = { if (isShowNotifyVote) { SodaDialog( diff --git a/app/src/main/res/layout/item_audition_applicant.xml b/app/src/main/res/layout/item_audition_applicant.xml index 3c6835e..73e5319 100644 --- a/app/src/main/res/layout/item_audition_applicant.xml +++ b/app/src/main/res/layout/item_audition_applicant.xml @@ -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">