mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Major performance and bug fixes to downloads (#1164)
This commit is contained in:
parent
29ec554334
commit
03b8b6e637
10 changed files with 195 additions and 170 deletions
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3
|
||||||
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
import org.schabi.newpipe.extractor.downloader.Request
|
import org.schabi.newpipe.extractor.downloader.Request
|
||||||
import org.schabi.newpipe.extractor.downloader.Response
|
import org.schabi.newpipe.extractor.downloader.Response
|
||||||
|
@ -18,7 +19,7 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
|
||||||
val dataToSend: ByteArray? = request.dataToSend()
|
val dataToSend: ByteArray? = request.dataToSend()
|
||||||
var requestBody: RequestBody? = null
|
var requestBody: RequestBody? = null
|
||||||
if (dataToSend != null) {
|
if (dataToSend != null) {
|
||||||
requestBody = RequestBody.create(null, dataToSend)
|
requestBody = dataToSend.toRequestBody(null, 0, dataToSend.size)
|
||||||
}
|
}
|
||||||
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
|
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
|
||||||
.method(httpMethod, requestBody).url(url)
|
.method(httpMethod, requestBody).url(url)
|
||||||
|
|
|
@ -13,9 +13,10 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
|
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
|
||||||
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
|
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
|
||||||
|
@ -26,6 +27,9 @@ const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
||||||
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
const val DOWNLOAD_ACTION_DOWNLOAD = 4
|
||||||
const val DOWNLOAD_ACTION_LONG_CLICK = 5
|
const val DOWNLOAD_ACTION_LONG_CLICK = 5
|
||||||
|
|
||||||
|
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
|
||||||
|
const val DOWNLOAD_ACTION_LOAD_RESULT = 1
|
||||||
|
|
||||||
abstract class VisualDownloadCached(
|
abstract class VisualDownloadCached(
|
||||||
open val currentBytes: Long,
|
open val currentBytes: Long,
|
||||||
open val totalBytes: Long,
|
open val totalBytes: Long,
|
||||||
|
@ -93,110 +97,128 @@ class DownloadAdapter(
|
||||||
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
fun bind(card: VisualDownloadCached?) {
|
fun bind(card: VisualDownloadCached?) {
|
||||||
when (binding) {
|
when (binding) {
|
||||||
is DownloadHeaderEpisodeBinding -> binding.apply {
|
is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadHeaderCached)
|
||||||
if (card == null || card !is VisualDownloadHeaderCached) return@apply
|
is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadChildCached)
|
||||||
val d = card.data
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downloadHeaderPoster.apply {
|
@SuppressLint("SetTextI18n")
|
||||||
setImage(d.poster)
|
private fun bindHeader(card: VisualDownloadHeaderCached?) {
|
||||||
setOnClickListener {
|
if (binding !is DownloadHeaderEpisodeBinding) return
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
|
card ?: return
|
||||||
}
|
val d = card.data
|
||||||
}
|
|
||||||
|
|
||||||
downloadHeaderTitle.text = d.name
|
binding.apply {
|
||||||
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
|
downloadHeaderPoster.apply {
|
||||||
|
setImage(d.poster)
|
||||||
if (card.child != null) {
|
setOnClickListener {
|
||||||
downloadHeaderGotoChild.isVisible = false
|
clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_LOAD_RESULT, d))
|
||||||
|
|
||||||
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
|
|
||||||
downloadButton.isVisible = true
|
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
|
||||||
mediaClickCallback.invoke(
|
|
||||||
DownloadClickEvent(
|
|
||||||
DOWNLOAD_ACTION_PLAY_FILE,
|
|
||||||
card.child
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
downloadButton.isVisible = false
|
|
||||||
downloadHeaderGotoChild.isVisible = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
downloadHeaderInfo.text =
|
|
||||||
downloadHeaderInfo.context.getString(R.string.extra_info_format)
|
|
||||||
.format(
|
|
||||||
card.totalDownloads,
|
|
||||||
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(
|
|
||||||
R.string.episode
|
|
||||||
) else downloadHeaderInfo.context.getString(
|
|
||||||
R.string.episodes
|
|
||||||
),
|
|
||||||
mbString
|
|
||||||
)
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
// You probably formatted incorrectly
|
|
||||||
downloadHeaderInfo.text = "Error"
|
|
||||||
logError(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeHolder.setOnClickListener {
|
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadChildEpisodeBinding -> binding.apply {
|
downloadHeaderTitle.text = d.name
|
||||||
if (card == null || card !is VisualDownloadChildCached) return@apply
|
val formattedSizeString = formatShortFileSize(itemView.context, card.totalBytes)
|
||||||
val d = card.data
|
|
||||||
|
|
||||||
val posDur = DataStoreHelper.getViewPos(d.id)
|
if (card.child != null) {
|
||||||
downloadChildEpisodeProgress.apply {
|
downloadHeaderGotoChild.isVisible = false
|
||||||
if (posDur != null) {
|
|
||||||
val visualPos = posDur.fixVisual()
|
val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes)
|
||||||
max = (visualPos.duration / 1000).toInt()
|
if (status == DownloadStatusTell.IsDone) {
|
||||||
progress = (visualPos.position / 1000).toInt()
|
// We do this here instead if we are finished downloading
|
||||||
isVisible = true
|
// so that we can use the value from the view model
|
||||||
} else isVisible = false
|
// rather than extra unneeded disk operations and to prevent a
|
||||||
|
// delay in updating download icon state.
|
||||||
|
downloadButton.setProgress(card.currentBytes, card.totalBytes)
|
||||||
|
downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes)
|
||||||
|
// We will let the view model handle this
|
||||||
|
downloadButton.doSetProgress = false
|
||||||
|
downloadHeaderInfo.text = formattedSizeString
|
||||||
|
} else downloadButton.doSetProgress = true
|
||||||
|
|
||||||
|
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
|
||||||
|
downloadButton.isVisible = true
|
||||||
|
|
||||||
|
episodeHolder.setOnClickListener {
|
||||||
|
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadButton.isVisible = false
|
||||||
|
downloadHeaderGotoChild.isVisible = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format)
|
||||||
|
.format(
|
||||||
|
card.totalDownloads,
|
||||||
|
downloadHeaderInfo.context.resources.getQuantityString(
|
||||||
|
R.plurals.episodes,
|
||||||
|
card.totalDownloads
|
||||||
|
),
|
||||||
|
formattedSizeString
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// You probably formatted incorrectly
|
||||||
|
downloadHeaderInfo.text = "Error"
|
||||||
|
logError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadButton.setDefaultClickListener(card.data, downloadChildEpisodeTextExtra, mediaClickCallback)
|
episodeHolder.setOnClickListener {
|
||||||
|
clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_GO_TO_CHILD, d))
|
||||||
downloadChildEpisodeText.apply {
|
|
||||||
text = context.getNameFull(d.name, d.episode, d.season)
|
|
||||||
isSelected = true // Needed for text repeating
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downloadChildEpisodeHolder.setOnClickListener {
|
private fun bindChild(card: VisualDownloadChildCached?) {
|
||||||
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
if (binding !is DownloadChildEpisodeBinding) return
|
||||||
|
card ?: return
|
||||||
|
val d = card.data
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
val posDur = getViewPos(d.id)
|
||||||
|
downloadChildEpisodeProgress.apply {
|
||||||
|
isVisible = posDur != null
|
||||||
|
posDur?.let {
|
||||||
|
val visualPos = it.fixVisual()
|
||||||
|
max = (visualPos.duration / 1000).toInt()
|
||||||
|
progress = (visualPos.position / 1000).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val status = downloadButton.getStatus(d.id, card.currentBytes, card.totalBytes)
|
||||||
|
if (status == DownloadStatusTell.IsDone) {
|
||||||
|
// We do this here instead if we are finished downloading
|
||||||
|
// so that we can use the value from the view model
|
||||||
|
// rather than extra unneeded disk operations and to prevent a
|
||||||
|
// delay in updating download icon state.
|
||||||
|
downloadButton.setProgress(card.currentBytes, card.totalBytes)
|
||||||
|
downloadButton.applyMetaData(d.id, card.currentBytes, card.totalBytes)
|
||||||
|
// We will let the view model handle this
|
||||||
|
downloadButton.doSetProgress = false
|
||||||
|
downloadChildEpisodeTextExtra.text = formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes)
|
||||||
|
} else downloadButton.doSetProgress = true
|
||||||
|
|
||||||
|
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, mediaClickCallback)
|
||||||
|
downloadButton.isVisible = true
|
||||||
|
|
||||||
|
downloadChildEpisodeText.apply {
|
||||||
|
text = context.getNameFull(d.name, d.episode, d.season)
|
||||||
|
isSelected = true // Needed for text repeating
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadChildEpisodeHolder.setOnClickListener {
|
||||||
|
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
val binding = when (viewType) {
|
val binding = when (viewType) {
|
||||||
VIEW_TYPE_HEADER -> {
|
VIEW_TYPE_HEADER -> DownloadHeaderEpisodeBinding.inflate(inflater, parent, false)
|
||||||
DownloadHeaderEpisodeBinding.inflate(
|
VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
|
||||||
LayoutInflater.from(parent.context),
|
|
||||||
parent,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
VIEW_TYPE_CHILD -> {
|
|
||||||
DownloadChildEpisodeBinding.inflate(
|
|
||||||
LayoutInflater.from(parent.context),
|
|
||||||
parent,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("Invalid view type")
|
else -> throw IllegalArgumentException("Invalid view type")
|
||||||
}
|
}
|
||||||
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
|
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
|
||||||
|
@ -207,8 +229,11 @@ class DownloadAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
val card = getItem(position)
|
return when (getItem(position)) {
|
||||||
return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER
|
is VisualDownloadChildCached -> VIEW_TYPE_CHILD
|
||||||
|
is VisualDownloadHeaderCached -> VIEW_TYPE_HEADER
|
||||||
|
else -> throw IllegalArgumentException("Invalid data type at position $position")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
|
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
|
||||||
|
|
|
@ -35,6 +35,7 @@ class DownloadChildFragment : Fragment() {
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||||
|
downloadDeleteEventListener = null
|
||||||
binding = null
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
|
||||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
||||||
import com.lagradost.cloudstream3.utils.DataStore
|
import com.lagradost.cloudstream3.utils.DataStore
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
|
@ -65,16 +64,8 @@ class DownloadFragment : Fragment() {
|
||||||
this.layoutParams = param
|
this.layoutParams = param
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
|
||||||
main {
|
|
||||||
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
downloadDeleteEventListener?.let {
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||||
VideoDownloadManager.downloadDeleteEvent -= it
|
|
||||||
}
|
|
||||||
downloadDeleteEventListener = null
|
downloadDeleteEventListener = null
|
||||||
binding = null
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -100,12 +91,10 @@ class DownloadFragment : Fragment() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
||||||
|
|
||||||
observe(downloadsViewModel.noDownloadsText) {
|
|
||||||
binding?.textNoDownloads?.text = it
|
|
||||||
}
|
|
||||||
observe(downloadsViewModel.headerCards) {
|
observe(downloadsViewModel.headerCards) {
|
||||||
setList(it)
|
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(it)
|
||||||
binding?.downloadLoading?.isVisible = false
|
binding?.downloadLoading?.isVisible = false
|
||||||
|
binding?.textNoDownloads?.isVisible = it.isEmpty()
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.availableBytes) {
|
observe(downloadsViewModel.availableBytes) {
|
||||||
updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree)
|
updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree)
|
||||||
|
@ -164,7 +153,7 @@ class DownloadFragment : Fragment() {
|
||||||
|
|
||||||
private fun handleItemClick(click: DownloadHeaderClickEvent) {
|
private fun handleItemClick(click: DownloadHeaderClickEvent) {
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
0 -> {
|
DOWNLOAD_ACTION_GO_TO_CHILD -> {
|
||||||
if (!click.data.type.isMovieType()) {
|
if (!click.data.type.isMovieType()) {
|
||||||
val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString())
|
val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString())
|
||||||
activity?.navigate(
|
activity?.navigate(
|
||||||
|
@ -173,7 +162,7 @@ class DownloadFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1 -> {
|
DOWNLOAD_ACTION_LOAD_RESULT -> {
|
||||||
(activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName)
|
(activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,11 @@ import com.lagradost.cloudstream3.utils.DataStore.getFolderName
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadFileInfoAndUpdateSettings
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class DownloadViewModel : ViewModel() {
|
class DownloadViewModel : ViewModel() {
|
||||||
private val _noDownloadsText = MutableLiveData<String>().apply {
|
|
||||||
value = ""
|
|
||||||
}
|
|
||||||
val noDownloadsText: LiveData<String> = _noDownloadsText
|
|
||||||
|
|
||||||
private val _headerCards =
|
private val _headerCards =
|
||||||
MutableLiveData<List<VisualDownloadHeaderCached>>().apply { listOf<VisualDownloadHeaderCached>() }
|
MutableLiveData<List<VisualDownloadHeaderCached>>().apply { listOf<VisualDownloadHeaderCached>() }
|
||||||
val headerCards: LiveData<List<VisualDownloadHeaderCached>> = _headerCards
|
val headerCards: LiveData<List<VisualDownloadHeaderCached>> = _headerCards
|
||||||
|
@ -43,8 +37,8 @@ class DownloadViewModel : ViewModel() {
|
||||||
|
|
||||||
fun updateList(context: Context) = viewModelScope.launchSafe {
|
fun updateList(context: Context) = viewModelScope.launchSafe {
|
||||||
val children = withContext(Dispatchers.IO) {
|
val children = withContext(Dispatchers.IO) {
|
||||||
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
||||||
headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
|
.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
|
||||||
.distinctBy { it.id } // Remove duplicates
|
.distinctBy { it.id } // Remove duplicates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,10 +51,10 @@ class DownloadViewModel : ViewModel() {
|
||||||
|
|
||||||
// Gets all children downloads
|
// Gets all children downloads
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
for (c in children) {
|
children.forEach { c ->
|
||||||
val childFile = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, c.id) ?: continue
|
val childFile = getDownloadFileInfoAndUpdateSettings(context, c.id) ?: return@forEach
|
||||||
|
|
||||||
if (childFile.fileLength <= 1) continue
|
if (childFile.fileLength <= 1) return@forEach
|
||||||
val len = childFile.totalBytes
|
val len = childFile.totalBytes
|
||||||
val flen = childFile.fileLength
|
val flen = childFile.fileLength
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.ui.download.button
|
package com.lagradost.cloudstream3.ui.download.button
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -9,6 +9,8 @@ import androidx.annotation.LayoutRes
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
import androidx.core.widget.ContentLoadingProgressBar
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
|
||||||
typealias DownloadStatusTell = VideoDownloadManager.DownloadType
|
typealias DownloadStatusTell = VideoDownloadManager.DownloadType
|
||||||
|
@ -34,7 +36,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
lateinit var progressBar: ContentLoadingProgressBar
|
lateinit var progressBar: ContentLoadingProgressBar
|
||||||
var progressText: TextView? = null
|
var progressText: TextView? = null
|
||||||
|
|
||||||
/*val gid: String? get() = sessionIdToGid[persistentId]
|
/* val gid: String? get() = sessionIdToGid[persistentId]
|
||||||
|
|
||||||
// used for resuming data
|
// used for resuming data
|
||||||
var _lastRequestOverride: UriRequest? = null
|
var _lastRequestOverride: UriRequest? = null
|
||||||
|
@ -44,7 +46,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
_lastRequestOverride = value
|
_lastRequestOverride = value
|
||||||
}
|
}
|
||||||
|
|
||||||
var files: List<AbstractClient.JsonFile> = emptyList()*/
|
var files: List<AbstractClient.JsonFile> = emptyList() */
|
||||||
protected var isZeroBytes: Boolean = true
|
protected var isZeroBytes: Boolean = true
|
||||||
|
|
||||||
fun inflate(@LayoutRes layout: Int) {
|
fun inflate(@LayoutRes layout: Int) {
|
||||||
|
@ -55,9 +57,12 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
resetViewData()
|
resetViewData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var doSetProgress = true
|
||||||
|
|
||||||
open fun resetViewData() {
|
open fun resetViewData() {
|
||||||
// lastRequest = null
|
// lastRequest = null
|
||||||
isZeroBytes = true
|
isZeroBytes = true
|
||||||
|
doSetProgress = true
|
||||||
persistentId = null
|
persistentId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,37 +73,45 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
persistentId = id
|
persistentId = id
|
||||||
currentMetaData.id = id
|
currentMetaData.id = id
|
||||||
|
|
||||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)?.let { savedData ->
|
if (!doSetProgress) return
|
||||||
val downloadedBytes = savedData.fileLength
|
|
||||||
val totalBytes = savedData.totalBytes
|
|
||||||
|
|
||||||
/*lastRequest = savedData.uriRequest
|
ioSafe {
|
||||||
files = savedData.files
|
val savedData = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)
|
||||||
|
|
||||||
var totalBytes: Long = 0
|
mainWork {
|
||||||
var downloadedBytes: Long = 0
|
if (savedData != null) {
|
||||||
for (file in savedData.files) {
|
val downloadedBytes = savedData.fileLength
|
||||||
downloadedBytes += file.completedLength
|
val totalBytes = savedData.totalBytes
|
||||||
totalBytes += file.length
|
|
||||||
}*/
|
setProgress(downloadedBytes, totalBytes)
|
||||||
setProgress(downloadedBytes, totalBytes)
|
applyMetaData(id, downloadedBytes, totalBytes)
|
||||||
// some extra padding for just in case
|
} else run { resetView() }
|
||||||
val status = VideoDownloadManager.downloadStatus[id]
|
|
||||||
?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) DownloadStatusTell.IsDone else DownloadStatusTell.IsPaused
|
|
||||||
currentMetaData.apply {
|
|
||||||
this.id = id
|
|
||||||
this.downloadedLength = downloadedBytes
|
|
||||||
this.totalLength = totalBytes
|
|
||||||
this.status = status
|
|
||||||
}
|
}
|
||||||
setStatus(status)
|
|
||||||
} ?: run {
|
|
||||||
resetView()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun setStatus(status: VideoDownloadManager.DownloadType?)
|
abstract fun setStatus(status: VideoDownloadManager.DownloadType?)
|
||||||
|
|
||||||
|
fun getStatus(id:Int, downloadedBytes: Long, totalBytes: Long): DownloadStatusTell {
|
||||||
|
// some extra padding for just in case
|
||||||
|
return VideoDownloadManager.downloadStatus[id]
|
||||||
|
?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) {
|
||||||
|
DownloadStatusTell.IsDone
|
||||||
|
} else DownloadStatusTell.IsPaused
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyMetaData(id:Int, downloadedBytes: Long, totalBytes: Long) {
|
||||||
|
val status = getStatus(id, downloadedBytes, totalBytes)
|
||||||
|
|
||||||
|
currentMetaData.apply {
|
||||||
|
this.id = id
|
||||||
|
this.downloadedLength = downloadedBytes
|
||||||
|
this.totalLength = totalBytes
|
||||||
|
this.status = status
|
||||||
|
}
|
||||||
|
setStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
|
open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
|
||||||
isZeroBytes = downloadedBytes == 0L
|
isZeroBytes = downloadedBytes == 0L
|
||||||
progressBar.post {
|
progressBar.post {
|
||||||
|
@ -124,13 +137,15 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
if (isZeroBytes) {
|
if (isZeroBytes) {
|
||||||
progressText?.isVisible = false
|
progressText?.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
progressText?.apply {
|
if (doSetProgress) {
|
||||||
val currentMbString = Formatter.formatShortFileSize(context, downloadedBytes)
|
progressText?.apply {
|
||||||
val totalMbString = Formatter.formatShortFileSize(context, totalBytes)
|
val currentFormattedSizeString = formatShortFileSize(context, downloadedBytes)
|
||||||
text =
|
val totalFormattedSizeString = formatShortFileSize(context, totalBytes)
|
||||||
//if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
|
text =
|
||||||
context?.getString(R.string.download_size_format)
|
// if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
|
||||||
?.format(currentMbString, totalMbString)
|
context?.getString(R.string.download_size_format)
|
||||||
|
?.format(currentFormattedSizeString, totalFormattedSizeString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,8 +182,8 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent
|
VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent
|
||||||
//VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent
|
// VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent
|
||||||
//VideoDownloadManager.downloadEvent += ::downloadEvent
|
// VideoDownloadManager.downloadEvent += ::downloadEvent
|
||||||
VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent
|
VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent
|
||||||
|
|
||||||
val pid = persistentId
|
val pid = persistentId
|
||||||
|
@ -182,8 +197,8 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent
|
VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent
|
||||||
//VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent
|
// VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent
|
||||||
//VideoDownloadManager.downloadEvent -= ::downloadEvent
|
// VideoDownloadManager.downloadEvent -= ::downloadEvent
|
||||||
VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent
|
VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent
|
||||||
|
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
|
@ -198,5 +213,4 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
* Get a clean slate again, might be useful in recyclerview?
|
* Get a clean slate again, might be useful in recyclerview?
|
||||||
* */
|
* */
|
||||||
abstract fun resetView()
|
abstract fun resetView()
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,7 +13,6 @@ import androidx.annotation.MainThread
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -29,7 +28,6 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.KEY_RESUME_PACKAGES
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.KEY_RESUME_PACKAGES
|
||||||
|
|
||||||
|
|
||||||
open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
BaseFetchButton(context, attributeSet) {
|
BaseFetchButton(context, attributeSet) {
|
||||||
|
|
||||||
|
@ -303,6 +301,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
setStatus(null)
|
setStatus(null)
|
||||||
currentMetaData = DownloadMetadata(0, 0, 0, null)
|
currentMetaData = DownloadMetadata(0, 0, 0, null)
|
||||||
isZeroBytes = true
|
isZeroBytes = true
|
||||||
|
doSetProgress = true
|
||||||
progressBar.progress = 0
|
progressBar.progress = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
|
@ -28,7 +29,6 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|
||||||
import com.lagradost.cloudstream3.services.VideoDownloadService
|
import com.lagradost.cloudstream3.services.VideoDownloadService
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
@ -234,10 +234,10 @@ object VideoDownloadManager {
|
||||||
return cachedBitmaps[url]
|
return cachedBitmaps[url]
|
||||||
}
|
}
|
||||||
|
|
||||||
val bitmap = com.bumptech.glide.Glide.with(this)
|
val bitmap = Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(GlideUrl(url) { headers ?: emptyMap() })
|
.load(GlideUrl(url) { headers ?: emptyMap() })
|
||||||
.into(720, 720)
|
.submit(720, 720)
|
||||||
.get()
|
.get()
|
||||||
|
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
|
|
|
@ -143,17 +143,14 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_no_downloads"
|
android:id="@+id/text_no_downloads"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_gravity="center"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_margin="30dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:text="@string/downloads_empty"
|
||||||
android:textAlignment="center"
|
android:gravity="center"
|
||||||
android:textSize="20sp"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
tools:visibility="visible" />
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
|
|
@ -149,6 +149,7 @@
|
||||||
<string name="download_canceled">Download Canceled</string>
|
<string name="download_canceled">Download Canceled</string>
|
||||||
<string name="download_done">Download Done</string>
|
<string name="download_done">Download Done</string>
|
||||||
<string name="download_format" translatable="false">%s - %s</string>
|
<string name="download_format" translatable="false">%s - %s</string>
|
||||||
|
<string name="downloads_empty">There are currently no downloads.</string>
|
||||||
<string name="update_started">Update Started</string>
|
<string name="update_started">Update Started</string>
|
||||||
<string name="stream">Network stream</string>
|
<string name="stream">Network stream</string>
|
||||||
<string name="open_local_video">Open local video</string>
|
<string name="open_local_video">Open local video</string>
|
||||||
|
@ -340,6 +341,10 @@
|
||||||
<string name="livestreams">Livestreams</string>
|
<string name="livestreams">Livestreams</string>
|
||||||
<string name="nsfw">NSFW</string>
|
<string name="nsfw">NSFW</string>
|
||||||
<string name="others">Others</string>
|
<string name="others">Others</string>
|
||||||
|
<plurals name="episodes" translatable="false">
|
||||||
|
<item quantity="one">@string/episode</item>
|
||||||
|
<item quantity="other">@string/episodes</item>
|
||||||
|
</plurals>
|
||||||
<!--singular-->
|
<!--singular-->
|
||||||
<string name="movies_singular">Movie</string>
|
<string name="movies_singular">Movie</string>
|
||||||
<string name="tv_series_singular">Series</string>
|
<string name="tv_series_singular">Series</string>
|
||||||
|
|
Loading…
Reference in a new issue