Add ability to swipe up and down to adjust volume (#48)
* Add ability to swipe up and down to adjust volume Created AudioController to adjust global volume for media channel. Alarm, system and other volume levels are unaffected. This way it doesn't need specific implementation for separate players. During swiping system volume slider is shown and it is possible to change direction mid-swipe. AudioController should probably be singleton and provided by DI but currently PlayerActivity is handling all the playback so it seemed unnecessarily complicated. Sensitivity can be adjusted by threshold value in VerticalSwipeListener. * Add audio controller class Co-authored-by: Jarne Demeulemeester <32322857+jarnedemeulemeester@users.noreply.github.com>
This commit is contained in:
parent
62d09b3566
commit
b0b7d7f5b5
4 changed files with 105 additions and 6 deletions
|
@ -20,14 +20,19 @@ import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
|
|||
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
|
||||
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
||||
import dev.jdtech.jellyfin.mpv.TrackType
|
||||
import dev.jdtech.jellyfin.utils.AudioController
|
||||
import dev.jdtech.jellyfin.utils.VerticalSwipeListener
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||
import timber.log.Timber
|
||||
import kotlin.math.max
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PlayerActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityPlayerBinding
|
||||
private val viewModel: PlayerActivityViewModel by viewModels()
|
||||
private val args: PlayerActivityArgs by navArgs()
|
||||
private val audioController by lazy { AudioController(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -39,6 +44,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
binding.playerView.player = viewModel.player
|
||||
|
||||
val playerControls = binding.playerView.findViewById<View>(R.id.player_controls)
|
||||
setupVolumeControl()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
binding.playerView.findViewById<View>(R.id.player_controls)
|
||||
|
@ -179,6 +185,20 @@ class PlayerActivity : AppCompatActivity() {
|
|||
hideSystemUI()
|
||||
}
|
||||
|
||||
private fun setupVolumeControl() {
|
||||
val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
windowManager.currentWindowMetrics.bounds.height()
|
||||
} else {
|
||||
windowManager.defaultDisplay.height
|
||||
}
|
||||
binding.playerView.setOnTouchListener(VerticalSwipeListener(
|
||||
onUp = { audioController.volumeUp() },
|
||||
onDown = { audioController.volumeDown() },
|
||||
onTouch = { audioController.showVolumeSlider() },
|
||||
threshold = max(height / 8, 100)
|
||||
))
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun hideSystemUI() {
|
||||
// These methods are deprecated but we still use them because the new WindowInsetsControllerCompat has a bug which makes the action bar reappear
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.json.JSONException
|
|||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
|
@ -1199,22 +1200,22 @@ class MPVPlayer(
|
|||
* @param volume The volume to set.
|
||||
*/
|
||||
override fun setDeviceVolume(volume: Int) {
|
||||
TODO("Not yet implemented")
|
||||
throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.")
|
||||
}
|
||||
|
||||
/** Increases the volume of the device. */
|
||||
override fun increaseDeviceVolume() {
|
||||
TODO("Not yet implemented")
|
||||
throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.")
|
||||
}
|
||||
|
||||
/** Decreases the volume of the device. */
|
||||
override fun decreaseDeviceVolume() {
|
||||
TODO("Not yet implemented")
|
||||
throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.")
|
||||
}
|
||||
|
||||
/** Sets the mute state of the device. */
|
||||
override fun setDeviceMuted(muted: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
throw IllegalArgumentException("You should use global volume controls. Check out AUDIO_SERVICE.")
|
||||
}
|
||||
|
||||
private class CurrentTrackSelection(
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package dev.jdtech.jellyfin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.ADJUST_LOWER
|
||||
import android.media.AudioManager.ADJUST_RAISE
|
||||
import android.media.AudioManager.ADJUST_SAME
|
||||
import android.media.AudioManager.FLAG_SHOW_UI
|
||||
import android.media.AudioManager.STREAM_MUSIC
|
||||
|
||||
internal class AudioController internal constructor(context: Context) {
|
||||
|
||||
private val manager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
fun volumeUp() = manager.adjustStreamVolume(STREAM_MUSIC, ADJUST_RAISE, FLAG_SHOW_UI)
|
||||
fun volumeDown() = manager.adjustStreamVolume(STREAM_MUSIC, ADJUST_LOWER, FLAG_SHOW_UI)
|
||||
fun showVolumeSlider() = manager.adjustStreamVolume(STREAM_MUSIC, ADJUST_SAME, FLAG_SHOW_UI)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package dev.jdtech.jellyfin.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.ACTION_DOWN
|
||||
import android.view.MotionEvent.ACTION_MOVE
|
||||
import android.view.MotionEvent.ACTION_UP
|
||||
import android.view.View
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/**
|
||||
* Never consumes touch events. Can report when swipe starts with onTouch.
|
||||
*/
|
||||
internal class VerticalSwipeListener(
|
||||
private val onUp: () -> Unit,
|
||||
private val onDown: () -> Unit,
|
||||
private val onTouch: () -> Unit = {},
|
||||
private val threshold: Int = 150,
|
||||
): View.OnTouchListener {
|
||||
|
||||
private var verticalStart = 0f
|
||||
private var distance = 0f
|
||||
private var dispatchTouchEvent = true
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
when(event.action) {
|
||||
ACTION_DOWN -> verticalStart = event.y
|
||||
ACTION_UP -> dispatchTouchEvent = true
|
||||
ACTION_MOVE -> event.reportSwipe()
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun MotionEvent.reportSwipe() {
|
||||
reportFirstTouch()
|
||||
|
||||
distance = verticalStart - y
|
||||
if (distance.tooShort()) return
|
||||
|
||||
verticalStart = y
|
||||
|
||||
if (distance > 0) {
|
||||
onUp()
|
||||
} else {
|
||||
onDown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportFirstTouch() {
|
||||
if (dispatchTouchEvent) {
|
||||
onTouch()
|
||||
dispatchTouchEvent = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun Float.tooShort() = absoluteValue < threshold
|
||||
}
|
Loading…
Reference in a new issue