mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 06:10:31 +05:30
feat: playing queue support for downloaded videos
This commit is contained in:
parent
db8ec51b12
commit
9030a6e871
@ -3,6 +3,7 @@ package com.github.libretube.db.obj
|
|||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -17,4 +18,14 @@ data class Download(
|
|||||||
val duration: Long? = null,
|
val duration: Long? = null,
|
||||||
val uploadDate: LocalDate? = null,
|
val uploadDate: LocalDate? = null,
|
||||||
val thumbnailPath: Path? = null
|
val thumbnailPath: Path? = null
|
||||||
)
|
) {
|
||||||
|
fun toStreamItem() = StreamItem(
|
||||||
|
url = videoId,
|
||||||
|
title = title,
|
||||||
|
shortDescription = description,
|
||||||
|
thumbnail = thumbnailPath?.toUri()?.toString(),
|
||||||
|
duration = duration,
|
||||||
|
uploadedDate = uploadDate?.toString(),
|
||||||
|
uploaderName = uploader,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -15,7 +15,6 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
@ -23,24 +22,15 @@ import androidx.media3.exoplayer.ExoPlayer
|
|||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.IntentData
|
|
||||||
import com.github.libretube.db.DatabaseHolder
|
|
||||||
import com.github.libretube.db.obj.DownloadWithItems
|
|
||||||
import com.github.libretube.enums.FileType
|
|
||||||
import com.github.libretube.enums.NotificationId
|
import com.github.libretube.enums.NotificationId
|
||||||
import com.github.libretube.enums.PlayerEvent
|
import com.github.libretube.enums.PlayerEvent
|
||||||
import com.github.libretube.extensions.serializableExtra
|
import com.github.libretube.extensions.serializableExtra
|
||||||
import com.github.libretube.extensions.toAndroidUri
|
|
||||||
import com.github.libretube.extensions.updateParameters
|
import com.github.libretube.extensions.updateParameters
|
||||||
import com.github.libretube.helpers.PlayerHelper
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
import com.github.libretube.obj.PlayerNotificationData
|
|
||||||
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 kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlin.io.path.exists
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
abstract class AbstractPlayerService : LifecycleService() {
|
abstract class AbstractPlayerService : LifecycleService() {
|
||||||
@ -138,6 +128,8 @@ abstract class AbstractPlayerService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
PlayingQueue.resetToDefaults()
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
createPlayerAndNotification()
|
createPlayerAndNotification()
|
||||||
|
@ -8,11 +8,13 @@ import androidx.media3.common.Player
|
|||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
import com.github.libretube.db.obj.DownloadWithItems
|
import com.github.libretube.db.DatabaseHolder.Database
|
||||||
import com.github.libretube.enums.FileType
|
import com.github.libretube.enums.FileType
|
||||||
import com.github.libretube.extensions.toAndroidUri
|
import com.github.libretube.extensions.toAndroidUri
|
||||||
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.helpers.PlayerHelper
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
import com.github.libretube.obj.PlayerNotificationData
|
import com.github.libretube.obj.PlayerNotificationData
|
||||||
|
import com.github.libretube.util.PlayingQueue
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -23,39 +25,28 @@ import kotlin.io.path.exists
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class OfflinePlayerService : AbstractPlayerService() {
|
class OfflinePlayerService : AbstractPlayerService() {
|
||||||
private var downloadsWithItems: List<DownloadWithItems> = emptyList()
|
|
||||||
|
|
||||||
override suspend fun onServiceCreated(intent: Intent) {
|
override suspend fun onServiceCreated(intent: Intent) {
|
||||||
downloadsWithItems = withContext(Dispatchers.IO) {
|
videoId = intent.getStringExtra(IntentData.videoId) ?: return
|
||||||
DatabaseHolder.Database.downloadDao().getAll()
|
|
||||||
}
|
PlayingQueue.clear()
|
||||||
if (downloadsWithItems.isEmpty()) {
|
|
||||||
onDestroy()
|
PlayingQueue.setOnQueueTapListener { streamItem ->
|
||||||
return
|
streamItem.url?.toID()?.let { playNextVideo(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoId = intent.getStringExtra(IntentData.videoId)
|
fillQueue()
|
||||||
|
|
||||||
val downloadToPlay = if (videoId == null) {
|
|
||||||
downloadsWithItems = downloadsWithItems.shuffled()
|
|
||||||
downloadsWithItems.first()
|
|
||||||
} else {
|
|
||||||
downloadsWithItems.first { it.download.videoId == videoId }
|
|
||||||
}
|
|
||||||
|
|
||||||
this@OfflinePlayerService.videoId = downloadToPlay.download.videoId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to start an audio player with the given download items
|
* Attempt to start an audio player with the given download items
|
||||||
*/
|
*/
|
||||||
override suspend fun startPlaybackAndUpdateNotification() {
|
override suspend fun startPlaybackAndUpdateNotification() {
|
||||||
val downloadWithItems = downloadsWithItems.firstOrNull { it.download.videoId == videoId }
|
val downloadWithItems = withContext(Dispatchers.IO) {
|
||||||
if (downloadWithItems == null) {
|
Database.downloadDao().findById(videoId)
|
||||||
stopSelf()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayingQueue.updateCurrent(downloadWithItems.download.toStreamItem())
|
||||||
|
|
||||||
val notificationData = PlayerNotificationData(
|
val notificationData = PlayerNotificationData(
|
||||||
title = downloadWithItems.download.title,
|
title = downloadWithItems.download.title,
|
||||||
uploaderName = downloadWithItems.download.uploader,
|
uploaderName = downloadWithItems.download.uploader,
|
||||||
@ -88,6 +79,24 @@ class OfflinePlayerService : AbstractPlayerService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fillQueue() {
|
||||||
|
val downloads = withContext(Dispatchers.IO) {
|
||||||
|
Database.downloadDao().getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayingQueue.insertRelatedStreams(downloads.map { it.download.toStreamItem() })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playNextVideo(videoId: String) {
|
||||||
|
saveWatchPosition()
|
||||||
|
|
||||||
|
this.videoId = videoId
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
startPlaybackAndUpdateNotification()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
super.onBind(intent)
|
super.onBind(intent)
|
||||||
return null
|
return null
|
||||||
@ -103,15 +112,8 @@ class OfflinePlayerService : AbstractPlayerService() {
|
|||||||
|
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
// automatically go to the next video/audio when the current one ended
|
// automatically go to the next video/audio when the current one ended
|
||||||
if (playbackState == Player.STATE_ENDED) {
|
if (playbackState == Player.STATE_ENDED && PlayerHelper.isAutoPlayEnabled()) {
|
||||||
val currentIndex = downloadsWithItems.indexOfFirst { it.download.videoId == videoId }
|
playNextVideo(PlayingQueue.getNext() ?: return)
|
||||||
downloadsWithItems.getOrNull(currentIndex + 1)?.let {
|
|
||||||
this@OfflinePlayerService.videoId = it.download.videoId
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
startPlaybackAndUpdateNotification()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,13 @@
|
|||||||
package com.github.libretube.services
|
package com.github.libretube.services
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Handler
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Looper
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.ServiceCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.LifecycleService
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.C
|
|
||||||
import androidx.media3.common.C.WAKE_MODE_NETWORK
|
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MimeTypes
|
import androidx.media3.common.MimeTypes
|
||||||
import androidx.media3.common.PlaybackException
|
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
|
||||||
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
|
||||||
import com.github.libretube.R
|
|
||||||
import com.github.libretube.api.JsonHelper
|
import com.github.libretube.api.JsonHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.StreamsExtractor
|
import com.github.libretube.api.StreamsExtractor
|
||||||
@ -33,21 +15,15 @@ import com.github.libretube.api.obj.Segment
|
|||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.db.DatabaseHelper
|
import com.github.libretube.db.DatabaseHelper
|
||||||
import com.github.libretube.enums.NotificationId
|
|
||||||
import com.github.libretube.enums.PlayerEvent
|
|
||||||
import com.github.libretube.extensions.parcelableExtra
|
import com.github.libretube.extensions.parcelableExtra
|
||||||
import com.github.libretube.extensions.serializableExtra
|
|
||||||
import com.github.libretube.extensions.setMetadata
|
import com.github.libretube.extensions.setMetadata
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||||
import com.github.libretube.extensions.updateParameters
|
|
||||||
import com.github.libretube.helpers.PlayerHelper
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
import com.github.libretube.helpers.PlayerHelper.checkForSegments
|
import com.github.libretube.helpers.PlayerHelper.checkForSegments
|
||||||
import com.github.libretube.helpers.ProxyHelper
|
import com.github.libretube.helpers.ProxyHelper
|
||||||
import com.github.libretube.obj.PlayerNotificationData
|
import com.github.libretube.obj.PlayerNotificationData
|
||||||
import com.github.libretube.parcelable.PlayerData
|
import com.github.libretube.parcelable.PlayerData
|
||||||
import com.github.libretube.util.NowPlayingNotification
|
|
||||||
import com.github.libretube.util.PauseableTimer
|
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -90,9 +66,6 @@ class OnlinePlayerService : AbstractPlayerService() {
|
|||||||
var onNewVideo: ((streams: Streams, videoId: String) -> Unit)? = null
|
var onNewVideo: ((streams: Streams, videoId: String) -> Unit)? = null
|
||||||
|
|
||||||
override suspend fun onServiceCreated(intent: Intent) {
|
override suspend fun onServiceCreated(intent: Intent) {
|
||||||
// reset the playing queue listeners
|
|
||||||
PlayingQueue.resetToDefaults()
|
|
||||||
|
|
||||||
val playerData = intent.parcelableExtra<PlayerData>(IntentData.playerData)
|
val playerData = intent.parcelableExtra<PlayerData>(IntentData.playerData)
|
||||||
if (playerData == null) {
|
if (playerData == null) {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
|
@ -49,6 +49,7 @@ import com.github.libretube.ui.models.OfflinePlayerViewModel
|
|||||||
import com.github.libretube.util.NowPlayingNotification
|
import com.github.libretube.util.NowPlayingNotification
|
||||||
import com.github.libretube.util.OfflineTimeFrameReceiver
|
import com.github.libretube.util.OfflineTimeFrameReceiver
|
||||||
import com.github.libretube.util.PauseableTimer
|
import com.github.libretube.util.PauseableTimer
|
||||||
|
import com.github.libretube.util.PlayingQueue
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -85,7 +86,10 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
super.onIsPlayingChanged(isPlaying)
|
super.onIsPlayingChanged(isPlaying)
|
||||||
|
|
||||||
if (PlayerHelper.pipEnabled) {
|
if (PlayerHelper.pipEnabled) {
|
||||||
PictureInPictureCompat.setPictureInPictureParams(this@OfflinePlayerActivity, pipParams)
|
PictureInPictureCompat.setPictureInPictureParams(
|
||||||
|
this@OfflinePlayerActivity,
|
||||||
|
pipParams
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start or pause watch position timer
|
// Start or pause watch position timer
|
||||||
@ -108,17 +112,28 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (playbackState == Player.STATE_ENDED && PlayerHelper.isAutoPlayEnabled()) {
|
||||||
|
playNextVideo(PlayingQueue.getNext() ?: return)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val playerActionReceiver = object : BroadcastReceiver() {
|
private val playerActionReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val event = intent.serializableExtra<PlayerEvent>(PlayerHelper.CONTROL_TYPE) ?: return
|
val event = intent.serializableExtra<PlayerEvent>(PlayerHelper.CONTROL_TYPE) ?: return
|
||||||
PlayerHelper.handlePlayerAction(viewModel.player, event)
|
if (PlayerHelper.handlePlayerAction(viewModel.player, event)) return
|
||||||
|
|
||||||
|
when (event) {
|
||||||
|
PlayerEvent.Prev -> playNextVideo(PlayingQueue.getPrev() ?: return)
|
||||||
|
PlayerEvent.Next -> playNextVideo(PlayingQueue.getNext() ?: return)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pipParams get() = PictureInPictureParamsCompat.Builder()
|
private val pipParams
|
||||||
|
get() = PictureInPictureParamsCompat.Builder()
|
||||||
.setActions(PlayerHelper.getPiPModeActions(this, viewModel.player.isPlaying))
|
.setActions(PlayerHelper.getPiPModeActions(this, viewModel.player.isPlaying))
|
||||||
.setAutoEnterEnabled(PlayerHelper.pipEnabled && viewModel.player.isPlaying)
|
.setAutoEnterEnabled(PlayerHelper.pipEnabled && viewModel.player.isPlaying)
|
||||||
.setAspectRatio(viewModel.player.videoSize)
|
.setAspectRatio(viewModel.player.videoSize)
|
||||||
@ -136,6 +151,13 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
binding = ActivityOfflinePlayerBinding.inflate(layoutInflater)
|
binding = ActivityOfflinePlayerBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
PlayingQueue.resetToDefaults()
|
||||||
|
PlayingQueue.clear()
|
||||||
|
|
||||||
|
PlayingQueue.setOnQueueTapListener { streamItem ->
|
||||||
|
playNextVideo(streamItem.url ?: return@setOnQueueTapListener)
|
||||||
|
}
|
||||||
|
|
||||||
initializePlayer()
|
initializePlayer()
|
||||||
playVideo()
|
playVideo()
|
||||||
|
|
||||||
@ -154,6 +176,14 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
if (PlayerHelper.pipEnabled) {
|
if (PlayerHelper.pipEnabled) {
|
||||||
PictureInPictureCompat.setPictureInPictureParams(this, pipParams)
|
PictureInPictureCompat.setPictureInPictureParams(this, pipParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch { fillQueue() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playNextVideo(videoId: String) {
|
||||||
|
saveWatchPosition()
|
||||||
|
this.videoId = videoId
|
||||||
|
playVideo()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializePlayer() {
|
private fun initializePlayer() {
|
||||||
@ -171,13 +201,25 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playerBinding.skipPrev.setOnClickListener {
|
||||||
|
playNextVideo(PlayingQueue.getPrev() ?: return@setOnClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
playerBinding.skipNext.setOnClickListener {
|
||||||
|
playNextVideo(PlayingQueue.getNext() ?: return@setOnClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
binding.player.initialize(
|
binding.player.initialize(
|
||||||
binding.doubleTapOverlay.binding,
|
binding.doubleTapOverlay.binding,
|
||||||
binding.playerGestureControlsView.binding,
|
binding.playerGestureControlsView.binding,
|
||||||
chaptersViewModel
|
chaptersViewModel
|
||||||
)
|
)
|
||||||
|
|
||||||
nowPlayingNotification = NowPlayingNotification(this, viewModel.player, NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_OFFLINE)
|
nowPlayingNotification = NowPlayingNotification(
|
||||||
|
this,
|
||||||
|
viewModel.player,
|
||||||
|
NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_OFFLINE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playVideo() {
|
private fun playVideo() {
|
||||||
@ -185,6 +227,8 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
val (downloadInfo, downloadItems, downloadChapters) = withContext(Dispatchers.IO) {
|
val (downloadInfo, downloadItems, downloadChapters) = withContext(Dispatchers.IO) {
|
||||||
Database.downloadDao().findById(videoId)
|
Database.downloadDao().findById(videoId)
|
||||||
}
|
}
|
||||||
|
PlayingQueue.updateCurrent(downloadInfo.toStreamItem())
|
||||||
|
|
||||||
val chapters = downloadChapters.map(DownloadChapter::toChapterSegment)
|
val chapters = downloadChapters.map(DownloadChapter::toChapterSegment)
|
||||||
chaptersViewModel.chaptersLiveData.value = chapters
|
chaptersViewModel.chaptersLiveData.value = chapters
|
||||||
binding.player.setChapters(chapters)
|
binding.player.setChapters(chapters)
|
||||||
@ -221,7 +265,11 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val data = PlayerNotificationData(downloadInfo.title, downloadInfo.uploader, downloadInfo.thumbnailPath.toString())
|
val data = PlayerNotificationData(
|
||||||
|
downloadInfo.title,
|
||||||
|
downloadInfo.uploader,
|
||||||
|
downloadInfo.thumbnailPath.toString()
|
||||||
|
)
|
||||||
nowPlayingNotification?.updatePlayerNotification(videoId, data)
|
nowPlayingNotification?.updatePlayerNotification(videoId, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,6 +322,14 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fillQueue() {
|
||||||
|
val downloads = withContext(Dispatchers.IO) {
|
||||||
|
Database.downloadDao().getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayingQueue.insertRelatedStreams(downloads.map { it.download.toStreamItem() })
|
||||||
|
}
|
||||||
|
|
||||||
private fun saveWatchPosition() {
|
private fun saveWatchPosition() {
|
||||||
if (!PlayerHelper.watchPositionsVideo) return
|
if (!PlayerHelper.watchPositionsVideo) return
|
||||||
|
|
||||||
@ -320,7 +376,10 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
super.onUserLeaveHint()
|
super.onUserLeaveHint()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, configuration: Configuration) {
|
override fun onPictureInPictureModeChanged(
|
||||||
|
isInPictureInPictureMode: Boolean,
|
||||||
|
newConfig: Configuration
|
||||||
|
) {
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
||||||
|
|
||||||
if (isInPictureInPictureMode) {
|
if (isInPictureInPictureMode) {
|
||||||
|
@ -171,10 +171,6 @@ class DownloadsFragment : DynamicLayoutManagerFragment() {
|
|||||||
|
|
||||||
toggleButtonsVisibility()
|
toggleButtonsVisibility()
|
||||||
|
|
||||||
binding.shuffleBackground.setOnClickListener {
|
|
||||||
BackgroundHelper.playOnBackgroundOffline(requireContext(), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.deleteAll.setOnClickListener {
|
binding.deleteAll.setOnClickListener {
|
||||||
showDeleteAllDialog(binding.root.context, adapter)
|
showDeleteAllDialog(binding.root.context, adapter)
|
||||||
}
|
}
|
||||||
@ -188,7 +184,6 @@ class DownloadsFragment : DynamicLayoutManagerFragment() {
|
|||||||
binding.downloads.isGone = isEmpty
|
binding.downloads.isGone = isEmpty
|
||||||
binding.sortType.isGone = isEmpty
|
binding.sortType.isGone = isEmpty
|
||||||
binding.deleteAll.isGone = isEmpty
|
binding.deleteAll.isGone = isEmpty
|
||||||
binding.shuffleBackground.isGone = isEmpty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sortDownloadList(sortType: Int, previousSortType: Int? = null) {
|
private fun sortDownloadList(sortType: Int, previousSortType: Int? = null) {
|
||||||
|
@ -66,6 +66,7 @@ import com.github.libretube.ui.models.ChaptersViewModel
|
|||||||
import com.github.libretube.ui.sheets.BaseBottomSheet
|
import com.github.libretube.ui.sheets.BaseBottomSheet
|
||||||
import com.github.libretube.ui.sheets.ChaptersBottomSheet
|
import com.github.libretube.ui.sheets.ChaptersBottomSheet
|
||||||
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
||||||
|
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||||
import com.github.libretube.ui.sheets.SleepTimerSheet
|
import com.github.libretube.ui.sheets.SleepTimerSheet
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
|
|
||||||
@ -203,6 +204,12 @@ abstract class CustomExoPlayerView(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
binding.autoPlay.isChecked = PlayerHelper.autoPlayEnabled
|
||||||
|
|
||||||
|
binding.autoPlay.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
PlayerHelper.autoPlayEnabled = isChecked
|
||||||
|
}
|
||||||
|
|
||||||
// restore the duration type from the previous session
|
// restore the duration type from the previous session
|
||||||
updateDisplayedDurationType()
|
updateDisplayedDurationType()
|
||||||
|
|
||||||
@ -248,6 +255,10 @@ abstract class CustomExoPlayerView(
|
|||||||
sheet.show(activity.supportFragmentManager)
|
sheet.show(activity.supportFragmentManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.queueToggle.setOnClickListener {
|
||||||
|
PlayingQueueSheet().show(supportFragmentManager, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,18 +173,6 @@ class OnlinePlayerView(
|
|||||||
binding.exoTitle.isInvisible = !isFullscreen
|
binding.exoTitle.isInvisible = !isFullscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.autoPlay.isVisible = true
|
|
||||||
binding.autoPlay.isChecked = PlayerHelper.autoPlayEnabled
|
|
||||||
|
|
||||||
binding.autoPlay.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
PlayerHelper.autoPlayEnabled = isChecked
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.queueToggle.isVisible = true
|
|
||||||
binding.queueToggle.setOnClickListener {
|
|
||||||
PlayingQueueSheet().show(activity.supportFragmentManager, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateSbImageResource = {
|
val updateSbImageResource = {
|
||||||
binding.sbToggle.setImageResource(
|
binding.sbToggle.setImageResource(
|
||||||
if (playerViewModel.sponsorBlockEnabled) R.drawable.ic_sb_enabled else R.drawable.ic_sb_disabled
|
if (playerViewModel.sponsorBlockEnabled) R.drawable.ic_sb_enabled else R.drawable.ic_sb_disabled
|
||||||
|
@ -76,7 +76,6 @@
|
|||||||
android:scaleY="0.8"
|
android:scaleY="0.8"
|
||||||
android:thumb="@drawable/player_switch_thumb"
|
android:thumb="@drawable/player_switch_thumb"
|
||||||
android:tooltipText="@string/player_autoplay"
|
android:tooltipText="@string/player_autoplay"
|
||||||
android:visibility="gone"
|
|
||||||
app:thumbTint="@android:color/white"
|
app:thumbTint="@android:color/white"
|
||||||
app:track="@drawable/player_switch_track"
|
app:track="@drawable/player_switch_track"
|
||||||
app:trackTint="#88ffffff" />
|
app:trackTint="#88ffffff" />
|
||||||
@ -114,7 +113,6 @@
|
|||||||
android:layout_marginEnd="2dp"
|
android:layout_marginEnd="2dp"
|
||||||
android:src="@drawable/ic_queue"
|
android:src="@drawable/ic_queue"
|
||||||
android:tooltipText="@string/queue"
|
android:tooltipText="@string/queue"
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="@android:color/white" />
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginEnd="18dp"
|
android:layout_marginEnd="18dp"
|
||||||
android:layout_marginBottom="80dp"
|
android:layout_marginBottom="18dp"
|
||||||
android:contentDescription="@string/shuffle"
|
android:contentDescription="@string/shuffle"
|
||||||
android:src="@drawable/ic_delete"
|
android:src="@drawable/ic_delete"
|
||||||
android:tooltipText="@string/delete"
|
android:tooltipText="@string/delete"
|
||||||
@ -76,19 +76,4 @@
|
|||||||
tools:targetApi="o"
|
tools:targetApi="o"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/shuffle_background"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_margin="18dp"
|
|
||||||
android:contentDescription="@string/shuffle"
|
|
||||||
android:src="@drawable/ic_shuffle"
|
|
||||||
android:tooltipText="@string/shuffle"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
tools:targetApi="o"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue
Block a user