Skip to content

Commit d050e8b

Browse files
Carl MillerCarl Miller
authored andcommitted
feat: Add hold-to-fast-forward (2x speed) feature
- Add tap and hold gesture to increase playback speed to 2x while held - Add settings toggle in Video & Audio settings to enable/disable the feature - Add visual indicator overlay showing '2x Speed' when feature is active - Feature is enabled by default and respects user preference
1 parent 6e0b7be commit d050e8b

File tree

8 files changed

+168
-0
lines changed

8 files changed

+168
-0
lines changed

app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.view.View
99
import androidx.core.os.postDelayed
1010
import org.schabi.newpipe.databinding.PlayerBinding
1111
import org.schabi.newpipe.player.Player
12+
import org.schabi.newpipe.player.helper.PlayerHelper
1213
import org.schabi.newpipe.player.ui.VideoPlayerUi
1314

1415
/**
@@ -24,11 +25,87 @@ abstract class BasePlayerGestureListener(
2425
protected val player: Player = playerUi.player
2526
protected val binding: PlayerBinding = playerUi.binding
2627

28+
// ///////////////////////////////////////////////////////////////////
29+
// Hold to fast forward (2x speed)
30+
// ///////////////////////////////////////////////////////////////////
31+
32+
private var isHoldingForFastForward = false
33+
private var originalPlaybackSpeed = 1.0f
34+
private val fastForwardSpeed = 2.0f
35+
2736
override fun onTouch(v: View, event: MotionEvent): Boolean {
2837
playerUi.gestureDetector.onTouchEvent(event)
38+
39+
// Handle touch up to restore original speed when hold-to-fast-forward is active
40+
if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
41+
if (isHoldingForFastForward) {
42+
stopHoldToFastForward()
43+
}
44+
}
45+
2946
return false
3047
}
3148

49+
override fun onLongPress(e: MotionEvent) {
50+
if (DEBUG) {
51+
Log.d(TAG, "onLongPress called with e = [$e]")
52+
}
53+
54+
// Check if hold-to-fast-forward is enabled in settings
55+
if (!PlayerHelper.isHoldToFastForwardEnabled(player.context)) {
56+
return
57+
}
58+
59+
// Only activate if player is playing and not in a popup menu
60+
if (player.currentState != Player.STATE_PLAYING || playerUi.isSomePopupMenuVisible) {
61+
return
62+
}
63+
64+
// Don't activate during double tap mode
65+
if (isDoubleTapping) {
66+
return
67+
}
68+
69+
startHoldToFastForward()
70+
}
71+
72+
private fun startHoldToFastForward() {
73+
if (isHoldingForFastForward) {
74+
return
75+
}
76+
77+
if (DEBUG) {
78+
Log.d(TAG, "startHoldToFastForward: activating 2x speed")
79+
}
80+
81+
isHoldingForFastForward = true
82+
originalPlaybackSpeed = player.playbackSpeed
83+
84+
// Set playback speed to 2x
85+
player.setPlaybackSpeed(fastForwardSpeed)
86+
87+
// Show visual feedback
88+
playerUi.onHoldToFastForwardStart()
89+
}
90+
91+
private fun stopHoldToFastForward() {
92+
if (!isHoldingForFastForward) {
93+
return
94+
}
95+
96+
if (DEBUG) {
97+
Log.d(TAG, "stopHoldToFastForward: restoring original speed $originalPlaybackSpeed")
98+
}
99+
100+
isHoldingForFastForward = false
101+
102+
// Restore original playback speed
103+
player.setPlaybackSpeed(originalPlaybackSpeed)
104+
105+
// Hide visual feedback
106+
playerUi.onHoldToFastForwardEnd()
107+
}
108+
32109
private fun onDoubleTap(
33110
event: MotionEvent,
34111
portion: DisplayPortion

app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ public static boolean isAutoQueueEnabled(@NonNull final Context context) {
231231
.getBoolean(context.getString(R.string.auto_queue_key), false);
232232
}
233233

234+
public static boolean isHoldToFastForwardEnabled(@NonNull final Context context) {
235+
return getPreferences(context)
236+
.getBoolean(context.getString(R.string.hold_to_fast_forward_key), true);
237+
}
238+
234239
public static boolean isClearingQueueConfirmationRequired(@NonNull final Context context) {
235240
return getPreferences(context)
236241
.getBoolean(context.getString(R.string.clear_queue_confirmation_key), false);

app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,33 @@ protected void setupElementsSize(final int buttonsMinWidth,
441441
//endregion
442442

443443

444+
/*//////////////////////////////////////////////////////////////////////////
445+
// Hold to fast forward
446+
//////////////////////////////////////////////////////////////////////////*/
447+
//region Hold to fast forward
448+
449+
/**
450+
* Called when hold-to-fast-forward is activated (long press detected).
451+
* Shows the visual indicator overlay.
452+
*/
453+
public void onHoldToFastForwardStart() {
454+
animate(binding.holdToFastForwardOverlay, true, DEFAULT_CONTROLS_DURATION);
455+
// Hide controls while fast forwarding
456+
if (isControlsVisible()) {
457+
hideControls(DEFAULT_CONTROLS_DURATION, 0);
458+
}
459+
}
460+
461+
/**
462+
* Called when hold-to-fast-forward is deactivated (finger released).
463+
* Hides the visual indicator overlay.
464+
*/
465+
public void onHoldToFastForwardEnd() {
466+
animate(binding.holdToFastForwardOverlay, false, DEFAULT_CONTROLS_DURATION);
467+
}
468+
//endregion
469+
470+
444471
/*//////////////////////////////////////////////////////////////////////////
445472
// Broadcast receiver
446473
//////////////////////////////////////////////////////////////////////////*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:tint="@color/defaultIconTint"
5+
android:viewportWidth="24"
6+
android:viewportHeight="24">
7+
<path
8+
android:fillColor="#ffffff"
9+
android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z" />
10+
</vector>

app/src/main/res/layout/player.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,4 +791,40 @@
791791
android:alpha="0"
792792
android:visibility="invisible" /> <!-- Required for the first appearance fading correctly -->
793793

794+
<!-- Hold to fast forward indicator -->
795+
<LinearLayout
796+
android:id="@+id/holdToFastForwardOverlay"
797+
android:layout_width="wrap_content"
798+
android:layout_height="wrap_content"
799+
android:layout_centerHorizontal="true"
800+
android:layout_marginTop="80dp"
801+
android:background="@drawable/background_oval_black_transparent"
802+
android:gravity="center"
803+
android:orientation="horizontal"
804+
android:paddingStart="20dp"
805+
android:paddingTop="12dp"
806+
android:paddingEnd="20dp"
807+
android:paddingBottom="12dp"
808+
android:visibility="gone"
809+
tools:visibility="visible">
810+
811+
<androidx.appcompat.widget.AppCompatImageView
812+
android:layout_width="24dp"
813+
android:layout_height="24dp"
814+
android:layout_marginEnd="8dp"
815+
android:src="@drawable/ic_fast_forward"
816+
app:tint="@color/white"
817+
tools:ignore="ContentDescription" />
818+
819+
<org.schabi.newpipe.views.NewPipeTextView
820+
android:id="@+id/holdToFastForwardText"
821+
android:layout_width="wrap_content"
822+
android:layout_height="wrap_content"
823+
android:text="@string/hold_to_fast_forward_indicator"
824+
android:textColor="@android:color/white"
825+
android:textSize="16sp"
826+
android:textStyle="bold" />
827+
828+
</LinearLayout>
829+
794830
</RelativeLayout>

app/src/main/res/values/settings_keys.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
<string name="clear_queue_confirmation_key">clear_queue_confirmation_key</string>
2626
<string name="ignore_hardware_media_buttons_key">ignore_hardware_media_buttons_key</string>
2727

28+
<string name="hold_to_fast_forward_key">hold_to_fast_forward_key</string>
29+
2830
<string name="popup_saved_width_key">popup_saved_width</string>
2931
<string name="popup_saved_x_key">popup_saved_x</string>
3032
<string name="popup_saved_y_key">popup_saved_y</string>

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
<string name="clear_queue_confirmation_description">The active player queue will be replaced</string>
9292
<string name="ignore_hardware_media_buttons_title">Ignore hardware media button events</string>
9393
<string name="ignore_hardware_media_buttons_summary">Useful, for instance, if you are using a headset with broken physical buttons</string>
94+
<string name="hold_to_fast_forward_title">Hold to fast forward</string>
95+
<string name="hold_to_fast_forward_summary">Tap and hold on the video to play at 2x speed while held</string>
96+
<string name="hold_to_fast_forward_indicator">2x Speed</string>
9497
<string name="show_comments_title">Show comments</string>
9598
<string name="show_comments_summary">Turn off to hide comments</string>
9699
<string name="show_next_and_similar_title">Show \'Next\' and \'Similar\' videos</string>

app/src/main/res/xml/video_audio_settings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,5 +249,13 @@
249249
android:title="@string/ignore_hardware_media_buttons_title"
250250
app:singleLineTitle="false"
251251
app:iconSpaceReserved="false" />
252+
253+
<SwitchPreferenceCompat
254+
android:defaultValue="true"
255+
android:key="@string/hold_to_fast_forward_key"
256+
android:summary="@string/hold_to_fast_forward_summary"
257+
android:title="@string/hold_to_fast_forward_title"
258+
app:singleLineTitle="false"
259+
app:iconSpaceReserved="false" />
252260
</PreferenceCategory>
253261
</PreferenceScreen>

0 commit comments

Comments
 (0)