From 3b6b3dedc0c69029e46c2999eccd25637673949e Mon Sep 17 00:00:00 2001 From: LagradOst Date: Mon, 30 Aug 2021 19:11:04 +0200 Subject: [PATCH] code fix --- app/build.gradle | 3 + .../com/lagradost/cloudstream3/MainAPI.kt | 33 ++- .../lagradost/cloudstream3/MainActivity.kt | 7 +- .../torrentproviders/NyaaProvider.kt | 67 +++++ .../cloudstream3/ui/player/PlayerFragment.kt | 112 +++++++- .../cloudstream3/ui/result/ResultFragment.kt | 19 +- .../cloudstream3/ui/result/ResultViewModel.kt | 19 ++ .../cloudstream3/ui/search/SearchFragment.kt | 3 +- .../utils/VideoDownloadManager.kt | 261 ++++++++++++++++++ app/src/main/res/layout/fragment_player.xml | 41 ++- app/src/main/res/layout/fragment_result.xml | 2 + app/src/main/res/values/strings.xml | 4 + 12 files changed, 550 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/torrentproviders/NyaaProvider.kt diff --git a/app/build.gradle b/app/build.gradle index ef3440e5..67c7b7f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,4 +128,7 @@ dependencies { //run JS implementation 'org.mozilla:rhino:1.7R4' + + // TorrentStream + implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0' } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 713dbda6..ecd35e7c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.lagradost.cloudstream3.animeproviders.* import com.lagradost.cloudstream3.movieproviders.* +import com.lagradost.cloudstream3.torrentproviders.* import com.lagradost.cloudstream3.utils.ExtractorLink import java.util.* @@ -41,6 +42,7 @@ object APIHolder { WatchCartoonOnlineProvider(), AllMoviesForYouProvider(), AsiaFlixProvider(), + //NyaaProvider(), ) fun getApiFromName(apiName: String?): MainAPI { @@ -116,7 +118,7 @@ abstract class MainAPI { TvType.TvSeries, TvType.Cartoon, TvType.Anime, - TvType.ONA + TvType.ONA, ) open val vpnStatus = VPNStatus.None @@ -222,11 +224,12 @@ enum class TvType { Cartoon, Anime, ONA, + Torrent, } // IN CASE OF FUTURE ANIME MOVIE OR SMTH fun TvType.isMovieType(): Boolean { - return this == TvType.Movie || this == TvType.AnimeMovie + return this == TvType.Movie || this == TvType.AnimeMovie || this == TvType.Torrent } data class SubtitleFile(val lang: String, val url: String) @@ -265,6 +268,16 @@ data class AnimeSearchResponse( override val id: Int? = null, ) : SearchResponse +data class TorrentSearchResponse( + override val name: String, + override val url: String, + override val apiName: String, + override val type: TvType, + + override val posterUrl: String?, + override val id: Int? = null, +) : SearchResponse + data class MovieSearchResponse( override val name: String, override val url: String, @@ -321,6 +334,22 @@ data class AnimeEpisode( val descript: String? = null, ) +data class TorrentLoadResponse( + override val name: String, + override val url: String, + override val apiName: String, + val magnet: String?, + val torrent: String?, + override val plot: String?, + override val type: TvType = TvType.Torrent, + override val posterUrl: String? = null, + override val year: Int? = null, + override val rating: Int? = null, + override val tags: List? = null, + override val duration: String? = null, + override val trailerUrl: String? = null +) : LoadResponse + data class AnimeLoadResponse( val engName: String?, val japName: String?, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 5e0fdcc6..1ec405cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageManager import android.content.res.ColorStateList import android.os.Build import android.os.Bundle +import android.os.Environment import android.view.* import android.widget.TextView import android.widget.Toast @@ -16,6 +17,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.navigation.NavOptions import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment +import com.github.se_bastiaan.torrentstream.StreamStatus +import com.github.se_bastiaan.torrentstream.Torrent +import com.github.se_bastiaan.torrentstream.TorrentOptions +import com.github.se_bastiaan.torrentstream.TorrentStream +import com.github.se_bastiaan.torrentstream.listeners.TorrentListener import com.google.android.gms.cast.framework.CastButtonFactory import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.lagradost.cloudstream3.APIHolder.apis @@ -351,5 +357,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { runAutoUpdate() } } - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/torrentproviders/NyaaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/torrentproviders/NyaaProvider.kt new file mode 100644 index 00000000..03363f61 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/torrentproviders/NyaaProvider.kt @@ -0,0 +1,67 @@ +package com.lagradost.cloudstream3.torrentproviders + +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import org.jsoup.Jsoup + +class NyaaProvider : MainAPI() { + override val name: String + get() = "Nyaa" + override val hasChromecastSupport: Boolean + get() = false + // override val hasDownloadSupport: Boolean + // get() = false + override val mainUrl: String + get() = "https://nyaa.si" + override val supportedTypes: Set + get() = setOf(TvType.Torrent) + override val vpnStatus: VPNStatus + get() = VPNStatus.Torrent + override val instantLinkLoading: Boolean + get() = true + + override fun search(query: String): List { + val url = "$mainUrl/?f=0&c=0_0&q=$query&s=seeders&o=desc" + val response = khttp.get(url) + val document = Jsoup.parse(response.text) + + val returnValues = ArrayList() + val elements = document.select("table > tbody > tr") + for (element in elements) { + val tds = element.select("> td") + if(tds.size < 2) continue + val type = tds[0].select("> a").attr("title") + val titleHeader = tds[1].select("> a").last() + val href = titleHeader.attr("href") + val title = titleHeader.text() + if (title.contains("[Batch]") || !type.contains("Anime")) continue + returnValues.add(TorrentSearchResponse(title, fixUrl(href), this.name, TvType.Torrent, null)) + } + + return returnValues + } + + override fun load(url: String): LoadResponse { + val response = khttp.get(url) + val document = Jsoup.parse(response.text) + val title = document.selectFirst("h3.panel-title").text() + val description = document.selectFirst("div#torrent-description").text() + val downloadLinks = document.select("div.panel-footer > a") + val magnet = downloadLinks[1].attr("href") + val torrent = downloadLinks[0].attr("href") + + return TorrentLoadResponse(title, url, this.name, magnet, fixUrl(torrent) , description) + } + + override fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + if (isCasting) return false + callback.invoke(ExtractorLink(this.name, this.name, data, "", Qualities.Unknown.value)) + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index c5414a61..179c2a9e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -29,6 +29,7 @@ import android.widget.* import android.widget.Toast.LENGTH_SHORT import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager @@ -39,6 +40,11 @@ import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.readValue +import com.github.se_bastiaan.torrentstream.StreamStatus +import com.github.se_bastiaan.torrentstream.Torrent +import com.github.se_bastiaan.torrentstream.TorrentOptions +import com.github.se_bastiaan.torrentstream.TorrentStream +import com.github.se_bastiaan.torrentstream.listeners.TorrentListener import com.google.android.exoplayer2.* import com.google.android.exoplayer2.C.TIME_UNSET import com.google.android.exoplayer2.database.ExoDatabaseProvider @@ -163,6 +169,73 @@ data class UriData( // YE, I KNOW, THIS COULD BE HANDLED A LOT BETTER class PlayerFragment : Fragment() { + + // ============ TORRENT ============ + private var torrentStream: TorrentStream? = null + private var lastTorrentUrl = "" + private val isTorrent: Boolean get() = torrentStream != null + private fun initTorrentStream(torrentUrl: String) { + if (lastTorrentUrl == torrentUrl) return + lastTorrentUrl = torrentUrl + torrentStream?.stopStream() + torrentStream = null + + activity?.let { act -> + val normalPath = + act.cacheDir.absolutePath // "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath" + val torrentOptions: TorrentOptions = TorrentOptions.Builder() + .saveLocation(normalPath) + .removeFilesAfterStop(true) + .build() + + torrentStream = TorrentStream.init(torrentOptions) + torrentStream?.startStream(torrentUrl) + torrentStream?.addListener(object : TorrentListener { + override fun onStreamPrepared(torrent: Torrent?) { + showToast(activity, "Stream Prepared", LENGTH_SHORT) + } + + override fun onStreamStarted(torrent: Torrent?) { + showToast(activity, "Stream Started", LENGTH_SHORT) + } + + override fun onStreamError(torrent: Torrent?, e: java.lang.Exception?) { + e?.printStackTrace() + showToast(activity, e?.localizedMessage ?: "Error loading", LENGTH_SHORT) + } + + override fun onStreamReady(torrent: Torrent?) { + initPlayer(null, null, torrent?.videoFile?.toUri()) + } + + @SuppressLint("SetTextI18n") + override fun onStreamProgress(torrent: Torrent?, status: StreamStatus?) { + try { + println("Seeders ${status?.seeds}") + println("Download Speed ${status?.downloadSpeed}") + println("Progress ${status?.progress}%") + if (isShowing) + player_torrent_info?.visibility = VISIBLE + video_torrent_progress?.text = + "${"%.1f".format(status?.progress ?: 0f)}% at ${status?.downloadSpeed?.div(1000) ?: 0} kb/s" + video_torrent_seeders?.text = "${status?.seeds ?: 0} Seeders" + //streamSeeds.formatText(R.string.streamSeeds, status?.seeds) + //streamSpeed.formatText(R.string.streamDownloadSpeed, status?.downloadSpeed?.div(1024)) + //streamProgressTxt.formatText(R.string.streamProgress, status?.progress, "%") + } catch (e: IllegalStateException) { + e.printStackTrace() + } + } + + override fun onStreamStopped() { + println("stream stopped") + } + }) + } + } + + // ================================= + private lateinit var subStyle: SaveCaptionStyle private var subView: SubtitleView? = null @@ -312,7 +385,8 @@ class PlayerFragment : Fragment() { shadow_overlay?.startAnimation(fadeAnimation) } video_holder?.startAnimation(fadeAnimation) - + player_torrent_info?.visibility = if (isTorrent && isShowing) VISIBLE else GONE + // player_torrent_info?.startAnimation(fadeAnimation) //video_lock_holder?.startAnimation(fadeAnimation) } @@ -1199,7 +1273,7 @@ class PlayerFragment : Fragment() { activity?.popCurrentPage() } video_go_back_holder.setOnClickListener { - println("video_go_back_pressed") + //println("video_go_back_pressed") // activity?.popCurrentPage(isInPlayer = true, isInExpandedView = false, isInResults = false) activity?.popCurrentPage() } @@ -1460,6 +1534,7 @@ class PlayerFragment : Fragment() { override fun onResume() { super.onResume() + torrentStream?.currentTorrent?.resume() onAudioFocusEvent += ::handlePauseEvent activity?.hideSystemUI() @@ -1482,6 +1557,9 @@ class PlayerFragment : Fragment() { savePos() SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged + torrentStream?.stopStream() + torrentStream = null + super.onDestroy() canEnterPipMode = false @@ -1497,6 +1575,7 @@ class PlayerFragment : Fragment() { override fun onPause() { savePos() super.onPause() + torrentStream?.currentTorrent?.pause() if (Util.SDK_INT <= 23) { if (player_view != null) player_view.onPause() releasePlayer() @@ -1567,8 +1646,18 @@ class PlayerFragment : Fragment() { var preferredSubtitles: String = "" @SuppressLint("SetTextI18n") - fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null) { - if (currentUrl == null && uri == null) return + fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null, trueUri: Uri? = null) { + if (currentUrl == null && uri == null && trueUri == null) return + if (currentUrl?.url?.endsWith(".torrent") == true || currentUrl?.url?.startsWith("magnet") == true) { + initTorrentStream(currentUrl.url)//) + return + } + // player_torrent_info?.visibility = if(isTorrent) VISIBLE else GONE + // + isShowing = false + player_torrent_info?.visibility = GONE + //player_torrent_info?.alpha = 0f + println("LOADED: ${uri} or ${currentUrl}") isCurrentlyPlaying = true hasUsedFirstRender = false @@ -1605,14 +1694,14 @@ class PlayerFragment : Fragment() { if (currentUrl != null) { mediaItemBuilder.setUri(currentUrl.url) - } else if (uri != null) { - val uriPrimary = Uri.parse(uri) + } else if (trueUri != null || uri != null) { + val uriPrimary = trueUri ?: Uri.parse(uri) if (uriPrimary.scheme == "content") { mediaItemBuilder.setUri(uriPrimary) // video_title?.text = uriPrimary.toString() } else { //mediaItemBuilder.setUri(Uri.parse(currentUrl.url)) - val realUri = getVideoContentUri(requireContext(), uri) + val realUri = trueUri ?: getVideoContentUri(requireContext(), uri ?: uriPrimary.path ?: "") // video_title?.text = uri.toString() mediaItemBuilder.setUri(realUri) } @@ -1728,7 +1817,10 @@ class PlayerFragment : Fragment() { var epSeason: Int? = null var isEpisodeBased = true - if (isDownloadedFile) { + if (isTorrent) { + hName = "Torrent Stream" + isEpisodeBased = false + } else if (isDownloadedFile) { hName = uriData.name epEpisode = uriData.episode epSeason = uriData.season @@ -1786,7 +1878,9 @@ class PlayerFragment : Fragment() { video_title_rez?.text = if (height == null || width == null) currentUrl?.name - ?: "" else if (isDownloadedFile) "${width}x${height}" else "${currentUrl?.name} - ${width}x${height}" + ?: "" else + if (isTorrent) "${width}x${height}" else + if (isDownloadedFile || currentUrl?.name == null) "${width}x${height}" else "${currentUrl.name} - ${width}x${height}" if (!hasUsedFirstRender) { // DON'T WANT TO SET MULTIPLE MESSAGES changeSkip() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 74094b80..4e21a4cb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -441,6 +441,7 @@ class ResultFragment : Fragment() { TvType.TvSeries -> "TVSeries/$titleName" TvType.ONA -> "ONA" TvType.Cartoon -> "Cartoons/$titleName" + TvType.Torrent -> "Torrent" else -> null } @@ -457,7 +458,7 @@ class ResultFragment : Fragment() { url ?: return@let, currentType ?: return@let, currentHeaderName ?: return@let, - currentPoster ?: return@let, + currentPoster, currentId ?: return@let, System.currentTimeMillis(), ) @@ -863,7 +864,7 @@ class ResultFragment : Fragment() { i.data = Uri.parse(d.url) try { startActivity(i) - } catch (e : Exception) { + } catch (e: Exception) { e.printStackTrace() } } @@ -914,10 +915,13 @@ class ResultFragment : Fragment() { result_metadata.visibility = GONE } - if (d.posterUrl != null) { - result_poster?.setImage(d.posterUrl) - } + result_poster?.setImage(d.posterUrl) + result_poster_holder?.visibility = if (d.posterUrl.isNullOrBlank()) GONE else VISIBLE + result_play_movie?.text = + if (d.type == TvType.Torrent) getString(R.string.play_torrent_button) else getString(R.string.play_movie_button) + result_plot_header?.text = + if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot) if (d.plot != null) { var syno = d.plot!! if (syno.length > MAX_SYNO_LENGH) { @@ -930,7 +934,8 @@ class ResultFragment : Fragment() { } result_descript.text = syno } else { - result_descript.text = "No Plot found" + result_descript.text = + if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(R.string.normal_no_plot) } result_tag.removeAllViews() @@ -1065,7 +1070,7 @@ class ResultFragment : Fragment() { i.data = Uri.parse(tempUrl) try { startActivity(i) - } catch (e : Exception) { + } catch (e: Exception) { e.printStackTrace() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index add2763a..72667198 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -324,6 +324,25 @@ class ResultViewModel : ViewModel() { ), -1 ) } + is TorrentLoadResponse -> { + updateEpisodes( + context, mainId, arrayListOf( + context.buildResultEpisode( + d.name, + null, + 0, + null, + d.torrent ?: d.magnet ?: "", + d.apiName, + (mainId), // HAS SAME ID + 0, + null, + null, + ) + ), -1 + ) + } + } } else -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index f6c5a04c..c78ae82a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -130,7 +130,8 @@ class SearchFragment : Fragment() { Pair("Movies", listOf(TvType.Movie)), Pair("TvSeries", listOf(TvType.TvSeries)), Pair("Cartoons", listOf(TvType.Cartoon)), - Pair("Anime", listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)) + Pair("Anime", listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)), + Pair("Torrent", listOf(TvType.Torrent)), ) val arrayAdapter2 = ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) 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 275e256b..6184ad33 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.app.* import android.content.* import android.graphics.Bitmap @@ -14,6 +15,11 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.net.toUri import com.bumptech.glide.Glide +import com.github.se_bastiaan.torrentstream.StreamStatus +import com.github.se_bastiaan.torrentstream.Torrent +import com.github.se_bastiaan.torrentstream.TorrentOptions +import com.github.se_bastiaan.torrentstream.TorrentStream +import com.github.se_bastiaan.torrentstream.listeners.TorrentListener import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.MainActivity.Companion.showToast import com.lagradost.cloudstream3.R @@ -488,6 +494,257 @@ object VideoDownloadManager { val bytesTotal: Long, ) + fun getSizeAndProgressFromTorrent(torrent: Torrent?, progress: Float?): Pair? { + try { + if (torrent == null || progress == null) return null + val length = torrent.videoFile?.length() ?: 0 + if (length > 0) { + // val bytesTotal = (length * 100 / progress).toLong() + // if (bytesTotal > 0 && length > 0) { + return Pair((length * progress / 100).toLong(), length) + //} + } + return null + } catch (e: Exception) { + return null + } + } + + private fun downloadTorrent( + 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" + val fileStream: OutputStream + val fileLength: Long + val resume = false + val normalPath = "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" + + if (isScopedStorage()) { + val cr = context.contentResolver ?: return ERROR_CONTENT_RESOLVER_NOT_FOUND + + 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) + } + + val torrentOptions: TorrentOptions = TorrentOptions.Builder() + .saveLocation(context.cacheDir.absolutePath) + .removeFilesAfterStop(true) + .build() + + val torrentStream = TorrentStream.init(torrentOptions) + torrentStream.startStream(link) + + var progress = 0f + var isDone = false + var isFailed = false + + torrentStream.addListener(object : TorrentListener { + override fun onStreamPrepared(torrent: Torrent?) { + + //showToast(context, "Stream Prepared", Toast.LENGTH_SHORT) + } + + override fun onStreamStarted(torrent: Torrent?) { + // showToast(context, "Stream Started", Toast.LENGTH_SHORT) + } + + override fun onStreamError(torrent: Torrent?, e: java.lang.Exception?) { + isFailed = true + e?.printStackTrace() + // showToast(context, e?.localizedMessage ?: "Error loading", Toast.LENGTH_SHORT) + } + + override fun onStreamReady(torrent: Torrent?) { + + } + + @SuppressLint("SetTextI18n") + override fun onStreamProgress(torrent: Torrent?, status: StreamStatus?) { + try { + println("Seeders ${status?.seeds}") + println("Download Speed ${status?.downloadSpeed}") + println("Progress ${status?.progress}%") + + val lengthSize = getSizeAndProgressFromTorrent(torrent, status?.progress) + if (lengthSize != null) { + progress = status?.progress!! + val type = if (progress >= 100f) DownloadType.IsDone else DownloadType.IsDownloading + parentId?.let { id -> + try { + downloadStatus[id] = type + downloadStatusEvent.invoke(Pair(id, type)) + downloadProgressEvent.invoke(Triple(id, lengthSize.first, lengthSize.second)) + } catch (e: Exception) { + // IDK MIGHT ERROR + } + } + createNotificationCallback.invoke( + CreateNotificationMetadata( + type, + lengthSize.first, lengthSize.second + ) + ) + } + val pro = status?.progress + if (pro != null && pro >= 100) { + isDone = true + } + + } catch (e: IllegalStateException) { + e.printStackTrace() + } + } + + override fun onStreamStopped() { + if (progress > 98) { + isDone = true + } else { + isFailed = true + } + println("stream stopped") + } + }) + + fun updateNot(type: DownloadType) { + val lengthSize = getSizeAndProgressFromTorrent(torrentStream.currentTorrent, progress) ?: return + + createNotificationCallback.invoke( + CreateNotificationMetadata( + type, + lengthSize.first, lengthSize.second + ) + ) + } + + val downloadEventListener = { event: Pair -> + if (event.first == parentId) { + when (event.second) { + DownloadActionType.Pause -> { + torrentStream?.currentTorrent?.pause() + updateNot(DownloadType.IsPaused) + } + DownloadActionType.Stop -> { + isFailed = true + torrentStream.stopStream() + torrentStream?.currentTorrent?.videoFile?.delete() + updateNot(DownloadType.IsStopped) + context.removeKey(KEY_RESUME_PACKAGES, event.first.toString()) + saveQueue(context) + } + DownloadActionType.Resume -> { + torrentStream?.currentTorrent?.resume() + updateNot(DownloadType.IsDownloading) + } + } + } + } + + if (parentId != null) + downloadEvent += downloadEventListener + + var lastProgress = progress + var lastUpdateTime = System.currentTimeMillis() + while (!isDone && !isFailed) { + sleep(100) + if (lastProgress != progress) { + lastUpdateTime = System.currentTimeMillis() + lastProgress = progress + } + if (progress >= 98f && System.currentTimeMillis() - lastUpdateTime > 10000L) { // after 10 sec set as done + isDone = true + } + } + + if (parentId != null) + downloadEvent -= downloadEventListener + + // RETURN MESSAGE + return when { + isFailed -> { + parentId?.let { id -> downloadProgressEvent.invoke(Triple(id, 0, 0)) } + SUCCESS_STOPPED + } + isDone -> { + torrentStream?.currentTorrent?.videoStream?.copyTo(fileStream) + torrentStream?.currentTorrent?.videoFile?.delete() + + SUCCESS_DOWNLOAD_DONE + } + else -> { + SUCCESS_DOWNLOAD_DONE + //idk + } + } + } + fun downloadThing( context: Context, link: IDownloadableMinimum, @@ -498,6 +755,10 @@ object VideoDownloadManager { parentId: Int?, createNotificationCallback: (CreateNotificationMetadata) -> Unit ): Int { + if (link.url.startsWith("magnet") || link.url.endsWith(".torrent")) { + return downloadTorrent(context, link.url, name, folder, extension, parentId, createNotificationCallback) + } + val relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) val displayName = "$name.$extension" diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml index 3a9ce82f..4cd85b4f 100644 --- a/app/src/main/res/layout/fragment_player.xml +++ b/app/src/main/res/layout/fragment_player.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal" android:keepScreenOn="true" app:backgroundTint="@android:color/black" @@ -60,6 +60,8 @@ android:layout_height="45dp"> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 066e8055..c096bf89 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -154,6 +154,7 @@ android:layout_height="wrap_content"> @@ -275,6 +276,7 @@ None Play Movie + Stream Torrent Sources Subtitles Retry connection… @@ -99,4 +100,7 @@ A VPN might be needed for this provider to work correctly This providers is a torrent, a VPN is recommended + Description + No Plot Found + No Description Found \ No newline at end of file