Merge pull request #5286 from Bnyro/master

feat: automatically play the next downloaded video in offline audio mode
This commit is contained in:
Bnyro 2023-12-04 16:20:59 +01:00 committed by GitHub
commit e61be4e3ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 31 deletions

View File

@ -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<DownloadWithItems> = 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
}

View File

@ -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)