오디션 지원자

- 오디오 재생시 시간과 ProgressBar 추가
This commit is contained in:
klaus 2025-01-03 19:47:10 +09:00
parent 679e9ed349
commit 931a9433f3
6 changed files with 190 additions and 23 deletions

View File

@ -1,6 +1,8 @@
package kr.co.vividnext.sodalive.audition.applicant
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -8,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.Utils
import kr.co.vividnext.sodalive.databinding.ItemAuditionApplicantBinding
class AuditionApplicantListAdapter(
@ -16,6 +19,8 @@ class AuditionApplicantListAdapter(
) : ListAdapter<GetAuditionRoleApplicantItem, AuditionApplicantListAdapter.ViewHolder>(DiffCallback()) {
private var currentPlayingIndex: Int = -1
private var currentTotalDuration: Int = 0
private var currentTime: Int = 0
private var isPlaying: Boolean = false
class DiffCallback : DiffUtil.ItemCallback<GetAuditionRoleApplicantItem>() {
@ -37,9 +42,14 @@ class AuditionApplicantListAdapter(
inner class ViewHolder(
private val binding: ItemAuditionApplicantBinding
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(item: GetAuditionRoleApplicantItem) {
binding.llVote.setOnClickListener { onClickVote(bindingAdapterPosition) }
binding.ivProfile.setOnClickListener {
if (currentPlayingIndex != bindingAdapterPosition) {
currentTime = 0
currentTotalDuration = 0
}
onClickPlayOrPause(bindingAdapterPosition, item.applicantId, item.voiceUrl)
}
@ -58,6 +68,31 @@ class AuditionApplicantListAdapter(
R.drawable.ic_audition_play
}
)
if (bindingAdapterPosition == currentPlayingIndex) {
binding.tvTotalDuration.visibility = View.VISIBLE
binding.tvCurrentTime.visibility = View.VISIBLE
binding.sbProgress.visibility = View.VISIBLE
binding.sbProgress.max = currentTotalDuration
binding.sbProgress.progress = currentTime
binding.tvTotalDuration.text =
"/${
Utils.convertDurationToString(
currentTotalDuration,
showHours = false
)
}"
binding.tvCurrentTime.text = Utils.convertDurationToString(
currentTime,
showHours = false
)
} else {
binding.tvTotalDuration.visibility = View.GONE
binding.tvCurrentTime.visibility = View.GONE
binding.sbProgress.visibility = View.GONE
}
}
}
@ -81,4 +116,14 @@ class AuditionApplicantListAdapter(
notifyItemChanged(previousIndex)
notifyItemChanged(currentPlayingIndex)
}
fun updateTotalDuration(totalDuration: Int) {
currentTotalDuration = totalDuration
notifyItemChanged(currentPlayingIndex)
}
fun updateCurrentTime(currentTime: Int) {
this.currentTime = currentTime
notifyItemChanged(currentPlayingIndex)
}
}

View File

@ -5,29 +5,46 @@ import android.content.Intent
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import com.orhanobut.logger.Logger
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
import java.io.IOException
@OptIn(UnstableApi::class)
class AuditionApplicantMediaPlayerManager(
private val context: Context,
private val updateUI: (Int, Boolean) -> Unit
private val updatePlayPauseState: (Int, Boolean) -> Unit,
private val updateTotalDuration: (Int) -> Unit,
private val updateCurrentTime: (Int) -> Unit,
private val showLoadingDialog: (Boolean) -> Unit
) {
private var mediaPlayer: MediaPlayer? = null
private var currentPlayingApplicantId: Long = -1
private var currentPlayingPosition: Int = -1
private val handler = Handler(Looper.getMainLooper())
private var changeMediaPlayerPositionRunnable = object : Runnable {
override fun run() {
if (mediaPlayer != null) {
updateCurrentTime(mediaPlayer!!.currentPosition)
}
handler.postDelayed(this, 1000)
}
}
fun pauseContent() {
mediaPlayer?.pause()
updateUI(currentPlayingPosition, false)
updatePlayPauseState(currentPlayingPosition, false)
}
private fun resumeContent() {
pauseAudioContentService()
mediaPlayer?.start()
updateUI(currentPlayingPosition, true)
updatePlayPauseState(currentPlayingPosition, true)
}
fun stopContent() {
@ -38,22 +55,27 @@ class AuditionApplicantMediaPlayerManager(
}
currentPlayingApplicantId = -1
currentPlayingPosition = -1
updateUI(currentPlayingPosition, false)
updatePlayPauseState(currentPlayingPosition, false)
handler.removeCallbacks(changeMediaPlayerPositionRunnable)
}
fun toggleContent(position: Int, applicantId: Long, voiceUrl: String) {
if (currentPlayingApplicantId == applicantId && currentPlayingPosition == position) {
if (mediaPlayer?.isPlaying == true) {
pauseContent()
handler.removeCallbacks(changeMediaPlayerPositionRunnable)
} else {
resumeContent()
handler.postDelayed(changeMediaPlayerPositionRunnable, 1000)
}
} else {
playContent(position, applicantId, voiceUrl)
handler.postDelayed(changeMediaPlayerPositionRunnable, 1000)
}
}
private fun playContent(position: Int, applicantId: Long, voiceUrl: String) {
showLoadingDialog(true)
pauseAudioContentService()
stopContent()
@ -73,15 +95,18 @@ class AuditionApplicantMediaPlayerManager(
prepareAsync() // 비동기적으로 준비
setOnPreparedListener {
start()
updateUI(currentPlayingPosition, true)
updateTotalDuration(duration)
updatePlayPauseState(currentPlayingPosition, true)
showLoadingDialog(false)
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(context, "콘텐츠를 재생하지 못했습니다.\n다시 시도해 주세요", Toast.LENGTH_SHORT).show()
showLoadingDialog(false)
}
setOnCompletionListener {
updateUI(currentPlayingPosition, false)
updatePlayPauseState(currentPlayingPosition, false)
currentPlayingApplicantId = -1
}
}

View File

@ -91,10 +91,24 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
super.onCreate(savedInstanceState)
mediaPlayerManager = AuditionApplicantMediaPlayerManager(
this
) { position, isPlaying ->
context = this,
updatePlayPauseState = { position, isPlaying ->
adapter.updatePlayingIndex(position, isPlaying)
},
updateTotalDuration = { duration ->
adapter.updateTotalDuration(totalDuration = duration)
},
updateCurrentTime = { currentTime ->
adapter.updateCurrentTime(currentTime)
},
showLoadingDialog = {
if (it) {
loadingDialog.show(screenWidth, "")
} else {
loadingDialog.dismiss()
}
}
)
bindData()
viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId)
@ -197,6 +211,8 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
)
val recyclerView = binding.rvApplicant
recyclerView.setHasFixedSize(true)
recyclerView.itemAnimator = null
recyclerView.layoutManager = LinearLayoutManager(
applicationContext,
LinearLayoutManager.VERTICAL,

View File

@ -1,13 +1,17 @@
package kr.co.vividnext.sodalive.common
object Utils {
fun convertDurationToString(duration: Int): String {
fun convertDurationToString(duration: Int, showHours: Boolean = true): String {
val durationSeconds = duration / 1000
val hours = (durationSeconds / 3600)
val minutes = ((durationSeconds % 3600) / 60)
val minutes = if (showHours) (durationSeconds % 3600) / 60 else durationSeconds / 60
val seconds = (durationSeconds % 60)
return "%02d:%02d:%02d".format(hours, minutes, seconds)
return if (showHours) {
"%02d:%02d:%02d".format(hours, minutes, seconds)
} else {
"%02d:%02d".format(minutes, seconds)
}
}
fun convertStringToDuration(timeString: String): Long {

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<corners android:radius="6.7dp" />
<solid android:color="@color/color_cc979797" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="6.7dp" />
<solid android:color="@color/color_cc979797" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="6.7dp" />
<solid android:color="@color/color_3bb9f1" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -27,18 +27,70 @@
app:layout_constraintStart_toStartOf="@+id/iv_profile"
app:layout_constraintTop_toTopOf="@+id/iv_profile" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="13.3dp"
android:gravity="center_vertical"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
app:layout_constraintEnd_toStartOf="@+id/ll_vote"
app:layout_constraintStart_toEndOf="@+id/iv_profile"
app:layout_constraintTop_toTopOf="@+id/iv_profile">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:layout_centerVertical="true"
android:ellipsize="end"
android:fontFamily="@font/gmarket_sans_medium"
android:maxEms="9"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
app:layout_constraintStart_toEndOf="@+id/iv_profile"
app:layout_constraintTop_toTopOf="@+id/iv_profile"
tools:text="닉네임" />
tools:text="닉네임닉네임닉네임닉네임닉네임" />
<TextView
android:id="@+id/tv_current_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/tv_total_duration"
android:layout_toEndOf="@+id/tv_nickname"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="end"
android:textColor="@color/color_777777"
android:textSize="12sp"
tools:text="00:10:00" />
<TextView
android:id="@+id/tv_total_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:fontFamily="@font/gmarket_sans_medium"
android:textColor="@color/color_777777"
android:textSize="12sp"
tools:text="/30:00:00" />
</RelativeLayout>
<SeekBar
android:id="@+id/sb_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2.3dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:progressDrawable="@drawable/audition_player_seekbar"
android:thumb="@null"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_vote"