forked from recloudstream/cloudstream
code fix
This commit is contained in:
parent
613c26ec63
commit
3b6b3dedc0
12 changed files with 550 additions and 21 deletions
|
@ -128,4 +128,7 @@ dependencies {
|
|||
|
||||
//run JS
|
||||
implementation 'org.mozilla:rhino:1.7R4'
|
||||
|
||||
// TorrentStream
|
||||
implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
||||
}
|
|
@ -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<String>? = null,
|
||||
override val duration: String? = null,
|
||||
override val trailerUrl: String? = null
|
||||
) : LoadResponse
|
||||
|
||||
data class AnimeLoadResponse(
|
||||
val engName: String?,
|
||||
val japName: String?,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<TvType>
|
||||
get() = setOf(TvType.Torrent)
|
||||
override val vpnStatus: VPNStatus
|
||||
get() = VPNStatus.Torrent
|
||||
override val instantLinkLoading: Boolean
|
||||
get() = true
|
||||
|
||||
override fun search(query: String): List<SearchResponse> {
|
||||
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<SearchResponse>()
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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<String>(view.context, R.layout.sort_bottom_single_choice)
|
||||
|
|
|
@ -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<Long, Long>? {
|
||||
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<Int, DownloadActionType> ->
|
||||
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"
|
||||
|
||||
|
|
|
@ -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">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
|
@ -94,4 +96,41 @@
|
|||
</ImageView>
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
<FrameLayout
|
||||
android:visibility="gone"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:id="@+id/player_torrent_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:gravity="start"
|
||||
android:layout_marginTop="15dp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:id="@+id/video_torrent_progress"
|
||||
tools:text="78% at 18kb/s"
|
||||
>
|
||||
</TextView>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:gravity="start"
|
||||
android:layout_marginTop="0dp"
|
||||
android:textColor="@color/white"
|
||||
android:id="@+id/video_torrent_seeders"
|
||||
tools:text="17 seeders"
|
||||
app:layout_constraintTop_toBottomOf="@+id/video_title">
|
||||
</TextView>
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -154,6 +154,7 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/result_poster_holder"
|
||||
app:cardCornerRadius="@dimen/roundedImageRadius"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="140dp">
|
||||
|
@ -275,6 +276,7 @@
|
|||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/result_plot_header"
|
||||
android:text="@string/result_plot"
|
||||
android:textSize="17sp"
|
||||
android:layout_marginTop="10dp"
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<string name="type_none">None</string>
|
||||
|
||||
<string name="play_movie_button">Play Movie</string>
|
||||
<string name="play_torrent_button">Stream Torrent</string>
|
||||
<string name="pick_source">Sources</string>
|
||||
<string name="pick_subtitle">Subtitles</string>
|
||||
<string name="reload_error">Retry connection…</string>
|
||||
|
@ -99,4 +100,7 @@
|
|||
|
||||
<string name="vpn_might_be_needed">A VPN might be needed for this provider to work correctly</string>
|
||||
<string name="vpn_torrent">This providers is a torrent, a VPN is recommended</string>
|
||||
<string name="torrent_plot">Description</string>
|
||||
<string name="normal_no_plot">No Plot Found</string>
|
||||
<string name="torrent_no_plot">No Description Found</string>
|
||||
</resources>
|
Loading…
Reference in a new issue