ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt

1278 lines
48 KiB
Kotlin
Raw Normal View History

/*
* PlayerFragment.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
2021-06-19 20:05:38 +02:00
package org.moire.ultrasonic.fragment
2021-06-19 20:42:03 +02:00
import android.annotation.SuppressLint
2021-06-19 20:05:38 +02:00
import android.app.AlertDialog
import android.graphics.Point
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
2022-04-03 23:57:50 +02:00
import android.os.Looper
import android.view.ContextMenu
2021-06-19 20:05:38 +02:00
import android.view.ContextMenu.ContextMenuInfo
import android.view.GestureDetector
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
2021-06-19 20:05:38 +02:00
import android.view.animation.AnimationUtils
import android.widget.AdapterView.AdapterContextMenuInfo
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SeekBar
2021-06-19 20:05:38 +02:00
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
2022-07-02 01:27:12 +02:00
import android.widget.Toast
import android.widget.ViewFlipper
import androidx.core.view.isVisible
2021-06-19 20:05:38 +02:00
import androidx.fragment.app.Fragment
2022-07-02 01:27:12 +02:00
import androidx.media3.common.HeartRating
2022-04-03 23:57:50 +02:00
import androidx.media3.common.Player
import androidx.media3.common.Timeline
2022-07-02 01:27:12 +02:00
import androidx.media3.session.SessionResult
2021-06-19 20:05:38 +02:00
import androidx.navigation.Navigation
2021-11-15 20:01:04 +01:00
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG
2021-11-15 20:01:04 +01:00
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
2022-07-02 01:27:12 +02:00
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
2022-04-03 23:57:50 +02:00
import io.reactivex.rxjava3.disposables.CompositeDisposable
2022-04-04 17:59:12 +02:00
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.abs
import kotlin.math.max
2021-11-13 12:57:14 +01:00
import kotlinx.coroutines.CoroutineScope
2021-11-16 19:14:47 +01:00
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.core.component.KoinComponent
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.BaseAdapter
2021-11-15 20:01:04 +01:00
import org.moire.ultrasonic.adapters.TrackViewBinder
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
2021-11-15 20:01:04 +01:00
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.Track
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.service.RxBus
2022-04-03 23:57:50 +02:00
import org.moire.ultrasonic.service.plusAssign
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.CommunicationError
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.view.AutoRepeatButton
import org.moire.ultrasonic.view.VisualizerView
import timber.log.Timber
2021-02-05 21:45:50 +01:00
/**
* Contains the Music Player screen of Ultrasonic with playback controls and the playlist
2022-04-03 23:57:50 +02:00
* TODO: Add timeline lister -> updateProgressBar().
*/
2021-06-19 20:42:03 +02:00
@Suppress("LargeClass", "TooManyFunctions", "MagicNumber")
2021-11-16 19:14:47 +01:00
class PlayerFragment :
Fragment(),
GestureDetector.OnGestureListener,
KoinComponent,
CoroutineScope by CoroutineScope(Dispatchers.Main) {
2021-11-15 20:01:04 +01:00
// Settings
2021-06-19 20:05:38 +02:00
private var swipeDistance = 0
private var swipeVelocity = 0
private var jukeboxAvailable = false
private var useFiveStarRating = false
private var isEqualizerAvailable = false
private var isVisualizerAvailable = false
2021-06-19 20:42:03 +02:00
// Detectors & Callbacks
private lateinit var gestureScanner: GestureDetector
private lateinit var cancellationToken: CancellationToken
2021-11-15 20:01:04 +01:00
private lateinit var dragTouchHelper: ItemTouchHelper
2021-06-19 20:42:03 +02:00
// Data & Services
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
private val mediaPlayerController: MediaPlayerController by inject()
private val shareHandler: ShareHandler by inject()
private val imageLoaderProvider: ImageLoaderProvider by inject()
private var currentPlaying: DownloadFile? = null
private var currentSong: Track? = null
2021-11-15 20:01:04 +01:00
private lateinit var viewManager: LinearLayoutManager
2022-04-03 23:57:50 +02:00
private var rxBusSubscription: CompositeDisposable = CompositeDisposable()
private lateinit var executorService: ScheduledExecutorService
private var ioScope = CoroutineScope(Dispatchers.IO)
2021-06-19 20:42:03 +02:00
// Views and UI Elements
private lateinit var visualizerViewLayout: LinearLayout
private lateinit var visualizerView: VisualizerView
private lateinit var playlistNameView: EditText
private lateinit var starMenuItem: MenuItem
private lateinit var fiveStar1ImageView: ImageView
private lateinit var fiveStar2ImageView: ImageView
private lateinit var fiveStar3ImageView: ImageView
private lateinit var fiveStar4ImageView: ImageView
private lateinit var fiveStar5ImageView: ImageView
private lateinit var playlistFlipper: ViewFlipper
private lateinit var emptyTextView: TextView
private lateinit var songTitleTextView: TextView
private lateinit var artistTextView: TextView
private lateinit var albumTextView: TextView
private lateinit var genreTextView: TextView
private lateinit var bitrateFormatTextView: TextView
2021-06-19 20:42:03 +02:00
private lateinit var albumArtImageView: ImageView
2021-11-15 20:01:04 +01:00
private lateinit var playlistView: RecyclerView
2021-06-19 20:42:03 +02:00
private lateinit var positionTextView: TextView
private lateinit var downloadTrackTextView: TextView
private lateinit var downloadTotalDurationTextView: TextView
private lateinit var durationTextView: TextView
private lateinit var pauseButton: View
private lateinit var stopButton: View
2022-04-03 23:57:50 +02:00
private lateinit var playButton: View
private lateinit var shuffleButton: View
2021-06-19 20:42:03 +02:00
private lateinit var repeatButton: ImageView
private lateinit var hollowStar: Drawable
private lateinit var fullStar: Drawable
private lateinit var progressBar: SeekBar
2021-06-19 20:05:38 +02:00
internal val viewAdapter: BaseAdapter<Identifiable> by lazy {
BaseAdapter()
2021-11-15 20:01:04 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context)
super.onCreate(savedInstanceState)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
2021-06-19 20:05:38 +02:00
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.current_playing, container, false)
2021-02-05 21:45:50 +01:00
}
private fun findViews(view: View) {
2021-06-19 20:05:38 +02:00
playlistFlipper = view.findViewById(R.id.current_playing_playlist_flipper)
emptyTextView = view.findViewById(R.id.playlist_empty)
songTitleTextView = view.findViewById(R.id.current_playing_song)
artistTextView = view.findViewById(R.id.current_playing_artist)
albumTextView = view.findViewById(R.id.current_playing_album)
genreTextView = view.findViewById(R.id.current_playing_genre)
bitrateFormatTextView = view.findViewById(R.id.current_playing_bitrate_format)
2021-06-19 20:05:38 +02:00
albumArtImageView = view.findViewById(R.id.current_playing_album_art_image)
positionTextView = view.findViewById(R.id.current_playing_position)
downloadTrackTextView = view.findViewById(R.id.current_playing_track)
downloadTotalDurationTextView = view.findViewById(R.id.current_total_duration)
durationTextView = view.findViewById(R.id.current_playing_duration)
progressBar = view.findViewById(R.id.current_playing_progress_bar)
playlistView = view.findViewById(R.id.playlist_view)
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
pauseButton = view.findViewById(R.id.button_pause)
stopButton = view.findViewById(R.id.button_stop)
2022-04-03 23:57:50 +02:00
playButton = view.findViewById(R.id.button_start)
2021-06-19 20:05:38 +02:00
repeatButton = view.findViewById(R.id.button_repeat)
visualizerViewLayout = view.findViewById(R.id.current_playing_visualizer_layout)
fiveStar1ImageView = view.findViewById(R.id.song_five_star_1)
fiveStar2ImageView = view.findViewById(R.id.song_five_star_2)
fiveStar3ImageView = view.findViewById(R.id.song_five_star_3)
fiveStar4ImageView = view.findViewById(R.id.song_five_star_4)
fiveStar5ImageView = view.findViewById(R.id.song_five_star_5)
2021-06-19 20:42:03 +02:00
}
@Suppress("LongMethod")
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
cancellationToken = CancellationToken()
setTitle(this, R.string.common_appname)
val windowManager = requireActivity().windowManager
val display = windowManager.defaultDisplay
val size = Point()
display.getSize(size)
val width = size.x
val height = size.y
setHasOptionsMenu(true)
2021-12-07 13:10:15 +01:00
useFiveStarRating = Settings.useFiveStarRating
2021-06-19 20:42:03 +02:00
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100
swipeVelocity = swipeDistance
gestureScanner = GestureDetector(context, this)
findViews(view)
val previousButton: AutoRepeatButton = view.findViewById(R.id.button_previous)
val nextButton: AutoRepeatButton = view.findViewById(R.id.button_next)
shuffleButton = view.findViewById(R.id.button_shuffle)
updateShuffleButtonState(mediaPlayerController.isShufflePlayEnabled)
updateRepeatButtonState(mediaPlayerController.repeatMode)
2021-06-19 20:42:03 +02:00
val ratingLinearLayout = view.findViewById<LinearLayout>(R.id.song_rating)
if (!useFiveStarRating) ratingLinearLayout.isVisible = false
2021-06-19 20:05:38 +02:00
hollowStar = Util.getDrawableFromAttribute(view.context, R.attr.star_hollow)
fullStar = Util.getDrawableFromAttribute(view.context, R.attr.star_full)
2021-06-19 20:42:03 +02:00
fiveStar1ImageView.setOnClickListener { setSongRating(1) }
fiveStar2ImageView.setOnClickListener { setSongRating(2) }
fiveStar3ImageView.setOnClickListener { setSongRating(3) }
fiveStar4ImageView.setOnClickListener { setSongRating(4) }
fiveStar5ImageView.setOnClickListener { setSongRating(5) }
albumArtImageView.setOnTouchListener { _, me ->
gestureScanner.onTouchEvent(me)
}
albumArtImageView.setOnClickListener {
toggleFullScreenAlbumArt()
}
2021-06-19 20:05:38 +02:00
previousButton.setOnClickListener {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.previous()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
previousButton.setOnRepeatListener {
val incrementTime = Settings.incrementTime
2021-06-19 20:05:38 +02:00
changeProgress(-incrementTime)
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
nextButton.setOnClickListener {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.next()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
nextButton.setOnRepeatListener {
val incrementTime = Settings.incrementTime
2021-06-19 20:05:38 +02:00
changeProgress(incrementTime)
}
2021-06-19 20:42:03 +02:00
pauseButton.setOnClickListener {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.pause()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:42:03 +02:00
}
2021-06-19 20:42:03 +02:00
stopButton.setOnClickListener {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.reset()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:42:03 +02:00
}
2021-11-16 19:14:47 +01:00
2022-04-03 23:57:50 +02:00
playButton.setOnClickListener {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2022-04-03 23:57:50 +02:00
mediaPlayerController.play()
2021-11-13 12:57:14 +01:00
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:42:03 +02:00
}
2021-11-16 19:14:47 +01:00
2021-06-19 20:05:38 +02:00
shuffleButton.setOnClickListener {
toggleShuffle()
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
repeatButton.setOnClickListener {
2022-04-03 23:57:50 +02:00
var newRepeat = mediaPlayerController.repeatMode + 1
if (newRepeat == 3) {
newRepeat = 0
}
mediaPlayerController.repeatMode = newRepeat
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2022-04-03 23:57:50 +02:00
when (newRepeat) {
0 -> Util.toast(
2021-06-19 20:05:38 +02:00
context, R.string.download_repeat_off
)
2022-04-03 23:57:50 +02:00
1 -> Util.toast(
2021-06-19 20:05:38 +02:00
context, R.string.download_repeat_single
)
2022-04-03 23:57:50 +02:00
2 -> Util.toast(
context, R.string.download_repeat_all
)
2021-06-19 20:05:38 +02:00
else -> {
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
}
progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
2021-06-19 20:05:38 +02:00
override fun onStopTrackingTouch(seekBar: SeekBar) {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.seekTo(progressBar.progress)
onSliderProgressChanged()
}
2021-06-19 20:05:38 +02:00
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
})
2021-11-15 20:01:04 +01:00
initPlaylistDisplay()
2021-11-16 19:14:47 +01:00
2021-06-19 20:05:38 +02:00
registerForContextMenu(playlistView)
2021-06-19 20:42:03 +02:00
if (arguments != null && requireArguments().getBoolean(
2021-12-20 19:41:55 +01:00
Constants.INTENT_SHUFFLE,
false
)
2021-06-19 20:05:38 +02:00
) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.isShufflePlayEnabled = true
}
2021-06-19 20:42:03 +02:00
visualizerViewLayout.isVisible = false
VisualizerController.get().observe(
2022-04-03 23:57:50 +02:00
requireActivity()
) { visualizerController ->
if (visualizerController != null) {
Timber.d("VisualizerController Observer.onChanged received controller")
visualizerView = VisualizerView(context)
visualizerViewLayout.addView(
visualizerView,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
2021-06-19 20:05:38 +02:00
)
2022-04-03 23:57:50 +02:00
)
2022-04-03 23:57:50 +02:00
visualizerViewLayout.isVisible = visualizerView.isActive
2022-04-03 23:57:50 +02:00
visualizerView.setOnTouchListener { _, _ ->
visualizerView.isActive = !visualizerView.isActive
mediaPlayerController.showVisualization = visualizerView.isActive
true
2021-02-05 21:45:50 +01:00
}
2022-04-03 23:57:50 +02:00
isVisualizerAvailable = true
} else {
Timber.d("VisualizerController Observer.onChanged has no controller")
visualizerViewLayout.isVisible = false
isVisualizerAvailable = false
2021-06-19 20:05:38 +02:00
}
2022-04-03 23:57:50 +02:00
}
2021-06-19 20:42:03 +02:00
EqualizerController.get().observe(
2022-04-03 23:57:50 +02:00
requireActivity()
) { equalizerController ->
isEqualizerAvailable = if (equalizerController != null) {
Timber.d("EqualizerController Observer.onChanged received controller")
true
} else {
Timber.d("EqualizerController Observer.onChanged has no controller")
false
2021-06-19 20:05:38 +02:00
}
2022-04-03 23:57:50 +02:00
}
// Observe playlist changes and update the UI
2022-04-03 23:57:50 +02:00
rxBusSubscription += RxBus.playlistObservable.subscribe {
// Use launch to ensure running it in the main thread
launch {
onPlaylistChanged()
}
}
2022-04-03 23:57:50 +02:00
rxBusSubscription += RxBus.playerStateObservable.subscribe {
// Use launch to ensure running it in the main thread
launch {
update()
}
2022-04-03 23:57:50 +02:00
}
mediaPlayerController.controller?.addListener(object : Player.Listener {
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
onSliderProgressChanged()
}
})
// Query the Jukebox state in an IO Context
ioScope.launch(CommunicationError.getHandler(context)) {
2021-06-19 20:05:38 +02:00
try {
2021-06-19 20:42:03 +02:00
jukeboxAvailable = mediaPlayerController.isJukeboxAvailable
} catch (all: Exception) {
Timber.e(all)
2021-06-19 20:05:38 +02:00
}
}
2021-06-19 20:42:03 +02:00
view.setOnTouchListener { _, event -> gestureScanner.onTouchEvent(event) }
2021-02-05 21:45:50 +01:00
}
private fun updateShuffleButtonState(isEnabled: Boolean) {
if (isEnabled) {
2022-04-08 21:28:14 +02:00
shuffleButton.alpha = ALPHA_ACTIVATED
} else {
2022-04-08 21:28:14 +02:00
shuffleButton.alpha = ALPHA_DEACTIVATED
}
}
private fun updateRepeatButtonState(repeatMode: Int) {
when (repeatMode) {
0 -> {
repeatButton.setImageDrawable(
Util.getDrawableFromAttribute(
requireContext(), R.attr.media_repeat_off
)
)
2022-04-08 21:28:14 +02:00
repeatButton.alpha = ALPHA_DEACTIVATED
}
1 -> {
repeatButton.setImageDrawable(
Util.getDrawableFromAttribute(
requireContext(), R.attr.media_repeat_single
)
)
2022-04-08 21:28:14 +02:00
repeatButton.alpha = ALPHA_ACTIVATED
}
2 -> {
repeatButton.setImageDrawable(
Util.getDrawableFromAttribute(
requireContext(), R.attr.media_repeat_all
)
)
2022-04-08 21:28:14 +02:00
repeatButton.alpha = ALPHA_ACTIVATED
}
else -> {
}
}
}
private fun toggleShuffle() {
val isEnabled = mediaPlayerController.toggleShuffle()
if (isEnabled) {
Util.toast(activity, R.string.download_menu_shuffle_on)
} else {
Util.toast(activity, R.string.download_menu_shuffle_off)
}
updateShuffleButtonState(isEnabled)
}
2021-06-19 20:05:38 +02:00
override fun onResume() {
super.onResume()
2022-04-03 23:57:50 +02:00
if (mediaPlayerController.currentPlayingLegacy == null) {
2021-06-19 20:42:03 +02:00
playlistFlipper.displayedChild = 1
2021-06-19 20:05:38 +02:00
} else {
2022-04-03 23:57:50 +02:00
// Download list and Album art must be updated when resumed
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:05:38 +02:00
onCurrentChanged()
2021-02-05 21:45:50 +01:00
}
2022-04-03 23:57:50 +02:00
val handler = Handler(Looper.getMainLooper())
2021-06-19 20:05:38 +02:00
val runnable = Runnable { handler.post { update(cancellationToken) } }
executorService = Executors.newSingleThreadScheduledExecutor()
executorService.scheduleWithFixedDelay(runnable, 0L, 500L, TimeUnit.MILLISECONDS)
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.keepScreenOn) {
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-06-19 20:05:38 +02:00
} else {
2021-06-19 20:42:03 +02:00
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
if (::visualizerView.isInitialized) {
visualizerView.isActive = mediaPlayerController.showVisualization
}
2021-06-19 20:42:03 +02:00
requireActivity().invalidateOptionsMenu()
2021-02-05 21:45:50 +01:00
}
// Scroll to current playing.
2021-06-19 20:05:38 +02:00
private fun scrollToCurrent() {
2022-04-03 23:57:50 +02:00
val index = mediaPlayerController.currentMediaItemIndex
2021-11-15 20:01:04 +01:00
if (index != -1) {
val smoothScroller = LinearSmoothScroller(context)
smoothScroller.targetPosition = index
viewManager.startSmoothScroll(smoothScroller)
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:05:38 +02:00
override fun onPause() {
super.onPause()
2021-06-19 20:42:03 +02:00
executorService.shutdown()
if (::visualizerView.isInitialized) {
visualizerView.isActive = mediaPlayerController.showVisualization
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onDestroyView() {
2022-04-03 23:57:50 +02:00
rxBusSubscription.dispose()
2021-11-16 19:14:47 +01:00
cancel("CoroutineScope cancelled because the view was destroyed")
2021-06-19 20:42:03 +02:00
cancellationToken.cancel()
2021-06-19 20:05:38 +02:00
super.onDestroyView()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.nowplaying, menu)
super.onCreateOptionsMenu(menu, inflater)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
@Suppress("ComplexMethod", "LongMethod", "NestedBlockDepth")
2021-06-19 20:05:38 +02:00
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
val screenOption = menu.findItem(R.id.menu_item_screen_on_off)
val jukeboxOption = menu.findItem(R.id.menu_item_jukebox)
val equalizerMenuItem = menu.findItem(R.id.menu_item_equalizer)
val visualizerMenuItem = menu.findItem(R.id.menu_item_visualizer)
val shareMenuItem = menu.findItem(R.id.menu_item_share)
val shareSongMenuItem = menu.findItem(R.id.menu_item_share_song)
2021-06-19 20:05:38 +02:00
starMenuItem = menu.findItem(R.id.menu_item_star)
val bookmarkMenuItem = menu.findItem(R.id.menu_item_bookmark_set)
val bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete)
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
if (isOffline()) {
if (shareMenuItem != null) {
shareMenuItem.isVisible = false
}
2021-06-19 20:42:03 +02:00
starMenuItem.isVisible = false
2021-06-19 20:05:38 +02:00
if (bookmarkMenuItem != null) {
bookmarkMenuItem.isVisible = false
}
if (bookmarkRemoveMenuItem != null) {
bookmarkRemoveMenuItem.isVisible = false
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:05:38 +02:00
if (equalizerMenuItem != null) {
equalizerMenuItem.isEnabled = isEqualizerAvailable
equalizerMenuItem.isVisible = isEqualizerAvailable
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
if (visualizerMenuItem != null) {
visualizerMenuItem.isEnabled = isVisualizerAvailable
visualizerMenuItem.isVisible = isVisualizerAvailable
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
val mediaPlayerController = mediaPlayerController
2022-04-03 23:57:50 +02:00
val downloadFile = mediaPlayerController.currentPlayingLegacy
2021-06-19 20:42:03 +02:00
if (downloadFile != null) {
currentSong = downloadFile.track
2021-06-19 20:42:03 +02:00
}
2021-06-19 20:42:03 +02:00
if (useFiveStarRating) starMenuItem.isVisible = false
2021-06-19 20:42:03 +02:00
if (currentSong != null) {
starMenuItem.icon = if (currentSong!!.starred) fullStar else hollowStar
shareSongMenuItem.isVisible = true
2021-06-19 20:42:03 +02:00
} else {
starMenuItem.icon = hollowStar
shareSongMenuItem.isVisible = false
2021-06-19 20:42:03 +02:00
}
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.keepScreenOn) {
screenOption?.setTitle(R.string.download_menu_screen_off)
} else {
screenOption?.setTitle(R.string.download_menu_screen_on)
}
2021-06-19 20:42:03 +02:00
if (jukeboxOption != null) {
jukeboxOption.isEnabled = jukeboxAvailable
jukeboxOption.isVisible = jukeboxAvailable
if (mediaPlayerController.isJukeboxEnabled) {
jukeboxOption.setTitle(R.string.download_menu_jukebox_off)
2021-06-19 20:05:38 +02:00
} else {
2021-06-19 20:42:03 +02:00
jukeboxOption.setTitle(R.string.download_menu_jukebox_on)
2021-02-05 21:45:50 +01:00
}
}
}
2021-06-19 20:05:38 +02:00
override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
super.onCreateContextMenu(menu, view, menuInfo)
if (view === playlistView) {
val info = menuInfo as AdapterContextMenuInfo?
2021-11-15 20:01:04 +01:00
val downloadFile = viewAdapter.getCurrentList()[info!!.position] as DownloadFile
2021-06-19 20:42:03 +02:00
val menuInflater = requireActivity().menuInflater
2021-06-19 20:05:38 +02:00
menuInflater.inflate(R.menu.nowplaying_context, menu)
val song: Track?
2021-06-19 20:42:03 +02:00
song = downloadFile.track
2021-06-19 20:42:03 +02:00
if (song.parent == null) {
2021-06-19 20:05:38 +02:00
val menuItem = menu.findItem(R.id.menu_show_album)
if (menuItem != null) {
menuItem.isVisible = false
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:42:03 +02:00
if (isOffline() || !Settings.shouldUseId3Tags) {
2021-06-19 20:42:03 +02:00
menu.findItem(R.id.menu_show_artist)?.isVisible = false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
if (isOffline()) {
2021-06-19 20:42:03 +02:00
menu.findItem(R.id.menu_lyrics)?.isVisible = false
2021-02-05 21:45:50 +01:00
}
}
}
2021-06-19 20:05:38 +02:00
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return menuItemSelected(item.itemId, null) || super.onOptionsItemSelected(item)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
@Suppress("ComplexMethod", "LongMethod", "ReturnCount")
2021-06-19 20:05:38 +02:00
private fun menuItemSelected(menuItemId: Int, song: DownloadFile?): Boolean {
var track: Track? = null
2021-06-19 20:05:38 +02:00
val bundle: Bundle
if (song != null) {
track = song.track
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
when (menuItemId) {
R.id.menu_show_artist -> {
if (track == null) return false
if (Settings.shouldUseId3Tags) {
2021-06-19 20:42:03 +02:00
bundle = Bundle()
bundle.putString(Constants.INTENT_ID, track.artistId)
bundle.putString(Constants.INTENT_NAME, track.artist)
bundle.putString(Constants.INTENT_PARENT_ID, track.artistId)
2021-11-30 21:21:50 +01:00
bundle.putBoolean(Constants.INTENT_ARTIST, true)
Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
2021-06-19 20:42:03 +02:00
}
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_show_album -> {
if (track == null) return false
val albumId = if (Settings.shouldUseId3Tags) track.albumId else track.parent
2021-06-19 20:05:38 +02:00
bundle = Bundle()
2021-11-30 21:21:50 +01:00
bundle.putString(Constants.INTENT_ID, albumId)
bundle.putString(Constants.INTENT_NAME, track.album)
bundle.putString(Constants.INTENT_PARENT_ID, track.parent)
2021-11-30 21:21:50 +01:00
bundle.putBoolean(Constants.INTENT_IS_ALBUM, true)
Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
2021-06-19 20:42:03 +02:00
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_lyrics -> {
if (track == null) return false
2021-06-19 20:42:03 +02:00
bundle = Bundle()
bundle.putString(Constants.INTENT_ARTIST, track.artist)
bundle.putString(Constants.INTENT_TITLE, track.title)
Navigation.findNavController(requireView()).navigate(R.id.playerToLyrics, bundle)
2021-06-19 20:42:03 +02:00
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_remove -> {
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:42:03 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_screen_on_off -> {
val window = requireActivity().window
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.keepScreenOn) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-06-19 20:42:03 +02:00
mediaPlayerController.keepScreenOn = false
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-06-19 20:42:03 +02:00
mediaPlayerController.keepScreenOn = true
}
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_shuffle -> {
toggleShuffle()
2021-06-19 20:42:03 +02:00
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_equalizer -> {
Navigation.findNavController(requireView()).navigate(R.id.playerToEqualizer)
2021-06-19 20:42:03 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_visualizer -> {
val active = !visualizerView.isActive
visualizerView.isActive = active
visualizerViewLayout.isVisible = visualizerView.isActive
2021-06-19 20:42:03 +02:00
mediaPlayerController.showVisualization = visualizerView.isActive
Util.toast(
context,
if (active) R.string.download_visualizer_on
else R.string.download_visualizer_off
2021-06-19 20:42:03 +02:00
)
2021-06-19 20:05:38 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_jukebox -> {
val jukeboxEnabled = !mediaPlayerController.isJukeboxEnabled
mediaPlayerController.isJukeboxEnabled = jukeboxEnabled
Util.toast(
context,
if (jukeboxEnabled) R.string.download_jukebox_on
else R.string.download_jukebox_off,
2021-06-19 20:42:03 +02:00
false
)
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_toggle_list -> {
toggleFullScreenAlbumArt()
2021-06-19 20:05:38 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_clear_playlist -> {
mediaPlayerController.isShufflePlayEnabled = false
mediaPlayerController.clear()
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:42:03 +02:00
return true
}
R.id.menu_item_save_playlist -> {
if (mediaPlayerController.playlistSize > 0) {
showSavePlaylistDialog()
}
2021-06-19 20:05:38 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_star -> {
if (currentSong == null) return true
2021-06-19 20:42:03 +02:00
val isStarred = currentSong!!.starred
2022-07-02 01:27:12 +02:00
mediaPlayerController.controller?.setRating(
HeartRating(!isStarred)
)?.let {
2022-07-02 21:58:45 +02:00
Futures.addCallback(
it,
object : FutureCallback<SessionResult> {
override fun onSuccess(result: SessionResult?) {
if (isStarred) {
starMenuItem.icon = hollowStar
currentSong!!.starred = false
} else {
starMenuItem.icon = fullStar
currentSong!!.starred = true
}
2022-07-02 01:27:12 +02:00
}
2022-07-02 21:58:45 +02:00
override fun onFailure(t: Throwable) {
Toast.makeText(context, "SetRating failed", Toast.LENGTH_SHORT)
.show()
}
},
this.executorService
)
2022-07-02 01:27:12 +02:00
}
2021-06-19 20:42:03 +02:00
return true
}
R.id.menu_item_bookmark_set -> {
if (currentSong == null) return true
2021-06-19 20:42:03 +02:00
val songId = currentSong!!.id
val playerPosition = mediaPlayerController.playerPosition
currentSong!!.bookmarkPosition = playerPosition
val bookmarkTime = Util.formatTotalDuration(playerPosition.toLong(), true)
Thread {
val musicService = getMusicService()
try {
musicService.createBookmark(songId, playerPosition)
} catch (all: Exception) {
Timber.e(all)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
}.start()
val msg = resources.getString(
R.string.download_bookmark_set_at_position,
bookmarkTime
)
2021-06-19 20:42:03 +02:00
Util.toast(context, msg)
return true
}
R.id.menu_item_bookmark_delete -> {
if (currentSong == null) return true
2021-06-19 20:42:03 +02:00
val bookmarkSongId = currentSong!!.id
currentSong!!.bookmarkPosition = 0
Thread {
val musicService = getMusicService()
try {
musicService.deleteBookmark(bookmarkSongId)
} catch (all: Exception) {
Timber.e(all)
}
}.start()
Util.toast(context, R.string.download_bookmark_removed)
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_share -> {
val mediaPlayerController = mediaPlayerController
val tracks: MutableList<Track?> = ArrayList()
2021-06-19 20:42:03 +02:00
val downloadServiceSongs = mediaPlayerController.playList
for (downloadFile in downloadServiceSongs) {
val playlistEntry = downloadFile.track
tracks.add(playlistEntry)
2021-06-19 20:42:03 +02:00
}
shareHandler.createShare(this, tracks, null, cancellationToken)
2021-06-19 20:42:03 +02:00
return true
}
R.id.menu_item_share_song -> {
if (currentSong == null) return true
val tracks: MutableList<Track?> = ArrayList()
tracks.add(currentSong)
shareHandler.createShare(this, tracks, null, cancellationToken)
return true
}
2021-06-19 20:42:03 +02:00
else -> return false
2021-02-05 21:45:50 +01:00
}
}
2022-04-03 23:57:50 +02:00
private fun update(cancel: CancellationToken? = null) {
if (cancel?.isCancellationRequested == true) return
2021-06-19 20:42:03 +02:00
val mediaPlayerController = mediaPlayerController
2022-04-03 23:57:50 +02:00
if (currentPlaying != mediaPlayerController.currentPlayingLegacy) {
2021-06-19 20:05:38 +02:00
onCurrentChanged()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
onSliderProgressChanged()
2021-06-19 20:42:03 +02:00
requireActivity().invalidateOptionsMenu()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun savePlaylistInBackground(playlistName: String) {
Util.toast(context, resources.getString(R.string.download_playlist_saving, playlistName))
2021-06-19 20:42:03 +02:00
mediaPlayerController.suggestedPlaylistName = playlistName
2021-02-05 21:45:50 +01:00
ioScope.launch {
val entries = mediaPlayerController.playList.map {
it.track
2021-02-05 21:45:50 +01:00
}
2021-11-13 12:57:14 +01:00
val musicService = getMusicService()
musicService.createPlaylist(null, playlistName, entries)
}.invokeOnCompletion {
if (it == null || it is CancellationException) {
Util.toast(context, R.string.download_playlist_done)
} else {
Timber.e(it, "Exception has occurred in savePlaylistInBackground")
val msg = String.format(
Locale.ROOT,
2021-06-19 20:05:38 +02:00
"%s %s",
resources.getString(R.string.download_playlist_error),
CommunicationError.getErrorMessage(it, context)
2021-06-19 20:05:38 +02:00
)
Util.toast(context, msg)
2021-02-05 21:45:50 +01:00
}
2021-11-13 12:57:14 +01:00
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun toggleFullScreenAlbumArt() {
2021-06-19 20:42:03 +02:00
if (playlistFlipper.displayedChild == 1) {
playlistFlipper.inAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_down_in)
2021-06-19 20:42:03 +02:00
playlistFlipper.outAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_down_out)
2021-06-19 20:42:03 +02:00
playlistFlipper.displayedChild = 0
2021-06-19 20:05:38 +02:00
} else {
2021-06-19 20:42:03 +02:00
playlistFlipper.inAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_up_in)
2021-06-19 20:42:03 +02:00
playlistFlipper.outAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_up_out)
2021-06-19 20:42:03 +02:00
playlistFlipper.displayedChild = 1
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
scrollToCurrent()
2021-02-05 21:45:50 +01:00
}
2021-11-15 20:01:04 +01:00
private fun initPlaylistDisplay() {
// Create a View Manager
viewManager = LinearLayoutManager(this.context)
// Hook up the view with the manager and the adapter
playlistView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
}
// Create listener
2022-04-03 23:57:50 +02:00
val clickHandler: ((DownloadFile, Int) -> Unit) = { _, pos ->
mediaPlayerController.seekTo(pos, 0)
mediaPlayerController.prepare()
mediaPlayerController.play()
2021-11-15 20:01:04 +01:00
onCurrentChanged()
onSliderProgressChanged()
}
viewAdapter.register(
TrackViewBinder(
2022-04-03 23:57:50 +02:00
onItemClick = clickHandler,
2021-11-15 20:01:04 +01:00
checkable = false,
draggable = true,
context = requireContext(),
lifecycleOwner = viewLifecycleOwner,
2021-12-07 00:06:41 +01:00
).apply {
this.startDrag = { holder ->
dragTouchHelper.startDrag(holder)
2021-02-05 21:45:50 +01:00
}
}
2021-11-15 20:01:04 +01:00
)
2022-04-05 21:56:13 +02:00
val callback = object : ItemTouchHelper.SimpleCallback(
2021-11-30 21:50:53 +01:00
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
2021-11-15 20:01:04 +01:00
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
2021-06-19 20:42:03 +02:00
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
2021-06-19 20:42:03 +02:00
// Move it in the data set
mediaPlayerController.moveItemInPlaylist(from, to)
return true
}
2021-11-15 20:01:04 +01:00
// Swipe to delete from playlist
@SuppressLint("NotifyDataSetChanged")
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val pos = viewHolder.bindingAdapterPosition
val item = mediaPlayerController.controller?.getMediaItemAt(pos)
mediaPlayerController.removeFromPlaylist(pos)
2021-11-30 21:50:53 +01:00
val songRemoved = String.format(
resources.getString(R.string.download_song_removed),
item?.mediaMetadata?.title
)
2021-11-30 21:50:53 +01:00
Util.toast(context, songRemoved)
}
override fun onSelectedChanged(
viewHolder: RecyclerView.ViewHolder?,
actionState: Int
) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ACTION_STATE_DRAG) {
2022-04-08 21:28:14 +02:00
viewHolder?.itemView?.alpha = ALPHA_DEACTIVATED
}
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
}
override fun isLongPressDragEnabled(): Boolean {
return false
2021-06-19 20:05:38 +02:00
}
}
2022-04-05 21:56:13 +02:00
dragTouchHelper = ItemTouchHelper(callback)
2021-11-15 20:01:04 +01:00
dragTouchHelper.attachToRecyclerView(playlistView)
}
private fun onPlaylistChanged() {
val mediaPlayerController = mediaPlayerController
val list = mediaPlayerController.playList
emptyTextView.setText(R.string.playlist_empty)
viewAdapter.submitList(list)
emptyTextView.isVisible = list.isEmpty()
updateRepeatButtonState(mediaPlayerController.repeatMode)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun onCurrentChanged() {
2022-04-03 23:57:50 +02:00
currentPlaying = mediaPlayerController.currentPlayingLegacy
2021-06-19 20:05:38 +02:00
scrollToCurrent()
val totalDuration = mediaPlayerController.playListDuration
val totalSongs = mediaPlayerController.playlistSize.toLong()
2022-04-03 23:57:50 +02:00
val currentSongIndex = mediaPlayerController.currentMediaItemIndex + 1
2021-06-19 20:05:38 +02:00
val duration = Util.formatTotalDuration(totalDuration)
val trackFormat =
String.format(Locale.getDefault(), "%d / %d", currentSongIndex, totalSongs)
if (currentPlaying != null) {
currentSong = currentPlaying!!.track
2021-06-19 20:42:03 +02:00
songTitleTextView.text = currentSong!!.title
artistTextView.text = currentSong!!.artist
albumTextView.text = currentSong!!.album
if (currentSong!!.year != null && Settings.showNowPlayingDetails)
albumTextView.append(String.format(Locale.ROOT, " (%d)", currentSong!!.year))
if (Settings.showNowPlayingDetails) {
genreTextView.text = currentSong!!.genre
genreTextView.isVisible =
(currentSong!!.genre != null && currentSong!!.genre!!.isNotBlank())
2022-04-03 23:57:50 +02:00
var bitRate = ""
if (currentSong!!.bitRate != null && currentSong!!.bitRate!! > 0)
bitRate = String.format(
Util.appContext().getString(R.string.song_details_kbps),
currentSong!!.bitRate
)
bitrateFormatTextView.text = String.format(
Locale.ROOT, "%s %s",
bitRate, currentSong!!.suffix
)
bitrateFormatTextView.isVisible = true
} else {
genreTextView.isVisible = false
bitrateFormatTextView.isVisible = false
}
2021-06-19 20:42:03 +02:00
downloadTrackTextView.text = trackFormat
downloadTotalDurationTextView.text = duration
imageLoaderProvider.getImageLoader()
2021-06-19 20:05:38 +02:00
.loadImage(albumArtImageView, currentSong, true, 0)
displaySongRating()
} else {
currentSong = null
2021-06-19 20:42:03 +02:00
songTitleTextView.text = null
artistTextView.text = null
albumTextView.text = null
genreTextView.text = null
bitrateFormatTextView.text = null
2021-06-19 20:42:03 +02:00
downloadTrackTextView.text = null
downloadTotalDurationTextView.text = null
imageLoaderProvider.getImageLoader()
.loadImage(albumArtImageView, null, true, 0)
2021-02-05 21:45:50 +01:00
}
}
2022-04-04 18:18:52 +02:00
@Suppress("LongMethod")
2021-11-16 19:14:47 +01:00
@Synchronized
2021-06-19 20:05:38 +02:00
private fun onSliderProgressChanged() {
2021-11-13 12:57:14 +01:00
2021-11-16 19:14:47 +01:00
val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled
val millisPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration: Int = mediaPlayerController.playerDuration
2022-04-03 23:57:50 +02:00
val playbackState: Int = mediaPlayerController.playbackState
val isPlaying = mediaPlayerController.isPlaying
2021-11-13 12:57:14 +01:00
2021-11-16 19:14:47 +01:00
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
2022-04-03 23:57:50 +02:00
progressBar.isEnabled = mediaPlayerController.isPlaying || isJukeboxEnabled
2021-11-16 19:14:47 +01:00
} else {
positionTextView.setText(R.string.util_zero_time)
durationTextView.setText(R.string.util_no_time)
progressBar.progress = 0
progressBar.max = 0
progressBar.isEnabled = false
}
2021-06-19 20:05:38 +02:00
2022-04-03 23:57:50 +02:00
val progress = mediaPlayerController.bufferedPercentage
when (playbackState) {
Player.STATE_BUFFERING -> {
2021-11-16 19:14:47 +01:00
val downloadStatus = resources.getString(
R.string.download_playerstate_loading
2021-11-13 12:57:14 +01:00
)
2022-04-03 23:57:50 +02:00
progressBar.secondaryProgress = progress
2021-11-16 19:14:47 +01:00
setTitle(this@PlayerFragment, downloadStatus)
2021-11-13 12:57:14 +01:00
}
2022-04-03 23:57:50 +02:00
Player.STATE_READY -> {
progressBar.secondaryProgress = progress
2021-11-16 19:14:47 +01:00
if (mediaPlayerController.isShufflePlayEnabled) {
setTitle(
this@PlayerFragment,
R.string.download_playerstate_playing_shuffle
)
} else {
setTitle(this@PlayerFragment, R.string.common_appname)
2021-11-13 12:57:14 +01:00
}
2021-02-05 21:45:50 +01:00
}
2022-04-03 23:57:50 +02:00
Player.STATE_IDLE,
Player.STATE_ENDED,
-> {
2021-11-16 19:14:47 +01:00
}
else -> setTitle(this@PlayerFragment, R.string.common_appname)
}
2021-11-13 12:57:14 +01:00
2022-04-03 23:57:50 +02:00
when (playbackState) {
Player.STATE_READY -> {
pauseButton.isVisible = isPlaying
2021-11-16 19:14:47 +01:00
stopButton.isVisible = false
2022-04-03 23:57:50 +02:00
playButton.isVisible = !isPlaying
2021-11-16 19:14:47 +01:00
}
2022-04-03 23:57:50 +02:00
Player.STATE_BUFFERING -> {
2021-11-16 19:14:47 +01:00
pauseButton.isVisible = false
stopButton.isVisible = true
2022-04-03 23:57:50 +02:00
playButton.isVisible = false
2021-11-16 19:14:47 +01:00
}
else -> {
pauseButton.isVisible = false
stopButton.isVisible = false
2022-04-03 23:57:50 +02:00
playButton.isVisible = true
2021-11-16 19:14:47 +01:00
}
2021-02-05 21:45:50 +01:00
}
2021-11-16 19:14:47 +01:00
// TODO: It would be a lot nicer if MediaPlayerController would send an event
// when this is necessary instead of updating every time
displaySongRating()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun changeProgress(ms: Int) {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
val msPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration = mediaPlayerController.playerDuration
val seekTo = (msPlayed + ms).coerceAtMost(duration)
mediaPlayerController.seekTo(seekTo)
progressBar.progress = seekTo
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onDown(me: MotionEvent): Boolean {
return false
}
2021-02-05 21:45:50 +01:00
2021-06-19 20:42:03 +02:00
@Suppress("ReturnCount")
2021-06-19 20:05:38 +02:00
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val e1X = e1.x
val e2X = e2.x
val e1Y = e1.y
val e2Y = e2.y
2021-06-19 20:42:03 +02:00
val absX = abs(velocityX)
val absY = abs(velocityY)
2021-02-05 21:45:50 +01:00
// Right to Left swipe
2021-06-19 20:05:38 +02:00
if (e1X - e2X > swipeDistance && absX > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.next()
onCurrentChanged()
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
// Left to Right swipe
2021-06-19 20:05:38 +02:00
if (e2X - e1X > swipeDistance && absX > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.previous()
onCurrentChanged()
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
// Top to Bottom swipe
2021-06-19 20:05:38 +02:00
if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.seekTo(mediaPlayerController.playerPosition + 30000)
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
// Bottom to Top swipe
2021-06-19 20:05:38 +02:00
if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.seekTo(mediaPlayerController.playerPosition - 8000)
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onLongPress(e: MotionEvent) {}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onShowPress(e: MotionEvent) {}
override fun onSingleTapUp(e: MotionEvent): Boolean {
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun displaySongRating() {
var rating = 0
if (currentSong?.userRating != null) {
rating = currentSong!!.userRating!!
}
2021-06-19 20:42:03 +02:00
fiveStar1ImageView.setImageDrawable(if (rating > 0) fullStar else hollowStar)
fiveStar2ImageView.setImageDrawable(if (rating > 1) fullStar else hollowStar)
fiveStar3ImageView.setImageDrawable(if (rating > 2) fullStar else hollowStar)
fiveStar4ImageView.setImageDrawable(if (rating > 3) fullStar else hollowStar)
fiveStar5ImageView.setImageDrawable(if (rating > 4) fullStar else hollowStar)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun setSongRating(rating: Int) {
if (currentSong == null) return
displaySongRating()
2021-06-19 20:42:03 +02:00
mediaPlayerController.setSongRating(rating)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun showSavePlaylistDialog() {
val layout = LayoutInflater.from(this.context).inflate(R.layout.save_playlist, null)
playlistNameView = layout.findViewById(R.id.save_playlist_name)
2021-06-19 20:42:03 +02:00
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
2021-06-19 20:05:38 +02:00
builder.setTitle(R.string.download_playlist_title)
builder.setMessage(R.string.download_playlist_name)
2021-06-19 20:42:03 +02:00
builder.setPositiveButton(R.string.common_save) { _, _ ->
2021-06-19 20:05:38 +02:00
savePlaylistInBackground(
2021-06-19 20:42:03 +02:00
playlistNameView.text.toString()
2021-06-19 20:05:38 +02:00
)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
builder.setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.cancel() }
2021-06-19 20:05:38 +02:00
builder.setView(layout)
builder.setCancelable(true)
val dialog = builder.create()
2021-06-19 20:42:03 +02:00
val playlistName = mediaPlayerController.suggestedPlaylistName
2021-06-19 20:05:38 +02:00
if (playlistName != null) {
2021-06-19 20:42:03 +02:00
playlistNameView.setText(playlistName)
2021-06-19 20:05:38 +02:00
} else {
val dateFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
2021-06-19 20:42:03 +02:00
playlistNameView.setText(dateFormat.format(Date()))
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
dialog.show()
}
2021-02-05 21:45:50 +01:00
2021-06-19 20:05:38 +02:00
companion object {
private const val PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5
2022-04-08 21:28:14 +02:00
private const val ALPHA_ACTIVATED = 1f
private const val ALPHA_DEACTIVATED = 0.4f
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
}