diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt index 421ee87..e7386df 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt @@ -13,12 +13,14 @@ import android.view.ViewGroup import android.widget.SeekBar import android.widget.Toast import androidx.core.content.ContextCompat +import androidx.core.os.BundleCompat import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaController import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionResult import androidx.media3.session.SessionToken import coil.load import coil.transform.CircleCropTransformation @@ -26,6 +28,7 @@ import coil.transform.RoundedCornersTransformation import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.orhanobut.logger.Logger import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.audio_content.playlist.detail.AudioContentPlaylistContent import kr.co.vividnext.sodalive.common.Constants @@ -95,9 +98,9 @@ class AudioContentPlayerFragment( } override fun onDestroyView() { + handler.removeCallbacksAndMessages(null) mediaController?.release() mediaController = null - handler.removeCallbacksAndMessages(null) super.onDestroyView() } @@ -170,13 +173,6 @@ class AudioContentPlayerFragment( override fun onPlaybackStateChanged(playbackState: Int) { mediaController?.let { when (playbackState) { - Player.STATE_READY -> { - binding.sbProgress.max = it.duration.toInt() - binding.tvTotalTime.text = Utils.convertDurationToString( - it.duration.toInt() - ) - } - Player.STATE_ENDED -> it.seekTo(0) else -> {} } @@ -212,6 +208,26 @@ class AudioContentPlayerFragment( } val sessionCommand = SessionCommand("UPDATE_PLAYLIST", Bundle.EMPTY) mediaController!!.sendCustomCommand(sessionCommand, extras) + } else { + context?.let { + val sessionCommand = SessionCommand("GET_PLAYLIST", Bundle.EMPTY) + val resultFuture = mediaController!!.sendCustomCommand(sessionCommand, Bundle.EMPTY) + resultFuture.addListener( + { + val result = resultFuture.get() + if (result.resultCode == SessionResult.RESULT_SUCCESS) { + val data = BundleCompat.getParcelableArrayList( + result.extras, + Constants.EXTRA_AUDIO_CONTENT_PLAYLIST, + AudioContentPlaylistContent::class.java + ) + + Logger.e("playlist: $data") + } + }, + ContextCompat.getMainExecutor(it) + ) + } } } @@ -297,8 +313,13 @@ class AudioContentPlayerFragment( private fun updateTimeUI() { mediaController?.let { + val duration = it.duration val currentPosition = it.currentPosition + + binding.sbProgress.max = duration.toInt() binding.sbProgress.progress = currentPosition.toInt() + + binding.tvTotalTime.text = Utils.convertDurationToString(duration.toInt()) binding.tvProgressTime.text = Utils.convertDurationToString(currentPosition.toInt()) } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt index 8dfb1e5..55281f0 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt @@ -41,6 +41,8 @@ class AudioContentPlayerService : MediaSessionService() { val compositeDisposable = CompositeDisposable() + private val playlist = ArrayList() + override fun onCreate() { super.onCreate() @@ -81,6 +83,14 @@ class AudioContentPlayerService : MediaSessionService() { override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent?.action == "STOP_SERVICE") { + onStopService() + } + + return super.onStartCommand(intent, flags, startId) + } + private fun initPlayer() { player = ExoPlayer.Builder(this).build() player!!.addListener(object : Player.Listener { @@ -116,7 +126,7 @@ class AudioContentPlayerService : MediaSessionService() { .add(SessionCommand("UPDATE_PLAYLIST", Bundle.EMPTY)) .add(SessionCommand("PLAY_NEXT_CONTENT", Bundle.EMPTY)) .add(SessionCommand("PLAY_PREVIOUS_CONTENT", Bundle.EMPTY)) - .add(SessionCommand("SEEK_TO", Bundle.EMPTY)) + .add(SessionCommand("GET_PLAYLIST", Bundle.EMPTY)) .build() return MediaSession.ConnectionResult.AcceptedResultBuilder(session) @@ -139,6 +149,7 @@ class AudioContentPlayerService : MediaSessionService() { ) if (playlist != null) { + this@AudioContentPlayerService.playlist.addAll(playlist) playlistManager = AudioContentPlaylistManager(playlist) playNextContent() } @@ -156,6 +167,22 @@ class AudioContentPlayerService : MediaSessionService() { Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } + "GET_PLAYLIST" -> { + val extras = Bundle().apply { + putParcelableArrayList( + Constants.EXTRA_AUDIO_CONTENT_PLAYLIST, + playlist + ) + } + + Futures.immediateFuture( + SessionResult( + SessionResult.RESULT_SUCCESS, + extras + ) + ) + } + else -> super.onCustomCommand(session, controller, customCommand, args) } } @@ -170,6 +197,12 @@ class AudioContentPlayerService : MediaSessionService() { stopForeground(true) } + mediaSession?.run { + player.release() + release() + mediaSession = null + } + stopSelf() } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt index 35b9927..7c2fefe 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/playlist/detail/AudioContentPlaylistDetailActivity.kt @@ -1,26 +1,40 @@ package kr.co.vividnext.sodalive.audio_content.playlist.detail import android.annotation.SuppressLint +import android.content.ComponentName import android.content.Intent +import android.content.SharedPreferences import android.graphics.Rect import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.View import android.widget.ImageView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import androidx.media3.common.MediaItem +import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.load +import coil.transform.RoundedCornersTransformation +import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerFragment +import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService import kr.co.vividnext.sodalive.audio_content.playlist.modify.AudioContentPlaylistModifyActivity import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityAudioContentPlaylistDetailBinding import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject +import kotlin.random.Random @UnstableApi class AudioContentPlaylistDetailActivity : BaseActivity( @@ -34,10 +48,6 @@ class AudioContentPlaylistDetailActivity : BaseActivity @@ -47,6 +57,101 @@ class AudioContentPlaylistDetailActivity : BaseActivity() + private var mediaController: MediaController? = null + private val handler = Handler(Looper.getMainLooper()) + + @SuppressLint("SetTextI18n") + private val preferenceChangeListener = + SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> + // 특정 키에 대한 값이 변경될 때 UI 업데이트 + if (key == Constants.PREF_IS_PLAYER_SERVICE_RUNNING) { + handler.postDelayed( + { + if (sharedPreferences.getBoolean(key, false)) { + initAndVisibleMiniPlayer() + } else { + deInitMiniPlayer() + } + }, + 500 + ) + } + } + + private fun initAndVisibleMiniPlayer() { + binding.clMiniPlayer.visibility = View.VISIBLE + binding.clMiniPlayer.setOnClickListener { showPlayerFragment() } + binding.ivStop.setOnClickListener { + val stopIntent = Intent( + applicationContext, + AudioContentPlayerService::class.java + ) + stopIntent.action = "STOP_SERVICE" + startService(stopIntent) + } + connectPlayerService() + } + + private fun connectPlayerService() { + val componentName = ComponentName(applicationContext, AudioContentPlayerService::class.java) + val sessionToken = SessionToken(applicationContext, componentName) + val mediaControllerFuture = + MediaController.Builder(applicationContext, sessionToken).buildAsync() + mediaControllerFuture.addListener( + { + mediaController = mediaControllerFuture.get() + setupMediaController() + binding.ivPlayOrPause.setOnClickListener { + mediaController?.let { + if (it.playWhenReady) { + it.pause() + } else { + it.play() + } + } + } + }, + ContextCompat.getMainExecutor(applicationContext) + ) + } + + private fun setupMediaController() { + if (mediaController == null) { + deInitMiniPlayer() + return + } + + mediaController!!.addListener(object : Player.Listener { + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + mediaItem?.mediaMetadata?.let { + binding.ivPlayerCover.load(it.artworkUri) { + crossfade(true) + placeholder(R.drawable.ic_place_holder) + transformations(RoundedCornersTransformation(5.3f.dpToPx())) + } + + binding.tvPlayerTitle.text = it.title + binding.tvPlayerNickname.text = it.artist + } + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + binding.ivPlayOrPause.setImageResource( + if (playWhenReady) { + R.drawable.ic_player_pause + } else { + R.drawable.ic_player_play + } + ) + } + }) + } + + private fun deInitMiniPlayer() { + binding.clMiniPlayer.visibility = View.GONE + mediaController?.release() + mediaController = null + } override fun onCreate(savedInstanceState: Bundle?) { playlistId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYLIST_ID, 0) @@ -59,6 +164,26 @@ class AudioContentPlaylistDetailActivity : BaseActivity = arrayListOf() + ) { + val playerFragment = AudioContentPlayerFragment(screenWidth, contentList) + playerFragment.show(supportFragmentManager, playerFragment.tag) } override fun setupView() { @@ -105,10 +230,13 @@ class AudioContentPlaylistDetailActivity : BaseActivity + + + + + + + + + + + + +