Skip to content

Commit 8405211

Browse files
osamasayed585Bnyro
authored andcommitted
feat: allow drag-to-seek like YouTube
Implement a drag-to-seek gesture on the time bar, similar to the native YouTube player. This change modifies the touch handling to: - Prevent seeking when the time bar is tapped. - Initiate a seek only after the user drags their finger past a certain threshold. - Start the scrub from the current touch position. - Forward touch events to the underlying `DefaultTimeBar` only after a drag is detected.
1 parent 16f7c95 commit 8405211

1 file changed

Lines changed: 69 additions & 3 deletions

File tree

app/src/main/java/com/github/libretube/ui/views/DismissableTimeBar.kt

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import android.annotation.SuppressLint
44
import android.content.Context
55
import android.util.AttributeSet
66
import android.view.MotionEvent
7+
import android.view.ViewConfiguration
78
import androidx.media3.common.Player
89
import androidx.media3.common.util.UnstableApi
910
import androidx.media3.ui.DefaultTimeBar
1011
import androidx.media3.ui.PlayerControlView
1112
import androidx.media3.ui.TimeBar
1213
import androidx.media3.ui.TimeBar.OnScrubListener
1314
import com.github.libretube.extensions.dpToPx
15+
import kotlin.math.abs
1416

1517
@UnstableApi
1618
open class DismissableTimeBar(
@@ -19,7 +21,13 @@ open class DismissableTimeBar(
1921
): DefaultTimeBar(context, attributeSet) {
2022
private var shouldAddListener = false
2123
var exoPlayer: Player? = null
22-
private var lastYPosition = 0f
24+
25+
// Drag-only seeking state
26+
private var initialX: Float = 0f
27+
private var initialY: Float = 0f
28+
private var waitingForDrag: Boolean = false
29+
private var dragStarted: Boolean = false
30+
private val touchSlopPx: Int = ViewConfiguration.get(context).scaledTouchSlop
2331

2432
init {
2533
addSeekBarListener(object : OnScrubListener {
@@ -28,14 +36,72 @@ open class DismissableTimeBar(
2836
override fun onScrubMove(timeBar: TimeBar, position: Long) = Unit
2937

3038
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
31-
if (lastYPosition > MINIMUM_ACCEPTED_HEIGHT.dpToPx()) exoPlayer?.seekTo(position)
39+
if (canceled) return
40+
// Ignore if gesture started too far above the bar (keep original behavior)
41+
if (initialY <= MINIMUM_ACCEPTED_HEIGHT.dpToPx()) return
42+
exoPlayer?.seekTo(position)
3243
}
3344
})
3445
}
3546

3647
@SuppressLint("ClickableViewAccessibility")
3748
override fun onTouchEvent(event: MotionEvent): Boolean {
38-
lastYPosition = event.y
49+
when (event.actionMasked) {
50+
MotionEvent.ACTION_DOWN -> {
51+
initialX = event.x
52+
initialY = event.y
53+
waitingForDrag = true
54+
dragStarted = false
55+
// Consume without forwarding to prevent tap-to-seek or thumb jump
56+
return true
57+
}
58+
59+
MotionEvent.ACTION_MOVE -> {
60+
if (waitingForDrag) {
61+
// make sure that the user dragged at least touchSlopPx pixels
62+
// which is the minimum amount to trigger a drag event
63+
val dx = abs(event.x - initialX)
64+
val dy = abs(event.y - initialY)
65+
if (dx > touchSlopPx || dy > touchSlopPx) {
66+
// Begin scrubbing now by synthesizing a DOWN at the current progress X
67+
val fakeDown = MotionEvent.obtain(event).apply {
68+
action = MotionEvent.ACTION_DOWN
69+
setLocation(event.x, event.y)
70+
}
71+
super.onTouchEvent(fakeDown)
72+
fakeDown.recycle()
73+
74+
waitingForDrag = false
75+
dragStarted = true
76+
} else {
77+
// Still not considered a drag; consume
78+
return true
79+
}
80+
}
81+
82+
// Forward MOVE only after we've started dragging, with relative X
83+
if (dragStarted) {
84+
return super.onTouchEvent(event)
85+
}
86+
return true
87+
}
88+
89+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
90+
// If we started a drag, forward the terminal event to finish scrubbing
91+
if (dragStarted) {
92+
waitingForDrag = false
93+
dragStarted = false
94+
return super.onTouchEvent(event)
95+
}
96+
97+
// It's a tap without drag: do nothing (prevent seek and thumb jump)
98+
waitingForDrag = false
99+
dragStarted = false
100+
// Optionally report click for accessibility without side effects
101+
performClick()
102+
return true
103+
}
104+
}
39105

40106
return super.onTouchEvent(event)
41107
}

0 commit comments

Comments
 (0)