From 0b059ccc12ab5a6df89c3c61f34680ad18db5471 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 19 Feb 2024 21:56:33 +0100 Subject: [PATCH] feat(player): support for keyboard navigation --- .../libretube/ui/activities/MainActivity.kt | 23 ++++++---- .../ui/activities/OfflinePlayerActivity.kt | 5 +++ .../ui/fragments/AudioPlayerFragment.kt | 8 +--- .../libretube/ui/fragments/PlayerFragment.kt | 42 +++++++++++++------ .../libretube/ui/views/CustomExoPlayerView.kt | 32 ++++++++++++++ .../libretube/util/NowPlayingNotification.kt | 8 +--- .../com/github/libretube/util/PlayingQueue.kt | 12 ++++++ 7 files changed, 96 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt index 8ec7bb45c..00520d6e0 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.res.Configuration import android.os.Bundle +import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.view.View @@ -138,12 +139,7 @@ class MainActivity : BaseActivity() { // new way of handling back presses onBackPressedDispatcher.addCallback { if (playerViewModel.isFullscreen.value == true) { - supportFragmentManager.fragments.filterIsInstance() - .firstOrNull() - ?.let { - it.unsetFullscreen() - return@addCallback - } + runOnPlayerFragment { unsetFullscreen() } } if (binding.mainMotionLayout.progress == 0F) { @@ -490,9 +486,8 @@ class MainActivity : BaseActivity() { override fun onUserLeaveHint() { super.onUserLeaveHint() - supportFragmentManager.fragments.forEach { fragment -> - (fragment as? PlayerFragment)?.onUserLeaveHint() - } + + runOnPlayerFragment { onUserLeaveHint() } } override fun onNewIntent(intent: Intent?) { @@ -500,4 +495,14 @@ class MainActivity : BaseActivity() { this.intent = intent loadIntentData() } + + override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + return runOnPlayerFragment { onKeyUp(keyCode, event) } ?: false + } + + private fun runOnPlayerFragment(action: PlayerFragment.() -> T): T? { + return supportFragmentManager.fragments.filterIsInstance() + .firstOrNull() + ?.let(action) + } } 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 78f5b4b32..d44c9dad2 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 @@ -5,6 +5,7 @@ import android.media.session.PlaybackState import android.net.Uri import android.os.Bundle import android.text.format.DateUtils +import android.view.KeyEvent import androidx.activity.viewModels import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -231,4 +232,8 @@ class OfflinePlayerActivity : BaseActivity() { super.onUserLeaveHint() } + + override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + return binding.player.onKeyBoardAction(keyCode, event) + } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt index 128543b05..9ce97c4dc 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt @@ -123,15 +123,11 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions { } binding.prev.setOnClickListener { - val currentIndex = PlayingQueue.currentIndex() - if (!PlayingQueue.hasPrev()) return@setOnClickListener - PlayingQueue.onQueueItemSelected(currentIndex - 1) + PlayingQueue.navigatePrev() } binding.next.setOnClickListener { - val currentIndex = PlayingQueue.currentIndex() - if (!PlayingQueue.hasNext()) return@setOnClickListener - PlayingQueue.onQueueItemSelected(currentIndex + 1) + PlayingQueue.navigateNext() } listOf(binding.forwardTV, binding.rewindTV).forEach { 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 ec5523bf3..a008563bf 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 @@ -15,6 +15,7 @@ import android.os.Handler import android.os.Looper import android.os.PowerManager import android.text.format.DateUtils +import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -190,6 +191,10 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { override fun onBackPressed() { unsetFullscreen() } + + override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { + return _binding?.player?.onKeyUp(keyCode, event) ?: true + } } } @@ -535,19 +540,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { // FullScreen button trigger // hide fullscreen button if autorotation enabled playerBinding.fullscreen.setOnClickListener { - // hide player controller - binding.player.hideController() - if (viewModel.isFullscreen.value == false) { - // go to fullscreen mode - setFullscreen() - } else { - // exit fullscreen mode - unsetFullscreen() - - // disable the fullscreen button for auto fullscreen - // this is necessary to hide the button after an auto fullscreen for shorts - playerBinding.fullscreen.isVisible = !PlayerHelper.autoFullscreenEnabled - } + toggleFullscreen() } val updateSbImageResource = { @@ -732,6 +725,25 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { binding.player.updateMarginsByFullscreenMode() } + /** + * Enable or disable fullscreen depending on the current state + */ + fun toggleFullscreen() { + // hide player controller + binding.player.hideController() + if (viewModel.isFullscreen.value == false) { + // go to fullscreen mode + setFullscreen() + } else { + // exit fullscreen mode + unsetFullscreen() + + // disable the fullscreen button for auto fullscreen + // this is necessary to hide the button after an auto fullscreen for shorts + playerBinding.fullscreen.isVisible = !PlayerHelper.autoFullscreenEnabled + } + } + private fun openOrCloseFullscreenDialog(open: Boolean) { val playerView = binding.player (playerView.parent as ViewGroup).removeView(playerView) @@ -1690,4 +1702,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { binding.player.useController = false binding.player.hideController() } + + fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + return _binding?.player?.onKeyBoardAction(keyCode, event) ?: false + } } 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 acc8e9acf..10044f453 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 @@ -10,6 +10,7 @@ import android.os.Handler import android.os.Looper 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 @@ -23,6 +24,7 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.marginStart import androidx.core.view.updateLayoutParams +import androidx.fragment.app.commit import androidx.media3.common.C import androidx.media3.common.Player import androidx.media3.common.text.Cue @@ -44,6 +46,7 @@ import com.github.libretube.extensions.seekBy import com.github.libretube.extensions.togglePlayPauseState import com.github.libretube.helpers.AudioHelper import com.github.libretube.helpers.BrightnessHelper +import com.github.libretube.helpers.ContextHelper import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.WindowHelper @@ -51,6 +54,7 @@ import com.github.libretube.obj.BottomSheetItem import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.extensions.toggleSystemBars import com.github.libretube.ui.extensions.trySetTooltip +import com.github.libretube.ui.fragments.PlayerFragment import com.github.libretube.ui.interfaces.PlayerGestureOptions import com.github.libretube.ui.interfaces.PlayerOptions import com.github.libretube.ui.listeners.PlayerGestureController @@ -729,6 +733,34 @@ open class CustomExoPlayerView( return super.onInterceptTouchEvent(ev) } + fun onKeyBoardAction(keyCode: Int, event: KeyEvent?): Boolean { + when (keyCode) { + KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { + player?.togglePlayPauseState() + } + KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> { + forward() + } + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_MEDIA_REWIND -> { + rewind() + } + KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_NAVIGATE_NEXT -> { + PlayingQueue.navigateNext() + } + KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_NAVIGATE_PREVIOUS -> { + PlayingQueue.navigatePrev() + } + KeyEvent.KEYCODE_F -> { + val fragmentManager = ContextHelper.unwrapActivity(context).supportFragmentManager + fragmentManager.fragments.filterIsInstance().firstOrNull() + ?.toggleFullscreen() + } + else -> super.onKeyUp(keyCode, event) + } + + return true + } + open fun minimizeOrExitPlayer() = Unit open fun getWindow(): Window = activity.window diff --git a/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt b/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt index 65c456919..8669c8bb5 100644 --- a/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt +++ b/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt @@ -279,15 +279,11 @@ class NowPlayingNotification( private fun handlePlayerAction(action: String) { when (action) { NEXT -> { - if (!PlayingQueue.hasNext()) return - - PlayingQueue.onQueueItemSelected(PlayingQueue.currentIndex() + 1) + PlayingQueue.navigateNext() } PREV -> { - if (!PlayingQueue.hasPrev()) return - - PlayingQueue.onQueueItemSelected(PlayingQueue.currentIndex() - 1) + PlayingQueue.navigatePrev() } REWIND -> { diff --git a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt index 4f4bf16de..d13014b04 100644 --- a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt +++ b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt @@ -222,6 +222,18 @@ object PlayingQueue { } } + fun navigatePrev() { + if (!hasPrev()) return + + onQueueItemSelected(currentIndex() - 1) + } + + fun navigateNext() { + if (!hasNext()) return + + onQueueItemSelected(currentIndex() + 1) + } + fun setOnQueueTapListener(listener: (StreamItem) -> Unit) { onQueueTapListener = listener }