Merge pull request #1328 from Bnyro/master

Improved playing queue
This commit is contained in:
Bnyro 2022-09-19 22:22:32 +02:00 committed by GitHub
commit de69dd3795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 107 additions and 81 deletions

View File

@ -1,12 +0,0 @@
package com.github.libretube
/**
* Global variables can be stored here
*/
object Globals {
// for downloads
var IS_DOWNLOAD_RUNNING = false
// history of played videos in the current lifecycle
val playingQueue = mutableListOf<String>()
}

View File

@ -56,9 +56,9 @@ object DatabaseHelper {
fun removeWatchPosition(videoId: String) { fun removeWatchPosition(videoId: String) {
Thread { Thread {
Database.watchPositionDao().delete( Database.watchPositionDao().findById(videoId)?.let {
Database.watchPositionDao().findById(videoId) Database.watchPositionDao().delete(it)
) }
}.start() }.start()
} }

View File

@ -13,7 +13,7 @@ interface WatchPositionDao {
fun getAll(): List<WatchPosition> fun getAll(): List<WatchPosition>
@Query("SELECT * FROM watchPosition WHERE videoId LIKE :videoId LIMIT 1") @Query("SELECT * FROM watchPosition WHERE videoId LIKE :videoId LIMIT 1")
fun findById(videoId: String): WatchPosition fun findById(videoId: String): WatchPosition?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(vararg watchPositions: WatchPosition) fun insertAll(vararg watchPositions: WatchPosition)

View File

@ -14,7 +14,7 @@ fun View?.setWatchProgressLength(videoId: String, duration: Long) {
Thread { Thread {
try { try {
progress = Database.watchPositionDao().findById(videoId).position progress = Database.watchPositionDao().findById(videoId)?.position
} catch (e: Exception) { } catch (e: Exception) {
progress = null progress = null
} }

View File

@ -35,7 +35,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.Globals
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.activities.MainActivity import com.github.libretube.activities.MainActivity
import com.github.libretube.adapters.ChaptersAdapter import com.github.libretube.adapters.ChaptersAdapter
@ -67,12 +66,14 @@ import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments import com.github.libretube.obj.Segments
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.services.BackgroundMode import com.github.libretube.services.BackgroundMode
import com.github.libretube.services.DownloadService
import com.github.libretube.util.AutoPlayHelper 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.NetworkHelper import com.github.libretube.util.NetworkHelper
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.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.DefaultLoadControl
@ -210,7 +211,7 @@ class PlayerFragment : BaseFragment() {
context?.hideKeyboard(view) context?.hideKeyboard(view)
// clear the playing queue // clear the playing queue
Globals.playingQueue.clear() PlayingQueue.clear()
setUserPrefs() setUserPrefs()
@ -686,6 +687,9 @@ class PlayerFragment : BaseFragment() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
try { try {
// clear the playing queue
PlayingQueue.clear()
saveWatchPosition() saveWatchPosition()
nowPlayingNotification.destroy() nowPlayingNotification.destroy()
activity?.requestedOrientation = activity?.requestedOrientation =
@ -695,6 +699,7 @@ class PlayerFragment : BaseFragment() {
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace()
} }
} }
@ -752,8 +757,9 @@ class PlayerFragment : BaseFragment() {
} }
private fun playVideo() { private fun playVideo() {
Globals.playingQueue += videoId!!
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
PlayingQueue.updateCurrent(videoId!!)
streams = try { streams = try {
RetrofitInstance.api.getStreams(videoId!!) RetrofitInstance.api.getStreams(videoId!!)
} catch (e: IOException) { } catch (e: IOException) {
@ -861,7 +867,7 @@ class PlayerFragment : BaseFragment() {
var position: Long? = null var position: Long? = null
Thread { Thread {
try { try {
position = Database.watchPositionDao().findById(videoId!!).position position = Database.watchPositionDao().findById(videoId!!)?.position
// position is almost the end of the video => don't seek, start from beginning // position is almost the end of the video => don't seek, start from beginning
if (position!! > streams.duration!! * 1000 * 0.9) position = null if (position!! > streams.duration!! * 1000 * 0.9) position = null
} catch (e: Exception) { } catch (e: Exception) {
@ -875,7 +881,7 @@ class PlayerFragment : BaseFragment() {
private fun playNextVideo() { private fun playNextVideo() {
if (nextStreamId == null) return if (nextStreamId == null) return
// check whether there is a new video in the queue // check whether there is a new video in the queue
val nextQueueVideo = autoPlayHelper.getNextPlayingQueueVideoId(videoId!!) val nextQueueVideo = PlayingQueue.getNext()
if (nextQueueVideo != null) nextStreamId = nextQueueVideo 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()
@ -1023,7 +1029,7 @@ class PlayerFragment : BaseFragment() {
if (response.duration > 0) { if (response.duration > 0) {
// download clicked // download clicked
binding.relPlayerDownload.setOnClickListener { binding.relPlayerDownload.setOnClickListener {
if (!Globals.IS_DOWNLOAD_RUNNING) { if (!DownloadService.IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog(videoId!!) val newFragment = DownloadDialog(videoId!!)
newFragment.show(childFragmentManager, DownloadDialog::class.java.name) newFragment.show(childFragmentManager, DownloadDialog::class.java.name)
} else { } else {
@ -1105,7 +1111,7 @@ class PlayerFragment : BaseFragment() {
// next and previous buttons // next and previous buttons
playerBinding.skipPrev.visibility = if ( playerBinding.skipPrev.visibility = if (
skipButtonsEnabled && Globals.playingQueue.indexOf(videoId!!) != 0 skipButtonsEnabled && PlayingQueue.hasPrev()
) { ) {
View.VISIBLE View.VISIBLE
} else { } else {
@ -1114,8 +1120,7 @@ class PlayerFragment : BaseFragment() {
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 = Globals.playingQueue.indexOf(videoId!!) - 1 videoId = PlayingQueue.getPrev()
videoId = Globals.playingQueue[index]
playVideo() playVideo()
} }

View File

@ -11,7 +11,6 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.widget.Toast import android.widget.Toast
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.Globals
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
@ -25,6 +24,7 @@ import com.github.libretube.obj.Streams
import com.github.libretube.util.AutoPlayHelper 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.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayer
@ -119,7 +119,7 @@ class BackgroundMode : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
try { try {
// clear the playing queue // clear the playing queue
Globals.playingQueue.clear() PlayingQueue.clear()
// get the intent arguments // get the intent arguments
videoId = intent?.getStringExtra(IntentData.videoId)!! videoId = intent?.getStringExtra(IntentData.videoId)!!
@ -145,7 +145,7 @@ class BackgroundMode : Service() {
seekToPosition: Long = 0 seekToPosition: Long = 0
) { ) {
// append the video to the playing queue // append the video to the playing queue
Globals.playingQueue += videoId PlayingQueue.add(videoId)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
streams = RetrofitInstance.api.getStreams(videoId) streams = RetrofitInstance.api.getStreams(videoId)
@ -162,6 +162,8 @@ class BackgroundMode : Service() {
private fun playAudio( private fun playAudio(
seekToPosition: Long seekToPosition: Long
) { ) {
PlayingQueue.updateCurrent(videoId)
initializePlayer() initializePlayer()
setMediaItem() setMediaItem()
@ -247,7 +249,7 @@ class BackgroundMode : Service() {
*/ */
private fun playNextVideo() { private fun playNextVideo() {
if (nextStreamId == null || nextStreamId == videoId) return if (nextStreamId == null || nextStreamId == videoId) return
val nextQueueVideo = autoPlayHelper.getNextPlayingQueueVideoId(videoId) val nextQueueVideo = PlayingQueue.getNext()
if (nextQueueVideo != null) nextStreamId = nextQueueVideo if (nextQueueVideo != null) nextStreamId = nextQueueVideo
// play new video on background // play new video on background
@ -331,6 +333,9 @@ class BackgroundMode : Service() {
* destroy the [BackgroundMode] foreground service * destroy the [BackgroundMode] foreground service
*/ */
override fun onDestroy() { override fun onDestroy() {
// clear the playing queue
PlayingQueue.clear()
// called when the user pressed stop in the notification // called when the user pressed stop in the notification
// stop the service from being in the foreground and remove the notification // stop the service from being in the foreground and remove the notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

View File

@ -11,7 +11,6 @@ import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.github.libretube.Globals
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.DOWNLOAD_CHANNEL_ID import com.github.libretube.constants.DOWNLOAD_CHANNEL_ID
import com.github.libretube.constants.DOWNLOAD_FAILURE_NOTIFICATION_ID import com.github.libretube.constants.DOWNLOAD_FAILURE_NOTIFICATION_ID
@ -34,7 +33,7 @@ class DownloadService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Globals.IS_DOWNLOAD_RUNNING = true IS_DOWNLOAD_RUNNING = true
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -175,11 +174,16 @@ class DownloadService : Service() {
try { try {
unregisterReceiver(onDownloadComplete) unregisterReceiver(onDownloadComplete)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace()
} }
Globals.IS_DOWNLOAD_RUNNING = false IS_DOWNLOAD_RUNNING = false
stopService(Intent(this, DownloadService::class.java)) stopService(Intent(this, DownloadService::class.java))
super.onDestroy() super.onDestroy()
} }
companion object {
var IS_DOWNLOAD_RUNNING = false
}
} }

View File

@ -1,18 +1,14 @@
package com.github.libretube.sheets package com.github.libretube.sheets
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import com.github.libretube.Globals
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PLAYER_NOTIFICATION_ID
import com.github.libretube.dialogs.AddToPlaylistDialog import com.github.libretube.dialogs.AddToPlaylistDialog
import com.github.libretube.dialogs.DownloadDialog import com.github.libretube.dialogs.DownloadDialog
import com.github.libretube.dialogs.ShareDialog import com.github.libretube.dialogs.ShareDialog
import com.github.libretube.util.BackgroundHelper import com.github.libretube.util.BackgroundHelper
import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.github.libretube.views.BottomSheet import com.github.libretube.views.BottomSheet
@ -41,21 +37,13 @@ class VideoOptionsBottomSheet(
) )
} }
/** /**
* Check whether the player is running by observing the notification * Check whether the player is running and add queue options
*/ */
try { if (PlayingQueue.isNotEmpty()) {
val notificationManager = optionsList += context?.getString(R.string.play_next)!!
context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager optionsList += context?.getString(R.string.add_to_queue)!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notificationManager.activeNotifications.forEach {
if (it.id == PLAYER_NOTIFICATION_ID) {
optionsList += context?.getString(R.string.add_to_queue)!!
}
}
}
} catch (e: Exception) {
e.printStackTrace()
} }
setSimpleItems(optionsList) { which -> setSimpleItems(optionsList) { which ->
@ -89,8 +77,11 @@ class VideoOptionsBottomSheet(
// 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.play_next) -> {
PlayingQueue.playNext(videoId)
}
context?.getString(R.string.add_to_queue) -> { context?.getString(R.string.add_to_queue) -> {
Globals.playingQueue += videoId PlayingQueue.add(videoId)
} }
} }
} }

View File

@ -1,6 +1,5 @@
package com.github.libretube.util package com.github.libretube.util
import com.github.libretube.Globals
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
@ -21,12 +20,8 @@ class AutoPlayHelper(
currentVideoId: String, currentVideoId: String,
relatedStreams: List<StreamItem>? relatedStreams: List<StreamItem>?
): String? { ): String? {
return if (Globals.playingQueue.last() != currentVideoId) { return if (playlistId == null) {
val currentVideoIndex = Globals.playingQueue.indexOf(currentVideoId)
Globals.playingQueue[currentVideoIndex + 1]
} else if (playlistId == null) {
getNextTrendingVideoId( getNextTrendingVideoId(
currentVideoId,
relatedStreams relatedStreams
) )
} else { } else {
@ -40,21 +35,13 @@ class AutoPlayHelper(
* get the id of the next related video * get the id of the next related video
*/ */
private fun getNextTrendingVideoId( private fun getNextTrendingVideoId(
videoId: String,
relatedStreams: List<StreamItem>? relatedStreams: List<StreamItem>?
): String? { ): String? {
// don't play a video if it got played before already // don't play a video if it got played before already
if (relatedStreams == null || relatedStreams.isEmpty()) return null if (relatedStreams == null || relatedStreams.isEmpty()) return null
var index = 0 var index = 0
var nextStreamId: String? = null var nextStreamId: String? = null
while (nextStreamId == null || while (nextStreamId == null || PlayingQueue.containsBefore(nextStreamId)) {
(
Globals.playingQueue.contains(nextStreamId) &&
Globals.playingQueue.indexOf(videoId) > Globals.playingQueue.indexOf(
nextStreamId
)
)
) {
nextStreamId = relatedStreams[index].url!!.toID() nextStreamId = relatedStreams[index].url!!.toID()
if (index + 1 < relatedStreams.size) { if (index + 1 < relatedStreams.size) {
index += 1 index += 1
@ -103,18 +90,4 @@ class AutoPlayHelper(
// return null when no nextPage is found // return null when no nextPage is found
return null return null
} }
/**
* get the videoId of the next video in the playing queue
*/
fun getNextPlayingQueueVideoId(
currentVideoId: String
): String? {
return if (Globals.playingQueue.last() != currentVideoId) {
val currentVideoIndex = Globals.playingQueue.indexOf(currentVideoId)
Globals.playingQueue[currentVideoIndex + 1]
} else {
null
}
}
} }

View File

@ -166,6 +166,7 @@ class NowPlayingNotification(
Context.NOTIFICATION_SERVICE Context.NOTIFICATION_SERVICE
) as NotificationManager ) as NotificationManager
notificationManager.cancel(PLAYER_NOTIFICATION_ID) notificationManager.cancel(PLAYER_NOTIFICATION_ID)
player.stop()
player.release() player.release()
} }
} }

View File

@ -0,0 +1,58 @@
package com.github.libretube.util
object PlayingQueue {
private val queue = mutableListOf<String>()
private var currentVideoId: String? = null
fun clear() {
queue.clear()
}
fun add(videoId: String) {
if (currentVideoId == videoId) return
if (queue.contains(videoId)) queue.remove(videoId)
queue.add(videoId)
}
fun playNext(videoId: String) {
if (currentVideoId == videoId) return
if (queue.contains(videoId)) queue.remove(videoId)
queue.add(
queue.indexOf(currentVideoId) + 1,
videoId
)
}
fun getNext(): String? {
val currentIndex = queue.indexOf(currentVideoId)
return if (currentIndex >= queue.size) {
null
} else {
queue[currentIndex + 1]
}
}
fun getPrev(): String? {
val index = queue.indexOf(currentVideoId)
return if (index > 0) queue[index - 1] else null
}
fun hasPrev(): Boolean {
return queue.indexOf(currentVideoId) > 0
}
fun contains(videoId: String): Boolean {
return queue.contains(videoId)
}
fun containsBefore(videoId: String): Boolean {
return queue.contains(videoId) && queue.indexOf(videoId) < queue.indexOf(currentVideoId)
}
fun updateCurrent(videoId: String) {
currentVideoId = videoId
queue.add(videoId)
}
fun isNotEmpty() = queue.isNotEmpty()
}

View File

@ -329,6 +329,7 @@
<string name="backup_customInstances">Custom Instances</string> <string name="backup_customInstances">Custom Instances</string>
<string name="save_feed">Load feed in background</string> <string name="save_feed">Load feed in background</string>
<string name="save_feed_summary">Load the subscription feed in the background and prevent it from being auto-refreshed.</string> <string name="save_feed_summary">Load the subscription feed in the background and prevent it from being auto-refreshed.</string>
<string name="play_next">Play next</string>
<!-- Notification channel strings --> <!-- Notification channel strings -->
<string name="download_channel_name">Download Service</string> <string name="download_channel_name">Download Service</string>