Carved out from the lofi-app monorepo. Kotlin MVVM client with HLS playback for the 20 curated lo-fi channels, including a containerized build.
154 lines
5.3 KiB
Kotlin
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
|
|
}
|
|
} |