mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
fixed not downloading the last 20MiB on mp4 downloader + bump + mb/s notification
This commit is contained in:
parent
1901eb371e
commit
4e28e5f8cc
2 changed files with 60 additions and 21 deletions
|
@ -58,7 +58,7 @@ android {
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
|
|
||||||
versionCode = 59
|
versionCode = 59
|
||||||
versionName = "4.1.5"
|
versionName = "4.1.6"
|
||||||
|
|
||||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||||
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
|
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
|
||||||
|
|
|
@ -30,7 +30,6 @@ import com.lagradost.cloudstream3.BuildConfig
|
||||||
import com.lagradost.cloudstream3.MainActivity
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.amap
|
|
||||||
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.suspendSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
|
@ -256,7 +255,8 @@ object VideoDownloadManager {
|
||||||
total: Long,
|
total: Long,
|
||||||
notificationCallback: (Int, Notification) -> Unit,
|
notificationCallback: (Int, Notification) -> Unit,
|
||||||
hlsProgress: Long? = null,
|
hlsProgress: Long? = null,
|
||||||
hlsTotal: Long? = null
|
hlsTotal: Long? = null,
|
||||||
|
bytesPerSecond: Long
|
||||||
): Notification? {
|
): Notification? {
|
||||||
try {
|
try {
|
||||||
if (total <= 0) return null// crash, invalid data
|
if (total <= 0) return null// crash, invalid data
|
||||||
|
@ -327,22 +327,29 @@ object VideoDownloadManager {
|
||||||
val totalMbString: String
|
val totalMbString: String
|
||||||
val suffix: String
|
val suffix: String
|
||||||
|
|
||||||
|
val mbFormat = "%.1f MB"
|
||||||
|
|
||||||
if (hlsProgress != null && hlsTotal != null) {
|
if (hlsProgress != null && hlsTotal != null) {
|
||||||
progressPercentage = hlsProgress.toLong() * 100 / hlsTotal
|
progressPercentage = hlsProgress.toLong() * 100 / hlsTotal
|
||||||
progressMbString = hlsProgress.toString()
|
progressMbString = hlsProgress.toString()
|
||||||
totalMbString = hlsTotal.toString()
|
totalMbString = hlsTotal.toString()
|
||||||
suffix = " - %.1f MB".format(progress / 1000000f)
|
suffix = " - $mbFormat".format(progress / 1000000f)
|
||||||
} else {
|
} else {
|
||||||
progressPercentage = progress * 100 / total
|
progressPercentage = progress * 100 / total
|
||||||
progressMbString = "%.1f MB".format(progress / 1000000f)
|
progressMbString = mbFormat.format(progress / 1000000f)
|
||||||
totalMbString = "%.1f MB".format(total / 1000000f)
|
totalMbString = mbFormat.format(total / 1000000f)
|
||||||
suffix = ""
|
suffix = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mbPerSecondString =
|
||||||
|
if (state == DownloadType.IsDownloading) {
|
||||||
|
" ($mbFormat/s)".format(bytesPerSecond.toFloat() / 1000000f)
|
||||||
|
} else ""
|
||||||
|
|
||||||
val bigText =
|
val bigText =
|
||||||
when (state) {
|
when (state) {
|
||||||
DownloadType.IsDownloading, DownloadType.IsPaused -> {
|
DownloadType.IsDownloading, DownloadType.IsPaused -> {
|
||||||
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString/$totalMbString)$suffix"
|
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString/$totalMbString)$suffix$mbPerSecondString"
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadType.IsFailed -> {
|
DownloadType.IsFailed -> {
|
||||||
|
@ -608,6 +615,7 @@ object VideoDownloadManager {
|
||||||
val bytesTotal: Long,
|
val bytesTotal: Long,
|
||||||
val hlsProgress: Long? = null,
|
val hlsProgress: Long? = null,
|
||||||
val hlsTotal: Long? = null,
|
val hlsTotal: Long? = null,
|
||||||
|
val bytesPerSecond: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
data class StreamData(
|
data class StreamData(
|
||||||
|
@ -723,6 +731,7 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
// notification metadata
|
// notification metadata
|
||||||
private var lastUpdatedMs: Long = 0,
|
private var lastUpdatedMs: Long = 0,
|
||||||
|
private var lastDownloadedBytes: Long = 0,
|
||||||
private val createNotificationCallback: (CreateNotificationMetadata) -> Unit,
|
private val createNotificationCallback: (CreateNotificationMetadata) -> Unit,
|
||||||
|
|
||||||
private var internalType: DownloadType = DownloadType.IsPending,
|
private var internalType: DownloadType = DownloadType.IsPending,
|
||||||
|
@ -738,6 +747,12 @@ object VideoDownloadManager {
|
||||||
// this is used for copy with metadata on how much we have downloaded for setKey
|
// this is used for copy with metadata on how much we have downloaded for setKey
|
||||||
private var downloadFileInfoTemplate: DownloadedFileInfo? = null
|
private var downloadFileInfoTemplate: DownloadedFileInfo? = null
|
||||||
) : Closeable {
|
) : Closeable {
|
||||||
|
fun setResumeLength(length: Long) {
|
||||||
|
bytesDownloaded = length
|
||||||
|
bytesWritten = length
|
||||||
|
lastDownloadedBytes = length
|
||||||
|
}
|
||||||
|
|
||||||
val approxTotalBytes: Long
|
val approxTotalBytes: Long
|
||||||
get() = totalBytes ?: hlsTotal?.let { total ->
|
get() = totalBytes ?: hlsTotal?.let { total ->
|
||||||
(bytesDownloaded * (total / hlsProgress.toFloat())).toLong()
|
(bytesDownloaded * (total / hlsProgress.toFloat())).toLong()
|
||||||
|
@ -839,6 +854,13 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
@JvmName("DownloadMetaDataNotify")
|
@JvmName("DownloadMetaDataNotify")
|
||||||
private fun notify() {
|
private fun notify() {
|
||||||
|
// max 10 sec between notifications, min 0.1s, this is to stop div by zero
|
||||||
|
val dt = (System.currentTimeMillis() - lastUpdatedMs).coerceIn(100, 10000)
|
||||||
|
|
||||||
|
val bytesPerSecond =
|
||||||
|
((bytesDownloaded - lastDownloadedBytes) * 1000L) / dt
|
||||||
|
|
||||||
|
lastDownloadedBytes = bytesDownloaded
|
||||||
lastUpdatedMs = System.currentTimeMillis()
|
lastUpdatedMs = System.currentTimeMillis()
|
||||||
try {
|
try {
|
||||||
val bytes = approxTotalBytes
|
val bytes = approxTotalBytes
|
||||||
|
@ -851,7 +873,8 @@ object VideoDownloadManager {
|
||||||
bytesDownloaded,
|
bytesDownloaded,
|
||||||
bytes,
|
bytes,
|
||||||
hlsTotal = hlsTotal?.toLong(),
|
hlsTotal = hlsTotal?.toLong(),
|
||||||
hlsProgress = hlsProgress.toLong()
|
hlsProgress = hlsProgress.toLong(),
|
||||||
|
bytesPerSecond = bytesPerSecond
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -860,6 +883,7 @@ object VideoDownloadManager {
|
||||||
internalType,
|
internalType,
|
||||||
bytesDownloaded,
|
bytesDownloaded,
|
||||||
bytes,
|
bytes,
|
||||||
|
bytesPerSecond = bytesPerSecond
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1057,21 +1081,29 @@ object VideoDownloadManager {
|
||||||
// we don't want to make a separate connection for every 1kb
|
// we don't want to make a separate connection for every 1kb
|
||||||
require(chuckSize > 1000)
|
require(chuckSize > 1000)
|
||||||
|
|
||||||
val contentLength =
|
var contentLength =
|
||||||
app.head(url = url, headers = headers, referer = referer, verify = false).size
|
app.head(url = url, headers = headers, referer = referer, verify = false).size
|
||||||
|
if (contentLength != null && contentLength <= 0) contentLength = null
|
||||||
|
|
||||||
var downloadLength: Long? = null
|
var downloadLength: Long? = null
|
||||||
var totalLength: Long? = null
|
var totalLength: Long? = null
|
||||||
|
|
||||||
val ranges = if (contentLength == null) {
|
val ranges = if (contentLength == null) {
|
||||||
|
// is the equivalent of [startByte..EOF] as we don't know the size we can only do one
|
||||||
|
// connection
|
||||||
LongArray(1) { startByte }
|
LongArray(1) { startByte }
|
||||||
} else {
|
} else {
|
||||||
downloadLength = contentLength - startByte
|
downloadLength = contentLength - startByte
|
||||||
totalLength = contentLength
|
totalLength = contentLength
|
||||||
LongArray((downloadLength / chuckSize).toInt()) { idx ->
|
// div with ceiling as
|
||||||
|
// this makes the last part "unknown ending" and it will break at EOF
|
||||||
|
// so eg startByte = 0, downloadLength = 13, chuckSize = 10
|
||||||
|
// = LongArray(2) { 0, 10 } = [0,10) + [10..EOF]
|
||||||
|
LongArray(((downloadLength + chuckSize - 1) / chuckSize).toInt()) { idx ->
|
||||||
startByte + idx * chuckSize
|
startByte + idx * chuckSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return LazyStreamDownloadData(
|
return LazyStreamDownloadData(
|
||||||
url = url,
|
url = url,
|
||||||
headers = headers,
|
headers = headers,
|
||||||
|
@ -1158,8 +1190,7 @@ object VideoDownloadManager {
|
||||||
val resume = stream.resume ?: return@withContext ERROR_UNKNOWN
|
val resume = stream.resume ?: return@withContext ERROR_UNKNOWN
|
||||||
val fileLength = stream.fileLength ?: return@withContext ERROR_UNKNOWN
|
val fileLength = stream.fileLength ?: return@withContext ERROR_UNKNOWN
|
||||||
val resumeAt = (if (resume) fileLength else 0)
|
val resumeAt = (if (resume) fileLength else 0)
|
||||||
metadata.bytesDownloaded = resumeAt
|
metadata.setResumeLength(resumeAt)
|
||||||
metadata.bytesWritten = resumeAt
|
|
||||||
metadata.type = DownloadType.IsPending
|
metadata.type = DownloadType.IsPending
|
||||||
|
|
||||||
val items = streamLazy(
|
val items = streamLazy(
|
||||||
|
@ -1268,7 +1299,8 @@ object VideoDownloadManager {
|
||||||
if (!isActive) return@launch
|
if (!isActive) return@launch
|
||||||
fileMutex.withLock {
|
fileMutex.withLock {
|
||||||
if (metadata.type == DownloadType.IsStopped
|
if (metadata.type == DownloadType.IsStopped
|
||||||
|| metadata.type == DownloadType.IsFailed) return@launch
|
|| metadata.type == DownloadType.IsFailed
|
||||||
|
) return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// mutex just in case, we never want this to fail due to multithreading
|
// mutex just in case, we never want this to fail due to multithreading
|
||||||
|
@ -1374,7 +1406,7 @@ object VideoDownloadManager {
|
||||||
fileStream = stream.fileStream ?: return@withContext ERROR_UNKNOWN
|
fileStream = stream.fileStream ?: return@withContext ERROR_UNKNOWN
|
||||||
|
|
||||||
// push the metadata
|
// push the metadata
|
||||||
metadata.bytesDownloaded = stream.fileLength ?: 0
|
metadata.setResumeLength(stream.fileLength ?: 0)
|
||||||
metadata.hlsProgress = startAt
|
metadata.hlsProgress = startAt
|
||||||
metadata.type = DownloadType.IsPending
|
metadata.type = DownloadType.IsPending
|
||||||
metadata.setDownloadFileInfoTemplate(
|
metadata.setDownloadFileInfoTemplate(
|
||||||
|
@ -1446,12 +1478,15 @@ object VideoDownloadManager {
|
||||||
// if stopped then break to delete
|
// if stopped then break to delete
|
||||||
if (metadata.type == DownloadType.IsStopped || !isActive) return@launch
|
if (metadata.type == DownloadType.IsStopped || !isActive) return@launch
|
||||||
|
|
||||||
|
val segmentLength = bytes.size.toLong()
|
||||||
// send notification, no matter the actual write order
|
// send notification, no matter the actual write order
|
||||||
metadata.addSegment(bytes.size.toLong())
|
metadata.addSegment(segmentLength)
|
||||||
|
|
||||||
// directly write the bytes if you are first
|
// directly write the bytes if you are first
|
||||||
if (metadata.hlsWrittenProgress == index) {
|
if (metadata.hlsWrittenProgress == index) {
|
||||||
fileStream.write(bytes)
|
fileStream.write(bytes)
|
||||||
|
|
||||||
|
metadata.addBytesWritten(segmentLength)
|
||||||
metadata.setWrittenSegment(index)
|
metadata.setWrittenSegment(index)
|
||||||
} else {
|
} else {
|
||||||
// no need to clone as there will be no modification of this bytearray
|
// no need to clone as there will be no modification of this bytearray
|
||||||
|
@ -1460,12 +1495,14 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
// write the cached bytes submitted by other threads
|
// write the cached bytes submitted by other threads
|
||||||
while (true) {
|
while (true) {
|
||||||
fileStream.write(
|
val cache = pendingData.remove(metadata.hlsWrittenProgress) ?: break
|
||||||
pendingData.remove(metadata.hlsWrittenProgress) ?: break
|
val cacheLength = cache.size.toLong()
|
||||||
)
|
|
||||||
|
fileStream.write(cache)
|
||||||
|
metadata.addBytesWritten(cacheLength)
|
||||||
metadata.setWrittenSegment(metadata.hlsWrittenProgress)
|
metadata.setWrittenSegment(metadata.hlsWrittenProgress)
|
||||||
}
|
}
|
||||||
} catch (t : Throwable) {
|
} catch (t: Throwable) {
|
||||||
// this is in case of write fail
|
// this is in case of write fail
|
||||||
if (metadata.type != DownloadType.IsStopped) {
|
if (metadata.type != DownloadType.IsStopped) {
|
||||||
metadata.type = DownloadType.IsFailed
|
metadata.type = DownloadType.IsFailed
|
||||||
|
@ -1756,7 +1793,8 @@ object VideoDownloadManager {
|
||||||
meta.bytesTotal,
|
meta.bytesTotal,
|
||||||
notificationCallback,
|
notificationCallback,
|
||||||
meta.hlsProgress,
|
meta.hlsProgress,
|
||||||
meta.hlsTotal
|
meta.hlsTotal,
|
||||||
|
meta.bytesPerSecond
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1785,7 +1823,8 @@ object VideoDownloadManager {
|
||||||
meta.type,
|
meta.type,
|
||||||
meta.bytesDownloaded,
|
meta.bytesDownloaded,
|
||||||
meta.bytesTotal,
|
meta.bytesTotal,
|
||||||
notificationCallback
|
notificationCallback,
|
||||||
|
bytesPerSecond = meta.bytesPerSecond
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue