feat(YouTube - Swipe controls): Add Speed and seek gestures delay setting to defer horizontal swipe gestures to prevent conflict with pinch-to-zoom

The single-finger horizontal swipe gesture for speed and seek was triggering immediately upon scrolling. This created a race condition where a user attempting to perform a multi-touch pinch-to-zoom would inadvertently trigger a horizontal swipe before the second finger was registered.

This change introduces a short time-based delay before a horizontal swipe is confirmed. If a second pointer is detected during this delay, the pending swipe is canceled, allowing the native pinch-to-zoom gesture to proceed without interference.
This commit is contained in:
Aaron Veil 2025-11-02 12:08:26 +03:00
parent b73819f5d2
commit 8dfd715a3f
6 changed files with 75 additions and 23 deletions

View file

@ -578,9 +578,12 @@ public class Settings extends BaseSettings {
// PreferenceScreen: Swipe controls
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", TRUE, true);
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", TRUE, true);
public static final BooleanSetting SWIPE_SPEED = new BooleanSetting("revanced_swipe_speed", TRUE, true);
public static final BooleanSetting SWIPE_SEEK = new BooleanSetting("revanced_swipe_seek", TRUE, true);
public static final BooleanSetting SWIPE_SWITCH_SPEED_AND_SEEK = new BooleanSetting("revanced_swipe_switch_speed_and_seek", FALSE, true);
public static final BooleanSetting SWIPE_SWITCH_SPEED_AND_SEEK = new BooleanSetting("revanced_swipe_switch_speed_and_seek", FALSE, true, parentsAny(SWIPE_SPEED, SWIPE_SEEK));
public static final LongSetting SWIPE_SPEED_AND_SEEK_DELAY = new LongSetting("revanced_swipe_speed_and_seek_delay", 50L, true, parentsAny(SWIPE_SPEED, SWIPE_SEEK));
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", TRUE, true,
parent(SWIPE_BRIGHTNESS));
public static final BooleanSetting SWIPE_PRESS_TO_ENGAGE = new BooleanSetting("revanced_swipe_press_to_engage", FALSE, true,

View file

@ -1,6 +1,9 @@
package app.revanced.extension.youtube.swipecontrols.controller.gesture
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerControlsVisibilityObserver
import app.revanced.extension.youtube.shared.PlayerControlsVisibilityObserverImpl
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider
@ -15,6 +18,7 @@ import app.revanced.extension.youtube.swipecontrols.misc.toPoint
*
* @param controller reference to the main swipe controller
*/
@Suppress("DEPRECATED_SMARTCAST_ON_DELEGATED_PROPERTY")
class ClassicSwipeController(
private val controller: SwipeControlsHostActivity,
private val config: SwipeControlsConfigurationProvider,
@ -26,6 +30,11 @@ class ClassicSwipeController(
*/
private var lastOnDownEvent: MotionEvent? = null
private val handler = Handler(Looper.getMainLooper())
private var horizontalSwipeRunnable: Runnable? = null
private var isHorizontalSwipeConfirmed = false
private val horizontalSwipeDelayMs = Settings.SWIPE_SPEED_AND_SEEK_DELAY.get()
override val shouldForceInterceptEvents: Boolean
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL
@ -56,6 +65,10 @@ class ClassicSwipeController(
// ignore gestures with more than one pointer
// when such a gesture is detected, dispatch the first event of the gesture to downstream
if (motionEvent.pointerCount > 1) {
// This is a multitouch gesture (like pinch-to-zoom), so cancel any pending swipe action.
cancelHorizontalSwipeRunnable()
isHorizontalSwipeConfirmed = false
lastOnDownEvent?.let {
controller.dispatchDownstreamTouchEvent(it)
it.recycle()
@ -69,6 +82,9 @@ class ClassicSwipeController(
}
override fun onDown(motionEvent: MotionEvent): Boolean {
cancelHorizontalSwipeRunnable()
isHorizontalSwipeConfirmed = false
// save the event for later
lastOnDownEvent?.recycle()
lastOnDownEvent = MotionEvent.obtain(motionEvent)
@ -77,6 +93,12 @@ class ClassicSwipeController(
return isInSwipeZone(motionEvent) || isInHorizontalSwipeZone(motionEvent)
}
override fun onUp(motionEvent: MotionEvent) {
super.onUp(motionEvent)
cancelHorizontalSwipeRunnable()
isHorizontalSwipeConfirmed = false
}
override fun onSingleTapUp(motionEvent: MotionEvent): Boolean {
MotionEvent.obtain(motionEvent).let {
it.action = MotionEvent.ACTION_DOWN
@ -114,6 +136,9 @@ class ClassicSwipeController(
// cancel if locked
if (!config.enableSwipeControlsLockMode && config.isScreenLocked) return false
if (currentSwipe == SwipeDetector.SwipeDirection.VERTICAL) {
cancelHorizontalSwipeRunnable()
isHorizontalSwipeConfirmed = false
return when (from.toPoint()) {
in controller.zones.volume -> {
scrollVolume(distanceY)
@ -122,31 +147,51 @@ class ClassicSwipeController(
in controller.zones.brightness -> {
scrollBrightness(distanceY)
true}
else -> false
}
} else if (currentSwipe == SwipeDetector.SwipeDirection.HORIZONTAL) {
return when (from.toPoint()) {
in controller.zones.speed -> {
if (config.enableSpeedControl) {
scrollSpeed(distanceX)
true
}
false
true
}
in controller.zones.seek -> {
if (config.enableSeekControl) {
scrollSeek(distanceX)
true
}
false
}
else -> false
}
}
else if (currentSwipe == SwipeDetector.SwipeDirection.HORIZONTAL) {
if (isHorizontalSwipeConfirmed) {
return processHorizontalSwipe(from, distanceX)
}
if (horizontalSwipeRunnable == null) {
horizontalSwipeRunnable = Runnable {
isHorizontalSwipeConfirmed = true
processHorizontalSwipe(from, distanceX)
}
handler.postDelayed(horizontalSwipeRunnable!!, horizontalSwipeDelayMs)
}
return true
}
return false
}
private fun processHorizontalSwipe(from: MotionEvent, distanceX: Double): Boolean {
return when (from.toPoint()) {
in controller.zones.speed -> {
if (config.enableSpeedControl) {
scrollSpeed(distanceX)
true
} else false
}
in controller.zones.seek -> {
if (config.enableSeekControl) {
scrollSeek(distanceX)
true
} else false
}
else -> false
}
}
private fun cancelHorizontalSwipeRunnable() {
horizontalSwipeRunnable?.let {
handler.removeCallbacks(it)
}
horizontalSwipeRunnable = null
}
}

View file

@ -481,7 +481,6 @@ private val emptyTitles = setOf(
"revanced_external_downloader_package_name_playlist",
"revanced_external_downloader_package_name_video",
"revanced_external_downloader_package_name_video_long_press",
"revanced_fix_swipe_tap_and_hold_speed",
"revanced_gemini_transcribe_subtitles_font_size",
"revanced_gms_show_dialog",
"revanced_hide_live_chat_replay_button",
@ -524,6 +523,7 @@ private val emptyTitles = setOf(
"revanced_swipe_overlay_progress_speed_color",
"revanced_swipe_overlay_progress_volume_color",
"revanced_swipe_seek_distance",
"revanced_swipe_speed_and_seek_delay",
"revanced_swipe_speed_distance",
"revanced_swipe_switch_speed_and_seek",
"revanced_swipe_volume_distance",

View file

@ -2662,6 +2662,8 @@ Adjust brightness by swiping vertically on the left side of the screen."</string
Seek the video by swiping horizontally on the top half of the screen."</string>
<string name="revanced_swipe_seek_title">Enable seek gesture</string>
<string name="revanced_swipe_speed_and_seek_delay_summary">The delay in milliseconds before a horizontal swipe is recognized. Increase this value if it interferes with the pinch-to-zoom gesture.</string>
<string name="revanced_swipe_speed_and_seek_delay_title">Speed and seek gestures delay</string>
<string name="revanced_swipe_speed_distance_summary">The amount of pixels to swipe to change the video speed. Lower values make the speed change faster, higher values make it slower (1-1000).</string>
<string name="revanced_swipe_speed_distance_title">Speed distance</string>
<string name="revanced_swipe_speed_summary_off">Fullscreen speed swipe is disabled.</string>

View file

@ -779,6 +779,7 @@
<app.revanced.extension.shared.settings.preference.ResettableEditTextPreference android:title="@string/revanced_swipe_seek_distance_title" android:key="revanced_swipe_seek_distance" android:summary="@string/revanced_swipe_seek_distance_summary" android:inputType="number" />
<SwitchPreference android:title="@string/revanced_swipe_switch_speed_and_seek_title" android:key="revanced_swipe_switch_speed_and_seek" android:summaryOn="@string/revanced_swipe_switch_speed_and_seek_summary_on" android:summaryOff="@string/revanced_swipe_switch_speed_and_seek_summary_off" />
<app.revanced.extension.shared.settings.preference.ResettableEditTextPreference android:title="@string/revanced_swipe_speed_and_seek_delay_title" android:key="revanced_swipe_speed_and_seek_delay" android:summary="@string/revanced_swipe_speed_and_seek_delay_summary" android:inputType="number" />
<SwitchPreference android:title="@string/revanced_swipe_press_to_engage_title" android:key="revanced_swipe_press_to_engage" android:summaryOn="@string/revanced_swipe_press_to_engage_summary_on" android:summaryOff="@string/revanced_swipe_press_to_engage_summary_off" />
<SwitchPreference android:title="@string/revanced_swipe_haptic_feedback_title" android:key="revanced_swipe_haptic_feedback" android:summaryOn="@string/revanced_swipe_haptic_feedback_summary_on" android:summaryOff="@string/revanced_swipe_haptic_feedback_summary_off" />

View file

@ -49,6 +49,7 @@ BLACKLIST: set[str] = {
"revanced_swipe_seek_sensitivity",
"revanced_swipe_show_circular_overlay",
"revanced_swipe_speed",
"revanced_swipe_speed_and_seek_delay",
"revanced_swipe_speed_distance",
"revanced_swipe_speed_sensitivity",
"revanced_swipe_switch_speed_and_seek",