From 6e9644ce95bbba97aec59c9815b342b3307a7225 Mon Sep 17 00:00:00 2001 From: Guilherme Garcia Date: Wed, 24 Jun 2026 10:16:18 -0300 Subject: [PATCH] feat: add speed gesture when fullscreen --- .../org/schabi/newpipe/player/Player.java | 4 ++ .../player/event/BasePlayerGestureListener.kt | 6 ++- .../player/event/PlayerGestureListener.java | 53 +++++++++++++++++++ .../newpipe/player/helper/PlayerHelper.java | 5 ++ .../settings/GestureSettingsFragment.java | 27 ++++++++++ app/src/main/res/layout/player.xml | 14 +++++ app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/gesture_settings.xml | 8 +++ 9 files changed, 120 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index a11f1f64e..74a208dc5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -5493,6 +5493,10 @@ public TextView getSwipeSeekDisplay() { return binding.swipeSeekDisplay; } + public TextView getSwipeSpeedDisplay() { + return binding.swipeSpeedDisplay; + } + public PlayerFastSeekOverlay getFastSeekOverlay() { return binding.fastSeekOverlay; } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt index 081d640dd..cc18f9afd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt @@ -101,7 +101,11 @@ abstract class BasePlayerGestureListener( // Check if swiping up (negative y velocity) if (yVelocity < 0) { - v.parent.requestDisallowInterceptTouchEvent(player.isFullscreenGestureEnabled || player.isFullscreen) + v.parent.requestDisallowInterceptTouchEvent( + player.isFullscreenGestureEnabled + || player.isFullscreen + || PlayerHelper.isPlaybackSpeedGestureEnabled(service) + ) } else { v.parent.requestDisallowInterceptTouchEvent(player.isFullscreen) } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java index b46b7fb4f..e67aa1104 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java @@ -7,6 +7,8 @@ import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME; import static org.schabi.newpipe.player.Player.STATE_PLAYING; +import java.util.Locale; + import android.app.Activity; import android.util.Log; import android.view.MotionEvent; @@ -49,6 +51,12 @@ public class PlayerGestureListener private long swipeSeekTargetPosition = 0L; private boolean isChangingVolume = false; private boolean isChangingBrightness = false; + private boolean isChangingSpeed = false; + private float speedGestureStartSpeed = 1.0f; + private float accumulatedSpeedScroll = 0f; + private static final float SPEED_SWIPE_PIXELS_PER_STEP = 20f; + private static final float SPEED_MIN = 0.1f; + private static final float SPEED_MAX = 4.0f; private boolean isPendingScreenRotation = false; private boolean isFullscreenRotationGesture = false; @@ -127,6 +135,11 @@ public void onScroll(@NonNull final PlayerService.PlayerType playerType, return; } + if (isChangingSpeed) { + onScrollMainSpeed(distanceY); + return; + } + final boolean isHorizontal = Math.abs(distanceX) > Math.abs(distanceY); if (!isHorizontal && isFullscreenGestureEnabled && ((player.isFullscreen() && distanceY < 0 && portion == DisplayPortion.MIDDLE) || @@ -139,6 +152,15 @@ public void onScroll(@NonNull final PlayerService.PlayerType playerType, if(!player.isFullscreen()) { return; } + + final boolean isPlaybackSpeedGestureEnabled = + PlayerHelper.isPlaybackSpeedGestureEnabled(service); + if (!isHorizontal && isPlaybackSpeedGestureEnabled && player.isFullscreen() + && portion == DisplayPortion.MIDDLE) { + onScrollMainSpeed(distanceY); + return; + } + if (isSwipeSeekGestureEnabled && isHorizontal) { onScrollMainSeek(distanceX); return; @@ -258,6 +280,31 @@ private void onScrollMainBrightness(final float distanceX, final float distanceY } } + private void onScrollMainSpeed(final float distanceY) { + if (!isChangingSpeed) { + isChangingSpeed = true; + accumulatedSpeedScroll = 0f; + speedGestureStartSpeed = player.getPlaybackSpeed(); + animate(player.getSwipeSpeedDisplay(), true, DEFAULT_CONTROLS_DURATION, SCALE_AND_ALPHA); + if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { + animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA); + isChangingVolume = false; + } + if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { + animate(player.getBrightnessRelativeLayout(), false, 200, SCALE_AND_ALPHA); + isChangingBrightness = false; + } + } + + accumulatedSpeedScroll += distanceY; // positive = swipe up = faster + final float rawSpeed = speedGestureStartSpeed + + (accumulatedSpeedScroll / SPEED_SWIPE_PIXELS_PER_STEP) * 0.1f; + final float speed = Math.max(SPEED_MIN, Math.min(SPEED_MAX, + Math.round(rawSpeed * 10) / 10.0f)); + player.setPlaybackSpeed(speed); + player.getSwipeSpeedDisplay().setText(String.format(Locale.getDefault(), "%.1fx", speed)); + } + private void onScrollMainSeek(final float distanceX) { // The first swipe determines the active overlay; once seeking is engaged // we hide volume and brightness controls so mixed movements do not trigger them. @@ -327,6 +374,10 @@ public void onScrollEnd(@NonNull final PlayerService.PlayerType playerType, animate(player.getSwipeSeekDisplay(), false, 200, SCALE_AND_ALPHA); isSwipeSeeking = false; } + if (isChangingSpeed) { + animate(player.getSwipeSpeedDisplay(), false, 200, SCALE_AND_ALPHA, 200); + isChangingSpeed = false; + } if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA, 200); @@ -357,10 +408,12 @@ public void onPopupResizingStart() { player.hideControls(0, 0); animate(player.getFastSeekOverlay(), false, 0); animate(player.getSwipeSeekDisplay(), false, 0, ALPHA, 0); + animate(player.getSwipeSpeedDisplay(), false, 0, ALPHA, 0); animate(player.getVolumeRelativeLayout(), false, 0, ALPHA, 0); animate(player.getBrightnessRelativeLayout(), false, 0, ALPHA, 0); isChangingVolume = false; isChangingBrightness = false; + isChangingSpeed = false; } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 5aa298d03..51fbff46f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -334,6 +334,11 @@ public static boolean isSwipeSeekGestureEnabled(@NonNull final Context context) .getBoolean(context.getString(R.string.swipe_seek_gesture_control_key), true); } + public static boolean isPlaybackSpeedGestureEnabled(@NonNull final Context context) { + return getPreferences(context) + .getBoolean(context.getString(R.string.playback_speed_gesture_control_key), false); + } + public static boolean isStartMainPlayerFullscreenEnabled(@NonNull final Context context) { return getPreferences(context) .getBoolean(context.getString(R.string.start_main_player_fullscreen_key), false); diff --git a/app/src/main/java/org/schabi/newpipe/settings/GestureSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/GestureSettingsFragment.java index d117bc13b..c3b6e6ac1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/GestureSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/GestureSettingsFragment.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import androidx.preference.ListPreference; +import androidx.preference.SwitchPreferenceCompat; import org.schabi.newpipe.R; @@ -19,6 +20,32 @@ public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) { addPreferencesFromResourceRegistry(); updateSeekOptions(); + setupSpeedGestureMutualExclusion(); + } + + private void setupSpeedGestureMutualExclusion() { + final SwitchPreferenceCompat fullscreenPref = findPreference( + getString(R.string.fullscreen_gesture_control_key)); + final SwitchPreferenceCompat speedPref = findPreference( + getString(R.string.playback_speed_gesture_control_key)); + + if (fullscreenPref == null || speedPref == null) { + return; + } + + fullscreenPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (Boolean.TRUE.equals(newValue)) { + speedPref.setChecked(false); + } + return true; + }); + + speedPref.setOnPreferenceChangeListener((pref, newValue) -> { + if (Boolean.TRUE.equals(newValue)) { + fullscreenPref.setChecked(false); + } + return true; + }); } private void updateSeekOptions() { diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index d2b4d022e..70a860759 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -920,6 +920,20 @@ tools:ignore="RtlHardcoded" tools:text="+0:00 (0:00)" /> + + brightness_gesture_control fullscreen_gesture_control swipe_seek_gesture_control + playback_speed_gesture_control resume_on_audio_focus_gain popup_remember_size_pos_key use_inexact_seek_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e111ae3c7..7a31b2c58 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -93,6 +93,8 @@ Use gestures to control player brightness Swipe to seek gesture Swipe right to fast-forward, and left to rewind +Swipe to control playback speed +Use gestures to control playback speed. Disables fullscreen gesture control Search suggestions Choose the suggestions to show when searching Local search suggestions @@ -922,7 +924,7 @@ View on GitHub Support the Project Thank you for using PipePipe! If you find it useful, please consider becoming a supporter on Ko-Fi. Your support is important to me and helps me add more exciting new features. Every bit counts! 😇 -Use gestures to enter / exit fullscreen +Use gestures to enter / exit fullscreen. Disables playback speed gesture control Fullscreen gesture control Make sure only PipePipe is playing audio Require audio focus diff --git a/app/src/main/res/xml/gesture_settings.xml b/app/src/main/res/xml/gesture_settings.xml index dece82fe9..7086453d1 100644 --- a/app/src/main/res/xml/gesture_settings.xml +++ b/app/src/main/res/xml/gesture_settings.xml @@ -26,6 +26,14 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + +