2022-09-19 23:37:55 +05:30
|
|
|
package com.github.libretube.util
|
|
|
|
|
2022-11-06 16:20:04 +05:30
|
|
|
import android.util.Log
|
2023-10-14 21:29:22 +05:30
|
|
|
import androidx.media3.common.Player
|
2022-11-20 21:39:07 +05:30
|
|
|
import com.github.libretube.api.PlaylistsHelper
|
2022-10-23 18:13:47 +05:30
|
|
|
import com.github.libretube.api.RetrofitInstance
|
2022-10-23 15:03:35 +05:30
|
|
|
import com.github.libretube.api.obj.StreamItem
|
2022-10-23 18:39:58 +05:30
|
|
|
import com.github.libretube.extensions.move
|
2022-10-23 17:09:15 +05:30
|
|
|
import com.github.libretube.extensions.toID
|
2023-10-14 21:57:18 +05:30
|
|
|
import com.github.libretube.helpers.PlayerHelper
|
2022-10-23 18:13:47 +05:30
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.launch
|
2022-10-23 15:03:35 +05:30
|
|
|
|
2022-09-19 23:37:55 +05:30
|
|
|
object PlayingQueue {
|
2022-10-23 17:09:15 +05:30
|
|
|
private val queue = mutableListOf<StreamItem>()
|
|
|
|
private var currentStream: StreamItem? = null
|
2023-01-16 22:55:12 +05:30
|
|
|
private val scope = CoroutineScope(Dispatchers.IO)
|
2023-01-13 22:50:00 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener that gets called when the user selects an item from the queue
|
|
|
|
*/
|
2022-11-06 16:20:04 +05:30
|
|
|
private var onQueueTapListener: (StreamItem) -> Unit = {}
|
2023-01-13 22:50:00 +05:30
|
|
|
|
2023-10-14 21:29:22 +05:30
|
|
|
var repeatMode: Int = Player.REPEAT_MODE_OFF
|
2022-10-23 17:09:15 +05:30
|
|
|
|
2023-01-14 21:29:21 +05:30
|
|
|
fun clear() = queue.clear()
|
|
|
|
|
2023-08-20 21:56:41 +05:30
|
|
|
/**
|
|
|
|
* @param skipExisting Whether to skip the [streamItem] if it's already part of the queue
|
|
|
|
*/
|
|
|
|
fun add(vararg streamItem: StreamItem, skipExisting: Boolean = false) {
|
2023-01-17 00:32:07 +05:30
|
|
|
for (stream in streamItem) {
|
2023-10-14 21:57:18 +05:30
|
|
|
if ((skipExisting && contains(stream)) || stream.title.isNullOrBlank()) continue
|
|
|
|
|
2023-01-17 00:32:07 +05:30
|
|
|
queue.remove(stream)
|
|
|
|
queue.add(stream)
|
2022-10-23 17:09:15 +05:30
|
|
|
}
|
2022-09-19 23:37:55 +05:30
|
|
|
}
|
|
|
|
|
2022-10-23 17:09:15 +05:30
|
|
|
fun addAsNext(streamItem: StreamItem) {
|
|
|
|
if (currentStream == streamItem) return
|
|
|
|
if (queue.contains(streamItem)) queue.remove(streamItem)
|
2022-09-19 23:37:55 +05:30
|
|
|
queue.add(
|
2022-10-23 17:09:15 +05:30
|
|
|
currentIndex() + 1,
|
2023-06-24 23:27:00 +05:30
|
|
|
streamItem
|
2022-09-19 23:37:55 +05:30
|
|
|
)
|
|
|
|
}
|
2022-09-19 23:43:25 +05:30
|
|
|
|
2023-06-11 18:20:05 +05:30
|
|
|
// return the next item, or if repeating enabled, the first one of the queue
|
2023-10-14 21:29:22 +05:30
|
|
|
fun getNext(): String? {
|
|
|
|
if (repeatMode != Player.REPEAT_MODE_ONE) {
|
|
|
|
queue.getOrNull(currentIndex() + 1)?.url?.toID()?.let { return it }
|
|
|
|
}
|
|
|
|
|
|
|
|
return when (repeatMode) {
|
|
|
|
Player.REPEAT_MODE_ALL -> queue.firstOrNull()?.url?.toID()
|
|
|
|
Player.REPEAT_MODE_ONE -> currentStream?.url?.toID()
|
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
}
|
2022-09-20 01:07:30 +05:30
|
|
|
|
2023-06-11 18:20:05 +05:30
|
|
|
// return the previous item, or if repeating enabled, the last one of the queue
|
2023-10-14 21:29:22 +05:30
|
|
|
fun getPrev(): String? {
|
|
|
|
if (repeatMode != Player.REPEAT_MODE_ONE) {
|
|
|
|
queue.getOrNull(currentIndex() - 1)?.url?.toID()?.let { return it }
|
|
|
|
}
|
|
|
|
|
|
|
|
return when (repeatMode) {
|
|
|
|
Player.REPEAT_MODE_ALL -> queue.lastOrNull()?.url?.toID()
|
|
|
|
Player.REPEAT_MODE_ONE -> currentStream?.url?.toID()
|
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
}
|
2022-09-20 01:07:30 +05:30
|
|
|
|
2023-06-11 18:20:05 +05:30
|
|
|
fun hasPrev() = getPrev() != null
|
2022-11-24 21:19:38 +05:30
|
|
|
|
2023-06-11 18:20:05 +05:30
|
|
|
fun hasNext() = getNext() != null
|
2022-09-20 01:07:30 +05:30
|
|
|
|
2023-08-07 22:29:37 +05:30
|
|
|
fun updateCurrent(streamItem: StreamItem, asFirst: Boolean = true) {
|
2022-10-23 17:09:15 +05:30
|
|
|
currentStream = streamItem
|
2023-08-07 22:29:37 +05:30
|
|
|
if (!contains(streamItem)) {
|
|
|
|
val indexToAdd = if (asFirst) 0 else size()
|
|
|
|
queue.add(indexToAdd, streamItem)
|
|
|
|
}
|
2022-09-19 23:43:25 +05:30
|
|
|
}
|
2022-09-20 01:13:13 +05:30
|
|
|
|
|
|
|
fun isNotEmpty() = queue.isNotEmpty()
|
2022-09-23 17:42:41 +05:30
|
|
|
|
2022-10-23 17:09:15 +05:30
|
|
|
fun isEmpty() = queue.isEmpty()
|
|
|
|
|
|
|
|
fun size() = queue.size
|
2022-09-23 17:42:41 +05:30
|
|
|
|
2023-08-20 21:56:41 +05:30
|
|
|
fun isLast() = currentIndex() == size() - 1
|
|
|
|
|
2023-03-18 21:58:48 +05:30
|
|
|
fun currentIndex(): Int = queue.indexOfFirst {
|
|
|
|
it.url?.toID() == currentStream?.url?.toID()
|
|
|
|
}.takeIf { it >= 0 } ?: 0
|
2022-09-23 17:42:41 +05:30
|
|
|
|
2023-01-13 22:50:00 +05:30
|
|
|
fun getCurrent(): StreamItem? = currentStream
|
|
|
|
|
2022-10-23 18:13:47 +05:30
|
|
|
fun contains(streamItem: StreamItem) = queue.any { it.url?.toID() == streamItem.url?.toID() }
|
2022-10-23 17:09:15 +05:30
|
|
|
|
2023-01-04 23:19:06 +05:30
|
|
|
// only returns a copy of the queue, no write access
|
|
|
|
fun getStreams() = queue.toList()
|
2022-10-23 18:13:47 +05:30
|
|
|
|
2022-10-29 02:34:44 +05:30
|
|
|
fun setStreams(streams: List<StreamItem>) {
|
|
|
|
queue.clear()
|
|
|
|
queue.addAll(streams)
|
|
|
|
}
|
|
|
|
|
2022-10-23 18:24:02 +05:30
|
|
|
fun remove(index: Int) = queue.removeAt(index)
|
|
|
|
|
2022-10-23 18:39:58 +05:30
|
|
|
fun move(from: Int, to: Int) = queue.move(from, to)
|
|
|
|
|
2023-08-09 16:04:24 +05:30
|
|
|
/**
|
|
|
|
* Adds a list of videos to the current queue while updating the position of the current stream
|
2023-10-14 21:57:18 +05:30
|
|
|
* @param isMainList whether the videos are part of the list that initially has been used to
|
2023-08-09 16:04:24 +05:30
|
|
|
* start the queue, either from a channel or playlist. If it's false, the current stream won't
|
|
|
|
* be touched, since it's an independent list.
|
|
|
|
*/
|
|
|
|
private fun addToQueueAsync(
|
|
|
|
streams: List<StreamItem>,
|
|
|
|
currentStreamItem: StreamItem? = null,
|
|
|
|
isMainList: Boolean = true
|
|
|
|
) {
|
|
|
|
if (!isMainList) {
|
|
|
|
add(*streams.toTypedArray())
|
|
|
|
return
|
|
|
|
}
|
2023-08-07 22:29:37 +05:30
|
|
|
val currentStream = currentStreamItem ?: this.currentStream
|
|
|
|
// if the stream already got added to the queue earlier, although it's not yet
|
|
|
|
// been found in the playlist, remove it and re-add it later
|
2023-10-14 21:57:18 +05:30
|
|
|
var reAddStream = true
|
|
|
|
if (currentStream != null && streams.includes(currentStream)) {
|
|
|
|
queue.removeAll { it.url?.toID() == currentStream.url?.toID() }
|
|
|
|
reAddStream = false
|
2023-08-07 22:29:37 +05:30
|
|
|
}
|
|
|
|
// add all new stream items to the queue
|
|
|
|
add(*streams.toTypedArray())
|
2023-10-14 21:57:18 +05:30
|
|
|
|
|
|
|
if (currentStream != null && reAddStream) {
|
|
|
|
// re-add the stream to the end of the queue
|
|
|
|
updateCurrent(currentStream, false)
|
2023-08-07 22:29:37 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-09 16:04:24 +05:30
|
|
|
private fun fetchMoreFromPlaylist(playlistId: String, nextPage: String?, isMainList: Boolean) {
|
2023-08-07 22:29:37 +05:30
|
|
|
var playlistNextPage = nextPage
|
|
|
|
scope.launch(Dispatchers.IO) {
|
2022-10-23 18:13:47 +05:30
|
|
|
while (playlistNextPage != null) {
|
2023-08-07 22:29:37 +05:30
|
|
|
RetrofitInstance.authApi.getPlaylistNextPage(playlistId, playlistNextPage!!).run {
|
2023-08-09 16:04:24 +05:30
|
|
|
addToQueueAsync(relatedStreams, isMainList = isMainList)
|
2022-10-23 18:13:47 +05:30
|
|
|
playlistNextPage = this.nextpage
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-09 16:04:24 +05:30
|
|
|
fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem?) {
|
2023-08-07 22:29:37 +05:30
|
|
|
scope.launch(Dispatchers.IO) {
|
|
|
|
runCatching {
|
2022-12-01 19:07:44 +05:30
|
|
|
val playlist = PlaylistsHelper.getPlaylist(playlistId)
|
2023-08-09 16:04:24 +05:30
|
|
|
val isMainList = newCurrentStream != null
|
|
|
|
addToQueueAsync(playlist.relatedStreams, newCurrentStream, isMainList)
|
2022-11-20 21:39:07 +05:30
|
|
|
if (playlist.nextpage == null) return@launch
|
2023-08-09 16:04:24 +05:30
|
|
|
fetchMoreFromPlaylist(playlistId, playlist.nextpage, isMainList)
|
2022-10-23 18:13:47 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-06 16:20:04 +05:30
|
|
|
|
2022-12-02 19:15:52 +05:30
|
|
|
private fun fetchMoreFromChannel(channelId: String, nextPage: String?) {
|
2023-08-07 22:29:37 +05:30
|
|
|
var channelNextPage = nextPage
|
|
|
|
scope.launch(Dispatchers.IO) {
|
2023-10-14 21:57:18 +05:30
|
|
|
runCatching {
|
|
|
|
while (channelNextPage != null) {
|
|
|
|
RetrofitInstance.api.getChannelNextPage(channelId, nextPage!!).run {
|
|
|
|
addToQueueAsync(relatedStreams)
|
|
|
|
channelNextPage = this.nextpage
|
|
|
|
}
|
2022-12-02 19:15:52 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-20 21:56:41 +05:30
|
|
|
private fun insertChannel(channelId: String, newCurrentStream: StreamItem) {
|
2023-08-07 22:29:37 +05:30
|
|
|
scope.launch(Dispatchers.IO) {
|
2023-01-16 22:55:12 +05:30
|
|
|
runCatching {
|
2022-12-02 19:15:52 +05:30
|
|
|
val channel = RetrofitInstance.api.getChannel(channelId)
|
2023-08-07 22:29:37 +05:30
|
|
|
addToQueueAsync(channel.relatedStreams, newCurrentStream)
|
2022-12-02 19:15:52 +05:30
|
|
|
if (channel.nextpage == null) return@launch
|
|
|
|
fetchMoreFromChannel(channelId, channel.nextpage)
|
2023-01-16 22:55:12 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun insertByVideoId(videoId: String) {
|
|
|
|
scope.launch {
|
|
|
|
runCatching {
|
|
|
|
val streams = RetrofitInstance.api.getStreams(videoId.toID())
|
|
|
|
add(streams.toStreamItem(videoId))
|
2022-12-02 19:15:52 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-14 21:57:18 +05:30
|
|
|
fun updateQueue(streamItem: StreamItem, playlistId: String?, channelId: String?, relatedStreams: List<StreamItem> = emptyList()) {
|
2023-08-21 23:39:34 +05:30
|
|
|
if (playlistId != null) {
|
|
|
|
insertPlaylist(playlistId, streamItem)
|
|
|
|
} else if (channelId != null) {
|
|
|
|
insertChannel(channelId, streamItem)
|
2023-10-14 21:57:18 +05:30
|
|
|
} else if (relatedStreams.isNotEmpty()) {
|
|
|
|
insertRelatedStreams(relatedStreams)
|
2023-08-21 23:39:34 +05:30
|
|
|
}
|
2023-10-14 21:57:18 +05:30
|
|
|
updateCurrent(streamItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun insertRelatedStreams(streams: List<StreamItem>) {
|
|
|
|
if (!PlayerHelper.autoInsertRelatedVideos) return
|
|
|
|
add(*streams.toTypedArray(), skipExisting = true)
|
2023-08-20 21:56:41 +05:30
|
|
|
}
|
|
|
|
|
2022-11-06 16:20:04 +05:30
|
|
|
fun onQueueItemSelected(index: Int) {
|
|
|
|
try {
|
|
|
|
val streamItem = queue[index]
|
|
|
|
updateCurrent(streamItem)
|
|
|
|
onQueueTapListener.invoke(streamItem)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Log.e("Queue on tap", "lifecycle already ended")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setOnQueueTapListener(listener: (StreamItem) -> Unit) {
|
|
|
|
onQueueTapListener = listener
|
|
|
|
}
|
|
|
|
|
2022-11-17 21:15:07 +05:30
|
|
|
fun resetToDefaults() {
|
2023-10-14 21:29:22 +05:30
|
|
|
repeatMode = Player.REPEAT_MODE_OFF
|
2022-11-06 16:20:04 +05:30
|
|
|
onQueueTapListener = {}
|
|
|
|
}
|
2023-08-07 22:29:37 +05:30
|
|
|
|
|
|
|
private fun List<StreamItem>.includes(item: StreamItem) = any {
|
|
|
|
it.url?.toID() == item.url?.toID()
|
|
|
|
}
|
2022-09-19 23:37:55 +05:30
|
|
|
}
|