@@ -10,14 +10,17 @@ import android.os.Bundle
1010import android.os.Handler
1111import android.util.DisplayMetrics
1212import android.view.GestureDetector
13+ import android.view.HapticFeedbackConstants
1314import android.view.LayoutInflater
1415import android.view.MotionEvent
1516import android.view.Surface
1617import android.view.TextureView
1718import android.view.View
19+ import android.view.ViewConfiguration
1820import android.view.ViewGroup
1921import android.view.WindowManager
2022import android.widget.ImageView
23+ import android.widget.RelativeLayout
2124import android.widget.SeekBar
2225import android.widget.TextView
2326import androidx.appcompat.content.res.AppCompatResources
@@ -66,6 +69,7 @@ import org.fossify.gallery.activities.BaseViewerActivity
6669import org.fossify.gallery.activities.VideoActivity
6770import org.fossify.gallery.databinding.PagerVideoItemBinding
6871import org.fossify.gallery.extensions.config
72+ import org.fossify.gallery.extensions.getActionBarHeight
6973import org.fossify.gallery.extensions.getBottomActionsHeight
7074import org.fossify.gallery.extensions.getFormattedDuration
7175import org.fossify.gallery.extensions.getFriendlyMessage
@@ -84,13 +88,17 @@ import org.fossify.gallery.views.MediaSideScroll
8488import java.io.File
8589import java.io.FileInputStream
8690import java.text.DecimalFormat
91+ import kotlin.math.abs
8792
8893@UnstableApi
8994class VideoFragment : ViewPagerFragment (), TextureView.SurfaceTextureListener,
9095 SeekBar .OnSeekBarChangeListener , PlaybackSpeedListener {
9196 companion object {
9297 private const val PROGRESS = " progress"
9398 private const val UPDATE_INTERVAL_MS = 250L
99+ private const val TOUCH_HOLD_DURATION_MS = 500L
100+ private const val TOUCH_HOLD_SPEED_MULTIPLIER = 2.0f
101+ private const val TOUCH_SLOP_DIVIDER = 3
94102 }
95103
96104 private var mIsFullscreen = false
@@ -118,6 +126,19 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
118126 private var mStoredBottomActions = true
119127 private var mStoredExtendedDetails = 0
120128 private var mStoredRememberLastVideoPosition = false
129+ private var mOriginalPlaybackSpeed = 1f
130+ private var mIsLongPressActive = false
131+
132+ private val mTouchHoldRunnable = Runnable {
133+ mView.parent.requestDisallowInterceptTouchEvent(true )
134+ // This code runs after the delay, only if the user is still holding down.
135+ mIsLongPressActive = true
136+ mOriginalPlaybackSpeed = mExoPlayer?.playbackParameters?.speed ? : mConfig.playbackSpeed
137+ mView.performHapticFeedback(HapticFeedbackConstants .LONG_PRESS )
138+ updatePlaybackSpeed(TOUCH_HOLD_SPEED_MULTIPLIER )
139+
140+ mPlaybackSpeedPill.fadeIn()
141+ }
121142
122143 private lateinit var mTimeHolder: View
123144 private lateinit var mBrightnessSideScroll: MediaSideScroll
@@ -130,6 +151,10 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
130151 private lateinit var mCurrTimeView: TextView
131152 private lateinit var mPlayPauseButton: ImageView
132153 private lateinit var mSeekBar: SeekBar
154+ private lateinit var mPlaybackSpeedPill: TextView
155+ private var mTouchSlop = 0
156+ private var mInitialX = 0f
157+ private var mInitialY = 0f
133158
134159 override fun onCreateView (
135160 inflater : LayoutInflater ,
@@ -142,6 +167,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
142167
143168 mMedium = arguments.getSerializable(MEDIUM ) as Medium
144169 mConfig = context.config
170+ mTouchSlop = (ViewConfiguration .get(context).scaledTouchSlop) / TOUCH_SLOP_DIVIDER
145171 binding = PagerVideoItemBinding .inflate(inflater, container, false ).apply {
146172 panoramaOutline.setOnClickListener { openPanorama() }
147173 bottomVideoTimeHolder.videoCurrTime.setOnClickListener { skip(false ) }
@@ -170,6 +196,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
170196 }
171197
172198 mSeekBar = bottomVideoTimeHolder.videoSeekbar
199+ mPlaybackSpeedPill = playbackSpeedPill
173200 mSeekBar.setOnSeekBarChangeListener(this @VideoFragment)
174201 // adding an empty click listener just to avoid ripple animation at toggling fullscreen
175202 mSeekBar.setOnClickListener { }
@@ -178,6 +205,12 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
178205 mCurrTimeView = bottomVideoTimeHolder.videoCurrTime
179206 mBrightnessSideScroll = videoBrightnessController
180207 mVolumeSideScroll = videoVolumeController
208+ mBrightnessSideScroll.onVerticalScroll = {
209+ mTimerHandler.removeCallbacks(mTouchHoldRunnable)
210+ }
211+ mVolumeSideScroll.onVerticalScroll = {
212+ mTimerHandler.removeCallbacks(mTouchHoldRunnable)
213+ }
181214 mTextureView = videoSurface
182215 mTextureView.surfaceTextureListener = this @VideoFragment
183216
@@ -215,6 +248,10 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
215248 if (videoSurfaceFrame.controller.state.zoom == 1f ) {
216249 handleEvent(event)
217250 }
251+ handleTouchHoldEvent(event)
252+ if (mIsLongPressActive) {
253+ return @setOnTouchListener true
254+ }
218255
219256 gestureDetector.onTouchEvent(event)
220257 false
@@ -223,6 +260,15 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
223260
224261 ViewCompat .setOnApplyWindowInsetsListener(binding.videoHolder) { _, insets ->
225262 val system = insets.getInsetsIgnoringVisibility(Type .systemBars())
263+
264+ val pillTopMargin = system.top + resources.getActionBarHeight(context) +
265+ resources.getDimension(org.fossify.commons.R .dimen.normal_margin).toInt()
266+ (mPlaybackSpeedPill.layoutParams as ? RelativeLayout .LayoutParams )?.apply {
267+ setMargins(
268+ 0 , pillTopMargin, 0 , 0
269+ )
270+ }
271+
226272 binding.bottomActionsDummy.updateLayoutParams<ViewGroup .LayoutParams > {
227273 height = resources.getBottomActionsHeight() + system.bottom
228274 }
@@ -288,7 +334,6 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
288334 doubleTap = { x, y ->
289335 doSkip(false )
290336 })
291-
292337 mVolumeSideScroll.initialize(
293338 activity,
294339 slideInfo,
@@ -942,4 +987,43 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener,
942987 mTextureView.layoutParams = this
943988 }
944989 }
990+
991+ private fun handleTouchHoldEvent (event : MotionEvent ) {
992+ when (event.actionMasked) {
993+ MotionEvent .ACTION_DOWN -> {
994+ if (mIsPlaying && event.pointerCount == 1 ) {
995+ mInitialX = event.x
996+ mInitialY = event.y
997+ mTimerHandler.postDelayed(mTouchHoldRunnable, TOUCH_HOLD_DURATION_MS )
998+ }
999+ }
1000+
1001+ MotionEvent .ACTION_MOVE -> {
1002+ val deltaX = abs(event.x - mInitialX)
1003+ val deltaY = abs(event.y - mInitialY)
1004+ if (! mIsLongPressActive && (deltaX > mTouchSlop || deltaY > mTouchSlop)) {
1005+ mTimerHandler.removeCallbacks(mTouchHoldRunnable)
1006+ }
1007+ }
1008+
1009+ MotionEvent .ACTION_POINTER_DOWN -> {
1010+ if (! mIsLongPressActive) {
1011+ mTimerHandler.removeCallbacks(mTouchHoldRunnable)
1012+ }
1013+ }
1014+
1015+ MotionEvent .ACTION_UP , MotionEvent .ACTION_CANCEL -> {
1016+ mTimerHandler.removeCallbacks(mTouchHoldRunnable)
1017+ stopHoldSpeedMultiplierGesture()
1018+ }
1019+ }
1020+ }
1021+
1022+ private fun stopHoldSpeedMultiplierGesture () {
1023+ if (mIsLongPressActive) {
1024+ updatePlaybackSpeed(mOriginalPlaybackSpeed)
1025+ mIsLongPressActive = false
1026+ mPlaybackSpeedPill.fadeOut()
1027+ }
1028+ }
9451029}
0 commit comments