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 49e72328e40..555cf380883 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -176,6 +176,10 @@ public final class Player implements PlaybackListener, Listener {
public static final int RENDERER_UNAVAILABLE = -1;
private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG";
+ public static final int DEFAULT_PREV_NEXT_MODE = 0;
+ public static final int SMART_PREV_NEXT_MODE = 1;
+ public static final int SEEK_PREV_NEXT_MODE = 2;
+
/*//////////////////////////////////////////////////////////////////////////
// Playback
//////////////////////////////////////////////////////////////////////////*/
@@ -222,6 +226,8 @@ public final class Player implements PlaybackListener, Listener {
private PlayerType playerType = PlayerType.MAIN;
private int currentState = STATE_PREFLIGHT;
+ private int queueActionsArePrevNext = DEFAULT_PREV_NEXT_MODE;
+
// audio only mode does not mean that player type is background, but that the player was
// minimized to background but will resume automatically to the original player type
private boolean isAudioOnly = false;
@@ -1719,6 +1725,40 @@ public void fastRewind() {
seekBy(-retrieveSeekDurationFromPreferences(this));
triggerProgressUpdate();
}
+
+ public void playerActionNext() {
+ switch (queueActionsArePrevNext) {
+ case SMART_PREV_NEXT_MODE:
+ if (playQueue != null && playQueue.size() > 1) {
+ playNext();
+ } else {
+ fastForward();
+ }
+ break;
+ case SEEK_PREV_NEXT_MODE:
+ fastForward();
+ break;
+ default:
+ playNext();
+ }
+ }
+
+ public void playerActionPrevious() {
+ switch (queueActionsArePrevNext) {
+ case SMART_PREV_NEXT_MODE:
+ if (playQueue != null && playQueue.size() > 1) {
+ playPrevious();
+ } else {
+ fastRewind();
+ }
+ break;
+ case SEEK_PREV_NEXT_MODE:
+ fastRewind();
+ break;
+ default:
+ playPrevious();
+ }
+ }
//endregion
@@ -2237,6 +2277,9 @@ public void setAudioTrack(@Nullable final String audioTrackId) {
reloadPlayQueueManager();
}
+ public void setQueueActionsPrevNext(final int state) {
+ queueActionsArePrevNext = state;
+ }
@NonNull
public Context getContext() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
index 737ebc5dd04..47979622fab 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
@@ -42,6 +42,13 @@ public class MediaSessionPlayerUi extends PlayerUi
private MediaSessionConnector sessionConnector;
private final String ignoreHardwareMediaButtonsKey;
+
+ private final String prevNextButtonModeKey;
+
+ private final String smartModeKey;
+
+ private final String fwRwModeKey;
+
private boolean shouldIgnoreHardwareMediaButtons = false;
// used to check whether any notification action changed, before sending costly updates
@@ -52,6 +59,9 @@ public MediaSessionPlayerUi(@NonNull final Player player) {
super(player);
ignoreHardwareMediaButtonsKey =
context.getString(R.string.ignore_hardware_media_buttons_key);
+ prevNextButtonModeKey = context.getString(R.string.prev_next_button_mode_key);
+ smartModeKey = context.getString(R.string.prev_next_mode_smart_key);
+ fwRwModeKey = context.getString(R.string.prev_next_mode_seek_key);
}
@Override
@@ -73,6 +83,7 @@ public void initPlayer() {
// listen to changes to ignore_hardware_media_buttons_key
updateShouldIgnoreHardwareMediaButtons(player.getPrefs());
+ updatePrevNextMode(player.getPrefs());
player.getPrefs().registerOnSharedPreferenceChangeListener(this);
sessionConnector.setMetadataDeduplicationEnabled(true);
@@ -114,8 +125,14 @@ public void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
- if (key == null || key.equals(ignoreHardwareMediaButtonsKey)) {
+ if (key == null) {
+ updateShouldIgnoreHardwareMediaButtons(sharedPreferences);
+ updatePrevNextMode(sharedPreferences);
+ } else if (key.equals(ignoreHardwareMediaButtonsKey)) {
updateShouldIgnoreHardwareMediaButtons(sharedPreferences);
+
+ } else if (key.equals(prevNextButtonModeKey)) {
+ updatePrevNextMode(sharedPreferences);
}
}
@@ -124,6 +141,18 @@ public void updateShouldIgnoreHardwareMediaButtons(final SharedPreferences share
sharedPreferences.getBoolean(ignoreHardwareMediaButtonsKey, false);
}
+ public void updatePrevNextMode(final SharedPreferences sharedPreferences) {
+
+ final var modeString = sharedPreferences.getString(prevNextButtonModeKey, "");
+ int prevNextButtonMode = Player.DEFAULT_PREV_NEXT_MODE;
+ if (modeString.equals(smartModeKey)) {
+ prevNextButtonMode = Player.SMART_PREV_NEXT_MODE;
+ } else if (modeString.equals(fwRwModeKey)) {
+ prevNextButtonMode = Player.SEEK_PREV_NEXT_MODE;
+ }
+ player.setQueueActionsPrevNext(prevNextButtonMode);
+ }
+
public void handleMediaButtonIntent(final Intent intent) {
MediaButtonReceiver.handleIntent(mediaSession, intent);
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java
index 3339869c129..7f15f440c79 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java
@@ -73,7 +73,7 @@ public long getActiveQueueItemId(
@Override
public void onSkipToPrevious(@NonNull final com.google.android.exoplayer2.Player exoPlayer) {
- player.playPrevious();
+ player.playerActionPrevious();
}
@Override
@@ -86,7 +86,7 @@ public void onSkipToQueueItem(@NonNull final com.google.android.exoplayer2.Playe
@Override
public void onSkipToNext(@NonNull final com.google.android.exoplayer2.Player exoPlayer) {
- player.playNext();
+ player.playerActionNext();
}
private void publishFloatingQueueWindow() {
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index fb68a464d5a..69290250738 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -24,6 +24,7 @@
screen_brightness_timestamp_key
clear_queue_confirmation_key
ignore_hardware_media_buttons_key
+ prev_next_button_mode_key
popup_saved_width
popup_saved_x
@@ -229,6 +230,22 @@
- @string/none_control_key
+ prev_next_button_mode
+ @string/prev_next_mode_default_key
+ default_mode
+ seek_mode
+ smart_mode
+
+ - @string/prev_next_mode_default
+ - @string/prev_next_mode_seek
+ - @string/prev_next_mode_smart
+
+
+ - @string/prev_next_mode_default_key
+ - @string/prev_next_mode_seek_key
+ - @string/prev_next_mode_smart_key
+
+
prefer_original_audio
prefer_descriptive_audio
last_resize_mode
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 56140441cd0..e7afc117351 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -857,4 +857,9 @@
Show more
Show less
The settings in the export being imported use a vulnerable format that was deprecated since NewPipe 0.27.0. Make sure the export being imported is from a trusted source, and prefer using only exports obtained from NewPipe 0.27.0 or newer in the future. Support for importing settings in this vulnerable format will soon be removed completely, and then old versions of NewPipe will not be able to import settings of exports from new versions anymore.
+ Previous/Next button behavior
+ Previous/Next headset and notification mode behavior
+ Next/Previous track
+ Smart Next/Prev or Fw/Rw
+ Forward/Rewind
diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml
index 727ce4df40a..652124f3648 100644
--- a/app/src/main/res/xml/video_audio_settings.xml
+++ b/app/src/main/res/xml/video_audio_settings.xml
@@ -144,6 +144,16 @@
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
+
+