Skip to content

Commit

Permalink
Merge pull request #6434 from litetex/playerSeekbarPreview
Browse files Browse the repository at this point in the history
Player seekbar thumbnail preview
  • Loading branch information
Stypox authored Jul 19, 2021
2 parents 3167ab3 + 621af8d commit d57bfde
Show file tree
Hide file tree
Showing 12 changed files with 663 additions and 58 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
73 changes: 70 additions & 3 deletions app/src/main/java/org/schabi/newpipe/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
Expand Down Expand Up @@ -123,6 +124,8 @@
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;
Expand Down Expand Up @@ -379,6 +382,8 @@ public final class Player implements
@NonNull private final SharedPreferences prefs;
@NonNull private final HistoryRecordManager recordManager;

@NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
new SeekbarPreviewThumbnailHolder();


/*//////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1676,12 +1681,67 @@ private Disposable getProgressUpdateDisposable() {
@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);
}
}

Expand All @@ -1702,6 +1762,8 @@ public void onStartTrackingTouch(final SeekBar seekBar) {
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
Expand All @@ -1717,6 +1779,7 @@ public void onStopTrackingTouch(final SeekBar seekBar) {

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);
Expand Down Expand Up @@ -2866,6 +2929,10 @@ private void onMetadataChanged(@NonNull final MediaSourceTag tag) {
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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Bitmap> 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 = 10dp
DeviceUtils.dpToPx(10, 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();
}
}
}
Loading

0 comments on commit d57bfde

Please sign in to comment.