Replace the autoPlayHelper with the PlayingQueue

This commit is contained in:
Bnyro 2022-10-23 14:43:47 +02:00
parent 41162fafac
commit 02d4321a2d
7 changed files with 74 additions and 151 deletions

View File

@ -24,9 +24,7 @@ import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHelper import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.awaitQuery import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toStreamItem import com.github.libretube.extensions.toStreamItem
import com.github.libretube.util.AutoPlayHelper
import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.PlayerHelper import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
@ -81,16 +79,6 @@ class BackgroundMode : Service() {
*/ */
private lateinit var nowPlayingNotification: NowPlayingNotification private lateinit var nowPlayingNotification: NowPlayingNotification
/**
* The [videoId] of the next stream for autoplay
*/
private var nextStreamId: String? = null
/**
* Helper for finding the next video in the playlist
*/
private lateinit var autoPlayHelper: AutoPlayHelper
/** /**
* Autoplay Preference * Autoplay Preference
*/ */
@ -133,9 +121,6 @@ class BackgroundMode : Service() {
playlistId = intent.getStringExtra(IntentData.playlistId) playlistId = intent.getStringExtra(IntentData.playlistId)
val position = intent.getLongExtra(IntentData.position, 0L) val position = intent.getLongExtra(IntentData.position, 0L)
// initialize the playlist autoPlay Helper
autoPlayHelper = AutoPlayHelper(playlistId)
// play the audio in the background // play the audio in the background
loadAudio(videoId, position) loadAudio(videoId, position)
@ -169,7 +154,12 @@ class BackgroundMode : Service() {
return@launch return@launch
} }
PlayingQueue.updateCurrent(streams.toStreamItem(videoId)) // add the playlist video to the queue
if (playlistId != null && PlayingQueue.isEmpty()) {
PlayingQueue.insertPlaylist(playlistId!!, streams.toStreamItem(videoId))
} else {
PlayingQueue.updateCurrent(streams.toStreamItem(videoId))
}
handler.post { handler.post {
playAudio(seekToPosition) playAudio(seekToPosition)
@ -221,8 +211,6 @@ class BackgroundMode : Service() {
player?.setPlaybackSpeed(playbackSpeed) player?.setPlaybackSpeed(playbackSpeed)
fetchSponsorBlockSegments() fetchSponsorBlockSegments()
if (PlayerHelper.autoPlayEnabled) setNextStream()
} }
/** /**
@ -271,32 +259,16 @@ class BackgroundMode : Service() {
}) })
} }
/**
* set the videoId of the next stream for autoplay
*/
private fun setNextStream() {
if (streams!!.relatedStreams!!.isNotEmpty()) {
nextStreamId = streams?.relatedStreams!![0].url!!.toID()
}
if (playlistId == null) return
if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId!!)
// search for the next videoId in the playlist
CoroutineScope(Dispatchers.IO).launch {
nextStreamId = autoPlayHelper.getNextVideoId(videoId)
}
}
/** /**
* Plays the first related video to the current (used when the playback of the current video ended) * Plays the first related video to the current (used when the playback of the current video ended)
*/ */
private fun playNextVideo() { private fun playNextVideo() {
if (nextStreamId == null || nextStreamId == videoId) return val nextVideo = PlayingQueue.getNext()
val nextQueueVideo = PlayingQueue.getNext()
if (nextQueueVideo != null) nextStreamId = nextQueueVideo
// play new video on background // play new video on background
this.videoId = nextStreamId!! if (nextVideo != null) {
this.videoId = nextVideo
}
this.segmentData = null this.segmentData = null
loadAudio(videoId) loadAudio(videoId)
} }

View File

@ -4,14 +4,12 @@ import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.databinding.QueueRowBinding import com.github.libretube.databinding.QueueRowBinding
import com.github.libretube.ui.viewholders.PlayingQueueViewHolder import com.github.libretube.ui.viewholders.PlayingQueueViewHolder
import com.github.libretube.util.ImageHelper import com.github.libretube.util.ImageHelper
import com.github.libretube.util.PlayingQueue
class PlayingQueueAdapter( class PlayingQueueAdapter() : RecyclerView.Adapter<PlayingQueueViewHolder>() {
private val items: List<StreamItem>
) : RecyclerView.Adapter<PlayingQueueViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlayingQueueViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlayingQueueViewHolder {
val binding = QueueRowBinding.inflate( val binding = QueueRowBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
@ -22,16 +20,18 @@ class PlayingQueueAdapter(
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return items.size return PlayingQueue.size()
} }
override fun onBindViewHolder(holder: PlayingQueueViewHolder, position: Int) { override fun onBindViewHolder(holder: PlayingQueueViewHolder, position: Int) {
val streamItem = items[position] val streamItem = PlayingQueue.getStreams()[position]
holder.binding.apply { holder.binding.apply {
ImageHelper.loadImage(streamItem.thumbnail, thumbnail) ImageHelper.loadImage(streamItem.thumbnail, thumbnail)
title.text = streamItem.title title.text = streamItem.title
uploader.text = streamItem.uploaderName uploader.text = streamItem.uploaderName
duration.text = streamItem.duration?.let { DateUtils.formatElapsedTime(it) } duration.text = streamItem.duration?.let {
DateUtils.formatElapsedTime(it)
}
} }
} }
} }

View File

@ -20,7 +20,6 @@ import com.github.libretube.ui.adapters.ChannelAdapter
import com.github.libretube.ui.adapters.TrendingAdapter import com.github.libretube.ui.adapters.TrendingAdapter
import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.util.LocaleHelper import com.github.libretube.util.LocaleHelper
import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import retrofit2.HttpException import retrofit2.HttpException

View File

@ -67,7 +67,6 @@ import com.github.libretube.ui.dialogs.DownloadDialog
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.sheets.PlayingQueueSheet import com.github.libretube.ui.sheets.PlayingQueueSheet
import com.github.libretube.ui.views.BottomSheet import com.github.libretube.ui.views.BottomSheet
import com.github.libretube.util.AutoPlayHelper
import com.github.libretube.util.BackgroundHelper import com.github.libretube.util.BackgroundHelper
import com.github.libretube.util.ImageHelper import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.NowPlayingNotification
@ -155,12 +154,6 @@ class PlayerFragment : BaseFragment() {
private var token = PreferenceHelper.getToken() private var token = PreferenceHelper.getToken()
private var videoShownInExternalPlayer = false private var videoShownInExternalPlayer = false
/**
* for autoplay
*/
private var nextStreamId: String? = null
private lateinit var autoPlayHelper: AutoPlayHelper
/** /**
* for the player notification * for the player notification
*/ */
@ -649,11 +642,20 @@ class PlayerFragment : BaseFragment() {
return@launchWhenCreated return@launchWhenCreated
} }
PlayingQueue.updateCurrent(streams.toStreamItem(videoId!!)) if (PlayingQueue.size() == 0) {
CoroutineScope(Dispatchers.IO).launch {
if (PlayingQueue.size() <= 1) PlayingQueue.add( if (playlistId != null) {
*streams.relatedStreams.orEmpty().toTypedArray() PlayingQueue.insertPlaylist(playlistId!!, streams.toStreamItem(videoId!!))
) } else {
PlayingQueue.updateCurrent(streams.toStreamItem(videoId!!))
PlayingQueue.add(
*streams.relatedStreams.orEmpty().toTypedArray()
)
}
}
} else {
PlayingQueue.updateCurrent(streams.toStreamItem(videoId!!))
}
runOnUiThread { runOnUiThread {
// hide the button to skip SponsorBlock segments manually // hide the button to skip SponsorBlock segments manually
@ -678,8 +680,6 @@ class PlayerFragment : BaseFragment() {
if (PlayerHelper.sponsorBlockEnabled) fetchSponsorBlockSegments() if (PlayerHelper.sponsorBlockEnabled) fetchSponsorBlockSegments()
// show comments if related streams disabled // show comments if related streams disabled
if (!PlayerHelper.relatedStreamsEnabled) toggleComments() if (!PlayerHelper.relatedStreamsEnabled) toggleComments()
// prepare for autoplay
if (binding.player.autoplayEnabled) setNextStream()
// add the video to the watch history // add the video to the watch history
if (PlayerHelper.watchHistoryEnabled) { if (PlayerHelper.watchHistoryEnabled) {
@ -692,17 +692,6 @@ class PlayerFragment : BaseFragment() {
} }
} }
/**
* set the videoId of the next stream for autoplay
*/
private fun setNextStream() {
if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId)
// search for the next videoId in the playlist
lifecycleScope.launchWhenCreated {
nextStreamId = autoPlayHelper.getNextVideoId(videoId!!)
}
}
/** /**
* fetch the segments for SponsorBlock * fetch the segments for SponsorBlock
*/ */
@ -768,17 +757,14 @@ class PlayerFragment : BaseFragment() {
// used for autoplay and skipping to next video // used for autoplay and skipping to next video
private fun playNextVideo() { private fun playNextVideo() {
if (nextStreamId == null) return val nextVideo = PlayingQueue.getNext()
// check whether there is a new video in the queue
val nextQueueVideo = PlayingQueue.getNext()
if (nextQueueVideo != null) nextStreamId = nextQueueVideo
// by making sure that the next and the current video aren't the same // by making sure that the next and the current video aren't the same
saveWatchPosition() saveWatchPosition()
// forces the comments to reload for the new video // forces the comments to reload for the new video
commentsLoaded = false commentsLoaded = false
binding.commentsRecView.adapter = null binding.commentsRecView.adapter = null
// save the id of the next stream as videoId and load the next video // save the id of the next stream as videoId and load the next video
videoId = nextStreamId videoId = nextVideo
playVideo() playVideo()
} }
@ -876,7 +862,6 @@ class PlayerFragment : BaseFragment() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
if ( if (
playbackState == Player.STATE_ENDED && playbackState == Player.STATE_ENDED &&
nextStreamId != null &&
!transitioning && !transitioning &&
binding.player.autoplayEnabled binding.player.autoplayEnabled
) { ) {

View File

@ -7,7 +7,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.databinding.BottomSheetBinding import com.github.libretube.databinding.BottomSheetBinding
import com.github.libretube.ui.adapters.PlayingQueueAdapter import com.github.libretube.ui.adapters.PlayingQueueAdapter
import com.github.libretube.util.PlayingQueue
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class PlayingQueueSheet : BottomSheetDialogFragment() { class PlayingQueueSheet : BottomSheetDialogFragment() {
@ -26,8 +25,6 @@ class PlayingQueueSheet : BottomSheetDialogFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.optionsRecycler.layoutManager = LinearLayoutManager(context) binding.optionsRecycler.layoutManager = LinearLayoutManager(context)
binding.optionsRecycler.adapter = PlayingQueueAdapter( binding.optionsRecycler.adapter = PlayingQueueAdapter()
PlayingQueue.getStreams()
)
} }
} }

View File

@ -1,68 +0,0 @@
package com.github.libretube.util
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.extensions.toID
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AutoPlayHelper(
private val playlistId: String?
) {
private val playlistStreamIds = mutableListOf<String>()
private var playlistNextPage: String? = null
/**
* get the id of the next video to be played
*/
suspend fun getNextVideoId(
currentVideoId: String
): String? {
return if (playlistId == null) {
null
} else {
getNextPlaylistVideoId(
currentVideoId
)
}
}
/**
* get the videoId of the next video in a playlist
*/
private suspend fun getNextPlaylistVideoId(currentVideoId: String): String? {
// if the playlists contain the video, then save the next video as next stream
if (playlistStreamIds.contains(currentVideoId)) {
val index = playlistStreamIds.indexOf(currentVideoId)
// check whether there's a next video
return if (index + 1 < playlistStreamIds.size) {
playlistStreamIds[index + 1]
} else if (playlistNextPage == null) {
null
} else {
getNextPlaylistVideoId(currentVideoId)
}
} else if (playlistStreamIds.isEmpty() || playlistNextPage != null) {
// fetch the next page of the playlist
return withContext(Dispatchers.IO) {
// fetch the playlists or its nextPage's videos
val playlist =
if (playlistNextPage == null) {
RetrofitInstance.authApi.getPlaylist(playlistId!!)
} else {
RetrofitInstance.authApi.getPlaylistNextPage(
playlistId!!,
playlistNextPage!!
)
}
// save the playlist urls to the list
playlistStreamIds += playlist.relatedStreams!!.map { it.url!!.toID() }
// save playlistNextPage for usage if video is not contained
playlistNextPage = playlist.nextpage
return@withContext getNextPlaylistVideoId(currentVideoId)
}
}
// return null when no nextPage is found
return null
}
}

View File

@ -1,7 +1,11 @@
package com.github.libretube.util package com.github.libretube.util
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.StreamItem
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object PlayingQueue { object PlayingQueue {
private val queue = mutableListOf<StreamItem>() private val queue = mutableListOf<StreamItem>()
@ -44,7 +48,7 @@ object PlayingQueue {
fun updateCurrent(streamItem: StreamItem) { fun updateCurrent(streamItem: StreamItem) {
currentStream = streamItem currentStream = streamItem
queue.add(streamItem) if (!contains(streamItem)) queue.add(streamItem)
} }
fun isNotEmpty() = queue.isNotEmpty() fun isNotEmpty() = queue.isNotEmpty()
@ -57,7 +61,41 @@ object PlayingQueue {
private fun currentIndex() = queue.indexOf(currentStream) private fun currentIndex() = queue.indexOf(currentStream)
fun contains(streamItem: StreamItem) = queue.contains(streamItem) fun contains(streamItem: StreamItem) = queue.any { it.url?.toID() == streamItem.url?.toID() }
fun getStreams() = queue fun getStreams() = queue
private fun fetchMoreFromPlaylist(playlistId: String, nextPage: String?) {
var playlistNextPage: String? = nextPage
CoroutineScope(Dispatchers.IO).launch {
while (playlistNextPage != null) {
RetrofitInstance.authApi.getPlaylistNextPage(
playlistId,
playlistNextPage!!
).apply {
add(
*this.relatedStreams.orEmpty().toTypedArray()
)
playlistNextPage = this.nextpage
}
}
}
}
fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitInstance.authApi.getPlaylist(playlistId)
add(
*response.relatedStreams
.orEmpty()
.toTypedArray()
)
updateCurrent(newCurrentStream)
fetchMoreFromPlaylist(playlistId, response.nextpage)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
} }