콘텐츠 메인

- 콘텐츠 미니 플레이어 추가
This commit is contained in:
klaus 2025-02-22 12:12:01 +09:00
parent 3a7df3f16e
commit 09ca0487f8
2 changed files with 410 additions and 1 deletions

View File

@ -1,22 +1,48 @@
package kr.co.vividnext.sodalive.audio_content.main.v2
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.graphics.Typeface
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
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.transform.RoundedCornersTransformation
import com.google.android.material.tabs.TabLayout
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.main.v2.alarm.AudioContentMainTabAlarmFragment
import kr.co.vividnext.sodalive.audio_content.main.v2.asmr.AudioContentMainTabAsmrFragment
import kr.co.vividnext.sodalive.audio_content.main.v2.content.AudioContentMainTabContentFragment
import kr.co.vividnext.sodalive.audio_content.main.v2.free.AudioContentMainTabFreeFragment
import kr.co.vividnext.sodalive.audio_content.main.v2.replay.AudioContentMainTabReplayFragment
import kr.co.vividnext.sodalive.audio_content.main.v2.series.AudioContentMainTabSeriesFragment
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.common.Constants
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentMainBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.mypage.alarm.AlarmListActivity
import kotlin.math.min
@ -30,6 +56,7 @@ enum class AudioContentMainTab {
}
}
@OptIn(UnstableApi::class)
class AudioContentMainActivity : BaseActivity<ActivityAudioContentMainBinding>(
ActivityAudioContentMainBinding::inflate
) {
@ -39,6 +66,43 @@ class AudioContentMainActivity : BaseActivity<ActivityAudioContentMainBinding>(
private var startTabPosition: AudioContentMainTab = AudioContentMainTab.SERIES
private var mediaController: MediaController? = null
private val handler = Handler(Looper.getMainLooper())
private val audioContentReceiver = AudioContentReceiver()
override fun onDestroy() {
deInitMiniPlayer()
SharedPreferenceManager.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
super.onDestroy()
}
private fun showPlayerFragment() {
val playerFragment = AudioContentPlayerFragment(screenWidth, arrayListOf())
playerFragment.show(supportFragmentManager, playerFragment.tag)
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(audioContentReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(audioContentReceiver, intentFilter)
}
startService(
Intent(this, AudioContentPlayService::class.java).apply {
action = AudioContentPlayService.MusicAction.INIT.name
}
)
}
override fun onPause() {
unregisterReceiver(audioContentReceiver)
super.onPause()
}
override fun setupView() {
startTabPosition = AudioContentMainTab.fromOrdinal(
intent.getIntExtra(
@ -50,6 +114,13 @@ class AudioContentMainActivity : BaseActivity<ActivityAudioContentMainBinding>(
setupToolbar()
loadFont()
setupTabs()
SharedPreferenceManager.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
if (SharedPreferenceManager.isPlayerServiceRunning) {
initAndVisibleMiniPlayer()
} else {
deInitMiniPlayer()
}
}
private fun setupToolbar() {
@ -149,4 +220,199 @@ class AudioContentMainActivity : BaseActivity<ActivityAudioContentMainBinding>(
private fun setTabFont(tab: TabLayout.Tab, font: Typeface?) {
(tab.view.getChildAt(1) as? TextView)?.typeface = font
}
private val preferenceChangeListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
// 특정 키에 대한 값이 변경될 때 UI 업데이트
if (key == Constants.PREF_IS_PLAYER_SERVICE_RUNNING) {
if (sharedPreferences.getBoolean(key, false)) {
handler.postDelayed(
{
initAndVisibleMiniPlayer()
},
1500
)
} else {
deInitMiniPlayer()
}
}
}
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
}
inner class AudioContentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val contentId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
val title = intent?.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE)
val nickname = intent?.getStringExtra(Constants.EXTRA_NICKNAME)
val coverImageUrl = intent?.getStringExtra(
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL
)
val isPlaying = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYING, false)
val isShowing = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_SHOWING, false)
if (isShowing == true) {
binding.rlMiniPlayer.visibility = View.VISIBLE
if (contentId != null && contentId > 0) {
binding.rlMiniPlayer.setOnClickListener {
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java)
.apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
}
)
}
}
if (isPlaying == true) {
binding.ivPlayOrPause.setImageResource(R.drawable.ic_noti_pause)
binding.ivPlayOrPause.setOnClickListener {
startService(
Intent(
this@AudioContentMainActivity,
AudioContentPlayService::class.java
).apply {
action = AudioContentPlayService.MusicAction.PAUSE.name
}
)
}
} else {
binding.ivPlayOrPause.setImageResource(R.drawable.ic_noti_play)
binding.ivPlayOrPause.setOnClickListener {
startService(
Intent(
this@AudioContentMainActivity,
AudioContentPlayService::class.java
).apply {
action = AudioContentPlayService.MusicAction.PLAY.name
}
)
}
}
binding.ivStop.setOnClickListener {
startService(
Intent(
this@AudioContentMainActivity,
AudioContentPlayService::class.java
).apply {
action = AudioContentPlayService.MusicAction.STOP.name
}
)
}
if (!title.isNullOrBlank()) {
binding.tvMiniPlayerTitle.text = title
}
if (!nickname.isNullOrBlank()) {
binding.tvNickname.text = nickname
}
if (!coverImageUrl.isNullOrBlank()) {
binding.ivCover.load(coverImageUrl) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
}
}
} else {
handler.post {
binding.ivPlayOrPause.setImageResource(0)
binding.ivCover.setImageResource(0)
binding.tvMiniPlayerTitle.text = ""
binding.tvNickname.text = ""
binding.rlMiniPlayer.visibility = View.GONE
binding.ivPlayOrPause.setOnClickListener {}
}
}
}
}
}

View File

@ -2,7 +2,8 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<include
android:id="@+id/toolbar"
@ -38,4 +39,146 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabs" />
<RelativeLayout
android:id="@+id/rl_mini_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@color/color_222222"
android:paddingHorizontal="13.3dp"
android:paddingVertical="10.7dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_cover"
android:layout_width="36.7dp"
android:layout_height="36.7dp"
android:layout_centerVertical="true"
android:contentDescription="@null"
tools:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginHorizontal="10.7dp"
android:layout_toStartOf="@+id/iv_play_or_pause"
android:layout_toEndOf="@+id/iv_cover"
android:orientation="vertical">
<TextView
android:id="@+id/tv_mini_player_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/color_eeeeee"
android:textSize="13sp"
tools:text="JFLA 커버곡 Avicii for your self" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2.3dp"
android:textColor="@color/color_d2d2d2"
android:textSize="11sp"
tools:ignore="SmallSp"
tools:text="JFLA 커버곡 Avicii for your self" />
</LinearLayout>
<ImageView
android:id="@+id/iv_play_or_pause"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_toStartOf="@+id/iv_stop"
android:contentDescription="@null"
tools:src="@drawable/btn_bar_play" />
<ImageView
android:id="@+id/iv_stop"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:contentDescription="@null"
android:src="@drawable/ic_noti_stop" />
</RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_mini_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
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>
</androidx.constraintlayout.widget.ConstraintLayout>