This commit is contained in:
LagradOst 2021-08-30 19:11:04 +02:00
parent 613c26ec63
commit 3b6b3dedc0
12 changed files with 550 additions and 21 deletions

View file

@ -128,4 +128,7 @@ dependencies {
//run JS //run JS
implementation 'org.mozilla:rhino:1.7R4' implementation 'org.mozilla:rhino:1.7R4'
// TorrentStream
implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
} }

View file

@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.animeproviders.* import com.lagradost.cloudstream3.animeproviders.*
import com.lagradost.cloudstream3.movieproviders.* import com.lagradost.cloudstream3.movieproviders.*
import com.lagradost.cloudstream3.torrentproviders.*
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import java.util.* import java.util.*
@ -41,6 +42,7 @@ object APIHolder {
WatchCartoonOnlineProvider(), WatchCartoonOnlineProvider(),
AllMoviesForYouProvider(), AllMoviesForYouProvider(),
AsiaFlixProvider(), AsiaFlixProvider(),
//NyaaProvider(),
) )
fun getApiFromName(apiName: String?): MainAPI { fun getApiFromName(apiName: String?): MainAPI {
@ -116,7 +118,7 @@ abstract class MainAPI {
TvType.TvSeries, TvType.TvSeries,
TvType.Cartoon, TvType.Cartoon,
TvType.Anime, TvType.Anime,
TvType.ONA TvType.ONA,
) )
open val vpnStatus = VPNStatus.None open val vpnStatus = VPNStatus.None
@ -222,11 +224,12 @@ enum class TvType {
Cartoon, Cartoon,
Anime, Anime,
ONA, ONA,
Torrent,
} }
// IN CASE OF FUTURE ANIME MOVIE OR SMTH // IN CASE OF FUTURE ANIME MOVIE OR SMTH
fun TvType.isMovieType(): Boolean { 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) data class SubtitleFile(val lang: String, val url: String)
@ -265,6 +268,16 @@ data class AnimeSearchResponse(
override val id: Int? = null, override val id: Int? = null,
) : SearchResponse ) : 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( data class MovieSearchResponse(
override val name: String, override val name: String,
override val url: String, override val url: String,
@ -321,6 +334,22 @@ data class AnimeEpisode(
val descript: String? = null, 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( data class AnimeLoadResponse(
val engName: String?, val engName: String?,
val japName: String?, val japName: String?,

View file

@ -8,6 +8,7 @@ import android.content.pm.PackageManager
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.view.* import android.view.*
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@ -16,6 +17,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment 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.google.android.gms.cast.framework.CastButtonFactory
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
@ -351,5 +357,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
runAutoUpdate() runAutoUpdate()
} }
} }
} }

View file

@ -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
}
}

View file

@ -29,6 +29,7 @@ import android.widget.*
import android.widget.Toast.LENGTH_SHORT import android.widget.Toast.LENGTH_SHORT
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager 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.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue 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.*
import com.google.android.exoplayer2.C.TIME_UNSET import com.google.android.exoplayer2.C.TIME_UNSET
import com.google.android.exoplayer2.database.ExoDatabaseProvider import com.google.android.exoplayer2.database.ExoDatabaseProvider
@ -163,6 +169,73 @@ data class UriData(
// YE, I KNOW, THIS COULD BE HANDLED A LOT BETTER // YE, I KNOW, THIS COULD BE HANDLED A LOT BETTER
class PlayerFragment : Fragment() { 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 lateinit var subStyle: SaveCaptionStyle
private var subView: SubtitleView? = null private var subView: SubtitleView? = null
@ -312,7 +385,8 @@ class PlayerFragment : Fragment() {
shadow_overlay?.startAnimation(fadeAnimation) shadow_overlay?.startAnimation(fadeAnimation)
} }
video_holder?.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) //video_lock_holder?.startAnimation(fadeAnimation)
} }
@ -1199,7 +1273,7 @@ class PlayerFragment : Fragment() {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
video_go_back_holder.setOnClickListener { 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(isInPlayer = true, isInExpandedView = false, isInResults = false)
activity?.popCurrentPage() activity?.popCurrentPage()
} }
@ -1460,6 +1534,7 @@ class PlayerFragment : Fragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
torrentStream?.currentTorrent?.resume()
onAudioFocusEvent += ::handlePauseEvent onAudioFocusEvent += ::handlePauseEvent
activity?.hideSystemUI() activity?.hideSystemUI()
@ -1482,6 +1557,9 @@ class PlayerFragment : Fragment() {
savePos() savePos()
SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged
torrentStream?.stopStream()
torrentStream = null
super.onDestroy() super.onDestroy()
canEnterPipMode = false canEnterPipMode = false
@ -1497,6 +1575,7 @@ class PlayerFragment : Fragment() {
override fun onPause() { override fun onPause() {
savePos() savePos()
super.onPause() super.onPause()
torrentStream?.currentTorrent?.pause()
if (Util.SDK_INT <= 23) { if (Util.SDK_INT <= 23) {
if (player_view != null) player_view.onPause() if (player_view != null) player_view.onPause()
releasePlayer() releasePlayer()
@ -1567,8 +1646,18 @@ class PlayerFragment : Fragment() {
var preferredSubtitles: String = "" var preferredSubtitles: String = ""
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null) { fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null, trueUri: Uri? = null) {
if (currentUrl == null && uri == null) return 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 isCurrentlyPlaying = true
hasUsedFirstRender = false hasUsedFirstRender = false
@ -1605,14 +1694,14 @@ class PlayerFragment : Fragment() {
if (currentUrl != null) { if (currentUrl != null) {
mediaItemBuilder.setUri(currentUrl.url) mediaItemBuilder.setUri(currentUrl.url)
} else if (uri != null) { } else if (trueUri != null || uri != null) {
val uriPrimary = Uri.parse(uri) val uriPrimary = trueUri ?: Uri.parse(uri)
if (uriPrimary.scheme == "content") { if (uriPrimary.scheme == "content") {
mediaItemBuilder.setUri(uriPrimary) mediaItemBuilder.setUri(uriPrimary)
// video_title?.text = uriPrimary.toString() // video_title?.text = uriPrimary.toString()
} else { } else {
//mediaItemBuilder.setUri(Uri.parse(currentUrl.url)) //mediaItemBuilder.setUri(Uri.parse(currentUrl.url))
val realUri = getVideoContentUri(requireContext(), uri) val realUri = trueUri ?: getVideoContentUri(requireContext(), uri ?: uriPrimary.path ?: "")
// video_title?.text = uri.toString() // video_title?.text = uri.toString()
mediaItemBuilder.setUri(realUri) mediaItemBuilder.setUri(realUri)
} }
@ -1728,7 +1817,10 @@ class PlayerFragment : Fragment() {
var epSeason: Int? = null var epSeason: Int? = null
var isEpisodeBased = true var isEpisodeBased = true
if (isDownloadedFile) { if (isTorrent) {
hName = "Torrent Stream"
isEpisodeBased = false
} else if (isDownloadedFile) {
hName = uriData.name hName = uriData.name
epEpisode = uriData.episode epEpisode = uriData.episode
epSeason = uriData.season epSeason = uriData.season
@ -1786,7 +1878,9 @@ class PlayerFragment : Fragment() {
video_title_rez?.text = video_title_rez?.text =
if (height == null || width == null) currentUrl?.name 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 if (!hasUsedFirstRender) { // DON'T WANT TO SET MULTIPLE MESSAGES
changeSkip() changeSkip()

View file

@ -441,6 +441,7 @@ class ResultFragment : Fragment() {
TvType.TvSeries -> "TVSeries/$titleName" TvType.TvSeries -> "TVSeries/$titleName"
TvType.ONA -> "ONA" TvType.ONA -> "ONA"
TvType.Cartoon -> "Cartoons/$titleName" TvType.Cartoon -> "Cartoons/$titleName"
TvType.Torrent -> "Torrent"
else -> null else -> null
} }
@ -457,7 +458,7 @@ class ResultFragment : Fragment() {
url ?: return@let, url ?: return@let,
currentType ?: return@let, currentType ?: return@let,
currentHeaderName ?: return@let, currentHeaderName ?: return@let,
currentPoster ?: return@let, currentPoster,
currentId ?: return@let, currentId ?: return@let,
System.currentTimeMillis(), System.currentTimeMillis(),
) )
@ -863,7 +864,7 @@ class ResultFragment : Fragment() {
i.data = Uri.parse(d.url) i.data = Uri.parse(d.url)
try { try {
startActivity(i) startActivity(i)
} catch (e : Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
@ -914,10 +915,13 @@ class ResultFragment : Fragment() {
result_metadata.visibility = GONE 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) { if (d.plot != null) {
var syno = d.plot!! var syno = d.plot!!
if (syno.length > MAX_SYNO_LENGH) { if (syno.length > MAX_SYNO_LENGH) {
@ -930,7 +934,8 @@ class ResultFragment : Fragment() {
} }
result_descript.text = syno result_descript.text = syno
} else { } 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() result_tag.removeAllViews()
@ -1065,7 +1070,7 @@ class ResultFragment : Fragment() {
i.data = Uri.parse(tempUrl) i.data = Uri.parse(tempUrl)
try { try {
startActivity(i) startActivity(i)
} catch (e : Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }

View file

@ -324,6 +324,25 @@ class ResultViewModel : ViewModel() {
), -1 ), -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 -> { else -> {

View file

@ -130,7 +130,8 @@ class SearchFragment : Fragment() {
Pair("Movies", listOf(TvType.Movie)), Pair("Movies", listOf(TvType.Movie)),
Pair("TvSeries", listOf(TvType.TvSeries)), Pair("TvSeries", listOf(TvType.TvSeries)),
Pair("Cartoons", listOf(TvType.Cartoon)), 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) val arrayAdapter2 = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.utils package com.lagradost.cloudstream3.utils
import android.annotation.SuppressLint
import android.app.* import android.app.*
import android.content.* import android.content.*
import android.graphics.Bitmap import android.graphics.Bitmap
@ -14,6 +15,11 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import com.bumptech.glide.Glide 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
import com.lagradost.cloudstream3.MainActivity.Companion.showToast import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
@ -488,6 +494,257 @@ object VideoDownloadManager {
val bytesTotal: Long, 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( fun downloadThing(
context: Context, context: Context,
link: IDownloadableMinimum, link: IDownloadableMinimum,
@ -498,6 +755,10 @@ object VideoDownloadManager {
parentId: Int?, parentId: Int?,
createNotificationCallback: (CreateNotificationMetadata) -> Unit createNotificationCallback: (CreateNotificationMetadata) -> Unit
): Int { ): 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 relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar)
val displayName = "$name.$extension" val displayName = "$name.$extension"

View file

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" 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:orientation="horizontal"
android:keepScreenOn="true" android:keepScreenOn="true"
app:backgroundTint="@android:color/black" app:backgroundTint="@android:color/black"
@ -60,6 +60,8 @@
android:layout_height="45dp"> android:layout_height="45dp">
</com.google.android.material.button.MaterialButton> </com.google.android.material.button.MaterialButton>
<ProgressBar <ProgressBar
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
@ -94,4 +96,41 @@
</ImageView> </ImageView>
</FrameLayout> </FrameLayout>
</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> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -154,6 +154,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/result_poster_holder"
app:cardCornerRadius="@dimen/roundedImageRadius" app:cardCornerRadius="@dimen/roundedImageRadius"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="140dp"> android:layout_height="140dp">
@ -275,6 +276,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/result_plot_header"
android:text="@string/result_plot" android:text="@string/result_plot"
android:textSize="17sp" android:textSize="17sp"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"

View file

@ -34,6 +34,7 @@
<string name="type_none">None</string> <string name="type_none">None</string>
<string name="play_movie_button">Play Movie</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_source">Sources</string>
<string name="pick_subtitle">Subtitles</string> <string name="pick_subtitle">Subtitles</string>
<string name="reload_error">Retry connection…</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_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="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> </resources>