Add brightness swipe controls (#69)

* Add brightness swipe control

* Add background to the overlays

Co-authored-by: Jarne Demeulemeester <32322857+jarnedemeulemeester@users.noreply.github.com>
This commit is contained in:
Jcuhfehl 2021-11-27 16:03:17 +01:00 committed by GitHub
parent 598c11f299
commit e259c405bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 326 additions and 81 deletions

View file

@ -1,6 +1,7 @@
package dev.jdtech.jellyfin package dev.jdtech.jellyfin
import android.os.Build import android.content.Context
import android.media.AudioManager
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
@ -17,19 +18,17 @@ import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
import dev.jdtech.jellyfin.mpv.MPVPlayer import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.mpv.TrackType import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.utils.AudioController import dev.jdtech.jellyfin.utils.PlayerGestureHelper
import dev.jdtech.jellyfin.utils.VerticalSwipeListener
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import timber.log.Timber import timber.log.Timber
import kotlin.math.max
@AndroidEntryPoint @AndroidEntryPoint
class PlayerActivity : BasePlayerActivity() { class PlayerActivity : BasePlayerActivity() {
private lateinit var binding: ActivityPlayerBinding lateinit var binding: ActivityPlayerBinding
private lateinit var playerGestureHelper: PlayerGestureHelper
override val viewModel: PlayerActivityViewModel by viewModels() override val viewModel: PlayerActivityViewModel by viewModels()
private val args: PlayerActivityArgs by navArgs() private val args: PlayerActivityArgs by navArgs()
private val audioController by lazy { AudioController(this) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -42,10 +41,11 @@ class PlayerActivity : BasePlayerActivity() {
binding.playerView.player = viewModel.player binding.playerView.player = viewModel.player
val playerControls = binding.playerView.findViewById<View>(R.id.player_controls) val playerControls = binding.playerView.findViewById<View>(R.id.player_controls)
setupVolumeControl()
configureInsets(playerControls) configureInsets(playerControls)
playerGestureHelper = PlayerGestureHelper(this, binding.playerView, getSystemService(Context.AUDIO_SERVICE) as AudioManager)
binding.playerView.findViewById<View>(R.id.back_button).setOnClickListener { binding.playerView.findViewById<View>(R.id.back_button).setOnClickListener {
onBackPressed() onBackPressed()
} }
@ -158,19 +158,5 @@ class PlayerActivity : BasePlayerActivity() {
viewModel.initializePlayer(args.items) viewModel.initializePlayer(args.items)
hideSystemUI() 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)
))
}
} }

View file

@ -0,0 +1,129 @@
package dev.jdtech.jellyfin.utils
import android.media.AudioManager
import android.provider.Settings
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
import com.google.android.exoplayer2.ui.PlayerView
import dev.jdtech.jellyfin.PlayerActivity
import timber.log.Timber
import kotlin.math.abs
class PlayerGestureHelper(
private val activity: PlayerActivity,
private val playerView: PlayerView,
private val audioManager: AudioManager
) {
/**
* Tracks a value during a swipe gesture (between multiple onScroll calls).
* When the gesture starts it's reset to an initial value and gets increased or decreased
* (depending on the direction) as the gesture progresses.
*/
private var swipeGestureValueTrackerVolume = -1f
private var swipeGestureValueTrackerBrightness = -1f
private val gestureDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent): Boolean {
return true
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
playerView.apply {
if (!isControllerVisible) showController() else hideController()
}
return true
}
override fun onScroll(firstEvent: MotionEvent, currentEvent: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
// Check whether swipe was oriented vertically
if (abs(distanceY / distanceX) < 2)
return false
val viewCenterX = playerView.measuredWidth / 2
// Distance to swipe to go from min to max
val distanceFull = playerView.measuredHeight
val ratioChange = distanceY / distanceFull
if (firstEvent.x.toInt() > viewCenterX) {
// Swiping on the right, change volume
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
if (swipeGestureValueTrackerVolume == -1f) swipeGestureValueTrackerVolume = currentVolume.toFloat()
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val change = ratioChange * maxVolume
swipeGestureValueTrackerVolume += change
val toSet = swipeGestureValueTrackerVolume.toInt().coerceIn(0, maxVolume)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, toSet, 0)
activity.binding.gestureVolumeLayout.visibility = View.VISIBLE
activity.binding.gestureVolumeProgressBar.max = maxVolume
activity.binding.gestureVolumeProgressBar.progress = toSet
activity.binding.gestureVolumeText.text = "${(toSet.toFloat()/maxVolume.toFloat()).times(100).toInt()}%"
} else {
// Swiping on the left, change brightness
val window = activity.window
val brightnessRange = BRIGHTNESS_OVERRIDE_OFF..BRIGHTNESS_OVERRIDE_FULL
// Initialize on first swipe
if (swipeGestureValueTrackerBrightness == -1f) {
val brightness = window.attributes.screenBrightness
Timber.d("Brightness ${Settings.System.getFloat(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS)}")
swipeGestureValueTrackerBrightness = when (brightness) {
in brightnessRange -> brightness
else -> Settings.System.getFloat(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS)/255
}
}
swipeGestureValueTrackerBrightness = (swipeGestureValueTrackerBrightness + ratioChange).coerceIn(brightnessRange)
val lp = window.attributes
lp.screenBrightness = swipeGestureValueTrackerBrightness
window.attributes = lp
activity.binding.gestureBrightnessLayout.visibility = View.VISIBLE
activity.binding.gestureBrightnessProgressBar.max = BRIGHTNESS_OVERRIDE_FULL.times(100).toInt()
activity.binding.gestureBrightnessProgressBar.progress = lp.screenBrightness.times(100).toInt()
activity.binding.gestureBrightnessText.text = "${(lp.screenBrightness/BRIGHTNESS_OVERRIDE_FULL).times(100).toInt()}%"
}
return true
}
})
private val hideGestureVolumeIndicatorOverlayAction = Runnable {
activity.binding.gestureVolumeLayout.visibility = View.GONE
}
private val hideGestureBrightnessIndicatorOverlayAction = Runnable {
activity.binding.gestureBrightnessLayout.visibility = View.GONE
}
init {
@Suppress("ClickableViewAccessibility")
playerView.setOnTouchListener { _, event ->
if (playerView.useController) {
when (event.pointerCount) {
1 -> gestureDetector.onTouchEvent(event)
}
}
if(event.action == MotionEvent.ACTION_UP) {
activity.binding.gestureVolumeLayout.apply {
if (visibility == View.VISIBLE) {
removeCallbacks(hideGestureVolumeIndicatorOverlayAction)
postDelayed(hideGestureVolumeIndicatorOverlayAction, 1000)
}
}
activity.binding.gestureBrightnessLayout.apply {
if (visibility == View.VISIBLE) {
removeCallbacks(hideGestureBrightnessIndicatorOverlayAction)
postDelayed(hideGestureBrightnessIndicatorOverlayAction, 1000)
}
}
}
true
}
}
}

View file

@ -1,60 +0,0 @@
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
}

View file

@ -0,0 +1,69 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M12,2v2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M12,20v2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M5,5l1.5,1.5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M17.5,17.5L19,19"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M2,12h2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M20,12h2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M5,19l1.5,-1.5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M17.5,6.5L19,5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,5l-5,4l-4,0l0,6l4,0l5,4l0,-14z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
<path
android:pathData="M19.07,4.93a10,10 0,0 1,0 14.14M15.54,8.46a5,5 0,0 1,0 7.07"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/white"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/player_background" />
<corners android:radius="10dp" />
</shape>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape >
<solid android:color="@color/transparent_white_300"/>
<corners android:radius="100dp"/>
</shape>
</item>
<item android:id="@android:id/progress">
<scale android:scaleGravity="bottom" android:scaleWidth="0%" android:scaleHeight="100%">
<shape >
<solid android:color="@color/white"/>
<corners android:radius="100dp"/>
</shape>
</scale>
</item>
</layer-list>

View file

@ -13,4 +13,78 @@
android:background="@color/black" android:background="@color/black"
app:show_buffering="always" /> app:show_buffering="always" />
<LinearLayout
android:id="@+id/gesture_volume_layout"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:clickable="false"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_margin="16dp"
android:visibility="gone"
android:background="@drawable/overlay_background">
<TextView
android:id="@+id/gesture_volume_text"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginVertical="16dp"
android:gravity="center"
android:textColor="@color/white"/>
<ProgressBar
android:id="@+id/gesture_volume_progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="8dp"
android:layout_height="125dp"
android:progressDrawable="@drawable/progress_scale_drawable"
android:progress="58"/>
<ImageView
android:id="@+id/gesture_volume_image"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_volume"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:id="@+id/gesture_brightness_layout"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:clickable="false"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_margin="16dp"
android:visibility="gone"
android:background="@drawable/overlay_background">
<TextView
android:id="@+id/gesture_brightness_text"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginVertical="16dp"
android:gravity="center"
android:textColor="@color/white"/>
<ProgressBar
android:id="@+id/gesture_brightness_progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="8dp"
android:layout_height="125dp"
android:progressDrawable="@drawable/progress_scale_drawable"
android:progress="58"/>
<ImageView
android:id="@+id/gesture_brightness_image"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_sun"
tools:ignore="ContentDescription" />
</LinearLayout>
</FrameLayout> </FrameLayout>

View file

@ -13,6 +13,8 @@
<color name="neutral_300">#A9BDD1</color> <color name="neutral_300">#A9BDD1</color>
<color name="neutral_200">#D7E0E9</color> <color name="neutral_200">#D7E0E9</color>
<color name="neutral_100">#EEF2F6</color> <color name="neutral_100">#EEF2F6</color>
<color name="transparent_white_300">#4DFFFFFF</color>
<color name="transparent_black_300">#4D000000</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="red">#EB5757</color> <color name="red">#EB5757</color>