lofi-android/app/src/main/java/com/lofiradio/ui/PlayerFragment.kt
Jarian Cottingham cb74c3ddcf Initial commit: Lofi Radio Android app
Carved out from the lofi-app monorepo. Kotlin MVVM client with
HLS playback for the 20 curated lo-fi channels, including a
containerized build.
2026-08-21 18:36:45 +00:00

154 lines
5.3 KiB
Kotlin

package com.lofiradio.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import coil.load
import com.lofiradio.R
import com.lofiradio.databinding.FragmentPlayerBinding
import com.lofiradio.player.AudioPlayer
import com.lofiradio.util.Preferences
class PlayerFragment : Fragment() {
private var _binding: FragmentPlayerBinding? = null
private val binding get() = _binding!!
private val viewModel: LofiViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentPlayerBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupControls()
observeViewModel()
}
private fun setupControls() {
binding.btnPlayPause.setOnClickListener {
when (viewModel.playerState.value) {
LofiViewModel.PlayerState.PLAYING -> viewModel.pausePlayback()
LofiViewModel.PlayerState.PAUSED -> viewModel.resumePlayback()
LofiViewModel.PlayerState.IDLE -> {
val channel = viewModel.currentChannel.value
if (channel != null) {
viewModel.playChannel(channel)
}
}
else -> {
val channel = viewModel.currentChannel.value
if (channel != null) {
viewModel.playChannel(channel)
}
}
}
}
binding.btnNext.setOnClickListener {
viewModel.playNext()
}
binding.btnPrevious.setOnClickListener {
viewModel.playPrevious()
}
binding.volumeSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
viewModel.setVolume(progress / 100f)
binding.volumeValue.text = "$progress%"
}
}
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
})
binding.btnRetry.setOnClickListener {
viewModel.retry()
}
}
private fun observeViewModel() {
viewModel.currentChannel.observe(viewLifecycleOwner) { channel ->
channel?.let {
binding.playerChannelName.text = it.name
binding.playerVideoTitle.text = it.description
val thumbUrl = it.liveThumbnail ?: it.thumbnail
if (!thumbUrl.isNullOrBlank()) {
binding.playerThumbnail.load(thumbUrl) {
crossfade(true)
}
}
binding.playerMode.text = when (viewModel.getStreamingMode()) {
Preferences.StreamingMode.SERVER -> "Server Mode"
Preferences.StreamingMode.DIRECT -> "Direct Mode"
}
} ?: run {
binding.playerChannelName.text = "No Channel Selected"
binding.playerVideoTitle.text = ""
binding.playerMode.text = ""
}
}
viewModel.playerState.observe(viewLifecycleOwner) { state ->
updatePlayerUi(state)
}
viewModel.isLoading.observe(viewLifecycleOwner) { loading ->
if (loading) {
binding.playerState.text = "Loading stream..."
binding.playerProgress.isIndeterminate = true
} else {
binding.playerProgress.isIndeterminate = false
}
}
viewModel.error.observe(viewLifecycleOwner) { error ->
if (error != null) {
binding.errorContainer.visibility = View.VISIBLE
binding.errorMessage.text = error
} else {
binding.errorContainer.visibility = View.GONE
}
}
}
private fun updatePlayerUi(state: LofiViewModel.PlayerState) {
binding.playerState.text = when (state) {
LofiViewModel.PlayerState.IDLE -> "No stream playing"
LofiViewModel.PlayerState.PLAYING -> "Playing"
LofiViewModel.PlayerState.PAUSED -> "Paused"
LofiViewModel.PlayerState.BUFFERING -> "Buffering..."
LofiViewModel.PlayerState.ERROR -> "Error"
LofiViewModel.PlayerState.ENDED -> "Stream ended"
}
binding.btnPlayPause.setImageResource(
when (state) {
LofiViewModel.PlayerState.PLAYING -> android.R.drawable.ic_media_pause
else -> android.R.drawable.ic_btn_now_playing
}
)
binding.playerProgress.visibility = if (state == LofiViewModel.PlayerState.BUFFERING) {
View.VISIBLE
} else {
View.INVISIBLE
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}