Code cleanup and minimization

* Deduplicated and simplified a lot of code
* Fixed ``invalidSeekConditions`` so that it's possible to seek while the player is loading (like currently the case)
This commit is contained in:
litetex 2021-12-03 20:29:18 +01:00
parent dac47d9f52
commit fe42206e94
11 changed files with 185 additions and 477 deletions

View File

@ -51,8 +51,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -189,8 +187,7 @@ import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.ExpandableSurfaceView; import org.schabi.newpipe.views.ExpandableSurfaceView;
import org.schabi.newpipe.views.player.CircleClipTapView; import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
import org.schabi.newpipe.views.player.PlayerSeekOverlay;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -454,6 +451,8 @@ public final class Player implements
initPlayer(true); initPlayer(true);
} }
initListeners(); initListeners();
setupPlayerSeekOverlay();
} }
private void initViews(@NonNull final PlayerBinding playerBinding) { private void initViews(@NonNull final PlayerBinding playerBinding) {
@ -530,12 +529,6 @@ public final class Player implements
binding.resizeTextView.setOnClickListener(this); binding.resizeTextView.setOnClickListener(this);
binding.playbackLiveSync.setOnClickListener(this); binding.playbackLiveSync.setOnClickListener(this);
playerGestureListener = new PlayerGestureListener(this, service);
gestureDetector = new GestureDetectorCompat(context, playerGestureListener);
//noinspection ClickableViewAccessibility
binding.getRoot().setOnTouchListener(playerGestureListener);
setupPlayerSeekOverlay();
binding.queueButton.setOnClickListener(this); binding.queueButton.setOnClickListener(this);
binding.segmentsButton.setOnClickListener(this); binding.segmentsButton.setOnClickListener(this);
binding.repeatButton.setOnClickListener(this); binding.repeatButton.setOnClickListener(this);
@ -586,26 +579,26 @@ public final class Player implements
v.getPaddingBottom())); v.getPaddingBottom()));
} }
/**
* Initializes the Fast-For/Backward overlay.
*/
private void setupPlayerSeekOverlay() { private void setupPlayerSeekOverlay() {
binding.seekOverlay.showCircle(true) playerGestureListener = new PlayerGestureListener(this, service);
.circleBackgroundColorInt(CircleClipTapView.COLOR_DARK_TRANSPARENT) gestureDetector = new GestureDetectorCompat(context, playerGestureListener);
binding.getRoot().setOnTouchListener(playerGestureListener);
binding.fastSeekOverlay
.seekSeconds((int) (retrieveSeekDurationFromPreferences(this) / 1000.0f)) .seekSeconds((int) (retrieveSeekDurationFromPreferences(this) / 1000.0f))
.performListener(new PlayerSeekOverlay.PerformListener() { .performListener(new PlayerFastSeekOverlay.PerformListener() {
@Override @Override
public void onPrepare() { public void onDoubleTabStart() {
if (invalidSeekConditions()) { // TODO
playerGestureListener.endMultiDoubleTap();
return;
}
binding.seekOverlay.arcSize(
CircleClipTapView.Companion.calculateArcSize(getSurfaceView())
);
} }
@Override @Override
public void onAnimationStart() { public void onDoubleTab() {
animate(binding.seekOverlay, true, SEEK_OVERLAY_DURATION); animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION);
animate(binding.playbackControlsShadow, animate(binding.playbackControlsShadow,
!simpleExoPlayer.getPlayWhenReady(), SEEK_OVERLAY_DURATION); !simpleExoPlayer.getPlayWhenReady(), SEEK_OVERLAY_DURATION);
animate(binding.playerTopShadow, false, SEEK_OVERLAY_DURATION); animate(binding.playerTopShadow, false, SEEK_OVERLAY_DURATION);
@ -615,8 +608,8 @@ public final class Player implements
} }
@Override @Override
public void onAnimationEnd() { public void onDoubleTabEnd() {
animate(binding.seekOverlay, false, SEEK_OVERLAY_DURATION); animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION);
if (!simpleExoPlayer.getPlayWhenReady()) { if (!simpleExoPlayer.getPlayWhenReady()) {
showControls(SEEK_OVERLAY_DURATION); showControls(SEEK_OVERLAY_DURATION);
} else { } else {
@ -629,6 +622,7 @@ public final class Player implements
// Null indicates an invalid area or condition e.g. the middle portion // Null indicates an invalid area or condition e.g. the middle portion
// or video start or end was reached during double tap seeking // or video start or end was reached during double tap seeking
if (invalidSeekConditions()) { if (invalidSeekConditions()) {
playerGestureListener.endMultiDoubleTap();
return null; return null;
} }
if (portion == DisplayPortion.LEFT if (portion == DisplayPortion.LEFT
@ -637,9 +631,9 @@ public final class Player implements
return false; return false;
} else if (portion == DisplayPortion.RIGHT) { } else if (portion == DisplayPortion.RIGHT) {
return true; return true;
} else /* portion == DisplayPortion.MIDDLE */ {
return null;
} }
/* portion == DisplayPortion.MIDDLE */
return null;
} }
@Override @Override
@ -653,12 +647,13 @@ public final class Player implements
} }
private boolean invalidSeekConditions() { private boolean invalidSeekConditions() {
return simpleExoPlayer.getCurrentPosition() == simpleExoPlayer.getDuration() return exoPlayerIsNull()
|| currentState == STATE_COMPLETED || simpleExoPlayer.getPlaybackState() == SimpleExoPlayer.STATE_ENDED
|| !isPrepared; || simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()
|| currentState == STATE_COMPLETED;
} }
}); });
playerGestureListener.doubleTapControls(binding.seekOverlay); playerGestureListener.doubleTapControls(binding.fastSeekOverlay);
} }
//endregion //endregion
@ -1879,71 +1874,6 @@ public final class Player implements
return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE;
} }
/**
* Show a animation, and depending on goneOnEnd, will stay on the screen or be gone.
*
* @param drawableId the drawable that will be used to animate,
* pass -1 to clear any animation that is visible
* @param goneOnEnd will set the animation view to GONE on the end of the animation
*/
public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
if (DEBUG) {
Log.d(TAG, "showAndAnimateControl() called with: "
+ "drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
}
if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
if (DEBUG) {
Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
}
controlViewAnimator.end();
}
if (drawableId == -1) {
if (binding.controlAnimationView.getVisibility() == View.VISIBLE) {
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
binding.controlAnimationView,
PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
).setDuration(DEFAULT_CONTROLS_DURATION);
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
binding.controlAnimationView.setVisibility(View.GONE);
}
});
controlViewAnimator.start();
}
return;
}
final float scaleFrom = goneOnEnd ? 1f : 1f;
final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
final float alphaFrom = goneOnEnd ? 1f : 0f;
final float alphaTo = goneOnEnd ? 0f : 1f;
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
binding.controlAnimationView,
PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
);
controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
binding.controlAnimationView.setVisibility(goneOnEnd ? View.GONE : View.VISIBLE);
}
});
binding.controlAnimationView.setVisibility(View.VISIBLE);
binding.controlAnimationView.setImageDrawable(
AppCompatResources.getDrawable(context, drawableId));
controlViewAnimator.start();
}
public void showControlsThenHide() { public void showControlsThenHide() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "showControlsThenHide() called"); Log.d(TAG, "showControlsThenHide() called");
@ -2214,8 +2144,6 @@ public final class Player implements
updateStreamRelatedViews(); updateStreamRelatedViews();
showAndAnimateControl(-1, true);
binding.playbackSeekBar.setEnabled(true); binding.playbackSeekBar.setEnabled(true);
binding.playbackSeekBar.getThumb() binding.playbackSeekBar.getThumb()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
@ -2295,7 +2223,6 @@ public final class Player implements
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onPausedSeek() called"); Log.d(TAG, "onPausedSeek() called");
} }
showAndAnimateControl(-1, true);
animatePlayButtons(false, 100); animatePlayButtons(false, 100);
binding.getRoot().setKeepScreenOn(true); binding.getRoot().setKeepScreenOn(true);
@ -4364,8 +4291,8 @@ public final class Player implements
return binding.currentDisplaySeek; return binding.currentDisplaySeek;
} }
public PlayerSeekOverlay getSeekOverlay() { public PlayerFastSeekOverlay getFastSeekOverlay() {
return binding.seekOverlay; return binding.fastSeekOverlay;
} }
@Nullable @Nullable

View File

@ -411,7 +411,7 @@ abstract class BasePlayerGestureListener(
var doubleTapControls: DoubleTapListener? = null var doubleTapControls: DoubleTapListener? = null
private set private set
val isDoubleTapEnabled: Boolean private val isDoubleTapEnabled: Boolean
get() = doubleTapDelay > 0 get() = doubleTapDelay > 0
var isDoubleTapping = false var isDoubleTapping = false
@ -459,10 +459,6 @@ abstract class BasePlayerGestureListener(
doubleTapControls?.onDoubleTapFinished() doubleTapControls?.onDoubleTapFinished()
} }
fun enableMultiDoubleTap(enable: Boolean) = apply {
doubleTapDelay = if (enable) DOUBLE_TAP_DELAY else 0
}
// /////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////
// Utils // Utils
// /////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////

View File

@ -230,11 +230,10 @@ public class PlayerGestureListener
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onPopupResizingStart called"); Log.d(TAG, "onPopupResizingStart called");
} }
player.showAndAnimateControl(-1, true);
player.getLoadingPanel().setVisibility(View.GONE); player.getLoadingPanel().setVisibility(View.GONE);
player.hideControls(0, 0); player.hideControls(0, 0);
animate(player.getSeekOverlay(), false, 0); animate(player.getFastSeekOverlay(), false, 0);
animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0); animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0);
} }

View File

@ -6,18 +6,9 @@ import android.graphics.Paint
import android.graphics.Path import android.graphics.Path
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import org.schabi.newpipe.player.event.DisplayPortion
class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context, attrs) { class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context, attrs) {
companion object {
const val COLOR_DARK = 0x45000000
const val COLOR_DARK_TRANSPARENT = 0x30000000
const val COLOR_LIGHT_TRANSPARENT = 0x25EEEEEE
fun calculateArcSize(view: View): Float = view.height / 11.4f
}
private var backgroundPaint = Paint() private var backgroundPaint = Paint()
private var widthPx = 0 private var widthPx = 0
@ -26,6 +17,7 @@ class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context,
// Background // Background
private var shapePath = Path() private var shapePath = Path()
private var arcSize: Float = 80f
private var isLeft = true private var isLeft = true
init { init {
@ -34,7 +26,7 @@ class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context,
backgroundPaint.apply { backgroundPaint.apply {
style = Paint.Style.FILL style = Paint.Style.FILL
isAntiAlias = true isAntiAlias = true
color = COLOR_LIGHT_TRANSPARENT color = 0x30000000
} }
val dm = context.resources.displayMetrics val dm = context.resources.displayMetrics
@ -44,24 +36,15 @@ class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context,
updatePathShape() updatePathShape()
} }
var arcSize: Float = 80f fun updateArcSize(baseView: View) {
set(value) { val newArcSize = baseView.height / 11.4f
field = value if (arcSize != newArcSize) {
arcSize = newArcSize
updatePathShape() updatePathShape()
} }
}
var circleBackgroundColor: Int fun updatePosition(newIsLeft: Boolean) {
get() = backgroundPaint.color
set(value) {
backgroundPaint.color = value
}
/*
Background
*/
fun updatePosition(portion: DisplayPortion) {
val newIsLeft = portion == DisplayPortion.LEFT
if (isLeft != newIsLeft) { if (isLeft != newIsLeft) {
isLeft = newIsLeft isLeft = newIsLeft
updatePathShape() updatePathShape()

View File

@ -0,0 +1,136 @@
package org.schabi.newpipe.views.player
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
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 org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R
import org.schabi.newpipe.player.event.DisplayPortion
import org.schabi.newpipe.player.event.DoubleTapListener
class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) :
ConstraintLayout(context, attrs), DoubleTapListener {
private var secondsView: SecondsView
private var circleClipTapView: CircleClipTapView
private var rootConstraintLayout: ConstraintLayout
private var wasForwarding: Boolean = false
init {
LayoutInflater.from(context).inflate(R.layout.player_fast_seek_overlay, this, true)
secondsView = findViewById(R.id.seconds_view)
circleClipTapView = findViewById(R.id.circle_clip_tap_view)
rootConstraintLayout = findViewById(R.id.root_constraint_layout)
addOnLayoutChangeListener { view, _, _, _, _, _, _, _, _ ->
circleClipTapView.updateArcSize(view)
}
}
private var performListener: PerformListener? = null
fun performListener(listener: PerformListener) = apply {
performListener = listener
}
var seekSeconds: Int = 0
private set
fun seekSeconds(seconds: Int) = apply {
seekSeconds = seconds
}
// Indicates whether this (double) tap is the first of a series
// Decides whether to call performListener.onAnimationStart or not
private var initTap: Boolean = false
override fun onDoubleTapStarted(portion: DisplayPortion) {
if (DEBUG)
Log.d(TAG, "onDoubleTapStarted called with portion = [$portion]")
initTap = false
performListener?.onDoubleTabStart()
secondsView.stop()
}
override fun onDoubleTapProgressDown(portion: DisplayPortion) {
val shouldForward: Boolean = performListener?.shouldFastForward(portion) ?: return
if (DEBUG)
Log.d(
TAG,
"onDoubleTapProgressDown called with " +
"shouldForward = [$shouldForward], " +
"wasForwarding = [$wasForwarding], " +
"initTap = [$initTap], "
)
/*
* Check if a initial tab occurred or if direction was switched
*/
if (!initTap || wasForwarding != shouldForward) {
// Reset seconds and update position
secondsView.seconds = 0
changeConstraints(shouldForward)
circleClipTapView.updatePosition(!shouldForward)
secondsView.setForwarding(shouldForward)
wasForwarding = shouldForward
if (!initTap) {
// Start animation
secondsView.start()
initTap = true
}
}
performListener?.onDoubleTab()
secondsView.seconds += seekSeconds
performListener?.seek(forward = shouldForward)
}
override fun onDoubleTapFinished() {
if (DEBUG)
Log.d(TAG, "onDoubleTapFinished called with initTap = [$initTap]")
if (initTap) performListener?.onDoubleTabEnd()
initTap = false
}
private fun changeConstraints(forward: Boolean) {
val constraintSet = ConstraintSet()
with(constraintSet) {
clone(rootConstraintLayout)
clear(secondsView.id, if (forward) START else END)
connect(
secondsView.id, if (forward) END else START,
PARENT_ID, if (forward) END else START
)
secondsView.start()
applyTo(rootConstraintLayout)
}
}
interface PerformListener {
fun onDoubleTabStart() {}
fun onDoubleTab()
fun onDoubleTabEnd()
fun shouldFastForward(portion: DisplayPortion): Boolean?
fun seek(forward: Boolean)
}
companion object {
private const val TAG = "PlayerSeekOverlay"
private val DEBUG = MainActivity.DEBUG
}
}

View File

@ -1,278 +0,0 @@
package org.schabi.newpipe.views.player
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
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.core.content.ContextCompat
import androidx.core.widget.TextViewCompat
import androidx.preference.PreferenceManager
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?) :
ConstraintLayout(context, attrs), DoubleTapListener {
private var secondsView: SecondsView
private var circleClipTapView: CircleClipTapView
private var rootConstraintLayout: ConstraintLayout
private var isForwarding: Boolean? = null
init {
LayoutInflater.from(context).inflate(R.layout.player_seek_overlay, this, true)
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
isForwarding = null
changeConstraints(true)
}
private fun initializeAttributes() {
circleBackgroundColorInt(CircleClipTapView.COLOR_LIGHT_TRANSPARENT)
iconAnimationDuration(SecondsView.ICON_ANIMATION_DURATION)
icon(R.drawable.ic_play_seek_triangle)
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val durationKey = context.getString(R.string.seek_duration_key)
val seekValue = prefs.getString(
durationKey, context.getString(R.string.seek_duration_default_value)
)
seekSeconds(seekValue?.toInt()?.div(1000) ?: 10)
}
private var performListener: PerformListener? = null
fun performListener(listener: PerformListener) = apply {
performListener = listener
}
var seekSeconds: Int = 0
private set
fun seekSeconds(seconds: Int) = apply {
seekSeconds = seconds
}
var circleBackgroundColor: Int
get() = circleClipTapView.circleBackgroundColor
private set(value) {
circleClipTapView.circleBackgroundColor = value
}
fun circleBackgroundColorRes(@ColorRes resId: Int) = apply {
circleBackgroundColor = ContextCompat.getColor(context, resId)
}
fun circleBackgroundColorInt(@ColorInt color: Int) = apply {
circleBackgroundColor = color
}
var arcSize: Float
get() = circleClipTapView.arcSize
internal set(value) {
circleClipTapView.arcSize = value
}
fun arcSize(@DimenRes resId: Int) = apply {
arcSize = context.resources.getDimension(resId)
}
fun arcSize(px: Float) = apply {
arcSize = px
}
var showCircle: Boolean = true
private set(value) {
circleClipTapView.visibility = if (value) View.VISIBLE else View.GONE
field = value
}
fun showCircle(show: Boolean) = apply {
showCircle = show
}
var iconAnimationDuration: Long = SecondsView.ICON_ANIMATION_DURATION
get() = secondsView.cycleDuration
private set(value) {
secondsView.cycleDuration = value
field = value
}
fun iconAnimationDuration(duration: Long) = apply {
iconAnimationDuration = duration
}
@DrawableRes
var icon: Int = 0
get() = secondsView.icon
private set(value) {
secondsView.stop()
secondsView.icon = value
field = value
}
fun icon(@DrawableRes resId: Int) = apply {
icon = resId
}
@StyleRes
var textAppearance: Int = 0
private set(value) {
TextViewCompat.setTextAppearance(secondsView.textView, value)
field = value
}
fun textAppearance(@StyleRes resId: Int) = apply {
textAppearance = resId
}
// Indicates whether this (double) tap is the first of a series
// Decides whether to call performListener.onAnimationStart or not
private var initTap: Boolean = false
override fun onDoubleTapStarted(portion: DisplayPortion) {
if (DEBUG)
Log.d(TAG, "onDoubleTapStarted called with portion = [$portion]")
initTap = false
performListener?.onPrepare()
changeConstraints(secondsView.isForward)
if (showCircle) circleClipTapView.updatePosition(portion)
isForwarding = null
if (this.alpha == 0f)
secondsView.stop()
}
override fun onDoubleTapProgressDown(portion: DisplayPortion) {
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], "
)
// Using this check prevents from fast switching (one touches)
if (isForwarding != null && isForwarding != shouldForward) {
isForwarding = shouldForward
return
}
isForwarding = shouldForward
if (this.visibility != View.VISIBLE || !initTap) {
if (!initTap) {
secondsView.seconds = 0
performListener?.onAnimationStart()
secondsView.start()
initTap = true
}
}
if (shouldForward)
forwarding()
else
rewinding()
}
override fun onDoubleTapFinished() {
if (DEBUG)
Log.d(TAG, "onDoubleTapFinished called with initTap = [$initTap]")
if (initTap) performListener?.onAnimationEnd()
initTap = false
}
private fun forwarding() {
if (DEBUG)
Log.d(TAG, "forwarding called")
// First time tap or switched
if (!secondsView.isForward) {
changeConstraints(true)
if (showCircle) circleClipTapView.updatePosition(DisplayPortion.RIGHT)
secondsView.apply {
isForward = true
seconds = 0
}
}
secondsView.seconds += seekSeconds
performListener?.seek(forward = true)
}
private fun rewinding() {
if (DEBUG)
Log.d(TAG, "rewinding called")
// First time tap or switched
if (secondsView.isForward) {
changeConstraints(false)
if (showCircle) circleClipTapView.updatePosition(DisplayPortion.LEFT)
secondsView.apply {
isForward = false
seconds = 0
}
}
secondsView.seconds += seekSeconds
performListener?.seek(forward = false)
}
private fun changeConstraints(forward: Boolean) {
val constraintSet = ConstraintSet()
with(constraintSet) {
clone(rootConstraintLayout)
if (forward) {
clear(secondsView.id, START)
connect(
secondsView.id, END,
PARENT_ID, END
)
} else {
clear(secondsView.id, END)
connect(
secondsView.id, START,
PARENT_ID, START
)
}
secondsView.start()
applyTo(rootConstraintLayout)
}
}
interface PerformListener {
fun onPrepare() {}
fun onAnimationStart()
fun onAnimationEnd()
fun shouldFastForward(portion: DisplayPortion): Boolean?
fun seek(forward: Boolean)
}
companion object {
private const val TAG = "PlayerSeekOverlay"
private val DEBUG = MainActivity.DEBUG
}
}

View File

@ -6,10 +6,8 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.DrawableRes
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.PlayerSeekSecondsViewBinding import org.schabi.newpipe.databinding.PlayerFastSeekSecondsViewBinding
class SecondsView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { class SecondsView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
@ -35,33 +33,17 @@ class SecondsView(context: Context, attrs: AttributeSet?) : LinearLayout(context
field = value field = value
} }
var isForward: Boolean = true val binding = PlayerFastSeekSecondsViewBinding.inflate(LayoutInflater.from(context), this)
set(value) {
binding.triangleContainer.rotation = if (value) 0f else 180f
field = value
}
val binding = PlayerSeekSecondsViewBinding.inflate(LayoutInflater.from(context), this)
val textView: TextView
get() = binding.tvSeconds
@DrawableRes
var icon: Int = R.drawable.ic_play_seek_triangle
set(value) {
if (value > 0) {
binding.icon1.setImageResource(value)
binding.icon2.setImageResource(value)
binding.icon3.setImageResource(value)
}
field = value
}
init { init {
orientation = VERTICAL orientation = VERTICAL
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
} }
fun setForwarding(isForward: Boolean) {
binding.triangleContainer.rotation = if (isForward) 0f else 180f
}
fun start() { fun start() {
stop() stop()
firstAnimator.start() firstAnimator.start()

View File

@ -647,24 +647,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/controlAnimationView"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/background_oval_black_transparent"
android:padding="15dp"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_fast_rewind"
tools:visibility="visible" />
</LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/loading_panel" android:id="@+id/loading_panel"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -764,8 +746,8 @@
android:textColor="@color/white" android:textColor="@color/white"
android:visibility="gone" /> android:visibility="gone" />
<org.schabi.newpipe.views.player.PlayerSeekOverlay <org.schabi.newpipe.views.player.PlayerFastSeekOverlay
android:id="@+id/seekOverlay" android:id="@+id/fast_seek_overlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:alpha="0" android:alpha="0"

View File

@ -643,24 +643,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/controlAnimationView"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/background_oval_black_transparent"
android:padding="15dp"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_fast_rewind"
tools:visibility="visible" />
</LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/loading_panel" android:id="@+id/loading_panel"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -761,8 +743,8 @@
android:textColor="@color/white" android:textColor="@color/white"
android:visibility="gone" /> android:visibility="gone" />
<org.schabi.newpipe.views.player.PlayerSeekOverlay <org.schabi.newpipe.views.player.PlayerFastSeekOverlay
android:id="@+id/seekOverlay" android:id="@+id/fast_seek_overlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:alpha="0" android:alpha="0"

View File

@ -10,8 +10,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false" />
android:visibility="invisible" />
<org.schabi.newpipe.views.player.SecondsView <org.schabi.newpipe.views.player.SecondsView
android:id="@+id/seconds_view" android:id="@+id/seconds_view"