mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
Merge pull request #5286 from Bnyro/master
feat: automatically play the next downloaded video in offline audio mode
This commit is contained in:
commit
e61be4e3ff
@ -2,12 +2,15 @@ package com.github.libretube.services
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
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.MediaItem
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
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
|
||||||
@ -33,6 +36,23 @@ import kotlinx.coroutines.withContext
|
|||||||
class OfflinePlayerService : LifecycleService() {
|
class OfflinePlayerService : LifecycleService() {
|
||||||
private var player: ExoPlayer? = null
|
private var player: ExoPlayer? = null
|
||||||
private var nowPlayingNotification: NowPlayingNotification? = 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() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
@ -47,52 +67,55 @@ class OfflinePlayerService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
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) {
|
lifecycleScope.launch {
|
||||||
val downloadWithItems = DatabaseHolder.Database.downloadDao().findById(videoId!!)
|
downloadsWithItems = withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.Main) {
|
DatabaseHolder.Database.downloadDao().getAll()
|
||||||
if (startAudioPlayer(downloadWithItems)) {
|
}
|
||||||
nowPlayingNotification = NowPlayingNotification(
|
val downloadWithItems = downloadsWithItems.first { it.download.videoId == videoId }
|
||||||
this@OfflinePlayerService,
|
|
||||||
player!!,
|
createPlayerAndNotification()
|
||||||
true
|
|
||||||
)
|
// destroy the service if there was no success playing the selected audio/video
|
||||||
|
if (!startAudioPlayer(downloadWithItems)) onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun createPlayerAndNotification() {
|
||||||
|
val trackSelector = DefaultTrackSelector(this@OfflinePlayerService)
|
||||||
|
trackSelector.updateParameters {
|
||||||
|
setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, 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)
|
||||||
|
|
||||||
|
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(
|
val notificationData = PlayerNotificationData(
|
||||||
title = downloadWithItems.download.title,
|
title = downloadWithItems.download.title,
|
||||||
uploaderName = downloadWithItems.download.uploader,
|
uploaderName = downloadWithItems.download.uploader,
|
||||||
thumbnailPath = downloadWithItems.download.thumbnailPath
|
thumbnailPath = downloadWithItems.download.thumbnailPath
|
||||||
)
|
)
|
||||||
nowPlayingNotification?.updatePlayerNotification(videoId, notificationData)
|
nowPlayingNotification?.updatePlayerNotification(videoId, notificationData)
|
||||||
} else {
|
|
||||||
onDestroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onStartCommand(intent, flags, startId)
|
val audioItem = downloadWithItems.downloadItems.filter { it.path.exists() }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
trackSelector.updateParameters {
|
|
||||||
setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
player = PlayerHelper.createPlayer(this, trackSelector, true)
|
|
||||||
// prevent android from putting LibreTube to sleep when locked
|
|
||||||
player!!.setWakeMode(C.WAKE_MODE_LOCAL)
|
|
||||||
|
|
||||||
val audioItem = downloadWithItem.downloadItems.filter { it.path.exists() }
|
|
||||||
.firstOrNull { it.type == FileType.AUDIO }
|
.firstOrNull { it.type == FileType.AUDIO }
|
||||||
?: // in some rare cases, video files can contain 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
|
?: return false
|
||||||
|
|
||||||
val mediaItem = MediaItem.Builder()
|
val mediaItem = MediaItem.Builder()
|
||||||
@ -102,6 +125,7 @@ class OfflinePlayerService : LifecycleService() {
|
|||||||
player?.setMediaItem(mediaItem)
|
player?.setMediaItem(mediaItem)
|
||||||
player?.playWhenReady = PlayerHelper.playAutomatically
|
player?.playWhenReady = PlayerHelper.playAutomatically
|
||||||
player?.prepare()
|
player?.prepare()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,9 @@ class DownloadsAdapter(
|
|||||||
items.forEach {
|
items.forEach {
|
||||||
it.path.deleteIfExists()
|
it.path.deleteIfExists()
|
||||||
}
|
}
|
||||||
|
runCatching {
|
||||||
download.thumbnailPath?.deleteIfExists()
|
download.thumbnailPath?.deleteIfExists()
|
||||||
|
}
|
||||||
|
|
||||||
runBlocking(Dispatchers.IO) {
|
runBlocking(Dispatchers.IO) {
|
||||||
DatabaseHolder.Database.downloadDao().deleteDownload(download)
|
DatabaseHolder.Database.downloadDao().deleteDownload(download)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user