mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-01-07 10:00:31 +05:30
Merge pull request #2993 from Isira-Seneviratne/LifecycleService
Use LifecycleService.
This commit is contained in:
commit
793ad8e731
@ -92,6 +92,7 @@ dependencies {
|
|||||||
implementation libs.lifecycle.viewmodel
|
implementation libs.lifecycle.viewmodel
|
||||||
implementation libs.lifecycle.runtime
|
implementation libs.lifecycle.runtime
|
||||||
implementation libs.lifecycle.livedata
|
implementation libs.lifecycle.livedata
|
||||||
|
implementation libs.lifecycle.service
|
||||||
|
|
||||||
/* Testing */
|
/* Testing */
|
||||||
androidTestImplementation libs.androidx.test.junit
|
androidTestImplementation libs.androidx.test.junit
|
||||||
|
@ -3,7 +3,6 @@ package com.github.libretube.services
|
|||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -13,10 +12,11 @@ import android.os.Looper
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
|
import androidx.lifecycle.LifecycleService
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.libretube.R
|
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.obj.Segment
|
|
||||||
import com.github.libretube.api.obj.SegmentData
|
import com.github.libretube.api.obj.SegmentData
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
|
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
|
||||||
@ -37,15 +37,15 @@ import com.google.android.exoplayer2.ExoPlayer
|
|||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.PlaybackException
|
import com.google.android.exoplayer2.PlaybackException
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the selected videos audio in background mode with a notification area.
|
* Loads the selected videos audio in background mode with a notification area.
|
||||||
*/
|
*/
|
||||||
class BackgroundMode : Service() {
|
class BackgroundMode : LifecycleService() {
|
||||||
/**
|
/**
|
||||||
* VideoId of the video
|
* VideoId of the video
|
||||||
*/
|
*/
|
||||||
@ -171,7 +171,7 @@ class BackgroundMode : Service() {
|
|||||||
seekToPosition: Long = 0,
|
seekToPosition: Long = 0,
|
||||||
keepQueue: Boolean = false
|
keepQueue: Boolean = false
|
||||||
) {
|
) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
streams = runCatching {
|
streams = runCatching {
|
||||||
RetrofitInstance.api.getStreams(videoId)
|
RetrofitInstance.api.getStreams(videoId)
|
||||||
}.getOrNull() ?: return@launch
|
}.getOrNull() ?: return@launch
|
||||||
@ -186,7 +186,7 @@ class BackgroundMode : Service() {
|
|||||||
PlayingQueue.updateCurrent(it)
|
PlayingQueue.updateCurrent(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.post {
|
withContext(Dispatchers.Main) {
|
||||||
playAudio(seekToPosition)
|
playAudio(seekToPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,17 +292,13 @@ class BackgroundMode : Service() {
|
|||||||
* Sets the [MediaItem] with the [streams] into the [player]
|
* Sets the [MediaItem] with the [streams] into the [player]
|
||||||
*/
|
*/
|
||||||
private fun setMediaItem() {
|
private fun setMediaItem() {
|
||||||
|
val streams = streams
|
||||||
streams ?: return
|
streams ?: return
|
||||||
|
|
||||||
val uri = if (streams!!.audioStreams.orEmpty().isNotEmpty()) {
|
val uri = if (streams.audioStreams.isNotEmpty()) {
|
||||||
PlayerHelper.getAudioSource(
|
PlayerHelper.getAudioSource(this, streams.audioStreams)
|
||||||
this,
|
|
||||||
streams!!.audioStreams!!
|
|
||||||
)
|
|
||||||
} else if (streams!!.hls != null) {
|
|
||||||
streams!!.hls
|
|
||||||
} else {
|
} else {
|
||||||
return
|
streams.hls ?: return
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaItem = MediaItem.Builder()
|
val mediaItem = MediaItem.Builder()
|
||||||
@ -315,7 +311,7 @@ class BackgroundMode : Service() {
|
|||||||
* fetch the segments for SponsorBlock
|
* fetch the segments for SponsorBlock
|
||||||
*/
|
*/
|
||||||
private fun fetchSponsorBlockSegments() {
|
private fun fetchSponsorBlockSegments() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val categories = PlayerHelper.getSponsorBlockCategories()
|
val categories = PlayerHelper.getSponsorBlockCategories()
|
||||||
if (categories.isEmpty()) return@runCatching
|
if (categories.isEmpty()) return@runCatching
|
||||||
@ -336,7 +332,7 @@ class BackgroundMode : Service() {
|
|||||||
|
|
||||||
if (segmentData == null || segmentData!!.segments.isEmpty()) return
|
if (segmentData == null || segmentData!!.segments.isEmpty()) return
|
||||||
|
|
||||||
segmentData!!.segments.forEach { segment: Segment ->
|
segmentData!!.segments.forEach { segment ->
|
||||||
val segmentStart = (segment.segment[0] * 1000f).toLong()
|
val segmentStart = (segment.segment[0] * 1000f).toLong()
|
||||||
val segmentEnd = (segment.segment[1] * 1000f).toLong()
|
val segmentEnd = (segment.segment[1] * 1000f).toLong()
|
||||||
val currentPosition = player?.currentPosition
|
val currentPosition = player?.currentPosition
|
||||||
@ -399,7 +395,8 @@ class BackgroundMode : Service() {
|
|||||||
fun getService(): BackgroundMode = this@BackgroundMode
|
fun getService(): BackgroundMode = this@BackgroundMode
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(p0: Intent?): IBinder {
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
super.onBind(intent)
|
||||||
return binder
|
return binder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package com.github.libretube.services
|
|||||||
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
@ -11,6 +10,8 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.core.util.set
|
import androidx.core.util.set
|
||||||
import androidx.core.util.valueIterator
|
import androidx.core.util.valueIterator
|
||||||
|
import androidx.lifecycle.LifecycleService
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.CronetHelper
|
import com.github.libretube.api.CronetHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
@ -40,9 +41,7 @@ import java.io.File
|
|||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@ -55,16 +54,15 @@ import okio.BufferedSink
|
|||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import okio.source
|
import okio.source
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download service with custom implementation of downloading using [HttpURLConnection].
|
* Download service with custom implementation of downloading using [HttpURLConnection].
|
||||||
*/
|
*/
|
||||||
class DownloadService : Service() {
|
class DownloadService : LifecycleService() {
|
||||||
|
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
private val jobMain = SupervisorJob()
|
private val coroutineContext = dispatcher + SupervisorJob()
|
||||||
private val scope = CoroutineScope(dispatcher + jobMain)
|
|
||||||
|
|
||||||
private lateinit var notificationManager: NotificationManager
|
private lateinit var notificationManager: NotificationManager
|
||||||
private lateinit var summaryNotificationBuilder: NotificationCompat.Builder
|
private lateinit var summaryNotificationBuilder: NotificationCompat.Builder
|
||||||
@ -81,6 +79,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
super.onStartCommand(intent, flags, startId)
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
ACTION_DOWNLOAD_RESUME -> resume(intent.getIntExtra("id", -1))
|
ACTION_DOWNLOAD_RESUME -> resume(intent.getIntExtra("id", -1))
|
||||||
ACTION_DOWNLOAD_PAUSE -> pause(intent.getIntExtra("id", -1))
|
ACTION_DOWNLOAD_PAUSE -> pause(intent.getIntExtra("id", -1))
|
||||||
@ -94,9 +93,11 @@ class DownloadService : Service() {
|
|||||||
val audioQuality = intent.getStringExtra(IntentData.audioQuality)
|
val audioQuality = intent.getStringExtra(IntentData.audioQuality)
|
||||||
val subtitleCode = intent.getStringExtra(IntentData.subtitleCode)
|
val subtitleCode = intent.getStringExtra(IntentData.subtitleCode)
|
||||||
|
|
||||||
scope.launch {
|
lifecycleScope.launch(coroutineContext) {
|
||||||
try {
|
try {
|
||||||
val streams = RetrofitInstance.api.getStreams(videoId)
|
val streams = withContext(Dispatchers.IO) {
|
||||||
|
RetrofitInstance.api.getStreams(videoId)
|
||||||
|
}
|
||||||
|
|
||||||
val thumbnailTargetFile = getDownloadFile(DownloadHelper.THUMBNAIL_DIR, fileName)
|
val thumbnailTargetFile = getDownloadFile(DownloadHelper.THUMBNAIL_DIR, fileName)
|
||||||
|
|
||||||
@ -153,7 +154,7 @@ class DownloadService : Service() {
|
|||||||
Database.downloadDao().insertDownloadItem(item)
|
Database.downloadDao().insertDownloadItem(item)
|
||||||
}.toInt()
|
}.toInt()
|
||||||
|
|
||||||
scope.launch {
|
lifecycleScope.launch(coroutineContext) {
|
||||||
downloadFile(item)
|
downloadFile(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,7 +270,8 @@ class DownloadService : Service() {
|
|||||||
sourceByte.close()
|
sourceByte.close()
|
||||||
con.disconnect()
|
con.disconnect()
|
||||||
}
|
}
|
||||||
} catch (_: Exception) { }
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
val completed = when {
|
val completed = when {
|
||||||
totalRead < item.downloadSize -> {
|
totalRead < item.downloadSize -> {
|
||||||
@ -297,7 +299,7 @@ class DownloadService : Service() {
|
|||||||
val downloadCount = downloadQueue.valueIterator().asSequence().count { it }
|
val downloadCount = downloadQueue.valueIterator().asSequence().count { it }
|
||||||
if (downloadCount >= DownloadHelper.getMaxConcurrentDownloads()) {
|
if (downloadCount >= DownloadHelper.getMaxConcurrentDownloads()) {
|
||||||
toastFromMainThread(getString(R.string.concurrent_downloads_limit_reached))
|
toastFromMainThread(getString(R.string.concurrent_downloads_limit_reached))
|
||||||
scope.launch {
|
lifecycleScope.launch(coroutineContext) {
|
||||||
_downloadFlow.emit(id to DownloadStatus.Paused)
|
_downloadFlow.emit(id to DownloadStatus.Paused)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -306,7 +308,7 @@ class DownloadService : Service() {
|
|||||||
val downloadItem = awaitQuery {
|
val downloadItem = awaitQuery {
|
||||||
Database.downloadDao().findDownloadItemById(id)
|
Database.downloadDao().findDownloadItemById(id)
|
||||||
}
|
}
|
||||||
scope.launch {
|
lifecycleScope.launch(coroutineContext) {
|
||||||
downloadFile(downloadItem)
|
downloadFile(downloadItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,9 +470,9 @@ class DownloadService : Service() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder {
|
override fun onBind(intent: Intent): IBinder {
|
||||||
val ids = intent?.getIntArrayExtra("ids")
|
super.onBind(intent)
|
||||||
ids?.forEach { id -> resume(id) }
|
intent.getIntArrayExtra("ids")?.forEach { resume(it) }
|
||||||
return binder
|
return binder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ square-leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-andr
|
|||||||
lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
|
lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
|
||||||
lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||||
lifecycle-livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
|
lifecycle-livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
|
||||||
|
lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycle" }
|
||||||
room = { group = "androidx.room", name="room-ktx", version.ref = "room" }
|
room = { group = "androidx.room", name="room-ktx", version.ref = "room" }
|
||||||
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
||||||
kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||||
|
Loading…
Reference in New Issue
Block a user