Merge pull request #5641 from Bnyro/master

feat(player): support for keyboard navigation
This commit is contained in:
Bnyro 2024-02-19 21:57:06 +01:00 committed by GitHub
commit ce8ed66e92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 96 additions and 34 deletions

View File

@ -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<PlayerFragment>()
.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 <T> runOnPlayerFragment(action: PlayerFragment.() -> T): T? {
return supportFragmentManager.fragments.filterIsInstance<PlayerFragment>()
.firstOrNull()
?.let(action)
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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<PlayerFragment>().firstOrNull()
?.toggleFullscreen()
}
else -> super.onKeyUp(keyCode, event)
}
return true
}
open fun minimizeOrExitPlayer() = Unit
open fun getWindow(): Window = activity.window

View File

@ -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 -> {

View File

@ -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
}