diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt
index a1a96b20d..5adb4e3ef 100644
--- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt
+++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt
@@ -54,7 +54,7 @@ fun View.animate(
)
Log.d(TAG, "animate(): $msg")
}
- if (isVisible && enterOrExit) {
+ if (isVisible && enterOrExit && alpha == 1f && animationType == AnimationType.ALPHA) {
if (MainActivity.DEBUG) {
Log.d(TAG, "animate(): view was already visible > view = [$this]")
}
@@ -75,8 +75,15 @@ fun View.animate(
}
animate().setListener(null).cancel()
isVisible = true
+
+ val alphaRelativeDuration = if (enterOrExit && alpha < 1.0f) {
+ (duration * (1 - alpha)).toLong()
+ } else {
+ (duration * alpha).toLong()
+ }
+
when (animationType) {
- AnimationType.ALPHA -> animateAlpha(enterOrExit, duration, delay, execOnEnd)
+ AnimationType.ALPHA -> animateAlpha(enterOrExit, alphaRelativeDuration, delay, execOnEnd)
AnimationType.SCALE_AND_ALPHA -> animateScaleAndAlpha(enterOrExit, duration, delay, execOnEnd)
AnimationType.LIGHT_SCALE_AND_ALPHA -> animateLightScaleAndAlpha(enterOrExit, duration, delay, execOnEnd)
AnimationType.SLIDE_AND_ALPHA -> animateSlideAndAlpha(enterOrExit, duration, delay, execOnEnd)
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 81ef25db1..de8bffaf7 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -136,6 +136,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
+import org.jetbrains.annotations.NotNull;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
@@ -154,6 +155,7 @@ import org.schabi.newpipe.info_list.StreamSegmentAdapter;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
+import org.schabi.newpipe.player.event.DisplayPortion;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.event.PlayerGestureListener;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
@@ -188,6 +190,8 @@ import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.ExpandableSurfaceView;
+import org.schabi.newpipe.views.player.CircleClipTapView;
+import org.schabi.newpipe.views.player.PlayerSeekOverlay;
import java.io.IOException;
import java.util.ArrayList;
@@ -365,6 +369,7 @@ public final class Player implements
private int maxGestureLength; // scaled
private GestureDetectorCompat gestureDetector;
+ private PlayerGestureListener playerGestureListener;
/*//////////////////////////////////////////////////////////////////////////
// Listeners and disposables
@@ -525,9 +530,10 @@ public final class Player implements
binding.resizeTextView.setOnClickListener(this);
binding.playbackLiveSync.setOnClickListener(this);
- final PlayerGestureListener listener = new PlayerGestureListener(this, service);
- gestureDetector = new GestureDetectorCompat(context, listener);
- binding.getRoot().setOnTouchListener(listener);
+ playerGestureListener = new PlayerGestureListener(this, service);
+ gestureDetector = new GestureDetectorCompat(context, playerGestureListener);
+ binding.getRoot().setOnTouchListener(playerGestureListener);
+ setupPlayerSeekOverlay();
binding.queueButton.setOnClickListener(this);
binding.segmentsButton.setOnClickListener(this);
@@ -578,6 +584,83 @@ public final class Player implements
v.getPaddingRight(),
v.getPaddingBottom()));
}
+
+ private void setupPlayerSeekOverlay() {
+ final int fadeDurations = 450;
+ binding.seekOverlay.showCircle(true)
+ .circleBackgroundColorInt(CircleClipTapView.COLOR_DARK_TRANSPARENT)
+ .seekSeconds(retrieveSeekDurationFromPreferences(this) / 1000)
+ .performListener(new PlayerSeekOverlay.PerformListener() {
+
+ @Override
+ public void onPrepare() {
+ if (invalidSeekConditions()) {
+ playerGestureListener.endMultiDoubleTap();
+ return;
+ }
+ binding.seekOverlay.arcSize(
+ CircleClipTapView.Companion.calculateArcSize(getSurfaceView())
+ );
+ }
+
+ @Override
+ public void onAnimationStart() {
+ animate(binding.seekOverlay, true, fadeDurations);
+ animate(binding.playbackControlsShadow,
+ !simpleExoPlayer.getPlayWhenReady(), fadeDurations);
+ animate(binding.playerTopShadow, false, fadeDurations);
+ animate(binding.playerBottomShadow, false, fadeDurations);
+ animate(binding.playbackControlRoot, false, fadeDurations);
+ hideSystemUIIfNeeded();
+ }
+
+ @Override
+ public void onAnimationEnd() {
+ animate(binding.seekOverlay, false, fadeDurations);
+ if (!simpleExoPlayer.getPlayWhenReady()) {
+ showControls(fadeDurations);
+ } else {
+ showHideShadow(false, fadeDurations);
+ }
+ }
+
+ @Override
+ public Boolean shouldFastForward(@NotNull final DisplayPortion portion) {
+ // Null indicates an invalid area or condition e.g. the middle portion
+ // or video start or end was reached during double tap seeking
+ if (invalidSeekConditions()) {
+ return null;
+ }
+ if (portion == DisplayPortion.LEFT
+ // Small puffer to eliminate infinite rewind seeking
+ && simpleExoPlayer.getCurrentPosition() > 500L) {
+ return false;
+ } else if (portion == DisplayPortion.RIGHT) {
+ return true;
+ } else /* portion == DisplayPortion.MIDDLE */ {
+ return null;
+ }
+ }
+
+ @Override
+ public void seek(final boolean forward) {
+ playerGestureListener.keepInDoubleTapMode();
+ if (forward) {
+ fastForward();
+ } else {
+ fastRewind();
+ }
+ }
+
+ private boolean invalidSeekConditions() {
+ return simpleExoPlayer.getCurrentPosition() == simpleExoPlayer.getDuration()
+ || currentState == STATE_COMPLETED
+ || !isPrepared;
+ }
+ });
+ playerGestureListener.doubleTapControls(binding.seekOverlay);
+ }
+
//endregion
@@ -1905,6 +1988,7 @@ public final class Player implements
}
private void showHideShadow(final boolean show, final long duration) {
+ animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null);
animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null);
animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null);
}
@@ -2179,18 +2263,21 @@ public final class Player implements
stopProgressLoop();
}
- showControls(400);
- binding.loadingPanel.setVisibility(View.GONE);
-
- animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
- () -> {
- binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow);
- animatePlayButtons(true, 200);
- if (!isQueueVisible) {
- binding.playPauseButton.requestFocus();
- }
- });
+ // Don't let UI elements popup during double tap seeking. This state is entered sometimes
+ // during seeking/loading. This if-else check ensures that the controls aren't popping up.
+ if (!playerGestureListener.isDoubleTapping()) {
+ showControls(400);
+ binding.loadingPanel.setVisibility(View.GONE);
+ animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
+ () -> {
+ binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow);
+ animatePlayButtons(true, 200);
+ if (!isQueueVisible) {
+ binding.playPauseButton.requestFocus();
+ }
+ });
+ }
changePopupWindowFlags(IDLE_WINDOW_FLAGS);
// Remove running notification when user does not want minimization to background or popup
@@ -2838,7 +2925,6 @@ public final class Player implements
}
seekBy(retrieveSeekDurationFromPreferences(this));
triggerProgressUpdate();
- showAndAnimateControl(R.drawable.ic_fast_forward, true);
}
public void fastRewind() {
@@ -2847,7 +2933,6 @@ public final class Player implements
}
seekBy(-retrieveSeekDurationFromPreferences(this));
triggerProgressUpdate();
- showAndAnimateControl(R.drawable.ic_fast_rewind, true);
}
//endregion
@@ -4279,6 +4364,10 @@ public final class Player implements
return binding.currentDisplaySeek;
}
+ public PlayerSeekOverlay getSeekOverlay() {
+ return binding.seekOverlay;
+ }
+
@Nullable
public WindowManager.LayoutParams getPopupLayoutParams() {
return popupLayoutParams;
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
index 25ace1c05..b215584e8 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -55,12 +55,10 @@ public class PlayerGestureListener
player.hideControls(0, 0);
}
- if (portion == DisplayPortion.LEFT) {
- player.fastRewind();
+ if (portion == DisplayPortion.LEFT || portion == DisplayPortion.RIGHT) {
+ startMultiDoubleTap(event);
} else if (portion == DisplayPortion.MIDDLE) {
player.playPause();
- } else if (portion == DisplayPortion.RIGHT) {
- player.fastForward();
}
}
@@ -236,6 +234,7 @@ public class PlayerGestureListener
player.getLoadingPanel().setVisibility(View.GONE);
player.hideControls(0, 0);
+ animate(player.getSeekOverlay(), false, 0);
animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0);
}
diff --git a/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt b/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt
index cbb4df738..6b22538e0 100644
--- a/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt
+++ b/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt
@@ -11,7 +11,7 @@ import org.schabi.newpipe.player.event.DisplayPortion
class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context, attrs) {
companion object {
- const val COLOR_DARK = 0x40000000
+ const val COLOR_DARK = 0x45000000
const val COLOR_DARK_TRANSPARENT = 0x30000000
const val COLOR_LIGHT_TRANSPARENT = 0x25EEEEEE
diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerSeekOverlay.kt
index 5bdf0c97b..d61989d92 100644
--- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerSeekOverlay.kt
+++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerSeekOverlay.kt
@@ -5,24 +5,30 @@ import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
-import androidx.annotation.*
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StyleRes
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.END
+import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.START
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.*
import androidx.core.content.ContextCompat
import androidx.core.widget.TextViewCompat
import androidx.preference.PreferenceManager
-import kotlinx.android.synthetic.main.player_seek_overlay.view.*
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R
import org.schabi.newpipe.player.event.DisplayPortion
import org.schabi.newpipe.player.event.DoubleTapListener
-class PlayerSeekOverlay(context: Context?, private val attrs: AttributeSet?) :
+class PlayerSeekOverlay(context: Context, private val attrs: AttributeSet?) :
ConstraintLayout(context, attrs), DoubleTapListener {
private var secondsView: SecondsView
private var circleClipTapView: CircleClipTapView
+ private var rootConstraintLayout: ConstraintLayout
private var isForwarding: Boolean? = null
@@ -31,6 +37,7 @@ class PlayerSeekOverlay(context: Context?, private val attrs: AttributeSet?) :
secondsView = findViewById(R.id.seconds_view)
circleClipTapView = findViewById(R.id.circle_clip_tap_view)
+ rootConstraintLayout = findViewById(R.id.root_constraint_layout)
initializeAttributes()
secondsView.isForward = true
@@ -161,11 +168,14 @@ class PlayerSeekOverlay(context: Context?, private val attrs: AttributeSet?) :
val shouldForward: Boolean = performListener?.shouldFastForward(portion) ?: return
if (DEBUG)
- Log.d(TAG,"onDoubleTapProgressDown called with " +
- "shouldForward = [$shouldForward], " +
- "isForwarding = [$isForwarding], " +
- "secondsView#isForward = [${secondsView.isForward}], " +
- "initTap = [$initTap], ")
+ Log.d(
+ TAG,
+ "onDoubleTapProgressDown called with " +
+ "shouldForward = [$shouldForward], " +
+ "isForwarding = [$isForwarding], " +
+ "secondsView#isForward = [${secondsView.isForward}], " +
+ "initTap = [$initTap], "
+ )
// Using this check prevents from fast switching (one touches)
if (isForwarding != null && isForwarding != shouldForward) {
@@ -234,18 +244,22 @@ class PlayerSeekOverlay(context: Context?, private val attrs: AttributeSet?) :
private fun changeConstraints(forward: Boolean) {
val constraintSet = ConstraintSet()
with(constraintSet) {
- clone(root_constraint_layout)
+ clone(rootConstraintLayout)
if (forward) {
- clear(seconds_view.id, START)
- connect(seconds_view.id, END,
- PARENT_ID, END)
+ clear(secondsView.id, START)
+ connect(
+ secondsView.id, END,
+ PARENT_ID, END
+ )
} else {
- clear(seconds_view.id, END)
- connect(seconds_view.id, START,
- PARENT_ID, START)
+ clear(secondsView.id, END)
+ connect(
+ secondsView.id, START,
+ PARENT_ID, START
+ )
}
secondsView.start()
- applyTo(root_constraint_layout)
+ applyTo(rootConstraintLayout)
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt b/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt
index 30bfe1217..fa2dbd63e 100644
--- a/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt
+++ b/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt
@@ -11,7 +11,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import kotlinx.android.synthetic.main.player_seek_seconds_view.view.*
import org.schabi.newpipe.R
-class SecondsView(context: Context?, attrs: AttributeSet?) :
+class SecondsView(context: Context, attrs: AttributeSet?) :
ConstraintLayout(context, attrs) {
companion object {
@@ -45,7 +45,6 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
val textView: TextView
get() = tv_seconds
-
@DrawableRes
var icon: Int = R.drawable.ic_play_seek_triangle
set(value) {
@@ -87,9 +86,11 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
icon_1.alpha = 0f
icon_2.alpha = 0f
icon_3.alpha = 0f
- }, {
+ },
+ {
icon_1.alpha = it
- }, {
+ },
+ {
secondAnimator.start()
}
)
@@ -99,9 +100,11 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
icon_1.alpha = 1f
icon_2.alpha = 0f
icon_3.alpha = 0f
- }, {
+ },
+ {
icon_2.alpha = it
- }, {
+ },
+ {
thirdAnimator.start()
}
)
@@ -111,10 +114,12 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
icon_1.alpha = 1f
icon_2.alpha = 1f
icon_3.alpha = 0f
- }, {
+ },
+ {
icon_1.alpha = 1f - icon_3.alpha
icon_3.alpha = it
- }, {
+ },
+ {
fourthAnimator.start()
}
)
@@ -124,9 +129,11 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
icon_1.alpha = 0f
icon_2.alpha = 1f
icon_3.alpha = 1f
- }, {
+ },
+ {
icon_2.alpha = 1f - it
- }, {
+ },
+ {
fifthAnimator.start()
}
)
@@ -136,16 +143,20 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
icon_1.alpha = 0f
icon_2.alpha = 0f
icon_3.alpha = 1f
- }, {
+ },
+ {
icon_3.alpha = 1f - it
- }, {
+ },
+ {
firstAnimator.start()
}
)
private inner class CustomValueAnimator(
- start: () -> Unit, update: (value: Float) -> Unit, end: () -> Unit
- ): ValueAnimator() {
+ start: () -> Unit,
+ update: (value: Float) -> Unit,
+ end: () -> Unit
+ ) : ValueAnimator() {
init {
duration = cycleDuration / 5
@@ -164,7 +175,6 @@ class SecondsView(context: Context?, attrs: AttributeSet?) :
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationRepeat(animation: Animator?) = Unit
-
})
}
}
diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml
index f3b1056d2..cdb5849ab 100644
--- a/app/src/main/res/layout-large-land/player.xml
+++ b/app/src/main/res/layout-large-land/player.xml
@@ -54,11 +54,19 @@
tools:ignore="ContentDescription"
tools:visibility="visible" />
+
+
@@ -754,4 +762,11 @@
android:textColor="@color/white"
android:visibility="gone" />
+
+
diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml
index c2d1c84ff..e71ffdd96 100644
--- a/app/src/main/res/layout/player.xml
+++ b/app/src/main/res/layout/player.xml
@@ -54,11 +54,19 @@
tools:ignore="ContentDescription"
tools:visibility="visible" />
+
+
@@ -751,4 +759,11 @@
android:textColor="@color/white"
android:visibility="gone" />
+
+