구간반복 기능 추가
This commit is contained in:
parent
c7af522cfb
commit
bddf7b750b
|
@ -117,6 +117,7 @@ class AudioContentPlayerFragment(
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = AudioContentPlaylistDetailAdapter { contentId ->
|
adapter = AudioContentPlaylistDetailAdapter { contentId ->
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
val extras = Bundle().apply {
|
val extras = Bundle().apply {
|
||||||
putLong(
|
putLong(
|
||||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||||
|
@ -165,6 +166,24 @@ class AudioContentPlayerFragment(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
recyclerView.adapter = adapter
|
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() {
|
private fun bindData() {
|
||||||
|
@ -282,6 +301,7 @@ class AudioContentPlayerFragment(
|
||||||
})
|
})
|
||||||
|
|
||||||
if (playlist.isNotEmpty()) {
|
if (playlist.isNotEmpty()) {
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
val extras = Bundle().apply {
|
val extras = Bundle().apply {
|
||||||
putParcelableArrayList(
|
putParcelableArrayList(
|
||||||
Constants.EXTRA_AUDIO_CONTENT_PLAYLIST,
|
Constants.EXTRA_AUDIO_CONTENT_PLAYLIST,
|
||||||
|
@ -292,6 +312,7 @@ class AudioContentPlayerFragment(
|
||||||
mediaController!!.sendCustomCommand(sessionCommand, extras)
|
mediaController!!.sendCustomCommand(sessionCommand, extras)
|
||||||
adapter.updateItems(playlist)
|
adapter.updateItems(playlist)
|
||||||
} else {
|
} else {
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
context?.let {
|
context?.let {
|
||||||
val sessionCommand = SessionCommand("GET_PLAYLIST", Bundle.EMPTY)
|
val sessionCommand = SessionCommand("GET_PLAYLIST", Bundle.EMPTY)
|
||||||
val resultFuture = mediaController!!.sendCustomCommand(sessionCommand, Bundle.EMPTY)
|
val resultFuture = mediaController!!.sendCustomCommand(sessionCommand, Bundle.EMPTY)
|
||||||
|
@ -341,6 +362,7 @@ class AudioContentPlayerFragment(
|
||||||
|
|
||||||
binding.ivSkipForward.setOnClickListener {
|
binding.ivSkipForward.setOnClickListener {
|
||||||
mediaController?.let {
|
mediaController?.let {
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
val sessionCommand = SessionCommand(
|
val sessionCommand = SessionCommand(
|
||||||
"PLAY_NEXT_CONTENT",
|
"PLAY_NEXT_CONTENT",
|
||||||
Bundle.EMPTY
|
Bundle.EMPTY
|
||||||
|
@ -351,6 +373,7 @@ class AudioContentPlayerFragment(
|
||||||
|
|
||||||
binding.ivSkipBack.setOnClickListener {
|
binding.ivSkipBack.setOnClickListener {
|
||||||
mediaController?.let {
|
mediaController?.let {
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
val sessionCommand = SessionCommand(
|
val sessionCommand = SessionCommand(
|
||||||
"PLAY_PREVIOUS_CONTENT",
|
"PLAY_PREVIOUS_CONTENT",
|
||||||
Bundle.EMPTY
|
Bundle.EMPTY
|
||||||
|
@ -361,6 +384,7 @@ class AudioContentPlayerFragment(
|
||||||
|
|
||||||
binding.ivSeekForward10.setOnClickListener {
|
binding.ivSeekForward10.setOnClickListener {
|
||||||
mediaController?.let {
|
mediaController?.let {
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
val sessionCommand = SessionCommand(
|
val sessionCommand = SessionCommand(
|
||||||
"SEEK_FORWARD",
|
"SEEK_FORWARD",
|
||||||
Bundle.EMPTY
|
Bundle.EMPTY
|
||||||
|
@ -371,6 +395,7 @@ class AudioContentPlayerFragment(
|
||||||
|
|
||||||
binding.ivSeekBackward10.setOnClickListener {
|
binding.ivSeekBackward10.setOnClickListener {
|
||||||
mediaController?.let {
|
mediaController?.let {
|
||||||
|
binding.ivLoopSegment.setImageResource(R.drawable.ic_loop_segment_idle)
|
||||||
val sessionCommand = SessionCommand(
|
val sessionCommand = SessionCommand(
|
||||||
"SEEK_BACKWARD",
|
"SEEK_BACKWARD",
|
||||||
Bundle.EMPTY
|
Bundle.EMPTY
|
||||||
|
|
|
@ -5,6 +5,8 @@ import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import androidx.core.os.BundleCompat
|
import androidx.core.os.BundleCompat
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaMetadata
|
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.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
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.audio_content.playlist.detail.AudioContentPlaylistContent
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
@ -45,6 +48,12 @@ class AudioContentPlayerService : MediaSessionService() {
|
||||||
|
|
||||||
private val playlist = ArrayList<AudioContentPlaylistContent>()
|
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() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
@ -66,6 +75,7 @@ class AudioContentPlayerService : MediaSessionService() {
|
||||||
|
|
||||||
compositeDisposable.dispose()
|
compositeDisposable.dispose()
|
||||||
SharedPreferenceManager.isPlayerServiceRunning = false
|
SharedPreferenceManager.isPlayerServiceRunning = false
|
||||||
|
stopLoop()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +93,44 @@ class AudioContentPlayerService : MediaSessionService() {
|
||||||
return super.onStartCommand(intent, flags, startId)
|
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() {
|
private fun initPlayer() {
|
||||||
player = ExoPlayer.Builder(this).build()
|
player = ExoPlayer.Builder(this).build()
|
||||||
player!!.addListener(object : Player.Listener {
|
player!!.addListener(object : Player.Listener {
|
||||||
|
@ -132,6 +180,7 @@ class AudioContentPlayerService : MediaSessionService() {
|
||||||
.add(SessionCommand("GET_PLAYLIST", Bundle.EMPTY))
|
.add(SessionCommand("GET_PLAYLIST", Bundle.EMPTY))
|
||||||
.add(SessionCommand("SEEK_FORWARD", Bundle.EMPTY))
|
.add(SessionCommand("SEEK_FORWARD", Bundle.EMPTY))
|
||||||
.add(SessionCommand("SEEK_BACKWARD", Bundle.EMPTY))
|
.add(SessionCommand("SEEK_BACKWARD", Bundle.EMPTY))
|
||||||
|
.add(SessionCommand("TOGGLE_SEGMENT_LOOP", Bundle.EMPTY))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||||
|
@ -147,6 +196,7 @@ class AudioContentPlayerService : MediaSessionService() {
|
||||||
): ListenableFuture<SessionResult> {
|
): ListenableFuture<SessionResult> {
|
||||||
return when (customCommand.customAction) {
|
return when (customCommand.customAction) {
|
||||||
"UPDATE_PLAYLIST" -> {
|
"UPDATE_PLAYLIST" -> {
|
||||||
|
stopLoop()
|
||||||
val playlist = BundleCompat.getParcelableArrayList(
|
val playlist = BundleCompat.getParcelableArrayList(
|
||||||
args,
|
args,
|
||||||
Constants.EXTRA_AUDIO_CONTENT_PLAYLIST,
|
Constants.EXTRA_AUDIO_CONTENT_PLAYLIST,
|
||||||
|
@ -163,31 +213,66 @@ class AudioContentPlayerService : MediaSessionService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
"PLAY_NEXT_CONTENT" -> {
|
"PLAY_NEXT_CONTENT" -> {
|
||||||
|
stopLoop()
|
||||||
playNextContent()
|
playNextContent()
|
||||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
}
|
}
|
||||||
|
|
||||||
"PLAY_PREVIOUS_CONTENT" -> {
|
"PLAY_PREVIOUS_CONTENT" -> {
|
||||||
|
stopLoop()
|
||||||
playPreviousContent()
|
playPreviousContent()
|
||||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
}
|
}
|
||||||
|
|
||||||
"PLAY_SELECTED_CONTENT" -> {
|
"PLAY_SELECTED_CONTENT" -> {
|
||||||
|
stopLoop()
|
||||||
val selectedContentId = args.getLong(Constants.EXTRA_AUDIO_CONTENT_ID)
|
val selectedContentId = args.getLong(Constants.EXTRA_AUDIO_CONTENT_ID)
|
||||||
playSelectedContent(contentId = selectedContentId)
|
playSelectedContent(contentId = selectedContentId)
|
||||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
}
|
}
|
||||||
|
|
||||||
"SEEK_FORWARD" -> {
|
"SEEK_FORWARD" -> {
|
||||||
|
stopLoop()
|
||||||
playSeekForward()
|
playSeekForward()
|
||||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
}
|
}
|
||||||
|
|
||||||
"SEEK_BACKWARD" -> {
|
"SEEK_BACKWARD" -> {
|
||||||
|
stopLoop()
|
||||||
playSeekBackward()
|
playSeekBackward()
|
||||||
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
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" -> {
|
"GET_PLAYLIST" -> {
|
||||||
val extras = Bundle().apply {
|
val extras = Bundle().apply {
|
||||||
putParcelableArrayList(
|
putParcelableArrayList(
|
||||||
|
|
|
@ -77,6 +77,7 @@ object Constants {
|
||||||
const val EXTRA_AUDIO_CONTENT_ALERT_PREVIEW = "audio_content_alert_preview"
|
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_COVER_IMAGE_URL = "audio_content_cover_image_url"
|
||||||
const val EXTRA_AUDIO_CONTENT_PLAYLIST = "extra_audio_content_playlist"
|
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 EXTRA_IS_SHOW_SECRET = "extra_is_show_secret"
|
||||||
|
|
||||||
const val LIVE_SERVICE_NOTIFICATION_ID: Int = 2
|
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_constraintStart_toEndOf="@+id/iv_play_or_pause"
|
||||||
app:layout_constraintTop_toTopOf="@+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
|
<ImageView
|
||||||
android:id="@+id/iv_playlist"
|
android:id="@+id/iv_playlist"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Reference in New Issue