forked from PierfrancescoSoffritti/android-youtube-player
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add fast forward/rewind functionality
Adds a customizable fast forward/rewind view that can be used for seeking the player Bug: PierfrancescoSoffritti#132
- Loading branch information
1 parent
eb33709
commit 1321c8b
Showing
8 changed files
with
349 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
...ncescosoffritti/androidyoutubeplayer/core/ui/views/FastForwardRewindTrianglesAnimation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.pierfrancescosoffritti.androidyoutubeplayer.core.ui.views | ||
|
||
import android.view.View | ||
import android.view.animation.AlphaAnimation | ||
import android.view.animation.Animation | ||
|
||
/** | ||
* Creates an animation that's *specifically* made for each of the triangles in the fast forward/rewind icon. | ||
*/ | ||
internal class FastForwardRewindTrianglesAnimation(fromAlpha: Float, toAlpha: Float) : AlphaAnimation(fromAlpha, toAlpha) { | ||
|
||
/** | ||
* @param fromAlpha Initial alpha | ||
* @param toAlpha Final alpha | ||
* @param startOffset What time to start the animation. This is used to specify the current triangle animation's sequence | ||
* @param singleTriangleAnimationDuration How long this triangle should take to fade in | ||
* @param entireAnimationDuration How long all triangles take to execute a single animation cycle | ||
* @param triangleView View of the triangle that's using this animation | ||
*/ | ||
constructor(fromAlpha: Float, toAlpha: Float, startOffset: Long, singleTriangleAnimationDuration: Long, entireAnimationDuration: Long, triangleView: View) : this(fromAlpha, toAlpha) { | ||
this.duration = singleTriangleAnimationDuration | ||
this.startOffset = startOffset | ||
this.initialStartOffset = startOffset | ||
this.entireAnimationDuration = entireAnimationDuration | ||
this.triangleView = triangleView | ||
} | ||
|
||
private var entireAnimationDuration = 0L | ||
private var initialStartOffset = 0L | ||
private lateinit var triangleView: View | ||
|
||
init { | ||
this.repeatCount = Animation.INFINITE | ||
this.setAnimationListener(object : AnimationListener { | ||
/** | ||
* Change the startOffset so this triangle's animation loops after all triangles have | ||
* completed a single cycle | ||
*/ | ||
override fun onAnimationRepeat(animation: Animation?) { | ||
animation?.startOffset = entireAnimationDuration | ||
} | ||
|
||
override fun onAnimationStart(animation: Animation?) { | ||
triangleView.visibility = View.VISIBLE | ||
} | ||
|
||
/** | ||
* Reset startOffset so this triangle's animation restarts properly next time | ||
* (so it doesn't start where it was stopped) | ||
*/ | ||
override fun onAnimationEnd(animation: Animation?) { | ||
triangleView.visibility = View.INVISIBLE | ||
animation?.startOffset = initialStartOffset | ||
} | ||
}) | ||
} | ||
} |
175 changes: 175 additions & 0 deletions
175
...erfrancescosoffritti/androidyoutubeplayer/core/ui/views/YouTubePlayerFastForwardRewind.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package com.pierfrancescosoffritti.androidyoutubeplayer.core.ui.views | ||
|
||
import android.content.Context | ||
import android.os.CountDownTimer | ||
import android.util.AttributeSet | ||
import android.view.MotionEvent | ||
import android.view.View | ||
import android.widget.ImageView | ||
import android.widget.LinearLayout | ||
import android.widget.RelativeLayout | ||
import android.widget.TextView | ||
import com.pierfrancescosoffritti.androidyoutubeplayer.R | ||
|
||
|
||
/** | ||
* A view used for fast forwarding or fast rewinding the player. | ||
* This view itself doesn't perform the seek event, but it exposes [addOnSeekAction] for other components | ||
* to add callbacks that will be executed whenever a seek action occurs. | ||
*/ | ||
|
||
class YouTubePlayerFastForwardRewind(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) { | ||
|
||
private val fastForwardRewindIndicator: LinearLayout | ||
|
||
private val fastForwardRewindLeftTriangle: ImageView | ||
private val fastForwardRewindMidImageTriangle: ImageView | ||
private val fastForwardRewindRightImageTriangle: ImageView | ||
private lateinit var fadeInLeftTriangleAnim: FastForwardRewindTrianglesAnimation | ||
private lateinit var fadeInMidTriangleAnim: FastForwardRewindTrianglesAnimation | ||
private lateinit var fadeInRightTriangleAnim: FastForwardRewindTrianglesAnimation | ||
|
||
private val fastForwardRewindText: TextView | ||
|
||
private val fastForwardRewindTimer: CountDownTimer | ||
private val msFastForwardRewindWaitTime = 1000L | ||
private var secToSeek = 0F | ||
private var secToIncrementPerClick = 0F | ||
|
||
private var hasBeenClickedOnce = false | ||
|
||
private val seekActions = HashSet<(Float) -> Unit>() | ||
private val otherFastForwardRewindViews = HashSet<YouTubePlayerFastForwardRewind>() | ||
|
||
init { | ||
inflate(context, R.layout.ayp_fast_forward_rewind, this) | ||
|
||
val fastForwardRewindLayout: RelativeLayout = findViewById(R.id.fast_forward_rewind_layout) | ||
|
||
fastForwardRewindIndicator = findViewById(R.id.fast_forward_rewind_indicator) | ||
|
||
fastForwardRewindLeftTriangle = findViewById(R.id.fast_forward_rewind_left_image) | ||
fastForwardRewindMidImageTriangle = findViewById(R.id.fast_forward_rewind_mid_image) | ||
fastForwardRewindRightImageTriangle = findViewById(R.id.fast_forward_rewind_right_image) | ||
|
||
fastForwardRewindText = findViewById(R.id.fast_forward_rewind_text) | ||
|
||
|
||
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.YouTubePlayerFastForwardRewind, 0, 0) | ||
val shouldUseFastRewindLayout = typedArray.getBoolean(R.styleable.YouTubePlayerFastForwardRewind_useFastRewindLayout, false) | ||
|
||
useFastRewindLayout(shouldUseFastRewindLayout) | ||
|
||
fastForwardRewindTimer = object : CountDownTimer(msFastForwardRewindWaitTime, msFastForwardRewindWaitTime) { | ||
override fun onTick(millisUntilFinished: Long) { } | ||
|
||
/** | ||
* Run all [seekActions] with [secToSeek] as a parameter | ||
*/ | ||
override fun onFinish() { | ||
// Fire seek actions only if [secToSeek] is valid. Can be invalid if timer ends after just a single click | ||
if (secToSeek != 0F) { | ||
for (seekAction in seekActions) | ||
seekAction(secToSeek) | ||
} | ||
resetUIAndVariables() | ||
} | ||
} | ||
|
||
fastForwardRewindLayout.setOnTouchListener { v, event -> | ||
when (event?.action) { | ||
MotionEvent.ACTION_DOWN -> { | ||
// Start or restart timer | ||
fastForwardRewindTimer.cancel() | ||
fastForwardRewindTimer.start() | ||
|
||
// Need to stop other fastForwardRewindViews, this will avoid running multiple seekers in parallel | ||
for (otherFastForwardRewindView in otherFastForwardRewindViews) | ||
otherFastForwardRewindView.interruptSeek() | ||
|
||
// If has been clicked before, then fast forward | ||
if (hasBeenClickedOnce) { | ||
|
||
secToSeek += secToIncrementPerClick | ||
|
||
fastForwardRewindText.text = "${Math.abs(secToSeek.toInt())} seconds" | ||
|
||
// Make text and triangles parent visible, then start fast forward/rewind animation | ||
if (fastForwardRewindIndicator.visibility == View.INVISIBLE) { | ||
fastForwardRewindIndicator.visibility = View.VISIBLE | ||
fastForwardRewindIndicator.animate().alpha(1f).setDuration(200) | ||
|
||
fastForwardRewindLeftTriangle.startAnimation(fadeInLeftTriangleAnim) | ||
fastForwardRewindMidImageTriangle.startAnimation(fadeInMidTriangleAnim) | ||
fastForwardRewindRightImageTriangle.startAnimation(fadeInRightTriangleAnim) | ||
} | ||
} else hasBeenClickedOnce = true | ||
} | ||
} | ||
v?.onTouchEvent(event) ?: true | ||
} | ||
} | ||
|
||
fun useFastRewindLayout(shouldUseFastRewindLayout: Boolean) { | ||
val animationDurationForSingleTriangle = msFastForwardRewindWaitTime / 3 | ||
|
||
if (shouldUseFastRewindLayout) { | ||
secToIncrementPerClick = -10F | ||
|
||
fastForwardRewindLeftTriangle.rotation = -90f | ||
fastForwardRewindMidImageTriangle.rotation = -90f | ||
fastForwardRewindRightImageTriangle.rotation = -90f | ||
|
||
fadeInRightTriangleAnim = FastForwardRewindTrianglesAnimation(0f, 1f, 0, animationDurationForSingleTriangle, msFastForwardRewindWaitTime, fastForwardRewindLeftTriangle) | ||
fadeInMidTriangleAnim = FastForwardRewindTrianglesAnimation(0f, 1f, animationDurationForSingleTriangle / 2, animationDurationForSingleTriangle, msFastForwardRewindWaitTime, fastForwardRewindMidImageTriangle) | ||
fadeInLeftTriangleAnim = FastForwardRewindTrianglesAnimation(0f, 1f, animationDurationForSingleTriangle, animationDurationForSingleTriangle, msFastForwardRewindWaitTime, fastForwardRewindRightImageTriangle) | ||
|
||
} else { | ||
secToIncrementPerClick = 10F | ||
|
||
fastForwardRewindLeftTriangle.rotation = 90f | ||
fastForwardRewindMidImageTriangle.rotation = 90f | ||
fastForwardRewindRightImageTriangle.rotation = 90f | ||
|
||
fadeInLeftTriangleAnim = FastForwardRewindTrianglesAnimation(0f, 1f, 0, animationDurationForSingleTriangle, msFastForwardRewindWaitTime, fastForwardRewindLeftTriangle) | ||
fadeInMidTriangleAnim = FastForwardRewindTrianglesAnimation(0f, 1f, animationDurationForSingleTriangle / 2, animationDurationForSingleTriangle, msFastForwardRewindWaitTime, fastForwardRewindMidImageTriangle) | ||
fadeInRightTriangleAnim = FastForwardRewindTrianglesAnimation(0f, 1f, animationDurationForSingleTriangle, animationDurationForSingleTriangle, msFastForwardRewindWaitTime, fastForwardRewindRightImageTriangle) | ||
} | ||
} | ||
|
||
/** | ||
* Resets the view to the way it was before any seek event was started. | ||
* Note: This will not stop an already running seek event. If you want to | ||
* stop an already running seek event, see [interruptSeek] | ||
*/ | ||
fun resetUIAndVariables() { | ||
secToSeek = 0F | ||
hasBeenClickedOnce = false | ||
fastForwardRewindIndicator.animate().alpha(0f).setDuration(200).withEndAction { fastForwardRewindIndicator.visibility = View.INVISIBLE } | ||
|
||
fastForwardRewindLeftTriangle.clearAnimation() | ||
fastForwardRewindMidImageTriangle.clearAnimation() | ||
fastForwardRewindRightImageTriangle.clearAnimation() | ||
} | ||
|
||
/** | ||
* Stops the currently running seek event then resets the view to the way it was before | ||
* any seek event was started. | ||
*/ | ||
fun interruptSeek() { | ||
fastForwardRewindTimer.cancel() | ||
resetUIAndVariables() | ||
} | ||
|
||
/** | ||
* @param func Callback executed when a seek action occurs | ||
*/ | ||
fun addOnSeekAction(func: (Float) -> Unit): Boolean = seekActions.add(func) | ||
fun removeOnSeekAction(func: (Float) -> Unit): Boolean = seekActions.remove(func) | ||
|
||
/** | ||
* @param otherFastForwardRewindView Other [YouTubePlayerFastForwardRewind] that should be interrupted while using this one. | ||
*/ | ||
fun addOtherFastForwardRewindView(otherFastForwardRewindView: YouTubePlayerFastForwardRewind): Boolean = otherFastForwardRewindViews.add(otherFastForwardRewindView) | ||
fun removeOtherFastForwardRewindView(otherFastForwardRewindView: YouTubePlayerFastForwardRewind): Boolean = otherFastForwardRewindViews.remove(otherFastForwardRewindView) | ||
} |
9 changes: 9 additions & 0 deletions
9
core/src/main/res/drawable/ayp_ic_fast_forward_rewind_triangle_16dp.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="16dp" | ||
android:height="16dp" | ||
android:viewportWidth="24.0" | ||
android:viewportHeight="24.0"> | ||
<path | ||
android:fillColor="#fff" | ||
android:pathData="M1,24l23,0l-12,-15z"/> | ||
</vector> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:id="@+id/fast_forward_rewind_layout" | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
|
||
android:background="@drawable/ayp_background_item_selected" | ||
|
||
android:clickable="true" | ||
android:focusable="true"> | ||
|
||
<LinearLayout | ||
android:id="@+id/fast_forward_rewind_indicator" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
|
||
android:layout_centerInParent="true" | ||
|
||
android:orientation="vertical" | ||
android:visibility="invisible"> | ||
|
||
<LinearLayout | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center"> | ||
|
||
<ImageView | ||
android:id="@+id/fast_forward_rewind_left_image" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:src="@drawable/ayp_ic_fast_forward_rewind_triangle_16dp" | ||
android:visibility="invisible" /> | ||
|
||
<ImageView | ||
android:id="@+id/fast_forward_rewind_mid_image" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:src="@drawable/ayp_ic_fast_forward_rewind_triangle_16dp" | ||
android:visibility="invisible" /> | ||
|
||
<ImageView | ||
android:id="@+id/fast_forward_rewind_right_image" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:src="@drawable/ayp_ic_fast_forward_rewind_triangle_16dp" | ||
android:visibility="invisible" /> | ||
|
||
</LinearLayout> | ||
|
||
<TextView | ||
android:id="@+id/fast_forward_rewind_text" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center" | ||
|
||
android:padding="8dp" | ||
android:textColor="@android:color/white" | ||
android:textSize="12sp" /> | ||
|
||
</LinearLayout> | ||
</RelativeLayout> |
Oops, something went wrong.