diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 81895a50..905113d7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,7 +21,7 @@ + android:screenOrientation="sensorLandscape" /> (R.id.back_button).setOnClickListener { onBackPressed() diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/AppPreferences.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/AppPreferences.kt new file mode 100644 index 00000000..46e0dbbd --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/AppPreferences.kt @@ -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) + } + } + +} diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/Constants.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/Constants.kt new file mode 100644 index 00000000..667bdb3f --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/Constants.kt @@ -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" + +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/PlayerGestureHelper.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/PlayerGestureHelper.kt index d769b07f..afcc2f1c 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/utils/PlayerGestureHelper.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/PlayerGestureHelper.kt @@ -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) { diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/extensions.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/extensions.kt index 69d04439..6eb182ba 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/utils/extensions.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/utils/extensions.kt @@ -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() \ No newline at end of file + Toast.makeText(this, text, duration).show() + + +inline fun Resources.dip(px: Int) = (px * displayMetrics.density).toInt() \ No newline at end of file diff --git a/app/src/main/res/layout/exo_player_view.xml b/app/src/main/res/layout/exo_player_view.xml new file mode 100644 index 00000000..3b16637e --- /dev/null +++ b/app/src/main/res/layout/exo_player_view.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file