Remove static field leaks on SeekBar,

cleanup code and update baseline
This commit is contained in:
tzugen 2021-06-19 23:09:38 +02:00
parent d8b032e2e3
commit 8c2896ea16
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
5 changed files with 329 additions and 397 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,14 @@
/*
* PlayerFragment.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.fragment package org.moire.ultrasonic.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context
import android.graphics.Point import android.graphics.Point
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
@ -31,6 +37,20 @@ import androidx.fragment.app.Fragment
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
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Date
import java.util.LinkedList
import java.util.Locale
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import kotlin.math.abs
import kotlin.math.max
import org.koin.android.ext.android.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.audiofx.EqualizerController import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController import org.moire.ultrasonic.audiofx.VisualizerController
@ -42,6 +62,7 @@ import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.DownloadFile import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.LocalMediaPlayer
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
@ -49,29 +70,19 @@ import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.SilentBackgroundTask
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.AutoRepeatButton import org.moire.ultrasonic.view.AutoRepeatButton
import org.moire.ultrasonic.view.SongListAdapter import org.moire.ultrasonic.view.SongListAdapter
import org.moire.ultrasonic.view.VisualizerView import org.moire.ultrasonic.view.VisualizerView
import timber.log.Timber import timber.log.Timber
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Date
import java.util.LinkedList
import java.util.Locale
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import kotlin.math.abs
import org.koin.android.ext.android.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.moire.ultrasonic.util.SilentBackgroundTask
import kotlin.math.max
/** /**
* 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.
* The usage of threads and SilentBackgroundTask can be replaced with Coroutines.
*/ */
@Suppress("LargeClass", "TooManyFunctions", "MagicNumber") @Suppress("LargeClass", "TooManyFunctions", "MagicNumber")
class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinComponent { class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinComponent {
@ -84,7 +95,6 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
private var isEqualizerAvailable = false private var isEqualizerAvailable = false
private var isVisualizerAvailable = false private var isVisualizerAvailable = false
// Detectors & Callbacks // Detectors & Callbacks
private lateinit var gestureScanner: GestureDetector private lateinit var gestureScanner: GestureDetector
private lateinit var cancellationToken: CancellationToken private lateinit var cancellationToken: CancellationToken
@ -92,6 +102,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
// Data & Services // Data & Services
private val networkAndStorageChecker: NetworkAndStorageChecker by inject() private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
private val mediaPlayerController: MediaPlayerController by inject() private val mediaPlayerController: MediaPlayerController by inject()
private val localMediaPlayer: LocalMediaPlayer by inject()
private val shareHandler: ShareHandler by inject() private val shareHandler: ShareHandler by inject()
private val imageLoaderProvider: ImageLoaderProvider by inject() private val imageLoaderProvider: ImageLoaderProvider by inject()
private lateinit var executorService: ScheduledExecutorService private lateinit var executorService: ScheduledExecutorService
@ -126,6 +137,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
private lateinit var repeatButton: ImageView private lateinit var repeatButton: ImageView
private lateinit var hollowStar: Drawable private lateinit var hollowStar: Drawable
private lateinit var fullStar: Drawable private lateinit var fullStar: Drawable
private lateinit var progressBar: SeekBar
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context) Util.applyTheme(this.context)
@ -133,7 +145,8 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
return inflater.inflate(R.layout.current_playing, container, false) return inflater.inflate(R.layout.current_playing, container, false)
@ -163,7 +176,6 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
fiveStar3ImageView = view.findViewById(R.id.song_five_star_3) fiveStar3ImageView = view.findViewById(R.id.song_five_star_3)
fiveStar4ImageView = view.findViewById(R.id.song_five_star_4) fiveStar4ImageView = view.findViewById(R.id.song_five_star_4)
fiveStar5ImageView = view.findViewById(R.id.song_five_star_5) fiveStar5ImageView = view.findViewById(R.id.song_five_star_5)
} }
@Suppress("LongMethod") @Suppress("LongMethod")
@ -183,6 +195,14 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
swipeVelocity = swipeDistance swipeVelocity = swipeDistance
gestureScanner = GestureDetector(context, this) gestureScanner = GestureDetector(context, this)
// The secondary progress is an indicator of how far the song is cached.
localMediaPlayer.secondaryProgress.observe(
viewLifecycleOwner,
{
progressBar.secondaryProgress = it
}
)
findViews(view) findViews(view)
val previousButton: AutoRepeatButton = view.findViewById(R.id.button_previous) val previousButton: AutoRepeatButton = view.findViewById(R.id.button_previous)
val nextButton: AutoRepeatButton = view.findViewById(R.id.button_next) val nextButton: AutoRepeatButton = view.findViewById(R.id.button_next)
@ -311,11 +331,11 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
} }
progressBar!!.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onStopTrackingTouch(seekBar: SeekBar) { override fun onStopTrackingTouch(seekBar: SeekBar) {
object : SilentBackgroundTask<Void?>(activity) { object : SilentBackgroundTask<Void?>(activity) {
override fun doInBackground(): Void? { override fun doInBackground(): Void? {
mediaPlayerController.seekTo(progressBar!!.progress) mediaPlayerController.seekTo(progressBar.progress)
return null return null
} }
@ -328,7 +348,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
override fun onStartTrackingTouch(seekBar: SeekBar) {} override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {} override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
}) })
playlistView.setOnItemClickListener { _, _, position, _ -> playlistView.setOnItemClickListener { _, _, position, _ ->
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
object : SilentBackgroundTask<Void?>(activity) { object : SilentBackgroundTask<Void?>(activity) {
@ -346,53 +366,59 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
registerForContextMenu(playlistView) registerForContextMenu(playlistView)
if (arguments != null && requireArguments().getBoolean( if (arguments != null && requireArguments().getBoolean(
Constants.INTENT_EXTRA_NAME_SHUFFLE, Constants.INTENT_EXTRA_NAME_SHUFFLE,
false false
) )
) { ) {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.isShufflePlayEnabled = true mediaPlayerController.isShufflePlayEnabled = true
} }
visualizerViewLayout.visibility = View.GONE visualizerViewLayout.visibility = View.GONE
VisualizerController.get().observe(requireActivity(), { visualizerController -> VisualizerController.get().observe(
if (visualizerController != null) { requireActivity(),
Timber.d("VisualizerController Observer.onChanged received controller") { visualizerController ->
visualizerView = VisualizerView(context) if (visualizerController != null) {
visualizerViewLayout.addView( Timber.d("VisualizerController Observer.onChanged received controller")
visualizerView, visualizerView = VisualizerView(context)
LinearLayout.LayoutParams( visualizerViewLayout.addView(
LinearLayout.LayoutParams.MATCH_PARENT, visualizerView,
LinearLayout.LayoutParams.MATCH_PARENT LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
) )
) if (!visualizerView.isActive) {
if (!visualizerView.isActive) { visualizerViewLayout.visibility = View.GONE
visualizerViewLayout.visibility = View.GONE } else {
visualizerViewLayout.visibility = View.VISIBLE
}
visualizerView.setOnTouchListener { _, _ ->
visualizerView.isActive = !visualizerView.isActive
mediaPlayerController.showVisualization = visualizerView.isActive
true
}
isVisualizerAvailable = true
} else { } else {
visualizerViewLayout.visibility = View.VISIBLE Timber.d("VisualizerController Observer.onChanged has no controller")
visualizerViewLayout.visibility = View.GONE
isVisualizerAvailable = false
} }
visualizerView.setOnTouchListener { _, _ ->
visualizerView.isActive = !visualizerView.isActive
mediaPlayerController.showVisualization = visualizerView.isActive
true
}
isVisualizerAvailable = true
} else {
Timber.d("VisualizerController Observer.onChanged has no controller")
visualizerViewLayout.visibility = View.GONE
isVisualizerAvailable = false
} }
}) )
EqualizerController.get().observe(requireActivity(), { equalizerController -> EqualizerController.get().observe(
isEqualizerAvailable = if (equalizerController != null) { requireActivity(),
Timber.d("EqualizerController Observer.onChanged received controller") { equalizerController ->
true isEqualizerAvailable = if (equalizerController != null) {
} else { Timber.d("EqualizerController Observer.onChanged received controller")
Timber.d("EqualizerController Observer.onChanged has no controller") true
false } else {
Timber.d("EqualizerController Observer.onChanged has no controller")
false
}
} }
}) )
Thread { Thread {
try { try {
jukeboxAvailable = mediaPlayerController.isJukeboxAvailable jukeboxAvailable = mediaPlayerController.isJukeboxAvailable
@ -423,7 +449,9 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} }
visualizerView.isActive = mediaPlayerController.showVisualization if (::visualizerView.isInitialized) {
visualizerView.isActive = mediaPlayerController.showVisualization
}
requireActivity().invalidateOptionsMenu() requireActivity().invalidateOptionsMenu()
} }
@ -452,7 +480,9 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
executorService.shutdown() executorService.shutdown()
visualizerView.isActive = false if (::visualizerView.isInitialized) {
visualizerView.isActive = mediaPlayerController.showVisualization
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -583,7 +613,8 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.artist) bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.artist)
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.artistId) bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.artistId)
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true) bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true)
Navigation.findNavController(view!!).navigate(R.id.playerToSelectAlbum, bundle) Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
} }
return true return true
} }
@ -597,7 +628,8 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.album) bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.album)
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent) bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent)
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true) bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true)
Navigation.findNavController(view!!).navigate(R.id.playerToSelectAlbum, bundle) Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
return true return true
} }
R.id.menu_lyrics -> { R.id.menu_lyrics -> {
@ -607,7 +639,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
bundle = Bundle() bundle = Bundle()
bundle.putString(Constants.INTENT_EXTRA_NAME_ARTIST, entry.artist) bundle.putString(Constants.INTENT_EXTRA_NAME_ARTIST, entry.artist)
bundle.putString(Constants.INTENT_EXTRA_NAME_TITLE, entry.title) bundle.putString(Constants.INTENT_EXTRA_NAME_TITLE, entry.title)
Navigation.findNavController(view!!).navigate(R.id.playerToLyrics, bundle) Navigation.findNavController(requireView()).navigate(R.id.playerToLyrics, bundle)
return true return true
} }
R.id.menu_remove -> { R.id.menu_remove -> {
@ -616,11 +648,12 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
return true return true
} }
R.id.menu_item_screen_on_off -> { R.id.menu_item_screen_on_off -> {
val window = requireActivity().window
if (mediaPlayerController.keepScreenOn) { if (mediaPlayerController.keepScreenOn) {
activity!!.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
mediaPlayerController.keepScreenOn = false mediaPlayerController.keepScreenOn = false
} else { } else {
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
mediaPlayerController.keepScreenOn = true mediaPlayerController.keepScreenOn = true
} }
return true return true
@ -631,7 +664,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
return true return true
} }
R.id.menu_item_equalizer -> { R.id.menu_item_equalizer -> {
Navigation.findNavController(view!!).navigate(R.id.playerToEqualizer) Navigation.findNavController(requireView()).navigate(R.id.playerToEqualizer)
return true return true
} }
R.id.menu_item_visualizer -> { R.id.menu_item_visualizer -> {
@ -645,7 +678,8 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
mediaPlayerController.showVisualization = visualizerView.isActive mediaPlayerController.showVisualization = visualizerView.isActive
Util.toast( Util.toast(
context, context,
if (active) R.string.download_visualizer_on else R.string.download_visualizer_off if (active) R.string.download_visualizer_on
else R.string.download_visualizer_off
) )
return true return true
} }
@ -654,7 +688,8 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
mediaPlayerController.isJukeboxEnabled = jukeboxEnabled mediaPlayerController.isJukeboxEnabled = jukeboxEnabled
Util.toast( Util.toast(
context, context,
if (jukeboxEnabled) R.string.download_jukebox_on else R.string.download_jukebox_off, if (jukeboxEnabled) R.string.download_jukebox_on
else R.string.download_jukebox_off,
false false
) )
return true return true
@ -718,7 +753,10 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
Timber.e(all) Timber.e(all)
} }
}.start() }.start()
val msg = resources.getString(R.string.download_bookmark_set_at_position, bookmarkTime) val msg = resources.getString(
R.string.download_bookmark_set_at_position,
bookmarkTime
)
Util.toast(context, msg) Util.toast(context, msg)
return true return true
} }
@ -788,7 +826,8 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
override fun error(error: Throwable) { override fun error(error: Throwable) {
Timber.e(error, "Exception has occurred in savePlaylistInBackground") Timber.e(error, "Exception has occurred in savePlaylistInBackground")
val msg = String.format(Locale.ROOT, val msg = String.format(
Locale.ROOT,
"%s %s", "%s %s",
resources.getString(R.string.download_playlist_error), resources.getString(R.string.download_playlist_error),
getErrorMessage(error) getErrorMessage(error)
@ -818,8 +857,9 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
private fun start() { private fun start() {
val service = mediaPlayerController val service = mediaPlayerController
val state = service.playerState val state = service.playerState
if (state === PlayerState.PAUSED if (state === PlayerState.PAUSED ||
|| state === PlayerState.COMPLETED || state === PlayerState.STOPPED) { state === PlayerState.COMPLETED || state === PlayerState.STOPPED
) {
service.start() service.start()
} else if (state === PlayerState.IDLE) { } else if (state === PlayerState.IDLE) {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
@ -947,16 +987,16 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
val millisTotal = if (duration == null) 0 else duration!! val millisTotal = if (duration == null) 0 else duration!!
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true) positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
durationTextView.text = Util.formatTotalDuration(millisTotal.toLong(), true) durationTextView.text = Util.formatTotalDuration(millisTotal.toLong(), true)
progressBar!!.max = progressBar.max =
if (millisTotal == 0) 100 else millisTotal // Work-around for apparent bug. if (millisTotal == 0) 100 else millisTotal // Work-around for apparent bug.
progressBar!!.progress = millisPlayed progressBar.progress = millisPlayed
progressBar!!.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled
} else { } else {
positionTextView.setText(R.string.util_zero_time) positionTextView.setText(R.string.util_zero_time)
durationTextView.setText(R.string.util_no_time) durationTextView.setText(R.string.util_no_time)
progressBar!!.progress = 0 progressBar.progress = 0
progressBar!!.max = 0 progressBar.max = 0
progressBar!!.isEnabled = false progressBar.isEnabled = false
} }
when (playerState) { when (playerState) {
@ -1034,7 +1074,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
override fun done(result: Void?) { override fun done(result: Void?) {
progressBar!!.progress = seekTo progressBar.progress = seekTo
} }
}.execute() }.execute()
} }
@ -1109,8 +1149,12 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
private fun displaySongRating() { private fun displaySongRating() {
val rating = var rating = 0
if (currentSong == null || currentSong!!.userRating == null) 0 else currentSong!!.userRating!!
if (currentSong?.userRating != null) {
rating = currentSong!!.userRating!!
}
fiveStar1ImageView.setImageDrawable(if (rating > 0) fullStar else hollowStar) fiveStar1ImageView.setImageDrawable(if (rating > 0) fullStar else hollowStar)
fiveStar2ImageView.setImageDrawable(if (rating > 1) fullStar else hollowStar) fiveStar2ImageView.setImageDrawable(if (rating > 1) fullStar else hollowStar)
fiveStar3ImageView.setImageDrawable(if (rating > 2) fullStar else hollowStar) fiveStar3ImageView.setImageDrawable(if (rating > 2) fullStar else hollowStar)
@ -1125,15 +1169,10 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
} }
private fun showSavePlaylistDialog() { private fun showSavePlaylistDialog() {
val layoutInflater = val layout = LayoutInflater.from(this.context).inflate(R.layout.save_playlist, null)
requireContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout = layoutInflater.inflate( playlistNameView = layout.findViewById(R.id.save_playlist_name)
R.layout.save_playlist,
requireActivity().findViewById<View>(R.id.save_playlist_root) as ViewGroup
)
if (layout != null) {
playlistNameView = layout.findViewById(R.id.save_playlist_name)
}
val builder: AlertDialog.Builder = AlertDialog.Builder(context) val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder.setTitle(R.string.download_playlist_title) builder.setTitle(R.string.download_playlist_title)
builder.setMessage(R.string.download_playlist_name) builder.setMessage(R.string.download_playlist_name)
@ -1160,8 +1199,5 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
companion object { companion object {
private const val PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5 private const val PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5
var progressBar // TODO: Refactor this to not be static
: SeekBar? = null
private set
} }
} }

View File

@ -20,6 +20,7 @@ import android.os.Looper
import android.os.PowerManager import android.os.PowerManager
import android.os.PowerManager.PARTIAL_WAKE_LOCK import android.os.PowerManager.PARTIAL_WAKE_LOCK
import android.os.PowerManager.WakeLock import android.os.PowerManager.WakeLock
import androidx.lifecycle.MutableLiveData
import java.io.File import java.io.File
import java.net.URLEncoder import java.net.URLEncoder
import java.util.Locale import java.util.Locale
@ -29,7 +30,6 @@ import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController import org.moire.ultrasonic.audiofx.VisualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.PlayerState import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.fragment.PlayerFragment
import org.moire.ultrasonic.util.CancellableTask import org.moire.ultrasonic.util.CancellableTask
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.StreamProxy import org.moire.ultrasonic.util.StreamProxy
@ -79,10 +79,12 @@ class LocalMediaPlayer(
private var proxy: StreamProxy? = null private var proxy: StreamProxy? = null
private var bufferTask: CancellableTask? = null private var bufferTask: CancellableTask? = null
private var positionCache: PositionCache? = null private var positionCache: PositionCache? = null
private var secondaryProgress = -1
private val pm = context.getSystemService(POWER_SERVICE) as PowerManager private val pm = context.getSystemService(POWER_SERVICE) as PowerManager
private val wakeLock: WakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, this.javaClass.name) private val wakeLock: WakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, this.javaClass.name)
val secondaryProgress: MutableLiveData<Int> = MutableLiveData(0)
fun init() { fun init() {
Thread { Thread {
Thread.currentThread().name = "MediaPlayerThread" Thread.currentThread().name = "MediaPlayerThread"
@ -357,7 +359,6 @@ class LocalMediaPlayer(
downloadFile.updateModificationDate() downloadFile.updateModificationDate()
mediaPlayer.setOnCompletionListener(null) mediaPlayer.setOnCompletionListener(null)
secondaryProgress = -1 // Ensure seeking in non StreamProxy playback works
setPlayerState(PlayerState.IDLE) setPlayerState(PlayerState.IDLE)
setAudioAttributes(mediaPlayer) setAudioAttributes(mediaPlayer)
@ -388,28 +389,28 @@ class LocalMediaPlayer(
setPlayerState(PlayerState.PREPARING) setPlayerState(PlayerState.PREPARING)
mediaPlayer.setOnBufferingUpdateListener { mp, percent -> mediaPlayer.setOnBufferingUpdateListener { mp, percent ->
val progressBar = PlayerFragment.progressBar
val song = downloadFile.song val song = downloadFile.song
if (percent == 100) { if (percent == 100) {
mp.setOnBufferingUpdateListener(null) mp.setOnBufferingUpdateListener(null)
} }
secondaryProgress = (percent.toDouble() / 100.toDouble() * progressBar.max).toInt() // The secondary progress is an indicator of how far the song is cached.
if (song.transcodedContentType == null && Util.getMaxBitRate() == 0) { if (song.transcodedContentType == null && Util.getMaxBitRate() == 0) {
progressBar?.secondaryProgress = secondaryProgress val progress = (percent.toDouble() / 100.toDouble() * playerDuration).toInt()
secondaryProgress.postValue(progress)
} }
} }
mediaPlayer.setOnPreparedListener { mediaPlayer.setOnPreparedListener {
Timber.i("Media player prepared") Timber.i("Media player prepared")
setPlayerState(PlayerState.PREPARED) setPlayerState(PlayerState.PREPARED)
val progressBar = PlayerFragment.progressBar
if (progressBar != null && downloadFile.isWorkDone) { // Populate seek bar secondary progress if we have a complete file for consistency
// Populate seek bar secondary progress if we have a complete file for consistency if (downloadFile.isWorkDone) {
PlayerFragment.progressBar.secondaryProgress = 100 * progressBar.max secondaryProgress.postValue(playerDuration)
} }
synchronized(this@LocalMediaPlayer) { synchronized(this@LocalMediaPlayer) {
if (position != 0) { if (position != 0) {
Timber.i("Restarting player from position %d", position) Timber.i("Restarting player from position %d", position)

View File

@ -386,12 +386,6 @@ class MediaPlayerController(
@get:Synchronized @get:Synchronized
val playerDuration: Int val playerDuration: Int
get() { get() {
if (localMediaPlayer.currentPlaying != null) {
val duration = localMediaPlayer.currentPlaying!!.song.duration
if (duration != null) {
return duration * 1000
}
}
val mediaPlayerService = runningInstance ?: return 0 val mediaPlayerService = runningInstance ?: return 0
return mediaPlayerService.playerDuration return mediaPlayerService.playerDuration
} }

View File

@ -1,21 +1,10 @@
/* /*
This file is part of Subsonic. * SilentBackgroundTask.kt
* Copyright (C) 2009-2021 Ultrasonic developers
Subsonic is free software: you can redistribute it and/or modify *
it under the terms of the GNU General Public License as published by * Distributed under terms of the GNU GPLv3 license.
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus
*/ */
package org.moire.ultrasonic.util package org.moire.ultrasonic.util
import android.app.Activity import android.app.Activity