forked from recloudstream/cloudstream
fixed background downloads
merging this fucking shit directly to master because git has been assfucking for an hour now
This commit is contained in:
parent
0adb01e1cf
commit
77cd2cbc8e
5 changed files with 386 additions and 170 deletions
|
@ -24,13 +24,13 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
buildToolsVersion "30.0.3"
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.lagradost.cloudstream3"
|
applicationId "com.lagradost.cloudstream3"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
versionCode 25
|
versionCode 25
|
||||||
versionName "1.9.9"
|
versionName "1.9.9"
|
||||||
|
|
||||||
|
@ -131,4 +131,8 @@ dependencies {
|
||||||
|
|
||||||
// TorrentStream
|
// TorrentStream
|
||||||
implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
||||||
|
|
||||||
|
// Downloading
|
||||||
|
implementation "androidx.work:work-runtime:2.7.0-beta01"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.7.0-beta01"
|
||||||
}
|
}
|
|
@ -56,7 +56,7 @@ object DownloadButtonSetup {
|
||||||
activity?.let { ctx ->
|
activity?.let { ctx ->
|
||||||
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
|
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
|
||||||
if (pkg != null) {
|
if (pkg != null) {
|
||||||
VideoDownloadManager.downloadFromResume(ctx, pkg)
|
VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg)
|
||||||
} else {
|
} else {
|
||||||
VideoDownloadManager.downloadEvent.invoke(
|
VideoDownloadManager.downloadEvent.invoke(
|
||||||
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
||||||
|
|
|
@ -492,8 +492,8 @@ class ResultFragment : Fragment() {
|
||||||
)
|
)
|
||||||
|
|
||||||
// DOWNLOAD VIDEO
|
// DOWNLOAD VIDEO
|
||||||
VideoDownloadManager.downloadEpisode(
|
VideoDownloadManager.downloadEpisodeUsingWorker(
|
||||||
activity,
|
ctx,
|
||||||
src,//url ?: return,
|
src,//url ?: return,
|
||||||
folder,
|
folder,
|
||||||
meta,
|
meta,
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_INFO
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_PACKAGE
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.createNotification
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadCheck
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadEpisode
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadFromResume
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadStatusEvent
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
const val DOWNLOAD_CHECK = "DownloadCheck"
|
||||||
|
|
||||||
|
class DownloadFileWorkManager(val context: Context, val workerParams: WorkerParameters) :
|
||||||
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val key = workerParams.inputData.getString("key")
|
||||||
|
try {
|
||||||
|
println("KEY $key")
|
||||||
|
if (key == DOWNLOAD_CHECK) {
|
||||||
|
downloadCheck(applicationContext, ::handleNotification)?.let {
|
||||||
|
awaitDownload(it)
|
||||||
|
}
|
||||||
|
} else if (key != null) {
|
||||||
|
val info = applicationContext.getKey<VideoDownloadManager.DownloadInfo>(WORK_KEY_INFO, key)
|
||||||
|
val pkg =
|
||||||
|
applicationContext.getKey<VideoDownloadManager.DownloadResumePackage>(WORK_KEY_PACKAGE, key)
|
||||||
|
|
||||||
|
if (info != null) {
|
||||||
|
downloadEpisode(
|
||||||
|
applicationContext,
|
||||||
|
info.source,
|
||||||
|
info.folder,
|
||||||
|
info.ep,
|
||||||
|
info.links,
|
||||||
|
::handleNotification
|
||||||
|
)
|
||||||
|
awaitDownload(info.ep.id)
|
||||||
|
} else if (pkg != null) {
|
||||||
|
downloadFromResume(applicationContext, pkg, ::handleNotification)
|
||||||
|
awaitDownload(pkg.item.ep.id)
|
||||||
|
}
|
||||||
|
removeKeys(key)
|
||||||
|
}
|
||||||
|
return Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (key != null) {
|
||||||
|
removeKeys(key)
|
||||||
|
}
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeKeys(key: String) {
|
||||||
|
applicationContext.removeKey(WORK_KEY_INFO, key)
|
||||||
|
applicationContext.removeKey(WORK_KEY_PACKAGE, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun awaitDownload(id: Int) {
|
||||||
|
var isDone = false
|
||||||
|
val listener = { data: Pair<Int, VideoDownloadManager.DownloadType> ->
|
||||||
|
if (id == data.first) {
|
||||||
|
when (data.second) {
|
||||||
|
VideoDownloadManager.DownloadType.IsDone, VideoDownloadManager.DownloadType.IsFailed, VideoDownloadManager.DownloadType.IsStopped -> {
|
||||||
|
isDone = true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadStatusEvent += listener
|
||||||
|
while (!isDone) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
downloadStatusEvent -= listener
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleNotification(id: Int, notification: Notification) {
|
||||||
|
main {
|
||||||
|
setForegroundAsync(ForegroundInfo(id, notification))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.*
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
@ -17,12 +18,18 @@ import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import androidx.work.WorkManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.github.se_bastiaan.torrentstream.StreamStatus
|
import com.github.se_bastiaan.torrentstream.StreamStatus
|
||||||
import com.github.se_bastiaan.torrentstream.Torrent
|
import com.github.se_bastiaan.torrentstream.Torrent
|
||||||
import com.github.se_bastiaan.torrentstream.TorrentOptions
|
import com.github.se_bastiaan.torrentstream.TorrentOptions
|
||||||
import com.github.se_bastiaan.torrentstream.TorrentStream
|
import com.github.se_bastiaan.torrentstream.TorrentStream
|
||||||
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener
|
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener
|
||||||
|
import com.lagradost.cloudstream3.AnimeLoadResponse
|
||||||
import com.lagradost.cloudstream3.MainActivity
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
@ -217,7 +224,7 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotification(
|
suspend fun createNotification(
|
||||||
context: Context,
|
context: Context,
|
||||||
source: String?,
|
source: String?,
|
||||||
linkName: String?,
|
linkName: String?,
|
||||||
|
@ -225,84 +232,72 @@ object VideoDownloadManager {
|
||||||
state: DownloadType,
|
state: DownloadType,
|
||||||
progress: Long,
|
progress: Long,
|
||||||
total: Long,
|
total: Long,
|
||||||
) {
|
notificationCallback: (Int, Notification) -> Unit
|
||||||
if(total <= 0) return // crash, invalid data
|
|
||||||
|
|
||||||
main { // DON'T WANT TO SLOW IT DOWN
|
): Notification? {
|
||||||
val builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
|
if (total <= 0) return null// crash, invalid data
|
||||||
.setAutoCancel(true)
|
|
||||||
.setColorized(true)
|
|
||||||
.setOnlyAlertOnce(true)
|
|
||||||
.setShowWhen(false)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
||||||
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
|
||||||
.setContentTitle(ep.mainName)
|
|
||||||
.setSmallIcon(
|
|
||||||
when (state) {
|
|
||||||
DownloadType.IsDone -> imgDone
|
|
||||||
DownloadType.IsDownloading -> imgDownloading
|
|
||||||
DownloadType.IsPaused -> imgPaused
|
|
||||||
DownloadType.IsFailed -> imgError
|
|
||||||
DownloadType.IsStopped -> imgStopped
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (ep.sourceApiName != null) {
|
// main { // DON'T WANT TO SLOW IT DOWN
|
||||||
builder.setSubText(ep.sourceApiName)
|
val builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID)
|
||||||
}
|
.setAutoCancel(true)
|
||||||
|
.setColorized(true)
|
||||||
if (source != null) {
|
.setOnlyAlertOnce(true)
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
.setShowWhen(false)
|
||||||
data = source.toUri()
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
||||||
|
.setContentTitle(ep.mainName)
|
||||||
|
.setSmallIcon(
|
||||||
|
when (state) {
|
||||||
|
DownloadType.IsDone -> imgDone
|
||||||
|
DownloadType.IsDownloading -> imgDownloading
|
||||||
|
DownloadType.IsPaused -> imgPaused
|
||||||
|
DownloadType.IsFailed -> imgError
|
||||||
|
DownloadType.IsStopped -> imgStopped
|
||||||
}
|
}
|
||||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
|
)
|
||||||
builder.setContentIntent(pendingIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
if (ep.sourceApiName != null) {
|
||||||
builder.setProgress(total.toInt(), progress.toInt(), false)
|
builder.setSubText(ep.sourceApiName)
|
||||||
}
|
}
|
||||||
|
|
||||||
val rowTwoExtra = if (ep.name != null) " - ${ep.name}\n" else ""
|
if (source != null) {
|
||||||
val rowTwo = if (ep.season != null && ep.episode != null) {
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
"${context.getString(R.string.season_short)}${ep.season}:${context.getString(R.string.episode_short)}${ep.episode}" + rowTwoExtra
|
data = source.toUri()
|
||||||
} else if (ep.episode != null) {
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
"${context.getString(R.string.episode)} ${ep.episode}" + rowTwoExtra
|
|
||||||
} else {
|
|
||||||
(ep.name ?: "") + ""
|
|
||||||
}
|
}
|
||||||
val downloadFormat = context.getString(R.string.download_format)
|
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
|
||||||
|
builder.setContentIntent(pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
||||||
if (ep.poster != null) {
|
builder.setProgress(total.toInt(), progress.toInt(), false)
|
||||||
val poster = withContext(Dispatchers.IO) {
|
}
|
||||||
context.getImageBitmapFromUrl(ep.poster)
|
|
||||||
}
|
val rowTwoExtra = if (ep.name != null) " - ${ep.name}\n" else ""
|
||||||
if (poster != null)
|
val rowTwo = if (ep.season != null && ep.episode != null) {
|
||||||
builder.setLargeIcon(poster)
|
"${context.getString(R.string.season_short)}${ep.season}:${context.getString(R.string.episode_short)}${ep.episode}" + rowTwoExtra
|
||||||
|
} else if (ep.episode != null) {
|
||||||
|
"${context.getString(R.string.episode)} ${ep.episode}" + rowTwoExtra
|
||||||
|
} else {
|
||||||
|
(ep.name ?: "") + ""
|
||||||
|
}
|
||||||
|
val downloadFormat = context.getString(R.string.download_format)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (ep.poster != null) {
|
||||||
|
val poster = withContext(Dispatchers.IO) {
|
||||||
|
context.getImageBitmapFromUrl(ep.poster)
|
||||||
}
|
}
|
||||||
|
if (poster != null)
|
||||||
|
builder.setLargeIcon(poster)
|
||||||
|
}
|
||||||
|
|
||||||
val progressPercentage = progress * 100 / total
|
val progressPercentage = progress * 100 / total
|
||||||
val progressMbString = "%.1f".format(progress / 1000000f)
|
val progressMbString = "%.1f".format(progress / 1000000f)
|
||||||
val totalMbString = "%.1f".format(total / 1000000f)
|
val totalMbString = "%.1f".format(total / 1000000f)
|
||||||
val bigText =
|
val bigText =
|
||||||
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
||||||
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString MB/$totalMbString MB)"
|
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString MB/$totalMbString MB)"
|
||||||
} else if (state == DownloadType.IsFailed) {
|
|
||||||
downloadFormat.format(context.getString(R.string.download_failed), rowTwo)
|
|
||||||
} else if (state == DownloadType.IsDone) {
|
|
||||||
downloadFormat.format(context.getString(R.string.download_done), rowTwo)
|
|
||||||
} else {
|
|
||||||
downloadFormat.format(context.getString(R.string.download_canceled), rowTwo)
|
|
||||||
}
|
|
||||||
|
|
||||||
val bodyStyle = NotificationCompat.BigTextStyle()
|
|
||||||
bodyStyle.bigText(bigText)
|
|
||||||
builder.setStyle(bodyStyle)
|
|
||||||
} else {
|
|
||||||
val txt = if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
|
||||||
rowTwo
|
|
||||||
} else if (state == DownloadType.IsFailed) {
|
} else if (state == DownloadType.IsFailed) {
|
||||||
downloadFormat.format(context.getString(R.string.download_failed), rowTwo)
|
downloadFormat.format(context.getString(R.string.download_failed), rowTwo)
|
||||||
} else if (state == DownloadType.IsDone) {
|
} else if (state == DownloadType.IsDone) {
|
||||||
|
@ -311,68 +306,86 @@ object VideoDownloadManager {
|
||||||
downloadFormat.format(context.getString(R.string.download_canceled), rowTwo)
|
downloadFormat.format(context.getString(R.string.download_canceled), rowTwo)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setContentText(txt)
|
val bodyStyle = NotificationCompat.BigTextStyle()
|
||||||
|
bodyStyle.bigText(bigText)
|
||||||
|
builder.setStyle(bodyStyle)
|
||||||
|
} else {
|
||||||
|
val txt = if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
||||||
|
rowTwo
|
||||||
|
} else if (state == DownloadType.IsFailed) {
|
||||||
|
downloadFormat.format(context.getString(R.string.download_failed), rowTwo)
|
||||||
|
} else if (state == DownloadType.IsDone) {
|
||||||
|
downloadFormat.format(context.getString(R.string.download_done), rowTwo)
|
||||||
|
} else {
|
||||||
|
downloadFormat.format(context.getString(R.string.download_canceled), rowTwo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((state == DownloadType.IsDownloading || state == DownloadType.IsPaused) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
builder.setContentText(txt)
|
||||||
val actionTypes: MutableList<DownloadActionType> = ArrayList()
|
}
|
||||||
// INIT
|
|
||||||
if (state == DownloadType.IsDownloading) {
|
|
||||||
actionTypes.add(DownloadActionType.Pause)
|
|
||||||
actionTypes.add(DownloadActionType.Stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == DownloadType.IsPaused) {
|
if ((state == DownloadType.IsDownloading || state == DownloadType.IsPaused) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
actionTypes.add(DownloadActionType.Resume)
|
val actionTypes: MutableList<DownloadActionType> = ArrayList()
|
||||||
actionTypes.add(DownloadActionType.Stop)
|
// INIT
|
||||||
}
|
if (state == DownloadType.IsDownloading) {
|
||||||
|
actionTypes.add(DownloadActionType.Pause)
|
||||||
// ADD ACTIONS
|
actionTypes.add(DownloadActionType.Stop)
|
||||||
for ((index, i) in actionTypes.withIndex()) {
|
|
||||||
val actionResultIntent = Intent(context, VideoDownloadService::class.java)
|
|
||||||
|
|
||||||
actionResultIntent.putExtra(
|
|
||||||
"type", when (i) {
|
|
||||||
DownloadActionType.Resume -> "resume"
|
|
||||||
DownloadActionType.Pause -> "pause"
|
|
||||||
DownloadActionType.Stop -> "stop"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
actionResultIntent.putExtra("id", ep.id)
|
|
||||||
|
|
||||||
val pending: PendingIntent = PendingIntent.getService(
|
|
||||||
// BECAUSE episodes lying near will have the same id +1, index will give the same requested as the previous episode, *100000 fixes this
|
|
||||||
context, (4337 + index * 100000 + ep.id),
|
|
||||||
actionResultIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.addAction(
|
|
||||||
NotificationCompat.Action(
|
|
||||||
when (i) {
|
|
||||||
DownloadActionType.Resume -> pressToResumeIcon
|
|
||||||
DownloadActionType.Pause -> pressToPauseIcon
|
|
||||||
DownloadActionType.Stop -> pressToStopIcon
|
|
||||||
}, when (i) {
|
|
||||||
DownloadActionType.Resume -> context.getString(R.string.resume)
|
|
||||||
DownloadActionType.Pause -> context.getString(R.string.pause)
|
|
||||||
DownloadActionType.Stop -> context.getString(R.string.cancel)
|
|
||||||
}, pending
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCreatedNotChanel) {
|
if (state == DownloadType.IsPaused) {
|
||||||
context.createNotificationChannel()
|
actionTypes.add(DownloadActionType.Resume)
|
||||||
|
actionTypes.add(DownloadActionType.Stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(NotificationManagerCompat.from(context)) {
|
// ADD ACTIONS
|
||||||
// notificationId is a unique int for each notification that you must define
|
for ((index, i) in actionTypes.withIndex()) {
|
||||||
notify(ep.id, builder.build())
|
val actionResultIntent = Intent(context, VideoDownloadService::class.java)
|
||||||
|
|
||||||
|
actionResultIntent.putExtra(
|
||||||
|
"type", when (i) {
|
||||||
|
DownloadActionType.Resume -> "resume"
|
||||||
|
DownloadActionType.Pause -> "pause"
|
||||||
|
DownloadActionType.Stop -> "stop"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
actionResultIntent.putExtra("id", ep.id)
|
||||||
|
|
||||||
|
val pending: PendingIntent = PendingIntent.getService(
|
||||||
|
// BECAUSE episodes lying near will have the same id +1, index will give the same requested as the previous episode, *100000 fixes this
|
||||||
|
context, (4337 + index * 100000 + ep.id),
|
||||||
|
actionResultIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.addAction(
|
||||||
|
NotificationCompat.Action(
|
||||||
|
when (i) {
|
||||||
|
DownloadActionType.Resume -> pressToResumeIcon
|
||||||
|
DownloadActionType.Pause -> pressToPauseIcon
|
||||||
|
DownloadActionType.Stop -> pressToStopIcon
|
||||||
|
}, when (i) {
|
||||||
|
DownloadActionType.Resume -> context.getString(R.string.resume)
|
||||||
|
DownloadActionType.Pause -> context.getString(R.string.pause)
|
||||||
|
DownloadActionType.Stop -> context.getString(R.string.cancel)
|
||||||
|
}, pending
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasCreatedNotChanel) {
|
||||||
|
context.createNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
val notification = builder.build()
|
||||||
|
notificationCallback(ep.id, notification)
|
||||||
|
with(NotificationManagerCompat.from(context)) {
|
||||||
|
// notificationId is a unique int for each notification that you must define
|
||||||
|
notify(ep.id, notification)
|
||||||
|
}
|
||||||
|
return notification
|
||||||
|
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val reservedChars = "|\\?*<\":>+[]/\'"
|
private const val reservedChars = "|\\?*<\":>+[]/\'"
|
||||||
|
@ -678,7 +691,8 @@ object VideoDownloadManager {
|
||||||
createNotificationCallback.invoke(
|
createNotificationCallback.invoke(
|
||||||
CreateNotificationMetadata(
|
CreateNotificationMetadata(
|
||||||
type,
|
type,
|
||||||
lengthSize.first, lengthSize.second
|
lengthSize.first,
|
||||||
|
lengthSize.second
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -816,7 +830,8 @@ object VideoDownloadManager {
|
||||||
val fileLength = stream.fileLength!!
|
val fileLength = stream.fileLength!!
|
||||||
|
|
||||||
// CONNECT
|
// CONNECT
|
||||||
val connection: URLConnection = URL(link.url.replace(" ", "%20")).openConnection() // IDK OLD PHONES BE WACK
|
val connection: URLConnection =
|
||||||
|
URL(link.url.replace(" ", "%20")).openConnection() // IDK OLD PHONES BE WACK
|
||||||
|
|
||||||
// SET CONNECTION SETTINGS
|
// SET CONNECTION SETTINGS
|
||||||
connection.connectTimeout = 10000
|
connection.connectTimeout = 10000
|
||||||
|
@ -851,7 +866,8 @@ object VideoDownloadManager {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // fuck android
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // fuck android
|
||||||
connection.contentLengthLong ?: 0L
|
connection.contentLengthLong ?: 0L
|
||||||
} else {
|
} else {
|
||||||
connection.getHeaderField("content-length").toLongOrNull() ?: connection.contentLength?.toLong() ?: 0L
|
connection.getHeaderField("content-length").toLongOrNull() ?: connection.contentLength?.toLong()
|
||||||
|
?: 0L
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
|
@ -862,7 +878,11 @@ object VideoDownloadManager {
|
||||||
if (extension == "mp4" && bytesTotal < 5000000) return ERROR_TOO_SMALL_CONNECTION // DATA IS LESS THAN 5MB, SOMETHING IS WRONG
|
if (extension == "mp4" && bytesTotal < 5000000) return ERROR_TOO_SMALL_CONNECTION // DATA IS LESS THAN 5MB, SOMETHING IS WRONG
|
||||||
|
|
||||||
parentId?.let {
|
parentId?.let {
|
||||||
context.setKey(KEY_DOWNLOAD_INFO, it.toString(), DownloadedFileInfo(bytesTotal, relativePath, displayName))
|
context.setKey(
|
||||||
|
KEY_DOWNLOAD_INFO,
|
||||||
|
it.toString(),
|
||||||
|
DownloadedFileInfo(bytesTotal, relativePath, displayName)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could use connection.contentType for mime types when creating the file,
|
// Could use connection.contentType for mime types when creating the file,
|
||||||
|
@ -1140,7 +1160,13 @@ object VideoDownloadManager {
|
||||||
try {
|
try {
|
||||||
downloadStatus[id] = type
|
downloadStatus[id] = type
|
||||||
downloadStatusEvent.invoke(Pair(id, type))
|
downloadStatusEvent.invoke(Pair(id, type))
|
||||||
downloadProgressEvent.invoke(Triple(id, bytesDownloaded, (bytesDownloaded / tsProgress) * totalTs))
|
downloadProgressEvent.invoke(
|
||||||
|
Triple(
|
||||||
|
id,
|
||||||
|
bytesDownloaded,
|
||||||
|
(bytesDownloaded / tsProgress) * totalTs
|
||||||
|
)
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// IDK MIGHT ERROR
|
// IDK MIGHT ERROR
|
||||||
}
|
}
|
||||||
|
@ -1283,50 +1309,65 @@ object VideoDownloadManager {
|
||||||
folder: String?,
|
folder: String?,
|
||||||
ep: DownloadEpisodeMetadata,
|
ep: DownloadEpisodeMetadata,
|
||||||
link: ExtractorLink,
|
link: ExtractorLink,
|
||||||
|
notificationCallback: (Int, Notification) -> Unit,
|
||||||
tryResume: Boolean = false,
|
tryResume: Boolean = false,
|
||||||
): Int {
|
): Int {
|
||||||
val name = sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}")
|
val name = sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}")
|
||||||
|
|
||||||
if (link.isM3u8 || link.url.endsWith(".m3u8")) {
|
if (link.isM3u8 || link.url.endsWith(".m3u8")) {
|
||||||
val startIndex = if (tryResume) {
|
val startIndex = if (tryResume) {
|
||||||
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, ep.id.toString(), null)?.extraInfo?.toIntOrNull()
|
context.getKey<DownloadedFileInfo>(
|
||||||
|
KEY_DOWNLOAD_INFO,
|
||||||
|
ep.id.toString(),
|
||||||
|
null
|
||||||
|
)?.extraInfo?.toIntOrNull()
|
||||||
} else null
|
} else null
|
||||||
return downloadHLS(context, link, name, folder, ep.id, startIndex) { meta ->
|
return downloadHLS(context, link, name, folder, ep.id, startIndex) { meta ->
|
||||||
createNotification(
|
main {
|
||||||
context,
|
createNotification(
|
||||||
source,
|
context,
|
||||||
link.name,
|
source,
|
||||||
ep,
|
link.name,
|
||||||
meta.type,
|
ep,
|
||||||
meta.bytesDownloaded,
|
meta.type,
|
||||||
meta.bytesTotal
|
meta.bytesDownloaded,
|
||||||
)
|
meta.bytesTotal,
|
||||||
|
notificationCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalSafeApiCall {
|
return normalSafeApiCall {
|
||||||
downloadThing(context, link, name, folder, "mp4", tryResume, ep.id) { meta ->
|
downloadThing(context, link, name, folder, "mp4", tryResume, ep.id) { meta ->
|
||||||
createNotification(
|
main {
|
||||||
context,
|
createNotification(
|
||||||
source,
|
context,
|
||||||
link.name,
|
source,
|
||||||
ep,
|
link.name,
|
||||||
meta.type,
|
ep,
|
||||||
meta.bytesDownloaded,
|
meta.type,
|
||||||
meta.bytesTotal
|
meta.bytesDownloaded,
|
||||||
)
|
meta.bytesTotal,
|
||||||
|
notificationCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} ?: ERROR_UNKNOWN
|
} ?: ERROR_UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadCheck(context: Context) {
|
fun downloadCheck(
|
||||||
|
context: Context, notificationCallback: (Int, Notification) -> Unit,
|
||||||
|
): Int? {
|
||||||
if (currentDownloads.size < maxConcurrentDownloads && downloadQueue.size > 0) {
|
if (currentDownloads.size < maxConcurrentDownloads && downloadQueue.size > 0) {
|
||||||
val pkg = downloadQueue.removeFirst()
|
val pkg = downloadQueue.removeFirst()
|
||||||
val item = pkg.item
|
val item = pkg.item
|
||||||
val id = item.ep.id
|
val id = item.ep.id
|
||||||
if (currentDownloads.contains(id)) { // IF IT IS ALREADY DOWNLOADING, RESUME IT
|
if (currentDownloads.contains(id)) { // IF IT IS ALREADY DOWNLOADING, RESUME IT
|
||||||
downloadEvent.invoke(Pair(id, DownloadActionType.Resume))
|
downloadEvent.invoke(Pair(id, DownloadActionType.Resume))
|
||||||
return
|
/** ID needs to be returned to the work-manager to properly await notification */
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDownloads.add(id)
|
currentDownloads.add(id)
|
||||||
|
@ -1340,7 +1381,15 @@ object VideoDownloadManager {
|
||||||
context.setKey(KEY_RESUME_PACKAGES, id.toString(), DownloadResumePackage(item, index))
|
context.setKey(KEY_RESUME_PACKAGES, id.toString(), DownloadResumePackage(item, index))
|
||||||
val connectionResult = withContext(Dispatchers.IO) {
|
val connectionResult = withContext(Dispatchers.IO) {
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
downloadSingleEpisode(context, item.source, item.folder, item.ep, link, resume)
|
downloadSingleEpisode(
|
||||||
|
context,
|
||||||
|
item.source,
|
||||||
|
item.folder,
|
||||||
|
item.ep,
|
||||||
|
link,
|
||||||
|
notificationCallback,
|
||||||
|
resume
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (connectionResult != null && connectionResult > 0) { // SUCCESS
|
if (connectionResult != null && connectionResult > 0) { // SUCCESS
|
||||||
|
@ -1352,10 +1401,12 @@ object VideoDownloadManager {
|
||||||
logError(e)
|
logError(e)
|
||||||
} finally {
|
} finally {
|
||||||
currentDownloads.remove(id)
|
currentDownloads.remove(id)
|
||||||
downloadCheck(context)
|
// Because otherwise notifications will not get caught by the workmanager
|
||||||
|
downloadCheckUsingWorker(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadFileInfoAndUpdateSettings(context: Context, id: Int): DownloadedFileInfoResult? {
|
fun getDownloadFileInfoAndUpdateSettings(context: Context, id: Int): DownloadedFileInfoResult? {
|
||||||
|
@ -1414,23 +1465,28 @@ object VideoDownloadManager {
|
||||||
return context.getKey(KEY_RESUME_PACKAGES, id.toString())
|
return context.getKey(KEY_RESUME_PACKAGES, id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadFromResume(context: Activity, pkg: DownloadResumePackage, setKey: Boolean = true) {
|
fun downloadFromResume(
|
||||||
|
context: Context,
|
||||||
|
pkg: DownloadResumePackage,
|
||||||
|
notificationCallback: (Int, Notification) -> Unit,
|
||||||
|
setKey: Boolean = true
|
||||||
|
) {
|
||||||
if (!currentDownloads.any { it == pkg.item.ep.id }) {
|
if (!currentDownloads.any { it == pkg.item.ep.id }) {
|
||||||
if (currentDownloads.size == maxConcurrentDownloads) {
|
if (currentDownloads.size == maxConcurrentDownloads) {
|
||||||
main {
|
main {
|
||||||
showToast( // can be replaced with regular Toast
|
// showToast( // can be replaced with regular Toast
|
||||||
context,
|
// context,
|
||||||
"${pkg.item.ep.mainName}${pkg.item.ep.episode?.let { " ${context.getString(R.string.episode)} $it " } ?: " "}${
|
// "${pkg.item.ep.mainName}${pkg.item.ep.episode?.let { " ${context.getString(R.string.episode)} $it " } ?: " "}${
|
||||||
context.getString(
|
// context.getString(
|
||||||
R.string.queued
|
// R.string.queued
|
||||||
)
|
// )
|
||||||
}",
|
// }",
|
||||||
Toast.LENGTH_SHORT
|
// Toast.LENGTH_SHORT
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
downloadQueue.addLast(pkg)
|
downloadQueue.addLast(pkg)
|
||||||
downloadCheck(context)
|
downloadCheck(context, notificationCallback)
|
||||||
if (setKey) saveQueue(context)
|
if (setKey) saveQueue(context)
|
||||||
} else {
|
} else {
|
||||||
downloadEvent.invoke(
|
downloadEvent.invoke(
|
||||||
|
@ -1457,15 +1513,78 @@ object VideoDownloadManager {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
fun downloadEpisode(
|
fun downloadEpisode(
|
||||||
context: Activity?,
|
context: Context?,
|
||||||
source: String?,
|
source: String?,
|
||||||
folder: String?,
|
folder: String?,
|
||||||
ep: DownloadEpisodeMetadata,
|
ep: DownloadEpisodeMetadata,
|
||||||
links: List<ExtractorLink>
|
links: List<ExtractorLink>,
|
||||||
|
notificationCallback: (Int, Notification) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (context == null) return
|
if (context == null) return
|
||||||
if (links.isNotEmpty()) {
|
if (links.isNotEmpty()) {
|
||||||
downloadFromResume(context, DownloadResumePackage(DownloadItem(source, folder, ep, links), null))
|
downloadFromResume(
|
||||||
|
context,
|
||||||
|
DownloadResumePackage(DownloadItem(source, folder, ep, links), null),
|
||||||
|
notificationCallback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Worker stuff */
|
||||||
|
private fun startWork(context: Context, key: String) {
|
||||||
|
val req = OneTimeWorkRequest.Builder(DownloadFileWorkManager::class.java)
|
||||||
|
.setInputData(
|
||||||
|
Data.Builder()
|
||||||
|
.putString("key", key)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
(WorkManager.getInstance(context)).enqueueUniqueWork(
|
||||||
|
key,
|
||||||
|
ExistingWorkPolicy.KEEP,
|
||||||
|
req
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadCheckUsingWorker(
|
||||||
|
context: Context,
|
||||||
|
) {
|
||||||
|
startWork(context, DOWNLOAD_CHECK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadFromResumeUsingWorker(
|
||||||
|
context: Context,
|
||||||
|
pkg: DownloadResumePackage,
|
||||||
|
) {
|
||||||
|
val key = pkg.item.ep.id.toString()
|
||||||
|
context.setKey(WORK_KEY_PACKAGE, key, pkg)
|
||||||
|
startWork(context, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys are needed to transfer the data to the worker reliably and without exceeding the data limit
|
||||||
|
const val WORK_KEY_PACKAGE = "work_key_package"
|
||||||
|
const val WORK_KEY_INFO = "work_key_info"
|
||||||
|
|
||||||
|
fun downloadEpisodeUsingWorker(
|
||||||
|
context: Context,
|
||||||
|
source: String?,
|
||||||
|
folder: String?,
|
||||||
|
ep: DownloadEpisodeMetadata,
|
||||||
|
links: List<ExtractorLink>,
|
||||||
|
) {
|
||||||
|
val info = DownloadInfo(
|
||||||
|
source, folder, ep, links
|
||||||
|
)
|
||||||
|
|
||||||
|
val key = info.ep.id.toString()
|
||||||
|
context.setKey(WORK_KEY_INFO, key, info)
|
||||||
|
startWork(context, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DownloadInfo(
|
||||||
|
@JsonProperty("source") val source: String?,
|
||||||
|
@JsonProperty("folder") val folder: String?,
|
||||||
|
@JsonProperty("ep") val ep: DownloadEpisodeMetadata,
|
||||||
|
@JsonProperty("links") val links: List<ExtractorLink>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue