diff --git a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt index 8f35ddb08..d3e099a61 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt @@ -46,8 +46,6 @@ import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.SubscriptionHelper import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys -import com.github.libretube.constants.PreferenceRanges -import com.github.libretube.databinding.DialogSliderBinding import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.FragmentPlayerBinding @@ -61,10 +59,8 @@ import com.github.libretube.extensions.TAG import com.github.libretube.extensions.await import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.hideKeyboard -import com.github.libretube.extensions.setSliderRangeAndValue import com.github.libretube.extensions.toID -import com.github.libretube.interfaces.DoubleTapInterface -import com.github.libretube.interfaces.PlayerOptionsInterface +import com.github.libretube.interfaces.OnlinePlayerOptionsInterface import com.github.libretube.models.PlayerViewModel import com.github.libretube.obj.ChapterSegment import com.github.libretube.obj.Segment @@ -77,7 +73,6 @@ import com.github.libretube.util.ImageHelper import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.PlayerHelper import com.github.libretube.util.PreferenceHelper -import com.github.libretube.views.PlayerOptionsBottomSheet import com.google.android.exoplayer2.C import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.ExoPlayer @@ -92,13 +87,11 @@ import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.MergingMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.trackselection.DefaultTrackSelector -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.CaptionStyleCompat import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DefaultDataSource import com.google.android.exoplayer2.upstream.DefaultHttpDataSource -import com.google.android.exoplayer2.util.RepeatModeUtil import com.google.android.exoplayer2.video.VideoSize import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.CoroutineScope @@ -153,7 +146,6 @@ class PlayerFragment : BaseFragment() { * for the player view */ private lateinit var exoPlayerView: StyledPlayerView - private var isPlayerLocked: Boolean = false private var subtitle = mutableListOf() /** @@ -161,7 +153,6 @@ class PlayerFragment : BaseFragment() { */ private var token = "" private var relatedStreamsEnabled = true - private var autoplayEnabled = false private var autoRotationEnabled = true private var playbackSpeed = "1F" private var pausePlayerOnScreenOffEnabled = false @@ -169,7 +160,6 @@ class PlayerFragment : BaseFragment() { private var watchHistoryEnabled = true private var watchPositionsEnabled = true private var useSystemCaptionStyle = true - private var seekIncrement = 5L private var videoFormatPreference = "webm" private var defRes = "" private var bufferingGoal = 50000 @@ -178,7 +168,6 @@ class PlayerFragment : BaseFragment() { private var sponsorBlockNotifications = true private var skipButtonsEnabled = false private var pipEnabled = true - private var resizeModePref = "fit" /** * for autoplay @@ -245,7 +234,7 @@ class PlayerFragment : BaseFragment() { * somehow the bottom bar is invisible on low screen resolutions, this fixes it */ private fun showBottomBar() { - if (this::playerBinding.isInitialized && !isPlayerLocked) { + if (this::playerBinding.isInitialized && !binding.player.isPlayerLocked) { playerBinding.exoBottomBar.visibility = View.VISIBLE } Handler(Looper.getMainLooper()).postDelayed(this::showBottomBar, 100) @@ -260,11 +249,7 @@ class PlayerFragment : BaseFragment() { false ) - // save whether related streams and autoplay are enabled - autoplayEnabled = PreferenceHelper.getBoolean( - PreferenceKeys.AUTO_PLAY, - false - ) + // save whether related streams are enabled relatedStreamsEnabled = PreferenceHelper.getBoolean( PreferenceKeys.RELATED_STREAMS, true @@ -300,11 +285,6 @@ class PlayerFragment : BaseFragment() { true ) - seekIncrement = PreferenceHelper.getString( - PreferenceKeys.SEEK_INCREMENT, - "5" - ).toLong() * 1000 - videoFormatPreference = PreferenceHelper.getString( PreferenceKeys.PLAYER_VIDEO_FORMAT, "webm" @@ -348,11 +328,6 @@ class PlayerFragment : BaseFragment() { PreferenceKeys.PICTURE_IN_PICTURE, true ) - - resizeModePref = PreferenceHelper.getString( - PreferenceKeys.PLAYER_RESIZE_MODE, - "fit" - ) } @SuppressLint("ClickableViewAccessibility") @@ -424,25 +399,7 @@ class PlayerFragment : BaseFragment() { } } - private val playerOptionsInterface = object : PlayerOptionsInterface { - override fun onAutoplayClicked() { - // autoplay options dialog - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.player_autoplay) - .setItems( - arrayOf( - context?.getString(R.string.enabled), - context?.getString(R.string.disabled) - ) - ) { _, index -> - when (index) { - 0 -> autoplayEnabled = true - 1 -> autoplayEnabled = false - } - } - .show() - } - + private val onlinePlayerOptionsInterface = object : OnlinePlayerOptionsInterface { override fun onCaptionClicked() { if (!this@PlayerFragment::streams.isInitialized || streams.subtitles == null || @@ -515,62 +472,6 @@ class PlayerFragment : BaseFragment() { } .show() } - - override fun onPlaybackSpeedClicked() { - val playbackSpeedBinding = DialogSliderBinding.inflate(layoutInflater) - playbackSpeedBinding.slider.setSliderRangeAndValue( - PreferenceRanges.playbackSpeed - ) - playbackSpeedBinding.slider.value = exoPlayer.playbackParameters.speed - // change playback speed dialog - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.change_playback_speed) - .setView(playbackSpeedBinding.root) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.okay) { _, _ -> - exoPlayer.setPlaybackSpeed( - playbackSpeedBinding.slider.value - ) - } - .show() - } - - override fun onResizeModeClicked() { - // switching between original aspect ratio (black bars) and zoomed to fill device screen - val aspectRatioModeNames = context?.resources?.getStringArray(R.array.resizeMode) - - val aspectRatioModes = arrayOf( - AspectRatioFrameLayout.RESIZE_MODE_FIT, - AspectRatioFrameLayout.RESIZE_MODE_ZOOM, - AspectRatioFrameLayout.RESIZE_MODE_FILL - ) - - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.aspect_ratio) - .setItems(aspectRatioModeNames) { _, index -> - exoPlayerView.resizeMode = aspectRatioModes[index] - } - .show() - } - - override fun onRepeatModeClicked() { - val repeatModeNames = arrayOf( - context?.getString(R.string.repeat_mode_none), - context?.getString(R.string.repeat_mode_current) - ) - - val repeatModes = arrayOf( - RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL, - RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE - ) - // repeat mode options dialog - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.repeat_mode) - .setItems(repeatModeNames) { _, index -> - exoPlayer.repeatMode = repeatModes[index] - } - .show() - } } // actions that don't depend on video information @@ -591,55 +492,6 @@ class PlayerFragment : BaseFragment() { .remove(this) .commit() } - // show the advanced player options - playerBinding.toggleOptions.setOnClickListener { - val bottomSheetFragment = PlayerOptionsBottomSheet().apply { - setOnClickListeners(playerOptionsInterface) - // set the auto play mode - currentAutoplayMode = if (autoplayEnabled) { - context.getString(R.string.enabled) - } else { - context.getString(R.string.disabled) - } - // set the current caption language - currentCaptions = - if (trackSelector.parameters.preferredTextLanguages.isNotEmpty()) { - trackSelector.parameters.preferredTextLanguages[0] - } else { - context.getString(R.string.none) - } - // set the playback speed - currentPlaybackSpeed = "${ - exoPlayer.playbackParameters.speed.toString() - .replace(".0", "") - }x" - // set the quality text - val isAdaptive = exoPlayer.videoFormat?.codecs != null - val quality = exoPlayer.videoSize.height - if (quality != 0) { - currentQuality = - if (isAdaptive) { - "${context.getString(R.string.hls)} • ${quality}p" - } else { - "${quality}p" - } - } - // set the repeat mode - currentRepeatMode = - if (exoPlayer.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { - context.getString(R.string.repeat_mode_none) - } else { - context.getString(R.string.repeat_mode_current) - } - // set the aspect ratio mode - currentResizeMode = when (exoPlayerView.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) - } - } - bottomSheetFragment.show(childFragmentManager, null) - } binding.playImageView.setOnClickListener { if (!exoPlayer.isPlaying) { @@ -677,22 +529,6 @@ class PlayerFragment : BaseFragment() { } } - // lock and unlock the player - playerBinding.lockPlayer.setOnClickListener { - // change the locked/unlocked icon - if (!isPlayerLocked) { - playerBinding.lockPlayer.setImageResource(R.drawable.ic_locked) - } else { - playerBinding.lockPlayer.setImageResource(R.drawable.ic_unlocked) - } - - // show/hide all the controls - lockPlayer(isPlayerLocked) - - // change locked status - isPlayerLocked = !isPlayerLocked - } - // set default playback speed exoPlayer.setPlaybackSpeed(playbackSpeed.toFloat()) @@ -919,7 +755,7 @@ class PlayerFragment : BaseFragment() { // show comments if related streams disabled if (!relatedStreamsEnabled) toggleComments() // prepare for autoplay - if (autoplayEnabled) setNextStream() + if (binding.player.autoplayEnabled) setNextStream() // add the video to the watch history if (watchHistoryEnabled) DatabaseHelper.addToWatchHistory(videoId!!, streams) @@ -1023,11 +859,6 @@ class PlayerFragment : BaseFragment() { controllerHideOnTouch = true useController = false player = exoPlayer - resizeMode = when (resizeModePref) { - "fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL - "zoom" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM - else -> AspectRatioFrameLayout.RESIZE_MODE_FIT - } } if (useSystemCaptionStyle) { @@ -1049,6 +880,14 @@ class PlayerFragment : BaseFragment() { @SuppressLint("SetTextI18n") private fun initializePlayerView(response: Streams) { + // initialize the player view actions + binding.player.initialize( + childFragmentManager, + onlinePlayerOptionsInterface, + doubleTapOverlayBinding, + trackSelector + ) + binding.apply { playerViewsInfo.text = context?.getString(R.string.views, response.views.formatShort()) + @@ -1073,8 +912,6 @@ class PlayerFragment : BaseFragment() { playerBinding.exoTitle.text = response.title - enableDoubleTapToSeek() - // init the chapters recyclerview if (response.chapters != null) { chapters = response.chapters @@ -1120,11 +957,11 @@ class PlayerFragment : BaseFragment() { playbackState == Player.STATE_ENDED && nextStreamId != null && !transitioning && - autoplayEnabled + binding.player.autoplayEnabled ) { transitioning = true // check whether autoplay is enabled - if (autoplayEnabled) playNextVideo() + if (binding.player.autoplayEnabled) playNextVideo() } if (playbackState == Player.STATE_READY) { @@ -1250,83 +1087,6 @@ class PlayerFragment : BaseFragment() { } } - private fun enableDoubleTapToSeek() { - // set seek increment text - val seekIncrementText = (seekIncrement / 1000).toString() - doubleTapOverlayBinding.rewindTV.text = seekIncrementText - doubleTapOverlayBinding.forwardTV.text = seekIncrementText - binding.player.setOnDoubleTapListener( - object : DoubleTapInterface { - override fun onEvent(x: Float) { - val width = exoPlayerView.width - when { - width * 0.5 > x -> rewind() - width * 0.5 < x -> forward() - } - } - } - ) - } - - private fun rewind() { - exoPlayer.seekTo(exoPlayer.currentPosition - seekIncrement) - - // show the rewind button - doubleTapOverlayBinding.rewindBTN.apply { - visibility = View.VISIBLE - // clear previous animation - animate().rotation(0F).setDuration(0).start() - // start new animation - animate() - .rotation(-30F) - .setDuration(100) - .withEndAction { - // reset the animation when finished - animate().rotation(0F).setDuration(100).start() - } - .start() - - removeCallbacks(hideRewindButtonRunnable) - // start callback to hide the button - postDelayed(hideRewindButtonRunnable, 700) - } - } - - private fun forward() { - exoPlayer.seekTo(exoPlayer.currentPosition + seekIncrement) - - // show the forward button - doubleTapOverlayBinding.forwardBTN.apply { - visibility = View.VISIBLE - // clear previous animation - animate().rotation(0F).setDuration(0).start() - // start new animation - animate() - .rotation(30F) - .setDuration(100) - .withEndAction { - // reset the animation when finished - animate().rotation(0F).setDuration(100).start() - } - .start() - - // start callback to hide the button - removeCallbacks(hideForwardButtonRunnable) - postDelayed(hideForwardButtonRunnable, 700) - } - } - - private val hideForwardButtonRunnable = Runnable { - doubleTapOverlayBinding.forwardBTN.apply { - visibility = View.GONE - } - } - private val hideRewindButtonRunnable = Runnable { - doubleTapOverlayBinding.rewindBTN.apply { - visibility = View.GONE - } - } - private fun initializeChapters() { if (chapters.isEmpty()) { binding.chaptersRecView.visibility = View.GONE @@ -1533,7 +1293,7 @@ class PlayerFragment : BaseFragment() { // handles the audio focus val audioAttributes = AudioAttributes.Builder() .setUsage(C.USAGE_MEDIA) - .setContentType(C.CONTENT_TYPE_MOVIE) + .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) .build() // handles the duration of media to retain in the buffer prior to the current playback position (for fast backward seeking) @@ -1570,34 +1330,6 @@ class PlayerFragment : BaseFragment() { nowPlayingNotification.updatePlayerNotification(streams) } - // lock the player - private fun lockPlayer(isLocked: Boolean) { - // isLocked is the current (old) state of the player lock - val visibility = if (isLocked) View.VISIBLE else View.GONE - - playerBinding.exoTopBarRight.visibility = visibility - playerBinding.exoCenterControls.visibility = visibility - playerBinding.exoBottomBar.visibility = visibility - playerBinding.closeImageButton.visibility = visibility - playerBinding.exoTitle.visibility = - if (isLocked && - viewModel.isFullscreen.value == true - ) { - View.VISIBLE - } else { - View.INVISIBLE - } - - // disable double tap to seek when the player is locked - if (isLocked) { - // enable fast forward and rewind by double tapping - enableDoubleTapToSeek() - } else { - // disable fast forward and rewind by double tapping - binding.player.setOnDoubleTapListener(null) - } - } - private fun isSubscribed() { fun run() { val channelId = streams.uploaderUrl!!.toID() diff --git a/app/src/main/java/com/github/libretube/interfaces/OnlinePlayerOptionsInterface.kt b/app/src/main/java/com/github/libretube/interfaces/OnlinePlayerOptionsInterface.kt new file mode 100644 index 000000000..17c7e65f4 --- /dev/null +++ b/app/src/main/java/com/github/libretube/interfaces/OnlinePlayerOptionsInterface.kt @@ -0,0 +1,7 @@ +package com.github.libretube.interfaces + +interface OnlinePlayerOptionsInterface { + fun onCaptionClicked() + + fun onQualityClicked() +} diff --git a/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt b/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt index fe5d7377f..4553aadc4 100644 --- a/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt +++ b/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt @@ -1,13 +1,8 @@ package com.github.libretube.interfaces interface PlayerOptionsInterface { - fun onAutoplayClicked() - fun onCaptionClicked() - - fun onQualityClicked() - fun onPlaybackSpeedClicked() fun onResizeModeClicked() diff --git a/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt b/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt index 1b70e4d21..2e9f0a7f6 100644 --- a/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt @@ -2,13 +2,31 @@ package com.github.libretube.views import android.annotation.SuppressLint import android.content.Context +import android.os.Handler +import android.os.Looper import android.util.AttributeSet +import android.view.LayoutInflater import android.view.MotionEvent +import android.view.View +import androidx.fragment.app.FragmentManager +import com.github.libretube.R import com.github.libretube.activities.MainActivity +import com.github.libretube.constants.PreferenceKeys +import com.github.libretube.constants.PreferenceRanges +import com.github.libretube.databinding.DialogSliderBinding +import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding +import com.github.libretube.extensions.setSliderRangeAndValue import com.github.libretube.interfaces.DoubleTapInterface +import com.github.libretube.interfaces.OnlinePlayerOptionsInterface +import com.github.libretube.interfaces.PlayerOptionsInterface import com.github.libretube.util.DoubleTapListener +import com.github.libretube.util.PreferenceHelper +import com.google.android.exoplayer2.trackselection.TrackSelector +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.StyledPlayerView +import com.google.android.exoplayer2.util.RepeatModeUtil +import com.google.android.material.dialog.MaterialAlertDialogBuilder @SuppressLint("ClickableViewAccessibility") internal class CustomExoPlayerView( @@ -16,17 +34,40 @@ internal class CustomExoPlayerView( attributeSet: AttributeSet? = null ) : StyledPlayerView(context, attributeSet) { val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this) + private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null + /** + * Objects from the parent fragment + */ private var doubleTapListener: DoubleTapInterface? = null + private var onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface? = null + private lateinit var childFragmentManager: FragmentManager + private var trackSelector: TrackSelector? = null + + private val runnableHandler = Handler(Looper.getMainLooper()) // the x-position of where the user clicked private var xPos = 0F - fun setOnDoubleTapListener( - eventListener: DoubleTapInterface? - ) { - doubleTapListener = eventListener - } + var isPlayerLocked: Boolean = false + + /** + * Preferences + */ + var autoplayEnabled = PreferenceHelper.getBoolean( + PreferenceKeys.AUTO_PLAY, + false + ) + + private val seekIncrement = PreferenceHelper.getString( + PreferenceKeys.SEEK_INCREMENT, + "5" + ).toLong() * 1000 + + private var resizeModePref = PreferenceHelper.getString( + PreferenceKeys.PLAYER_RESIZE_MODE, + "fit" + ) private fun toggleController() { if (isControllerFullyVisible) hideController() else showController() @@ -42,9 +83,47 @@ internal class CustomExoPlayerView( } } - init { + fun initialize( + childFragmentManager: FragmentManager, + playerViewInterface: OnlinePlayerOptionsInterface, + doubleTapOverlayBinding: DoubleTapOverlayBinding, + trackSelector: TrackSelector + ) { + this.childFragmentManager = childFragmentManager + this.onlinePlayerOptionsInterface = playerViewInterface + this.doubleTapOverlayBinding = doubleTapOverlayBinding + this.trackSelector = trackSelector + // set the double click listener for rewind/forward setOnClickListener(doubleTouchListener) + + enableDoubleTapToSeek() + + initializeAdvancedOptions() + + // locking the player + binding.lockPlayer.setOnClickListener { + // change the locked/unlocked icon + binding.lockPlayer.setImageResource( + if (!isPlayerLocked) { + R.drawable.ic_locked + } else { + R.drawable.ic_unlocked + } + ) + + // show/hide all the controls + lockPlayer(isPlayerLocked) + + // change locked status + isPlayerLocked = !isPlayerLocked + } + + resizeMode = when (resizeModePref) { + "fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL + "zoom" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM + else -> AspectRatioFrameLayout.RESIZE_MODE_FIT + } } override fun hideController() { @@ -59,4 +138,225 @@ internal class CustomExoPlayerView( doubleTouchListener.onClick(this) return false } + + private fun initializeAdvancedOptions() { + binding.toggleOptions.setOnClickListener { + val bottomSheetFragment = PlayerOptionsBottomSheet().apply { + setOnClickListeners( + playerOptionsInterface, + onlinePlayerOptionsInterface + ) + // set the auto play mode + currentAutoplayMode = if (autoplayEnabled) { + context?.getString(R.string.enabled) + } else { + context?.getString(R.string.disabled) + } + // set the current caption language + currentCaptions = + if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) { + trackSelector!!.parameters.preferredTextLanguages[0] + } else { + context?.getString(R.string.none) + } + // set the playback speed + currentPlaybackSpeed = "${ + player?.playbackParameters?.speed.toString() + .replace(".0", "") + }x" + // set the quality text + val quality = player?.videoSize?.height + if (quality != 0) { + currentQuality = "${quality}p" + } + // set the repeat mode + currentRepeatMode = + if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { + context?.getString(R.string.repeat_mode_none) + } else { + context?.getString(R.string.repeat_mode_current) + } + // set the aspect ratio mode + currentResizeMode = 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) + } + } + bottomSheetFragment.show(childFragmentManager, null) + } + } + + // lock the player + private fun lockPlayer(isLocked: Boolean) { + // isLocked is the current (old) state of the player lock + val visibility = if (isLocked) View.VISIBLE else View.GONE + + binding.exoTopBarRight.visibility = visibility + binding.exoCenterControls.visibility = visibility + binding.exoBottomBar.visibility = visibility + binding.closeImageButton.visibility = visibility + + // disable double tap to seek when the player is locked + if (isLocked) { + // enable fast forward and rewind by double tapping + enableDoubleTapToSeek() + } else { + // disable fast forward and rewind by double tapping + doubleTapListener = null + } + } + + private fun enableDoubleTapToSeek() { + // set seek increment text + val seekIncrementText = (seekIncrement / 1000).toString() + doubleTapOverlayBinding?.rewindTV?.text = seekIncrementText + doubleTapOverlayBinding?.forwardTV?.text = seekIncrementText + doubleTapListener = + object : DoubleTapInterface { + override fun onEvent(x: Float) { + when { + width * 0.5 > x -> rewind() + width * 0.5 < x -> forward() + } + } + } + } + + private fun rewind() { + player?.seekTo((player?.currentPosition ?: 0L) - seekIncrement) + + // show the rewind button + doubleTapOverlayBinding?.rewindBTN.run { + this!!.visibility = View.VISIBLE + // clear previous animation + this.animate().rotation(0F).setDuration(0).start() + // start new animation + this.animate() + .rotation(-30F) + .setDuration(100) + .withEndAction { + // reset the animation when finished + animate().rotation(0F).setDuration(100).start() + } + .start() + + runnableHandler.removeCallbacks(hideRewindButtonRunnable) + // start callback to hide the button + runnableHandler.postDelayed(hideRewindButtonRunnable, 700) + } + } + + private fun forward() { + player?.seekTo(player!!.currentPosition + seekIncrement) + + // show the forward button + doubleTapOverlayBinding?.forwardBTN.apply { + visibility = View.VISIBLE + // clear previous animation + this!!.animate().rotation(0F).setDuration(0).start() + // start new animation + this.animate() + .rotation(30F) + .setDuration(100) + .withEndAction { + // reset the animation when finished + animate().rotation(0F).setDuration(100).start() + } + .start() + + // start callback to hide the button + runnableHandler.removeCallbacks(hideForwardButtonRunnable) + runnableHandler.postDelayed(hideForwardButtonRunnable, 700) + } + } + + private val hideForwardButtonRunnable = Runnable { + doubleTapOverlayBinding?.forwardBTN.apply { + this!!.visibility = View.GONE + } + } + private val hideRewindButtonRunnable = Runnable { + doubleTapOverlayBinding?.rewindBTN.apply { + this!!.visibility = View.GONE + } + } + + private val playerOptionsInterface = object : PlayerOptionsInterface { + override fun onAutoplayClicked() { + // autoplay options dialog + MaterialAlertDialogBuilder(context) + .setTitle(R.string.player_autoplay) + .setItems( + arrayOf( + context.getString(R.string.enabled), + context.getString(R.string.disabled) + ) + ) { _, index -> + when (index) { + 0 -> autoplayEnabled = true + 1 -> autoplayEnabled = false + } + } + .show() + } + + override fun onPlaybackSpeedClicked() { + val playbackSpeedBinding = DialogSliderBinding.inflate( + LayoutInflater.from(context) + ) + playbackSpeedBinding.slider.setSliderRangeAndValue( + PreferenceRanges.playbackSpeed + ) + playbackSpeedBinding.slider.value = player?.playbackParameters?.speed ?: 1f + // change playback speed dialog + MaterialAlertDialogBuilder(context) + .setTitle(R.string.change_playback_speed) + .setView(playbackSpeedBinding.root) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.okay) { _, _ -> + player?.setPlaybackSpeed( + playbackSpeedBinding.slider.value + ) + } + .show() + } + + override fun onResizeModeClicked() { + // switching between original aspect ratio (black bars) and zoomed to fill device screen + val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode) + + val aspectRatioModes = arrayOf( + AspectRatioFrameLayout.RESIZE_MODE_FIT, + AspectRatioFrameLayout.RESIZE_MODE_ZOOM, + AspectRatioFrameLayout.RESIZE_MODE_FILL + ) + + MaterialAlertDialogBuilder(context) + .setTitle(R.string.aspect_ratio) + .setItems(aspectRatioModeNames) { _, index -> + resizeMode = aspectRatioModes[index] + } + .show() + } + + override fun onRepeatModeClicked() { + val repeatModeNames = arrayOf( + context.getString(R.string.repeat_mode_none), + context.getString(R.string.repeat_mode_current) + ) + + val repeatModes = arrayOf( + RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL, + RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE + ) + // repeat mode options dialog + MaterialAlertDialogBuilder(context) + .setTitle(R.string.repeat_mode) + .setItems(repeatModeNames) { _, index -> + player?.repeatMode = repeatModes[index] + } + .show() + } + } } diff --git a/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt b/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt index ece3d1865..901f46894 100644 --- a/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt +++ b/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import com.github.libretube.databinding.BottomSheetBinding +import com.github.libretube.interfaces.OnlinePlayerOptionsInterface import com.github.libretube.interfaces.PlayerOptionsInterface import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -17,6 +18,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment class PlayerOptionsBottomSheet : BottomSheetDialogFragment() { lateinit var binding: BottomSheetBinding private lateinit var playerOptionsInterface: PlayerOptionsInterface + private var onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface? = null /** * current values @@ -46,13 +48,22 @@ class PlayerOptionsBottomSheet : BottomSheetDialogFragment() { return binding.root } - fun setOnClickListeners(playerOptionsInterface: PlayerOptionsInterface) { + fun setOnClickListeners( + playerOptionsInterface: PlayerOptionsInterface, + onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface? + ) { this.playerOptionsInterface = playerOptionsInterface + this.onlinePlayerOptionsInterface = onlinePlayerOptionsInterface } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (onlinePlayerOptionsInterface == null) { + binding.captions.visibility = View.GONE + binding.quality.visibility = View.GONE + } + /** * update the text if a value is selected */ @@ -75,7 +86,7 @@ class PlayerOptionsBottomSheet : BottomSheetDialogFragment() { } binding.quality.setOnClickListener { - playerOptionsInterface.onQualityClicked() + onlinePlayerOptionsInterface?.onQualityClicked() this.dismiss() } @@ -85,7 +96,7 @@ class PlayerOptionsBottomSheet : BottomSheetDialogFragment() { } binding.captions.setOnClickListener { - playerOptionsInterface.onCaptionClicked() + onlinePlayerOptionsInterface?.onCaptionClicked() this.dismiss() }