From 3cde15c96f96927850b9500a2c659383a564f051 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Fri, 12 Aug 2022 14:03:55 +0200 Subject: [PATCH] refactor advanced player options --- .../libretube/fragments/PlayerFragment.kt | 295 +++++++++--------- .../interfaces/DoubleTapInterface.kt | 5 + .../interfaces/PlayerOptionsInterface.kt | 16 + .../util/OnDoubleTapEventListener.kt | 5 - .../libretube/views/BottomSheetFragment.kt | 61 ++++ .../libretube/views/CustomExoPlayerView.kt | 12 +- app/src/main/res/drawable/ic_aspect_ratio.xml | 2 +- app/src/main/res/layout/bottom_sheet.xml | 44 +++ .../layout/exo_styled_player_control_view.xml | 73 ----- app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/style.xml | 10 + 11 files changed, 284 insertions(+), 241 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/interfaces/DoubleTapInterface.kt create mode 100644 app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt delete mode 100644 app/src/main/java/com/github/libretube/util/OnDoubleTapEventListener.kt create mode 100644 app/src/main/java/com/github/libretube/views/BottomSheetFragment.kt create mode 100644 app/src/main/res/layout/bottom_sheet.xml 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 c8cfd6076..cdafc6bce 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.content.res.Configuration -import android.graphics.Color import android.graphics.Rect import android.net.Uri import android.os.Build @@ -45,6 +44,8 @@ import com.github.libretube.dialogs.AddToPlaylistDialog import com.github.libretube.dialogs.DownloadDialog import com.github.libretube.dialogs.ShareDialog import com.github.libretube.extensions.BaseFragment +import com.github.libretube.interfaces.DoubleTapInterface +import com.github.libretube.interfaces.PlayerOptionsInterface import com.github.libretube.obj.ChapterSegment import com.github.libretube.obj.Segment import com.github.libretube.obj.Segments @@ -57,13 +58,13 @@ import com.github.libretube.util.BackgroundHelper import com.github.libretube.util.ConnectionHelper import com.github.libretube.util.CronetHelper import com.github.libretube.util.NowPlayingNotification -import com.github.libretube.util.OnDoubleTapEventListener import com.github.libretube.util.PlayerHelper import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.SubscriptionHelper import com.github.libretube.util.formatShort import com.github.libretube.util.hideKeyboard import com.github.libretube.util.toID +import com.github.libretube.views.BottomSheetFragment import com.google.android.exoplayer2.C import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.ExoPlayer @@ -206,8 +207,6 @@ class PlayerFragment : BaseFragment() { setUserPrefs() - if (autoplayEnabled) playerBinding.autoplayIV.setImageResource(R.drawable.ic_toggle_on) - val mainActivity = activity as MainActivity if (autoRotationEnabled) { // enable auto rotation @@ -397,6 +396,127 @@ class PlayerFragment : BaseFragment() { } } + private val playerOptionsInterface = object : PlayerOptionsInterface { + override fun onAutoplayClicked() { + // toggle autoplay + autoplayEnabled = !autoplayEnabled + } + + override fun onCaptionClicked() { + if (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!! + } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.captions) + .setItems(subtitlesNamesList.toTypedArray()) { _, index -> + val newParams = if (index != 0) { + // caption selected + + // get the caption name and language + val captionLanguage = subtitlesNamesList[index] + val captionLanguageCode = subtitleCodesList[index] + + // select the new caption preference + trackSelector.buildUponParameters() + .setPreferredTextLanguages( + captionLanguage, + captionLanguageCode + ) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + } else { + // none selected + // disable captions + trackSelector.buildUponParameters() + .setPreferredTextLanguage("") + } + + // set the new caption language + trackSelector.setParameters(newParams) + } + .show() + } + + override fun onQualityClicked() { + // get the available resolutions + val (videosNameArray, videosUrlArray) = getAvailableResolutions(streams) + + // Dialog for quality selection + val lastPosition = exoPlayer.currentPosition + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.choose_quality_dialog) + .setItems( + videosNameArray + ) { _, which -> + if ( + videosNameArray[which] == getString(R.string.hls) || + videosNameArray[which] == "LBRY HLS" + ) { + // no need to merge sources if using hls + val mediaItem: MediaItem = MediaItem.Builder() + .setUri(videosUrlArray[which]) + .setSubtitleConfigurations(subtitle) + .build() + exoPlayer.setMediaItem(mediaItem) + } else { + val videoUri = videosUrlArray[which] + val audioUrl = PlayerHelper.getAudioSource(streams.audioStreams!!) + setMediaSource(videoUri, audioUrl) + } + exoPlayer.seekTo(lastPosition) + } + .show() + } + + override fun onPlaybackSpeedClicked() { + val playbackSpeeds = context?.resources?.getStringArray(R.array.playbackSpeed)!! + val playbackSpeedValues = + context?.resources?.getStringArray(R.array.playbackSpeedValues)!! + + // change playback speed dialog + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.change_playback_speed) + .setItems(playbackSpeeds) { _, index -> + // set the new playback speed + val newPlaybackSpeed = playbackSpeedValues[index].toFloat() + exoPlayer.setPlaybackSpeed(newPlaybackSpeed) + } + .show() + } + + override fun onAspectRatioClicked() { + // switching between original aspect ratio (black bars) and zoomed to fill device screen + val aspectRatioModes = arrayOf( + AspectRatioFrameLayout.RESIZE_MODE_FIT, + AspectRatioFrameLayout.RESIZE_MODE_ZOOM, + AspectRatioFrameLayout.RESIZE_MODE_FILL + ) + val index = aspectRatioModes.indexOf(exoPlayerView.resizeMode) + val newAspectRatioMode = + if (index + 1 < aspectRatioModes.size) aspectRatioModes[index + 1] + else aspectRatioModes[0] + exoPlayerView.resizeMode = newAspectRatioMode + } + + override fun onRepeatModeClicked() { + // repeat toggle button + if (exoPlayer.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) { + // turn off repeat mode + exoPlayer.repeatMode = RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE + } else { + exoPlayer.repeatMode = RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL + } + } + } + // actions that don't depend on video information private fun initializeOnClickActions() { binding.closeImageView.setOnClickListener { @@ -417,24 +537,12 @@ class PlayerFragment : BaseFragment() { } // show the advanced player options playerBinding.toggleOptions.setOnClickListener { - if (playerBinding.advancedOptions.isVisible) { - playerBinding.toggleOptions.animate().rotation(0F).setDuration(250).start() - playerBinding.advancedOptions.visibility = View.GONE - } else { - playerBinding.toggleOptions.animate().rotation(180F).setDuration(250).start() - playerBinding.advancedOptions.visibility = View.VISIBLE - } - } - // autoplay toggle button - playerBinding.autoplayLL.setOnClickListener { - autoplayEnabled = if (autoplayEnabled) { - playerBinding.autoplayIV.setImageResource(R.drawable.ic_toggle_off) - false - } else { - playerBinding.autoplayIV.setImageResource(R.drawable.ic_toggle_on) - true + val bottomSheetFragment = BottomSheetFragment().apply { + setOnClickListeners(playerOptionsInterface) } + bottomSheetFragment.show(childFragmentManager, null) } + binding.playImageView.setOnClickListener { if (!exoPlayer.isPlaying) { // start or go on playing @@ -471,20 +579,6 @@ class PlayerFragment : BaseFragment() { } } - // switching between original aspect ratio (black bars) and zoomed to fill device screen - val aspectRatioModes = arrayOf( - AspectRatioFrameLayout.RESIZE_MODE_FIT, - AspectRatioFrameLayout.RESIZE_MODE_ZOOM, - AspectRatioFrameLayout.RESIZE_MODE_FILL - ) - playerBinding.aspectRatioButton.setOnClickListener { - val index = aspectRatioModes.indexOf(exoPlayerView.resizeMode) - val newAspectRatioMode = - if (index + 1 < aspectRatioModes.size) aspectRatioModes[index + 1] - else aspectRatioModes[0] - exoPlayerView.resizeMode = newAspectRatioMode - } - // lock and unlock the player playerBinding.lockPlayer.setOnClickListener { // change the locked/unlocked icon @@ -502,37 +596,7 @@ class PlayerFragment : BaseFragment() { } // set default playback speed - val playbackSpeeds = context?.resources?.getStringArray(R.array.playbackSpeed)!! - val playbackSpeedValues = - context?.resources?.getStringArray(R.array.playbackSpeedValues)!! exoPlayer.setPlaybackSpeed(playbackSpeed.toFloat()) - val speedIndex = playbackSpeedValues.indexOf(playbackSpeed) - playerBinding.speedText.text = playbackSpeeds[speedIndex] - - // change playback speed button - playerBinding.speedText.setOnClickListener { - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.change_playback_speed) - .setItems(playbackSpeeds) { _, index -> - // set the new playback speed - val newPlaybackSpeed = playbackSpeedValues[index].toFloat() - exoPlayer.setPlaybackSpeed(newPlaybackSpeed) - playerBinding.speedText.text = playbackSpeeds[index] - } - .show() - } - - // repeat toggle button - playerBinding.repeatToggle.setOnClickListener { - if (exoPlayer.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) { - // turn off repeat mode - exoPlayer.repeatMode = RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE - playerBinding.repeatToggle.setColorFilter(Color.GRAY) - } else { - exoPlayer.repeatMode = RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL - playerBinding.repeatToggle.setColorFilter(Color.WHITE) - } - } // share button binding.relPlayerShare.setOnClickListener { @@ -785,7 +849,6 @@ class PlayerFragment : BaseFragment() { // switch back to normal speed when on the end of live stream if (exoPlayer.duration - exoPlayer.currentPosition < 7000) { exoPlayer.setPlaybackSpeed(1F) - playerBinding.speedText.text = "1x" playerBinding.liveSeparator.visibility = View.GONE playerBinding.liveDiff.text = "" } else { @@ -911,12 +974,6 @@ class PlayerFragment : BaseFragment() { override fun onVideoSizeChanged( videoSize: VideoSize ) { - // show the resolution in the video resolution text view - if (playerBinding.qualityText.text == context?.getString(R.string.hls)) { - playerBinding.qualityText.text = - "${context?.getString(R.string.hls)} (${videoSize.height}p)" - } - // Set new width/height of view // height or width must be cast to float as int/int will give 0 @@ -1075,7 +1132,7 @@ class PlayerFragment : BaseFragment() { doubleTapOverlayBinding.rewindTV.text = seekIncrementText doubleTapOverlayBinding.forwardTV.text = seekIncrementText binding.player.setOnDoubleTapListener( - object : OnDoubleTapEventListener { + object : DoubleTapInterface { override fun onEvent(x: Float) { val width = exoPlayerView.width when { @@ -1234,17 +1291,17 @@ class PlayerFragment : BaseFragment() { exoPlayer.setMediaSource(mergeSource) } - private fun setResolutionAndSubtitles(response: Streams) { - var videosNameArray: Array = arrayOf() + private fun getAvailableResolutions(streams: Streams): Pair, Array> { + var videosNameArray: Array = arrayOf() var videosUrlArray: Array = arrayOf() // append hls to list if available - if (response.hls != null) { + if (streams.hls != null) { videosNameArray += getString(R.string.hls) - videosUrlArray += response.hls.toUri() + videosUrlArray += streams.hls.toUri() } - for (vid in response.videoStreams!!) { + for (vid in streams.videoStreams!!) { // append quality to list if it has the preferred format (e.g. MPEG) val preferredMimeType = "video/$videoFormatPreference" if (vid.url != null && vid.mimeType == preferredMimeType) { // preferred format @@ -1255,6 +1312,13 @@ class PlayerFragment : BaseFragment() { videosUrlArray += vid.url!!.toUri() } } + return Pair(videosNameArray, videosUrlArray) + } + + private fun setResolutionAndSubtitles(response: Streams) { + // get the available resolutions + val (videosNameArray, videosUrlArray) = getAvailableResolutions(response) + // create a list of subtitles subtitle = mutableListOf() val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!) @@ -1276,46 +1340,6 @@ class PlayerFragment : BaseFragment() { .setPreferredTextLanguage(defaultSubtitleCode) .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) trackSelector.setParameters(newParams) - playerBinding.captions.setImageResource(R.drawable.ic_caption) - } - - // captions selection dialog - // hide caption selection view if no subtitles available - if (response.subtitles.isEmpty()) playerBinding.captions.visibility = View.GONE - playerBinding.captions.setOnClickListener { - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.captions) - .setItems(subtitlesNamesList.toTypedArray()) { _, index -> - val newParams = if (index != 0) { - // caption selected - - // get the caption name and language - val captionLanguage = subtitlesNamesList[index] - val captionLanguageCode = subtitleCodesList[index] - - // update the icon of the captions button - playerBinding.captions.setImageResource(R.drawable.ic_caption) - - // select the new caption preference - trackSelector.buildUponParameters() - .setPreferredTextLanguages( - captionLanguage, - captionLanguageCode - ) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - } else { - // none selected - playerBinding.captions.setImageResource(R.drawable.ic_caption_outlined) - - // disable captions - trackSelector.buildUponParameters() - .setPreferredTextLanguage("") - } - - // set the new caption language - trackSelector.setParameters(newParams) - } - .show() } // set media source and resolution in the beginning @@ -1324,43 +1348,11 @@ class PlayerFragment : BaseFragment() { videosNameArray, videosUrlArray ) - - playerBinding.qualityText.setOnClickListener { - // Dialog for quality selection - val builder: MaterialAlertDialogBuilder? = activity?.let { - MaterialAlertDialogBuilder(it) - } - val lastPosition = exoPlayer.currentPosition - builder!!.setTitle(R.string.choose_quality_dialog) - .setItems( - videosNameArray - ) { _, which -> - if ( - videosNameArray[which] == getString(R.string.hls) || - videosNameArray[which] == "LBRY HLS" - ) { - // no need to merge sources if using hls - val mediaItem: MediaItem = MediaItem.Builder() - .setUri(videosUrlArray[which]) - .setSubtitleConfigurations(subtitle) - .build() - exoPlayer.setMediaItem(mediaItem) - } else { - val videoUri = videosUrlArray[which] - val audioUrl = PlayerHelper.getAudioSource(response.audioStreams!!) - setMediaSource(videoUri, audioUrl) - } - exoPlayer.seekTo(lastPosition) - playerBinding.qualityText.text = videosNameArray[which] - } - val dialog = builder.create() - dialog.show() - } } private fun setStreamSource( streams: Streams, - videosNameArray: Array, + videosNameArray: Array, videosUrlArray: Array ) { if (defRes != "") { @@ -1370,7 +1362,6 @@ class PlayerFragment : BaseFragment() { val videoUri = videosUrlArray[index] val audioUrl = PlayerHelper.getAudioSource(streams.audioStreams!!) setMediaSource(videoUri, audioUrl) - playerBinding.qualityText.text = videosNameArray[index] return } } @@ -1383,7 +1374,6 @@ class PlayerFragment : BaseFragment() { .setSubtitleConfigurations(subtitle) .build() exoPlayer.setMediaItem(mediaItem) - playerBinding.qualityText.text = context?.getString(R.string.hls) return } @@ -1392,7 +1382,6 @@ class PlayerFragment : BaseFragment() { val videoUri = videosUrlArray[0] val audioUrl = PlayerHelper.getAudioSource(streams.audioStreams!!) setMediaSource(videoUri, audioUrl) - playerBinding.qualityText.text = videosNameArray[0] } } diff --git a/app/src/main/java/com/github/libretube/interfaces/DoubleTapInterface.kt b/app/src/main/java/com/github/libretube/interfaces/DoubleTapInterface.kt new file mode 100644 index 000000000..ba8bcd8f0 --- /dev/null +++ b/app/src/main/java/com/github/libretube/interfaces/DoubleTapInterface.kt @@ -0,0 +1,5 @@ +package com.github.libretube.interfaces + +interface DoubleTapInterface { + fun onEvent(x: Float) +} diff --git a/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt b/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt new file mode 100644 index 000000000..1f7bfbcb1 --- /dev/null +++ b/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt @@ -0,0 +1,16 @@ +package com.github.libretube.interfaces + +interface PlayerOptionsInterface { + + fun onAutoplayClicked() + + fun onCaptionClicked() + + fun onQualityClicked() + + fun onPlaybackSpeedClicked() + + fun onAspectRatioClicked() + + fun onRepeatModeClicked() +} diff --git a/app/src/main/java/com/github/libretube/util/OnDoubleTapEventListener.kt b/app/src/main/java/com/github/libretube/util/OnDoubleTapEventListener.kt deleted file mode 100644 index d9b135f06..000000000 --- a/app/src/main/java/com/github/libretube/util/OnDoubleTapEventListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.libretube.util - -interface OnDoubleTapEventListener { - fun onEvent(x: Float) -} diff --git a/app/src/main/java/com/github/libretube/views/BottomSheetFragment.kt b/app/src/main/java/com/github/libretube/views/BottomSheetFragment.kt new file mode 100644 index 000000000..48de8f0ef --- /dev/null +++ b/app/src/main/java/com/github/libretube/views/BottomSheetFragment.kt @@ -0,0 +1,61 @@ +package com.github.libretube.views + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.github.libretube.databinding.BottomSheetBinding +import com.github.libretube.interfaces.PlayerOptionsInterface +import com.google.android.material.bottomsheet.BottomSheetDialogFragment + +class BottomSheetFragment : BottomSheetDialogFragment() { + private lateinit var binding: BottomSheetBinding + private lateinit var playerOptionsInterface: PlayerOptionsInterface + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = BottomSheetBinding.inflate(layoutInflater, container, false) + return binding.root + } + + fun setOnClickListeners(playerOptionsInterface: PlayerOptionsInterface) { + this.playerOptionsInterface = playerOptionsInterface + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.aspectRatio.setOnClickListener { + playerOptionsInterface.onAspectRatioClicked() + this.dismiss() + } + + binding.quality.setOnClickListener { + playerOptionsInterface.onQualityClicked() + this.dismiss() + } + + binding.playbackSpeed.setOnClickListener { + playerOptionsInterface.onPlaybackSpeedClicked() + this.dismiss() + } + + binding.captions.setOnClickListener { + playerOptionsInterface.onCaptionClicked() + this.dismiss() + } + + binding.autoplay.setOnClickListener { + playerOptionsInterface.onAutoplayClicked() + this.dismiss() + } + + binding.repeatMode.setOnClickListener { + playerOptionsInterface.onRepeatModeClicked() + this.dismiss() + } + } +} 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 193609b63..ccad7cdbd 100644 --- a/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt @@ -4,10 +4,9 @@ import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.view.MotionEvent -import android.view.View import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding +import com.github.libretube.interfaces.DoubleTapInterface import com.github.libretube.util.DoubleTapListener -import com.github.libretube.util.OnDoubleTapEventListener import com.google.android.exoplayer2.ui.StyledPlayerView @SuppressLint("ClickableViewAccessibility") @@ -18,13 +17,13 @@ internal class CustomExoPlayerView( val TAG = "CustomExoPlayerView" val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this) - private var doubleTapListener: OnDoubleTapEventListener? = null + private var doubleTapListener: DoubleTapInterface? = null // the x-position of where the user clicked private var xPos = 0F fun setOnDoubleTapListener( - eventListener: OnDoubleTapEventListener? + eventListener: DoubleTapInterface? ) { doubleTapListener = eventListener } @@ -44,11 +43,6 @@ internal class CustomExoPlayerView( } init { - setControllerVisibilityListener { - // hide the advanced options - binding.toggleOptions.animate().rotation(0F).setDuration(250).start() - binding.advancedOptions.visibility = View.GONE - } // set the double click listener for rewind/forward setOnClickListener(doubleTouchListener) } diff --git a/app/src/main/res/drawable/ic_aspect_ratio.xml b/app/src/main/res/drawable/ic_aspect_ratio.xml index a783c45e8..5fe2b2d8a 100644 --- a/app/src/main/res/drawable/ic_aspect_ratio.xml +++ b/app/src/main/res/drawable/ic_aspect_ratio.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/exo_styled_player_control_view.xml b/app/src/main/res/layout/exo_styled_player_control_view.xml index 958b6d4c7..9741d8d52 100644 --- a/app/src/main/res/layout/exo_styled_player_control_view.xml +++ b/app/src/main/res/layout/exo_styled_player_control_view.xml @@ -67,17 +67,6 @@ android:layout_gravity="center" android:layoutDirection="ltr"> - - - - - - - - - - - - - - - - - - - - - - - - - - Time to take a break You already spent %1$s minutes in the app, time to take a break. Shorts + No subtitles available + Repeat Mode diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml index 2f012ca54..515f27e5c 100644 --- a/app/src/main/res/values/style.xml +++ b/app/src/main/res/values/style.xml @@ -157,4 +157,14 @@ + + \ No newline at end of file