From 43133df2ad4021ab583431c693f9206daa2c073b Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Tue, 8 Jun 2021 21:28:49 +0200
Subject: [PATCH 01/11] Added settings for seekbar-preview-thumbnail
---
app/src/main/res/values-de/strings.xml | 4 ++++
app/src/main/res/values/settings_keys.xml | 15 +++++++++++++++
app/src/main/res/values/strings.xml | 5 +++++
app/src/main/res/xml/video_audio_settings.xml | 10 ++++++++++
4 files changed, 34 insertions(+)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index a1b9e4a6f..18c21203f 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -422,6 +422,10 @@
Raster
Automatisch
Ansicht wechseln
+ Vorschaubild der Suchleiste
+ Hohe Qualität (größer)
+ Niedrige Qualität (kleiner)
+ Nicht anzeigen
Eine NewPipe-Aktualisierung ist verfügbar!
Zum Herunterladen antippen
Fertig
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 399e4c832..c57b24f16 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -89,6 +89,21 @@
- @string/never
+ seekbar_preview_thumbnail_key
+ seekbar_preview_thumbnail_high_quality
+ seekbar_preview_thumbnail_low_quality
+ seekbar_preview_thumbnail_none
+
+ - @string/seekbar_preview_thumbnail_high_quality
+ - @string/seekbar_preview_thumbnail_low_quality
+ - @string/seekbar_preview_thumbnail_none
+
+
+ - @string/high_quality_larger
+ - @string/low_quality_smaller
+ - @string/dont_show
+
+
default_resolution
720p60
show_higher_resolutions
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f75380101..f6d0246dd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -587,6 +587,11 @@
Grid
Auto
Switch View
+
+ Seekbar thumbnail preview
+ High quality (larger)
+ Low quality (smaller)
+ Don\'t show
NewPipe update is available!
Tap to download
diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml
index 35f3359da..76b5439cc 100644
--- a/app/src/main/res/xml/video_audio_settings.xml
+++ b/app/src/main/res/xml/video_audio_settings.xml
@@ -79,6 +79,16 @@
android:summary="@string/show_play_with_kodi_summary"
android:title="@string/show_play_with_kodi_title"
app:iconSpaceReserved="false" />
+
From 2e2dbaf77f6f5a207034de0fc4ca768985f82967 Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Tue, 8 Jun 2021 21:33:44 +0200
Subject: [PATCH 02/11] Added seekbar-preview to the player layout
---
app/src/main/res/layout-large-land/player.xml | 79 ++++++++++++-------
app/src/main/res/layout/player.xml | 79 ++++++++++++-------
2 files changed, 104 insertions(+), 54 deletions(-)
diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml
index e3377f831..ba7b4a033 100644
--- a/app/src/main/res/layout-large-land/player.xml
+++ b/app/src/main/res/layout-large-land/player.xml
@@ -103,8 +103,8 @@
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
android:visibility="gone"
- app:tint="@color/white"
app:srcCompat="@drawable/ic_close"
+ app:tint="@color/white"
tools:ignore="ContentDescription,RtlHardcoded" />
@@ -208,8 +208,8 @@
android:paddingBottom="3dp"
android:scaleType="fitCenter"
android:visibility="gone"
- app:tint="@color/white"
app:srcCompat="@drawable/ic_format_list_numbered"
+ app:tint="@color/white"
tools:ignore="ContentDescription,RtlHardcoded"
tools:visibility="visible" />
@@ -222,8 +222,8 @@
android:focusable="true"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
- app:tint="@color/white"
app:srcCompat="@drawable/ic_expand_more"
+ app:tint="@color/white"
tools:ignore="ContentDescription,RtlHardcoded" />
@@ -287,8 +287,8 @@
android:focusable="true"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
- app:tint="@color/white"
app:srcCompat="@drawable/ic_cast"
+ app:tint="@color/white"
tools:ignore="RtlHardcoded" />
@@ -354,6 +354,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml
index 3614d1fbb..9e859fcd5 100644
--- a/app/src/main/res/layout/player.xml
+++ b/app/src/main/res/layout/player.xml
@@ -101,8 +101,8 @@
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
android:visibility="gone"
- app:tint="@color/white"
app:srcCompat="@drawable/ic_close"
+ app:tint="@color/white"
tools:ignore="ContentDescription,RtlHardcoded" />
@@ -286,8 +286,8 @@
android:focusable="true"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
- app:tint="@color/white"
app:srcCompat="@drawable/ic_cast"
+ app:tint="@color/white"
tools:ignore="RtlHardcoded" />
@@ -353,6 +353,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
From 253526e565a52f36191d0bb6c84a67586ab28ea4 Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Tue, 8 Jun 2021 21:35:16 +0200
Subject: [PATCH 03/11] Updated build.gradle so the PR-build works
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 4b6890eac..07c291638 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -186,7 +186,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:c38a06e8dcd9c206a52b622704b138b78d633274'
+ implementation 'com.github.litetex:NewPipeExtractor:playerSeekbarPreview-SNAPSHOT'
/** Checkstyle **/
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
From 384d964827caf443ddddf36e64122ad8bc497494 Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Wed, 9 Jun 2021 20:35:29 +0200
Subject: [PATCH 04/11] Added seekbarThumbnailPreview
---
.../org/schabi/newpipe/player/Player.java | 74 ++++-
.../SeekbarPreviewThumbnailHelper.java | 108 ++++++++
.../SeekbarPreviewThumbnailHolder.java | 252 ++++++++++++++++++
.../SyncImageLoadingListener.java | 87 ++++++
.../newpipe/util/ImageDisplayConstants.java | 5 +
5 files changed, 523 insertions(+), 3 deletions(-)
create mode 100644 app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java
create mode 100644 app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java
create mode 100644 app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java
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 544d51ca6..d24aaa699 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -27,6 +27,8 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
+import android.view.GestureDetector;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -123,6 +125,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
+import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper;
+import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.ListHelper;
@@ -379,6 +383,8 @@ public final class Player implements
@NonNull private final SharedPreferences prefs;
@NonNull private final HistoryRecordManager recordManager;
+ @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
+ new SeekbarPreviewThumbnailHolder();
/*//////////////////////////////////////////////////////////////////////////
@@ -1676,12 +1682,67 @@ public final class Player implements
@Override // seekbar listener
public void onProgressChanged(final SeekBar seekBar, final int progress,
final boolean fromUser) {
- if (DEBUG && fromUser) {
+ // Currently we don't need method execution when fromUser is false
+ if (!fromUser) {
+ return;
+ }
+ if (DEBUG) {
Log.d(TAG, "onProgressChanged() called with: "
+ "seekBar = [" + seekBar + "], progress = [" + progress + "]");
}
- if (fromUser) {
- binding.currentDisplaySeek.setText(getTimeString(progress));
+
+ binding.currentDisplaySeek.setText(getTimeString(progress));
+
+ // Seekbar Preview Thumbnail
+ SeekbarPreviewThumbnailHelper
+ .tryResizeAndSetSeekbarPreviewThumbnail(
+ getContext(),
+ seekbarPreviewThumbnailHolder.getBitmapAt(progress),
+ binding.currentSeekbarPreviewThumbnail,
+ binding.subtitleView::getWidth);
+
+ adjustSeekbarPreviewContainer();
+ }
+
+ private void adjustSeekbarPreviewContainer() {
+ try {
+ // Should only be required when an error occurred before
+ // and the layout was positioned in the center
+ binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY);
+
+ // Calculate the current left position of seekbar progress in px
+ // More info: https://stackoverflow.com/q/20493577
+ final int currentSeekbarLeft =
+ binding.playbackSeekBar.getLeft()
+ + binding.playbackSeekBar.getPaddingLeft()
+ + binding.playbackSeekBar.getThumb().getBounds().left;
+
+ // Calculate the (unchecked) left position of the container
+ final int uncheckedContainerLeft =
+ currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2);
+
+ // Fix the position so it's within the boundaries
+ final int checkedContainerLeft =
+ Math.max(
+ Math.min(
+ uncheckedContainerLeft,
+ // Max left
+ binding.playbackWindowRoot.getWidth()
+ - binding.seekbarPreviewContainer.getWidth()
+ ),
+ 0 // Min left
+ );
+
+ // See also: https://stackoverflow.com/a/23249734
+ final LinearLayout.LayoutParams params =
+ new LinearLayout.LayoutParams(
+ binding.seekbarPreviewContainer.getLayoutParams());
+ params.setMarginStart(checkedContainerLeft);
+ binding.seekbarPreviewContainer.setLayoutParams(params);
+ } catch (final Exception ex) {
+ Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex);
+ // Fallback - position in the middle
+ binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER);
}
}
@@ -1702,6 +1763,8 @@ public final class Player implements
showControls(0);
animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION,
AnimationType.SCALE_AND_ALPHA);
+ animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION,
+ AnimationType.SCALE_AND_ALPHA);
}
@Override // seekbar listener
@@ -1717,6 +1780,7 @@ public final class Player implements
binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA);
+ animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA);
if (currentState == STATE_PAUSED_SEEK) {
changeState(STATE_BUFFERING);
@@ -2866,6 +2930,10 @@ public final class Player implements
binding.titleTextView.setText(tag.getMetadata().getName());
binding.channelTextView.setText(tag.getMetadata().getUploaderName());
+ this.seekbarPreviewThumbnailHolder.resetFrom(
+ this.getContext(),
+ tag.getMetadata().getPreviewFrames());
+
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
notifyMetadataUpdateToListeners();
diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java
new file mode 100644
index 000000000..f8e4e9ed7
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java
@@ -0,0 +1,108 @@
+package org.schabi.newpipe.player.seekbarpreview;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.DeviceUtils;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.IntSupplier;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType.HIGH_QUALITY;
+import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType.LOW_QUALITY;
+import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType.NONE;
+
+/**
+ * Helper for the seekbar preview.
+ */
+public final class SeekbarPreviewThumbnailHelper {
+
+ // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
+ // or it fails with an IllegalArgumentException
+ // https://stackoverflow.com/a/54744028
+ public static final String TAG = "SeekbarPrevThumbHelper";
+
+ private SeekbarPreviewThumbnailHelper() {
+ // No impl pls
+ }
+
+ @Retention(SOURCE)
+ @IntDef({HIGH_QUALITY, LOW_QUALITY,
+ NONE})
+ public @interface SeekbarPreviewThumbnailType {
+ int HIGH_QUALITY = 0;
+ int LOW_QUALITY = 1;
+ int NONE = 2;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Settings Resolution
+ ///////////////////////////////////////////////////////////////////////////
+
+ @SeekbarPreviewThumbnailType
+ public static int getSeekbarPreviewThumbnailType(@NonNull final Context context) {
+ final String type = PreferenceManager.getDefaultSharedPreferences(context).getString(
+ context.getString(R.string.seekbar_preview_thumbnail_key), "");
+ if (type.equals(context.getString(R.string.seekbar_preview_thumbnail_none))) {
+ return NONE;
+ } else if (type.equals(context.getString(R.string.seekbar_preview_thumbnail_low_quality))) {
+ return LOW_QUALITY;
+ } else {
+ return HIGH_QUALITY; // default
+ }
+ }
+
+ public static void tryResizeAndSetSeekbarPreviewThumbnail(
+ @NonNull final Context context,
+ @NonNull final Optional optPreviewThumbnail,
+ @NonNull final ImageView currentSeekbarPreviewThumbnail,
+ @NonNull final IntSupplier baseViewWidthSupplier) {
+
+ if (!optPreviewThumbnail.isPresent()) {
+ currentSeekbarPreviewThumbnail.setVisibility(View.GONE);
+ return;
+ }
+
+ currentSeekbarPreviewThumbnail.setVisibility(View.VISIBLE);
+ final Bitmap srcBitmap = optPreviewThumbnail.get();
+
+ // Resize original bitmap
+ try {
+ Objects.requireNonNull(srcBitmap);
+
+ final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1;
+ final int newWidth = Math.max(
+ Math.min(
+ // Use 1/4 of the width for the preview
+ Math.round(baseViewWidthSupplier.getAsInt() / 4f),
+ // Scaling more than that factor looks really pixelated -> max
+ Math.round(srcWidth * 2.5f)
+ ),
+ // Min width = 80dp
+ DeviceUtils.dpToPx(80, context)
+ );
+
+ final float scaleFactor = (float) newWidth / srcWidth;
+ final int newHeight = (int) (srcBitmap.getHeight() * scaleFactor);
+
+ currentSeekbarPreviewThumbnail.setImageBitmap(
+ Bitmap.createScaledBitmap(srcBitmap, newWidth, newHeight, true));
+ } catch (final Exception ex) {
+ Log.e(TAG, "Failed to resize and set seekbar preview thumbnail", ex);
+ currentSeekbarPreviewThumbnail.setVisibility(View.GONE);
+ } finally {
+ srcBitmap.recycle();
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java
new file mode 100644
index 000000000..30c5ce910
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java
@@ -0,0 +1,252 @@
+package org.schabi.newpipe.player.seekbarpreview;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.base.Stopwatch;
+import com.nostra13.universalimageloader.core.ImageLoader;
+
+import org.schabi.newpipe.extractor.stream.Frameset;
+import org.schabi.newpipe.util.ImageDisplayConstants;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType;
+
+public class SeekbarPreviewThumbnailHolder {
+
+ // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
+ // or it fails with an IllegalArgumentException
+ // https://stackoverflow.com/a/54744028
+ public static final String TAG = "SeekbarPrevThumbHolder";
+
+ // Key = Position of the picture in milliseconds
+ // Supplier = Supplies the bitmap for that position
+ private final Map> seekbarPreviewData = new ConcurrentHashMap<>();
+
+ // This ensures that if the reset is still undergoing
+ // and another reset starts, only the last reset is processed
+ private UUID currentUpdateRequestIdentifier = UUID.randomUUID();
+
+ public synchronized void resetFrom(
+ @NonNull final Context context,
+ final List