add rwd/ffwd double tap indicator & pause/play on long press
This commit is contained in:
parent
3880b3ab75
commit
256c32aa3c
|
@ -11,7 +11,6 @@ import android.view.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.postDelayed
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
|
@ -23,7 +22,10 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
import com.google.android.exoplayer2.util.Util
|
import com.google.android.exoplayer2.util.Util
|
||||||
import kotlinx.android.synthetic.main.activity_player.*
|
import kotlinx.android.synthetic.main.activity_player.*
|
||||||
import kotlinx.android.synthetic.main.player_controls.*
|
import kotlinx.android.synthetic.main.player_controls.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.ui.fragments.MediaFragment
|
import org.mosad.teapod.ui.fragments.MediaFragment
|
||||||
|
@ -34,7 +36,6 @@ import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.scheduleAtFixedRate
|
import kotlin.concurrent.scheduleAtFixedRate
|
||||||
|
|
||||||
|
|
||||||
class PlayerActivity : AppCompatActivity() {
|
class PlayerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var player: SimpleExoPlayer
|
private lateinit var player: SimpleExoPlayer
|
||||||
|
@ -257,12 +258,52 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
Log.d(javaClass.name, "Released player")
|
Log.d(javaClass.name, "Released player")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO set position of rewind/fast forward indicators programmatically
|
||||||
|
*/
|
||||||
|
|
||||||
private fun rewind() {
|
private fun rewind() {
|
||||||
player.seekTo(player.currentPosition - rwdTime)
|
player.seekTo(player.currentPosition - rwdTime)
|
||||||
|
|
||||||
|
// hide/show needed components
|
||||||
|
exo_double_tap_indicator.visibility = View.VISIBLE
|
||||||
|
ffwd_10_indicator.visibility = View.INVISIBLE
|
||||||
|
ffwd_10.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
rwd_10_indicator.onAnimationEndCallback = {
|
||||||
|
exo_double_tap_indicator.visibility = View.GONE
|
||||||
|
ffwd_10_indicator.visibility = View.VISIBLE
|
||||||
|
ffwd_10.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
// run animation
|
||||||
|
rwd_10_indicator.runOnClickAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun forward() {
|
private fun forward() {
|
||||||
player.seekTo(player.currentPosition + fwdTime)
|
player.seekTo(player.currentPosition + fwdTime)
|
||||||
|
|
||||||
|
// hide/show needed components
|
||||||
|
exo_double_tap_indicator.visibility = View.VISIBLE
|
||||||
|
rwd_10_indicator.visibility = View.INVISIBLE
|
||||||
|
ffwd_10.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
ffwd_10_indicator.onAnimationEndCallback = {
|
||||||
|
exo_double_tap_indicator.visibility = View.GONE
|
||||||
|
rwd_10_indicator.visibility = View.VISIBLE
|
||||||
|
ffwd_10.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
// run animation
|
||||||
|
ffwd_10_indicator.runOnClickAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun togglePausePlay() {
|
||||||
|
if (player.isPlaying) {
|
||||||
|
player.pause()
|
||||||
|
} else {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playNextEpisode() {
|
private fun playNextEpisode() {
|
||||||
|
@ -282,7 +323,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
// watchedCallback for next ep
|
// watchedCallback for next ep
|
||||||
currentEpisode = nextEp // set current ep to next ep
|
currentEpisode = nextEp // set current ep to next ep
|
||||||
episodeId = nextEp.id
|
episodeId = nextEp.id
|
||||||
MediaFragment.instance.updateWatchedState(nextEp) // TODO i don't like this
|
MediaFragment.instance.updateWatchedState(nextEp)
|
||||||
|
|
||||||
nextEpisode = selectNextEpisode()
|
nextEpisode = selectNextEpisode()
|
||||||
}
|
}
|
||||||
|
@ -362,15 +403,14 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
button_next_ep.animate()
|
button_next_ep.animate()
|
||||||
.alpha(0.0f)
|
.alpha(0.0f)
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
super.onAnimationEnd(animation)
|
super.onAnimationEnd(animation)
|
||||||
button_next_ep.visibility = View.GONE
|
button_next_ep.visibility = View.GONE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inner class PlayerGestureListener : GestureDetector.SimpleOnGestureListener() {
|
inner class PlayerGestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -386,22 +426,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
*/
|
*/
|
||||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||||
val eventPosX = e?.x?.toInt() ?: 0
|
val eventPosX = e?.x?.toInt() ?: 0
|
||||||
val eventPosY = e?.y?.toInt() ?: 0
|
|
||||||
val viewCenterX = video_view.measuredWidth / 2
|
val viewCenterX = video_view.measuredWidth / 2
|
||||||
val viewCenterY = video_view.measuredHeight / 2
|
|
||||||
|
|
||||||
// Show ripple effect (Jellyfin Android App) TODO replace this with a netflix player like animation?
|
|
||||||
video_view.foreground?.apply {
|
|
||||||
val left = if (eventPosX < viewCenterX) 0 else viewCenterX
|
|
||||||
val right = if (eventPosX < viewCenterX) viewCenterX else video_view.measuredWidth
|
|
||||||
|
|
||||||
setBounds(left, viewCenterY - viewCenterX / 2, right, viewCenterY + viewCenterX / 2)
|
|
||||||
setHotspot(eventPosX.toFloat(), eventPosY.toFloat())
|
|
||||||
state = intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed)
|
|
||||||
video_view.postDelayed(100) {
|
|
||||||
state = IntArray(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the event position is on the left side rewind, if it's on the right forward
|
// if the event position is on the left side rewind, if it's on the right forward
|
||||||
if (eventPosX < viewCenterX) {
|
if (eventPosX < viewCenterX) {
|
||||||
|
@ -420,6 +445,10 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
|
togglePausePlay()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,24 +10,18 @@ import android.widget.FrameLayout
|
||||||
import kotlinx.android.synthetic.main.button_fast_forward.view.*
|
import kotlinx.android.synthetic.main.button_fast_forward.view.*
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
|
||||||
class FastForwardButton(context: Context, attrs: AttributeSet): FrameLayout(context, attrs) {
|
class FastForwardButton(context: Context, attrs: AttributeSet?): FrameLayout(context, attrs) {
|
||||||
|
|
||||||
private val animationDuration: Long = 800
|
private val animationDuration: Long = 800
|
||||||
|
private val buttonAnimation: ObjectAnimator
|
||||||
|
private val labelAnimation: ObjectAnimator
|
||||||
|
|
||||||
|
var onAnimationEndCallback: (() -> Unit)? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
inflate(context, R.layout.button_fast_forward, this)
|
inflate(context, R.layout.button_fast_forward, this)
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnButtonClickListener(func: FastForwardButton.() -> Unit) {
|
buttonAnimation = ObjectAnimator.ofFloat(imageButton, View.ROTATION, 0f, 50f).apply {
|
||||||
imageButton.setOnClickListener {
|
|
||||||
func()
|
|
||||||
runOnClickAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runOnClickAnimation() {
|
|
||||||
// run button animation
|
|
||||||
ObjectAnimator.ofFloat(imageButton, View.ROTATION, 0f, 50f).apply {
|
|
||||||
duration = animationDuration / 4
|
duration = animationDuration / 4
|
||||||
repeatCount = 1
|
repeatCount = 1
|
||||||
repeatMode = ObjectAnimator.REVERSE
|
repeatMode = ObjectAnimator.REVERSE
|
||||||
|
@ -36,29 +30,39 @@ class FastForwardButton(context: Context, attrs: AttributeSet): FrameLayout(cont
|
||||||
imageButton.isEnabled = false // disable button
|
imageButton.isEnabled = false // disable button
|
||||||
imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_24)
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_24)
|
||||||
}
|
}
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
imageButton.isEnabled = true // enable button
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run lbl animation
|
labelAnimation = ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, 35f).apply {
|
||||||
textView.visibility = View.VISIBLE
|
|
||||||
ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, 35f).apply {
|
|
||||||
duration = animationDuration
|
duration = animationDuration
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
// the label animation takes longer then the button animation, reset stuff in here
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
imageButton.isEnabled = true // enable button
|
imageButton.isEnabled = true // enable button
|
||||||
imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_10_24)
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_10_24)
|
||||||
|
|
||||||
textView.visibility = View.GONE
|
textView.visibility = View.GONE
|
||||||
textView.animate().translationX(0f)
|
textView.animate().translationX(0f)
|
||||||
|
|
||||||
|
onAnimationEndCallback?.invoke()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
start()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnButtonClickListener(func: FastForwardButton.() -> Unit) {
|
||||||
|
imageButton.setOnClickListener {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runOnClickAnimation() {
|
||||||
|
// run button animation
|
||||||
|
buttonAnimation.start()
|
||||||
|
|
||||||
|
// run lbl animation
|
||||||
|
textView.visibility = View.VISIBLE
|
||||||
|
labelAnimation.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,21 +13,15 @@ import org.mosad.teapod.R
|
||||||
class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context, attrs) {
|
class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context, attrs) {
|
||||||
|
|
||||||
private val animationDuration: Long = 800
|
private val animationDuration: Long = 800
|
||||||
|
private val buttonAnimation: ObjectAnimator
|
||||||
|
private val labelAnimation: ObjectAnimator
|
||||||
|
|
||||||
|
var onAnimationEndCallback: (() -> Unit)? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
inflate(context, R.layout.button_rewind, this)
|
inflate(context, R.layout.button_rewind, this)
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnButtonClickListener(func: RewindButton.() -> Unit) {
|
buttonAnimation = ObjectAnimator.ofFloat(imageButton, View.ROTATION, 0f, -50f).apply {
|
||||||
imageButton.setOnClickListener {
|
|
||||||
func()
|
|
||||||
runOnClickAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runOnClickAnimation() {
|
|
||||||
// run button animation
|
|
||||||
ObjectAnimator.ofFloat(imageButton, View.ROTATION, 0f, -50f).apply {
|
|
||||||
duration = animationDuration / 4
|
duration = animationDuration / 4
|
||||||
repeatCount = 1
|
repeatCount = 1
|
||||||
repeatMode = ObjectAnimator.REVERSE
|
repeatMode = ObjectAnimator.REVERSE
|
||||||
|
@ -36,16 +30,10 @@ class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context,
|
||||||
imageButton.isEnabled = false // disable button
|
imageButton.isEnabled = false // disable button
|
||||||
imageButton.setBackgroundResource(R.drawable.ic_baseline_rewind_24)
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_rewind_24)
|
||||||
}
|
}
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
imageButton.isEnabled = true // enable button
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run lbl animation
|
labelAnimation = ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -35f).apply {
|
||||||
textView.visibility = View.VISIBLE
|
|
||||||
ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -35f).apply {
|
|
||||||
duration = animationDuration
|
duration = animationDuration
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
@ -54,11 +42,26 @@ class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context,
|
||||||
|
|
||||||
textView.visibility = View.GONE
|
textView.visibility = View.GONE
|
||||||
textView.animate().translationX(0f)
|
textView.animate().translationX(0f)
|
||||||
|
|
||||||
|
onAnimationEndCallback?.invoke()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
start()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnButtonClickListener(func: RewindButton.() -> Unit) {
|
||||||
|
imageButton.setOnClickListener {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runOnClickAnimation() {
|
||||||
|
// run button animation
|
||||||
|
buttonAnimation.start()
|
||||||
|
|
||||||
|
// run lbl animation
|
||||||
|
textView.visibility = View.VISIBLE
|
||||||
|
labelAnimation.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,10 +2,11 @@
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/player_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:keepScreenOn="true"
|
|
||||||
android:background="#000000"
|
android:background="#000000"
|
||||||
|
android:keepScreenOn="true"
|
||||||
tools:context=".PlayerActivity">
|
tools:context=".PlayerActivity">
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.StyledPlayerView
|
<com.google.android.exoplayer2.ui.StyledPlayerView
|
||||||
|
@ -15,9 +16,9 @@
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
android:foreground="@drawable/ripple_background"
|
android:foreground="@drawable/ripple_background"
|
||||||
|
app:controller_layout_id="@layout/player_controls"
|
||||||
app:fastforward_increment="10000"
|
app:fastforward_increment="10000"
|
||||||
app:rewind_increment="10000"
|
app:rewind_increment="10000" />
|
||||||
app:controller_layout_id="@layout/player_controls"/>
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.ProgressIndicator
|
<com.google.android.material.progressindicator.ProgressIndicator
|
||||||
android:id="@+id/loading"
|
android:id="@+id/loading"
|
||||||
|
@ -28,6 +29,50 @@
|
||||||
app:indicatorColor="@color/exo_white"
|
app:indicatorColor="@color/exo_white"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exo_double_tap_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<org.mosad.teapod.ui.components.RewindButton
|
||||||
|
android:id="@+id/rwd_10_indicator"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<org.mosad.teapod.ui.components.FastForwardButton
|
||||||
|
android:id="@+id/ffwd_10_indicator"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/button_next_ep"
|
android:id="@+id/button_next_ep"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Reference in New Issue