diff --git a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt index 793a823dfc..ec1f8e9b02 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt @@ -199,11 +199,7 @@ class OfflinePlayerActivity : BaseActivity() { playNextVideo(PlayingQueue.getNext() ?: return@setOnClickListener) } - binding.player.initialize( - binding.doubleTapOverlay.binding, - binding.playerGestureControlsView.binding, - chaptersViewModel - ) + binding.player.initialize(chaptersViewModel) } private suspend fun loadPlayerData() { 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 308290d21a..d8ecfb2c91 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 @@ -125,9 +125,8 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { private var _binding: FragmentPlayerBinding? = null val binding get() = _binding!! - private val playerBinding get() = binding.player.binding - private val doubleTapOverlayBinding get() = binding.doubleTapOverlay.binding - private val playerGestureControlsViewBinding get() = binding.playerGestureControlsView.binding + private val playerControlsBinding get() = binding.player.binding + private val playerBackgroundBinding get() = binding.player.backgroundBinding private val commonPlayerViewModel: CommonPlayerViewModel by activityViewModels() private val viewModel: PlayerViewModel by viewModels() @@ -275,7 +274,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { // check if video has ended, next video is available and autoplay is enabled/the video is part of a played playlist. if (playbackState == Player.STATE_ENDED) { - binding.sbSkipBtn.isGone = true + playerBackgroundBinding.sbSkipBtn.isGone = true if (PlayerHelper.isAutoPlayEnabled(playlistId != null) && autoPlayCountdownEnabled) { showAutoPlayCountdown() } else { @@ -326,7 +325,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { mediaMetadata.extras?.getString(IntentData.videoId)?.let { videoId = it - _binding?.autoplayCountdown?.cancelAndHideCountdown() + if (_binding != null) playerBackgroundBinding.autoplayCountdown.cancelAndHideCountdown() // fix: if the fragment is recreated, play the current video, and not the initial one arguments?.run { @@ -432,8 +431,8 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { viewModel.segments.observe(viewLifecycleOwner) { segments -> binding.descriptionLayout.setSegments(segments) - playerBinding.exoProgress.setSegments(segments) - playerBinding.sbToggle.isVisible = segments.isNotEmpty() + playerControlsBinding.exoProgress.setSegments(segments) + playerControlsBinding.sbToggle.isVisible = segments.isNotEmpty() segments.firstOrNull { it.category == PlayerHelper.SPONSOR_HIGHLIGHT_CATEGORY } ?.let { lifecycleScope.launch(Dispatchers.IO) { initializeHighlight(it) } @@ -508,6 +507,8 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { // if the player is minimized, the fragment behind the player should handle the event onBackPressedCallback.isEnabled = isMiniPlayerVisible != true } + + toggleVideoInfoVisibility(false) } private fun attachToPlayerService(playerData: PlayerData, startNewSession: Boolean) { @@ -596,7 +597,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { updateCurrentSubtitle(null) disableController() commonPlayerViewModel.setSheetExpand(null) - binding.sbSkipBtn.isGone = true + playerBackgroundBinding.sbSkipBtn.isGone = true if (NavBarHelper.hasTabs()) { mainMotionLayout.progress = 1F } @@ -644,7 +645,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { binding.closeImageView.setOnClickListener { killPlayerFragment() } - playerBinding.closeImageButton.setOnClickListener { + playerControlsBinding.closeImageButton.setOnClickListener { killPlayerFragment() } @@ -672,7 +673,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { // FullScreen button trigger // hide fullscreen button if autorotation enabled - playerBinding.fullscreen.setOnClickListener { + playerControlsBinding.fullscreen.setOnClickListener { toggleFullscreen() } @@ -739,11 +740,11 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { }.show(childFragmentManager, AddToPlaylistDialog::class.java.name) } - playerBinding.skipPrev.setOnClickListener { + playerControlsBinding.skipPrev.setOnClickListener { PlayingQueue.getPrev()?.let { prev -> playNextVideo(prev) } } - playerBinding.skipNext.setOnClickListener { + playerControlsBinding.skipNext.setOnClickListener { PlayingQueue.getNext()?.let { next -> playNextVideo(next) } } @@ -1040,27 +1041,27 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { SbSkipOptions.MANUAL ) || autoSkipTemporarilyDisabled ) { - binding.sbSkipBtn.isVisible = true - binding.sbSkipBtn.setOnClickListener { + playerBackgroundBinding.sbSkipBtn.isVisible = true + playerBackgroundBinding.sbSkipBtn.setOnClickListener { playerController.seekTo((segment.segmentStartAndEnd.second * 1000f).toLong()) segment.skipped = true } } } else { - binding.sbSkipBtn.isGone = true + playerBackgroundBinding.sbSkipBtn.isGone = true } } private fun setPlayerDefaults() { // reset the player view - playerBinding.exoProgress.clearSegments() - playerBinding.sbToggle.isGone = true + playerControlsBinding.exoProgress.clearSegments() + playerControlsBinding.sbToggle.isGone = true // reset the comments to become reloaded later commentsViewModel.reset() // hide the button to skip SponsorBlock segments manually - binding.sbSkipBtn.isGone = true + playerBackgroundBinding.sbSkipBtn.isGone = true // use the video's default audio track when starting playback playerController.sendCustomCommand( @@ -1104,7 +1105,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { binding.descriptionLayout.isInvisible = !show binding.relatedRecView.isInvisible = !show binding.playerChannel.isInvisible = !show - binding.videoTransitionProgress.isVisible = !show + playerBackgroundBinding.videoTransitionProgress.isVisible = !show } @SuppressLint("SetTextI18n") @@ -1125,11 +1126,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { } // initialize the player view actions - binding.player.initialize( - doubleTapOverlayBinding, - playerGestureControlsViewBinding, - chaptersViewModel - ) + binding.player.initialize(chaptersViewModel) binding.player.initPlayerOptions( viewModel, commonPlayerViewModel, @@ -1163,7 +1160,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { player.isLive = streams.isLive relPlayerDownload.isVisible = !streams.isLive } - playerBinding.exoTitle.text = streams.title + playerControlsBinding.exoTitle.text = streams.title // init the chapters recyclerview chaptersViewModel.chaptersLiveData.postValue(streams.chapters) @@ -1186,10 +1183,10 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { ) // seekbar preview setup - playerBinding.seekbarPreview.isGone = true - seekBarPreviewListener?.let { playerBinding.exoProgress.removeListener(it) } + playerControlsBinding.seekbarPreview.isGone = true + seekBarPreviewListener?.let { playerControlsBinding.exoProgress.removeListener(it) } seekBarPreviewListener = createSeekbarPreviewListener().also { - playerBinding.exoProgress.addSeekBarListener(it) + playerControlsBinding.exoProgress.addSeekBarListener(it) } } @@ -1197,14 +1194,14 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { if (!PlayingQueue.hasNext()) return disableController() - binding.autoplayCountdown.setHideSelfListener { + playerBackgroundBinding.autoplayCountdown.setHideSelfListener { // could fail if the video already got closed before runCatching { - binding.autoplayCountdown.isGone = true + playerBackgroundBinding.autoplayCountdown.isGone = true binding.player.useController = true } } - binding.autoplayCountdown.startCountdown { + playerBackgroundBinding.autoplayCountdown.startCountdown { PlayingQueue.getNext()?.let { playNextVideo(it) } } } @@ -1507,7 +1504,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { private fun createSeekbarPreviewListener(): SeekbarPreviewListener { return SeekbarPreviewListener( OnlineTimeFrameReceiver(requireContext(), streams.previewFrames), - playerBinding, + playerControlsBinding, streams.duration * 1000 ) } 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 7b019e4796..16a321eada 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 @@ -12,7 +12,6 @@ import android.text.format.DateUtils import android.util.AttributeSet import android.view.KeyEvent import android.view.MotionEvent -import android.view.View import android.view.Window import android.widget.FrameLayout import android.widget.ImageView @@ -40,6 +39,7 @@ import androidx.media3.ui.TimeBar import com.github.libretube.R import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys +import com.github.libretube.databinding.CustomExoPlayerViewTemplateBinding import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.PlayerGestureControlsViewBinding @@ -80,16 +80,18 @@ abstract class CustomExoPlayerView( ) : PlayerView(context, attributeSet), PlayerOptions, PlayerGestureOptions { @Suppress("LeakingThis") val binding = ExoStyledPlayerControlViewBinding.bind(this) + val backgroundBinding = CustomExoPlayerViewTemplateBinding.bind(this) /** * Objects for player tap and swipe gesture */ - private lateinit var gestureViewBinding: PlayerGestureControlsViewBinding + private val gestureViewBinding: PlayerGestureControlsViewBinding get() = backgroundBinding.playerGestureControlsView.binding + private val doubleTapOverlayBinding: DoubleTapOverlayBinding get() = backgroundBinding.doubleTapOverlay.binding + private lateinit var playerGestureController: PlayerGestureController private lateinit var brightnessHelper: BrightnessHelper private lateinit var audioHelper: AudioHelper private lateinit var chaptersViewModel: ChaptersViewModel - private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null private var chaptersBottomSheet: ChaptersBottomSheet? = null private var scrubbingTimeBar = false @@ -132,13 +134,7 @@ abstract class CustomExoPlayerView( if (isControllerFullyVisible) hideController() else showController() } - fun initialize( - doubleTapOverlayBinding: DoubleTapOverlayBinding, - playerGestureControlsViewBinding: PlayerGestureControlsViewBinding, - chaptersViewModel: ChaptersViewModel - ) { - this.doubleTapOverlayBinding = doubleTapOverlayBinding - this.gestureViewBinding = playerGestureControlsViewBinding + fun initialize(chaptersViewModel: ChaptersViewModel) { this.chaptersViewModel = chaptersViewModel this.playerGestureController = PlayerGestureController(context as BaseActivity, this) this.brightnessHelper = BrightnessHelper(context as Activity) @@ -367,6 +363,10 @@ abstract class CustomExoPlayerView( // remove the callback to hide the controller cancelHideControllerTask() super.hideController() + backgroundBinding.exoControlsBackground.animate() + .alpha(0f) + .setDuration(500) + .start() } override fun showController() { @@ -375,6 +375,10 @@ abstract class CustomExoPlayerView( // automatically hide the controller after 2 seconds enqueueHideControllerTask() super.showController() + backgroundBinding.exoControlsBackground.animate() + .alpha(1f) + .setDuration(200) + .start() } fun showControllerPermanently() { @@ -388,12 +392,12 @@ abstract class CustomExoPlayerView( private fun initRewindAndForward() { val seekIncrementText = (PlayerHelper.seekIncrement / 1000).toString() listOf( - doubleTapOverlayBinding?.rewindTV, - doubleTapOverlayBinding?.forwardTV, + doubleTapOverlayBinding.rewindTV, + doubleTapOverlayBinding.forwardTV, binding.forwardTV, binding.rewindTV ).forEach { - it?.text = seekIncrementText + it.text = seekIncrementText } binding.forwardBTN.setOnClickListener { player?.seekBy(PlayerHelper.seekIncrement) @@ -475,7 +479,7 @@ abstract class CustomExoPlayerView( } // hide the dimming background overlay if locked - binding.exoControlsBackground.setBackgroundColor( + backgroundBinding.exoControlsBackground.setBackgroundColor( if (isLocked) { ContextCompat.getColor( context, @@ -494,7 +498,7 @@ abstract class CustomExoPlayerView( player?.seekBy(-PlayerHelper.seekIncrement) // show the rewind button - doubleTapOverlayBinding?.apply { + doubleTapOverlayBinding.apply { animateSeeking(rewindBTN, rewindIV, rewindTV, true) // start callback to hide the button @@ -509,7 +513,7 @@ abstract class CustomExoPlayerView( player?.seekBy(PlayerHelper.seekIncrement) // show the forward button - doubleTapOverlayBinding?.apply { + doubleTapOverlayBinding.apply { animateSeeking(forwardBTN, forwardIV, forwardTV, false) // start callback to hide the button @@ -604,7 +608,7 @@ abstract class CustomExoPlayerView( private fun updateVolume(distance: Float) { val bar = gestureViewBinding.volumeProgressBar gestureViewBinding.volumeControlView.apply { - if (visibility == View.GONE) { + if (isGone) { isVisible = true // Volume could be changed using other mediums, sync progress // bar with new value. diff --git a/app/src/main/res/layout-land/fragment_player.xml b/app/src/main/res/layout-land/fragment_player.xml index 1c14d50e7d..addf2c3031 100644 --- a/app/src/main/res/layout-land/fragment_player.xml +++ b/app/src/main/res/layout-land/fragment_player.xml @@ -207,70 +207,14 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="@android:color/black" + app:player_layout_id="@layout/custom_exo_player_view" app:controller_layout_id="@layout/exo_styled_player_control_view" app:layout_constraintBottom_toBottomOf="@id/main_container" app:layout_constraintEnd_toEndOf="@id/main_container" app:layout_constraintStart_toStartOf="@id/main_container" app:layout_constraintTop_toTopOf="@id/main_container" app:show_buffering="when_playing" - app:surface_type="surface_view"> - - - - - - - - - - - - - - - - + app:surface_type="surface_view" /> - - - - - - + app:show_buffering="when_playing" /> \ No newline at end of file diff --git a/app/src/main/res/layout/custom_exo_player_view.xml b/app/src/main/res/layout/custom_exo_player_view.xml new file mode 100644 index 0000000000..ef226383e3 --- /dev/null +++ b/app/src/main/res/layout/custom_exo_player_view.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/custom_exo_player_view_template.xml b/app/src/main/res/layout/custom_exo_player_view_template.xml new file mode 100644 index 0000000000..64cbd0bdba --- /dev/null +++ b/app/src/main/res/layout/custom_exo_player_view_template.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 50282750f5..74c16484b5 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 @@ -3,12 +3,6 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - - - - - - - - - - - - - - - - - - + app:surface_type="surface_view" />