diff --git a/src/main/assets/changelog-alpha.txt b/src/main/assets/changelog-alpha.txt
index 27c02f985..7035cbd91 100644
--- a/src/main/assets/changelog-alpha.txt
+++ b/src/main/assets/changelog-alpha.txt
@@ -1,3 +1,6 @@
+/Alpha 360 (2025-04-21)
+Added video frame step controls (thanks to ecawthorne and japanesephundroid)
+
/Alpha 359 (2025-04-20)
Added support for emotes in comment flairs (thanks to bharatknv)
Added "Mark as Read/Unread" fling action, and optional context menu item (thanks to JoshAusHessen and codeofdusk)
diff --git a/src/main/assets/changelog.txt b/src/main/assets/changelog.txt
index 64ecaf899..a78eb4513 100644
--- a/src/main/assets/changelog.txt
+++ b/src/main/assets/changelog.txt
@@ -1,5 +1,6 @@
114/1.25
Added video playback speed control (thanks to folkemat)
+Added video frame step controls (thanks to ecawthorne and japanesephundroid)
Added support for emotes in comment flairs (thanks to bharatknv)
Show label on crossposts, and add "Go to Crosspost Origin" to post menu (thanks to folkemat)
Added "Mark as Read/Unread" fling action, and optional post menu item (thanks to JoshAusHessen and codeofdusk)
diff --git a/src/main/java/org/quantumbadger/redreader/common/PrefsUtility.java b/src/main/java/org/quantumbadger/redreader/common/PrefsUtility.java
index 7fed56a1b..c47ce987d 100644
--- a/src/main/java/org/quantumbadger/redreader/common/PrefsUtility.java
+++ b/src/main/java/org/quantumbadger/redreader/common/PrefsUtility.java
@@ -802,6 +802,11 @@ public static boolean pref_behaviour_video_playback_controls() {
true);
}
+ public static boolean pref_behaviour_video_frame_step() {
+ return getBoolean(R.string.pref_behaviour_video_frame_step_key,
+ false);
+ }
+
public static boolean pref_behaviour_video_mute_default() {
return getBoolean(
R.string.pref_behaviour_video_mute_default_key,
diff --git a/src/main/java/org/quantumbadger/redreader/views/video/ExoPlayerWrapperView.java b/src/main/java/org/quantumbadger/redreader/views/video/ExoPlayerWrapperView.java
index 0bc6d8c31..d745bd21f 100644
--- a/src/main/java/org/quantumbadger/redreader/views/video/ExoPlayerWrapperView.java
+++ b/src/main/java/org/quantumbadger/redreader/views/video/ExoPlayerWrapperView.java
@@ -146,7 +146,7 @@ public ExoPlayerWrapperView(
addButton(createButton(
context,
mControlView,
- R.drawable.icon_previous,
+ R.drawable.icon_restart,
R.string.video_restart,
view -> {
mVideoPlayer.seekTo(0);
@@ -178,7 +178,68 @@ public ExoPlayerWrapperView(
updateProgress();
});
- addButton(mPlayButton, buttons);
+ if (PrefsUtility.pref_behaviour_video_frame_step()) {
+ final long frameDuration = (long)(1000f / (mVideoPlayer.getVideoFormat() != null
+ ? mVideoPlayer.getVideoFormat().frameRate
+ : 30));
+
+ final ImageButton stepBackButton = createButton(
+ context,
+ mControlView,
+ R.drawable.icon_step_back,
+ R.string.video_step_back,
+ view -> {
+ mVideoPlayer.seekTo(mVideoPlayer.getCurrentPosition() - frameDuration);
+ updateProgress();
+ }
+ );
+
+ final ImageButton stepForwardButton = createButton(
+ context,
+ mControlView,
+ R.drawable.icon_step_forward,
+ R.string.video_step_forward,
+ view -> {
+ mVideoPlayer.seekTo(mVideoPlayer.getCurrentPosition() + frameDuration);
+ updateProgress();
+ }
+ );
+
+ mVideoPlayer.addListener(new Player.Listener() {
+ @Override
+ public void onIsPlayingChanged(final boolean isPlaying) {
+ if (isPlaying) {
+ stepBackButton.setImageAlpha(0x3F);
+ stepBackButton.setContentDescription(
+ context.getString(R.string.video_step_back_disabled));
+ stepBackButton.setEnabled(false);
+
+ stepForwardButton.setImageAlpha(0x3F);
+ stepForwardButton.setContentDescription(
+ context.getString(R.string.video_step_forward_disabled));
+ stepForwardButton.setEnabled(false);
+
+ } else {
+ stepBackButton.setImageAlpha(0xFF);
+ stepBackButton.setContentDescription(
+ context.getString(R.string.video_step_back));
+ stepBackButton.setEnabled(true);
+
+ stepForwardButton.setImageAlpha(0xFF);
+ stepForwardButton.setContentDescription(
+ context.getString(R.string.video_step_forward));
+ stepForwardButton.setEnabled(true);
+ }
+ }
+ });
+
+ addButton(stepBackButton, buttons);
+ addButton(mPlayButton, buttons);
+ addButton(stepForwardButton, buttons);
+
+ } else {
+ addButton(mPlayButton, buttons);
+ }
addButton(createButton(
context,
diff --git a/src/main/res/drawable/icon_restart.xml b/src/main/res/drawable/icon_restart.xml
new file mode 100644
index 000000000..7ebe4bc66
--- /dev/null
+++ b/src/main/res/drawable/icon_restart.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/src/main/res/drawable/icon_previous.xml b/src/main/res/drawable/icon_step_back.xml
similarity index 72%
rename from src/main/res/drawable/icon_previous.xml
rename to src/main/res/drawable/icon_step_back.xml
index 9564a2a35..1397eb87d 100644
--- a/src/main/res/drawable/icon_previous.xml
+++ b/src/main/res/drawable/icon_step_back.xml
@@ -1,4 +1,4 @@
-
+
+ android:height="32dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
-
+
+
+
diff --git a/src/main/res/drawable/icon_step_forward.xml b/src/main/res/drawable/icon_step_forward.xml
new file mode 100644
index 000000000..2ced92191
--- /dev/null
+++ b/src/main/res/drawable/icon_step_forward.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 120ec4efd..eba05baca 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -1482,9 +1482,13 @@
Unmute
Restart video
Rewind
+ Step back
+ Step back disabled
Play
Pause
Fast forward
+ Step forward
+ Step forward disabled
Zoom in
Zoom out
@@ -1910,4 +1914,8 @@
Crosspost tag
Crosspost.
Go to Crosspost Origin
+
+ Enable stepping frame by frame
+ pref_behaviour_video_frame_step
+
diff --git a/src/main/res/xml/prefs_images_video.xml b/src/main/res/xml/prefs_images_video.xml
index 7126cba0d..248931758 100644
--- a/src/main/res/xml/prefs_images_video.xml
+++ b/src/main/res/xml/prefs_images_video.xml
@@ -43,6 +43,10 @@
android:key="@string/pref_behaviour_video_playback_controls_key"
android:defaultValue="true"/>
+
+