메인페이지

- 하단에 미니 플레이어 추가
This commit is contained in:
klaus 2024-12-13 22:26:48 +09:00
parent 6da3192fe8
commit 29595670af
10 changed files with 246 additions and 23 deletions

View File

@ -139,6 +139,7 @@ class AudioContentPlayerFragment(
} }
private fun startPlayerService(context: Context) { private fun startPlayerService(context: Context) {
Toast.makeText(requireContext(), "startPlayerService", Toast.LENGTH_LONG).show()
val serviceIntent = Intent(context, AudioContentPlayerService::class.java) val serviceIntent = Intent(context, AudioContentPlayerService::class.java)
context.startService(serviceIntent) context.startService(serviceIntent)
} }
@ -232,6 +233,14 @@ class AudioContentPlayerFragment(
} }
private fun updatePlayerUI() { private fun updatePlayerUI() {
binding.ivPlayOrPause.setImageResource(
if (mediaController!!.isPlaying) {
R.drawable.ic_player_pause
} else {
R.drawable.ic_player_play
}
)
binding.ivPlayOrPause.setOnClickListener { binding.ivPlayOrPause.setOnClickListener {
mediaController?.let { mediaController?.let {
if (it.playWhenReady) { if (it.playWhenReady) {

View File

@ -47,8 +47,6 @@ class AudioContentPlayerService : MediaSessionService() {
super.onCreate() super.onCreate()
try { try {
SharedPreferenceManager.isPlayerServiceRunning = true
initPlayer() initPlayer()
initMediaSession() initMediaSession()
} catch (e: Exception) { } catch (e: Exception) {
@ -121,6 +119,7 @@ class AudioContentPlayerService : MediaSessionService() {
session: MediaSession, session: MediaSession,
controller: MediaSession.ControllerInfo controller: MediaSession.ControllerInfo
): MediaSession.ConnectionResult { ): MediaSession.ConnectionResult {
SharedPreferenceManager.isPlayerServiceRunning = true
val allowedCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS val allowedCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS
.buildUpon() .buildUpon()
.add(SessionCommand("UPDATE_PLAYLIST", Bundle.EMPTY)) .add(SessionCommand("UPDATE_PLAYLIST", Bundle.EMPTY))

View File

@ -2,15 +2,9 @@ package kr.co.vividnext.sodalive.audio_content.player
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class AudioContentPlayerViewModel( class AudioContentPlayerViewModel : BaseViewModel() {
private val repository: AudioContentGenerateUrlRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>() private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?> val toastLiveData: LiveData<String?>
get() = _toastLiveData get() = _toastLiveData

View File

@ -14,6 +14,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaController import androidx.media3.session.MediaController
@ -73,7 +74,7 @@ class AudioContentPlaylistDetailActivity : BaseActivity<ActivityAudioContentPlay
deInitMiniPlayer() deInitMiniPlayer()
} }
}, },
500 2000
) )
} }
} }
@ -100,6 +101,16 @@ class AudioContentPlaylistDetailActivity : BaseActivity<ActivityAudioContentPlay
{ {
mediaController = mediaControllerFuture.get() mediaController = mediaControllerFuture.get()
setupMediaController() setupMediaController()
updateMediaMetadata(mediaController?.mediaMetadata)
binding.ivPlayOrPause.setImageResource(
if (mediaController!!.isPlaying) {
R.drawable.ic_player_pause
} else {
R.drawable.ic_player_play
}
)
binding.ivPlayOrPause.setOnClickListener { binding.ivPlayOrPause.setOnClickListener {
mediaController?.let { mediaController?.let {
if (it.playWhenReady) { if (it.playWhenReady) {
@ -114,6 +125,19 @@ class AudioContentPlaylistDetailActivity : BaseActivity<ActivityAudioContentPlay
) )
} }
private fun updateMediaMetadata(metadata: MediaMetadata?) {
metadata?.let {
binding.tvPlayerTitle.text = it.title
binding.tvPlayerNickname.text = it.artist
binding.ivPlayerCover.load(it.artworkUri) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4f))
}
}
}
private fun setupMediaController() { private fun setupMediaController() {
if (mediaController == null) { if (mediaController == null) {
deInitMiniPlayer() deInitMiniPlayer()
@ -122,16 +146,7 @@ class AudioContentPlaylistDetailActivity : BaseActivity<ActivityAudioContentPlay
mediaController!!.addListener(object : Player.Listener { mediaController!!.addListener(object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
mediaItem?.mediaMetadata?.let { updateMediaMetadata(mediaItem?.mediaMetadata)
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) { override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {

View File

@ -278,7 +278,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { AudioContentPlaylistDetailViewModel(get()) } viewModel { AudioContentPlaylistDetailViewModel(get()) }
viewModel { AudioContentPlaylistCreateViewModel(get()) } viewModel { AudioContentPlaylistCreateViewModel(get()) }
viewModel { AudioContentPlaylistModifyViewModel(get()) } viewModel { AudioContentPlaylistModifyViewModel(get()) }
viewModel { AudioContentPlayerViewModel(get()) } viewModel { AudioContentPlayerViewModel() }
} }
private val repositoryModule = module { private val repositoryModule = module {

View File

@ -59,6 +59,7 @@ import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
@UnstableApi
class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::inflate) { class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::inflate) {
private val viewModel: LiveViewModel by inject() private val viewModel: LiveViewModel by inject()

View File

@ -28,6 +28,7 @@ import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@UnstableApi
class LiveNowAllActivity : BaseActivity<ActivityLiveNowAllBinding>( class LiveNowAllActivity : BaseActivity<ActivityLiveNowAllBinding>(
ActivityLiveNowAllBinding::inflate ActivityLiveNowAllBinding::inflate
) { ) {
@ -96,7 +97,6 @@ class LiveNowAllActivity : BaseActivity<ActivityLiveNowAllBinding>(
} }
} }
@UnstableApi
private fun enterLiveRoom(roomId: Long) { private fun enterLiveRoom(roomId: Long) {
startService( startService(
Intent(applicationContext, AudioContentPlayService::class.java).apply { Intent(applicationContext, AudioContentPlayService::class.java).apply {

View File

@ -2,9 +2,11 @@ package kr.co.vividnext.sodalive.main
import android.Manifest import android.Manifest
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -13,6 +15,12 @@ import android.os.Looper
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
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.SessionToken
import coil.load import coil.load
import coil.transform.RoundedCornersTransformation import coil.transform.RoundedCornersTransformation
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
@ -23,6 +31,8 @@ import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainFragment import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainFragment
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerFragment
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
@ -39,6 +49,7 @@ import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsDialog import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsDialog
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@UnstableApi
class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::inflate) { class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::inflate) {
private val viewModel: MainViewModel by inject() private val viewModel: MainViewModel by inject()
@ -47,9 +58,115 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var notificationSettingsDialog: NotificationSettingsDialog private lateinit var notificationSettingsDialog: NotificationSettingsDialog
private var mediaController: MediaController? = null
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private val audioContentReceiver = AudioContentReceiver() private val audioContentReceiver = AudioContentReceiver()
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()
}
},
2000
)
}
}
private fun initAndVisibleMiniPlayer() {
binding.clMiniPlayer.visibility = View.VISIBLE
binding.clMiniPlayer.setOnClickListener { showPlayerFragment() }
binding.ivPlayerStop.setOnClickListener {
startService(
Intent(applicationContext, AudioContentPlayerService::class.java).apply {
action = "STOP_SERVICE"
}
)
}
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()
updateMediaMetadata(mediaController?.mediaMetadata)
binding.ivPlayerPlayOrPause.setImageResource(
if (mediaController!!.isPlaying) {
R.drawable.ic_player_pause
} else {
R.drawable.ic_player_play
}
)
binding.ivPlayerPlayOrPause.setOnClickListener {
mediaController?.let {
if (it.playWhenReady) {
it.pause()
} else {
it.play()
}
}
}
},
ContextCompat.getMainExecutor(applicationContext)
)
}
private fun updateMediaMetadata(metadata: MediaMetadata?) {
metadata?.let {
binding.tvPlayerTitle.text = it.title
binding.tvPlayerNickname.text = it.artist
binding.ivPlayerCover.load(it.artworkUri) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4f))
}
}
}
private fun setupMediaController() {
if (mediaController == null) {
deInitMiniPlayer()
return
}
mediaController!!.addListener(object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
updateMediaMetadata(mediaItem?.mediaMetadata)
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
binding.ivPlayerPlayOrPause.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 onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
executeDeeplink(intent) executeDeeplink(intent)
@ -64,9 +181,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
getMemberInfo() getMemberInfo()
getEventPopup() getEventPopup()
SharedPreferenceManager.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
if (SharedPreferenceManager.isPlayerServiceRunning) {
initAndVisibleMiniPlayer()
} else {
deInitMiniPlayer()
}
handler.postDelayed({ executeDeeplink(intent) }, 500) handler.postDelayed({ executeDeeplink(intent) }, 500)
} }
override fun onDestroy() {
deInitMiniPlayer()
SharedPreferenceManager.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
super.onDestroy()
}
private fun showPlayerFragment() {
val playerFragment = AudioContentPlayerFragment(screenWidth, arrayListOf())
playerFragment.show(supportFragmentManager, playerFragment.tag)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val intentFilter = IntentFilter(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER) val intentFilter = IntentFilter(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)

View File

@ -27,6 +27,7 @@ import kr.co.vividnext.sodalive.settings.terms.TermsActivity
import kr.co.vividnext.sodalive.splash.SplashActivity import kr.co.vividnext.sodalive.splash.SplashActivity
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@UnstableApi
class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsBinding::inflate) { class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsBinding::inflate) {
private val logoutDialog: SodaDialog by lazy { private val logoutDialog: SodaDialog by lazy {
SodaDialog( SodaDialog(
@ -159,7 +160,6 @@ class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsB
} }
} }
@UnstableApi
private fun logout() { private fun logout() {
startService( startService(
Intent(applicationContext, AudioContentPlayService::class.java).apply { Intent(applicationContext, AudioContentPlayService::class.java).apply {

View File

@ -81,6 +81,76 @@
android:src="@drawable/ic_noti_stop" /> android:src="@drawable/ic_noti_stop" />
</RelativeLayout> </RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_mini_player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/ll_tab"
android:background="@color/color_222222"
android:paddingHorizontal="13.3dp"
android:paddingVertical="10.7dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_player_cover"
android:layout_width="36.7dp"
android:layout_height="36.7dp"
android:layout_centerVertical="true"
android:contentDescription="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_player_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10.7dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/color_eeeeee"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@+id/iv_player_play_or_pause"
app:layout_constraintStart_toEndOf="@+id/iv_player_cover"
app:layout_constraintTop_toTopOf="@+id/iv_player_cover"
tools:text="JFLA 커버곡 Avicii for your self" />
<TextView
android:id="@+id/tv_player_nickname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2.3dp"
android:textColor="@color/color_d2d2d2"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="@+id/tv_player_title"
app:layout_constraintStart_toStartOf="@+id/tv_player_title"
app:layout_constraintTop_toBottomOf="@+id/tv_player_title"
tools:ignore="SmallSp"
tools:text="JFLA 커버곡 Avicii for your self" />
<ImageView
android:id="@+id/iv_player_play_or_pause"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="16dp"
android:contentDescription="@null"
app:layout_constraintBottom_toBottomOf="@+id/iv_player_stop"
app:layout_constraintEnd_toStartOf="@+id/iv_player_stop"
app:layout_constraintTop_toTopOf="@+id/iv_player_stop"
tools:src="@drawable/btn_bar_play" />
<ImageView
android:id="@+id/iv_player_stop"
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@null"
android:src="@drawable/ic_noti_stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout <LinearLayout
android:id="@+id/ll_tab" android:id="@+id/ll_tab"
android:layout_width="match_parent" android:layout_width="match_parent"