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 framesets) { + + final int seekbarPreviewType = + SeekbarPreviewThumbnailHelper.getSeekbarPreviewThumbnailType(context); + + final UUID updateRequestIdentifier = UUID.randomUUID(); + this.currentUpdateRequestIdentifier = updateRequestIdentifier; + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.submit(() -> { + try { + resetFromAsync(seekbarPreviewType, framesets, updateRequestIdentifier); + } catch (final Exception ex) { + Log.e(TAG, "Failed to execute async", ex); + } + }); + // ensure that the executorService stops/destroys it's threads + // after the task is finished + executorService.shutdown(); + } + + private void resetFromAsync( + final int seekbarPreviewType, + final List framesets, + final UUID updateRequestIdentifier) { + + Log.d(TAG, "Clearing seekbarPreviewData"); + seekbarPreviewData.clear(); + + if (seekbarPreviewType == SeekbarPreviewThumbnailType.NONE) { + Log.d(TAG, "Not processing seekbarPreviewData due to settings"); + return; + } + + final Frameset frameset = getFrameSetForType(framesets, seekbarPreviewType); + if (frameset == null) { + Log.d(TAG, "No frameset was found to fill seekbarPreviewData"); + return; + } + + Log.d(TAG, "Frameset quality info: " + + "[width=" + frameset.getFrameWidth() + + ", heigh=" + frameset.getFrameHeight() + "]"); + + // Abort method execution if we are not the latest request + if (!isRequestIdentifierCurrent(updateRequestIdentifier)) { + return; + } + + generateDataFrom(frameset, updateRequestIdentifier); + } + + private Frameset getFrameSetForType( + final List framesets, + final int seekbarPreviewType) { + + if (seekbarPreviewType == SeekbarPreviewThumbnailType.HIGH_QUALITY) { + Log.d(TAG, "Strategy for seekbarPreviewData: high quality"); + return framesets.stream() + .max(Comparator.comparingInt(fs -> fs.getFrameHeight() * fs.getFrameWidth())) + .orElse(null); + } else { + Log.d(TAG, "Strategy for seekbarPreviewData: low quality"); + return framesets.stream() + .min(Comparator.comparingInt(fs -> fs.getFrameHeight() * fs.getFrameWidth())) + .orElse(null); + } + } + + private void generateDataFrom( + final Frameset frameset, + final UUID updateRequestIdentifier) { + + Log.d(TAG, "Starting generation of seekbarPreviewData"); + final Stopwatch sw = Log.isLoggable(TAG, Log.DEBUG) ? Stopwatch.createStarted() : null; + + int currentPosMs = 0; + int pos = 1; + + final int frameCountPerUrl = frameset.getFramesPerPageX() * frameset.getFramesPerPageY(); + + // Process each url in the frameset + for (final String url : frameset.getUrls()) { + // get the bitmap + final Bitmap srcBitMap = getBitMapFrom(url); + + // The data is not added directly to "seekbarPreviewData" due to + // concurrency and checks for "updateRequestIdentifier" + final Map> generatedDataForUrl = new HashMap<>(); + + // The bitmap consists of several images, which we process here + // foreach frame in the returned bitmap + for (int i = 0; i < frameCountPerUrl; i++) { + // Frames outside the video length are skipped + if (pos > frameset.getTotalCount()) { + break; + } + + // Get the bounds where the frame is found + final int[] bounds = frameset.getFrameBoundsAt(currentPosMs); + generatedDataForUrl.put(currentPosMs, () -> { + // It can happen, that the original bitmap could not be downloaded + // In such a case - we don't want a NullPointer - simply return null + if (srcBitMap == null) { + return null; + } + + // Cut out the corresponding bitmap form the "srcBitMap" + return Bitmap.createBitmap(srcBitMap, bounds[1], bounds[2], + frameset.getFrameWidth(), frameset.getFrameHeight()); + }); + + currentPosMs += frameset.getDurationPerFrame(); + pos++; + } + + // Check if we are still the latest request + // If not abort method execution + if (isRequestIdentifierCurrent(updateRequestIdentifier)) { + seekbarPreviewData.putAll(generatedDataForUrl); + } else { + Log.d(TAG, "Aborted of generation of seekbarPreviewData"); + break; + } + } + + if (sw != null) { + Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop().toString()); + } + } + + private Bitmap getBitMapFrom(final String url) { + if (url == null) { + Log.w(TAG, "url is null; This should never happen"); + return null; + } + + final Stopwatch sw = Log.isLoggable(TAG, Log.DEBUG) ? Stopwatch.createStarted() : null; + try { + final SyncImageLoadingListener syncImageLoadingListener = + new SyncImageLoadingListener(); + + Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'"); + + // Ensure that everything is running + ImageLoader.getInstance().resume(); + // Load the image + // Impl-Note: + // Ensure that your are not running on the main-Thread this will otherwise hang + ImageLoader.getInstance().loadImage( + url, + ImageDisplayConstants.DISPLAY_SEEKBAR_PREVIEW_OPTIONS, + syncImageLoadingListener); + + // Get the bitmap within the timeout + final Bitmap bitmap = + syncImageLoadingListener.waitForBitmapOrThrow(30, TimeUnit.SECONDS); + + if (sw != null) { + Log.d(TAG, + "Download of bitmap for seekbarPreview from '" + url + + "' took " + sw.stop().toString()); + } + + return bitmap; + } catch (final Exception ex) { + Log.w(TAG, + "Failed to get bitmap for seekbarPreview from url='" + url + + "' in time", + ex); + return null; + } + } + + private boolean isRequestIdentifierCurrent(final UUID requestIdentifier) { + return this.currentUpdateRequestIdentifier.equals(requestIdentifier); + } + + + public Optional getBitmapAt(final int positionInMs) { + // Check if the BitmapData is empty + if (seekbarPreviewData.isEmpty()) { + return Optional.empty(); + } + + // Get the closest frame to the requested position + final int closestIndexPosition = + seekbarPreviewData.keySet().stream() + .min(Comparator.comparingInt(i -> Math.abs(i - positionInMs))) + .orElse(-1); + + // this should never happen, because + // it indicates that "seekbarPreviewData" is empty which was already checked + if (closestIndexPosition == -1) { + return Optional.empty(); + } + + try { + // Get the bitmap for the position (executes the supplier) + return Optional.ofNullable(seekbarPreviewData.get(closestIndexPosition).get()); + } catch (final Exception ex) { + // If there is an error, log it and return Optional.empty + Log.w(TAG, "Unable to get seekbar preview", ex); + return Optional.empty(); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java new file mode 100644 index 000000000..46c278bf2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java @@ -0,0 +1,87 @@ +package org.schabi.newpipe.player.seekbarpreview; + +import android.graphics.Bitmap; +import android.view.View; + +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Listener for synchronously downloading of an image/bitmap. + */ +public class SyncImageLoadingListener extends SimpleImageLoadingListener { + + private final CountDownLatch countDownLatch = new CountDownLatch(1); + + private Bitmap bitmap; + private boolean cancelled = false; + private FailReason failReason = null; + + @SuppressWarnings("checkstyle:HiddenField") + @Override + public void onLoadingFailed( + final String imageUri, + final View view, + final FailReason failReason) { + + this.failReason = failReason; + countDownLatch.countDown(); + } + + @Override + public void onLoadingComplete( + final String imageUri, + final View view, + final Bitmap loadedImage) { + + bitmap = loadedImage; + countDownLatch.countDown(); + } + + @Override + public void onLoadingCancelled(final String imageUri, final View view) { + cancelled = true; + countDownLatch.countDown(); + } + + public Bitmap waitForBitmapOrThrow(final long timeout, final TimeUnit timeUnit) + throws InterruptedException, TimeoutException { + + // Wait for the download to finish + if (!countDownLatch.await(timeout, timeUnit)) { + throw new TimeoutException("Couldn't get the image in time"); + } + + if (isCancelled()) { + throw new CancellationException("Download of image was cancelled"); + } + + if (getFailReason() != null) { + throw new RuntimeException("Failed to download image" + getFailReason().getType(), + getFailReason().getCause()); + } + + if (getBitmap() == null) { + throw new NullPointerException("Bitmap is null"); + } + + return getBitmap(); + } + + public Bitmap getBitmap() { + return bitmap; + } + + public boolean isCancelled() { + return cancelled; + } + + public FailReason getFailReason() { + return failReason; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java index 37ebd636a..62e80275e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java +++ b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java @@ -56,5 +56,10 @@ public final class ImageDisplayConstants { .showImageOnFail(R.drawable.dummy_thumbnail_playlist) .build(); + public static final DisplayImageOptions DISPLAY_SEEKBAR_PREVIEW_OPTIONS = + new DisplayImageOptions.Builder() + .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) + .build(); + private ImageDisplayConstants() { } } From c5f2eb1dd86412abfd7172e2b5307e3138a1f72a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 15 Jun 2021 19:51:42 +0200 Subject: [PATCH 05/11] Enlarged currentDisplaySeek a bit --- app/src/main/res/layout/player.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index 9e859fcd5..1c8273081 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -385,7 +385,7 @@ android:paddingRight="5dp" android:paddingBottom="2dp" android:textColor="@android:color/white" - android:textSize="14sp" + android:textSize="18sp" android:textStyle="bold" android:visibility="gone" tools:ignore="RtlHardcoded" From 88c41952603ff0942a2ef1d499f66c513d979e38 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 15 Jun 2021 20:00:23 +0200 Subject: [PATCH 06/11] Enlarged currentDisplaySeek-text on large-handed player --- app/src/main/res/layout-large-land/player.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml index ba7b4a033..4cdc48556 100644 --- a/app/src/main/res/layout-large-land/player.xml +++ b/app/src/main/res/layout-large-land/player.xml @@ -386,7 +386,7 @@ android:paddingRight="5dp" android:paddingBottom="2dp" android:textColor="@android:color/white" - android:textSize="14sp" + android:textSize="18sp" android:textStyle="bold" android:visibility="gone" tools:ignore="RtlHardcoded" From 2a24532e1d215d2f1468322b10c359451bf49ab0 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 19 Jun 2021 13:58:30 +0200 Subject: [PATCH 07/11] Fine tuned padding Moved seekbar preview up a bit, so the finger is not obstructing the view --- app/src/main/res/layout-large-land/player.xml | 5 +++-- app/src/main/res/layout/player.xml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml index 4cdc48556..89ab960d1 100644 --- a/app/src/main/res/layout-large-land/player.xml +++ b/app/src/main/res/layout-large-land/player.xml @@ -366,13 +366,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:paddingBottom="8dp"> diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index 1c8273081..436bdb704 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -365,13 +365,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:paddingBottom="8dp"> From a9b5ef3bd30d19622ba753d6407503e777adb93a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 19 Jun 2021 14:26:40 +0200 Subject: [PATCH 08/11] Set minWidth to 10dp so that the popup player works (mostly) correctly --- .../player/seekbarpreview/SeekbarPreviewThumbnailHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index f8e4e9ed7..54d11da83 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -89,8 +89,8 @@ public final class SeekbarPreviewThumbnailHelper { // Scaling more than that factor looks really pixelated -> max Math.round(srcWidth * 2.5f) ), - // Min width = 80dp - DeviceUtils.dpToPx(80, context) + // Min width = 10dp + DeviceUtils.dpToPx(10, context) ); final float scaleFactor = (float) newWidth / srcWidth; From 0b2629e910391528ca45ebe5889f593a6e89969e Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 6 Jul 2021 21:14:24 +0200 Subject: [PATCH 09/11] Moved time to the top --- app/src/main/res/layout-large-land/player.xml | 18 +++++++++--------- app/src/main/res/layout/player.xml | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml index 89ab960d1..830470b57 100644 --- a/app/src/main/res/layout-large-land/player.xml +++ b/app/src/main/res/layout-large-land/player.xml @@ -369,15 +369,6 @@ android:orientation="vertical" android:paddingBottom="8dp"> - - + + diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index 436bdb704..4d332d674 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -368,15 +368,6 @@ android:orientation="vertical" android:paddingBottom="8dp"> - - + + From efd038a5363e13a901f4725821e2f22a62044af9 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 6 Jul 2021 22:25:00 +0200 Subject: [PATCH 10/11] Increased padding of preview thumbnail --- app/src/main/res/layout-large-land/player.xml | 2 +- app/src/main/res/layout/player.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml index 830470b57..f55632174 100644 --- a/app/src/main/res/layout-large-land/player.xml +++ b/app/src/main/res/layout-large-land/player.xml @@ -367,7 +367,7 @@ android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" - android:paddingBottom="8dp"> + android:paddingBottom="12dp"> + android:paddingBottom="12dp"> Date: Sat, 17 Jul 2021 16:52:24 +0200 Subject: [PATCH 11/11] Removed unused import (rebasing/merge problem) --- app/src/main/java/org/schabi/newpipe/player/Player.java | 1 - 1 file changed, 1 deletion(-) 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 d24aaa699..a5449b162 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -27,7 +27,6 @@ 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;