parent
679e9ed349
commit
931a9433f3
|
@ -1,6 +1,8 @@
|
||||||
package kr.co.vividnext.sodalive.audition.applicant
|
package kr.co.vividnext.sodalive.audition.applicant
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
@ -8,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.load
|
import coil.load
|
||||||
import coil.transform.CircleCropTransformation
|
import coil.transform.CircleCropTransformation
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.common.Utils
|
||||||
import kr.co.vividnext.sodalive.databinding.ItemAuditionApplicantBinding
|
import kr.co.vividnext.sodalive.databinding.ItemAuditionApplicantBinding
|
||||||
|
|
||||||
class AuditionApplicantListAdapter(
|
class AuditionApplicantListAdapter(
|
||||||
|
@ -16,6 +19,8 @@ class AuditionApplicantListAdapter(
|
||||||
) : ListAdapter<GetAuditionRoleApplicantItem, AuditionApplicantListAdapter.ViewHolder>(DiffCallback()) {
|
) : ListAdapter<GetAuditionRoleApplicantItem, AuditionApplicantListAdapter.ViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
private var currentPlayingIndex: Int = -1
|
private var currentPlayingIndex: Int = -1
|
||||||
|
private var currentTotalDuration: Int = 0
|
||||||
|
private var currentTime: Int = 0
|
||||||
private var isPlaying: Boolean = false
|
private var isPlaying: Boolean = false
|
||||||
|
|
||||||
class DiffCallback : DiffUtil.ItemCallback<GetAuditionRoleApplicantItem>() {
|
class DiffCallback : DiffUtil.ItemCallback<GetAuditionRoleApplicantItem>() {
|
||||||
|
@ -37,9 +42,14 @@ class AuditionApplicantListAdapter(
|
||||||
inner class ViewHolder(
|
inner class ViewHolder(
|
||||||
private val binding: ItemAuditionApplicantBinding
|
private val binding: ItemAuditionApplicantBinding
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(item: GetAuditionRoleApplicantItem) {
|
fun bind(item: GetAuditionRoleApplicantItem) {
|
||||||
binding.llVote.setOnClickListener { onClickVote(bindingAdapterPosition) }
|
binding.llVote.setOnClickListener { onClickVote(bindingAdapterPosition) }
|
||||||
binding.ivProfile.setOnClickListener {
|
binding.ivProfile.setOnClickListener {
|
||||||
|
if (currentPlayingIndex != bindingAdapterPosition) {
|
||||||
|
currentTime = 0
|
||||||
|
currentTotalDuration = 0
|
||||||
|
}
|
||||||
onClickPlayOrPause(bindingAdapterPosition, item.applicantId, item.voiceUrl)
|
onClickPlayOrPause(bindingAdapterPosition, item.applicantId, item.voiceUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +68,31 @@ class AuditionApplicantListAdapter(
|
||||||
R.drawable.ic_audition_play
|
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(previousIndex)
|
||||||
notifyItemChanged(currentPlayingIndex)
|
notifyItemChanged(currentPlayingIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateTotalDuration(totalDuration: Int) {
|
||||||
|
currentTotalDuration = totalDuration
|
||||||
|
notifyItemChanged(currentPlayingIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCurrentTime(currentTime: Int) {
|
||||||
|
this.currentTime = currentTime
|
||||||
|
notifyItemChanged(currentPlayingIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,29 +5,46 @@ import android.content.Intent
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.widget.Toast
|
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.AudioContentPlayService
|
||||||
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
class AuditionApplicantMediaPlayerManager(
|
class AuditionApplicantMediaPlayerManager(
|
||||||
private val context: Context,
|
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 mediaPlayer: MediaPlayer? = null
|
||||||
private var currentPlayingApplicantId: Long = -1
|
private var currentPlayingApplicantId: Long = -1
|
||||||
private var currentPlayingPosition: Int = -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() {
|
fun pauseContent() {
|
||||||
mediaPlayer?.pause()
|
mediaPlayer?.pause()
|
||||||
updateUI(currentPlayingPosition, false)
|
updatePlayPauseState(currentPlayingPosition, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resumeContent() {
|
private fun resumeContent() {
|
||||||
pauseAudioContentService()
|
pauseAudioContentService()
|
||||||
mediaPlayer?.start()
|
mediaPlayer?.start()
|
||||||
updateUI(currentPlayingPosition, true)
|
updatePlayPauseState(currentPlayingPosition, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopContent() {
|
fun stopContent() {
|
||||||
|
@ -38,22 +55,27 @@ class AuditionApplicantMediaPlayerManager(
|
||||||
}
|
}
|
||||||
currentPlayingApplicantId = -1
|
currentPlayingApplicantId = -1
|
||||||
currentPlayingPosition = -1
|
currentPlayingPosition = -1
|
||||||
updateUI(currentPlayingPosition, false)
|
updatePlayPauseState(currentPlayingPosition, false)
|
||||||
|
handler.removeCallbacks(changeMediaPlayerPositionRunnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleContent(position: Int, applicantId: Long, voiceUrl: String) {
|
fun toggleContent(position: Int, applicantId: Long, voiceUrl: String) {
|
||||||
if (currentPlayingApplicantId == applicantId && currentPlayingPosition == position) {
|
if (currentPlayingApplicantId == applicantId && currentPlayingPosition == position) {
|
||||||
if (mediaPlayer?.isPlaying == true) {
|
if (mediaPlayer?.isPlaying == true) {
|
||||||
pauseContent()
|
pauseContent()
|
||||||
|
handler.removeCallbacks(changeMediaPlayerPositionRunnable)
|
||||||
} else {
|
} else {
|
||||||
resumeContent()
|
resumeContent()
|
||||||
|
handler.postDelayed(changeMediaPlayerPositionRunnable, 1000)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
playContent(position, applicantId, voiceUrl)
|
playContent(position, applicantId, voiceUrl)
|
||||||
|
handler.postDelayed(changeMediaPlayerPositionRunnable, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playContent(position: Int, applicantId: Long, voiceUrl: String) {
|
private fun playContent(position: Int, applicantId: Long, voiceUrl: String) {
|
||||||
|
showLoadingDialog(true)
|
||||||
pauseAudioContentService()
|
pauseAudioContentService()
|
||||||
stopContent()
|
stopContent()
|
||||||
|
|
||||||
|
@ -73,15 +95,18 @@ class AuditionApplicantMediaPlayerManager(
|
||||||
prepareAsync() // 비동기적으로 준비
|
prepareAsync() // 비동기적으로 준비
|
||||||
setOnPreparedListener {
|
setOnPreparedListener {
|
||||||
start()
|
start()
|
||||||
updateUI(currentPlayingPosition, true)
|
updateTotalDuration(duration)
|
||||||
|
updatePlayPauseState(currentPlayingPosition, true)
|
||||||
|
showLoadingDialog(false)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
Toast.makeText(context, "콘텐츠를 재생하지 못했습니다.\n다시 시도해 주세요", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "콘텐츠를 재생하지 못했습니다.\n다시 시도해 주세요", Toast.LENGTH_SHORT).show()
|
||||||
|
showLoadingDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnCompletionListener {
|
setOnCompletionListener {
|
||||||
updateUI(currentPlayingPosition, false)
|
updatePlayPauseState(currentPlayingPosition, false)
|
||||||
currentPlayingApplicantId = -1
|
currentPlayingApplicantId = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,10 +91,24 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
mediaPlayerManager = AuditionApplicantMediaPlayerManager(
|
mediaPlayerManager = AuditionApplicantMediaPlayerManager(
|
||||||
this
|
context = this,
|
||||||
) { position, isPlaying ->
|
updatePlayPauseState = { position, isPlaying ->
|
||||||
adapter.updatePlayingIndex(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()
|
bindData()
|
||||||
viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId)
|
viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId)
|
||||||
|
@ -197,6 +211,8 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||||
)
|
)
|
||||||
|
|
||||||
val recyclerView = binding.rvApplicant
|
val recyclerView = binding.rvApplicant
|
||||||
|
recyclerView.setHasFixedSize(true)
|
||||||
|
recyclerView.itemAnimator = null
|
||||||
recyclerView.layoutManager = LinearLayoutManager(
|
recyclerView.layoutManager = LinearLayoutManager(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
LinearLayoutManager.VERTICAL,
|
LinearLayoutManager.VERTICAL,
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package kr.co.vividnext.sodalive.common
|
package kr.co.vividnext.sodalive.common
|
||||||
|
|
||||||
object Utils {
|
object Utils {
|
||||||
fun convertDurationToString(duration: Int): String {
|
fun convertDurationToString(duration: Int, showHours: Boolean = true): String {
|
||||||
val durationSeconds = duration / 1000
|
val durationSeconds = duration / 1000
|
||||||
val hours = (durationSeconds / 3600)
|
val hours = (durationSeconds / 3600)
|
||||||
val minutes = ((durationSeconds % 3600) / 60)
|
val minutes = if (showHours) (durationSeconds % 3600) / 60 else durationSeconds / 60
|
||||||
val seconds = (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 {
|
fun convertStringToDuration(timeString: String): Long {
|
||||||
|
|
|
@ -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>
|
|
@ -27,18 +27,70 @@
|
||||||
app:layout_constraintStart_toStartOf="@+id/iv_profile"
|
app:layout_constraintStart_toStartOf="@+id/iv_profile"
|
||||||
app:layout_constraintTop_toTopOf="@+id/iv_profile" />
|
app:layout_constraintTop_toTopOf="@+id/iv_profile" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tv_nickname"
|
android:layout_width="0dp"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="13.3dp"
|
android:layout_marginHorizontal="13.3dp"
|
||||||
android:fontFamily="@font/gmarket_sans_medium"
|
android:gravity="center_vertical"
|
||||||
android:textColor="@color/white"
|
android:orientation="vertical"
|
||||||
android:textSize="12sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
|
app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/ll_vote"
|
||||||
app:layout_constraintStart_toEndOf="@+id/iv_profile"
|
app:layout_constraintStart_toEndOf="@+id/iv_profile"
|
||||||
app:layout_constraintTop_toTopOf="@+id/iv_profile"
|
app:layout_constraintTop_toTopOf="@+id/iv_profile">
|
||||||
tools:text="닉네임" />
|
|
||||||
|
<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_centerVertical="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:maxEms="9"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
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
|
<LinearLayout
|
||||||
android:id="@+id/ll_vote"
|
android:id="@+id/ll_vote"
|
||||||
|
|
Loading…
Reference in New Issue