Convert DownloadService and BackgroundMode to lifecycle services.

This commit is contained in:
Isira Seneviratne 2023-02-06 07:13:38 +05:30
parent 0173ace37e
commit 5e6d171675
2 changed files with 28 additions and 37 deletions

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,18 +292,12 @@ 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, } else streams.hls ?: return
streams!!.audioStreams!!
)
} else if (streams!!.hls != null) {
streams!!.hls
} else {
return
}
val mediaItem = MediaItem.Builder() val mediaItem = MediaItem.Builder()
.setUri(uri) .setUri(uri)
@ -315,7 +309,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 +330,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 +393,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,13 +41,9 @@ 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.asCoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -59,12 +56,8 @@ import okio.source
/** /**
* 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 jobMain = 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 +74,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 +88,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 {
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 +149,7 @@ class DownloadService : Service() {
Database.downloadDao().insertDownloadItem(item) Database.downloadDao().insertDownloadItem(item)
}.toInt() }.toInt()
scope.launch { lifecycleScope.launch {
downloadFile(item) downloadFile(item)
} }
} }
@ -297,7 +293,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 {
_downloadFlow.emit(id to DownloadStatus.Paused) _downloadFlow.emit(id to DownloadStatus.Paused)
} }
return return
@ -306,7 +302,7 @@ class DownloadService : Service() {
val downloadItem = awaitQuery { val downloadItem = awaitQuery {
Database.downloadDao().findDownloadItemById(id) Database.downloadDao().findDownloadItemById(id)
} }
scope.launch { lifecycleScope.launch {
downloadFile(downloadItem) downloadFile(downloadItem)
} }
} }
@ -468,9 +464,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
} }