Merge pull request #2993 from Isira-Seneviratne/LifecycleService

Use LifecycleService.
This commit is contained in:
Bnyro 2023-02-08 10:00:58 +01:00 committed by GitHub
commit 793ad8e731
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 33 deletions

View File

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

View File

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

View File

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

View File

@ -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" }