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
|
//run JS
|
||||||
implementation 'org.mozilla:rhino:1.7R4'
|
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.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?,
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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 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()
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue