Merge pull request #7348 from Bnyro/master

feat: support for adding downloads to playback queue
This commit is contained in:
Bnyro 2025-04-25 16:38:27 +02:00 committed by GitHub
commit 09a306eefd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 50 additions and 19 deletions

View File

@ -36,7 +36,6 @@ object IntentData {
const val url = "url" const val url = "url"
const val videoStats = "videoStats" const val videoStats = "videoStats"
const val bitmapUrl = "bitmapUrl" const val bitmapUrl = "bitmapUrl"
const val isCurrentlyPlaying = "isCurrentlyPlaying"
const val isSubscribed = "isSubscribed" const val isSubscribed = "isSubscribed"
const val sortOptions = "sortOptions" const val sortOptions = "sortOptions"
const val hideWatched = "hideWatched" const val hideWatched = "hideWatched"

View File

@ -37,6 +37,7 @@ import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.PauseableTimer import com.github.libretube.util.PauseableTimer
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PlayingQueueMode
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -100,6 +101,7 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
Player.STATE_ENDED -> { Player.STATE_ENDED -> {
saveWatchPosition() saveWatchPosition()
} }
Player.STATE_READY -> { Player.STATE_READY -> {
isTransitioning = false isTransitioning = false
} }
@ -115,6 +117,9 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
): ListenableFuture<SessionResult> { ): ListenableFuture<SessionResult> {
when (customCommand.customAction) { when (customCommand.customAction) {
START_SERVICE_ACTION -> { START_SERVICE_ACTION -> {
PlayingQueue.queueMode =
if (isOfflinePlayer) PlayingQueueMode.OFFLINE else PlayingQueueMode.ONLINE
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
onServiceCreated(args) onServiceCreated(args)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -262,8 +267,9 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
abstract val isOfflinePlayer: Boolean abstract val isOfflinePlayer: Boolean
var isAudioOnlyPlayer: Boolean = false var isAudioOnlyPlayer: Boolean = false
val watchPositionsEnabled get() = val watchPositionsEnabled
(PlayerHelper.watchPositionsAudio && isAudioOnlyPlayer) || (PlayerHelper.watchPositionsVideo && !isAudioOnlyPlayer) get() =
(PlayerHelper.watchPositionsAudio && isAudioOnlyPlayer) || (PlayerHelper.watchPositionsVideo && !isAudioOnlyPlayer)
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? = override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
mediaLibrarySession mediaLibrarySession

View File

@ -137,8 +137,7 @@ class DownloadsAdapter(
DownloadOptionsBottomSheet() DownloadOptionsBottomSheet()
.apply { .apply {
arguments = bundleOf( arguments = bundleOf(
IntentData.videoId to download.videoId, IntentData.streamItem to download.toStreamItem(),
IntentData.channelName to download.uploader,
IntentData.downloadTab to downloadTab IntentData.downloadTab to downloadTab
) )
} }

View File

@ -471,10 +471,7 @@ class AudioPlayerFragment : Fragment(R.layout.fragment_audio_player), AudioPlaye
val current = PlayingQueue.getCurrent() ?: return val current = PlayingQueue.getCurrent() ?: return
VideoOptionsBottomSheet() VideoOptionsBottomSheet()
.apply { .apply {
arguments = bundleOf( arguments = bundleOf(IntentData.streamItem to current)
IntentData.streamItem to current,
IntentData.isCurrentlyPlaying to true
)
} }
.show(childFragmentManager) .show(childFragmentManager)
} }

View File

@ -60,7 +60,8 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
} }
binding.watchHistoryRecView.setOnDismissListener { position -> binding.watchHistoryRecView.setOnDismissListener { position ->
val item = viewModel.filteredWatchHistory.value?.getOrNull(position) ?: return@setOnDismissListener val item = viewModel.filteredWatchHistory.value?.getOrNull(position)
?: return@setOnDismissListener
viewModel.removeFromHistory(item) viewModel.removeFromHistory(item)
} }

View File

@ -4,33 +4,43 @@ import android.os.Bundle
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.enums.ShareObjectType import com.github.libretube.enums.ShareObjectType
import com.github.libretube.extensions.parcelable
import com.github.libretube.extensions.serializable import com.github.libretube.extensions.serializable
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.BackgroundHelper import com.github.libretube.helpers.BackgroundHelper
import com.github.libretube.helpers.ContextHelper import com.github.libretube.helpers.ContextHelper
import com.github.libretube.helpers.NavigationHelper import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.obj.ShareData import com.github.libretube.obj.ShareData
import com.github.libretube.ui.activities.DownloadActivity
import com.github.libretube.ui.activities.NoInternetActivity import com.github.libretube.ui.activities.NoInternetActivity
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.fragments.DownloadTab import com.github.libretube.ui.fragments.DownloadTab
import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PlayingQueueMode
class DownloadOptionsBottomSheet : BaseBottomSheet() { class DownloadOptionsBottomSheet : BaseBottomSheet() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val videoId = arguments?.getString(IntentData.videoId)!! val streamItem = arguments?.parcelable<StreamItem>(IntentData.streamItem)!!
val videoId = streamItem.url!!.toID()
val downloadTab = arguments?.serializable<DownloadTab>(IntentData.downloadTab)!! val downloadTab = arguments?.serializable<DownloadTab>(IntentData.downloadTab)!!
val options = mutableListOf( val options = mutableListOf(
R.string.playOnBackground, R.string.playOnBackground,
R.string.go_to_video,
R.string.share, R.string.share,
R.string.delete R.string.delete
) )
// can't navigate to video while in offline activity // can't navigate to video while in offline activity
if (ContextHelper.tryUnwrapActivity<NoInternetActivity>(requireContext()) != null) { if (ContextHelper.tryUnwrapActivity<NoInternetActivity>(requireContext()) == null) {
options.remove(R.string.go_to_video) options += R.string.go_to_video
}
val isSelectedVideoCurrentlyPlaying = PlayingQueue.getCurrent()?.url?.toID() == videoId
if (!isSelectedVideoCurrentlyPlaying && PlayingQueue.isNotEmpty() && PlayingQueue.queueMode == PlayingQueueMode.OFFLINE) {
options += R.string.play_next
options += R.string.add_to_queue
} }
setSimpleItems(options.map { getString(it) }) { which -> setSimpleItems(options.map { getString(it) }) { which ->
@ -59,6 +69,14 @@ class DownloadOptionsBottomSheet : BaseBottomSheet() {
setFragmentResult(DELETE_DOWNLOAD_REQUEST_KEY, bundleOf()) setFragmentResult(DELETE_DOWNLOAD_REQUEST_KEY, bundleOf())
dialog?.dismiss() dialog?.dismiss()
} }
R.string.play_next -> {
PlayingQueue.addAsNext(streamItem)
}
R.string.add_to_queue -> {
PlayingQueue.add(streamItem)
}
} }
} }

View File

@ -24,6 +24,7 @@ import com.github.libretube.ui.dialogs.AddToPlaylistDialog
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.fragments.SubscriptionsFragment import com.github.libretube.ui.fragments.SubscriptionsFragment
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PlayingQueueMode
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -35,18 +36,17 @@ import kotlinx.coroutines.withContext
*/ */
class VideoOptionsBottomSheet : BaseBottomSheet() { class VideoOptionsBottomSheet : BaseBottomSheet() {
private lateinit var streamItem: StreamItem private lateinit var streamItem: StreamItem
private var isCurrentlyPlaying = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
streamItem = arguments?.parcelable(IntentData.streamItem)!! streamItem = arguments?.parcelable(IntentData.streamItem)!!
isCurrentlyPlaying = arguments?.getBoolean(IntentData.isCurrentlyPlaying) ?: false
val videoId = streamItem.url?.toID() ?: return val videoId = streamItem.url?.toID() ?: return
setTitle(streamItem.title) setTitle(streamItem.title)
val optionsList = mutableListOf<Int>() val optionsList = mutableListOf<Int>()
if (!isCurrentlyPlaying) { // these options are only available for other videos than the currently playing one
if (PlayingQueue.getCurrent()?.url?.toID() != videoId) {
optionsList += getOptionsForNotActivePlayback(videoId) optionsList += getOptionsForNotActivePlayback(videoId)
} }
@ -136,7 +136,7 @@ class VideoOptionsBottomSheet : BaseBottomSheet() {
val optionsList = mutableListOf(R.string.playOnBackground) val optionsList = mutableListOf(R.string.playOnBackground)
// Check whether the player is running and add queue options // Check whether the player is running and add queue options
if (PlayingQueue.isNotEmpty()) { if (PlayingQueue.isNotEmpty() && PlayingQueue.queueMode == PlayingQueueMode.ONLINE) {
optionsList += R.string.play_next optionsList += R.string.play_next
optionsList += R.string.add_to_queue optionsList += R.string.add_to_queue
} }

View File

@ -18,6 +18,12 @@ object PlayingQueue {
private val queueJobs = mutableListOf<Job>() private val queueJobs = mutableListOf<Job>()
/**
* Current use case of the queue. Do NOT add any offline videos while the [queueMode] is online
* or vice versa.
*/
var queueMode: PlayingQueueMode = PlayingQueueMode.ONLINE
// wrapper around PlayerHelper#repeatMode for compatibility // wrapper around PlayerHelper#repeatMode for compatibility
var repeatMode: Int var repeatMode: Int
get() = PlayerHelper.repeatMode get() = PlayerHelper.repeatMode
@ -222,3 +228,8 @@ object PlayingQueue {
add(*streams.filter { !it.isLive }.toTypedArray(), skipExisting = true) add(*streams.filter { !it.isLive }.toTypedArray(), skipExisting = true)
} }
} }
enum class PlayingQueueMode {
ONLINE,
OFFLINE
}