From 6644983154468cdb8661eef14c54fb7e9746297a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 29 Oct 2022 15:11:11 +0200 Subject: [PATCH 1/3] Improve double tap behavior --- .../libretube/util/DoubleTapListener.kt | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/github/libretube/util/DoubleTapListener.kt b/app/src/main/java/com/github/libretube/util/DoubleTapListener.kt index 5a96b4539..b9a6ee815 100644 --- a/app/src/main/java/com/github/libretube/util/DoubleTapListener.kt +++ b/app/src/main/java/com/github/libretube/util/DoubleTapListener.kt @@ -3,40 +3,49 @@ package com.github.libretube.util import android.os.Handler import android.os.Looper import android.os.SystemClock +import android.util.Log import android.view.View abstract class DoubleTapListener : View.OnClickListener { - private val maximumTimeDifference = 300L private val handler = Handler(Looper.getMainLooper()) - private var isSingleEvent = false - private var timeStampLastClick = 0L - private var timeStampLastDoubleClick = 0L - - override fun onClick(v: View?) { - if (SystemClock.elapsedRealtime() - timeStampLastClick < maximumTimeDifference) { - isSingleEvent = false - handler.removeCallbacks(runnable) - timeStampLastDoubleClick = SystemClock.elapsedRealtime() - onDoubleClick() - return - } - isSingleEvent = true - handler.removeCallbacks(runnable) - handler.postDelayed(runnable, maximumTimeDifference) - timeStampLastClick = SystemClock.elapsedRealtime() - } + private var lastClick = 0L + private var lastDoubleClick = 0L abstract fun onDoubleClick() abstract fun onSingleClick() - private val runnable = Runnable { - if (!isSingleEvent || - SystemClock.elapsedRealtime() - timeStampLastDoubleClick < maximumTimeDifference - ) { - return@Runnable + override fun onClick(v: View?) { + if (isSecondClick()) { + handler.removeCallbacks(runnable) + lastDoubleClick = elapsedTime() + onDoubleClick() + } else { + if (recentDoubleClick()) return + handler.removeCallbacks(runnable) + handler.postDelayed(runnable, MAX_TIME_DIFF) + lastClick = elapsedTime() } + } + + private val runnable = Runnable { + if (isSecondClick()) return@Runnable + Log.e("single", "single") onSingleClick() } + + private fun isSecondClick(): Boolean { + return elapsedTime() - lastClick < MAX_TIME_DIFF + } + + private fun recentDoubleClick(): Boolean { + return elapsedTime() - lastDoubleClick < MAX_TIME_DIFF + } + + fun elapsedTime() = SystemClock.elapsedRealtime() + + companion object { + private const val MAX_TIME_DIFF = 400L + } } From ae3f48fc45b8f18ecf7cb14f69f261b03fdad473 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 29 Oct 2022 15:16:54 +0200 Subject: [PATCH 2/3] player fragment cleanup --- .../libretube/ui/fragments/PlayerFragment.kt | 142 +++++++++--------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index c91f3c5e4..a2a1a2562 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -105,7 +105,7 @@ import java.io.IOException import java.util.concurrent.Executors import kotlin.math.abs -class PlayerFragment : BaseFragment() { +class PlayerFragment : BaseFragment(), PlayerOptionsInterface { lateinit var binding: FragmentPlayerBinding private lateinit var playerBinding: ExoStyledPlayerControlViewBinding @@ -296,76 +296,6 @@ class PlayerFragment : BaseFragment() { } } - private val onlinePlayerOptionsInterface = object : PlayerOptionsInterface { - override fun onCaptionClicked() { - if (!this@PlayerFragment::streams.isInitialized || - streams.subtitles == null || - streams.subtitles!!.isEmpty() - ) { - Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show() - return - } - - val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!) - val subtitleCodesList = mutableListOf("") - streams.subtitles!!.forEach { - subtitlesNamesList += it.name!! - subtitleCodesList += it.code!! - } - - BottomSheet() - .setSimpleItems(subtitlesNamesList) { index -> - val newParams = if (index != 0) { - // caption selected - - // get the caption language code - val captionLanguageCode = subtitleCodesList[index] - - // select the new caption preference - trackSelector.buildUponParameters() - .setPreferredTextLanguage(captionLanguageCode) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - } else { - // none selected - // disable captions - trackSelector.buildUponParameters() - .setPreferredTextLanguage("") - } - - // set the new caption language - trackSelector.setParameters(newParams) - } - .show(childFragmentManager) - } - - override fun onQualityClicked() { - // get the available resolutions - val (videosNameArray, videosUrlArray) = getAvailableResolutions() - - // Dialog for quality selection - val lastPosition = exoPlayer.currentPosition - BottomSheet() - .setSimpleItems( - videosNameArray.toList() - ) { which -> - if ( - videosNameArray[which] == getString(R.string.hls) || - videosNameArray[which] == "LBRY HLS" - ) { - // set the progressive media source - setHLSMediaSource(videosUrlArray[which]) - } else { - val videoUri = videosUrlArray[which] - val audioUrl = - PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!) - setMediaSource(videoUri, audioUrl) - } - exoPlayer.seekTo(lastPosition) - } - .show(childFragmentManager) - } - } - // actions that don't depend on video information private fun initializeOnClickActions() { binding.closeImageView.setOnClickListener { @@ -814,7 +744,7 @@ class PlayerFragment : BaseFragment() { // initialize the player view actions binding.player.initialize( childFragmentManager, - onlinePlayerOptionsInterface, + this, doubleTapOverlayBinding, trackSelector ) @@ -1389,6 +1319,74 @@ class PlayerFragment : BaseFragment() { } } + override fun onCaptionClicked() { + if (!this@PlayerFragment::streams.isInitialized || + streams.subtitles == null || + streams.subtitles!!.isEmpty() + ) { + Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show() + return + } + + val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!) + val subtitleCodesList = mutableListOf("") + streams.subtitles!!.forEach { + subtitlesNamesList += it.name!! + subtitleCodesList += it.code!! + } + + BottomSheet() + .setSimpleItems(subtitlesNamesList) { index -> + val newParams = if (index != 0) { + // caption selected + + // get the caption language code + val captionLanguageCode = subtitleCodesList[index] + + // select the new caption preference + trackSelector.buildUponParameters() + .setPreferredTextLanguage(captionLanguageCode) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + } else { + // none selected + // disable captions + trackSelector.buildUponParameters() + .setPreferredTextLanguage("") + } + + // set the new caption language + trackSelector.setParameters(newParams) + } + .show(childFragmentManager) + } + + override fun onQualityClicked() { + // get the available resolutions + val (videosNameArray, videosUrlArray) = getAvailableResolutions() + + // Dialog for quality selection + val lastPosition = exoPlayer.currentPosition + BottomSheet() + .setSimpleItems( + videosNameArray.toList() + ) { which -> + if ( + videosNameArray[which] == getString(R.string.hls) || + videosNameArray[which] == "LBRY HLS" + ) { + // set the progressive media source + setHLSMediaSource(videosUrlArray[which]) + } else { + val videoUri = videosUrlArray[which] + val audioUrl = + PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!) + setMediaSource(videoUri, audioUrl) + } + exoPlayer.seekTo(lastPosition) + } + .show(childFragmentManager) + } + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { super.onPictureInPictureModeChanged(isInPictureInPictureMode) if (isInPictureInPictureMode) { From c8c2ab02e21fb9ef06d9fbfedf57d261974ffd26 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 29 Oct 2022 15:33:06 +0200 Subject: [PATCH 3/3] player view cleanup --- ...onsInterface.kt => OnlinePlayerOptions.kt} | 4 +- .../models/interfaces/PlayerOptions.kt | 11 ++ .../github/libretube/obj/BottomSheetItem.kt | 3 +- .../ui/adapters/BottomSheetAdapter.kt | 1 + .../libretube/ui/fragments/PlayerFragment.kt | 6 +- .../github/libretube/ui/views/BottomSheet.kt | 8 +- .../libretube/ui/views/CustomExoPlayerView.kt | 156 +++++++++--------- 7 files changed, 102 insertions(+), 87 deletions(-) rename app/src/main/java/com/github/libretube/models/interfaces/{PlayerOptionsInterface.kt => OnlinePlayerOptions.kt} (55%) create mode 100644 app/src/main/java/com/github/libretube/models/interfaces/PlayerOptions.kt diff --git a/app/src/main/java/com/github/libretube/models/interfaces/PlayerOptionsInterface.kt b/app/src/main/java/com/github/libretube/models/interfaces/OnlinePlayerOptions.kt similarity index 55% rename from app/src/main/java/com/github/libretube/models/interfaces/PlayerOptionsInterface.kt rename to app/src/main/java/com/github/libretube/models/interfaces/OnlinePlayerOptions.kt index 66d3d87d9..10a354921 100644 --- a/app/src/main/java/com/github/libretube/models/interfaces/PlayerOptionsInterface.kt +++ b/app/src/main/java/com/github/libretube/models/interfaces/OnlinePlayerOptions.kt @@ -1,7 +1,7 @@ package com.github.libretube.models.interfaces -interface PlayerOptionsInterface { - fun onCaptionClicked() +interface OnlinePlayerOptions { + fun onCaptionsClicked() fun onQualityClicked() } diff --git a/app/src/main/java/com/github/libretube/models/interfaces/PlayerOptions.kt b/app/src/main/java/com/github/libretube/models/interfaces/PlayerOptions.kt new file mode 100644 index 000000000..fe2a5db67 --- /dev/null +++ b/app/src/main/java/com/github/libretube/models/interfaces/PlayerOptions.kt @@ -0,0 +1,11 @@ +package com.github.libretube.models.interfaces + +interface PlayerOptions { + fun onAutoplayClicked() + + fun onPlaybackSpeedClicked() + + fun onResizeModeClicked() + + fun onRepeatModeClicked() +} diff --git a/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt b/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt index d9614e5f8..dad4c43f8 100644 --- a/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt +++ b/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt @@ -3,5 +3,6 @@ package com.github.libretube.obj data class BottomSheetItem( val title: String, val drawable: Int? = null, - val currentValue: String? = null + val currentValue: String? = null, + val onClick: () -> Unit = {} ) diff --git a/app/src/main/java/com/github/libretube/ui/adapters/BottomSheetAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/BottomSheetAdapter.kt index e694e8f5a..3b1b92a57 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/BottomSheetAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/BottomSheetAdapter.kt @@ -34,6 +34,7 @@ class BottomSheetAdapter( } root.setOnClickListener { + item.onClick.invoke() listener.invoke(position) } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index a2a1a2562..231a8f055 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -56,7 +56,7 @@ import com.github.libretube.extensions.query import com.github.libretube.extensions.toID import com.github.libretube.extensions.toStreamItem import com.github.libretube.models.PlayerViewModel -import com.github.libretube.models.interfaces.PlayerOptionsInterface +import com.github.libretube.models.interfaces.OnlinePlayerOptions import com.github.libretube.services.BackgroundMode import com.github.libretube.services.DownloadService import com.github.libretube.ui.activities.MainActivity @@ -105,7 +105,7 @@ import java.io.IOException import java.util.concurrent.Executors import kotlin.math.abs -class PlayerFragment : BaseFragment(), PlayerOptionsInterface { +class PlayerFragment : BaseFragment(), OnlinePlayerOptions { lateinit var binding: FragmentPlayerBinding private lateinit var playerBinding: ExoStyledPlayerControlViewBinding @@ -1319,7 +1319,7 @@ class PlayerFragment : BaseFragment(), PlayerOptionsInterface { } } - override fun onCaptionClicked() { + override fun onCaptionsClicked() { if (!this@PlayerFragment::streams.isInitialized || streams.subtitles == null || streams.subtitles!!.isEmpty() diff --git a/app/src/main/java/com/github/libretube/ui/views/BottomSheet.kt b/app/src/main/java/com/github/libretube/ui/views/BottomSheet.kt index 3fd8268cf..af1890c2e 100644 --- a/app/src/main/java/com/github/libretube/ui/views/BottomSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/views/BottomSheet.kt @@ -32,18 +32,18 @@ open class BottomSheet : BottomSheetDialogFragment() { binding.optionsRecycler.adapter = BottomSheetAdapter(items, listener) } - fun setItems(items: List, listener: (index: Int) -> Unit) = apply { + fun setItems(items: List, listener: ((index: Int) -> Unit)?) = apply { this.items = items this.listener = { index -> - listener.invoke(index) + listener?.invoke(index) dialog?.dismiss() } } - fun setSimpleItems(titles: List, listener: (index: Int) -> Unit) = apply { + fun setSimpleItems(titles: List, listener: ((index: Int) -> Unit)?) = apply { this.items = titles.map { BottomSheetItem(it) } this.listener = { index -> - listener.invoke(index) + listener?.invoke(index) dialog?.dismiss() } } diff --git a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt index f1a887f41..bebb7d4e7 100644 --- a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt @@ -14,7 +14,8 @@ import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.extensions.toDp import com.github.libretube.models.interfaces.DoubleTapInterface -import com.github.libretube.models.interfaces.PlayerOptionsInterface +import com.github.libretube.models.interfaces.OnlinePlayerOptions +import com.github.libretube.models.interfaces.PlayerOptions import com.github.libretube.obj.BottomSheetItem import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.sheets.PlaybackSpeedSheet @@ -30,7 +31,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil internal class CustomExoPlayerView( context: Context, attributeSet: AttributeSet? = null -) : StyledPlayerView(context, attributeSet) { +) : StyledPlayerView(context, attributeSet), PlayerOptions { val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this) private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null @@ -38,7 +39,7 @@ internal class CustomExoPlayerView( * Objects from the parent fragment */ private var doubleTapListener: DoubleTapInterface? = null - private var playerOptionsInterface: PlayerOptionsInterface? = null + private var playerOptionsInterface: OnlinePlayerOptions? = null private lateinit var childFragmentManager: FragmentManager private var trackSelector: TrackSelector? = null @@ -72,7 +73,7 @@ internal class CustomExoPlayerView( fun initialize( childFragmentManager: FragmentManager, - playerViewInterface: PlayerOptionsInterface?, + playerViewInterface: OnlinePlayerOptions?, doubleTapOverlayBinding: DoubleTapOverlayBinding, trackSelector: TrackSelector? ) { @@ -136,78 +137,79 @@ internal class CustomExoPlayerView( private fun initializeAdvancedOptions(context: Context) { binding.toggleOptions.setOnClickListener { - val bottomSheetFragment = BottomSheet().apply { - val items = mutableListOf( - BottomSheetItem( - context.getString(R.string.player_autoplay), - R.drawable.ic_play, - if (autoplayEnabled) { - context.getString(R.string.enabled) - } else { - context.getString(R.string.disabled) - } - ), - BottomSheetItem( - context.getString(R.string.repeat_mode), - R.drawable.ic_repeat, - if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { - context.getString(R.string.repeat_mode_none) - } else { - context.getString(R.string.repeat_mode_current) - } - ), - BottomSheetItem( - context.getString(R.string.player_resize_mode), - R.drawable.ic_aspect_ratio, - when (resizeMode) { - AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.resize_mode_fit) - AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.resize_mode_fill) - else -> context.getString(R.string.resize_mode_zoom) - } - ), - BottomSheetItem( - context.getString(R.string.playback_speed), - R.drawable.ic_speed, - "${ - player?.playbackParameters?.speed - .toString() - .replace(".0", "") - }x" - ) - ) - - if (playerOptionsInterface != null) { - items.add( - BottomSheetItem( - context.getString(R.string.quality), - R.drawable.ic_hd, - "${player?.videoSize?.height}p" - ) - ) - items.add( - BottomSheetItem( - context.getString(R.string.captions), - R.drawable.ic_caption, - if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) { - trackSelector!!.parameters.preferredTextLanguages[0] - } else { - context.getString(R.string.none) - } - ) - ) - } - - setItems(items) { index -> - when (index) { - 0 -> onAutoplayClicked() - 1 -> onRepeatModeClicked() - 2 -> onResizeModeClicked() - 3 -> onPlaybackSpeedClicked() - 4 -> playerOptionsInterface?.onQualityClicked() - 5 -> playerOptionsInterface?.onCaptionClicked() + val items = mutableListOf( + BottomSheetItem( + context.getString(R.string.player_autoplay), + R.drawable.ic_play, + if (autoplayEnabled) { + context.getString(R.string.enabled) + } else { + context.getString(R.string.disabled) } + ) { + onAutoplayClicked() + }, + BottomSheetItem( + context.getString(R.string.repeat_mode), + R.drawable.ic_repeat, + if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { + context.getString(R.string.repeat_mode_none) + } else { + context.getString(R.string.repeat_mode_current) + } + ) { + onRepeatModeClicked() + }, + BottomSheetItem( + context.getString(R.string.player_resize_mode), + R.drawable.ic_aspect_ratio, + when (resizeMode) { + AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.resize_mode_fit) + AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.resize_mode_fill) + else -> context.getString(R.string.resize_mode_zoom) + } + ) { + onResizeModeClicked() + }, + BottomSheetItem( + context.getString(R.string.playback_speed), + R.drawable.ic_speed, + "${ + player?.playbackParameters?.speed + .toString() + .replace(".0", "") + }x" + ) { + onPlaybackSpeedClicked() } + ) + + if (playerOptionsInterface != null) { + items.add( + BottomSheetItem( + context.getString(R.string.quality), + R.drawable.ic_hd, + "${player?.videoSize?.height}p" + ) { + playerOptionsInterface?.onQualityClicked() + } + ) + items.add( + BottomSheetItem( + context.getString(R.string.captions), + R.drawable.ic_caption, + if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) { + trackSelector!!.parameters.preferredTextLanguages[0] + } else { + context.getString(R.string.none) + } + ) { + playerOptionsInterface?.onCaptionsClicked() + } + ) } + + val bottomSheetFragment = BottomSheet().setItems(items, null) bottomSheetFragment.show(childFragmentManager, null) } } @@ -307,7 +309,7 @@ internal class CustomExoPlayerView( } } - private fun onAutoplayClicked() { + override fun onAutoplayClicked() { // autoplay options dialog BottomSheet() .setSimpleItems( @@ -324,11 +326,11 @@ internal class CustomExoPlayerView( .show(childFragmentManager) } - private fun onPlaybackSpeedClicked() { + override fun onPlaybackSpeedClicked() { player?.let { PlaybackSpeedSheet(it).show(childFragmentManager) } } - private fun onResizeModeClicked() { + override fun onResizeModeClicked() { // switching between original aspect ratio (black bars) and zoomed to fill device screen val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode) ?.toList().orEmpty() @@ -346,7 +348,7 @@ internal class CustomExoPlayerView( .show(childFragmentManager) } - private fun onRepeatModeClicked() { + override fun onRepeatModeClicked() { val repeatModeNames = listOf( context.getString(R.string.repeat_mode_none), context.getString(R.string.repeat_mode_current)