From dffa7a39c44ba2e877cf4b334afa5c0c8d2fc8c6 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Wed, 1 Sep 2021 23:30:21 +0200 Subject: [PATCH] idk stuff not working --- .../movieproviders/AsiaFlixProvider.kt | 2 - .../movieproviders/HDMProvider.kt | 2 - .../movieproviders/TrailersToProvider.kt | 6 +- .../cloudstream3/utils/M3u8Helper.kt | 34 +- .../utils/VideoDownloadManager.kt | 408 ++++++++---------- 5 files changed, 211 insertions(+), 241 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AsiaFlixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AsiaFlixProvider.kt index d65a8bff..20b37724 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AsiaFlixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AsiaFlixProvider.kt @@ -19,8 +19,6 @@ class AsiaFlixProvider : MainAPI() { get() = false override val hasMainPage: Boolean get() = true - override val hasDownloadSupport: Boolean - get() = false override val hasChromecastSupport: Boolean get() = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMProvider.kt index 8525537d..fd9d8bd6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/HDMProvider.kt @@ -10,8 +10,6 @@ class HDMProvider : MainAPI() { get() = "HD Movies" override val mainUrl: String get() = "https://hdm.to" - override val hasDownloadSupport: Boolean - get() = false override val supportedTypes: Set get() = setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt index aa69def6..691bdf41 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt @@ -197,7 +197,7 @@ class TrailersToProvider : MainAPI() { return false } - override fun load(url: String): LoadResponse? { + override fun load(url: String): LoadResponse { val response = khttp.get(url) val document = Jsoup.parse(response.text) val metaInfo = document.select("div.post-info-meta > ul.post-info-meta-list > li") @@ -225,7 +225,7 @@ class TrailersToProvider : MainAPI() { val isTvShow = url.contains("/tvshow/") if (isTvShow) { - val episodes = document.select("#seasons-accordion .card-body > .tour-modern") ?: return null + val episodes = document.select("#seasons-accordion .card-body > .tour-modern") ?: throw ErrorLoadingException("No Episodes found") val parsedEpisodes = episodes.withIndex().map { (index, item) -> val epPoster = item.selectFirst("img").attr("src") val main = item.selectFirst(".tour-modern-main") @@ -283,7 +283,7 @@ class TrailersToProvider : MainAPI() { } else "" val data = mapper.writeValueAsString( - Pair(subUrl, fixUrl(document.selectFirst("content")?.attr("data-url") ?: return null)) + Pair(subUrl, fixUrl(document?.selectFirst("content")?.attr("data-url") ?: throw ErrorLoadingException("Link not found"))) ) return MovieLoadResponse( title, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt index 5c3b2e6a..b7746aee 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt @@ -60,9 +60,11 @@ class M3u8Helper { ) private fun selectBest(qualities: List): M3u8Stream? { - val result = qualities.sortedBy { if (it.quality != null && it.quality <= 1080) it.quality else 0 + val result = qualities.sortedBy { + if (it.quality != null && it.quality <= 1080) it.quality else 0 }.reversed().filter { - listOf("m3u", "m3u8").contains(absoluteExtensionDetermination(it.streamUrl)) + it.streamUrl.contains(".m3u8") + // listOf("m3u", "m3u8").contains(absoluteExtensionDetermination(it.streamUrl)) } return result.getOrNull(0) } @@ -80,7 +82,7 @@ class M3u8Helper { public fun m3u8Generation(m3u8: M3u8Stream): List { val generate = sequence { val m3u8Parent = getParentLink(m3u8.streamUrl) - val response = khttp.get(m3u8.streamUrl, headers=m3u8.headers) + val response = khttp.get(m3u8.streamUrl, headers = m3u8.headers) for (match in QUALITY_REGEX.findAll(response.text)) { var (quality, m3u8Link) = match.destructured @@ -117,7 +119,7 @@ class M3u8Helper { val errored: Boolean = false ) - public fun hlsYield(qualities: List): Iterator { + fun hlsYield(qualities: List, startIndex: Int = 0): Iterator { if (qualities.isEmpty()) return listOf(HlsDownloadData(byteArrayOf(), 0, 0, true)).iterator() var selected = selectBest(qualities) @@ -127,21 +129,22 @@ class M3u8Helper { val headers = selected.headers val streams = qualities.map { m3u8Generation(it) }.flatten() - val sslVerification = if (headers.containsKey("ssl_verification")) headers["ssl_verification"].toBoolean() else true + //val sslVerification = if (headers.containsKey("ssl_verification")) headers["ssl_verification"].toBoolean() else true val secondSelection = selectBest(streams.ifEmpty { listOf(selected) }) if (secondSelection != null) { - val m3u8Response = khttp.get(secondSelection.streamUrl, headers=headers) + val m3u8Response = khttp.get(secondSelection.streamUrl, headers = headers) val m3u8Data = m3u8Response.text var encryptionUri: String? = null var encryptionIv = byteArrayOf() - var encryptionData= byteArrayOf() + var encryptionData = byteArrayOf() val encryptionState = isEncrypted(m3u8Data) if (encryptionState) { - val match = ENCRYPTION_URL_IV_REGEX.find(m3u8Data)!!.destructured // its safe to assume that its not going to be null + val match = + ENCRYPTION_URL_IV_REGEX.find(m3u8Data)!!.destructured // its safe to assume that its not going to be null encryptionUri = match.component2() if (!isCompleteUrl(encryptionUri)) { @@ -149,29 +152,30 @@ class M3u8Helper { } encryptionIv = match.component3().toByteArray() - val encryptionKeyResponse = khttp.get(encryptionUri, headers=headers) + val encryptionKeyResponse = khttp.get(encryptionUri, headers = headers) encryptionData = encryptionKeyResponse.content } val allTs = TS_EXTENSION_REGEX.findAll(m3u8Data) - val totalTs = allTs.toList().size + val allTsList = allTs.toList() + val totalTs =allTsList .size if (totalTs == 0) { - return listOf(HlsDownloadData(byteArrayOf(), 0, 0, true)).iterator() + return listOf(HlsDownloadData(byteArrayOf(), 0, 0, true)).iterator() } var lastYield = 0 val relativeUrl = getParentLink(secondSelection.streamUrl) var retries = 0 - val tsByteGen = sequence { + val tsByteGen = sequence { loop@ for ((index, ts) in allTs.withIndex()) { val url = if ( isCompleteUrl(ts.destructured.component1()) ) ts.destructured.component1() else "$relativeUrl/${ts.destructured.component1()}" - val c = index+1 + val c = index + 1 + startIndex while (lastYield != c) { try { - val tsResponse = khttp.get(url, headers=headers) + val tsResponse = khttp.get(url, headers = headers) var tsData = tsResponse.content if (encryptionState) { @@ -196,6 +200,6 @@ class M3u8Helper { } return tsByteGen.iterator() } - return listOf(HlsDownloadData(byteArrayOf(), 0, 0, true)).iterator() + return listOf(HlsDownloadData(byteArrayOf(), 0, 0, true)).iterator() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index a7a93087..8ee23bf5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -1,7 +1,10 @@ package com.lagradost.cloudstream3.utils import android.annotation.SuppressLint -import android.app.* +import android.app.Activity +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent import android.content.* import android.graphics.Bitmap import android.net.Uri @@ -31,8 +34,6 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.M3u8Helper -import kotlin.math.roundToInt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @@ -41,6 +42,7 @@ import java.lang.Thread.sleep import java.net.URL import java.net.URLConnection import java.util.* +import kotlin.math.roundToInt const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general" const val DOWNLOAD_CHANNEL_NAME = "Downloads" @@ -114,7 +116,7 @@ object VideoDownloadManager { val source: String?, val folder: String?, val ep: DownloadEpisodeMetadata, - val links: List + val links: List, ) data class DownloadResumePackage( @@ -126,7 +128,7 @@ object VideoDownloadManager { val totalBytes: Long, val relativePath: String, val displayName: String, - val extraData : String? = null, + val extraInfo: String? = null ) data class DownloadedFileInfoResult( @@ -141,6 +143,7 @@ object VideoDownloadManager { ) private const val SUCCESS_DOWNLOAD_DONE = 1 + private const val SUCCESS_STREAM = 3 private const val SUCCESS_STOPPED = 2 private const val ERROR_DELETING_FILE = 3 // will not download the next one, but is still classified as an error private const val ERROR_CREATE_FILE = -2 @@ -514,25 +517,27 @@ object VideoDownloadManager { } } - private fun downloadTorrent( + data class StreamData( + val errorCode: Int, + val resume: Boolean? = null, + val fileLength: Long? = null, + val fileStream: OutputStream? = null, + ) + + private fun setupStream( context: Context, - link: String, name: String, folder: String?, extension: String, - //tryResume: Boolean = false, - parentId: Int?, - createNotificationCallback: (CreateNotificationMetadata) -> Unit - ): Int { - val relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) - val displayName = "$name.$extension" + tryResume: Boolean, + ): StreamData { + val relativePath = getRelativePath(folder) + val displayName = getDisplayName(name, extension) val fileStream: OutputStream val fileLength: Long - val resume = false - val normalPath = "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" - + var resume = tryResume if (isScopedStorage()) { - val cr = context.contentResolver ?: return ERROR_CONTENT_RESOLVER_NOT_FOUND + val cr = context.contentResolver ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND) val currentExistingFile = cr.getExistingDownloadUriOrNullQ(relativePath, displayName) // CURRENT FILE WITH THE SAME PATH @@ -573,30 +578,47 @@ object VideoDownloadManager { cr.insert( contentUri, newFile - ) ?: return ERROR_MEDIA_STORE_URI_CANT_BE_CREATED + ) ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND) } fileStream = cr.openOutputStream(newFileUri, "w" + (if (appendFile) "a" else "")) - ?: return ERROR_CONTENT_RESOLVER_CANT_OPEN_STREAM + ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND) } else { + val normalPath = getNormalPath(relativePath, displayName) // NORMAL NON SCOPED STORAGE FILE CREATION val rFile = File(normalPath) if (!rFile.exists()) { fileLength = 0 rFile.parentFile?.mkdirs() - if (!rFile.createNewFile()) return ERROR_CREATE_FILE + if (!rFile.createNewFile()) return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND) } else { if (resume) { fileLength = rFile.length() } else { fileLength = 0 rFile.parentFile?.mkdirs() - if (!rFile.delete()) return ERROR_DELETING_FILE - if (!rFile.createNewFile()) return ERROR_CREATE_FILE + if (!rFile.delete()) return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND) + if (!rFile.createNewFile()) return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND) } } fileStream = FileOutputStream(rFile, false) } + if (fileLength == 0L) resume = false + return StreamData(SUCCESS_STREAM, resume, fileLength, fileStream) + } + + private fun downloadTorrent( + context: Context, + link: String, + name: String, + folder: String?, + extension: String, + //tryResume: Boolean = false, + parentId: Int?, + createNotificationCallback: (CreateNotificationMetadata) -> Unit + ): Int { + val stream = setupStream(context, name, folder, extension, false) + if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode val torrentOptions: TorrentOptions = TorrentOptions.Builder() .saveLocation(context.cacheDir.absolutePath) @@ -737,8 +759,10 @@ object VideoDownloadManager { SUCCESS_STOPPED } isDone -> { - torrentStream?.currentTorrent?.videoStream?.copyTo(fileStream) - torrentStream?.currentTorrent?.videoFile?.delete() + stream.fileStream?.let { fileStream -> + torrentStream?.currentTorrent?.videoStream?.copyTo(fileStream) + torrentStream?.currentTorrent?.videoFile?.delete() + } SUCCESS_DOWNLOAD_DONE } @@ -760,100 +784,33 @@ object VideoDownloadManager { createNotificationCallback: (CreateNotificationMetadata) -> Unit ): Int { if (link.url.startsWith("magnet") || link.url.endsWith(".torrent")) { - return normalSafeApiCall { downloadTorrent(context, link.url, name, folder, extension, parentId, createNotificationCallback) } ?: ERROR_UNKNOWN + return normalSafeApiCall { + downloadTorrent( + context, + link.url, + name, + folder, + extension, + parentId, + createNotificationCallback + ) + } ?: ERROR_UNKNOWN } - val relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) - val displayName = "$name.$extension" + val relativePath = getRelativePath(folder) + val displayName = getDisplayName(name, extension) - val normalPath = "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" - var resume = tryResume - - val fileStream: OutputStream - val fileLength: Long fun deleteFile(): Int { - if (isScopedStorage()) { - val lastContent = context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName) - if (lastContent != null) { - context.contentResolver.delete(lastContent, null, null) - } - } else { - if (!File(normalPath).delete()) return ERROR_DELETING_FILE - } - parentId?.let { - downloadDeleteEvent.invoke(parentId) - } - return SUCCESS_STOPPED + return delete(context, name, folder, extension, parentId) } - if (isScopedStorage()) { - val cr = context.contentResolver ?: return ERROR_CONTENT_RESOLVER_NOT_FOUND + val stream = setupStream(context, name, folder, extension, tryResume) + if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode - val currentExistingFile = - cr.getExistingDownloadUriOrNullQ(relativePath, displayName) // CURRENT FILE WITH THE SAME PATH - - fileLength = - if (currentExistingFile == null || !resume) 0 else (cr.getFileLength(currentExistingFile) - ?: 0)// IF NOT RESUME THEN 0, OTHERWISE THE CURRENT FILE SIZE - - if (!resume && currentExistingFile != null) { // DELETE FILE IF FILE EXITS AND NOT RESUME - val rowsDeleted = context.contentResolver.delete(currentExistingFile, null, null) - if (rowsDeleted < 1) { - println("ERROR DELETING FILE!!!") - } - } - - var appendFile = false - val newFileUri = if (resume && currentExistingFile != null) { - appendFile = true - currentExistingFile - } else { - val contentUri = - MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI - //val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - val currentMimeType = when (extension) { - "vtt" -> "text/vtt" - "mp4" -> "video/mp4" - "srt" -> "text/plain" - else -> null - } - val newFile = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) - put(MediaStore.MediaColumns.TITLE, name) - if (currentMimeType != null) - put(MediaStore.MediaColumns.MIME_TYPE, currentMimeType) - put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) - } - - cr.insert( - contentUri, - newFile - ) ?: return ERROR_MEDIA_STORE_URI_CANT_BE_CREATED - } - - fileStream = cr.openOutputStream(newFileUri, "w" + (if (appendFile) "a" else "")) - ?: return ERROR_CONTENT_RESOLVER_CANT_OPEN_STREAM - } else { - // NORMAL NON SCOPED STORAGE FILE CREATION - val rFile = File(normalPath) - if (!rFile.exists()) { - fileLength = 0 - rFile.parentFile?.mkdirs() - if (!rFile.createNewFile()) return ERROR_CREATE_FILE - } else { - if (resume) { - fileLength = rFile.length() - } else { - fileLength = 0 - rFile.parentFile?.mkdirs() - if (!rFile.delete()) return ERROR_DELETING_FILE - if (!rFile.createNewFile()) return ERROR_CREATE_FILE - } - } - fileStream = FileOutputStream(rFile, false) - } - if (fileLength == 0L) resume = false + val resume = stream.resume!! + val fileStream = stream.fileStream!! + val fileLength = stream.fileLength!! // CONNECT val connection: URLConnection = URL(link.url.replace(" ", "%20")).openConnection() // IDK OLD PHONES BE WACK @@ -1049,14 +1006,52 @@ object VideoDownloadManager { } } + private fun getRelativePath(folder: String?): String { + return (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) + } + + private fun getDisplayName(name: String, extension: String): String { + return "$name.$extension" + } + + private fun getNormalPath(relativePath: String, displayName: String): String { + return "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" + } + + private fun delete( + context: Context, + name: String, + folder: String?, + extension: String, + parentId: Int?, + ): Int { + val relativePath = getRelativePath(folder) + val displayName = getDisplayName(name, extension) + + if (isScopedStorage()) { + val lastContent = context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName) + if (lastContent != null) { + context.contentResolver.delete(lastContent, null, null) + } + } else { + if (!File(getNormalPath(relativePath, displayName)).delete()) return ERROR_DELETING_FILE + } + parentId?.let { + downloadDeleteEvent.invoke(parentId) + } + return SUCCESS_STOPPED + } + private fun downloadHLS( context: Context, link: ExtractorLink, name: String, folder: String?, parentId: Int?, + startIndex: Int?, createNotificationCallback: (CreateNotificationMetadata) -> Unit ): Int { + val extension = "mp4" fun logcatPrint(vararg items: Any?) { items.forEach { println("[HLS]: $it") @@ -1066,93 +1061,40 @@ object VideoDownloadManager { val m3u8Helper = M3u8Helper() logcatPrint("initialised the HLS downloader.") - val m3u8 = M3u8Helper.M3u8Stream(link.url, when (link.quality) { - -2 -> 360 - -1 -> 480 - 1 -> 720 - 2 -> 1080 - else -> null - }, mapOf("referer" to link.referer)) - val tsIterator = m3u8Helper.hlsYield(listOf(m3u8)) + val m3u8 = M3u8Helper.M3u8Stream( + link.url, when (link.quality) { + -2 -> 360 + -1 -> 480 + 1 -> 720 + 2 -> 1080 + else -> null + }, mapOf("referer" to link.referer) + ) - val relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) - val displayName = "$name.ts" + var realIndex = startIndex ?: 0 + val stream = setupStream(context, name, folder, extension, realIndex > 0) + if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode - val normalPath = "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" + if (!stream.resume!!) realIndex = 0 + val tsIterator = m3u8Helper.hlsYield(listOf(m3u8), realIndex) - val fileStream: OutputStream - val fileLength: Long + val relativePath = getRelativePath(folder) + val displayName = getDisplayName(name, extension) - fun deleteFile(): Int { - if (isScopedStorage()) { - val lastContent = context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName) - if (lastContent != null) { - context.contentResolver.delete(lastContent, null, null) - } - } else { - if (!File(normalPath).delete()) return ERROR_DELETING_FILE - } - parentId?.let { - downloadDeleteEvent.invoke(parentId) - } - return SUCCESS_STOPPED - } + val fileStream = stream.fileStream!! - if (isScopedStorage()) { - val cr = context.contentResolver ?: return ERROR_CONTENT_RESOLVER_NOT_FOUND - - val currentExistingFile = - cr.getExistingDownloadUriOrNullQ(relativePath, displayName) // CURRENT FILE WITH THE SAME PATH - - if (currentExistingFile != null) { // DELETE FILE IF FILE EXITS - val rowsDeleted = context.contentResolver.delete(currentExistingFile, null, null) - if (rowsDeleted < 1) { - println("ERROR DELETING FILE!!!") - } - } - - val newFileUri = if (currentExistingFile != null) { - currentExistingFile - } else { - val contentUri = - MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI - //val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - val currentMimeType = "video/mp2t" - val newFile = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) - put(MediaStore.MediaColumns.TITLE, name) - put(MediaStore.MediaColumns.MIME_TYPE, currentMimeType) - put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) - } - - cr.insert( - contentUri, - newFile - ) ?: return ERROR_MEDIA_STORE_URI_CANT_BE_CREATED - } - - fileStream = cr.openOutputStream(newFileUri, "a") - ?: return ERROR_CONTENT_RESOLVER_CANT_OPEN_STREAM - } else { - // NORMAL NON SCOPED STORAGE FILE CREATION - val rFile = File(normalPath) - if (!rFile.exists()) { - rFile.parentFile?.mkdirs() - if (!rFile.createNewFile()) return ERROR_CREATE_FILE - } else { - rFile.parentFile?.mkdirs() - if (!rFile.delete()) return ERROR_DELETING_FILE - if (!rFile.createNewFile()) return ERROR_CREATE_FILE - } - fileStream = FileOutputStream(rFile, false) - } val firstTs = tsIterator.next() var isDone = false var isFailed = false + var isPaused = false var bytesDownloaded = firstTs.bytes.size.toLong() - var tsProgress = 1L + var tsProgress = 1L + realIndex val totalTs = firstTs.totalTs.toLong() + + fun deleteFile(): Int { + return delete(context, name, folder, extension, parentId) + } /* Most of the auto generated m3u8 out there have TS of the same size. And only the last TS might have a different size. @@ -1163,15 +1105,27 @@ object VideoDownloadManager { > (bytesDownloaded/tsProgress)*totalTs */ - - parentId?.let { - context.setKey(KEY_DOWNLOAD_INFO, it.toString(), DownloadedFileInfo((bytesDownloaded/tsProgress)*totalTs, relativePath, displayName)) + fun updateInfo() { + parentId?.let { + context.setKey( + KEY_DOWNLOAD_INFO, + it.toString(), + DownloadedFileInfo( + (bytesDownloaded / tsProgress) * totalTs, + relativePath, + displayName, + tsProgress.toString() + ) + ) + } } + updateInfo() fun updateNotification() { val type = when { isDone -> DownloadType.IsDone isFailed -> DownloadType.IsFailed + isPaused -> DownloadType.IsPaused else -> DownloadType.IsDownloading } @@ -1179,19 +1133,24 @@ object VideoDownloadManager { try { downloadStatus[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) { // IDK MIGHT ERROR } } - createNotificationCallback.invoke(CreateNotificationMetadata(type, bytesDownloaded, (bytesDownloaded/tsProgress)*totalTs)) + createNotificationCallback.invoke( + CreateNotificationMetadata( + type, + bytesDownloaded, + (bytesDownloaded / tsProgress) * totalTs + ) + ) } fun stopIfError(ts: M3u8Helper.HlsDownloadData): Int? { if (ts.errored || ts.bytes.isEmpty()) { - val error: Int - error = if (!ts.errored) { + val error: Int = if (!ts.errored) { logcatPrint("Error: No stream was found.") ERROR_UNKNOWN } else { @@ -1225,11 +1184,15 @@ object VideoDownloadManager { isFailed = true } DownloadActionType.Pause -> { - isFailed = true // Pausing is not supported since well...I need to know the index of the ts it was paused at + isPaused = + true // Pausing is not supported since well...I need to know the index of the ts it was paused at // it may be possible to store it in a variable, but when the app restarts it will be lost } - else -> updateNotification() // do nothing, since well...I don't support anything else + DownloadActionType.Resume -> { + isPaused = false + } } + updateNotification() } } @@ -1250,8 +1213,8 @@ object VideoDownloadManager { } notificationCoroutine.cancel() } - - stopIfError(firstTs).let { + + stopIfError(firstTs).let { if (it != null) { closeAll() return it @@ -1263,14 +1226,28 @@ object VideoDownloadManager { fileStream.write(firstTs.bytes) + fun onFailed() { + fileStream.close() + deleteFile() + updateNotification() + closeAll() + + } + for (ts in tsIterator) { + while (isPaused) { + if (isFailed) { + onFailed() + return SUCCESS_STOPPED + } + sleep(100) + } + if (isFailed) { - fileStream.close() - deleteFile() - updateNotification() - closeAll() + onFailed() return SUCCESS_STOPPED } + stopIfError(ts).let { if (it != null) { closeAll() @@ -1281,20 +1258,18 @@ object VideoDownloadManager { fileStream.write(ts.bytes) tsProgress = ts.currentIndex.toLong() bytesDownloaded += ts.bytes.size.toLong() - logcatPrint("Download progress ${((tsProgress.toFloat()/totalTs.toFloat())*100).roundToInt()}%") + logcatPrint("Download progress ${((tsProgress.toFloat() / totalTs.toFloat()) * 100).roundToInt()}%") + updateInfo() } isDone = true fileStream.close() updateNotification() closeAll() - parentId?.let { - context.setKey(KEY_DOWNLOAD_INFO, it.toString(), DownloadedFileInfo(bytesDownloaded, relativePath, displayName)) - } - + updateInfo() return SUCCESS_DOWNLOAD_DONE } - + private fun downloadSingleEpisode( context: Context, source: String?, @@ -1305,8 +1280,11 @@ object VideoDownloadManager { ): Int { val name = sanitizeFilename(ep.name ?: "Episode ${ep.episode}") - if (link.isM3u8) { - return downloadHLS(context, link, name, folder, ep.id) { meta -> + if (link.isM3u8 || link.url.endsWith(".m3u8")) { + val startIndex = if (tryResume) { + context.getKey(KEY_DOWNLOAD_INFO, ep.id.toString(), null)?.extraInfo?.toIntOrNull() + } else null + return downloadHLS(context, link, name, folder, ep.id, startIndex) { meta -> createNotification( context, source, @@ -1390,11 +1368,7 @@ object VideoDownloadManager { if (fileLength == 0L) return null return DownloadedFileInfoResult(fileLength, info.totalBytes, fileUri) } else { - val normalPath = - "${Environment.getExternalStorageDirectory()}${File.separatorChar}${info.relativePath}${info.displayName}".replace( - '/', - File.separatorChar - ) + val normalPath = getNormalPath(info.relativePath, info.displayName) val dFile = File(normalPath) if (!dFile.exists()) return null return DownloadedFileInfoResult(dFile.length(), info.totalBytes, dFile.toUri()) @@ -1422,11 +1396,7 @@ object VideoDownloadManager { return cr.delete(fileUri, null, null) > 0 // IF DELETED ROWS IS OVER 0 } else { - val normalPath = - "${Environment.getExternalStorageDirectory()}${File.separatorChar}${info.relativePath}${info.displayName}".replace( - '/', - File.separatorChar - ) + val normalPath = getNormalPath(info.relativePath, info.displayName) val dFile = File(normalPath) if (!dFile.exists()) return true return dFile.delete()