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 {
|
companion object {
|
||||||
private const val USER_AGENT =
|
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
|
private var instance: DownloaderTestImpl? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,7 @@ import java.util.*
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
const val USER_AGENT =
|
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 baseHeader = mapOf("User-Agent" to USER_AGENT)
|
||||||
val mapper = JsonMapper.builder().addModule(KotlinModule())
|
val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
|
|
|
@ -23,7 +23,7 @@ data class VisualDownloadChildCached(
|
||||||
val data: VideoDownloadHelper.DownloadEpisodeCached,
|
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(
|
class DownloadChildAdapter(
|
||||||
var cardList: List<VisualDownloadChildCached>,
|
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
|
val progressPercentage: Long
|
||||||
get() = if (downloadedLength < 1024) 0 else maxOf(
|
get() = if (downloadedLength < 1024) 0 else maxOf(
|
||||||
0,
|
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) {
|
open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
|
||||||
isZeroBytes = downloadedBytes == 0L
|
isZeroBytes = downloadedBytes == 0L
|
||||||
|
progressBar.post {
|
||||||
val steps = 10000L
|
val steps = 10000L
|
||||||
progressBar.max = steps.toInt()
|
progressBar.max = steps.toInt()
|
||||||
// div by zero error and 1 byte off is ok impo
|
// div by zero error and 1 byte off is ok impo
|
||||||
|
|
||||||
val progress = (downloadedBytes * steps / (totalBytes + 1L)).toInt()
|
val progress = (downloadedBytes * steps / (totalBytes + 1L)).toInt()
|
||||||
|
|
||||||
val animation = ProgressBarAnimation(
|
val animation = ProgressBarAnimation(
|
||||||
|
@ -134,6 +136,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
|
||||||
progressBar.startAnimation(animation)
|
progressBar.startAnimation(animation)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun downloadStatusEvent(data: Pair<Int, VideoDownloadManager.DownloadType>) {
|
fun downloadStatusEvent(data: Pair<Int, VideoDownloadManager.DownloadType>) {
|
||||||
val (id, status) = data
|
val (id, status) = data
|
||||||
|
|
|
@ -21,7 +21,7 @@ class DownloadButton(context: Context, attributeSet: AttributeSet) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setStatus(status: DownloadStatusTell?) {
|
override fun setStatus(status: DownloadStatusTell?) {
|
||||||
super.setStatus(status)
|
mainText?.post {
|
||||||
val txt = when (status) {
|
val txt = when (status) {
|
||||||
DownloadStatusTell.IsPaused -> R.string.download_paused
|
DownloadStatusTell.IsPaused -> R.string.download_paused
|
||||||
DownloadStatusTell.IsDownloading -> R.string.downloading
|
DownloadStatusTell.IsDownloading -> R.string.downloading
|
||||||
|
@ -30,6 +30,9 @@ class DownloadButton(context: Context, attributeSet: AttributeSet) :
|
||||||
}
|
}
|
||||||
mainText?.setText(txt)
|
mainText?.setText(txt)
|
||||||
}
|
}
|
||||||
|
super.setStatus(status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun setDefaultClickListener(
|
override fun setDefaultClickListener(
|
||||||
card: VideoDownloadHelper.DownloadEpisodeCached,
|
card: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
|
|
@ -174,7 +174,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
|
||||||
currentMetaData.apply {
|
currentMetaData.apply {
|
||||||
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
||||||
if ((downloadedLength * 100 / totalLength) < 98) {
|
if (progressPercentage < 98) {
|
||||||
list.add(
|
list.add(
|
||||||
if (status == VideoDownloadManager.DownloadType.IsDownloading)
|
if (status == VideoDownloadManager.DownloadType.IsDownloading)
|
||||||
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
||||||
|
@ -248,8 +248,8 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
//progressBar.isVisible =
|
//progressBar.isVisible =
|
||||||
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error
|
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error
|
||||||
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete
|
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete
|
||||||
|
progressBarBackground.post {
|
||||||
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
||||||
|
|
||||||
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
||||||
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
||||||
progressBarBackground.startAnimation(animation)
|
progressBarBackground.startAnimation(animation)
|
||||||
|
@ -276,6 +276,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
progressBarBackground.isGone = hide
|
progressBarBackground.isGone = hide
|
||||||
progressBar.isGone = hide
|
progressBar.isGone = hide
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun resetView() {
|
override fun resetView() {
|
||||||
setStatus(null)
|
setStatus(null)
|
||||||
|
|
|
@ -186,6 +186,27 @@ object M3u8Helper2 {
|
||||||
) {
|
) {
|
||||||
val size get() = allTsLinks.size
|
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(
|
suspend fun resolveLinkSafe(
|
||||||
index: Int,
|
index: Int,
|
||||||
tries: Int = 3,
|
tries: Int = 3,
|
||||||
|
@ -240,8 +261,6 @@ object M3u8Helper2 {
|
||||||
verify = false
|
verify = false
|
||||||
).text
|
).text
|
||||||
|
|
||||||
println("m3u8Response=$m3u8Response")
|
|
||||||
|
|
||||||
// encryption, this is because crunchy uses it
|
// encryption, this is because crunchy uses it
|
||||||
var encryptionIv = byteArrayOf()
|
var encryptionIv = byteArrayOf()
|
||||||
var encryptionData = byteArrayOf()
|
var encryptionData = byteArrayOf()
|
||||||
|
|
|
@ -2,20 +2,18 @@ package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
|
||||||
|
|
||||||
object VideoDownloadHelper {
|
object VideoDownloadHelper {
|
||||||
data class DownloadEpisodeCached(
|
data class DownloadEpisodeCached(
|
||||||
@JsonProperty("name") val name: String?,
|
@JsonProperty("name") val name: String?,
|
||||||
@JsonProperty("poster") val poster: String?,
|
@JsonProperty("poster") val poster: String?,
|
||||||
@JsonProperty("episode") val episode: Int,
|
@JsonProperty("episode") val episode: Int,
|
||||||
@JsonProperty("season") val season: Int?,
|
@JsonProperty("season") val season: Int?,
|
||||||
@JsonProperty("id") override val id: Int,
|
@JsonProperty("id") val id: Int,
|
||||||
@JsonProperty("parentId") val parentId: Int,
|
@JsonProperty("parentId") val parentId: Int,
|
||||||
@JsonProperty("rating") val rating: Int?,
|
@JsonProperty("rating") val rating: Int?,
|
||||||
@JsonProperty("description") val description: String?,
|
@JsonProperty("description") val description: String?,
|
||||||
@JsonProperty("cacheTime") val cacheTime: Long,
|
@JsonProperty("cacheTime") val cacheTime: Long,
|
||||||
) : EasyDownloadButton.IMinimumData
|
)
|
||||||
|
|
||||||
data class DownloadHeaderCached(
|
data class DownloadHeaderCached(
|
||||||
@JsonProperty("apiName") val apiName: String,
|
@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