playback queue

This commit is contained in:
Bnyro 2022-08-10 15:49:31 +02:00
parent c4d9de3566
commit ed87a7b33a
6 changed files with 71 additions and 32 deletions

View File

@ -16,4 +16,7 @@ object Globals {
// for playlists // for playlists
var SELECTED_PLAYLIST_ID: String? = null var SELECTED_PLAYLIST_ID: String? = null
// history of played videos in the current lifecycle
val videoIds = mutableListOf<String>()
} }

View File

@ -1,10 +1,14 @@
package com.github.libretube.dialogs package com.github.libretube.dialogs
import android.app.Dialog import android.app.Dialog
import android.app.NotificationManager
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.Globals
import com.github.libretube.PLAYER_NOTIFICATION_ID
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.BackgroundHelper import com.github.libretube.util.BackgroundHelper
@ -27,12 +31,22 @@ class VideoOptionsDialog(
/** /**
* List that stores the different menu options. In the future could be add more options here. * List that stores the different menu options. In the future could be add more options here.
*/ */
val optionsList = listOf( val optionsList = mutableListOf(
context?.getString(R.string.playOnBackground), context?.getString(R.string.playOnBackground),
context?.getString(R.string.addToPlaylist), context?.getString(R.string.addToPlaylist),
context?.getString(R.string.share) context?.getString(R.string.share)
) )
/**
* Check whether the player is running by observing the notification
*/
val notificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.activeNotifications.forEach {
if (it.id == PLAYER_NOTIFICATION_ID) {
optionsList += context?.getString(R.string.add_to_queue)
}
}
return MaterialAlertDialogBuilder(requireContext()) return MaterialAlertDialogBuilder(requireContext())
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setAdapter( .setAdapter(
@ -68,6 +82,9 @@ class VideoOptionsDialog(
// using parentFragmentManager is important here // using parentFragmentManager is important here
shareDialog.show(parentFragmentManager, ShareDialog::class.java.name) shareDialog.show(parentFragmentManager, ShareDialog::class.java.name)
} }
context?.getString(R.string.add_to_queue) -> {
Globals.videoIds += videoId
}
} }
} }
.show() .show()

View File

@ -27,7 +27,6 @@ import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.os.postDelayed
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -176,11 +175,6 @@ class PlayerFragment : BaseFragment() {
*/ */
private lateinit var nowPlayingNotification: NowPlayingNotification private lateinit var nowPlayingNotification: NowPlayingNotification
/**
* history of played videos in the current lifecycle
*/
val videoIds = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -754,7 +748,7 @@ class PlayerFragment : BaseFragment() {
} }
} }
} }
videoIds += videoId!! Globals.videoIds += videoId!!
} }
run() run()
} }
@ -763,24 +757,10 @@ class PlayerFragment : BaseFragment() {
* set the videoId of the next stream for autoplay * set the videoId of the next stream for autoplay
*/ */
private fun setNextStream() { private fun setNextStream() {
// don't play a video if it got played before already if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId)
var index = 0
while (nextStreamId == null || nextStreamId == videoId!! ||
(
videoIds.contains(nextStreamId) &&
videoIds.indexOf(videoId) > videoIds.indexOf(nextStreamId)
)
) {
nextStreamId = streams.relatedStreams!![index].url.toID()
if (index + 1 < streams.relatedStreams!!.size) index += 1
else break
}
if (playlistId == null) return
if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId!!)
// search for the next videoId in the playlist // search for the next videoId in the playlist
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val nextId = autoPlayHelper.getNextPlaylistVideoId(videoId!!) nextStreamId = autoPlayHelper.getNextVideoId(videoId!!, streams.relatedStreams!!)
if (nextId != null) nextStreamId = nextId
} }
} }
@ -1073,13 +1053,13 @@ class PlayerFragment : BaseFragment() {
// next and previous buttons // next and previous buttons
playerBinding.skipPrev.visibility = if ( playerBinding.skipPrev.visibility = if (
skipButtonsEnabled && videoIds.indexOf(videoId!!) != 0 skipButtonsEnabled && Globals.videoIds.indexOf(videoId!!) != 0
) View.VISIBLE else View.INVISIBLE ) View.VISIBLE else View.INVISIBLE
playerBinding.skipNext.visibility = if (skipButtonsEnabled) View.VISIBLE else View.INVISIBLE playerBinding.skipNext.visibility = if (skipButtonsEnabled) View.VISIBLE else View.INVISIBLE
playerBinding.skipPrev.setOnClickListener { playerBinding.skipPrev.setOnClickListener {
val index = videoIds.indexOf(videoId!!) - 1 val index = Globals.videoIds.indexOf(videoId!!) - 1
videoId = videoIds[index] videoId = Globals.videoIds[index]
playVideo() playVideo()
} }

View File

@ -217,7 +217,7 @@ class BackgroundMode : Service() {
if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId!!) if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId!!)
// search for the next videoId in the playlist // search for the next videoId in the playlist
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val nextId = autoPlayHelper.getNextPlaylistVideoId(videoId) val nextId = autoPlayHelper.getNextVideoId(videoId)
if (nextId != null) nextStreamId = nextId if (nextId != null) nextStreamId = nextId
} }
} }

View File

@ -1,17 +1,55 @@
package com.github.libretube.util package com.github.libretube.util
import com.github.libretube.Globals
import com.github.libretube.obj.StreamItem
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class AutoPlayHelper( class AutoPlayHelper(
private val playlistId: String private val playlistId: String?
) { ) {
private val TAG = "AutoPlayHelper" private val TAG = "AutoPlayHelper"
private val playlistStreamIds = mutableListOf<String>() private val playlistStreamIds = mutableListOf<String>()
private var playlistNextPage: String? = null private var playlistNextPage: String? = null
suspend fun getNextPlaylistVideoId(currentVideoId: String): String? { suspend fun getNextVideoId(
currentVideoId: String,
relatedStreams: List<StreamItem>
): String? {
val currentVideoIndex = Globals.videoIds.indexOf(currentVideoId)
return if (Globals.videoIds.size > currentVideoIndex + 1) {
Globals.videoIds[currentVideoIndex + 1]
} else if (playlistId == null) getNextTrendingVideoId(
currentVideoId,
relatedStreams
) else {
getNextPlaylistVideoId(
currentVideoId
)
}
}
private fun getNextTrendingVideoId(videoId: String, relatedStreams: List<StreamItem>): String? {
// don't play a video if it got played before already
var index = 0
var nextStreamId: String? = null
while (nextStreamId == null ||
(
Globals.videoIds.contains(nextStreamId) &&
Globals.videoIds.indexOf(videoId) > Globals.videoIds.indexOf(
nextStreamId
)
)
) {
nextStreamId = relatedStreams[index].url.toID()
if (index + 1 < relatedStreams.size) index += 1
else break
}
return nextStreamId
}
private suspend fun getNextPlaylistVideoId(currentVideoId: String): String? {
// if the playlists contain the video, then save the next video as next stream // if the playlists contain the video, then save the next video as next stream
if (playlistStreamIds.contains(currentVideoId)) { if (playlistStreamIds.contains(currentVideoId)) {
val index = playlistStreamIds.indexOf(currentVideoId) val index = playlistStreamIds.indexOf(currentVideoId)
@ -24,9 +62,9 @@ class AutoPlayHelper(
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
// fetch the playlists or its nextPage's videos // fetch the playlists or its nextPage's videos
val playlist = val playlist =
if (playlistNextPage == null) RetrofitInstance.authApi.getPlaylist(playlistId) if (playlistNextPage == null) RetrofitInstance.authApi.getPlaylist(playlistId!!)
else RetrofitInstance.authApi.getPlaylistNextPage( else RetrofitInstance.authApi.getPlaylistNextPage(
playlistId, playlistId!!,
playlistNextPage!! playlistNextPage!!
) )
// save the playlist urls to the list // save the playlist urls to the list

View File

@ -297,4 +297,5 @@
<string name="history_size">Maximum history size</string> <string name="history_size">Maximum history size</string>
<string name="unlimited">Unlimited</string> <string name="unlimited">Unlimited</string>
<string name="background_mode">Background mode</string> <string name="background_mode">Background mode</string>
<string name="add_to_queue">Add to queue</string>
</resources> </resources>