Merge pull request #7323 from Bnyro/master

feat: smoothen background player -> new video player and video player -> new background player transitions
This commit is contained in:
Bnyro 2025-04-22 21:04:10 +02:00 committed by GitHub
commit cc7aba6830
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 96 additions and 66 deletions

View File

@ -1,18 +1,14 @@
package com.github.libretube.helpers package com.github.libretube.helpers
import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.os.Process import android.os.Process
import androidx.annotation.OptIn
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.os.postDelayed
import androidx.fragment.app.commitNow import androidx.fragment.app.commitNow
import androidx.fragment.app.replace import androidx.fragment.app.replace
import androidx.media3.common.util.UnstableApi
import com.github.libretube.NavDirections import com.github.libretube.NavDirections
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
@ -29,8 +25,6 @@ import com.github.libretube.ui.views.SingleViewTouchableMotionLayout
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
object NavigationHelper { object NavigationHelper {
private val handler = Handler(Looper.getMainLooper())
fun navigateChannel(context: Context, channelUrlOrId: String?) { fun navigateChannel(context: Context, channelUrlOrId: String?) {
if (channelUrlOrId == null) return if (channelUrlOrId == null) return
@ -52,6 +46,7 @@ object NavigationHelper {
* Navigate to the given video using the other provided parameters as well * Navigate to the given video using the other provided parameters as well
* If the audio only mode is enabled, play it in the background, else as a normal video * If the audio only mode is enabled, play it in the background, else as a normal video
*/ */
@SuppressLint("UnsafeOptInUsageError")
fun navigateVideo( fun navigateVideo(
context: Context, context: Context,
videoUrlOrId: String?, videoUrlOrId: String?,
@ -60,22 +55,27 @@ object NavigationHelper {
keepQueue: Boolean = false, keepQueue: Boolean = false,
timestamp: Long = 0, timestamp: Long = 0,
alreadyStarted: Boolean = false, alreadyStarted: Boolean = false,
forceVideo: Boolean = false forceVideo: Boolean = false,
audioOnlyPlayerRequested: Boolean = false,
) { ) {
if (videoUrlOrId == null) return if (videoUrlOrId == null) return
if (PreferenceHelper.getBoolean(PreferenceKeys.AUDIO_ONLY_MODE, false) && !forceVideo) { // attempt to attach to the current media session first by using the corresponding
navigateAudio(context, videoUrlOrId.toID(), playlistId, channelId, keepQueue, timestamp) // video/audio player instance
return
}
val activity = ContextHelper.unwrapActivity<MainActivity>(context) val activity = ContextHelper.unwrapActivity<MainActivity>(context)
val attachedToRunningPlayer = activity.runOnPlayerFragment { val attachedToRunningPlayer = activity.runOnPlayerFragment {
try { try {
this.playNextVideo(videoUrlOrId.toID()) this.playNextVideo(videoUrlOrId.toID())
// maximize player
this.binding.playerMotionLayout.transitionToStart()
PlayingQueue.clear() PlayingQueue.clear()
if (audioOnlyPlayerRequested) {
// switch to audio only player
this.switchToAudioMode()
} else {
// maximize player
this.binding.playerMotionLayout.transitionToStart()
}
true true
} catch (e: Exception) { } catch (e: Exception) {
this.onDestroy() this.onDestroy()
@ -84,45 +84,46 @@ object NavigationHelper {
} }
if (attachedToRunningPlayer) return if (attachedToRunningPlayer) return
val playerData = val attachedToRunningAudioPlayer = activity.runOnAudioPlayerFragment {
PlayerData(videoUrlOrId.toID(), playlistId, channelId, keepQueue, timestamp) this.playNextVideo(videoUrlOrId.toID())
val bundle = bundleOf( PlayingQueue.clear()
IntentData.playerData to playerData,
IntentData.alreadyStarted to alreadyStarted if (!audioOnlyPlayerRequested) {
) // switch to video only player
activity.supportFragmentManager.commitNow { this.switchToVideoMode()
replace<PlayerFragment>(R.id.container, args = bundle) } else {
} // maximize player
} this.binding.playerMotionLayout.transitionToStart()
}
@OptIn(UnstableApi::class)
fun navigateAudio(
context: Context,
videoId: String,
playlistId: String? = null,
channelId: String? = null,
keepQueue: Boolean = false,
timestamp: Long = 0,
minimizeByDefault: Boolean = false
) {
val activity = ContextHelper.unwrapActivity<MainActivity>(context)
val attachedToRunningPlayer = activity.runOnAudioPlayerFragment {
this.playNextVideo(videoId)
true true
} }
if (attachedToRunningPlayer) return if (attachedToRunningAudioPlayer) return
BackgroundHelper.playOnBackground( val audioOnlyMode = PreferenceHelper.getBoolean(PreferenceKeys.AUDIO_ONLY_MODE, false)
context, if (audioOnlyPlayerRequested || (audioOnlyMode && !forceVideo)) {
videoId, // in contrast to the video player, the audio player doesn't start a media service on
timestamp, // its own!
playlistId, BackgroundHelper.playOnBackground(
channelId, context,
keepQueue videoUrlOrId.toID(),
) timestamp,
playlistId,
channelId,
keepQueue
)
handler.postDelayed(500) { openAudioPlayerFragment(context, minimizeByDefault = true)
openAudioPlayerFragment(context, minimizeByDefault = minimizeByDefault) } else {
openVideoPlayerFragment(
context,
videoUrlOrId.toID(),
playlistId,
channelId,
keepQueue,
timestamp,
alreadyStarted
)
} }
} }
@ -153,6 +154,31 @@ object NavigationHelper {
} }
} }
/**
* Starts the video player fragment for an already existing med
*/
fun openVideoPlayerFragment(
context: Context,
videoId: String,
playlistId: String? = null,
channelId: String? = null,
keepQueue: Boolean = false,
timestamp: Long = 0,
alreadyStarted: Boolean = false
) {
val activity = ContextHelper.unwrapActivity<BaseActivity>(context)
val playerData =
PlayerData(videoId, playlistId, channelId, keepQueue, timestamp)
val bundle = bundleOf(
IntentData.playerData to playerData,
IntentData.alreadyStarted to alreadyStarted
)
activity.supportFragmentManager.commitNow {
replace<PlayerFragment>(R.id.container, args = bundle)
}
}
/** /**
* Open a large, zoomable image preview * Open a large, zoomable image preview
*/ */

View File

@ -33,6 +33,7 @@ import com.github.libretube.enums.PlayerCommand
import com.github.libretube.extensions.navigateVideo import com.github.libretube.extensions.navigateVideo
import com.github.libretube.extensions.normalize import com.github.libretube.extensions.normalize
import com.github.libretube.extensions.seekBy import com.github.libretube.extensions.seekBy
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.togglePlayPauseState import com.github.libretube.extensions.togglePlayPauseState
import com.github.libretube.extensions.updateIfChanged import com.github.libretube.extensions.updateIfChanged
import com.github.libretube.helpers.AudioHelper import com.github.libretube.helpers.AudioHelper
@ -174,19 +175,7 @@ class AudioPlayerFragment : Fragment(R.layout.fragment_audio_player), AudioPlaye
} }
binding.openVideo.setOnClickListener { binding.openVideo.setOnClickListener {
playerController?.sendCustomCommand( switchToVideoMode()
AbstractPlayerService.runPlayerActionCommand,
bundleOf(PlayerCommand.TOGGLE_AUDIO_ONLY_MODE.name to false)
)
killFragment(false)
NavigationHelper.navigateVideo(
context = requireContext(),
videoUrlOrId = PlayingQueue.getCurrent()?.url,
alreadyStarted = true,
forceVideo = true
)
} }
childFragmentManager.setFragmentResultListener( childFragmentManager.setFragmentResultListener(
@ -271,6 +260,21 @@ class AudioPlayerFragment : Fragment(R.layout.fragment_audio_player), AudioPlaye
} }
} }
fun switchToVideoMode() {
playerController?.sendCustomCommand(
AbstractPlayerService.runPlayerActionCommand,
bundleOf(PlayerCommand.TOGGLE_AUDIO_ONLY_MODE.name to false)
)
killFragment(false)
NavigationHelper.openVideoPlayerFragment(
context = requireContext(),
videoId = PlayingQueue.getCurrent()?.url!!.toID(),
alreadyStarted = true,
)
}
private fun killFragment(stopPlayer: Boolean) { private fun killFragment(stopPlayer: Boolean) {
viewModel.isMiniPlayerVisible.value = false viewModel.isMiniPlayerVisible.value = false

View File

@ -218,7 +218,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions {
} }
PlayerEvent.Background -> { PlayerEvent.Background -> {
playOnBackground() switchToAudioMode()
// wait some time in order for the service to get started properly // wait some time in order for the service to get started properly
handler.postDelayed(500) { handler.postDelayed(500) {
pipActivity?.moveTaskToBack(false) pipActivity?.moveTaskToBack(false)
@ -698,7 +698,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions {
binding.relPlayerBackground.setOnClickListener { binding.relPlayerBackground.setOnClickListener {
// start the background mode // start the background mode
playOnBackground() switchToAudioMode()
} }
binding.relPlayerPip.isVisible = binding.relPlayerPip.isVisible =
@ -777,7 +777,7 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions {
chaptersViewModel.maxSheetHeightPx = maxHeight chaptersViewModel.maxSheetHeightPx = maxHeight
} }
private fun playOnBackground() { fun switchToAudioMode() {
playerController.sendCustomCommand( playerController.sendCustomCommand(
AbstractPlayerService.runPlayerActionCommand, AbstractPlayerService.runPlayerActionCommand,
bundleOf(PlayerCommand.TOGGLE_AUDIO_ONLY_MODE.name to true) bundleOf(PlayerCommand.TOGGLE_AUDIO_ONLY_MODE.name to true)

View File

@ -57,7 +57,7 @@ class VideoOptionsBottomSheet : BaseBottomSheet() {
when (optionsList[which]) { when (optionsList[which]) {
// Start the background mode // Start the background mode
R.string.playOnBackground -> { R.string.playOnBackground -> {
NavigationHelper.navigateAudio(requireContext(), videoId, minimizeByDefault = true) NavigationHelper.navigateVideo(requireContext(), videoId, audioOnlyPlayerRequested = true)
} }
// Add Video to Playlist Dialog // Add Video to Playlist Dialog
R.string.addToPlaylist -> { R.string.addToPlaylist -> {