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:
lsrom 2021-10-24 18:07:08 +02:00 committed by GitHub
parent 62d09b3566
commit b0b7d7f5b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 6 deletions

View file

@ -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

View file

@ -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(

View file

@ -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)
}

View file

@ -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
}