Remove static field leaks on SeekBar,
cleanup code and update baseline
This commit is contained in:
parent
d8b032e2e3
commit
8c2896ea16
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue