2022-06-28 20:02:26 +05:30
|
|
|
package com.github.libretube.services
|
2022-03-03 12:08:36 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
import android.app.NotificationManager
|
2023-04-09 17:36:28 +05:30
|
|
|
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
|
|
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
2022-03-03 12:08:36 +05:30
|
|
|
import android.content.Intent
|
2022-12-16 22:14:21 +05:30
|
|
|
import android.os.Binder
|
2022-03-03 12:08:36 +05:30
|
|
|
import android.os.IBinder
|
2023-02-06 07:25:48 +05:30
|
|
|
import android.util.SparseBooleanArray
|
2022-03-03 12:08:36 +05:30
|
|
|
import androidx.core.app.NotificationCompat
|
2023-04-09 17:36:28 +05:30
|
|
|
import androidx.core.app.PendingIntentCompat
|
2023-02-08 00:01:43 +05:30
|
|
|
import androidx.core.app.ServiceCompat
|
2023-03-31 06:09:38 +05:30
|
|
|
import androidx.core.content.getSystemService
|
2023-02-06 07:25:48 +05:30
|
|
|
import androidx.core.util.set
|
|
|
|
import androidx.core.util.valueIterator
|
2023-02-06 07:13:38 +05:30
|
|
|
import androidx.lifecycle.LifecycleService
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
2022-09-08 22:12:52 +05:30
|
|
|
import com.github.libretube.R
|
2022-12-24 23:39:56 +05:30
|
|
|
import com.github.libretube.api.CronetHelper
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.api.RetrofitInstance
|
2022-09-08 22:11:57 +05:30
|
|
|
import com.github.libretube.constants.DOWNLOAD_CHANNEL_ID
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.constants.DOWNLOAD_PROGRESS_NOTIFICATION_ID
|
|
|
|
import com.github.libretube.constants.IntentData
|
2023-02-11 07:26:07 +05:30
|
|
|
import com.github.libretube.db.DatabaseHolder.Database
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.db.obj.Download
|
|
|
|
import com.github.libretube.db.obj.DownloadItem
|
|
|
|
import com.github.libretube.enums.FileType
|
2022-12-17 15:56:57 +05:30
|
|
|
import com.github.libretube.extensions.formatAsFileSize
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.extensions.getContentLength
|
2023-06-09 07:04:19 +05:30
|
|
|
import com.github.libretube.extensions.parcelableExtra
|
2022-12-17 15:56:57 +05:30
|
|
|
import com.github.libretube.extensions.toastFromMainThread
|
2023-01-31 22:27:24 +05:30
|
|
|
import com.github.libretube.helpers.DownloadHelper
|
|
|
|
import com.github.libretube.helpers.DownloadHelper.getNotificationId
|
|
|
|
import com.github.libretube.helpers.ImageHelper
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.obj.DownloadStatus
|
2023-06-17 06:55:37 +05:30
|
|
|
import com.github.libretube.parcelable.DownloadData
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.receivers.NotificationReceiver
|
2022-12-21 21:10:48 +05:30
|
|
|
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_PAUSE
|
|
|
|
import com.github.libretube.receivers.NotificationReceiver.Companion.ACTION_DOWNLOAD_RESUME
|
2022-12-16 22:14:21 +05:30
|
|
|
import com.github.libretube.ui.activities.MainActivity
|
2023-05-09 22:11:05 +05:30
|
|
|
import java.io.File
|
|
|
|
import java.net.HttpURLConnection
|
|
|
|
import java.net.SocketTimeoutException
|
|
|
|
import java.net.URL
|
|
|
|
import java.nio.file.Path
|
|
|
|
import java.nio.file.StandardOpenOption
|
|
|
|
import java.util.concurrent.Executors
|
|
|
|
import kotlin.io.path.absolute
|
|
|
|
import kotlin.io.path.createFile
|
2023-05-10 23:09:37 +05:30
|
|
|
import kotlin.io.path.deleteIfExists
|
2023-05-09 22:11:05 +05:30
|
|
|
import kotlin.io.path.fileSize
|
2023-06-08 23:28:45 +05:30
|
|
|
import kotlin.math.min
|
2023-06-24 23:27:00 +05:30
|
|
|
import kotlinx.coroutines.CancellationException
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.SupervisorJob
|
|
|
|
import kotlinx.coroutines.asCoroutineDispatcher
|
|
|
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
|
|
import kotlinx.coroutines.flow.SharedFlow
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
import okio.buffer
|
|
|
|
import okio.sink
|
|
|
|
import okio.source
|
2022-03-04 23:30:50 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
/**
|
2022-12-21 21:10:48 +05:30
|
|
|
* Download service with custom implementation of downloading using [HttpURLConnection].
|
2022-12-16 22:14:21 +05:30
|
|
|
*/
|
2023-02-06 07:13:38 +05:30
|
|
|
class DownloadService : LifecycleService() {
|
2022-12-16 22:14:21 +05:30
|
|
|
private val binder = LocalBinder()
|
2023-02-08 06:20:06 +05:30
|
|
|
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
|
|
|
private val coroutineContext = dispatcher + SupervisorJob()
|
2022-10-23 23:12:33 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private lateinit var notificationManager: NotificationManager
|
|
|
|
private lateinit var summaryNotificationBuilder: NotificationCompat.Builder
|
|
|
|
|
2023-02-06 07:25:48 +05:30
|
|
|
private val downloadQueue = SparseBooleanArray()
|
2022-12-16 22:14:21 +05:30
|
|
|
private val _downloadFlow = MutableSharedFlow<Pair<Int, DownloadStatus>>()
|
|
|
|
val downloadFlow: SharedFlow<Pair<Int, DownloadStatus>> = _downloadFlow
|
2022-09-09 16:41:54 +05:30
|
|
|
|
2022-03-04 21:27:10 +05:30
|
|
|
override fun onCreate() {
|
|
|
|
super.onCreate()
|
2022-09-19 23:37:55 +05:30
|
|
|
IS_DOWNLOAD_RUNNING = true
|
2022-12-16 22:14:21 +05:30
|
|
|
notifyForeground()
|
2022-12-21 21:10:48 +05:30
|
|
|
sendBroadcast(Intent(ACTION_SERVICE_STARTED))
|
2022-03-04 21:27:10 +05:30
|
|
|
}
|
|
|
|
|
2022-03-03 12:08:36 +05:30
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
2023-02-06 07:13:38 +05:30
|
|
|
super.onStartCommand(intent, flags, startId)
|
2022-12-16 22:14:21 +05:30
|
|
|
when (intent?.action) {
|
2022-12-21 21:10:48 +05:30
|
|
|
ACTION_DOWNLOAD_RESUME -> resume(intent.getIntExtra("id", -1))
|
|
|
|
ACTION_DOWNLOAD_PAUSE -> pause(intent.getIntExtra("id", -1))
|
2022-08-24 21:26:57 +05:30
|
|
|
}
|
2022-09-09 21:09:49 +05:30
|
|
|
|
2023-06-18 19:11:55 +05:30
|
|
|
val downloadData = intent?.parcelableExtra<DownloadData>(IntentData.downloadData)
|
|
|
|
?: return START_NOT_STICKY
|
|
|
|
val (videoId, name) = downloadData
|
|
|
|
val fileName = name.ifEmpty { videoId }
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2023-02-08 06:20:06 +05:30
|
|
|
lifecycleScope.launch(coroutineContext) {
|
2022-12-16 22:14:21 +05:30
|
|
|
try {
|
2023-02-06 07:13:38 +05:30
|
|
|
val streams = withContext(Dispatchers.IO) {
|
|
|
|
RetrofitInstance.api.getStreams(videoId)
|
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2023-03-07 07:14:09 +05:30
|
|
|
val thumbnailTargetPath = getDownloadPath(DownloadHelper.THUMBNAIL_DIR, fileName)
|
2023-01-14 22:47:34 +05:30
|
|
|
|
2023-01-25 22:08:01 +05:30
|
|
|
val download = Download(
|
|
|
|
videoId,
|
|
|
|
streams.title,
|
|
|
|
streams.description,
|
|
|
|
streams.uploader,
|
2023-03-07 09:35:50 +05:30
|
|
|
streams.uploadDate,
|
2023-06-24 23:27:00 +05:30
|
|
|
thumbnailTargetPath
|
2023-01-25 22:08:01 +05:30
|
|
|
)
|
|
|
|
Database.downloadDao().insertDownload(download)
|
2023-01-18 07:01:06 +05:30
|
|
|
ImageHelper.downloadImage(
|
|
|
|
this@DownloadService,
|
|
|
|
streams.thumbnailUrl,
|
2023-06-24 23:27:00 +05:30
|
|
|
thumbnailTargetPath
|
2023-01-18 07:01:06 +05:30
|
|
|
)
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2023-06-18 19:11:55 +05:30
|
|
|
val downloadItems = streams.toDownloadItems(downloadData.copy(fileName = fileName))
|
2022-12-16 22:14:21 +05:30
|
|
|
downloadItems.forEach { start(it) }
|
|
|
|
} catch (e: Exception) {
|
|
|
|
return@launch
|
|
|
|
}
|
2022-06-05 23:10:16 +05:30
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
return START_NOT_STICKY
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
2022-05-21 13:32:04 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
|
|
|
* Initiate download [Job] using [DownloadItem] by creating file according to [FileType]
|
|
|
|
* for the requested file.
|
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun start(item: DownloadItem) {
|
2023-03-07 07:14:09 +05:30
|
|
|
item.path = when (item.type) {
|
|
|
|
FileType.AUDIO -> getDownloadPath(DownloadHelper.AUDIO_DIR, item.fileName)
|
|
|
|
FileType.VIDEO -> getDownloadPath(DownloadHelper.VIDEO_DIR, item.fileName)
|
|
|
|
FileType.SUBTITLE -> getDownloadPath(DownloadHelper.SUBTITLE_DIR, item.fileName)
|
2023-04-21 07:07:41 +05:30
|
|
|
}.apply { deleteIfExists() }.createFile()
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2023-02-08 06:20:06 +05:30
|
|
|
lifecycleScope.launch(coroutineContext) {
|
2023-01-25 22:08:01 +05:30
|
|
|
item.id = Database.downloadDao().insertDownloadItem(item).toInt()
|
2022-12-16 22:14:21 +05:30
|
|
|
downloadFile(item)
|
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
|
|
|
* Download file and emit [DownloadStatus] to the collectors of [downloadFlow]
|
|
|
|
* and notification.
|
|
|
|
*/
|
2023-06-08 23:28:45 +05:30
|
|
|
@Suppress("KotlinConstantConditions")
|
2022-12-16 22:14:21 +05:30
|
|
|
private suspend fun downloadFile(item: DownloadItem) {
|
2022-12-21 21:10:48 +05:30
|
|
|
downloadQueue[item.id] = true
|
2022-12-17 15:56:57 +05:30
|
|
|
val notificationBuilder = getNotificationBuilder(item)
|
|
|
|
setResumeNotification(notificationBuilder, item)
|
2023-03-07 07:14:09 +05:30
|
|
|
val path = item.path
|
|
|
|
var totalRead = path.fileSize()
|
2022-12-16 22:14:21 +05:30
|
|
|
val url = URL(item.url ?: return)
|
|
|
|
|
2023-06-08 23:08:51 +05:30
|
|
|
// only fetch the content length if it's not been returned by the API
|
|
|
|
if (item.downloadSize == 0L) {
|
2023-06-08 23:28:45 +05:30
|
|
|
url.getContentLength()?.let { size ->
|
2022-12-16 22:14:21 +05:30
|
|
|
item.downloadSize = size
|
2023-01-25 22:08:01 +05:30
|
|
|
Database.downloadDao().updateDownloadItem(item)
|
2022-05-21 13:32:04 +05:30
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
|
|
|
|
2023-06-08 23:28:45 +05:30
|
|
|
while (totalRead < item.downloadSize) {
|
|
|
|
try {
|
|
|
|
val con = startConnection(item, url, totalRead, item.downloadSize) ?: return
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2023-06-08 23:28:45 +05:30
|
|
|
@Suppress("NewApi") // The StandardOpenOption enum is desugared.
|
|
|
|
val sink = path.sink(StandardOpenOption.APPEND).buffer()
|
|
|
|
val sourceByte = con.inputStream.source()
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2023-06-08 23:28:45 +05:30
|
|
|
var lastTime = System.currentTimeMillis() / 1000
|
|
|
|
var lastRead: Long = 0
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2023-06-08 23:28:45 +05:30
|
|
|
try {
|
|
|
|
// Check if downloading is still active and read next bytes.
|
|
|
|
while (downloadQueue[item.id] && sourceByte
|
|
|
|
.read(sink.buffer, DownloadHelper.DOWNLOAD_CHUNK_SIZE)
|
|
|
|
.also { lastRead = it } != -1L
|
2022-12-21 21:10:48 +05:30
|
|
|
) {
|
2023-06-08 23:28:45 +05:30
|
|
|
sink.emit()
|
|
|
|
totalRead += lastRead
|
|
|
|
_downloadFlow.emit(
|
|
|
|
item.id to DownloadStatus.Progress(
|
|
|
|
lastRead,
|
|
|
|
totalRead,
|
2023-06-24 23:27:00 +05:30
|
|
|
item.downloadSize
|
|
|
|
)
|
2022-12-25 18:36:23 +05:30
|
|
|
)
|
2023-06-08 23:28:45 +05:30
|
|
|
if (item.downloadSize != -1L &&
|
|
|
|
System.currentTimeMillis() / 1000 > lastTime
|
|
|
|
) {
|
|
|
|
notificationBuilder
|
|
|
|
.setContentText(
|
|
|
|
totalRead.formatAsFileSize() + " / " +
|
2023-06-24 23:27:00 +05:30
|
|
|
item.downloadSize.formatAsFileSize()
|
2023-06-08 23:28:45 +05:30
|
|
|
)
|
|
|
|
.setProgress(
|
|
|
|
item.downloadSize.toInt(),
|
|
|
|
totalRead.toInt(),
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2023-06-08 23:28:45 +05:30
|
|
|
)
|
|
|
|
notificationManager.notify(
|
|
|
|
item.getNotificationId(),
|
2023-06-24 23:27:00 +05:30
|
|
|
notificationBuilder.build()
|
2023-06-08 23:28:45 +05:30
|
|
|
)
|
|
|
|
lastTime = System.currentTimeMillis() / 1000
|
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
}
|
2023-06-08 23:28:45 +05:30
|
|
|
} catch (_: CancellationException) {
|
|
|
|
} catch (e: Exception) {
|
|
|
|
toastFromMainThread("${getString(R.string.download)}: ${e.message}")
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Error(e.message.toString(), e))
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2023-06-08 23:28:45 +05:30
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
sink.flush()
|
|
|
|
sink.close()
|
|
|
|
sourceByte.close()
|
|
|
|
con.disconnect()
|
|
|
|
}
|
2023-06-24 23:27:00 +05:30
|
|
|
} catch (_: Exception) {
|
|
|
|
}
|
2023-02-08 06:20:06 +05:30
|
|
|
}
|
2022-09-09 21:09:49 +05:30
|
|
|
|
2022-12-25 14:37:01 +05:30
|
|
|
val completed = when {
|
|
|
|
totalRead < item.downloadSize -> {
|
2022-12-17 15:56:57 +05:30
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Paused)
|
|
|
|
false
|
|
|
|
}
|
2023-06-24 23:27:00 +05:30
|
|
|
|
2022-12-25 14:37:01 +05:30
|
|
|
else -> {
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Completed)
|
|
|
|
true
|
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-12-17 15:56:57 +05:30
|
|
|
setPauseNotification(notificationBuilder, item, completed)
|
2022-12-21 21:10:48 +05:30
|
|
|
pause(item.id)
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
2022-09-09 16:41:54 +05:30
|
|
|
|
2023-06-08 23:28:45 +05:30
|
|
|
private suspend fun startConnection(
|
|
|
|
item: DownloadItem,
|
|
|
|
url: URL,
|
|
|
|
alreadyRead: Long,
|
|
|
|
readLimit: Long?
|
|
|
|
): HttpURLConnection? {
|
|
|
|
// Set start range where last downloading was held.
|
|
|
|
val con = CronetHelper.cronetEngine.openConnection(url) as HttpURLConnection
|
|
|
|
con.requestMethod = "GET"
|
|
|
|
val limit = if (readLimit == null) {
|
|
|
|
""
|
|
|
|
} else {
|
|
|
|
min(readLimit, alreadyRead + BYTES_PER_REQUEST)
|
|
|
|
}
|
|
|
|
con.setRequestProperty("Range", "bytes=$alreadyRead-$limit")
|
|
|
|
con.connectTimeout = DownloadHelper.DEFAULT_TIMEOUT
|
|
|
|
con.readTimeout = DownloadHelper.DEFAULT_TIMEOUT
|
|
|
|
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
// Retry connecting to server for n times.
|
|
|
|
for (i in 1..DownloadHelper.DEFAULT_RETRY) {
|
|
|
|
try {
|
|
|
|
con.connect()
|
|
|
|
break
|
|
|
|
} catch (_: SocketTimeoutException) {
|
|
|
|
val message = getString(R.string.downloadfailed) + " " + i
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Error(message))
|
|
|
|
toastFromMainThread(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If link is expired try to regenerate using available info.
|
|
|
|
if (con.responseCode == 403) {
|
|
|
|
regenerateLink(item)
|
|
|
|
con.disconnect()
|
|
|
|
downloadFile(item)
|
|
|
|
return null
|
|
|
|
} else if (con.responseCode !in 200..299) {
|
|
|
|
val message = getString(R.string.downloadfailed) + ": " + con.responseMessage
|
|
|
|
_downloadFlow.emit(item.id to DownloadStatus.Error(message))
|
|
|
|
toastFromMainThread(message)
|
|
|
|
con.disconnect()
|
|
|
|
pause(item.id)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
return con
|
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
|
|
|
* Resume download which may have been paused.
|
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
fun resume(id: Int) {
|
2022-12-21 21:10:48 +05:30
|
|
|
// If file is already downloading then avoid new download job.
|
2023-02-06 07:25:48 +05:30
|
|
|
if (downloadQueue[id]) {
|
|
|
|
return
|
|
|
|
}
|
2022-12-21 21:10:48 +05:30
|
|
|
|
2023-02-06 07:25:48 +05:30
|
|
|
val downloadCount = downloadQueue.valueIterator().asSequence().count { it }
|
|
|
|
if (downloadCount >= DownloadHelper.getMaxConcurrentDownloads()) {
|
2022-12-25 15:33:28 +05:30
|
|
|
toastFromMainThread(getString(R.string.concurrent_downloads_limit_reached))
|
2023-02-08 06:20:06 +05:30
|
|
|
lifecycleScope.launch(coroutineContext) {
|
2022-12-25 15:33:28 +05:30
|
|
|
_downloadFlow.emit(id to DownloadStatus.Paused)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-08 06:20:06 +05:30
|
|
|
lifecycleScope.launch(coroutineContext) {
|
2023-01-25 22:08:01 +05:30
|
|
|
downloadFile(Database.downloadDao().findDownloadItemById(id))
|
2022-12-16 22:14:21 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
2022-12-21 21:10:48 +05:30
|
|
|
* Pause downloading job for given [id]. If no downloads are active, stop the service.
|
2022-12-17 15:56:57 +05:30
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
fun pause(id: Int) {
|
2022-12-21 21:10:48 +05:30
|
|
|
downloadQueue[id] = false
|
2022-12-17 15:56:57 +05:30
|
|
|
|
|
|
|
// Stop the service if no downloads are active.
|
2023-02-06 07:25:48 +05:30
|
|
|
if (downloadQueue.valueIterator().asSequence().none { it }) {
|
2023-02-06 08:10:05 +05:30
|
|
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
|
2022-12-21 21:10:48 +05:30
|
|
|
sendBroadcast(Intent(ACTION_SERVICE_STOPPED))
|
2022-12-17 15:56:57 +05:30
|
|
|
stopSelf()
|
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|
|
|
|
|
2022-12-25 10:16:53 +05:30
|
|
|
/**
|
|
|
|
* Regenerate stream url using available info format and quality.
|
|
|
|
*/
|
|
|
|
private suspend fun regenerateLink(item: DownloadItem) {
|
|
|
|
val streams = RetrofitInstance.api.getStreams(item.videoId)
|
|
|
|
val stream = when (item.type) {
|
|
|
|
FileType.AUDIO -> streams.audioStreams
|
|
|
|
FileType.VIDEO -> streams.videoStreams
|
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
stream?.find { it.format == item.format && it.quality == item.quality }?.let {
|
|
|
|
item.url = it.url
|
|
|
|
}
|
2023-01-25 22:08:01 +05:30
|
|
|
Database.downloadDao().updateDownloadItem(item)
|
2022-12-25 10:16:53 +05:30
|
|
|
}
|
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
/**
|
2022-12-21 21:10:48 +05:30
|
|
|
* Check whether the file downloading or not.
|
2022-12-17 15:56:57 +05:30
|
|
|
*/
|
2022-12-16 22:14:21 +05:30
|
|
|
fun isDownloading(id: Int): Boolean {
|
2023-02-06 07:25:48 +05:30
|
|
|
return downloadQueue[id]
|
2022-06-05 21:38:47 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun notifyForeground() {
|
2023-03-31 06:09:38 +05:30
|
|
|
notificationManager = getSystemService()!!
|
2022-09-09 21:09:49 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
summaryNotificationBuilder = NotificationCompat
|
|
|
|
.Builder(this, DOWNLOAD_CHANNEL_ID)
|
2023-01-14 22:47:34 +05:30
|
|
|
.setSmallIcon(R.drawable.ic_launcher_lockscreen)
|
2022-12-17 15:56:57 +05:30
|
|
|
.setContentTitle(getString(R.string.downloading))
|
|
|
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
|
|
.setGroup(DOWNLOAD_NOTIFICATION_GROUP)
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
|
|
.setOnlyAlertOnce(true)
|
|
|
|
.setGroupSummary(true)
|
|
|
|
|
|
|
|
startForeground(DOWNLOAD_PROGRESS_NOTIFICATION_ID, summaryNotificationBuilder.build())
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getNotificationBuilder(item: DownloadItem): NotificationCompat.Builder {
|
2023-04-09 17:36:28 +05:30
|
|
|
val intent = Intent(this@DownloadService, MainActivity::class.java)
|
|
|
|
.putExtra("fragmentToOpen", "downloads")
|
|
|
|
val activityIntent = PendingIntentCompat
|
|
|
|
.getActivity(this@DownloadService, 0, intent, FLAG_CANCEL_CURRENT, false)
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2022-12-17 15:56:57 +05:30
|
|
|
return NotificationCompat
|
2022-12-16 22:14:21 +05:30
|
|
|
.Builder(this, DOWNLOAD_CHANNEL_ID)
|
2022-12-21 21:10:48 +05:30
|
|
|
.setContentTitle("[${item.type}] ${item.fileName}")
|
2022-12-17 15:56:57 +05:30
|
|
|
.setProgress(0, 0, true)
|
2022-12-16 22:14:21 +05:30
|
|
|
.setOngoing(true)
|
2022-12-17 15:56:57 +05:30
|
|
|
.setContentIntent(activityIntent)
|
2022-12-16 22:14:21 +05:30
|
|
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
|
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
2022-12-17 15:56:57 +05:30
|
|
|
.setGroup(DOWNLOAD_NOTIFICATION_GROUP)
|
|
|
|
}
|
2022-12-16 22:14:21 +05:30
|
|
|
|
2022-12-25 18:36:23 +05:30
|
|
|
private fun setResumeNotification(
|
|
|
|
notificationBuilder: NotificationCompat.Builder,
|
2023-06-24 23:27:00 +05:30
|
|
|
item: DownloadItem
|
2022-12-25 18:36:23 +05:30
|
|
|
) {
|
2022-12-17 15:56:57 +05:30
|
|
|
notificationBuilder
|
|
|
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
|
|
|
.setWhen(System.currentTimeMillis())
|
|
|
|
.setOngoing(true)
|
|
|
|
.clearActions()
|
|
|
|
.addAction(getPauseAction(item.id))
|
|
|
|
|
2022-12-21 21:18:30 +05:30
|
|
|
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
|
2022-12-17 15:56:57 +05:30
|
|
|
}
|
|
|
|
|
2022-12-21 21:10:48 +05:30
|
|
|
private fun setPauseNotification(
|
|
|
|
notificationBuilder: NotificationCompat.Builder,
|
|
|
|
item: DownloadItem,
|
2023-06-24 23:27:00 +05:30
|
|
|
isCompleted: Boolean = false
|
2022-12-21 21:10:48 +05:30
|
|
|
) {
|
2022-12-17 15:56:57 +05:30
|
|
|
notificationBuilder
|
|
|
|
.setProgress(0, 0, false)
|
|
|
|
.setOngoing(false)
|
|
|
|
.clearActions()
|
|
|
|
|
|
|
|
if (isCompleted) {
|
|
|
|
notificationBuilder
|
|
|
|
.setSmallIcon(R.drawable.ic_done)
|
|
|
|
.setContentText(getString(R.string.download_completed))
|
|
|
|
} else {
|
|
|
|
notificationBuilder
|
|
|
|
.setSmallIcon(R.drawable.ic_pause)
|
|
|
|
.setContentText(getString(R.string.download_paused))
|
|
|
|
.addAction(getResumeAction(item.id))
|
|
|
|
}
|
2022-12-21 21:18:30 +05:30
|
|
|
notificationManager.notify(item.getNotificationId(), notificationBuilder.build())
|
2022-06-05 21:38:47 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun getResumeAction(id: Int): NotificationCompat.Action {
|
2023-04-09 17:36:28 +05:30
|
|
|
val intent = Intent(this, NotificationReceiver::class.java)
|
|
|
|
.setAction(ACTION_DOWNLOAD_RESUME)
|
|
|
|
.putExtra("id", id)
|
2022-12-16 22:14:21 +05:30
|
|
|
|
|
|
|
return NotificationCompat.Action.Builder(
|
2022-12-17 15:56:57 +05:30
|
|
|
R.drawable.ic_play,
|
|
|
|
getString(R.string.resume),
|
2023-06-24 23:27:00 +05:30
|
|
|
PendingIntentCompat.getBroadcast(this, id, intent, FLAG_UPDATE_CURRENT, false)
|
2022-12-16 22:14:21 +05:30
|
|
|
).build()
|
2022-06-05 23:10:16 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
private fun getPauseAction(id: Int): NotificationCompat.Action {
|
2023-01-18 09:00:27 +05:30
|
|
|
val intent = Intent(this, NotificationReceiver::class.java)
|
|
|
|
.setAction(ACTION_DOWNLOAD_PAUSE)
|
|
|
|
.putExtra("id", id)
|
2022-06-05 23:10:16 +05:30
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
return NotificationCompat.Action.Builder(
|
|
|
|
R.drawable.ic_pause,
|
2022-12-17 15:56:57 +05:30
|
|
|
getString(R.string.pause),
|
2023-06-24 23:27:00 +05:30
|
|
|
PendingIntentCompat.getBroadcast(this, id, intent, FLAG_UPDATE_CURRENT, false)
|
2022-12-16 22:14:21 +05:30
|
|
|
).build()
|
|
|
|
}
|
2022-09-08 22:11:57 +05:30
|
|
|
|
2023-01-14 22:47:34 +05:30
|
|
|
/**
|
|
|
|
* Get a [File] from the corresponding download directory and the file name
|
|
|
|
*/
|
2023-03-07 07:14:09 +05:30
|
|
|
private fun getDownloadPath(directory: String, fileName: String): Path {
|
2023-04-21 07:07:41 +05:30
|
|
|
return DownloadHelper.getDownloadDir(this, directory).resolve(fileName).absolute()
|
2023-01-14 22:47:34 +05:30
|
|
|
}
|
|
|
|
|
2022-12-16 22:14:21 +05:30
|
|
|
override fun onDestroy() {
|
2022-12-21 21:10:48 +05:30
|
|
|
downloadQueue.clear()
|
2022-12-16 22:14:21 +05:30
|
|
|
IS_DOWNLOAD_RUNNING = false
|
2022-12-21 21:10:48 +05:30
|
|
|
sendBroadcast(Intent(ACTION_SERVICE_STOPPED))
|
2022-03-03 12:08:36 +05:30
|
|
|
super.onDestroy()
|
|
|
|
}
|
2022-09-19 23:37:55 +05:30
|
|
|
|
2023-02-06 07:13:38 +05:30
|
|
|
override fun onBind(intent: Intent): IBinder {
|
|
|
|
super.onBind(intent)
|
|
|
|
intent.getIntArrayExtra("ids")?.forEach { resume(it) }
|
2022-12-16 22:14:21 +05:30
|
|
|
return binder
|
|
|
|
}
|
|
|
|
|
|
|
|
inner class LocalBinder : Binder() {
|
|
|
|
fun getService(): DownloadService = this@DownloadService
|
|
|
|
}
|
|
|
|
|
2022-09-19 23:37:55 +05:30
|
|
|
companion object {
|
2022-12-17 15:56:57 +05:30
|
|
|
private const val DOWNLOAD_NOTIFICATION_GROUP = "download_notification_group"
|
2022-12-21 21:10:48 +05:30
|
|
|
const val ACTION_SERVICE_STARTED =
|
|
|
|
"com.github.libretube.services.DownloadService.ACTION_SERVICE_STARTED"
|
|
|
|
const val ACTION_SERVICE_STOPPED =
|
|
|
|
"com.github.libretube.services.DownloadService.ACTION_SERVICE_STOPPED"
|
2022-09-19 23:37:55 +05:30
|
|
|
var IS_DOWNLOAD_RUNNING = false
|
2023-06-08 23:28:45 +05:30
|
|
|
private const val BYTES_PER_REQUEST = 10000000L
|
2022-09-19 23:37:55 +05:30
|
|
|
}
|
2022-03-03 12:08:36 +05:30
|
|
|
}
|