From 85c99d70c42b4865d74c6b9c38a7768718076bb0 Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Fri, 19 Dec 2025 19:28:58 +0530 Subject: [PATCH 1/8] added long press gesture to 2x speed in the separate video player --- .../gallery/activities/VideoPlayerActivity.kt | 122 +++++++++++++++++- .../main/res/layout/activity_video_player.xml | 20 +++ 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index 1e42e919f..ee2008157 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -13,17 +13,25 @@ import android.graphics.Point import android.graphics.SurfaceTexture import android.graphics.drawable.ColorDrawable import android.net.Uri +import android.os.Build import android.os.Bundle import android.os.Handler import android.util.DisplayMetrics import android.view.GestureDetector +import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.Surface import android.view.TextureView import android.view.View +import android.view.ViewConfiguration +import android.view.WindowInsets import android.view.WindowManager +import android.widget.RelativeLayout import android.widget.SeekBar +import android.widget.TextView +import androidx.annotation.RequiresApi import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.WindowInsetsCompat.Type import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.MediaItem @@ -45,6 +53,7 @@ import org.fossify.commons.extensions.beGone import org.fossify.commons.extensions.beVisible import org.fossify.commons.extensions.beVisibleIf import org.fossify.commons.extensions.fadeIn +import org.fossify.commons.extensions.fadeOut import org.fossify.commons.extensions.getColoredDrawableWithColor import org.fossify.commons.extensions.getFilenameFromUri import org.fossify.commons.extensions.getFormattedDuration @@ -57,6 +66,7 @@ import org.fossify.commons.extensions.viewBinding import org.fossify.gallery.R import org.fossify.gallery.databinding.ActivityVideoPlayerBinding import org.fossify.gallery.extensions.config +import org.fossify.gallery.extensions.getActionBarHeight import org.fossify.gallery.extensions.getFormattedDuration import org.fossify.gallery.extensions.getFriendlyMessage import org.fossify.gallery.extensions.hideSystemUI @@ -81,6 +91,7 @@ import org.fossify.gallery.helpers.SHOW_NEXT_ITEM import org.fossify.gallery.helpers.SHOW_PREV_ITEM import org.fossify.gallery.interfaces.PlaybackSpeedListener import java.text.DecimalFormat +import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -90,6 +101,8 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi companion object { private const val PLAY_WHEN_READY_DRAG_DELAY = 100L private const val UPDATE_INTERVAL_MS = 250L + private const val TOUCH_HOLD_DURATION_MS = 500L + private const val TOUCH_HOLD_SPEED_MULTIPLIER = 2.0f } private var mIsFullscreen = false @@ -114,6 +127,24 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi private var mPlayWhenReadyHandler = Handler() private var mIgnoreCloseDown = false + private var mOriginalPlaybackSpeed = 1f + private var mIsLongPressActive = false + private var mTouchSlop = 0 + private var mInitialX = 0f + private var mInitialY = 0f + private lateinit var mPlaybackSpeedPill: TextView + private val mTouchHoldRunnable = Runnable { + // Prevent parent views from intercepting touch events, like a ViewPager swipe + contentHolder.parent.requestDisallowInterceptTouchEvent(true) + mIsLongPressActive = true + mOriginalPlaybackSpeed = config.playbackSpeed // Get current speed + contentHolder.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + runOnUiThread { + mExoPlayer?.setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) // Set to 2x speed + updatePlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) + } + mPlaybackSpeedPill.fadeIn() // Show UI feedback + } private val binding by viewBinding(ActivityVideoPlayerBinding::inflate) @@ -126,6 +157,8 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) + mPlaybackSpeedPill = binding.playbackSpeedPill + mTouchSlop = (ViewConfiguration.get(this).scaledTouchSlop) setupEdgeToEdge( padBottomSystem = listOf(binding.bottomVideoTimeHolder.root), ) @@ -271,6 +304,10 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi }) binding.videoSurfaceFrame.setOnTouchListener { view, event -> + handleTouchHoldEvent(event) + if (mIsLongPressActive) { + return@setOnTouchListener true + } handleEvent(event) gestureDetector.onTouchEvent(event) false @@ -697,12 +734,26 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi mTouchDownY = event.rawY mTouchDownTime = System.currentTimeMillis() mProgressAtDown = mExoPlayer!!.currentPosition +// if (mIsPlaying && event.pointerCount == 1) { +// mInitialX = event.x +// mInitialY = event.y +// mTimerHandler.postDelayed(mTouchHoldRunnable, TOUCH_HOLD_DURATION_MS) +// } } - MotionEvent.ACTION_POINTER_DOWN -> mIgnoreCloseDown = true + MotionEvent.ACTION_POINTER_DOWN -> { + mIgnoreCloseDown = true +// if (!mIsLongPressActive) { +// mTimerHandler.removeCallbacks(mTouchHoldRunnable) +// } + } MotionEvent.ACTION_MOVE -> { val diffX = event.rawX - mTouchDownX val diffY = event.rawY - mTouchDownY +// +// if (!mIsLongPressActive && (diffX > abs(mTouchSlop) || diffY > abs(mTouchSlop))) { +// mTimerHandler.removeCallbacks(mTouchHoldRunnable) +// } if (mIsDragged || (Math.abs(diffX) > mDragThreshold && Math.abs(diffX) > Math.abs( diffY @@ -759,7 +810,53 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi } } mIsDragged = false +// mTimerHandler.removeCallbacks(mTouchHoldRunnable) +// stopHoldSpeedMultiplierGesture() } +// +// MotionEvent.ACTION_CANCEL -> { +// mTimerHandler.removeCallbacks(mTouchHoldRunnable) +// stopHoldSpeedMultiplierGesture() +// } + } + } + + private fun handleTouchHoldEvent(event: MotionEvent) { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + if (mIsPlaying && event.pointerCount == 1) { + mInitialX = event.x + mInitialY = event.y + mTimerHandler.postDelayed(mTouchHoldRunnable, TOUCH_HOLD_DURATION_MS) + } + } + + MotionEvent.ACTION_MOVE -> { + val deltaX = abs(event.x - mInitialX) + val deltaY = abs(event.y - mInitialY) + if (!mIsLongPressActive && (deltaX > mTouchSlop || deltaY > mTouchSlop)) { + mTimerHandler.removeCallbacks(mTouchHoldRunnable) + } + } + + MotionEvent.ACTION_POINTER_DOWN -> { + if (!mIsLongPressActive) { + mTimerHandler.removeCallbacks(mTouchHoldRunnable) + } + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + mTimerHandler.removeCallbacks(mTouchHoldRunnable) + stopHoldSpeedMultiplierGesture() + } + } + } + + private fun stopHoldSpeedMultiplierGesture() { + if (mIsLongPressActive) { + updatePlaybackSpeed(mOriginalPlaybackSpeed) + mIsLongPressActive = false + mPlaybackSpeedPill.fadeOut() } } @@ -823,9 +920,30 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi override fun onSurfaceTextureDestroyed(surface: SurfaceTexture) = false + @RequiresApi(Build.VERSION_CODES.R) override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { mExoPlayer?.setVideoSurface(Surface(binding.videoSurface.surfaceTexture)) + val system = WindowInsets.CONSUMED.getInsetsIgnoringVisibility(Type.systemBars()) + + val pillTopMargin = system.top + resources.getActionBarHeight(this) + + resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() + (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { + setMargins( + 0, pillTopMargin, 0, 0 + ) + } } - override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {} + @RequiresApi(Build.VERSION_CODES.R) + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { + val system = WindowInsets.CONSUMED.getInsetsIgnoringVisibility(Type.systemBars()) + + val pillTopMargin = system.top + resources.getActionBarHeight(this) + + resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() + (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { + setMargins( + 0, pillTopMargin, 0, 0 + ) + } + } } diff --git a/app/src/main/res/layout/activity_video_player.xml b/app/src/main/res/layout/activity_video_player.xml index db3e040ee..7410f40e1 100644 --- a/app/src/main/res/layout/activity_video_player.xml +++ b/app/src/main/res/layout/activity_video_player.xml @@ -6,6 +6,26 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + Date: Fri, 19 Dec 2025 19:31:52 +0530 Subject: [PATCH 2/8] updated CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abc5a9cc4..78f1669c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Long press gesture to to play videos at 2x speed in separate video player ([#830]) + ### Fixed -- Fixed invisible color picker button in black themes ([#337]) +- Fixed invisible color picker button in black themes ([#337]) ## [1.10.0] - 2025-12-16 ### Added @@ -263,6 +266,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#754]: https://github.com/FossifyOrg/Gallery/issues/754 [#759]: https://github.com/FossifyOrg/Gallery/issues/759 [#786]: https://github.com/FossifyOrg/Gallery/issues/786 +[#830]: https://github.com/FossifyOrg/Gallery/issues/830 [Unreleased]: https://github.com/FossifyOrg/Gallery/compare/1.10.0...HEAD [1.10.0]: https://github.com/FossifyOrg/Gallery/compare/1.9.1...1.10.0 From adab458e874b53197e817317a89d3ed27a3b5b26 Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Fri, 19 Dec 2025 19:37:35 +0530 Subject: [PATCH 3/8] updated VideoPlayerActivity.kt to remove unnecessary comments --- .../gallery/activities/VideoPlayerActivity.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index ee2008157..a5d1387f0 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -734,26 +734,14 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi mTouchDownY = event.rawY mTouchDownTime = System.currentTimeMillis() mProgressAtDown = mExoPlayer!!.currentPosition -// if (mIsPlaying && event.pointerCount == 1) { -// mInitialX = event.x -// mInitialY = event.y -// mTimerHandler.postDelayed(mTouchHoldRunnable, TOUCH_HOLD_DURATION_MS) -// } } MotionEvent.ACTION_POINTER_DOWN -> { mIgnoreCloseDown = true -// if (!mIsLongPressActive) { -// mTimerHandler.removeCallbacks(mTouchHoldRunnable) -// } } MotionEvent.ACTION_MOVE -> { val diffX = event.rawX - mTouchDownX val diffY = event.rawY - mTouchDownY -// -// if (!mIsLongPressActive && (diffX > abs(mTouchSlop) || diffY > abs(mTouchSlop))) { -// mTimerHandler.removeCallbacks(mTouchHoldRunnable) -// } if (mIsDragged || (Math.abs(diffX) > mDragThreshold && Math.abs(diffX) > Math.abs( diffY @@ -810,14 +798,7 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi } } mIsDragged = false -// mTimerHandler.removeCallbacks(mTouchHoldRunnable) -// stopHoldSpeedMultiplierGesture() } -// -// MotionEvent.ACTION_CANCEL -> { -// mTimerHandler.removeCallbacks(mTouchHoldRunnable) -// stopHoldSpeedMultiplierGesture() -// } } } From df7590958ddfdd1d7b9665cd38a097d426d7533b Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Sat, 20 Dec 2025 20:22:49 +0530 Subject: [PATCH 4/8] added VideoGestureHelper.kt to separate long press 2x speed feature logic --- .../gallery/activities/VideoPlayerActivity.kt | 79 +++++-------------- .../gallery/helpers/VideoGestureHelper.kt | 79 +++++++++++++++++++ 2 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index a5d1387f0..3c2d29640 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -89,9 +89,9 @@ import org.fossify.gallery.helpers.ROTATE_BY_DEVICE_ROTATION import org.fossify.gallery.helpers.ROTATE_BY_SYSTEM_SETTING import org.fossify.gallery.helpers.SHOW_NEXT_ITEM import org.fossify.gallery.helpers.SHOW_PREV_ITEM +import org.fossify.gallery.helpers.VideoGestureHelper import org.fossify.gallery.interfaces.PlaybackSpeedListener import java.text.DecimalFormat -import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -101,8 +101,6 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi companion object { private const val PLAY_WHEN_READY_DRAG_DELAY = 100L private const val UPDATE_INTERVAL_MS = 250L - private const val TOUCH_HOLD_DURATION_MS = 500L - private const val TOUCH_HOLD_SPEED_MULTIPLIER = 2.0f } private var mIsFullscreen = false @@ -127,24 +125,9 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi private var mPlayWhenReadyHandler = Handler() private var mIgnoreCloseDown = false - private var mOriginalPlaybackSpeed = 1f - private var mIsLongPressActive = false private var mTouchSlop = 0 - private var mInitialX = 0f - private var mInitialY = 0f private lateinit var mPlaybackSpeedPill: TextView - private val mTouchHoldRunnable = Runnable { - // Prevent parent views from intercepting touch events, like a ViewPager swipe - contentHolder.parent.requestDisallowInterceptTouchEvent(true) - mIsLongPressActive = true - mOriginalPlaybackSpeed = config.playbackSpeed // Get current speed - contentHolder.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - runOnUiThread { - mExoPlayer?.setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) // Set to 2x speed - updatePlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) - } - mPlaybackSpeedPill.fadeIn() // Show UI feedback - } + private lateinit var videoGestureHelper: VideoGestureHelper private val binding by viewBinding(ActivityVideoPlayerBinding::inflate) @@ -159,6 +142,19 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi setContentView(binding.root) mPlaybackSpeedPill = binding.playbackSpeedPill mTouchSlop = (ViewConfiguration.get(this).scaledTouchSlop) + videoGestureHelper = VideoGestureHelper( + touchSlop = mTouchSlop, + isPlaying = { mIsPlaying }, + getCurrentSpeed = { config.playbackSpeed }, + setPlaybackSpeed = { speed -> + mExoPlayer?.setPlaybackSpeed(speed) // Set to 2x speed + updatePlaybackSpeed(speed) + }, + showPill = { mPlaybackSpeedPill.fadeIn() }, + hidePill = { mPlaybackSpeedPill.fadeOut() }, + performHaptic = { contentHolder.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) }, + disallowParentIntercept = { contentHolder.parent.requestDisallowInterceptTouchEvent(true) } + ) setupEdgeToEdge( padBottomSystem = listOf(binding.bottomVideoTimeHolder.root), ) @@ -304,10 +300,12 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi }) binding.videoSurfaceFrame.setOnTouchListener { view, event -> - handleTouchHoldEvent(event) - if (mIsLongPressActive) { + videoGestureHelper.onTouchEvent(event) + + if (videoGestureHelper.isLongPressActive) { return@setOnTouchListener true } + handleEvent(event) gestureDetector.onTouchEvent(event) false @@ -802,45 +800,6 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi } } - private fun handleTouchHoldEvent(event: MotionEvent) { - when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> { - if (mIsPlaying && event.pointerCount == 1) { - mInitialX = event.x - mInitialY = event.y - mTimerHandler.postDelayed(mTouchHoldRunnable, TOUCH_HOLD_DURATION_MS) - } - } - - MotionEvent.ACTION_MOVE -> { - val deltaX = abs(event.x - mInitialX) - val deltaY = abs(event.y - mInitialY) - if (!mIsLongPressActive && (deltaX > mTouchSlop || deltaY > mTouchSlop)) { - mTimerHandler.removeCallbacks(mTouchHoldRunnable) - } - } - - MotionEvent.ACTION_POINTER_DOWN -> { - if (!mIsLongPressActive) { - mTimerHandler.removeCallbacks(mTouchHoldRunnable) - } - } - - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - mTimerHandler.removeCallbacks(mTouchHoldRunnable) - stopHoldSpeedMultiplierGesture() - } - } - } - - private fun stopHoldSpeedMultiplierGesture() { - if (mIsLongPressActive) { - updatePlaybackSpeed(mOriginalPlaybackSpeed) - mIsLongPressActive = false - mPlaybackSpeedPill.fadeOut() - } - } - private fun handleNextFile() { Intent().apply { putExtra(GO_TO_NEXT_ITEM, true) diff --git a/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt b/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt new file mode 100644 index 000000000..f5dee0d16 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt @@ -0,0 +1,79 @@ +package org.fossify.gallery.helpers + +import android.os.Handler +import android.os.Looper +import android.view.MotionEvent +import kotlin.math.abs + +class VideoGestureHelper( + private val touchSlop: Int, + private val isPlaying: () -> Boolean, + private val getCurrentSpeed: () -> Float, + private val setPlaybackSpeed: (Float) -> Unit, + private val showPill: () -> Unit, + private val hidePill: () -> Unit, + private val performHaptic: () -> Unit, + private val disallowParentIntercept: () -> Unit +) { + companion object { + private const val TOUCH_HOLD_DURATION_MS = 500L + private const val TOUCH_HOLD_SPEED_MULTIPLIER = 2.0f + } + + private val handler = Handler(Looper.getMainLooper()) + + private var initialX = 0f + private var initialY = 0f + private var originalSpeed = 1f + internal var isLongPressActive = false + + private val touchHoldRunnable = Runnable { + disallowParentIntercept() + isLongPressActive = true + originalSpeed = getCurrentSpeed() + performHaptic() + setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) + showPill() + } + + fun onTouchEvent(event: MotionEvent) { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + if (isPlaying() && event.pointerCount == 1) { + initialX = event.x + initialY = event.y + handler.postDelayed(touchHoldRunnable, TOUCH_HOLD_DURATION_MS) + } + } + + MotionEvent.ACTION_MOVE -> { + val dx = abs(event.x - initialX) + val dy = abs(event.y - initialY) + if (!isLongPressActive && (dx > touchSlop || dy > touchSlop)) { + handler.removeCallbacks(touchHoldRunnable) + } + } + + MotionEvent.ACTION_POINTER_DOWN -> { + if (!isLongPressActive) { + handler.removeCallbacks(touchHoldRunnable) + } + } + + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> { + handler.removeCallbacks(touchHoldRunnable) + stop() + } + } + } + + fun stop() { + if (isLongPressActive) { + setPlaybackSpeed(originalSpeed) + isLongPressActive = false + hidePill() + } + } + +} From bf5b6c719c2147d42f486c4580f2ad0b85fd21e2 Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Sat, 20 Dec 2025 20:36:15 +0530 Subject: [PATCH 5/8] updated VideoGestureHelper.kt and VideoPlayerActivity.kt --- .../gallery/activities/VideoPlayerActivity.kt | 22 ++++++------ .../gallery/helpers/VideoGestureHelper.kt | 34 +++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index 3c2d29640..fa59c7598 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -89,6 +89,7 @@ import org.fossify.gallery.helpers.ROTATE_BY_DEVICE_ROTATION import org.fossify.gallery.helpers.ROTATE_BY_SYSTEM_SETTING import org.fossify.gallery.helpers.SHOW_NEXT_ITEM import org.fossify.gallery.helpers.SHOW_PREV_ITEM +import org.fossify.gallery.helpers.VideoGestureCallbacks import org.fossify.gallery.helpers.VideoGestureHelper import org.fossify.gallery.interfaces.PlaybackSpeedListener import java.text.DecimalFormat @@ -144,16 +145,17 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi mTouchSlop = (ViewConfiguration.get(this).scaledTouchSlop) videoGestureHelper = VideoGestureHelper( touchSlop = mTouchSlop, - isPlaying = { mIsPlaying }, - getCurrentSpeed = { config.playbackSpeed }, - setPlaybackSpeed = { speed -> - mExoPlayer?.setPlaybackSpeed(speed) // Set to 2x speed - updatePlaybackSpeed(speed) - }, - showPill = { mPlaybackSpeedPill.fadeIn() }, - hidePill = { mPlaybackSpeedPill.fadeOut() }, - performHaptic = { contentHolder.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) }, - disallowParentIntercept = { contentHolder.parent.requestDisallowInterceptTouchEvent(true) } + callbacks = VideoGestureCallbacks( + isPlaying = { mIsPlaying }, + getCurrentSpeed = { config.playbackSpeed }, + setPlaybackSpeed = { speed -> + mExoPlayer?.setPlaybackSpeed(speed) // Set to 2x speed + updatePlaybackSpeed(speed) + }, + showPill = { mPlaybackSpeedPill.fadeIn() }, + hidePill = { mPlaybackSpeedPill.fadeOut() }, + performHaptic = { contentHolder.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) }, + disallowParentIntercept = { contentHolder.parent.requestDisallowInterceptTouchEvent(true) }) ) setupEdgeToEdge( padBottomSystem = listOf(binding.bottomVideoTimeHolder.root), diff --git a/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt b/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt index f5dee0d16..47c2b0439 100644 --- a/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt +++ b/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt @@ -5,15 +5,19 @@ import android.os.Looper import android.view.MotionEvent import kotlin.math.abs +data class VideoGestureCallbacks( + val isPlaying: () -> Boolean, + val getCurrentSpeed: () -> Float, + val setPlaybackSpeed: (Float) -> Unit, + val showPill: () -> Unit, + val hidePill: () -> Unit, + val performHaptic: () -> Unit, + val disallowParentIntercept: () -> Unit +) + class VideoGestureHelper( private val touchSlop: Int, - private val isPlaying: () -> Boolean, - private val getCurrentSpeed: () -> Float, - private val setPlaybackSpeed: (Float) -> Unit, - private val showPill: () -> Unit, - private val hidePill: () -> Unit, - private val performHaptic: () -> Unit, - private val disallowParentIntercept: () -> Unit + private val callbacks: VideoGestureCallbacks ) { companion object { private const val TOUCH_HOLD_DURATION_MS = 500L @@ -28,18 +32,18 @@ class VideoGestureHelper( internal var isLongPressActive = false private val touchHoldRunnable = Runnable { - disallowParentIntercept() + callbacks.disallowParentIntercept() isLongPressActive = true - originalSpeed = getCurrentSpeed() - performHaptic() - setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) - showPill() + originalSpeed = callbacks.getCurrentSpeed() + callbacks.performHaptic() + callbacks.setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) + callbacks.showPill() } fun onTouchEvent(event: MotionEvent) { when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { - if (isPlaying() && event.pointerCount == 1) { + if (callbacks.isPlaying() && event.pointerCount == 1) { initialX = event.x initialY = event.y handler.postDelayed(touchHoldRunnable, TOUCH_HOLD_DURATION_MS) @@ -70,9 +74,9 @@ class VideoGestureHelper( fun stop() { if (isLongPressActive) { - setPlaybackSpeed(originalSpeed) + callbacks.setPlaybackSpeed(originalSpeed) isLongPressActive = false - hidePill() + callbacks.hidePill() } } From d2a90dede0136d572abe6c0b6668348c5f242c43 Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Sat, 20 Dec 2025 20:48:13 +0530 Subject: [PATCH 6/8] updated VideoPlayerActivity.kt --- .../gallery/activities/VideoPlayerActivity.kt | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index fa59c7598..2cbc4882b 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -24,14 +24,14 @@ import android.view.Surface import android.view.TextureView import android.view.View import android.view.ViewConfiguration -import android.view.WindowInsets import android.view.WindowManager import android.widget.RelativeLayout import android.widget.SeekBar import android.widget.TextView import androidx.annotation.RequiresApi import androidx.appcompat.content.res.AppCompatResources -import androidx.core.view.WindowInsetsCompat.Type +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.MediaItem @@ -257,6 +257,21 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi private fun initPlayer() { mUri = intent.data ?: return binding.videoToolbar.title = getFilenameFromUri(mUri!!) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + + // Calculate the top margin using the safe inset value + val pillTopMargin = systemBars.top + resources.getActionBarHeight(this) + + resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() + + // Apply the margin to the pill + (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { + setMargins(0, pillTopMargin, 0, 0) + } + + // Return the insets so other views can consume them if needed + insets + } initTimeHolder() showSystemUI() @@ -834,6 +849,19 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi mExoPlayer = null } + /*@RequiresApi(Build.VERSION_CODES.R) + fun setPillHeight() { + val system = WindowInsets.CONSUMED.getInsetsIgnoringVisibility(Type.systemBars()) + + val pillTopMargin = system.top + resources.getActionBarHeight(this) + + resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() + (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { + setMargins( + 0, pillTopMargin, 0, 0 + ) + } + }*/ + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { if (mExoPlayer != null && fromUser) { setPosition(progress.toLong()) @@ -865,27 +893,8 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi @RequiresApi(Build.VERSION_CODES.R) override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { mExoPlayer?.setVideoSurface(Surface(binding.videoSurface.surfaceTexture)) - val system = WindowInsets.CONSUMED.getInsetsIgnoringVisibility(Type.systemBars()) - - val pillTopMargin = system.top + resources.getActionBarHeight(this) + - resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() - (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { - setMargins( - 0, pillTopMargin, 0, 0 - ) - } } @RequiresApi(Build.VERSION_CODES.R) - override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { - val system = WindowInsets.CONSUMED.getInsetsIgnoringVisibility(Type.systemBars()) - - val pillTopMargin = system.top + resources.getActionBarHeight(this) + - resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() - (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { - setMargins( - 0, pillTopMargin, 0, 0 - ) - } - } + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {} } From df2e9b91ebaa3c8038cc69dd0a3e9e4d3871fa01 Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Fri, 2 Jan 2026 17:00:46 +0530 Subject: [PATCH 7/8] updated VideoPlayerActivity.kt --- .../gallery/activities/VideoPlayerActivity.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index 7d4db5352..b8b512091 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -13,7 +13,6 @@ import android.graphics.Point import android.graphics.SurfaceTexture import android.graphics.drawable.ColorDrawable import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.Handler import android.util.DisplayMetrics @@ -28,7 +27,6 @@ import android.view.WindowManager import android.widget.RelativeLayout import android.widget.SeekBar import android.widget.TextView -import androidx.annotation.RequiresApi import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -70,11 +68,9 @@ import org.fossify.gallery.extensions.getActionBarHeight import org.fossify.gallery.extensions.getFormattedDuration import org.fossify.gallery.extensions.getFriendlyMessage import org.fossify.gallery.extensions.hideSystemUI -import org.fossify.gallery.extensions.mute import org.fossify.gallery.extensions.openPath import org.fossify.gallery.extensions.shareMediumPath import org.fossify.gallery.extensions.showSystemUI -import org.fossify.gallery.extensions.unmute import org.fossify.gallery.fragments.PlaybackSpeedFragment import org.fossify.gallery.helpers.DRAG_THRESHOLD import org.fossify.gallery.helpers.EXOPLAYER_MAX_BUFFER_MS @@ -851,19 +847,6 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi mExoPlayer = null } - /*@RequiresApi(Build.VERSION_CODES.R) - fun setPillHeight() { - val system = WindowInsets.CONSUMED.getInsetsIgnoringVisibility(Type.systemBars()) - - val pillTopMargin = system.top + resources.getActionBarHeight(this) + - resources.getDimension(org.fossify.commons.R.dimen.normal_margin).toInt() - (mPlaybackSpeedPill.layoutParams as? RelativeLayout.LayoutParams)?.apply { - setMargins( - 0, pillTopMargin, 0, 0 - ) - } - }*/ - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { if (mExoPlayer != null && fromUser) { setPosition(progress.toLong()) @@ -892,11 +875,9 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi override fun onSurfaceTextureDestroyed(surface: SurfaceTexture) = false - @RequiresApi(Build.VERSION_CODES.R) override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { mExoPlayer?.setVideoSurface(Surface(binding.videoSurface.surfaceTexture)) } - @RequiresApi(Build.VERSION_CODES.R) override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {} } From 35bbbd0bcc405bbac92d65a55423c49b2f8a2db7 Mon Sep 17 00:00:00 2001 From: Arihant Tonage Date: Mon, 5 Jan 2026 17:28:50 +0530 Subject: [PATCH 8/8] updated VideoPlayerActivity.kt and VideoGestureHelper.kt to fix long press gesture issue with player entering full screen mode --- .../gallery/activities/VideoPlayerActivity.kt | 6 ++++- .../gallery/helpers/VideoGestureHelper.kt | 22 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt index b8b512091..a4868262c 100644 --- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt +++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt @@ -629,7 +629,11 @@ open class VideoPlayerActivity : BaseViewerActivity(), SeekBar.OnSeekBarChangeLi } private fun toggleFullscreen() { - fullscreenToggled(!mIsFullscreen) + if (!videoGestureHelper.wasLongPressHandled()) { + fullscreenToggled(!mIsFullscreen) + } else { + videoGestureHelper.updateLongPressHandled() + } } private fun fullscreenToggled(isFullScreen: Boolean) { diff --git a/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt b/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt index 47c2b0439..afdc43f34 100644 --- a/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt +++ b/app/src/main/kotlin/org/fossify/gallery/helpers/VideoGestureHelper.kt @@ -30,14 +30,17 @@ class VideoGestureHelper( private var initialY = 0f private var originalSpeed = 1f internal var isLongPressActive = false + private var wasLongPressHandled = false private val touchHoldRunnable = Runnable { - callbacks.disallowParentIntercept() - isLongPressActive = true - originalSpeed = callbacks.getCurrentSpeed() - callbacks.performHaptic() - callbacks.setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) - callbacks.showPill() + if (callbacks.isPlaying()) { + callbacks.disallowParentIntercept() + isLongPressActive = true + originalSpeed = callbacks.getCurrentSpeed() + callbacks.performHaptic() + callbacks.setPlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER) + callbacks.showPill() + } } fun onTouchEvent(event: MotionEvent) { @@ -72,8 +75,15 @@ class VideoGestureHelper( } } + fun wasLongPressHandled() = wasLongPressHandled + + fun updateLongPressHandled() { + wasLongPressHandled = false + } + fun stop() { if (isLongPressActive) { + wasLongPressHandled = true callbacks.setPlaybackSpeed(originalSpeed) isLongPressActive = false callbacks.hidePill()