diff --git a/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt b/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt index fd56c12b8..3514ef662 100644 --- a/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt +++ b/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt @@ -2,12 +2,15 @@ package com.github.libretube.services import android.content.Intent import android.os.IBinder +import androidx.annotation.OptIn import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import androidx.media3.common.C import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME @@ -33,6 +36,23 @@ import kotlinx.coroutines.withContext class OfflinePlayerService : LifecycleService() { private var player: ExoPlayer? = null private var nowPlayingNotification: NowPlayingNotification? = null + private lateinit var videoId: String + private var downloadsWithItems: List = emptyList() + + private val playerListener = object : Player.Listener { + override fun onPlaybackStateChanged(playbackState: Int) { + super.onPlaybackStateChanged(playbackState) + + // automatically go to the next video/audio when the current one ended + if (playbackState == Player.STATE_ENDED) { + val currentIndex = downloadsWithItems.indexOfFirst { it.download.videoId == videoId } + downloadsWithItems.getOrNull(currentIndex + 1)?.let { + this@OfflinePlayerService.videoId = it.download.videoId + startAudioPlayer(it) + } + } + } + } override fun onCreate() { super.onCreate() @@ -47,52 +67,55 @@ class OfflinePlayerService : LifecycleService() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val videoId = intent?.getStringExtra(IntentData.videoId) + videoId = intent?.getStringExtra(IntentData.videoId)!! - lifecycleScope.launch(Dispatchers.IO) { - val downloadWithItems = DatabaseHolder.Database.downloadDao().findById(videoId!!) - withContext(Dispatchers.Main) { - if (startAudioPlayer(downloadWithItems)) { - nowPlayingNotification = NowPlayingNotification( - this@OfflinePlayerService, - player!!, - true - ) - val notificationData = PlayerNotificationData( - title = downloadWithItems.download.title, - uploaderName = downloadWithItems.download.uploader, - thumbnailPath = downloadWithItems.download.thumbnailPath - ) - nowPlayingNotification?.updatePlayerNotification(videoId, notificationData) - } else { - onDestroy() - } + lifecycleScope.launch { + downloadsWithItems = withContext(Dispatchers.IO) { + DatabaseHolder.Database.downloadDao().getAll() } + val downloadWithItems = downloadsWithItems.first { it.download.videoId == videoId } + + createPlayerAndNotification() + + // destroy the service if there was no success playing the selected audio/video + if (!startAudioPlayer(downloadWithItems)) onDestroy() } return super.onStartCommand(intent, flags, startId) } - /** - * Attempt to start an audio player with the given download items - * @param downloadWithItem The database download to play from - * @return whether starting the audio player succeeded - */ - @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) - private fun startAudioPlayer(downloadWithItem: DownloadWithItems): Boolean { - val trackSelector = DefaultTrackSelector(this) + @OptIn(UnstableApi::class) + private fun createPlayerAndNotification() { + val trackSelector = DefaultTrackSelector(this@OfflinePlayerService) trackSelector.updateParameters { setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, true) } - player = PlayerHelper.createPlayer(this, trackSelector, true) + player = PlayerHelper.createPlayer(this@OfflinePlayerService, trackSelector, true) // prevent android from putting LibreTube to sleep when locked player!!.setWakeMode(C.WAKE_MODE_LOCAL) + player!!.addListener(playerListener) - val audioItem = downloadWithItem.downloadItems.filter { it.path.exists() } + nowPlayingNotification = NowPlayingNotification(this, player!!, true) + } + + /** + * Attempt to start an audio player with the given download items + * @param downloadWithItems The database download to play from + * @return whether starting the audio player succeeded + */ + private fun startAudioPlayer(downloadWithItems: DownloadWithItems): Boolean { + val notificationData = PlayerNotificationData( + title = downloadWithItems.download.title, + uploaderName = downloadWithItems.download.uploader, + thumbnailPath = downloadWithItems.download.thumbnailPath + ) + nowPlayingNotification?.updatePlayerNotification(videoId, notificationData) + + val audioItem = downloadWithItems.downloadItems.filter { it.path.exists() } .firstOrNull { it.type == FileType.AUDIO } ?: // in some rare cases, video files can contain audio - downloadWithItem.downloadItems.firstOrNull { it.type == FileType.VIDEO } + downloadWithItems.downloadItems.firstOrNull { it.type == FileType.VIDEO } ?: return false val mediaItem = MediaItem.Builder() @@ -102,6 +125,7 @@ class OfflinePlayerService : LifecycleService() { player?.setMediaItem(mediaItem) player?.playWhenReady = PlayerHelper.playAutomatically player?.prepare() + return true } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt index 5d81e73d9..4959ecaec 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt @@ -138,7 +138,9 @@ class DownloadsAdapter( items.forEach { it.path.deleteIfExists() } - download.thumbnailPath?.deleteIfExists() + runCatching { + download.thumbnailPath?.deleteIfExists() + } runBlocking(Dispatchers.IO) { DatabaseHolder.Database.downloadDao().deleteDownload(download)