Improve player gestures and add pinch to zoom (#74)
* implemented pan/panStop on a GestureListener to detect the entry location and exclude top status bar area. * remember last brightness set for players [ Exoplayer , MPV] * Pinch to zoom and auto rotate feature for Exoplayer * Only save the brightness when the overlay disappears instead of on every scroll Also clean up some files * removed unnecessary configChanges from Manifest Co-authored-by: Jarne Demeulemeester <jarnedemeulemeester@gmail.com>
This commit is contained in:
parent
6f2c9945b5
commit
7d9857d3ce
7 changed files with 161 additions and 5 deletions
|
@ -21,7 +21,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".PlayerActivity"
|
||||
android:screenOrientation="userLandscape" />
|
||||
android:screenOrientation="sensorLandscape" />
|
||||
|
||||
<activity
|
||||
android:name=".tv.TvPlayerActivity"
|
||||
|
|
|
@ -18,13 +18,18 @@ 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.AppPreferences
|
||||
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PlayerActivity : BasePlayerActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
lateinit var binding: ActivityPlayerBinding
|
||||
private lateinit var playerGestureHelper: PlayerGestureHelper
|
||||
override val viewModel: PlayerActivityViewModel by viewModels()
|
||||
|
@ -44,7 +49,12 @@ class PlayerActivity : BasePlayerActivity() {
|
|||
|
||||
configureInsets(playerControls)
|
||||
|
||||
playerGestureHelper = PlayerGestureHelper(this, binding.playerView, getSystemService(Context.AUDIO_SERVICE) as AudioManager)
|
||||
playerGestureHelper = PlayerGestureHelper(
|
||||
appPreferences,
|
||||
this,
|
||||
binding.playerView,
|
||||
getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
)
|
||||
|
||||
binding.playerView.findViewById<View>(R.id.back_button).setOnClickListener {
|
||||
onBackPressed()
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package dev.jdtech.jellyfin.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
import androidx.core.content.edit
|
||||
import javax.inject.Inject
|
||||
|
||||
class AppPreferences
|
||||
@Inject
|
||||
constructor(
|
||||
private val sharedPreferences: SharedPreferences
|
||||
) {
|
||||
var playerBrightness: Float
|
||||
get() = sharedPreferences.getFloat(
|
||||
Constants.PREF_PLAYER_BRIGHTNESS,
|
||||
BRIGHTNESS_OVERRIDE_NONE
|
||||
)
|
||||
set(value) {
|
||||
sharedPreferences.edit {
|
||||
putFloat(Constants.PREF_PLAYER_BRIGHTNESS, value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
14
app/src/main/java/dev/jdtech/jellyfin/utils/Constants.kt
Normal file
14
app/src/main/java/dev/jdtech/jellyfin/utils/Constants.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package dev.jdtech.jellyfin.utils
|
||||
|
||||
object Constants {
|
||||
|
||||
//player
|
||||
const val GESTURE_EXCLUSION_AREA_TOP = 48
|
||||
const val FULL_SWIPE_RANGE_SCREEN_RATIO = 0.66f
|
||||
const val ZOOM_SCALE_BASE = 1f
|
||||
const val ZOOM_SCALE_THRESHOLD = 0.01f
|
||||
|
||||
//pref
|
||||
const val PREF_PLAYER_BRIGHTNESS = "pref_player_brightness"
|
||||
|
||||
}
|
|
@ -4,24 +4,36 @@ import android.media.AudioManager
|
|||
import android.provider.Settings
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.ScaleGestureDetector
|
||||
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.AspectRatioFrameLayout
|
||||
import com.google.android.exoplayer2.ui.PlayerView
|
||||
import dev.jdtech.jellyfin.PlayerActivity
|
||||
import timber.log.Timber
|
||||
import kotlin.math.abs
|
||||
|
||||
class PlayerGestureHelper(
|
||||
private val appPreferences: AppPreferences,
|
||||
private val activity: PlayerActivity,
|
||||
private val playerView: PlayerView,
|
||||
private val audioManager: AudioManager
|
||||
) {
|
||||
) {
|
||||
|
||||
|
||||
/**
|
||||
* Tracks whether video content should fill the screen, cutting off unwanted content on the sides.
|
||||
* Useful on wide-screen phones to remove black bars from some movies.
|
||||
*/
|
||||
private var isZoomEnabled = false
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
|
@ -42,10 +54,14 @@ class PlayerGestureHelper(
|
|||
if (abs(distanceY / distanceX) < 2)
|
||||
return false
|
||||
|
||||
if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP))
|
||||
return false
|
||||
|
||||
|
||||
val viewCenterX = playerView.measuredWidth / 2
|
||||
|
||||
// Distance to swipe to go from min to max
|
||||
val distanceFull = playerView.measuredHeight
|
||||
val distanceFull = playerView.measuredHeight * Constants.FULL_SWIPE_RANGE_SCREEN_RATIO
|
||||
val ratioChange = distanceY / distanceFull
|
||||
|
||||
if (firstEvent.x.toInt() > viewCenterX) {
|
||||
|
@ -99,14 +115,39 @@ class PlayerGestureHelper(
|
|||
|
||||
private val hideGestureBrightnessIndicatorOverlayAction = Runnable {
|
||||
activity.binding.gestureBrightnessLayout.visibility = View.GONE
|
||||
appPreferences.playerBrightness = activity.window.attributes.screenBrightness
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles scale/zoom gesture
|
||||
*/
|
||||
private val zoomGestureDetector = ScaleGestureDetector(playerView.context, object : ScaleGestureDetector.OnScaleGestureListener {
|
||||
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
|
||||
|
||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||
val scaleFactor = detector.scaleFactor
|
||||
if (abs(scaleFactor - Constants.ZOOM_SCALE_BASE) > Constants.ZOOM_SCALE_THRESHOLD) {
|
||||
isZoomEnabled = scaleFactor > 1
|
||||
updateZoomMode(isZoomEnabled)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
|
||||
}).apply { isQuickScaleEnabled = false }
|
||||
|
||||
private fun updateZoomMode(enabled: Boolean) {
|
||||
playerView.resizeMode = if (enabled) AspectRatioFrameLayout.RESIZE_MODE_ZOOM else AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
}
|
||||
|
||||
init {
|
||||
activity.window.attributes.screenBrightness = appPreferences.playerBrightness
|
||||
@Suppress("ClickableViewAccessibility")
|
||||
playerView.setOnTouchListener { _, event ->
|
||||
if (playerView.useController) {
|
||||
when (event.pointerCount) {
|
||||
1 -> gestureDetector.onTouchEvent(event)
|
||||
2 -> zoomGestureDetector.onTouchEvent(event)
|
||||
}
|
||||
}
|
||||
if(event.action == MotionEvent.ACTION_UP) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dev.jdtech.jellyfin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
|
@ -35,4 +36,7 @@ fun Fragment.checkIfLoginRequired(error: String) {
|
|||
|
||||
|
||||
inline fun Context.toast(@StringRes text: Int, duration: Int = Toast.LENGTH_SHORT) =
|
||||
Toast.makeText(this, text, duration).show()
|
||||
Toast.makeText(this, text, duration).show()
|
||||
|
||||
|
||||
inline fun Resources.dip(px: Int) = (px * displayMetrics.density).toInt()
|
63
app/src/main/res/layout/exo_player_view.xml
Normal file
63
app/src/main/res/layout/exo_player_view.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:keep="@layout/exo_styled_player_view">
|
||||
|
||||
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout android:id="@id/exo_content_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<!-- Video surface will be inserted as the first child of the content frame. -->
|
||||
|
||||
<View android:id="@id/exo_shutter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"/>
|
||||
|
||||
<ImageView android:id="@id/exo_artwork"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitXY"/>
|
||||
|
||||
<ProgressBar android:id="@id/exo_buffering"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<TextView android:id="@id/exo_error_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/exo_error_message_margin_bottom"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/exo_white"
|
||||
android:textSize="@dimen/exo_error_message_text_size"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:background="@drawable/exo_rounded_rectangle"
|
||||
android:paddingLeft="@dimen/exo_error_message_text_padding_horizontal"
|
||||
android:paddingRight="@dimen/exo_error_message_text_padding_horizontal"
|
||||
android:paddingTop="@dimen/exo_error_message_text_padding_vertical"
|
||||
android:paddingBottom="@dimen/exo_error_message_text_padding_vertical"/>
|
||||
|
||||
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
|
||||
|
||||
<com.google.android.exoplayer2.ui.SubtitleView android:id="@id/exo_subtitles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<FrameLayout android:id="@id/exo_ad_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<FrameLayout android:id="@id/exo_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<View android:id="@id/exo_controller_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</merge>
|
Loading…
Reference in a new issue