mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
refactored download system for better preference + bugfixes
This commit is contained in:
parent
e95dc1db2a
commit
56cb3d7181
11 changed files with 604 additions and 852 deletions
|
@ -50,7 +50,7 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
|
|||
|
||||
companion object {
|
||||
private const val USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
||||
private var instance: DownloaderTestImpl? = null
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.util.*
|
|||
import kotlin.math.absoluteValue
|
||||
|
||||
const val USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
||||
|
||||
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
|
||||
val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
|
|
|
@ -23,7 +23,7 @@ data class VisualDownloadChildCached(
|
|||
val data: VideoDownloadHelper.DownloadEpisodeCached,
|
||||
)
|
||||
|
||||
data class DownloadClickEvent(val action: Int, val data: EasyDownloadButton.IMinimumData)
|
||||
data class DownloadClickEvent(val action: Int, val data: VideoDownloadHelper.DownloadEpisodeCached)
|
||||
|
||||
class DownloadChildAdapter(
|
||||
var cardList: List<VisualDownloadChildCached>,
|
||||
|
|
|
@ -1,264 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.download
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.text.format.Formatter.formatShortFileSize
|
||||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.Coroutines
|
||||
import com.lagradost.cloudstream3.utils.IDisposable
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
|
||||
class EasyDownloadButton : IDisposable {
|
||||
interface IMinimumData {
|
||||
val id: Int
|
||||
}
|
||||
|
||||
private var _clickCallback: ((DownloadClickEvent) -> Unit)? = null
|
||||
private var _imageChangeCallback: ((Pair<Int, String>) -> Unit)? = null
|
||||
|
||||
override fun dispose() {
|
||||
try {
|
||||
_clickCallback = null
|
||||
_imageChangeCallback = null
|
||||
downloadProgressEventListener?.let { VideoDownloadManager.downloadProgressEvent -= it }
|
||||
downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent -= it }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private var downloadProgressEventListener: ((Triple<Int, Long, Long>) -> Unit)? = null
|
||||
private var downloadStatusEventListener: ((Pair<Int, VideoDownloadManager.DownloadType>) -> Unit)? =
|
||||
null
|
||||
|
||||
fun setUpMaterialButton(
|
||||
setupCurrentBytes: Long?,
|
||||
setupTotalBytes: Long?,
|
||||
progressBar: ContentLoadingProgressBar,
|
||||
downloadButton: MaterialButton,
|
||||
textView: TextView?,
|
||||
data: IMinimumData,
|
||||
clickCallback: (DownloadClickEvent) -> Unit,
|
||||
) {
|
||||
setUpDownloadButton(
|
||||
setupCurrentBytes,
|
||||
setupTotalBytes,
|
||||
progressBar,
|
||||
textView,
|
||||
data,
|
||||
downloadButton,
|
||||
{
|
||||
downloadButton.setIconResource(it.first)
|
||||
downloadButton.text = it.second
|
||||
},
|
||||
clickCallback
|
||||
)
|
||||
}
|
||||
|
||||
fun setUpMoreButton(
|
||||
setupCurrentBytes: Long?,
|
||||
setupTotalBytes: Long?,
|
||||
progressBar: ContentLoadingProgressBar,
|
||||
downloadImage: ImageView,
|
||||
textView: TextView?,
|
||||
textViewProgress: TextView?,
|
||||
clickableView: View,
|
||||
isTextPercentage: Boolean,
|
||||
data: IMinimumData,
|
||||
clickCallback: (DownloadClickEvent) -> Unit,
|
||||
) {
|
||||
setUpDownloadButton(
|
||||
setupCurrentBytes,
|
||||
setupTotalBytes,
|
||||
progressBar,
|
||||
textViewProgress,
|
||||
data,
|
||||
clickableView,
|
||||
{ (image, text) ->
|
||||
downloadImage.isVisible = textViewProgress?.isGone ?: true
|
||||
downloadImage.setImageResource(image)
|
||||
textView?.text = text
|
||||
},
|
||||
clickCallback, isTextPercentage
|
||||
)
|
||||
}
|
||||
|
||||
fun setUpButton(
|
||||
setupCurrentBytes: Long?,
|
||||
setupTotalBytes: Long?,
|
||||
progressBar: ContentLoadingProgressBar,
|
||||
downloadImage: ImageView,
|
||||
textView: TextView?,
|
||||
data: IMinimumData,
|
||||
clickCallback: (DownloadClickEvent) -> Unit,
|
||||
) {
|
||||
setUpDownloadButton(
|
||||
setupCurrentBytes,
|
||||
setupTotalBytes,
|
||||
progressBar,
|
||||
textView,
|
||||
data,
|
||||
downloadImage,
|
||||
{
|
||||
downloadImage.setImageResource(it.first)
|
||||
},
|
||||
clickCallback
|
||||
)
|
||||
}
|
||||
|
||||
private fun setUpDownloadButton(
|
||||
setupCurrentBytes: Long?,
|
||||
setupTotalBytes: Long?,
|
||||
progressBar: ContentLoadingProgressBar,
|
||||
textView: TextView?,
|
||||
data: IMinimumData,
|
||||
downloadView: View,
|
||||
downloadImageChangeCallback: (Pair<Int, String>) -> Unit,
|
||||
clickCallback: (DownloadClickEvent) -> Unit,
|
||||
isTextPercentage: Boolean = false
|
||||
) {
|
||||
_clickCallback = clickCallback
|
||||
_imageChangeCallback = downloadImageChangeCallback
|
||||
var lastState: VideoDownloadManager.DownloadType? = null
|
||||
var currentBytes = setupCurrentBytes ?: 0
|
||||
var totalBytes = setupTotalBytes ?: 0
|
||||
var needImageUpdate = true
|
||||
|
||||
fun changeDownloadImage(state: VideoDownloadManager.DownloadType) {
|
||||
lastState = state
|
||||
if (currentBytes <= 0) needImageUpdate = true
|
||||
val img = if (currentBytes > 0) {
|
||||
when (state) {
|
||||
VideoDownloadManager.DownloadType.IsPaused -> Pair(
|
||||
R.drawable.ic_baseline_play_arrow_24,
|
||||
R.string.download_paused
|
||||
)
|
||||
VideoDownloadManager.DownloadType.IsDownloading -> Pair(
|
||||
R.drawable.netflix_pause,
|
||||
R.string.downloading
|
||||
)
|
||||
else -> Pair(R.drawable.ic_baseline_delete_outline_24, R.string.downloaded)
|
||||
}
|
||||
} else {
|
||||
Pair(R.drawable.netflix_download, R.string.download)
|
||||
}
|
||||
_imageChangeCallback?.invoke(
|
||||
Pair(
|
||||
img.first,
|
||||
downloadView.context.getString(img.second)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun fixDownloadedBytes(setCurrentBytes: Long, setTotalBytes: Long, animate: Boolean) {
|
||||
currentBytes = setCurrentBytes
|
||||
totalBytes = setTotalBytes
|
||||
|
||||
if (currentBytes == 0L) {
|
||||
changeDownloadImage(VideoDownloadManager.DownloadType.IsStopped)
|
||||
textView?.visibility = View.GONE
|
||||
progressBar.visibility = View.GONE
|
||||
} else {
|
||||
if (lastState == VideoDownloadManager.DownloadType.IsStopped) {
|
||||
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
||||
}
|
||||
textView?.visibility = View.VISIBLE
|
||||
progressBar.visibility = View.VISIBLE
|
||||
val currentMbString = formatShortFileSize(textView?.context, setCurrentBytes)
|
||||
val totalMbString = formatShortFileSize(textView?.context, setTotalBytes)
|
||||
|
||||
textView?.text =
|
||||
if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
|
||||
textView?.context?.getString(R.string.download_size_format)
|
||||
?.format(currentMbString, totalMbString)
|
||||
|
||||
progressBar.let { bar ->
|
||||
bar.max = (setTotalBytes / 1000).toInt()
|
||||
|
||||
if (animate) {
|
||||
val animation: ObjectAnimator = ObjectAnimator.ofInt(
|
||||
bar,
|
||||
"progress",
|
||||
bar.progress,
|
||||
(setCurrentBytes / 1000).toInt()
|
||||
)
|
||||
animation.duration = 500
|
||||
animation.setAutoCancel(true)
|
||||
animation.interpolator = DecelerateInterpolator()
|
||||
animation.start()
|
||||
} else {
|
||||
bar.progress = (setCurrentBytes / 1000).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fixDownloadedBytes(currentBytes, totalBytes, false)
|
||||
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
||||
|
||||
downloadProgressEventListener = { downloadData: Triple<Int, Long, Long> ->
|
||||
if (data.id == downloadData.first) {
|
||||
if (downloadData.second != currentBytes || downloadData.third != totalBytes) { // TO PREVENT WASTING UI TIME
|
||||
Coroutines.runOnMainThread {
|
||||
fixDownloadedBytes(downloadData.second, downloadData.third, true)
|
||||
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadStatusEventListener =
|
||||
{ downloadData: Pair<Int, VideoDownloadManager.DownloadType> ->
|
||||
if (data.id == downloadData.first) {
|
||||
if (lastState != downloadData.second || needImageUpdate) { // TO PREVENT WASTING UI TIME
|
||||
Coroutines.runOnMainThread {
|
||||
changeDownloadImage(downloadData.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadProgressEventListener?.let { VideoDownloadManager.downloadProgressEvent += it }
|
||||
downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent += it }
|
||||
|
||||
downloadView.setOnClickListener {
|
||||
if (currentBytes <= 0 || totalBytes <= 0) {
|
||||
_clickCallback?.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data))
|
||||
} else {
|
||||
val list = arrayListOf(
|
||||
Pair(DOWNLOAD_ACTION_PLAY_FILE, R.string.popup_play_file),
|
||||
Pair(DOWNLOAD_ACTION_DELETE_FILE, R.string.popup_delete_file),
|
||||
)
|
||||
|
||||
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
||||
if ((currentBytes * 100 / totalBytes) < 98) {
|
||||
list.add(
|
||||
if (lastState == VideoDownloadManager.DownloadType.IsDownloading)
|
||||
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
||||
else
|
||||
Pair(DOWNLOAD_ACTION_RESUME_DOWNLOAD, R.string.popup_resume_download)
|
||||
)
|
||||
}
|
||||
|
||||
it.popupMenuNoIcons(
|
||||
list
|
||||
) {
|
||||
_clickCallback?.invoke(DownloadClickEvent(itemId, data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadView.setOnLongClickListener {
|
||||
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_LONG_CLICK, data))
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ data class DownloadMetadata(
|
|||
val progressPercentage: Long
|
||||
get() = if (downloadedLength < 1024) 0 else maxOf(
|
||||
0,
|
||||
minOf(100, (downloadedLength * 100L) / totalLength)
|
||||
minOf(100, (downloadedLength * 100L) / (totalLength + 1))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -101,9 +101,11 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
|
||||
open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
|
||||
isZeroBytes = downloadedBytes == 0L
|
||||
progressBar.post {
|
||||
val steps = 10000L
|
||||
progressBar.max = steps.toInt()
|
||||
// div by zero error and 1 byte off is ok impo
|
||||
|
||||
val progress = (downloadedBytes * steps / (totalBytes + 1L)).toInt()
|
||||
|
||||
val animation = ProgressBarAnimation(
|
||||
|
@ -134,6 +136,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
|
||||
progressBar.startAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadStatusEvent(data: Pair<Int, VideoDownloadManager.DownloadType>) {
|
||||
val (id, status) = data
|
||||
|
|
|
@ -21,7 +21,7 @@ class DownloadButton(context: Context, attributeSet: AttributeSet) :
|
|||
}
|
||||
|
||||
override fun setStatus(status: DownloadStatusTell?) {
|
||||
super.setStatus(status)
|
||||
mainText?.post {
|
||||
val txt = when (status) {
|
||||
DownloadStatusTell.IsPaused -> R.string.download_paused
|
||||
DownloadStatusTell.IsDownloading -> R.string.downloading
|
||||
|
@ -30,6 +30,9 @@ class DownloadButton(context: Context, attributeSet: AttributeSet) :
|
|||
}
|
||||
mainText?.setText(txt)
|
||||
}
|
||||
super.setStatus(status)
|
||||
|
||||
}
|
||||
|
||||
override fun setDefaultClickListener(
|
||||
card: VideoDownloadHelper.DownloadEpisodeCached,
|
||||
|
|
|
@ -174,7 +174,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
|
||||
currentMetaData.apply {
|
||||
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
||||
if ((downloadedLength * 100 / totalLength) < 98) {
|
||||
if (progressPercentage < 98) {
|
||||
list.add(
|
||||
if (status == VideoDownloadManager.DownloadType.IsDownloading)
|
||||
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
||||
|
@ -248,8 +248,8 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
//progressBar.isVisible =
|
||||
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error
|
||||
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete
|
||||
progressBarBackground.post {
|
||||
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
||||
|
||||
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
||||
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
||||
progressBarBackground.startAnimation(animation)
|
||||
|
@ -276,6 +276,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
progressBarBackground.isGone = hide
|
||||
progressBar.isGone = hide
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetView() {
|
||||
setStatus(null)
|
||||
|
|
|
@ -186,6 +186,27 @@ object M3u8Helper2 {
|
|||
) {
|
||||
val size get() = allTsLinks.size
|
||||
|
||||
suspend fun resolveLinkWhileSafe(
|
||||
index: Int,
|
||||
tries: Int = 3,
|
||||
failDelay: Long = 3000,
|
||||
condition : (() -> Boolean)
|
||||
): ByteArray? {
|
||||
for (i in 0 until tries) {
|
||||
if(!condition()) return null
|
||||
|
||||
try {
|
||||
val out = resolveLink(index)
|
||||
return if(condition()) out else null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return null
|
||||
} catch (t: Throwable) {
|
||||
delay(failDelay)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun resolveLinkSafe(
|
||||
index: Int,
|
||||
tries: Int = 3,
|
||||
|
@ -240,8 +261,6 @@ object M3u8Helper2 {
|
|||
verify = false
|
||||
).text
|
||||
|
||||
println("m3u8Response=$m3u8Response")
|
||||
|
||||
// encryption, this is because crunchy uses it
|
||||
var encryptionIv = byteArrayOf()
|
||||
var encryptionData = byteArrayOf()
|
||||
|
|
|
@ -2,20 +2,18 @@ package com.lagradost.cloudstream3.utils
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||
|
||||
object VideoDownloadHelper {
|
||||
data class DownloadEpisodeCached(
|
||||
@JsonProperty("name") val name: String?,
|
||||
@JsonProperty("poster") val poster: String?,
|
||||
@JsonProperty("episode") val episode: Int,
|
||||
@JsonProperty("season") val season: Int?,
|
||||
@JsonProperty("id") override val id: Int,
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("parentId") val parentId: Int,
|
||||
@JsonProperty("rating") val rating: Int?,
|
||||
@JsonProperty("description") val description: String?,
|
||||
@JsonProperty("cacheTime") val cacheTime: Long,
|
||||
) : EasyDownloadButton.IMinimumData
|
||||
)
|
||||
|
||||
data class DownloadHeaderCached(
|
||||
@JsonProperty("apiName") val apiName: String,
|
||||
|
|
File diff suppressed because it is too large
Load diff
10
app/src/main/res/drawable/baseline_stop_24.xml
Normal file
10
app/src/main/res/drawable/baseline_stop_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,6h12v12H6z" />
|
||||
</vector>
|
Loading…
Reference in a new issue