Provide Scope differently

This commit is contained in:
tzugen 2021-11-16 19:14:47 +01:00
parent a327a5b390
commit 51d6a23208
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
1 changed files with 91 additions and 94 deletions

View File

@ -35,7 +35,6 @@ import android.widget.TextView
import android.widget.ViewFlipper import android.widget.ViewFlipper
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation import androidx.navigation.Navigation
import com.mobeta.android.dslv.DragSortListView import com.mobeta.android.dslv.DragSortListView
import com.mobeta.android.dslv.DragSortListView.DragSortListener import com.mobeta.android.dslv.DragSortListView.DragSortListener
@ -53,7 +52,8 @@ import java.util.concurrent.TimeUnit
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -88,12 +88,13 @@ import timber.log.Timber
/** /**
* Contains the Music Player screen of Ultrasonic with playback controls and the playlist * Contains the Music Player screen of Ultrasonic with playback controls and the playlist
*
* TODO: This class was more or less straight converted from Java legacy code.
* There are many places where further cleanup would be nice.
*/ */
@Suppress("LargeClass", "TooManyFunctions", "MagicNumber") @Suppress("LargeClass", "TooManyFunctions", "MagicNumber")
class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinComponent { class PlayerFragment :
Fragment(),
GestureDetector.OnGestureListener,
KoinComponent,
CoroutineScope by CoroutineScope(Dispatchers.Main) {
private var swipeDistance = 0 private var swipeDistance = 0
private var swipeVelocity = 0 private var swipeVelocity = 0
private var jukeboxAvailable = false private var jukeboxAvailable = false
@ -114,9 +115,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
private lateinit var executorService: ScheduledExecutorService private lateinit var executorService: ScheduledExecutorService
private var currentPlaying: DownloadFile? = null private var currentPlaying: DownloadFile? = null
private var currentSong: MusicDirectory.Entry? = null private var currentSong: MusicDirectory.Entry? = null
private var onProgressChangedTask: Job? = null
private var rxBusSubscription: Disposable? = null private var rxBusSubscription: Disposable? = null
private val scope: CoroutineScope = viewLifecycleOwner.lifecycleScope
// Views and UI Elements // Views and UI Elements
private lateinit var visualizerViewLayout: LinearLayout private lateinit var visualizerViewLayout: LinearLayout
@ -236,7 +235,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
previousButton.setOnClickListener { previousButton.setOnClickListener {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
mediaPlayerController.previous() mediaPlayerController.previous()
onCurrentChanged() onCurrentChanged()
onSliderProgressChanged() onSliderProgressChanged()
@ -250,7 +249,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
nextButton.setOnClickListener { nextButton.setOnClickListener {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
mediaPlayerController.next() mediaPlayerController.next()
onCurrentChanged() onCurrentChanged()
onSliderProgressChanged() onSliderProgressChanged()
@ -262,27 +261,29 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
changeProgress(incrementTime) changeProgress(incrementTime)
} }
pauseButton.setOnClickListener { pauseButton.setOnClickListener {
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
mediaPlayerController.pause() mediaPlayerController.pause()
onCurrentChanged() onCurrentChanged()
onSliderProgressChanged() onSliderProgressChanged()
} }
} }
stopButton.setOnClickListener { stopButton.setOnClickListener {
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
mediaPlayerController.reset() mediaPlayerController.reset()
onCurrentChanged() onCurrentChanged()
onSliderProgressChanged() onSliderProgressChanged()
} }
} }
startButton.setOnClickListener { startButton.setOnClickListener {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
start() start()
onCurrentChanged() onCurrentChanged()
onSliderProgressChanged() onSliderProgressChanged()
} }
} }
shuffleButton.setOnClickListener { shuffleButton.setOnClickListener {
mediaPlayerController.shuffle() mediaPlayerController.shuffle()
Util.toast(activity, R.string.download_menu_shuffle_notification) Util.toast(activity, R.string.download_menu_shuffle_notification)
@ -309,7 +310,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onStopTrackingTouch(seekBar: SeekBar) { override fun onStopTrackingTouch(seekBar: SeekBar) {
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
mediaPlayerController.seekTo(progressBar.progress) mediaPlayerController.seekTo(progressBar.progress)
onSliderProgressChanged() onSliderProgressChanged()
} }
@ -321,12 +322,13 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
playlistView.setOnItemClickListener { _, _, position, _ -> playlistView.setOnItemClickListener { _, _, position, _ ->
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
mediaPlayerController.play(position) mediaPlayerController.play(position)
onCurrentChanged() onCurrentChanged()
onSliderProgressChanged() onSliderProgressChanged()
} }
} }
registerForContextMenu(playlistView) registerForContextMenu(playlistView)
if (arguments != null && requireArguments().getBoolean( if (arguments != null && requireArguments().getBoolean(
@ -388,7 +390,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
// Query the Jukebox state off-thread // Query the Jukebox state off-thread
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
try { try {
jukeboxAvailable = mediaPlayerController.isJukeboxAvailable jukeboxAvailable = mediaPlayerController.isJukeboxAvailable
} catch (all: Exception) { } catch (all: Exception) {
@ -450,6 +452,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
override fun onDestroyView() { override fun onDestroyView() {
rxBusSubscription?.dispose() rxBusSubscription?.dispose()
cancel("CoroutineScope cancelled because the view was destroyed")
cancellationToken.cancel() cancellationToken.cancel()
super.onDestroyView() super.onDestroyView()
} }
@ -779,7 +782,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
Util.toast(context, resources.getString(R.string.download_playlist_saving, playlistName)) Util.toast(context, resources.getString(R.string.download_playlist_saving, playlistName))
mediaPlayerController.suggestedPlaylistName = playlistName mediaPlayerController.suggestedPlaylistName = playlistName
scope.launch { launch {
val entries: MutableList<MusicDirectory.Entry> = LinkedList() val entries: MutableList<MusicDirectory.Entry> = LinkedList()
for (downloadFile in mediaPlayerController.playList) { for (downloadFile in mediaPlayerController.playList) {
entries.add(downloadFile.song) entries.add(downloadFile.song)
@ -930,94 +933,88 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
@Suppress("LongMethod", "ComplexMethod") @Suppress("LongMethod", "ComplexMethod")
@Synchronized
private fun onSliderProgressChanged() { private fun onSliderProgressChanged() {
if (onProgressChangedTask != null) {
return val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled
val millisPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration: Int = mediaPlayerController.playerDuration
val playerState: PlayerState = mediaPlayerController.playerState
if (cancellationToken.isCancellationRequested) return
if (currentPlaying != null) {
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
durationTextView.text = Util.formatTotalDuration(duration.toLong(), true)
progressBar.max =
if (duration == 0) 100 else duration // Work-around for apparent bug.
progressBar.progress = millisPlayed
progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled
} else {
positionTextView.setText(R.string.util_zero_time)
durationTextView.setText(R.string.util_no_time)
progressBar.progress = 0
progressBar.max = 0
progressBar.isEnabled = false
} }
onProgressChangedTask = scope.launch(CommunicationError.getHandler(context)) { when (playerState) {
PlayerState.DOWNLOADING -> {
val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled val progress =
val millisPlayed = max(0, mediaPlayerController.playerPosition) if (currentPlaying != null) currentPlaying!!.progress.value!! else 0
val duration = mediaPlayerController.playerDuration val downloadStatus = resources.getString(
val playerState = mediaPlayerController.playerState R.string.download_playerstate_downloading,
Util.formatPercentage(progress)
if (cancellationToken.isCancellationRequested) return@launch
if (currentPlaying != null) {
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
durationTextView.text = Util.formatTotalDuration(duration.toLong(), true)
progressBar.max =
if (duration == 0) 100 else duration // Work-around for apparent bug.
progressBar.progress = millisPlayed
progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled
} else {
positionTextView.setText(R.string.util_zero_time)
durationTextView.setText(R.string.util_no_time)
progressBar.progress = 0
progressBar.max = 0
progressBar.isEnabled = false
}
when (playerState) {
PlayerState.DOWNLOADING -> {
val progress =
if (currentPlaying != null) currentPlaying!!.progress.value!! else 0
val downloadStatus = resources.getString(
R.string.download_playerstate_downloading,
Util.formatPercentage(progress)
)
setTitle(this@PlayerFragment, downloadStatus)
}
PlayerState.PREPARING -> setTitle(
this@PlayerFragment,
R.string.download_playerstate_buffering
) )
PlayerState.STARTED -> { setTitle(this@PlayerFragment, downloadStatus)
if (mediaPlayerController.isShufflePlayEnabled) {
setTitle(
this@PlayerFragment,
R.string.download_playerstate_playing_shuffle
)
} else {
setTitle(this@PlayerFragment, R.string.common_appname)
}
}
PlayerState.IDLE,
PlayerState.PREPARED,
PlayerState.STOPPED,
PlayerState.PAUSED,
PlayerState.COMPLETED -> {
}
else -> setTitle(this@PlayerFragment, R.string.common_appname)
} }
PlayerState.PREPARING -> setTitle(
when (playerState) { this@PlayerFragment,
PlayerState.STARTED -> { R.string.download_playerstate_buffering
pauseButton.isVisible = true )
stopButton.isVisible = false PlayerState.STARTED -> {
startButton.isVisible = false if (mediaPlayerController.isShufflePlayEnabled) {
} setTitle(
PlayerState.DOWNLOADING, PlayerState.PREPARING -> { this@PlayerFragment,
pauseButton.isVisible = false R.string.download_playerstate_playing_shuffle
stopButton.isVisible = true )
startButton.isVisible = false } else {
} setTitle(this@PlayerFragment, R.string.common_appname)
else -> {
pauseButton.isVisible = false
stopButton.isVisible = false
startButton.isVisible = true
} }
} }
PlayerState.IDLE,
// TODO: It would be a lot nicer if MediaPlayerController would send an event PlayerState.PREPARED,
// when this is necessary instead of updating every time PlayerState.STOPPED,
displaySongRating() PlayerState.PAUSED,
onProgressChangedTask = null PlayerState.COMPLETED -> {
}
else -> setTitle(this@PlayerFragment, R.string.common_appname)
} }
when (playerState) {
PlayerState.STARTED -> {
pauseButton.isVisible = true
stopButton.isVisible = false
startButton.isVisible = false
}
PlayerState.DOWNLOADING, PlayerState.PREPARING -> {
pauseButton.isVisible = false
stopButton.isVisible = true
startButton.isVisible = false
}
else -> {
pauseButton.isVisible = false
stopButton.isVisible = false
startButton.isVisible = true
}
}
// TODO: It would be a lot nicer if MediaPlayerController would send an event
// when this is necessary instead of updating every time
displaySongRating()
} }
private fun changeProgress(ms: Int) { private fun changeProgress(ms: Int) {
scope.launch(CommunicationError.getHandler(context)) { launch(CommunicationError.getHandler(context)) {
val msPlayed: Int = max(0, mediaPlayerController.playerPosition) val msPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration = mediaPlayerController.playerDuration val duration = mediaPlayerController.playerDuration
val seekTo = (msPlayed + ms).coerceAtMost(duration) val seekTo = (msPlayed + ms).coerceAtMost(duration)