구간반복 기능 추가
This commit is contained in:
parent
c7af522cfb
commit
bddf7b750b
|
@ -117,6 +117,7 @@ class AudioContentPlayerFragment(
|
|||
}
|
||||
|
||||
adapter = AudioContentPlaylistDetailAdapter { contentId ->
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
val extras = Bundle().apply {
|
||||
putLong(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
|
@ -165,6 +166,24 @@ class AudioContentPlayerFragment(
|
|||
}
|
||||
})
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
binding.ivLoopSegment.setOnClickListener {
|
||||
val sessionCommand = SessionCommand("TOGGLE_SEGMENT_LOOP", Bundle.EMPTY)
|
||||
val resultFuture = mediaController!!.sendCustomCommand(sessionCommand, Bundle.EMPTY)
|
||||
resultFuture.addListener(
|
||||
{
|
||||
val result = resultFuture.get()
|
||||
if (result.resultCode == SessionResult.RESULT_SUCCESS) {
|
||||
val imageRes = result.extras.getInt(
|
||||
Constants.EXTRA_PLAYLIST_SEGMENT_LOOP_IMAGE,
|
||||
R.drawable.ic_loop_segment_idle
|
||||
)
|
||||
binding.ivLoopSegment.setImageResource(imageRes)
|
||||
}
|
||||
},
|
||||
ContextCompat.getMainExecutor(requireContext())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
|
@ -282,6 +301,7 @@ class AudioContentPlayerFragment(
|
|||
})
|
||||
|
||||
if (playlist.isNotEmpty()) {
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
val extras = Bundle().apply {
|
||||
putParcelableArrayList(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYLIST,
|
||||
|
@ -292,6 +312,7 @@ class AudioContentPlayerFragment(
|
|||
mediaController!!.sendCustomCommand(sessionCommand, extras)
|
||||
adapter.updateItems(playlist)
|
||||
} else {
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
context?.let {
|
||||
val sessionCommand = SessionCommand("GET_PLAYLIST", Bundle.EMPTY)
|
||||
val resultFuture = mediaController!!.sendCustomCommand(sessionCommand, Bundle.EMPTY)
|
||||
|
@ -341,6 +362,7 @@ class AudioContentPlayerFragment(
|
|||
|
||||
binding.ivSkipForward.setOnClickListener {
|
||||
mediaController?.let {
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
val sessionCommand = SessionCommand(
|
||||
"PLAY_NEXT_CONTENT",
|
||||
Bundle.EMPTY
|
||||
|
@ -351,6 +373,7 @@ class AudioContentPlayerFragment(
|
|||
|
||||
binding.ivSkipBack.setOnClickListener {
|
||||
mediaController?.let {
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
val sessionCommand = SessionCommand(
|
||||
"PLAY_PREVIOUS_CONTENT",
|
||||
Bundle.EMPTY
|
||||
|
@ -361,6 +384,7 @@ class AudioContentPlayerFragment(
|
|||
|
||||
binding.ivSeekForward10.setOnClickListener {
|
||||
mediaController?.let {
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
val sessionCommand = SessionCommand(
|
||||
"SEEK_FORWARD",
|
||||
Bundle.EMPTY
|
||||
|
@ -371,6 +395,7 @@ class AudioContentPlayerFragment(
|
|||
|
||||
binding.ivSeekBackward10.setOnClickListener {
|
||||
mediaController?.let {
|
||||
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||
val sessionCommand = SessionCommand(
|
||||
"SEEK_BACKWARD",
|
||||
Bundle.EMPTY
|
||||
|
|
|
@ -5,6 +5,8 @@ import android.content.Intent
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
|
@ -25,6 +27,7 @@ import com.orhanobut.logger.Logger
|
|||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
@ -45,6 +48,12 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||
|
||||
private val playlist = ArrayList<AudioContentPlaylistContent>()
|
||||
|
||||
private var loopStartMs: Long? = null
|
||||
private var loopEndMs: Long? = null
|
||||
private var isLooping = false
|
||||
private val loopHandler = Handler(Looper.getMainLooper())
|
||||
private val loopCheckInterval = 100L // 0.1초 간격
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
@ -66,6 +75,7 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||
|
||||
compositeDisposable.dispose()
|
||||
SharedPreferenceManager.isPlayerServiceRunning = false
|
||||
stopLoop()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -83,6 +93,44 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
private fun toggleSegmentLoop() {
|
||||
when {
|
||||
loopStartMs == null -> {
|
||||
loopStartMs = player?.currentPosition
|
||||
}
|
||||
|
||||
loopEndMs == null -> {
|
||||
loopEndMs = player?.currentPosition
|
||||
isLooping = true
|
||||
startLoopMonitoring()
|
||||
}
|
||||
|
||||
else -> {
|
||||
stopLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startLoopMonitoring() {
|
||||
loopHandler.post(object : Runnable {
|
||||
override fun run() {
|
||||
if (isLooping && loopStartMs != null && loopEndMs != null && player != null) {
|
||||
if (player!!.currentPosition >= loopEndMs!!) {
|
||||
player!!.seekTo(loopStartMs!!)
|
||||
}
|
||||
loopHandler.postDelayed(this, loopCheckInterval)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun stopLoop() {
|
||||
isLooping = false
|
||||
loopStartMs = null
|
||||
loopEndMs = null
|
||||
loopHandler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
private fun initPlayer() {
|
||||
player = ExoPlayer.Builder(this).build()
|
||||
player!!.addListener(object : Player.Listener {
|
||||
|
@ -132,6 +180,7 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||
.add(SessionCommand("GET_PLAYLIST", Bundle.EMPTY))
|
||||
.add(SessionCommand("SEEK_FORWARD", Bundle.EMPTY))
|
||||
.add(SessionCommand("SEEK_BACKWARD", Bundle.EMPTY))
|
||||
.add(SessionCommand("TOGGLE_SEGMENT_LOOP", Bundle.EMPTY))
|
||||
.build()
|
||||
|
||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
|
@ -147,6 +196,7 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||
): ListenableFuture<SessionResult> {
|
||||
return when (customCommand.customAction) {
|
||||
"UPDATE_PLAYLIST" -> {
|
||||
stopLoop()
|
||||
val playlist = BundleCompat.getParcelableArrayList(
|
||||
args,
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYLIST,
|
||||
|
@ -163,31 +213,66 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||
}
|
||||
|
||||
"PLAY_NEXT_CONTENT" -> {
|
||||
stopLoop()
|
||||
playNextContent()
|
||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
"PLAY_PREVIOUS_CONTENT" -> {
|
||||
stopLoop()
|
||||
playPreviousContent()
|
||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
"PLAY_SELECTED_CONTENT" -> {
|
||||
stopLoop()
|
||||
val selectedContentId = args.getLong(Constants.EXTRA_AUDIO_CONTENT_ID)
|
||||
playSelectedContent(contentId = selectedContentId)
|
||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
"SEEK_FORWARD" -> {
|
||||
stopLoop()
|
||||
playSeekForward()
|
||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
"SEEK_BACKWARD" -> {
|
||||
stopLoop()
|
||||
playSeekBackward()
|
||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
"TOGGLE_SEGMENT_LOOP" -> {
|
||||
val extras = Bundle().apply {
|
||||
putInt(
|
||||
Constants.EXTRA_PLAYLIST_SEGMENT_LOOP_IMAGE,
|
||||
when {
|
||||
loopStartMs == null -> {
|
||||
R.drawable.ic_loop_segment_start_set
|
||||
}
|
||||
|
||||
loopEndMs == null -> {
|
||||
R.drawable.ic_loop_segment_active
|
||||
}
|
||||
|
||||
else -> {
|
||||
R.drawable.ic_loop_segment_idle
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
toggleSegmentLoop()
|
||||
|
||||
Futures.immediateFuture(
|
||||
SessionResult(
|
||||
SessionResult.RESULT_SUCCESS,
|
||||
extras
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"GET_PLAYLIST" -> {
|
||||
val extras = Bundle().apply {
|
||||
putParcelableArrayList(
|
||||
|
|
|
@ -77,6 +77,7 @@ object Constants {
|
|||
const val EXTRA_AUDIO_CONTENT_ALERT_PREVIEW = "audio_content_alert_preview"
|
||||
const val EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL = "audio_content_cover_image_url"
|
||||
const val EXTRA_AUDIO_CONTENT_PLAYLIST = "extra_audio_content_playlist"
|
||||
const val EXTRA_PLAYLIST_SEGMENT_LOOP_IMAGE = "extra_playlist_segment_loop_image"
|
||||
const val EXTRA_IS_SHOW_SECRET = "extra_is_show_secret"
|
||||
|
||||
const val LIVE_SERVICE_NOTIFICATION_ID: Int = 2
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -165,6 +165,16 @@
|
|||
app:layout_constraintStart_toEndOf="@+id/iv_play_or_pause"
|
||||
app:layout_constraintTop_toTopOf="@+id/iv_play_or_pause" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_loop_segment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_loop_segment_idle"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_playlist"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Reference in New Issue