diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt
index 0453f297a4f..4c6d658cf88 100644
--- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt
@@ -9,6 +9,7 @@ import android.view.View
import androidx.core.os.postDelayed
import org.schabi.newpipe.databinding.PlayerBinding
import org.schabi.newpipe.player.Player
+import org.schabi.newpipe.player.helper.PlayerHelper
import org.schabi.newpipe.player.ui.VideoPlayerUi
/**
@@ -24,11 +25,87 @@ abstract class BasePlayerGestureListener(
protected val player: Player = playerUi.player
protected val binding: PlayerBinding = playerUi.binding
+ // ///////////////////////////////////////////////////////////////////
+ // Hold to fast forward (2x speed)
+ // ///////////////////////////////////////////////////////////////////
+
+ private var isHoldingForFastForward = false
+ private var originalPlaybackSpeed = 1.0f
+ private val fastForwardSpeed = 2.0f
+
override fun onTouch(v: View, event: MotionEvent): Boolean {
playerUi.gestureDetector.onTouchEvent(event)
+
+ // Handle touch up to restore original speed when hold-to-fast-forward is active
+ if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
+ if (isHoldingForFastForward) {
+ stopHoldToFastForward()
+ }
+ }
+
return false
}
+ override fun onLongPress(e: MotionEvent) {
+ if (DEBUG) {
+ Log.d(TAG, "onLongPress called with e = [$e]")
+ }
+
+ // Check if hold-to-fast-forward is enabled in settings
+ if (!PlayerHelper.isHoldToFastForwardEnabled(player.context)) {
+ return
+ }
+
+ // Only activate if player is playing and not in a popup menu
+ if (player.currentState != Player.STATE_PLAYING || playerUi.isSomePopupMenuVisible) {
+ return
+ }
+
+ // Don't activate during double tap mode
+ if (isDoubleTapping) {
+ return
+ }
+
+ startHoldToFastForward()
+ }
+
+ private fun startHoldToFastForward() {
+ if (isHoldingForFastForward) {
+ return
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "startHoldToFastForward: activating 2x speed")
+ }
+
+ isHoldingForFastForward = true
+ originalPlaybackSpeed = player.playbackSpeed
+
+ // Set playback speed to 2x
+ player.setPlaybackSpeed(fastForwardSpeed)
+
+ // Show visual feedback
+ playerUi.onHoldToFastForwardStart()
+ }
+
+ private fun stopHoldToFastForward() {
+ if (!isHoldingForFastForward) {
+ return
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "stopHoldToFastForward: restoring original speed $originalPlaybackSpeed")
+ }
+
+ isHoldingForFastForward = false
+
+ // Restore original playback speed
+ player.setPlaybackSpeed(originalPlaybackSpeed)
+
+ // Hide visual feedback
+ playerUi.onHoldToFastForwardEnd()
+ }
+
private fun onDoubleTap(
event: MotionEvent,
portion: DisplayPortion
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 c335e9b7c60..c02948c1783 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
@@ -231,6 +231,11 @@ public static boolean isAutoQueueEnabled(@NonNull final Context context) {
.getBoolean(context.getString(R.string.auto_queue_key), false);
}
+ public static boolean isHoldToFastForwardEnabled(@NonNull final Context context) {
+ return getPreferences(context)
+ .getBoolean(context.getString(R.string.hold_to_fast_forward_key), true);
+ }
+
public static boolean isClearingQueueConfirmationRequired(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.clear_queue_confirmation_key), false);
diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java
index 59be1d67dcc..669193c7ed6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java
@@ -441,6 +441,30 @@ protected void setupElementsSize(final int buttonsMinWidth,
//endregion
+ /*//////////////////////////////////////////////////////////////////////////
+ // Hold to fast forward
+ //////////////////////////////////////////////////////////////////////////*/
+ //region Hold to fast forward
+
+ /**
+ * Called when hold-to-fast-forward is activated (long press detected).
+ */
+ public void onHoldToFastForwardStart() {
+ // Hide controls while fast forwarding
+ if (isControlsVisible()) {
+ hideControls(DEFAULT_CONTROLS_DURATION, 0);
+ }
+ }
+
+ /**
+ * Called when hold-to-fast-forward is deactivated (finger released).
+ */
+ public void onHoldToFastForwardEnd() {
+ // No visual indicator to hide
+ }
+ //endregion
+
+
/*//////////////////////////////////////////////////////////////////////////
// Broadcast receiver
//////////////////////////////////////////////////////////////////////////*/
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index d95d1270cc9..f7b5237ee45 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -25,6 +25,8 @@
clear_queue_confirmation_key
ignore_hardware_media_buttons_key
+ hold_to_fast_forward_key
+
popup_saved_width
popup_saved_x
popup_saved_y
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c439f19e272..8c877cc56d6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -91,6 +91,8 @@
The active player queue will be replaced
Ignore hardware media button events
Useful, for instance, if you are using a headset with broken physical buttons
+ Hold to fast forward
+ Tap and hold on the video to play at 2x speed while held
Show comments
Turn off to hide comments
Show \'Next\' and \'Similar\' videos
diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml
index 727ce4df40a..b24b034beac 100644
--- a/app/src/main/res/xml/video_audio_settings.xml
+++ b/app/src/main/res/xml/video_audio_settings.xml
@@ -249,5 +249,13 @@
android:title="@string/ignore_hardware_media_buttons_title"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
+
+