diff --git a/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index a548cec0..48692319 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -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(R.id.player_controls) + setupVolumeControl() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { binding.playerView.findViewById(R.id.player_controls) @@ -179,12 +185,26 @@ 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 window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) + View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { diff --git a/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt b/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt index b8a9a573..2e60ac3e 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/mpv/MPVPlayer.kt @@ -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( diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/AudioController.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/AudioController.kt new file mode 100644 index 00000000..39894b02 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/AudioController.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/VerticalSwipeListener.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/VerticalSwipeListener.kt new file mode 100644 index 00000000..c96c8b02 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/VerticalSwipeListener.kt @@ -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 +} \ No newline at end of file