Merge remote-tracking branch 'origin/master'

This commit is contained in:
LagradOst 2022-04-13 19:29:38 +02:00
commit e5515b575c
22 changed files with 1010 additions and 437 deletions

View file

@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import okhttp3.Headers
import okhttp3.Interceptor
import java.text.SimpleDateFormat
import java.util.*
@ -107,6 +108,7 @@ object APIHolder {
MonoschinosProvider(),
KawaiifuProvider(), // disabled due to cloudflare
//MultiAnimeProvider(),
NginxProvider(),
)
}
@ -307,6 +309,7 @@ const val PROVIDER_STATUS_DOWN = 0
data class ProvidersInfoJson(
@JsonProperty("name") var name: String,
@JsonProperty("url") var url: String,
@JsonProperty("credentials") var credentials: String? = null,
@JsonProperty("status") var status: Int,
)
@ -319,6 +322,7 @@ abstract class MainAPI {
fun overrideWithNewData(data: ProvidersInfoJson) {
this.name = data.name
this.mainUrl = data.url
this.storedCredentials = data.credentials
}
init {
@ -329,6 +333,7 @@ abstract class MainAPI {
open var name = "NONE"
open var mainUrl = "NONE"
open var storedCredentials: String? = null
//open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
@ -591,17 +596,22 @@ fun getQualityFromString(string: String?): SearchQuality? {
"cam" -> SearchQuality.Cam
"camrip" -> SearchQuality.CamRip
"hdcam" -> SearchQuality.HdCam
"hdtc" -> SearchQuality.HdCam
"hdts" -> SearchQuality.HdCam
"highquality" -> SearchQuality.HQ
"hq" -> SearchQuality.HQ
"highdefinition" -> SearchQuality.HD
"hdrip" -> SearchQuality.HD
"hd" -> SearchQuality.HD
"hdtv" -> SearchQuality.HD
"rip" -> SearchQuality.CamRip
"telecine" -> SearchQuality.Telecine
"tc" -> SearchQuality.Telecine
"telesync" -> SearchQuality.Telesync
"ts" -> SearchQuality.Telesync
"dvd" -> SearchQuality.DVD
"dvdrip" -> SearchQuality.DVD
"dvdscr" -> SearchQuality.DVD
"blueray" -> SearchQuality.BlueRay
"bluray" -> SearchQuality.BlueRay
"br" -> SearchQuality.BlueRay
@ -613,6 +623,7 @@ fun getQualityFromString(string: String?): SearchQuality? {
"wp" -> SearchQuality.WorkPrint
"workprint" -> SearchQuality.WorkPrint
"webrip" -> SearchQuality.WebRip
"webdl" -> SearchQuality.WebRip
"web" -> SearchQuality.WebRip
"hdr" -> SearchQuality.HDR
"sdr" -> SearchQuality.SDR

View file

@ -64,12 +64,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.movieproviders.NginxProvider
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.collections.HashMap
import kotlin.concurrent.thread
@ -360,6 +362,57 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
e.printStackTrace()
false
}
fun addNginxToJson(data: java.util.HashMap<String, ProvidersInfoJson>): java.util.HashMap<String, ProvidersInfoJson>? {
try {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val nginxUrl =
settingsManager.getString(getString(R.string.nginx_url_key), "nginx_url_key").toString()
val nginxCredentials =
settingsManager.getString(getString(R.string.nginx_credentials), "nginx_credentials")
.toString()
val StoredNginxProvider = NginxProvider()
if (nginxUrl == "nginx_url_key" || nginxUrl == "") { // if key is default value, or empty:
data[StoredNginxProvider.javaClass.simpleName] = ProvidersInfoJson(
url = nginxUrl,
name = StoredNginxProvider.name,
status = PROVIDER_STATUS_DOWN, // the provider will not be display
credentials = nginxCredentials
)
} else { // valid url
data[StoredNginxProvider.javaClass.simpleName] = ProvidersInfoJson(
url = nginxUrl,
name = StoredNginxProvider.name,
status = PROVIDER_STATUS_OK,
credentials = nginxCredentials
)
}
return data
} catch (e: Exception) {
logError(e)
return data
}
}
fun createNginxJson() : ProvidersInfoJson? { //java.util.HashMap<String, ProvidersInfoJson>
return try {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val nginxUrl = settingsManager.getString(getString(R.string.nginx_url_key), "nginx_url_key").toString()
val nginxCredentials = settingsManager.getString(getString(R.string.nginx_credentials), "nginx_credentials").toString()
if (nginxUrl == "nginx_url_key" || nginxUrl == "") { // if key is default value or empty:
null // don't overwrite anything
} else {
ProvidersInfoJson(
url = nginxUrl,
name = NginxProvider().name,
status = PROVIDER_STATUS_OK,
credentials = nginxCredentials
)
}
} catch (e: Exception) {
logError(e)
null
}
}
// this pulls the latest data so ppl don't have to update to simply change provider url
if (downloadFromGithub) {
@ -379,8 +432,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
tryParseJson<HashMap<String, ProvidersInfoJson>>(txt)
setKey(PROVIDER_STATUS_KEY, txt)
MainAPI.overrideData = newCache // update all new providers
for (api in apis) { // update current providers
newCache?.get(api.javaClass.simpleName)?.let { data ->
val newUpdatedCache = newCache?.let { addNginxToJson(it) ?: it }
for (api in apis) { // update current providers
newUpdatedCache?.get(api.javaClass.simpleName)?.let { data ->
api.overrideWithNewData(data)
}
}
@ -397,12 +453,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
newCache
}?.let { providersJsonMap ->
MainAPI.overrideData = providersJsonMap
val providersJsonMapUpdated = addNginxToJson(providersJsonMap)?: providersJsonMap // if return null, use unchanged one
val acceptableProviders =
providersJsonMap.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW }
providersJsonMapUpdated.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW }
.map { it.key }.toSet()
val restrictedApis =
if (hasBenene) providersJsonMap.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY }
if (hasBenene) providersJsonMapUpdated.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY }
.map { it.key }.toSet() else emptySet()
apis = allProviders.filter { api ->
@ -425,6 +482,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
} else {
apis = allProviders
try {
val nginxProviderName = NginxProvider().name
val nginxProviderIndex = apis.indexOf(APIHolder.getApiFromName(nginxProviderName))
val createdJsonProvider = createNginxJson()
if (createdJsonProvider != null) {
apis[nginxProviderIndex].overrideWithNewData(createdJsonProvider) // people will have access to it if they disable metadata check (they are not filtered)
}
} catch (e: Exception) {
logError(e)
}
}
loadThemes(this)
@ -619,4 +687,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
val output = src?.doMath()
println("MASTER OUTPUT = $output")*/
}
}
}

View file

@ -30,6 +30,14 @@ class VizcloudLive : WcoStream() {
override var mainUrl = "https://vizcloud.live"
}
class VizcloudInfo : WcoStream() {
override var mainUrl = "https://vizcloud.info"
}
class MwvnVizcloudInfo : WcoStream() {
override var mainUrl = "https://mwvn.vizcloud.info"
}
open class WcoStream : ExtractorApi() {
override var name = "VidStream" //Cause works for animekisa and wco
override var mainUrl = "https://vidstream.pro"
@ -103,8 +111,9 @@ open class WcoStream : ExtractorApi() {
}
}
if (mainUrl == "https://vidstream.pro" || mainUrl == "https://vidstreamz.online" || mainUrl == "https://vizcloud2.online"
|| mainUrl == "https://vizcloud.xyz" || mainUrl == "https://vizcloud.live") {
if (it.file.contains("m3u8")) {
|| mainUrl == "https://vizcloud.xyz" || mainUrl == "https://vizcloud.live" || mainUrl == "https://vizcloud.info"
|| mainUrl == "https://mwvn.vizcloud.info") {
if (it.file.contains("m3u8")) {
hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(it.file.replace("#.mp4",""), null,
headers = mapOf("Referer" to url)), true)
.forEach { stream ->

View file

@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
class EgyBestProvider : MainAPI() {
override val lang = "ar"
override var mainUrl = "https://egy.best"
override var mainUrl = "https://www.egy.best"
override var name = "EgyBest"
override val usesWebView = false
override val hasMainPage = true
@ -26,6 +26,7 @@ class EgyBestProvider : MainAPI() {
val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url)
val tvType = if (isMovie) TvType.Movie else TvType.TvSeries
title = if (year !== null) title else title.split(" (")[0].trim()
val quality = select("span.ribbon span").text().replace("-", "")
// If you need to differentiate use the url.
return MovieSearchResponse(
title,
@ -35,18 +36,22 @@ class EgyBestProvider : MainAPI() {
posterUrl,
year,
null,
quality = getQualityFromString(quality)
)
}
override suspend fun getMainPage(): HomePageResponse {
// url, title
val doc = app.get(mainUrl).document
val pages = doc.select("#mainLoad div.mbox").apmap {
val pages = arrayListOf<HomePageList>()
doc.select("#mainLoad div.mbox").apmap {
val name = it.select(".bdb.pda > strong").text()
val list = it.select(".movie").mapNotNull { element ->
element.toSearchResponse()
if (it.select(".movie").first().attr("href").contains("season-(.....)|ep-(.....)".toRegex())) return@apmap
val list = arrayListOf<SearchResponse>()
it.select(".movie").map { element ->
list.add(element.toSearchResponse()!!)
}
HomePageList(name, list)
pages.add(HomePageList(name, list))
}
return HomePageResponse(pages)
}
@ -72,7 +77,7 @@ class EgyBestProvider : MainAPI() {
val isMovie = Regex(".*/movie/.*|.*/masrahiya/.*").matches(url)
val posterUrl = doc.select("div.movie_img a img")?.attr("src")
val year = doc.select("div.movie_title h1 a")?.text()?.toIntOrNull()
val title = doc.select("div.movie_title h1 span[itemprop=\"name\"]").text()
val title = doc.select("div.movie_title h1 span").text()
val synopsis = doc.select("div.mbox").firstOrNull {
it.text().contains("القصة")

View file

@ -0,0 +1,268 @@
package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addRating
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.Qualities
import java.lang.Exception
class NginxProvider : MainAPI() {
override var name = "Nginx"
override val hasQuickSearch = false
override val hasMainPage = true
override val supportedTypes = setOf(TvType.AnimeMovie, TvType.TvSeries, TvType.Movie)
fun getAuthHeader(storedCredentials: String?): Map<String, String> {
if (storedCredentials == null) {
return mapOf(Pair("Authorization", "Basic ")) // no Authorization headers
}
val basicAuthToken = base64Encode(storedCredentials.toByteArray()) // will this be loaded when not using the provider ??? can increase load
return mapOf(Pair("Authorization", "Basic $basicAuthToken"))
}
override suspend fun load(url: String): LoadResponse {
val authHeader = getAuthHeader(storedCredentials) // call again because it isn't reloaded if in main class and storedCredentials loads after
// url can be tvshow.nfo for series or mediaRootUrl for movies
val mediaRootDocument = app.get(url, authHeader).document
val nfoUrl = url + mediaRootDocument.getElementsByAttributeValueContaining("href", ".nfo").attr("href") // metadata url file
val metadataDocument = app.get(nfoUrl, authHeader).document // get the metadata nfo file
val isMovie = !nfoUrl.contains("tvshow.nfo")
val title = metadataDocument.selectFirst("title").text()
val description = metadataDocument.selectFirst("plot").text()
if (isMovie) {
val poster = metadataDocument.selectFirst("thumb").text()
val trailer = metadataDocument.select("trailer")?.mapNotNull {
it?.text()?.replace(
"plugin://plugin.video.youtube/play/?video_id=",
"https://www.youtube.com/watch?v="
)
}
val partialUrl = mediaRootDocument.getElementsByAttributeValueContaining("href", ".nfo").attr("href").replace(".nfo", ".")
val date = metadataDocument.selectFirst("year")?.text()?.toIntOrNull()
val ratingAverage = metadataDocument.selectFirst("value")?.text()?.toIntOrNull()
val tagsList = metadataDocument.select("genre")
?.mapNotNull { // all the tags like action, thriller ...
it?.text()
}
val dataList = mediaRootDocument.getElementsByAttributeValueContaining( // list of all urls of the webpage
"href",
partialUrl
)
val data = url + dataList.firstNotNullOf { item -> item.takeIf { (!it.attr("href").contains(".nfo") && !it.attr("href").contains(".jpg"))} }.attr("href").toString() // exclude poster and nfo (metadata) file
return MovieLoadResponse(
title,
data,
this.name,
TvType.Movie,
data,
poster,
date,
description,
ratingAverage,
tagsList,
null,
trailer,
null,
null,
)
} else // a tv serie
{
val list = ArrayList<Pair<Int, String>>()
val mediaRootUrl = url.replace("tvshow.nfo", "")
val posterUrl = mediaRootUrl + "poster.jpg"
val mediaRootDocument = app.get(mediaRootUrl, authHeader).document
val seasons =
mediaRootDocument.getElementsByAttributeValueContaining("href", "Season%20")
val tagsList = metadataDocument.select("genre")
?.mapNotNull { // all the tags like action, thriller ...; unused variable
it?.text()
}
//val actorsList = document.select("actor")
// ?.mapNotNull { // all the tags like action, thriller ...; unused variable
// it?.text()
// }
seasons.forEach { element ->
val season =
element.attr("href")?.replace("Season%20", "")?.replace("/", "")?.toIntOrNull()
val href = mediaRootUrl + element.attr("href")
if (season != null && season > 0 && href.isNotBlank()) {
list.add(Pair(season, href))
}
}
if (list.isEmpty()) throw ErrorLoadingException("No Seasons Found")
val episodeList = ArrayList<Episode>()
list.apmap { (seasonInt, seasonString) ->
val seasonDocument = app.get(seasonString, authHeader).document
val episodes = seasonDocument.getElementsByAttributeValueContaining(
"href",
".nfo"
) // get metadata
episodes.forEach { episode ->
val nfoDocument = app.get(seasonString + episode.attr("href"), authHeader).document // get episode metadata file
val epNum = nfoDocument.selectFirst("episode")?.text()?.toIntOrNull()
val poster =
seasonString + episode.attr("href").replace(".nfo", "-thumb.jpg")
val name = nfoDocument.selectFirst("title").text()
// val seasonInt = nfoDocument.selectFirst("season").text().toIntOrNull()
val date = nfoDocument.selectFirst("aired")?.text()
val plot = nfoDocument.selectFirst("plot")?.text()
val dataList = seasonDocument.getElementsByAttributeValueContaining(
"href",
episode.attr("href").replace(".nfo", "")
)
val data = seasonString + dataList.firstNotNullOf { item -> item.takeIf { (!it.attr("href").contains(".nfo") && !it.attr("href").contains(".jpg"))} }.attr("href").toString() // exclude poster and nfo (metadata) file
episodeList.add(
newEpisode(data) {
this.name = name
this.season = seasonInt
this.episode = epNum
this.posterUrl = poster
addDate(date)
this.description = plot
}
)
}
}
return newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodeList) {
this.name = title
this.url = url
this.posterUrl = posterUrl
this.episodes = episodeList
this.plot = description
this.tags = tagsList
}
}
}
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
// loadExtractor(data, null) { callback(it.copy(headers=authHeader)) }
val authHeader = getAuthHeader(storedCredentials) // call again because it isn't reloaded if in main class and storedCredentials loads after
callback.invoke (
ExtractorLink(
name,
name,
data,
data, // referer not needed
Qualities.Unknown.value,
false,
authHeader,
)
)
return true
}
override suspend fun getMainPage(): HomePageResponse? {
val authHeader = getAuthHeader(storedCredentials) // call again because it isn't reloaded if in main class and storedCredentials loads after
if (mainUrl == "NONE"){
throw ErrorLoadingException("No nginx url specified in the settings: Nginx Settigns > Nginx server url, try again in a few seconds")
}
val document = app.get(mainUrl, authHeader).document
val categories = document.select("a")
val returnList = categories.mapNotNull {
val categoryPath = mainUrl + it.attr("href") ?: return@mapNotNull null // get the url of the category; like http://192.168.1.10/media/Movies/
val categoryTitle = it.text() // get the category title like Movies or Series
if (categoryTitle != "../" && categoryTitle != "Music/") { // exclude parent dir and Music dir
val categoryDocument = app.get(categoryPath, authHeader).document // queries the page http://192.168.1.10/media/Movies/
val contentLinks = categoryDocument.select("a")
val currentList = contentLinks.mapNotNull { head ->
if (head.attr("href") != "../") {
try {
val mediaRootUrl =
categoryPath + head.attr("href")// like http://192.168.1.10/media/Series/Chernobyl/
val mediaDocument = app.get(mediaRootUrl, authHeader).document
val nfoFilename = mediaDocument.getElementsByAttributeValueContaining(
"href",
".nfo"
)[0].attr("href")
val isMovieType = nfoFilename != "tvshow.nfo"
val nfoPath =
mediaRootUrl + nfoFilename // must exist or will raise errors, only the first one is taken
val nfoContent =
app.get(nfoPath, authHeader).document // all the metadata
if (isMovieType) {
val movieName = nfoContent.select("title").text()
val posterUrl = mediaRootUrl + "poster.jpg"
return@mapNotNull MovieSearchResponse(
movieName,
mediaRootUrl,
this.name,
TvType.Movie,
posterUrl,
null,
)
} else { // tv serie
val serieName = nfoContent.select("title").text()
val posterUrl = mediaRootUrl + "poster.jpg"
TvSeriesSearchResponse(
serieName,
nfoPath,
this.name,
TvType.TvSeries,
posterUrl,
null,
null,
)
}
} catch (e: Exception) { // can cause issues invisible errors
null
//logError(e) // not working because it changes the return type of currentList to Any
}
} else null
}
if (currentList.isNotEmpty() && categoryTitle != "../") { // exclude upper dir
HomePageList(categoryTitle, currentList)
} else null
} else null // the path is ../ which is parent directory
}
// if (returnList.isEmpty()) return null // maybe doing nothing idk
return HomePageResponse(returnList)
}
}

View file

@ -663,7 +663,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("name") val name: String,
@JsonProperty("location") val location: String,
@JsonProperty("joined_at") val joined_at: String,
@JsonProperty("picture") val picture: String,
@JsonProperty("picture") val picture: String?,
)
data class MalMainPicture(
@ -694,4 +694,4 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
val id: Int,
val name: String,
)
}
}

View file

@ -545,10 +545,14 @@ class GeneratorPlayer : FullScreenPlayer() {
tvType = meta.tvType
}
}
player_episode_filler_holder?.isVisible = isFiller ?: false
player_video_title?.text = if (headerName != null) {
//Get limit of characters on Video Title
var limitTitle = 0
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
limitTitle = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0)
}
//Generate video title
var playerVideoTitle = if (headerName != null) {
(headerName +
if (tvType.isEpisodeBased() && episode != null)
if (season == null)
@ -559,6 +563,15 @@ class GeneratorPlayer : FullScreenPlayer() {
} else {
""
}
//Truncate video title if it exceeds limit
val differenceInLength = playerVideoTitle.length - limitTitle
val margin = 3 //If the difference is smaller than or equal to this value, ignore it
if (limitTitle > 0 && differenceInLength > margin) {
playerVideoTitle = playerVideoTitle.substring(0, limitTitle-1) + "..."
}
player_episode_filler_holder?.isVisible = isFiller ?: false
player_video_title?.text = playerVideoTitle
}
@SuppressLint("SetTextI18n")

View file

@ -47,6 +47,7 @@ import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showNginxTextInputDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
@ -485,6 +486,32 @@ class SettingsFragment : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
getPref(R.string.nginx_url_key)?.setOnPreferenceClickListener {
activity?.showNginxTextInputDialog(
settingsManager.getString(getString(R.string.nginx_url_pref), "Nginx server url").toString(),
settingsManager.getString(getString(R.string.nginx_url_key), "").toString(), // key: the actual you use rn
android.text.InputType.TYPE_TEXT_VARIATION_URI, // uri
{}) {
settingsManager.edit()
.putString(getString(R.string.nginx_url_key), it).apply() // change the stored url in nginx_url_key to it
}
return@setOnPreferenceClickListener true
}
getPref(R.string.nginx_credentials)?.setOnPreferenceClickListener {
activity?.showNginxTextInputDialog(
settingsManager.getString(getString(R.string.nginx_credentials_title), "Nginx Credentials").toString(),
settingsManager.getString(getString(R.string.nginx_credentials), "").toString(), // key: the actual you use rn
android.text.InputType.TYPE_TEXT_VARIATION_URI,
{}) {
settingsManager.edit()
.putString(getString(R.string.nginx_credentials), it).apply() // change the stored url in nginx_url_key to it
}
return@setOnPreferenceClickListener true
}
getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.media_type_pref)
val prefValues = resources.getIntArray(R.array.media_type_pref_values)
@ -678,6 +705,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
getPref(R.string.prefer_limit_title_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.limit_title_pref_names)
val prefValues = resources.getIntArray(R.array.limit_title_pref_values)
val current = settingsManager.getInt(getString(R.string.prefer_limit_title_key), 0)
activity?.showBottomDialog(
prefNames.toList(),
prefValues.indexOf(current),
getString(R.string.limit_title),
true,
{}) {
settingsManager.edit()
.putInt(getString(R.string.prefer_limit_title_key), prefValues[it])
.apply()
}
return@setOnPreferenceClickListener true
}
getPref(R.string.dns_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.dns_pref)
val prefValues = resources.getIntArray(R.array.dns_pref_values)

View file

@ -100,6 +100,8 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
VizcloudOnline(),
VizcloudXyz(),
VizcloudLive(),
VizcloudInfo(),
MwvnVizcloudInfo(),
Mp4Upload(),
StreamTape(),
MixDrop(),

View file

@ -144,6 +144,46 @@ object SingleSelectionHelper {
}
}
private fun Activity.showInputDialog(
dialog: Dialog,
value: String,
name: String,
textInputType: Int?,
callback: (String) -> Unit,
dismissCallback: () -> Unit
) {
val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
val textView = dialog.findViewById<TextView>(R.id.text1)!!
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!!
applyHolder.isVisible = true
textView.text = name
if (textInputType != null) {
inputView.inputType = textInputType // 16 for website url input type
}
inputView.setText(value, TextView.BufferType.EDITABLE)
applyButton.setOnClickListener {
callback.invoke(inputView.text.toString()) // try to save the setting, using callback
dialog.dismissSafe(this)
}
cancelButton.setOnClickListener { // just dismiss
dialog.dismissSafe(this)
}
dialog.setOnDismissListener {
dismissCallback.invoke()
}
}
fun Activity.showMultiDialog(
items: List<String>,
selectedIndex: List<Int>,
@ -192,7 +232,7 @@ object SingleSelectionHelper {
selectedIndex: Int,
name: String,
showApply: Boolean,
dismissCallback: () -> Unit,
dismissCallback: () -> Unit,
callback: (Int) -> Unit,
) {
val builder =
@ -211,4 +251,25 @@ object SingleSelectionHelper {
dismissCallback
)
}
}
fun Activity.showNginxTextInputDialog(
name: String,
value: String,
textInputType: Int?,
dismissCallback: () -> Unit,
callback: (String) -> Unit,
) {
val builder = BottomSheetDialog(this) // probably the stuff at the bottom
builder.setContentView(R.layout.bottom_input_dialog) // input layout
builder.show()
showInputDialog(
builder,
value,
name,
textInputType, // type is a uri
callback,
dismissCallback
)
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/white">
<path
android:fillColor="@android:color/white"
android:pathData="M5,17v2h14v-2L5,17zM9.5,12.8h5l0.9,2.2h2.1L12.75,4h-1.5L6.5,15h2.1l0.9,-2.2zM12,5.98L13.87,11h-3.74L12,5.98z"/>
</vector>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:nextFocusDown="@id/nginx_text_input"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Test" />
<EditText
android:id="@+id/nginx_text_input"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusLeft="@id/apply_btt"
android:layout_marginBottom="60dp"
android:layout_marginHorizontal="10dp"
android:paddingTop="10dp"
android:requiresFadingEdge="vertical"
tools:text="nginx.com"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:autofillHints="Autofill Hint"
android:inputType="text"
tools:ignore="LabelFor" />
<LinearLayout
android:id="@+id/apply_btt_holder"
android:orientation="horizontal"
android:layout_gravity="bottom"
android:gravity="bottom|end"
android:layout_marginTop="-60dp"
android:layout_width="match_parent"
android:layout_height="60dp">
<com.google.android.material.button.MaterialButton
style="@style/WhiteButton"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_apply"
android:id="@+id/apply_btt"
android:layout_width="wrap_content" />
<com.google.android.material.button.MaterialButton
style="@style/BlackButton"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_cancel"
android:id="@+id/cancel_btt"
android:layout_width="wrap_content" />
</LinearLayout>
</LinearLayout>

View file

@ -32,7 +32,7 @@
</array>
<array name="dns_pref">
<item>None</item>
<item>@string/none</item>
<item>Google</item>
<item>Cloudflare</item>
<!-- <item>OpenDns</item>-->
@ -59,6 +59,21 @@
<item>3</item>
</array>
<array name="limit_title_pref_names">
<item>@string/none</item>
<item>16 characters</item>
<item>32 characters</item>
<item>64 characters</item>
<item>128 characters</item>
</array>
<array name="limit_title_pref_values">
<item>0</item>
<item>16</item>
<item>32</item>
<item>64</item>
<item>128</item>
</array>
<array name="video_buffer_length_names">
<item>@string/automatic</item>
<item>1min</item>

View file

@ -13,6 +13,7 @@
<string name="subtitle_settings_key" translatable="false">subtitle_settings_key</string>
<string name="subtitle_settings_chromecast_key" translatable="false">subtitle_settings_chromecast_key</string>
<string name="quality_pref_key" translatable="false">quality_pref_key</string>
<string name="prefer_limit_title_key" translatable="false">prefer_limit_title_key</string>
<string name="video_buffer_size_key" translatable="false">video_buffer_size_key</string>
<string name="video_buffer_length_key" translatable="false">video_buffer_length_key</string>
<string name="video_buffer_clear_key" translatable="false">video_buffer_clear_key</string>
@ -31,6 +32,9 @@
<string name="provider_lang_key" translatable="false">provider_lang_key</string>
<string name="dns_key" translatable="false">dns_key</string>
<string name="download_path_key" translatable="false">download_path_key</string>
<string name="nginx_url_key" translatable="false">nginx_url_key</string>
<string name="nginx_credentials" translatable="false">nginx_credentials</string>
<string name="nginx_info" translatable="false">nginx_info</string>
<string name="app_name_download_path" translatable="false">Cloudstream</string>
<string name="app_layout_key" translatable="false">app_layout_key</string>
<string name="primary_color_key" translatable="false">primary_color_key</string>
@ -227,6 +231,12 @@
<string name="backup_failed_error_format">Error backing up %s</string>
<string name="search">Search</string>
<string name="nginx_category">Nginx Settings</string>
<string name="nginx_credentials_title">Nginx Credential</string>
<string name="nginx_credentials_summary">You have to use the following format mycoolusername:mysecurepassword123</string>
<string name="nginx_info_title">What is Nginx ?</string>
<string name="nginx_info_summary">Nginx is a software that can be used to display files from a server that you own. Click to see a Nginx setup guide</string>
<string name="settings_info">Info</string>
<string name="advanced_search">Advanced Search</string>
<string name="advanced_search_des">Gives you the search results separated by provider</string>
@ -339,6 +349,7 @@
<string name="dont_show_again">Don\'t show again</string>
<string name="update">Update</string>
<string name="watch_quality_pref">Preferred watch quality</string>
<string name="limit_title">Limit title characters on player</string>
<string name="video_buffer_size_settings">Video buffer size</string>
<string name="video_buffer_length_settings">Video buffer length</string>
<string name="video_buffer_disk_settings">Video cache on disk</string>
@ -352,6 +363,8 @@
<string name="download_path_pref">Download path</string>
<string name="nginx_url_pref">Nginx server url</string>
<string name="display_subbed_dubbed_settings">Display Dubbed/Subbed Anime</string>
<string name="resize_fit">Fit to screen</string>

View file

@ -20,6 +20,10 @@
android:key="@string/quality_pref_key"
android:title="@string/watch_quality_pref"
android:icon="@drawable/ic_baseline_hd_24" />
<Preference
android:key="@string/prefer_limit_title_key"
android:title="@string/limit_title"
android:icon="@drawable/ic_baseline_text_format_24" />
<SwitchPreference
android:icon="@drawable/ic_baseline_picture_in_picture_alt_24"
@ -162,6 +166,31 @@
app:defaultValue="true" />
</PreferenceCategory>
<PreferenceCategory
android:key="@string/nginx_category"
android:title="@string/nginx_category"
app:isPreferenceVisible="true">
<Preference
android:key="@string/nginx_url_key"
android:title="@string/nginx_url_pref"
android:icon="@drawable/ic_baseline_play_arrow_24" />
<Preference
android:key="@string/nginx_credentials"
android:title="@string/nginx_credentials_title"
android:icon="@drawable/video_locked"
android:summary="@string/nginx_credentials_summary"/>
<Preference
android:key="@string/nginx_info"
android:title="@string/nginx_info_title"
android:icon="@drawable/ic_baseline_play_arrow_24"
android:summary="@string/nginx_info_summary" >
<intent
android:action="android.intent.action.VIEW"
android:data="https://www.sarlays.com/use-nginx-with-cloudstream/" />
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="info"
android:title="@string/settings_info"