diff --git a/.github/downloads.jpg b/.github/downloads.jpg new file mode 100644 index 00000000..ca14a664 Binary files /dev/null and b/.github/downloads.jpg differ diff --git a/.github/home.jpg b/.github/home.jpg new file mode 100644 index 00000000..72370d3c Binary files /dev/null and b/.github/home.jpg differ diff --git a/.github/locales.py b/.github/locales.py index 7d6d6b90..e95ec902 100644 --- a/.github/locales.py +++ b/.github/locales.py @@ -1,14 +1,13 @@ import re import glob import requests -import lxml.etree as ET # builtin library doesn't preserve comments SETTINGS_PATH = "app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt" START_MARKER = "/* begin language list */" END_MARKER = "/* end language list */" XML_NAME = "app/src/main/res/values-" -ISO_MAP_URL = "https://raw.githubusercontent.com/haliaeetus/iso-639/master/data/iso_639-1.min.json" +ISO_MAP_URL = "https://gist.githubusercontent.com/Josantonius/b455e315bc7f790d14b136d61d9ae469/raw" INDENT = " "*4 iso_map = requests.get(ISO_MAP_URL, timeout=300).json() @@ -28,8 +27,7 @@ for lang in re.finditer(r'Triple\("(.*)", "(.*)", "(.*)"\)', rest): for folder in glob.glob(f"{XML_NAME}*"): iso = folder[len(XML_NAME):] if iso not in languages.keys(): - entry = iso_map.get(iso.lower(),{'nativeName':iso}) - languages[iso] = ("", entry['nativeName'].split(',')[0]) + languages[iso] = ("", iso_map.get(iso.lower(),iso)) # Create triples triples = [] @@ -46,18 +44,4 @@ open(SETTINGS_PATH, "w+",encoding='utf-8').write( "\n" + END_MARKER + after_src -) - -# Go through each values.xml file and fix escaped \@string -for file in glob.glob(f"{XML_NAME}*/strings.xml"): - try: - tree = ET.parse(file) - for child in tree.getroot(): - if child.text.startswith("\\@string/"): - print(f"[{file}] fixing {child.attrib['name']}") - child.text = child.text.replace("\\@string/", "@string/") - with open(file, 'wb') as fp: - fp.write(b'\n') - tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False) - except ET.ParseError as ex: - print(f"[{file}] {ex}") +) \ No newline at end of file diff --git a/.github/player.jpg b/.github/player.jpg new file mode 100644 index 00000000..f6959cf3 Binary files /dev/null and b/.github/player.jpg differ diff --git a/.github/results.jpg b/.github/results.jpg new file mode 100644 index 00000000..4dbc9b8d Binary files /dev/null and b/.github/results.jpg differ diff --git a/.github/search.jpg b/.github/search.jpg new file mode 100644 index 00000000..784bec89 Binary files /dev/null and b/.github/search.jpg differ diff --git a/.github/workflows/issue_action.yml b/.github/workflows/issue_action.yml index 108cec82..28b737b3 100644 --- a/.github/workflows/issue_action.yml +++ b/.github/workflows/issue_action.yml @@ -15,7 +15,6 @@ jobs: app_id: ${{ secrets.GH_APP_ID }} private_key: ${{ secrets.GH_APP_KEY }} - name: Similarity analysis - id: similarity uses: actions-cool/issues-similarity-analysis@v1 with: token: ${{ steps.generate_token.outputs.token }} @@ -25,18 +24,6 @@ jobs: ### Your issue looks similar to these issues: Please close if duplicate. comment-body: '${index}. ${similarity} #${number}' - - name: Label if possible duplicate - if: steps.similarity.outputs.similar-issues-found =='true' - uses: actions/github-script@v6 - with: - github-token: ${{ steps.generate_token.outputs.token }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ["possible duplicate"] - }) - uses: actions/checkout@v2 - name: Automatically close issues that dont follow the issue template uses: lucasbento/auto-close-issues@v1.0.2 @@ -66,18 +53,6 @@ jobs: Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM). Found provider name: `${{ steps.provider_check.outputs.name }}` - - name: Label if mentions provider - if: steps.provider_check.outputs.name != 'none' - uses: actions/github-script@v6 - with: - github-token: ${{ steps.generate_token.outputs.token }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ["possible provider issue"] - }) - name: Add eyes reaction to all issues uses: actions-cool/emoji-helper@v1.0.0 with: diff --git a/.github/workflows/update_locales.yml b/.github/workflows/update_locales.yml index 628e9bc9..93cdca44 100644 --- a/.github/workflows/update_locales.yml +++ b/.github/workflows/update_locales.yml @@ -1,4 +1,4 @@ -name: Fix locale issues +name: Update locale lists on: workflow_dispatch: @@ -9,7 +9,7 @@ on: - master concurrency: - group: "locale" + group: "locale-list" cancel-in-progress: true jobs: @@ -26,9 +26,6 @@ jobs: - uses: actions/checkout@v2 with: token: ${{ steps.generate_token.outputs.token }} - - name: Install dependencies - run: | - pip3 install lxml - name: Edit files run: | python3 .github/locales.py @@ -38,5 +35,5 @@ jobs: git config --local user.name "recloudstream[bot]" git add . # "echo" returns true so the build succeeds, even if no changed files - git commit -m 'chore(locales): fix locale issues' || echo + git commit -m 'update list of locales' || echo git push diff --git a/README.md b/README.md index e3d033ba..3430d626 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,12 @@ + Download and stream movies, tv-shows and anime + Chromecast +### Screenshots: + + + + ### Supported languages: Translation status - + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0bd56fe7..808c0cc3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -47,8 +47,8 @@ android { minSdk = 21 targetSdk = 33 - versionCode = 57 - versionName = "4.0.0" + versionCode = 56 + versionName = "3.5.0" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") @@ -159,8 +159,6 @@ dependencies { implementation("com.google.android.exoplayer:extension-cast:2.18.2") implementation("com.google.android.exoplayer:extension-mediasession:2.18.2") implementation("com.google.android.exoplayer:extension-okhttp:2.18.2") - // Use the Jellyfin ffmpeg extension for easy ffmpeg audio decoding in exoplayer. Thank you Jellyfin <3 -// implementation("org.jellyfin.exoplayer:exoplayer-ffmpeg-extension:2.18.2+1") //implementation("com.google.android.exoplayer:extension-leanback:2.14.0") @@ -186,13 +184,13 @@ dependencies { //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0") // Downloading - implementation("androidx.work:work-runtime:2.8.0") - implementation("androidx.work:work-runtime-ktx:2.8.0") + implementation("androidx.work:work-runtime:2.7.1") + implementation("androidx.work:work-runtime-ktx:2.7.1") // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") // implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1") - implementation("com.github.Blatzar:NiceHttp:0.4.2") + implementation("com.github.Blatzar:NiceHttp:0.4.1") // To fix SSL fuckery on android 9 implementation("org.conscrypt:conscrypt-android:2.2.1") // Util to skip the URI file fuckery 🙏 @@ -255,4 +253,4 @@ tasks.withType().configureEach { } } } -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 92042d60..81753f6b 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -1,8 +1,9 @@ package com.lagradost.cloudstream3 import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.SubtitleHelper -import com.lagradost.cloudstream3.utils.TestingUtils import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Test @@ -15,11 +16,142 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { + //@Test + //fun useAppContext() { + // // Context of the app under test. + // val appContext = InstrumentationRegistry.getInstrumentation().targetContext + // assertEquals("com.lagradost.cloudstream3", appContext.packageName) + //} + private fun getAllProviders(): List { - println("Providers: ${APIHolder.allProviders.size}") return APIHolder.allProviders //.filter { !it.usesWebView } } + private suspend fun loadLinks(api: MainAPI, url: String?): Boolean { + Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) + if (url == null) return true + var linksLoaded = 0 + try { + val success = api.loadLinks(url, false, {}) { link -> + Assert.assertTrue( + "Api ${api.name} returns link with invalid Quality", + Qualities.values().map { it.value }.contains(link.quality) + ) + Assert.assertTrue( + "Api ${api.name} returns link with invalid url ${link.url}", + link.url.length > 4 + ) + linksLoaded++ + } + if (success) { + return linksLoaded > 0 + } + Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success) + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider has not implemented .loadLinks") + } + logError(e) + } + return true + } + + private suspend fun testSingleProviderApi(api: MainAPI): Boolean { + val searchQueries = listOf("over", "iron", "guy") + var correctResponses = 0 + var searchResult: List? = null + for (query in searchQueries) { + val response = try { + api.search(query) + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider has not implemented .search") + } + logError(e) + null + } + if (!response.isNullOrEmpty()) { + correctResponses++ + if (searchResult == null) { + searchResult = response + } + } + } + + if (correctResponses == 0 || searchResult == null) { + System.err.println("Api ${api.name} did not return any valid search responses") + return false + } + + try { + var validResults = false + for (result in searchResult) { + Assert.assertEquals( + "Invalid apiName on response on ${api.name}", + result.apiName, + api.name + ) + val load = api.load(result.url) ?: continue + Assert.assertEquals( + "Invalid apiName on load on ${api.name}", + load.apiName, + result.apiName + ) + Assert.assertTrue( + "Api ${api.name} on load does not contain any of the supportedTypes", + api.supportedTypes.contains(load.type) + ) + when (load) { + is AnimeLoadResponse -> { + val gotNoEpisodes = + load.episodes.keys.isEmpty() || load.episodes.keys.any { load.episodes[it].isNullOrEmpty() } + + if (gotNoEpisodes) { + println("Api ${api.name} got no episodes on ${load.url}") + continue + } + + val url = (load.episodes[load.episodes.keys.first()])?.first()?.data + validResults = loadLinks(api, url) + if (!validResults) continue + } + is MovieLoadResponse -> { + val gotNoEpisodes = load.dataUrl.isBlank() + if (gotNoEpisodes) { + println("Api ${api.name} got no movie on ${load.url}") + continue + } + + validResults = loadLinks(api, load.dataUrl) + if (!validResults) continue + } + is TvSeriesLoadResponse -> { + val gotNoEpisodes = load.episodes.isEmpty() + if (gotNoEpisodes) { + println("Api ${api.name} got no episodes on ${load.url}") + continue + } + + validResults = loadLinks(api, load.episodes.first().data) + if (!validResults) continue + } + } + break + } + if (!validResults) { + System.err.println("Api ${api.name} did not load on any") + } + + return validResults + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider has not implemented .load") + } + logError(e) + return false + } + } + @Test fun providersExist() { Assert.assertTrue(getAllProviders().isNotEmpty()) @@ -27,7 +159,6 @@ class ExampleInstrumentedTest { } @Test - @Throws(AssertionError::class) fun providerCorrectData() { val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 } Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty()) @@ -50,20 +181,67 @@ class ExampleInstrumentedTest { fun providerCorrectHomepage() { runBlocking { getAllProviders().amap { api -> - TestingUtils.testHomepage(api, ::println) + if (api.hasMainPage) { + try { + val f = api.mainPage.first() + val homepage = + api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages)) + when { + homepage == null -> { + System.err.println("Homepage provider ${api.name} did not correctly load homepage!") + } + homepage.items.isEmpty() -> { + System.err.println("Homepage provider ${api.name} does not contain any items!") + } + homepage.items.any { it.list.isEmpty() } -> { + System.err.println("Homepage provider ${api.name} does not have any items on result!") + } + } + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") + } + logError(e) + } + } } } println("Done providerCorrectHomepage") } +// @Test +// fun testSingleProvider() { +// testSingleProviderApi(ThenosProvider()) +// } + @Test - fun testAllProvidersCorrect() { + fun providerCorrect() { runBlocking { - TestingUtils.getDeferredProviderTests( - this, - getAllProviders(), - ::println - ) { _, _ -> } + val invalidProvider = ArrayList>() + val providers = getAllProviders() + providers.amap { api -> + try { + println("Trying $api") + if (testSingleProviderApi(api)) { + println("Success $api") + } else { + System.err.println("Error $api") + invalidProvider.add(Pair(api, null)) + } + } catch (e: Exception) { + logError(e) + invalidProvider.add(Pair(api, e)) + } + } + if (invalidProvider.isEmpty()) { + println("No Invalid providers! :D") + } else { + println("Invalid providers are: ") + for (provider in invalidProvider) { + println("${provider.first}") + } + } } + println("Done providerCorrect") } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 563c82f8..871c4f69 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,16 +98,6 @@ - - - - - - - - - - diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index c20786c3..73859021 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -15,12 +15,13 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniList import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.player.SubtitleData +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf +import com.lagradost.cloudstream3.utils.ExtractorLink import okhttp3.Interceptor -import org.mozilla.javascript.Scriptable import java.text.SimpleDateFormat import java.util.* import kotlin.math.absoluteValue @@ -84,7 +85,7 @@ object APIHolder { initMap() return apiMap?.get(apiName)?.let { apis.getOrNull(it) } // Leave the ?. null check, it can crash regardless - ?: allProviders.firstOrNull { it.name == apiName } + ?: allProviders.firstOrNull { it?.name == apiName } } } @@ -162,53 +163,6 @@ object APIHolder { return null } - private var trackerCache: HashMap = hashMapOf() - - /** - * Get anime tracker information based on title, year and type. - * Both titles are attempted to be matched with both Romaji and English title. - * Uses the consumet api. - * - * @param titles uses first index to search, but if you have multiple titles and want extra guarantee to match you can also have that - * @param types Optional parameter to narrow down the scope to Movies, TV, etc. See TrackerType.getTypes() - * @param year Optional parameter to only get anime with a specific year - **/ - suspend fun getTracker( - titles: List, - types: Set?, - year: Int? - ): Tracker? { - return try { - require(titles.isNotEmpty()) { "titles must no be empty when calling getTracker" } - - val mainTitle = titles[0] - val search = - trackerCache[mainTitle] - ?: app.get("https://api.consumet.org/meta/anilist/$mainTitle") - .parsedSafe()?.also { - trackerCache[mainTitle] = it - } ?: return null - - val res = search.results?.find { media -> - val matchingYears = year == null || media.releaseDate == year - val matchingTitles = media.title?.let { title -> - titles.any { userTitle -> - title.isMatchingTitles(userTitle) - } - } ?: false - - val matchingTypes = types?.any { it.name.equals(media.type, true) } == true - matchingTitles && matchingTypes && matchingYears - } ?: return null - - Tracker(res.malId, res.aniId, res.image, res.cover) - } catch (t: Throwable) { - logError(t) - null - } - } - - fun Context.getApiSettings(): HashSet { //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -736,19 +690,6 @@ fun fixTitle(str: String): String { .replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it } } } -/** - * Get rhino context in a safe way as it needs to be initialized on the main thread. - * Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects() - * Use like the following: rhino.evaluateString(scope, js, "JavaScript", 1, null) - **/ -suspend fun getRhinoContext(): org.mozilla.javascript.Context { - return Coroutines.mainWork { - val rhino = org.mozilla.javascript.Context.enter() - rhino.initSafeStandardObjects() - rhino.optimizationLevel = -1 - rhino - } -} /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ fun imdbUrlToId(url: String): String? { @@ -1327,7 +1268,7 @@ fun LoadResponse?.isAnimeBased(): Boolean { fun TvType?.isEpisodeBased(): Boolean { if (this == null) return false - return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama) + return (this == TvType.TvSeries || this == TvType.Anime) } @@ -1351,7 +1292,6 @@ interface EpisodeResponse { var showStatus: ShowStatus? var nextAiring: NextAiring? var seasonNames: List? - fun getLatestEpisodes(): Map } @JvmName("addSeasonNamesString") @@ -1420,18 +1360,7 @@ data class AnimeLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse, EpisodeResponse { - override fun getLatestEpisodes(): Map { - return episodes.map { (status, episodes) -> - val maxSeason = episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE } - .takeUnless { it == Int.MIN_VALUE } - status to episodes - .filter { it.season == maxSeason } - .maxOfOrNull { it.episode ?: Int.MIN_VALUE } - .takeUnless { it == Int.MIN_VALUE } - }.toMap() - } -} +) : LoadResponse, EpisodeResponse /** * If episodes already exist appends the list. @@ -1629,17 +1558,7 @@ data class TvSeriesLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse, EpisodeResponse { - override fun getLatestEpisodes(): Map { - val maxSeason = - episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }.takeUnless { it == Int.MIN_VALUE } - val max = episodes - .filter { it.season == maxSeason } - .maxOfOrNull { it.episode ?: Int.MIN_VALUE } - .takeUnless { it == Int.MIN_VALUE } - return mapOf(DubStatus.None to max) - } -} +) : LoadResponse, EpisodeResponse suspend fun MainAPI.newTvSeriesLoadResponse( name: String, @@ -1671,61 +1590,3 @@ fun fetchUrls(text: String?): List { fun String?.toRatingInt(): Int? = this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt() - -data class Tracker( - val malId: Int? = null, - val aniId: String? = null, - val image: String? = null, - val cover: String? = null, -) - -data class Title( - @JsonProperty("romaji") val romaji: String? = null, - @JsonProperty("english") val english: String? = null, -) { - fun isMatchingTitles(title: String?): Boolean { - if (title == null) return false - return english.equals(title, true) || romaji.equals(title, true) - } -} - -data class Results( - @JsonProperty("id") val aniId: String? = null, - @JsonProperty("malId") val malId: Int? = null, - @JsonProperty("title") val title: Title? = null, - @JsonProperty("releaseDate") val releaseDate: Int? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("image") val image: String? = null, - @JsonProperty("cover") val cover: String? = null, -) - -data class AniSearch( - @JsonProperty("results") val results: ArrayList? = arrayListOf() -) - -/** - * used for the getTracker() method - **/ -enum class TrackerType { - MOVIE, - TV, - TV_SHORT, - ONA, - OVA, - SPECIAL, - MUSIC; - - companion object { - fun getTypes(type: TvType): Set { - return when (type) { - TvType.Movie -> setOf(MOVIE) - TvType.AnimeMovie -> setOf(MOVIE) - TvType.TvSeries -> setOf(TV, TV_SHORT) - TvType.Anime -> setOf(TV, TV_SHORT, ONA, OVA) - TvType.OVA -> setOf(OVA, SPECIAL, ONA) - TvType.Others -> setOf(MUSIC) - else -> emptySet() - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index d054f504..8b5ef879 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -5,8 +5,6 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.content.res.Configuration -import android.net.Uri -import android.os.Build import android.os.Bundle import android.util.AttributeSet import android.util.Log @@ -34,7 +32,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.android.gms.cast.framework.* import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.navigationrail.NavigationRailView -import com.google.android.material.snackbar.Snackbar import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.apis @@ -58,7 +55,6 @@ import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch @@ -67,13 +63,7 @@ import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.home.HomeViewModel -import com.lagradost.cloudstream3.ui.player.BasicLink -import com.lagradost.cloudstream3.ui.player.GeneratorPlayer -import com.lagradost.cloudstream3.ui.player.LinkGenerator -import com.lagradost.cloudstream3.ui.result.ResultViewModel2 -import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.ui.result.setText +import com.lagradost.cloudstream3.ui.result.* import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings @@ -86,7 +76,6 @@ import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable -import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadResult @@ -94,7 +83,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching @@ -175,12 +163,7 @@ open class ResultResume( val VLC = object : ResultResume( VLC_PACKAGE, - // Android 13 intent restrictions fucks up specifically launching the VLC player - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - "org.videolan.vlc.player.result" - } else { - Intent.ACTION_VIEW - }, + "org.videolan.vlc.player.result", "extra_position", "extra_duration", ) { @@ -279,8 +262,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { isWebview: Boolean ): Boolean = with(activity) { - // TODO MUCH BETTER HANDLING - // Invalid URIs can crash fun safeURI(uri: String) = normalSafeApiCall { URI(uri) } @@ -331,25 +312,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } else if (safeURI(str)?.scheme == appStringSearch) { nextSearchQuery = URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8") - - // Use both navigation views to support both layouts. - // It might be better to use the QuickSearch. - nav_view?.selectedItemId = R.id.navigation_search - nav_rail_view?.selectedItemId = R.id.navigation_search - } else if (safeURI(str)?.scheme == appStringPlayer) { - val uri = Uri.parse(str) - val name = uri.getQueryParameter("name") - val url = URLDecoder.decode(uri.authority, "UTF-8") - - navigate( - R.id.global_to_navigation_player, - GeneratorPlayer.newInstance( - LinkGenerator( - listOf(BasicLink(url, name)), - extract = true, - ) - ) - ) + nav_view.selectedItemId = R.id.navigation_search } else if (safeURI(str)?.scheme == appStringResumeWatching) { val id = str.substringAfter("$appStringResumeWatching://").toIntOrNull() @@ -381,7 +344,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } - var lastPopup: SearchResponse? = null + var lastPopup : SearchResponse? = null fun loadPopup(result: SearchResponse) { lastPopup = result viewModel.load( @@ -436,7 +399,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { R.id.navigation_settings_general, R.id.navigation_settings_extensions, R.id.navigation_settings_plugins, - R.id.navigation_test_providers, ).contains(destination.id) @@ -751,35 +713,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { changeStatusBarState(isEmulatorSettings()) - // Automatically enable jsdelivr if cant connect to raw.githubusercontent.com - if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) { - main { - if (checkGithubConnectivity()) { - this.setKey(getString(R.string.jsdelivr_proxy_key), false) - } else { - this.setKey(getString(R.string.jsdelivr_proxy_key), true) - val parentView: View = findViewById(android.R.id.content) - Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG) - .let { snackbar -> - snackbar.setAction(R.string.revert) { - setKey(getString(R.string.jsdelivr_proxy_key), false) - } - snackbar.setBackgroundTint(colorFromAttribute(R.attr.primaryGrayBackground)) - snackbar.setTextColor(colorFromAttribute(R.attr.textColor)) - snackbar.setActionTextColor(colorFromAttribute(R.attr.colorPrimary)) - snackbar.show() - } - } - - } - } - - - if (PluginManager.checkSafeModeFile()) { - normalSafeApiCall { - showToast(this, R.string.safe_mode_file, Toast.LENGTH_LONG) - } - } else if (lastError == null) { + if (lastError == null) { ioSafe { getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) @@ -860,7 +794,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { resultview_preview_meta_duration.setText(d.durationText) resultview_preview_meta_rating.setText(d.ratingText) - resultview_preview_description?.setText(d.plotText) + resultview_preview_description?.setTextHtml(d.plotText) resultview_preview_poster?.setImage( d.posterImage ?: d.posterBackgroundImage ) @@ -1147,15 +1081,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // } } - - suspend fun checkGithubConnectivity(): Boolean { - return try { - app.get( - "https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck", - timeout = 5 - ).text.trim() == "ok" - } catch (t: Throwable) { - false - } - } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt index 4b7cb19f..125e4bcf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -6,7 +6,6 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 import com.lagradost.cloudstream3.utils.Qualities import java.net.URL @@ -43,9 +42,18 @@ open class Dailymotion : ExtractorApi() { ) val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = cookies) .parsedSafe() ?: return - metaData.qualities.forEach { (_, video) -> + metaData.qualities.forEach { (key, video) -> video.forEach { - getStream(it.url, this.name, callback) + callback.invoke( + ExtractorLink( + name, + "$name $key", + it.url, + "", + Qualities.Unknown.value, + true + ) + ) } } } @@ -67,17 +75,6 @@ open class Dailymotion : ExtractorApi() { return null } - private suspend fun getStream( - streamLink: String, - name: String, - callback: (ExtractorLink) -> Unit - ) { - return generateM3u8( - name, - streamLink, - "", - ).forEach(callback) - } data class Config( val context: Context, val dmInternalData: InternalData diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 0d94eb08..7ec1fb22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -38,9 +38,6 @@ class DoodWsExtractor : DoodLaExtractor() { override var mainUrl = "https://dood.ws" } -class DoodYtExtractor : DoodLaExtractor() { - override var mainUrl = "https://dood.yt" -} open class DoodLaExtractor : ExtractorApi() { override var name = "DoodStream" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt index 3e38b446..eddbf6df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt @@ -16,7 +16,26 @@ open class Evoload : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List { - val id = url.replace("https://evoload.io/e/", "") // wanted media id + val lang = url.substring(0, 2) + val flag = + if (lang == "vo") { + " \uD83C\uDDEC\uD83C\uDDE7" + } + else if (lang == "vf"){ + " \uD83C\uDDE8\uD83C\uDDF5" + } else { + "" + } + + val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http:// + url + } else { + url.substring(2, url.length) + } + //println(lang) + //println(cleaned_url) + + val id = cleaned_url.replace("https://evoload.io/e/", "") // wanted media id val csrv_token = app.get("https://csrv.evosrv.com/captcha?m412548=").text // whatever that is val captchaPass = app.get("https://cd2.evosrv.com/html/jsx/e.jsx").text.take(300).split("captcha_pass = '")[1].split("\'")[0] //extract the captcha pass from the js response (located in the 300 first chars) val payload = mapOf("code" to id, "csrv_token" to csrv_token, "pass" to captchaPass) @@ -25,9 +44,9 @@ open class Evoload : ExtractorApi() { return listOf( ExtractorLink( name, - name, + name + flag, link, - url, + cleaned_url, Qualities.Unknown.value, ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt index 84fd0552..8e3dc730 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -1,57 +1,38 @@ package com.lagradost.cloudstream3.extractors -import com.lagradost.cloudstream3.SubtitleFile +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 - - -class Ztreamhub : Filesim() { - override val mainUrl: String = "https://ztreamhub.com" //Here 'cause works - override val name = "Zstreamhub" -} -class FileMoon : Filesim() { - override val mainUrl = "https://filemoon.to" - override val name = "FileMoon" -} - -class FileMoonSx : Filesim() { - override val mainUrl = "https://filemoon.sx" - override val name = "FileMoonSx" -} +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson open class Filesim : ExtractorApi() { override val name = "Filesim" override val mainUrl = "https://files.im" override val requiresReferer = false - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val response = app.get(url, referer = mainUrl).document - response.select("script[type=text/javascript]").map { script -> - if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) { - val unpackedscript = getAndUnpack(script.data()) - val m3u8Regex = Regex("file.\\\"(.*?m3u8.*?)\\\"") - val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: "" - if (m3u8.isNotEmpty()) { - generateM3u8( - name, - m3u8, - mainUrl - ).forEach(callback) + override suspend fun getUrl(url: String, referer: String?): List { + val sources = mutableListOf() + with(app.get(url).document) { + this.select("script").map { script -> + if (script.data().contains("eval(function(p,a,c,k,e,d)")) { + val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]") + tryParseJson>("[$data]")?.map { + M3u8Helper.generateM3u8( + name, + it.file, + "$mainUrl/", + ).forEach { m3uData -> sources.add(m3uData) } + } } } } + return sources } - /* private data class ResponseSource( + private data class ResponseSource( @JsonProperty("file") val file: String, @JsonProperty("type") val type: String?, @JsonProperty("label") val label: String? - ) */ + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt index 2adc00d5..f25cb5ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt @@ -6,11 +6,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* -class Vanfem : GuardareStream() { - override var name = "Vanfem" - override var mainUrl = "https://vanfem.com/" -} - class CineGrabber : GuardareStream() { override var name = "CineGrabber" override var mainUrl = "https://cinegrabber.com" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt index 6a4945bb..c28a8900 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt @@ -18,36 +18,31 @@ open class Linkbox : ExtractorApi() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val id = Regex("""(?:/f/|/file/|\?id=)(\w+)""").find(url)?.groupValues?.get(1) - app.get("$mainUrl/api/file/detail?itemId=$id", referer = url) - .parsedSafe()?.data?.itemInfo?.resolutionList?.map { link -> - callback.invoke( - ExtractorLink( - name, - name, - link.url ?: return@map null, - url, - getQualityFromName(link.resolution) - ) + val id = Regex("""(/file/|id=)(\S+)[&/?]""").find(url)?.groupValues?.get(2) + app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe()?.data?.rList?.map { link -> + callback.invoke( + ExtractorLink( + name, + name, + link.url, + url, + getQualityFromName(link.resolution) ) - } + ) + } } - data class Resolutions( - @JsonProperty("url") val url: String? = null, - @JsonProperty("resolution") val resolution: String? = null, - ) - - data class ItemInfo( - @JsonProperty("resolutionList") val resolutionList: ArrayList? = arrayListOf(), + data class RList( + @JsonProperty("url") val url: String, + @JsonProperty("resolution") val resolution: String?, ) data class Data( - @JsonProperty("itemInfo") val itemInfo: ItemInfo? = null, + @JsonProperty("rList") val rList: List?, ) data class Responses( - @JsonProperty("data") val data: Data? = null, + @JsonProperty("data") val data: Data?, ) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt deleted file mode 100644 index 514b802d..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 - -open class Sendvid : ExtractorApi() { - override var name = "Sendvid" - override val mainUrl = "https://sendvid.com" - override val requiresReferer = false - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val doc = app.get(url).document - val urlString = doc.select("head meta[property=og:video:secure_url]").attr("content") - if (urlString.contains("m3u8")) { - generateM3u8( - name, - urlString, - mainUrl, - ).forEach(callback) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt index cac31328..958d63fb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt @@ -77,10 +77,6 @@ class StreamSB10 : StreamSB() { override var mainUrl = "https://sbplay2.xyz" } -class StreamSB11 : StreamSB() { - override var mainUrl = "https://sbbrisk.com" -} - // This is a modified version of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/genoanime/src/eu/kanade/tachiyomi/animeextension/en/genoanime/extractors/StreamSBExtractor.kt // The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE open class StreamSB : ExtractorApi() { @@ -134,7 +130,7 @@ open class StreamSB : ExtractorApi() { it.value.replace(Regex("(embed-|/e/)"), "") }.first() // val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" - val master = "$mainUrl/sources15/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" + val master = "$mainUrl/sources50/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" val headers = mapOf( "watchsb" to "sbstream", ) @@ -160,4 +156,4 @@ open class StreamSB : ExtractorApi() { ) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt index 86bd9e0b..5109acc3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt @@ -7,10 +7,6 @@ class Uqload1 : Uqload() { override var mainUrl = "https://uqload.com" } -class Uqload2 : Uqload() { - override var mainUrl = "https://uqload.co" -} - open class Uqload : ExtractorApi() { override val name: String = "Uqload" override val mainUrl: String = "https://www.uqload.com" @@ -19,14 +15,30 @@ open class Uqload : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { - with(app.get(url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" + val lang = url.substring(0, 2) + val flag = + if (lang == "vo") { + " \uD83C\uDDEC\uD83C\uDDE7" + } + else if (lang == "vf"){ + " \uD83C\uDDE8\uD83C\uDDF5" + } else { + "" + } + + val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http:// + url + } else { + url.substring(2, url.length) + } + with(app.get(cleaned_url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link -> return listOf( ExtractorLink( name, - name, + name + flag, link, - url, + cleaned_url, Qualities.Unknown.value, ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt index a27bf188..b910f9dd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt @@ -59,8 +59,8 @@ open class VidSrcExtractor : ExtractorApi() { if (datahash.isNotBlank()) { val links = try { app.get( - "$absoluteUrl/srcrcp/$datahash", - referer = "https://rcp.vidsrc.me/" + "$absoluteUrl/src/$datahash", + referer = "https://source.vidsrc.me/" ).url } catch (e: Exception) { "" @@ -71,7 +71,7 @@ open class VidSrcExtractor : ExtractorApi() { serverslist.amap { server -> val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/") - if (linkfixed.contains("/prorcp")) { + if (linkfixed.contains("/pro")) { val srcresponse = app.get(server, referer = absoluteUrl).text val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)") val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt deleted file mode 100644 index 67e59281..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.lagradost.cloudstream3.extractors -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.getAndUnpack - -class Vido : ExtractorApi() { - override var name = "Vido" - override var mainUrl = "https://vido.lol" - private val srcRegex = Regex("""sources:\s*\["(.*?)"\]""") - override val requiresReferer = true - - override suspend fun getUrl(url: String, referer: String?): List? { - val methode = app.get(url.replace("/e/", "/embed-")) // fix wiflix and mesfilms - with(methode) { - if (!methode.isSuccessful) return null - //val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull() - srcRegex.find(this.text)?.groupValues?.get(1)?.let { link -> - return listOf( - ExtractorLink( - name, - name, - link, - url, - Qualities.Unknown.value, - true, - ) - ) - } - } - return null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index bb15bc85..afe956cc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -49,7 +49,7 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) { } } -fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) { +fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) { liveData.observe(this) { it?.let { t -> action(t) } } } @@ -121,21 +121,13 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } } -fun Throwable.getAllMessages(): String { - return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") -} - -fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { - val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else "" - return prefix + this.stackTrace.joinToString( - separator = "\n" - ) { - "${it.fileName} ${it.lineNumber}" - } -} - fun safeFail(throwable: Throwable): Resource { - val stackTraceMsg = throwable.getStackTracePretty() + val stackTraceMsg = + (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( + separator = "\n" + ) { + "${it.fileName} ${it.lineNumber}" + } return Resource.Failure(false, null, null, stackTraceMsg) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 0dee57eb..54fe5d75 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -16,7 +16,6 @@ import com.google.gson.Gson import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.removePluginMapping -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey @@ -145,10 +144,8 @@ object PluginManager { return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray() } - private val CLOUD_STREAM_FOLDER = - Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/" - - private val LOCAL_PLUGINS_PATH = CLOUD_STREAM_FOLDER + "plugins" + private val LOCAL_PLUGINS_PATH = + Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins" public var currentlyLoading: String? = null @@ -166,11 +163,11 @@ object PluginManager { private var loadedLocalPlugins = false private val gson = Gson() - private suspend fun maybeLoadPlugin(context: Context, file: File) { + private suspend fun maybeLoadPlugin(activity: Activity, file: File) { val name = file.name if (file.extension == "zip" || file.extension == "cs3") { loadPlugin( - context, + activity, file, PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET) ) @@ -200,7 +197,7 @@ object PluginManager { // var allCurrentOutDatedPlugins: Set = emptySet() - suspend fun loadSinglePlugin(context: Context, apiName: String): Boolean { + suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { return (getPluginsOnline().firstOrNull { // Most of the time the provider ends with Provider which isn't part of the api name it.internalName.replace("provider", "", ignoreCase = true) == apiName @@ -210,7 +207,7 @@ object PluginManager { })?.let { savedData -> // OnlinePluginData(savedData, onlineData) loadPlugin( - context, + activity, File(savedData.filePath), savedData ) @@ -372,11 +369,11 @@ object PluginManager { /** * Use updateAllOnlinePluginsAndLoadThem * */ - fun loadAllOnlinePlugins(context: Context) { + fun loadAllOnlinePlugins(activity: Activity) { // Load all plugins as fast as possible! (getPluginsOnline()).toList().apmap { pluginData -> loadPlugin( - context, + activity, File(pluginData.filePath), pluginData ) @@ -399,7 +396,7 @@ object PluginManager { * @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins * and reload all pages even if they are previously valid **/ - fun loadAllLocalPlugins(context: Context, forceReload: Boolean) { + fun loadAllLocalPlugins(activity: Activity, forceReload: Boolean) { val dir = File(LOCAL_PLUGINS_PATH) removeKey(PLUGINS_KEY_LOCAL) @@ -417,39 +414,24 @@ object PluginManager { Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins") sortedPlugins?.sortedBy { it.name }?.apmap { file -> - maybeLoadPlugin(context, file) + maybeLoadPlugin(activity, file) } loadedLocalPlugins = true afterPluginsLoadedEvent.invoke(forceReload) } - /** - * This can be used to override any extension loading to fix crashes! - * @return true if safe mode file is present - **/ - fun checkSafeModeFile(): Boolean { - return normalSafeApiCall { - val folder = File(CLOUD_STREAM_FOLDER) - if (!folder.exists()) return@normalSafeApiCall false - val files = folder.listFiles { _, name -> - name.equals("safe", ignoreCase = true) - } - files?.any() - } ?: false - } - /** * @return True if successful, false if not * */ - private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean { + private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean { val fileName = file.nameWithoutExtension val filePath = file.absolutePath currentlyLoading = fileName Log.i(TAG, "Loading plugin: $data") return try { - val loader = PathClassLoader(filePath, context.classLoader) + val loader = PathClassLoader(filePath, activity.classLoader) var manifest: Plugin.Manifest loader.getResourceAsStream("manifest.json").use { stream -> if (stream == null) { @@ -493,22 +475,22 @@ object PluginManager { addAssetPath.invoke(assets, file.absolutePath) pluginInstance.resources = Resources( assets, - context.resources.displayMetrics, - context.resources.configuration + activity.resources.displayMetrics, + activity.resources.configuration ) } plugins[filePath] = pluginInstance classLoaders[loader] = pluginInstance urlPlugins[data.url ?: filePath] = pluginInstance - pluginInstance.load(context) + pluginInstance.load(activity) Log.i(TAG, "Loaded plugin ${data.internalName} successfully") currentlyLoading = null true } catch (e: Throwable) { Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") showToast( - context.getActivity(), - context.getString(R.string.plugin_load_fail).format(fileName), + activity, + activity.getString(R.string.plugin_load_fail).format(fileName), Toast.LENGTH_LONG ) currentlyLoading = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index 742bf308..e77b2d54 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -2,10 +2,8 @@ package com.lagradost.cloudstream3.plugins import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError @@ -73,15 +71,6 @@ object RepositoryManager { val PREBUILT_REPOSITORIES: Array by lazy { getKey("PREBUILT_REPOSITORIES") ?: emptyArray() } - val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$") - - /* Convert raw.githubusercontent.com urls to cdn.jsdelivr.net if enabled in settings */ - fun convertRawGitUrl(url: String): String { - if (getKey(context!!.getString(R.string.jsdelivr_proxy_key)) != true) return url - val match = GH_REGEX.find(url) ?: return url - val (user, repo, rest) = match.destructured - return "https://cdn.jsdelivr.net/gh/$user/$repo@$rest" - } suspend fun parseRepoUrl(url: String): String? { val fixedUrl = url.trim() @@ -95,15 +84,10 @@ object RepositoryManager { } } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { suspendSafeApiCall { - app.get("https://l.cloudstream.cf/${fixedUrl}", allowRedirects = false).let { - it.headers["Location"]?.let { url -> - return@suspendSafeApiCall if (!url.startsWith("https://cutt.ly/branded-domains")) url - else null - } - app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 -> - it2.headers["Location"]?.let { url -> - return@suspendSafeApiCall if (url.startsWith("https://cutt.ly/404")) url else null - } + app.get("https://l.cloudstream.cf/${fixedUrl}").let { + return@let if (it.isSuccessful && !it.url.startsWith("https://cutt.ly/branded-domains")) it.url + else app.get("https://cutt.ly/${fixedUrl}").let let2@{ it2 -> + return@let2 if (it2.isSuccessful) it2.url else null } } } @@ -113,14 +97,14 @@ object RepositoryManager { suspend fun parseRepository(url: String): Repository? { return suspendSafeApiCall { // Take manifestVersion and such into account later - app.get(convertRawGitUrl(url)).parsedSafe() + app.get(url).parsedSafe() } } private suspend fun parsePlugins(pluginUrls: String): List { // Take manifestVersion and such into account later return try { - val response = app.get(convertRawGitUrl(pluginUrls)) + val response = app.get(pluginUrls) // Normal parsed function not working? // return response.parsedSafe() tryParseJson>(response.text)?.toList() ?: emptyList() @@ -155,7 +139,7 @@ object RepositoryManager { } file.createNewFile() - val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body + val body = app.get(pluginUrl).okhttpResponse.body write(body.byteStream(), file.outputStream()) file } diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt deleted file mode 100644 index adf5abfa..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt +++ /dev/null @@ -1,224 +0,0 @@ -package com.lagradost.cloudstream3.services - -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.core.net.toUri -import androidx.work.* -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings -import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.safeApiCall -import com.lagradost.cloudstream3.plugins.PluginManager -import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel -import com.lagradost.cloudstream3.utils.Coroutines.ioWork -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions -import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub -import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.VideoDownloadManager.getImageBitmapFromUrl -import kotlinx.coroutines.withTimeoutOrNull -import java.util.concurrent.TimeUnit - -const val SUBSCRIPTION_CHANNEL_ID = "cloudstream3.subscriptions" -const val SUBSCRIPTION_WORK_NAME = "work_subscription" -const val SUBSCRIPTION_CHANNEL_NAME = "Subscriptions" -const val SUBSCRIPTION_CHANNEL_DESCRIPTION = "Notifications for new episodes on subscribed shows" -const val SUBSCRIPTION_NOTIFICATION_ID = 938712897 // Random unique - -class SubscriptionWorkManager(val context: Context, workerParams: WorkerParameters) : - CoroutineWorker(context, workerParams) { - companion object { - fun enqueuePeriodicWork(context: Context?) { - if (context == null) return - - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - - val periodicSyncDataWork = - PeriodicWorkRequest.Builder(SubscriptionWorkManager::class.java, 6, TimeUnit.HOURS) - .addTag(SUBSCRIPTION_WORK_NAME) - .setConstraints(constraints) - .build() - - WorkManager.getInstance(context).enqueueUniquePeriodicWork( - SUBSCRIPTION_WORK_NAME, - ExistingPeriodicWorkPolicy.KEEP, - periodicSyncDataWork - ) - - // Uncomment below for testing - -// val oneTimeSyncDataWork = -// OneTimeWorkRequest.Builder(SubscriptionWorkManager::class.java) -// .addTag(SUBSCRIPTION_WORK_NAME) -// .setConstraints(constraints) -// .build() -// -// WorkManager.getInstance(context).enqueue(oneTimeSyncDataWork) - } - } - - private val progressNotificationBuilder = - NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID) - .setAutoCancel(false) - .setColorized(true) - .setOnlyAlertOnce(true) - .setSilent(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setColor(context.colorFromAttribute(R.attr.colorPrimary)) - .setContentTitle(context.getString(R.string.subscription_in_progress_notification)) - .setSmallIcon(R.drawable.quantum_ic_refresh_white_24) - .setProgress(0, 0, true) - - private val updateNotificationBuilder = - NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID) - .setColorized(true) - .setOnlyAlertOnce(true) - .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setColor(context.colorFromAttribute(R.attr.colorPrimary)) - .setSmallIcon(R.drawable.ic_cloudstream_monochrome_big) - - private val notificationManager: NotificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - private fun updateProgress(max: Int, progress: Int, indeterminate: Boolean) { - notificationManager.notify( - SUBSCRIPTION_NOTIFICATION_ID, progressNotificationBuilder - .setProgress(max, progress, indeterminate) - .build() - ) - } - - override suspend fun doWork(): Result { -// println("Update subscriptions!") - context.createNotificationChannel( - SUBSCRIPTION_CHANNEL_ID, - SUBSCRIPTION_CHANNEL_NAME, - SUBSCRIPTION_CHANNEL_DESCRIPTION - ) - - setForeground( - ForegroundInfo( - SUBSCRIPTION_NOTIFICATION_ID, - progressNotificationBuilder.build() - ) - ) - - val subscriptions = getAllSubscriptions() - - if (subscriptions.isEmpty()) { - WorkManager.getInstance(context).cancelWorkById(this.id) - return Result.success() - } - - val max = subscriptions.size - var progress = 0 - - updateProgress(max, progress, true) - - // We need all plugins loaded. - PluginManager.loadAllOnlinePlugins(context) - PluginManager.loadAllLocalPlugins(context, false) - - subscriptions.apmap { savedData -> - try { - val id = savedData.id ?: return@apmap null - val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null - - // Reasonable timeout to prevent having this worker run forever. - val response = withTimeoutOrNull(60_000) { - api.load(savedData.url) as? EpisodeResponse - } ?: return@apmap null - - val dubPreference = - getDub(id) ?: if ( - context.getApiDubstatusSettings().contains(DubStatus.Dubbed) - ) { - DubStatus.Dubbed - } else { - DubStatus.Subbed - } - - val latestEpisodes = response.getLatestEpisodes() - val latestPreferredEpisode = latestEpisodes[dubPreference] - - val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) { - val latestSeenEpisode = - savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE - val shouldUpdate = latestPreferredEpisode > latestSeenEpisode - shouldUpdate to latestPreferredEpisode - } else { - val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE - val latestSeenEpisode = - savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE - val shouldUpdate = latestEpisode > latestSeenEpisode - shouldUpdate to latestEpisode - } - - DataStoreHelper.updateSubscribedData( - id, - savedData, - response - ) - - if (shouldUpdate) { - val updateHeader = savedData.name - val updateDescription = txt( - R.string.subscription_episode_released, - latestEpisode, - savedData.name - ).asString(context) - - val intent = Intent(context, MainActivity::class.java).apply { - data = savedData.url.toUri() - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - - val pendingIntent = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.getActivity( - context, - 0, - intent, - PendingIntent.FLAG_IMMUTABLE - ) - } else { - PendingIntent.getActivity(context, 0, intent, 0) - } - - val poster = ioWork { - savedData.posterUrl?.let { url -> - context.getImageBitmapFromUrl( - url, - savedData.posterHeaders - ) - } - } - - val updateNotification = - updateNotificationBuilder.setContentTitle(updateHeader) - .setContentText(updateDescription) - .setContentIntent(pendingIntent) - .setLargeIcon(poster) - .build() - - notificationManager.notify(id, updateNotification) - } - - // You can probably get some issues here since this is async but it does not matter much. - updateProgress(max, ++progress, false) - } catch (_: Throwable) { - } - } - - return Result.success() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt b/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt index 6151a0ed..be2fe75b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt @@ -1,22 +1,11 @@ package com.lagradost.cloudstream3.services -import android.app.Service + +import android.app.IntentService import android.content.Intent -import android.os.IBinder import com.lagradost.cloudstream3.utils.VideoDownloadManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -class VideoDownloadService : Service() { - - private val downloadScope = CoroutineScope(Dispatchers.Default) - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { +class VideoDownloadService : IntentService("VideoDownloadService") { + override fun onHandleIntent(intent: Intent?) { if (intent != null) { val id = intent.getIntExtra("id", -1) val type = intent.getStringExtra("type") @@ -25,36 +14,10 @@ class VideoDownloadService : Service() { "resume" -> VideoDownloadManager.DownloadActionType.Resume "pause" -> VideoDownloadManager.DownloadActionType.Pause "stop" -> VideoDownloadManager.DownloadActionType.Stop - else -> return START_NOT_STICKY - } - - downloadScope.launch { - VideoDownloadManager.downloadEvent.invoke(Pair(id, state)) + else -> return } + VideoDownloadManager.downloadEvent.invoke(Pair(id, state)) } } - - return START_NOT_STICKY } - - override fun onDestroy() { - downloadScope.coroutineContext.cancel() - super.onDestroy() - } -} -// override fun onHandleIntent(intent: Intent?) { -// if (intent != null) { -// val id = intent.getIntExtra("id", -1) -// val type = intent.getStringExtra("type") -// if (id != -1 && type != null) { -// val state = when (type) { -// "resume" -> VideoDownloadManager.DownloadActionType.Resume -// "pause" -> VideoDownloadManager.DownloadActionType.Pause -// "stop" -> VideoDownloadManager.DownloadActionType.Stop -// else -> return -// } -// VideoDownloadManager.downloadEvent.invoke(Pair(id, state)) -// } -// } -// } -//} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index 8ce6bae2..f17086c1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -45,7 +45,6 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { const val appString = "cloudstreamapp" const val appStringRepo = "cloudstreamrepo" - const val appStringPlayer = "cloudstreamplayer" // Instantly start the search given a query const val appStringSearch = "cloudstreamsearch" diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 0010ce25..7d9de43a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -759,11 +759,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { return data != "" } - /** Used to query a saved MediaItem on the list to get the id for removal */ - data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null) - data class MediaListItem(@JsonProperty("MediaList") val MediaList: MediaListId? = null) - data class MediaListId(@JsonProperty("id") val id: Long? = null) - private suspend fun postDataAboutId( id: Int, type: AniListStatusType, @@ -771,43 +766,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { progress: Int? ): Boolean { val q = - // Delete item if status type is None - if (type == AniListStatusType.None) { - val userID = getKey(accountId, ANILIST_USER_KEY)?.id ?: return false - // Get list ID for deletion - val idQuery = """ - query MediaList(${'$'}userId: Int = $userID, ${'$'}mediaId: Int = $id) { - MediaList(userId: ${'$'}userId, mediaId: ${'$'}mediaId) { - id - } - } - """ - val response = postApi(idQuery) - val listId = - tryParseJson(response)?.data?.MediaList?.id ?: return false - """ - mutation(${'$'}id: Int = $listId) { - DeleteMediaListEntry(id: ${'$'}id) { - deleted - } - } - """ - } else { - """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ - aniListStatusString[maxOf( - 0, - type.value - )] - }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) { - SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) { - id - status - progress - score - } + """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ + aniListStatusString[maxOf( + 0, + type.value + )] + }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) { + SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) { + id + status + progress + score + } }""" - } - val data = postApi(q) return data != "" } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 7dd43fe7..0b081220 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -9,7 +9,6 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.Coroutines.ioWork -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState @@ -75,16 +74,13 @@ class LocalList : SyncAPI { group.value.mapNotNull { getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) } - } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { - it.toLibraryItem() - }) + } } val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { // None is not something to display it.stringRes to emptyList() - } + mapOf(R.string.subscription_list_name to emptyList()) - + } return SyncAPI.LibraryMetadata( (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, setOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index e80a8fa5..f0340845 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.LinkGenerator +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE @@ -39,7 +40,6 @@ import kotlinx.android.synthetic.main.stream_input.* import android.text.format.Formatter.formatShortFileSize import androidx.core.widget.doOnTextChanged import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.ui.player.BasicLink import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import java.net.URI @@ -225,7 +225,7 @@ class DownloadFragment : Fragment() { R.id.global_to_navigation_player, GeneratorPlayer.newInstance( LinkGenerator( - listOf(BasicLink(url)), + listOf(url), extract = true, referer = referer, isM3u8 = dialog.hls_switch?.isChecked diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 58c6dbe0..e6999c9e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -185,7 +185,7 @@ open class ParentItemAdapter( ) : RecyclerView.ViewHolder(itemView) { val title: TextView = itemView.home_child_more_info - private val recyclerView: RecyclerView = itemView.home_child_recyclerview + val recyclerView: RecyclerView = itemView.home_child_recyclerview fun update(expand: HomeViewModel.ExpandableHomepageList) { val info = expand.list diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index d7c06c4e..1c6af447 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -333,10 +333,8 @@ class LibraryFragment : Fragment() { handler.postDelayed(stopLoading, 300) savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos -> - if (currentPos < 0) return@let viewpager?.setCurrentItem(currentPos, false) - // Using remove() sets the key to 0 instead of removing it - savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1) + savedInstanceState.remove(VIEWPAGER_ITEM_KEY) } // Since the animation to scroll multiple items is so much its better to just hide diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index e0885671..4772a7f1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -9,11 +9,8 @@ import android.widget.FrameLayout import androidx.preference.PreferenceManager import com.google.android.exoplayer2.* import com.google.android.exoplayer2.C.* -import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON -import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector import com.google.android.exoplayer2.source.* import com.google.android.exoplayer2.text.TextRenderer import com.google.android.exoplayer2.trackselection.DefaultTrackSelector @@ -38,13 +35,11 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.utils.EpisodeSkip -import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import java.io.File -import java.time.Duration import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession @@ -540,17 +535,15 @@ class CS3IPlayer : IPlayer { OkHttpDataSource.Factory(client).setUserAgent(USER_AGENT) } - // Do no include empty referer, if the provider wants those they can use the header map. - val refererMap = - if (link.referer.isBlank()) emptyMap() else mapOf("referer" to link.referer) val headers = mapOf( + "referer" to link.referer, "accept" to "*/*", "sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"", "sec-ch-ua-mobile" to "?0", "sec-fetch-user" to "?1", "sec-fetch-mode" to "navigate", "sec-fetch-dest" to "video" - ) + refererMap + link.headers // Adds the headers from the provider, e.g Authorization + ) + link.headers // Adds the headers from the provider, e.g Authorization return source.apply { setDefaultRequestProperties(headers) @@ -673,27 +666,23 @@ class CS3IPlayer : IPlayer { val exoPlayerBuilder = ExoPlayer.Builder(context) .setRenderersFactory { eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput -> - DefaultRenderersFactory(context).apply { -// setEnableDecoderFallback(true) - // Enable Ffmpeg extension -// setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON) - }.createRenderers( - eventHandler, - videoRendererEventListener, - audioRendererEventListener, - textRendererOutput, - metadataRendererOutput - ).map { - if (it is TextRenderer) { - currentTextRenderer = CustomTextRenderer( - subtitleOffset, - textRendererOutput, - eventHandler.looper, - CustomSubtitleDecoderFactory() - ) - currentTextRenderer!! - } else it - }.toTypedArray() + DefaultRenderersFactory(context).createRenderers( + eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput + ).map { + if (it is TextRenderer) { + currentTextRenderer = CustomTextRenderer( + subtitleOffset, + textRendererOutput, + eventHandler.looper, + CustomSubtitleDecoderFactory() + ) + currentTextRenderer!! + } else it + }.toTypedArray() } .setTrackSelector( trackSelector ?: getTrackSelector( @@ -858,7 +847,7 @@ class CS3IPlayer : IPlayer { Log.i(TAG, "loadExo") val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val maxVideoHeight = settingsManager.getInt( - context.getString(if (context.isUsingMobileData()) com.lagradost.cloudstream3.R.string.quality_pref_mobile_data_key else com.lagradost.cloudstream3.R.string.quality_pref_key), + context.getString(com.lagradost.cloudstream3.R.string.quality_pref_key), Int.MAX_VALUE ) @@ -1204,10 +1193,10 @@ class CS3IPlayer : IPlayer { HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) } - val mime = when { - link.isM3u8 -> MimeTypes.APPLICATION_M3U8 - link.isDash -> MimeTypes.APPLICATION_MPD - else -> MimeTypes.VIDEO_MP4 + val mime = if (link.isM3u8) { + MimeTypes.APPLICATION_M3U8 + } else { + MimeTypes.VIDEO_MP4 } val mediaItems = if (link is ExtractorLinkPlayList) { @@ -1257,4 +1246,4 @@ class CS3IPlayer : IPlayer { loadOfflinePlayer(context, it) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index 974a5d26..690d3706 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -4,16 +4,13 @@ import android.content.Context import android.util.Log import androidx.preference.PreferenceManager import com.google.android.exoplayer2.Format -import com.google.android.exoplayer2.text.* -import com.google.android.exoplayer2.text.cea.Cea608Decoder -import com.google.android.exoplayer2.text.cea.Cea708Decoder -import com.google.android.exoplayer2.text.dvb.DvbDecoder -import com.google.android.exoplayer2.text.pgs.PgsDecoder +import com.google.android.exoplayer2.text.SubtitleDecoder +import com.google.android.exoplayer2.text.SubtitleDecoderFactory +import com.google.android.exoplayer2.text.SubtitleInputBuffer +import com.google.android.exoplayer2.text.SubtitleOutputBuffer import com.google.android.exoplayer2.text.ssa.SsaDecoder import com.google.android.exoplayer2.text.subrip.SubripDecoder import com.google.android.exoplayer2.text.ttml.TtmlDecoder -import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder -import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder import com.google.android.exoplayer2.text.webvtt.WebvttDecoder import com.google.android.exoplayer2.util.MimeTypes import com.lagradost.cloudstream3.R @@ -22,11 +19,7 @@ import org.mozilla.universalchardet.UniversalDetector import java.nio.ByteBuffer import java.nio.charset.Charset -/** - * @param fallbackFormat used to create a decoder based on mimetype if the subtitle string is not - * enough to identify the subtitle format. - **/ -class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder { +class CustomDecoder : SubtitleDecoder { companion object { fun updateForcedEncoding(context: Context) { val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) @@ -146,7 +139,7 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder { val inputString = getStr(inputBuffer) if (realDecoder == null && !inputString.isNullOrBlank()) { var str: String = inputString - // this way we read the subtitle file and decide what decoder to use instead of relying fully on mimetype + // this way we read the subtitle file and decide what decoder to use instead of relying on mimetype Log.i(TAG, "Got data from queueInputBuffer") //https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388 realDecoder = when { @@ -155,31 +148,8 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder { (str.startsWith( "[Script Info]", ignoreCase = true - ) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder(fallbackFormat?.initializationData) + ) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder() str.startsWith("1", ignoreCase = true) -> SubripDecoder() - fallbackFormat != null -> { - when (val mimeType = fallbackFormat.sampleMimeType) { - MimeTypes.TEXT_VTT -> WebvttDecoder() - MimeTypes.TEXT_SSA -> SsaDecoder(fallbackFormat.initializationData) - MimeTypes.APPLICATION_MP4VTT -> Mp4WebvttDecoder() - MimeTypes.APPLICATION_TTML -> TtmlDecoder() - MimeTypes.APPLICATION_SUBRIP -> SubripDecoder() - MimeTypes.APPLICATION_TX3G -> Tx3gDecoder(fallbackFormat.initializationData) - MimeTypes.APPLICATION_CEA608, MimeTypes.APPLICATION_MP4CEA608 -> Cea608Decoder( - mimeType, - fallbackFormat.accessibilityChannel, - Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS - ) - MimeTypes.APPLICATION_CEA708 -> Cea708Decoder( - fallbackFormat.accessibilityChannel, - fallbackFormat.initializationData - ) - MimeTypes.APPLICATION_DVBSUBS -> DvbDecoder(fallbackFormat.initializationData) - MimeTypes.APPLICATION_PGS -> PgsDecoder() - MimeTypes.TEXT_EXOPLAYER_CUES -> ExoplayerCuesDecoder() - else -> null - } - } else -> null } Log.i( @@ -276,6 +246,28 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { } override fun createDecoder(format: Format): SubtitleDecoder { - return CustomDecoder(format) + return CustomDecoder() + //return when (val mimeType = format.sampleMimeType) { + // MimeTypes.TEXT_VTT -> WebvttDecoder() + // MimeTypes.TEXT_SSA -> SsaDecoder(format.initializationData) + // MimeTypes.APPLICATION_MP4VTT -> Mp4WebvttDecoder() + // MimeTypes.APPLICATION_TTML -> TtmlDecoder() + // MimeTypes.APPLICATION_SUBRIP -> SubripDecoder() + // MimeTypes.APPLICATION_TX3G -> Tx3gDecoder(format.initializationData) + // MimeTypes.APPLICATION_CEA608, MimeTypes.APPLICATION_MP4CEA608 -> return Cea608Decoder( + // mimeType, + // format.accessibilityChannel, + // Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS + // ) + // MimeTypes.APPLICATION_CEA708 -> Cea708Decoder( + // format.accessibilityChannel, + // format.initializationData + // ) + // MimeTypes.APPLICATION_DVBSUBS -> DvbDecoder(format.initializationData) + // MimeTypes.APPLICATION_PGS -> PgsDecoder() + // MimeTypes.TEXT_EXOPLAYER_CUES -> ExoplayerCuesDecoder() + // // Default WebVttDecoder + // else -> WebvttDecoder() + //} } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt index 6f40e145..dc1bbba3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt @@ -42,7 +42,7 @@ class DownloadedPlayerActivity : AppCompatActivity() { R.id.global_to_navigation_player, GeneratorPlayer.newInstance( LinkGenerator( listOf( - BasicLink(url) + url ) ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 86e21fd6..2dad05ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -84,7 +83,6 @@ const val HORIZONTAL_MULTIPLIER = 2.0f const val DOUBLE_TAB_MAXIMUM_HOLD_TIME = 200L const val DOUBLE_TAB_MINIMUM_TIME_BETWEEN = 200L // this also affects the UI show response time const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions -private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay" // All the UI Logic for the player open class FullScreenPlayer : AbstractPlayerFragment() { @@ -111,8 +109,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { protected var currentPrefQuality = Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell protected var fastForwardTime = 10000L - protected var androidTVInterfaceOffSeekTime = 10000L; - protected var androidTVInterfaceOnSeekTime = 30000L; protected var swipeHorizontalEnabled = false protected var swipeVerticalEnabled = false protected var playBackSpeedEnabled = false @@ -1055,19 +1051,19 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } KeyEvent.KEYCODE_DPAD_LEFT -> { if (!isShowing && !isLocked) { - player.seekTime(-androidTVInterfaceOffSeekTime) + player.seekTime(-10000L) return true } else if (player_pause_play?.isFocused == true) { - player.seekTime(-androidTVInterfaceOnSeekTime) + player.seekTime(-30000L) return true } } KeyEvent.KEYCODE_DPAD_RIGHT -> { if (!isShowing && !isLocked) { - player.seekTime(androidTVInterfaceOffSeekTime) + player.seekTime(10000L) return true } else if (player_pause_play?.isFocused == true) { - player.seekTime(androidTVInterfaceOnSeekTime) + player.seekTime(30000L) return true } } @@ -1121,20 +1117,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() { resetRewindText() } - override fun onSaveInstanceState(outState: Bundle) { - // As this is video specific it is better to not do any setKey/getKey - outState.putLong(SUBTITLE_DELAY_BUNDLE_KEY, subtitleDelay) - super.onSaveInstanceState(outState) - } - @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // init variables setPlayBackSpeed(getKey(PLAYBACK_SPEED_KEY) ?: 1.0f) - savedInstanceState?.getLong(SUBTITLE_DELAY_BUNDLE_KEY)?.let { - subtitleDelay = it - } // handle tv controls playerEventListener = { eventType -> @@ -1220,13 +1207,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { settingsManager.getInt(ctx.getString(R.string.double_tap_seek_time_key), 10) .toLong() * 1000L - androidTVInterfaceOffSeekTime = - settingsManager.getInt(ctx.getString(R.string.android_tv_interface_off_seek_key), 10) - .toLong() * 1000L - androidTVInterfaceOnSeekTime = - settingsManager.getInt(ctx.getString(R.string.android_tv_interface_on_seek_key), 10) - .toLong() * 1000L - navigationBarHeight = ctx.getNavigationBarHeight() statusBarHeight = ctx.getStatusBarHeight() @@ -1257,8 +1237,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() { ctx.getString(R.string.double_tap_pause_enabled_key), false ) + currentPrefQuality = settingsManager.getInt( - ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key), + ctx.getString(R.string.quality_pref_key), currentPrefQuality ) // useSystemBrightness = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 46f2bca9..e15dcee6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -11,8 +11,11 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo import android.widget.* +import android.widget.TextView.OnEditorActionListener import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.core.animation.addListener import androidx.core.content.ContextCompat import androidx.core.view.isGone @@ -525,7 +528,7 @@ class GeneratorPlayer : FullScreenPlayer() { } } - var selectSourceDialog: Dialog? = null + var selectSourceDialog: AlertDialog? = null // var selectTracksDialog: AlertDialog? = null override fun showMirrorsDialogue() { @@ -537,8 +540,10 @@ class GeneratorPlayer : FullScreenPlayer() { player.handleEvent(CSPlayerEvent.Pause) val currentSubtitles = sortSubs(currentSubs) - val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) - sourceDialog.setContentView(R.layout.player_select_source_and_subs) + val sourceBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack) + .setView(R.layout.player_select_source_and_subs) + + val sourceDialog = sourceBuilder.create() selectSourceDialog = sourceDialog @@ -733,17 +738,19 @@ class GeneratorPlayer : FullScreenPlayer() { } val currentAudioTracks = tracks.allAudioTracks - val trackDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) - trackDialog.setContentView(R.layout.player_select_tracks) - trackDialog.show() + val trackBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack) + .setView(R.layout.player_select_tracks) + + val tracksDialog = trackBuilder.create() // selectTracksDialog = tracksDialog - val videosList = trackDialog.video_tracks_list - val audioList = trackDialog.auto_tracks_list + tracksDialog.show() + val videosList = tracksDialog.video_tracks_list + val audioList = tracksDialog.auto_tracks_list - trackDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1 - trackDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1 + tracksDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1 + tracksDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1 fun dismiss() { if (isPlaying) { @@ -778,7 +785,7 @@ class GeneratorPlayer : FullScreenPlayer() { videosList.setItemChecked(which, true) } - trackDialog.setOnDismissListener { + tracksDialog.setOnDismissListener { dismiss() // selectTracksDialog = null } @@ -808,11 +815,11 @@ class GeneratorPlayer : FullScreenPlayer() { audioList.setItemChecked(which, true) } - trackDialog.cancel_btt?.setOnClickListener { - trackDialog.dismissSafe(activity) + tracksDialog.cancel_btt?.setOnClickListener { + tracksDialog.dismissSafe(activity) } - trackDialog.apply_btt?.setOnClickListener { + tracksDialog.apply_btt?.setOnClickListener { val currentTrack = currentAudioTracks.getOrNull(audioIndexStart) player.setPreferredAudioTrack( currentTrack?.language, currentTrack?.id @@ -825,7 +832,7 @@ class GeneratorPlayer : FullScreenPlayer() { player.setMaxVideoSize(width, height, currentVideo?.id) } - trackDialog.dismissSafe(activity) + tracksDialog.dismissSafe(activity) } } } catch (e: Exception) { @@ -1142,7 +1149,7 @@ class GeneratorPlayer : FullScreenPlayer() { val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL" - val title = when (titleRez) { + val title = when (titleRez) { 0 -> "" 1 -> extra 2 -> source diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt index 0b560857..1f242481 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt @@ -5,15 +5,8 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.utils.* import java.net.URI -/** - * Used to open the player more easily with the LinkGenerator - **/ -data class BasicLink( - val url: String, - val name: String? = null, -) class LinkGenerator( - private val links: List, + private val links: List, private val extract: Boolean = true, private val referer: String? = null, private val isM3u8: Boolean? = null @@ -54,7 +47,7 @@ class LinkGenerator( offset: Int ): Boolean { links.amap { link -> - if (!extract || !loadExtractor(link.url, referer, { + if (!extract || !loadExtractor(link, referer, { subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it)) }) { callback(it to null) @@ -64,11 +57,11 @@ class LinkGenerator( callback( ExtractorLink( "", - link.name ?: link.url, - unshortenLinkSafe(link.url), // unshorten because it might be a raw link + link, + unshortenLinkSafe(link), // unshorten because it might be a raw link referer ?: "", Qualities.Unknown.value, isM3u8 ?: normalSafeApiCall { - URI(link.url).path?.substringAfterLast(".")?.contains("m3u") + URI(link).path?.substringAfterLast(".")?.contains("m3u") } ?: false ) to null ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt index affbcbb4..59a46264 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt @@ -7,13 +7,13 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.mvvm.logError fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) { - if (this == null) return + if(this == null) return this.layoutManager = this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } } ?: this.layoutManager } -open class LinearListLayout(context: Context?) : +class LinearListLayout(context: Context?) : LinearLayoutManager(context) { fun setHorizontal() { @@ -24,8 +24,7 @@ open class LinearListLayout(context: Context?) : orientation = VERTICAL } - private fun getCorrectParent(focused: View?): View? { - if (focused == null) return null + private fun getCorrectParent(focused: View): View? { var current: View? = focused val last: ArrayList = arrayListOf(focused) while (current != null && current !is RecyclerView) { @@ -55,17 +54,10 @@ open class LinearListLayout(context: Context?) : linearSmoothScroller.targetPosition = position startSmoothScroll(linearSmoothScroller) }*/ + override fun onInterceptFocusSearch(focused: View, direction: Int): View? { val dir = if (orientation == HORIZONTAL) { - if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) { - // This scrolls the recyclerview before doing focus search, which - // allows the focus search to work better. - - // Without this the recyclerview focus location on the screen - // would change when scrolling between recyclerviews. - (focused.parent as? RecyclerView)?.focusSearch(direction) - return null - } + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) return null if (direction == View.FOCUS_RIGHT) 1 else -1 } else { if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 5a3e28b4..2e2e46b7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -15,28 +15,24 @@ import android.view.ViewGroup import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.ImageView -import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView import com.discord.panels.OverlappingPanelsLayout import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.updateHasTrailers -import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.* -import com.lagradost.cloudstream3.services.SubscriptionWorkManager import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD @@ -532,25 +528,6 @@ open class ResultFragment : ResultTrailerPlayer() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - result_cast_items?.layoutManager = object : LinearListLayout(view.context) { - override fun onRequestChildFocus( - parent: RecyclerView, - state: RecyclerView.State, - child: View, - focused: View? - ): Boolean { - // Make the cast always focus the first visible item when focused - // from somewhere else. Otherwise it jumps to the last item. - return if (parent.focusedChild == null) { - scrollToPosition(this.findFirstCompletelyVisibleItemPosition()) - true - } else { - super.onRequestChildFocus(parent, state, child, focused) - } - } - }.apply { - this.orientation = RecyclerView.HORIZONTAL - } result_cast_items?.adapter = ActorAdaptor() updateUIListener = ::updateUI @@ -873,7 +850,7 @@ open class ResultFragment : ResultTrailerPlayer() { } observe(viewModel.page) { data -> - if (data == null) return@observe + if(data == null) return@observe when (data) { is Resource.Success -> { val d = data.value @@ -927,36 +904,6 @@ open class ResultFragment : ResultTrailerPlayer() { updateList(d.actors ?: emptyList()) } - observeNullable(viewModel.subscribeStatus) { isSubscribed -> - result_subscribe?.isVisible = isSubscribed != null - if (isSubscribed == null) return@observeNullable - - val drawable = if (isSubscribed) { - R.drawable.ic_baseline_notifications_active_24 - } else { - R.drawable.baseline_notifications_none_24 - } - - result_subscribe?.setImageResource(drawable) - } - - result_subscribe?.setOnClickListener { - val isSubscribed = - viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener - - val message = if (isSubscribed) { - // Kinda icky to have this here, but it works. - SubscriptionWorkManager.enqueuePeriodicWork(context) - R.string.subscription_new - } else { - R.string.subscription_deleted - } - - val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - showToast(activity, txt(message, name), Toast.LENGTH_SHORT) - } - result_open_in_browser?.isVisible = d.url.startsWith("http") result_open_in_browser?.setOnClickListener { val i = Intent(ACTION_VIEW) @@ -1027,7 +974,6 @@ open class ResultFragment : ResultTrailerPlayer() { chip.isCheckable = false chip.isFocusable = false chip.isClickable = false - chip.setTextColor(context.colorFromAttribute(R.attr.textColor)) addView(chip) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 2f232995..b38e1765 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -322,7 +322,9 @@ class ResultFragmentPhone : ResultFragment() { // it?.dismiss() //} builder.setCanceledOnTouchOutside(true) + builder.show() + builder } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 71ecb0e9..2bd8ff0f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -176,7 +176,8 @@ class ResultFragmentTv : ResultFragment() { loadingDialog = null } loadingDialog = loadingDialog ?: context?.let { ctx -> - val builder = BottomSheetDialog(ctx) + val builder = + BottomSheetDialog(ctx) builder.setContentView(R.layout.bottom_loading) builder.setOnDismissListener { loadingDialog = null @@ -186,7 +187,9 @@ class ResultFragmentTv : ResultFragment() { // it?.dismiss() //} builder.setCanceledOnTouchOutside(true) + builder.show() + builder } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 46a8c9f6..6817af6a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result import android.app.Activity import android.content.* import android.net.Uri -import android.os.Build import android.os.Bundle import android.util.Log import android.widget.Toast @@ -17,7 +16,6 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.unixTime -import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast @@ -27,7 +25,6 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.mvvm.* -import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.APIRepository @@ -416,9 +413,6 @@ class ResultViewModel2 : ViewModel() { private val _episodeSynopsis: MutableLiveData = MutableLiveData(null) val episodeSynopsis: LiveData = _episodeSynopsis - private val _subscribeStatus: MutableLiveData = MutableLiveData(null) - val subscribeStatus: LiveData = _subscribeStatus - companion object { const val TAG = "RVM2" private const val EPISODE_RANGE_SIZE = 20 @@ -820,42 +814,6 @@ class ResultViewModel2 : ViewModel() { } } - /** - * @return true if the new status is Subscribed, false if not. Null if not possible to subscribe. - **/ - fun toggleSubscriptionStatus(): Boolean? { - val isSubscribed = _subscribeStatus.value ?: return null - val response = currentResponse ?: return null - if (response !is EpisodeResponse) return null - - val currentId = response.getId() - - if (isSubscribed) { - DataStoreHelper.removeSubscribedData(currentId) - } else { - val current = DataStoreHelper.getSubscribedData(currentId) - - DataStoreHelper.setSubscribedData( - currentId, - DataStoreHelper.SubscribedData( - currentId, - current?.bookmarkedTime ?: unixTimeMS, - unixTimeMS, - response.getLatestEpisodes(), - response.name, - response.url, - response.apiName, - response.type, - response.posterUrl, - response.year - ) - ) - } - - _subscribeStatus.postValue(!isSubscribed) - return !isSubscribed - } - private fun startChromecast( activity: Activity?, result: ResultEpisode, @@ -1126,12 +1084,7 @@ class ResultViewModel2 : ViewModel() { 1L } - // Component no longer safe to use in A13 for VLC - // https://code.videolan.org/videolan/vlc-android/-/issues/2776 - // This will likely need to be updated once VLC fixes their documentation. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - component = VLC_COMPONENT - } + component = VLC_COMPONENT putExtra("from_start", !resume) putExtra("position", position) @@ -1469,128 +1422,85 @@ class ResultViewModel2 : ViewModel() { meta: SyncAPI.SyncResult?, syncs: Map? = null ): Pair { - //if (meta == null) return resp to false + if (meta == null) return resp to false var updateEpisodes = false val out = resp.apply { Log.i(TAG, "applyMeta") - if (meta != null) { - duration = duration ?: meta.duration - rating = rating ?: meta.publicScore - tags = tags ?: meta.genres - plot = if (plot.isNullOrBlank()) meta.synopsis else plot - posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl - actors = actors ?: meta.actors + duration = duration ?: meta.duration + rating = rating ?: meta.publicScore + tags = tags ?: meta.genres + plot = if (plot.isNullOrBlank()) meta.synopsis else plot + posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl + actors = actors ?: meta.actors - if (this is EpisodeResponse) { - nextAiring = nextAiring ?: meta.nextAiring - } - - val realRecommendations = ArrayList() - val apiNames = apis.filter { - it.name.contains("gogoanime", true) || - it.name.contains("9anime", true) - }.map { - it.name - } - - meta.recommendations?.forEach { rec -> - apiNames.forEach { name -> - realRecommendations.add(rec.copy(apiName = name)) - } - } - - recommendations = recommendations?.union(realRecommendations)?.toList() - ?: realRecommendations + if (this is EpisodeResponse) { + nextAiring = nextAiring ?: meta.nextAiring } for ((k, v) in syncs ?: emptyMap()) { syncData[k] = v } - argamap( - { - if (this !is AnimeLoadResponse) return@argamap - // already exist, no need to run getTracker - if (this.getAniListId() != null && this.getMalId() != null) return@argamap + val realRecommendations = ArrayList() + // TODO: fix + val apiNames = apis.filter { + it.name.contains("gogoanime", true) || + it.name.contains("9anime", true) + }.map { + it.name + } - val res = APIHolder.getTracker( - listOfNotNull( - this.engName, - this.name, - this.japName - ).filter { it.length > 2 } - .distinct(), // the reason why we filter is due to not wanting smth like " " or "?" - TrackerType.getTypes(this.type), - this.year + meta.recommendations?.forEach { rec -> + apiNames.forEach { name -> + realRecommendations.add(rec.copy(apiName = name)) + } + } + + recommendations = recommendations?.union(realRecommendations)?.toList() + ?: realRecommendations + + argamap({ + addTrailer(meta.trailers) + }, { + if (this !is AnimeLoadResponse) return@argamap + val map = + Kitsu.getEpisodesDetails( + getMalId(), + getAniListId(), + isResponseRequired = false ) - - val ids = arrayOf( - AccountManager.malApi.idPrefix to res?.malId?.toString(), - AccountManager.aniListApi.idPrefix to res?.aniId - ) - - if (ids.any { (id, new) -> - val current = syncData[id] - new != null && current != null && current != new - } - ) { - // getTracker fucked up as it conflicts with current implementation - return@argamap - } - - // set all the new data, prioritise old correct data - ids.forEach { (id, new) -> - new?.let { - syncData[id] = syncData[id] ?: it - } - } - - // set posters, might fuck up due to headers idk - posterUrl = posterUrl ?: res?.image - backgroundPosterUrl = backgroundPosterUrl ?: res?.cover - }, - { - if (meta == null) return@argamap - addTrailer(meta.trailers) - }, { - if (this !is AnimeLoadResponse) return@argamap - val map = - Kitsu.getEpisodesDetails( - getMalId(), - getAniListId(), - isResponseRequired = false - ) - if (map.isNullOrEmpty()) return@argamap - updateEpisodes = DubStatus.values().map { dubStatus -> - val current = - this.episodes[dubStatus]?.mapIndexed { index, episode -> - episode.apply { - this.episode = this.episode ?: (index + 1) - } - }?.sortedBy { it.episode ?: 0 }?.toMutableList() - if (current.isNullOrEmpty()) return@map false - val episodeNumbers = current.map { ep -> ep.episode!! } - var updateCount = 0 - map.forEach { (episode, node) -> - episodeNumbers.binarySearch(episode).let { index -> - current.getOrNull(index)?.let { currentEp -> - current[index] = currentEp.apply { - updateCount++ - this.description = this.description ?: node.description?.en - this.name = this.name ?: node.titles?.canonical - this.episode = - this.episode ?: node.num ?: episodeNumbers[index] - this.posterUrl = - this.posterUrl ?: node.thumbnail?.original?.url - } + if (map.isNullOrEmpty()) return@argamap + updateEpisodes = DubStatus.values().map { dubStatus -> + val current = + this.episodes[dubStatus]?.mapIndexed { index, episode -> + episode.apply { + this.episode = this.episode ?: (index + 1) + } + }?.sortedBy { it.episode ?: 0 }?.toMutableList() + if (current.isNullOrEmpty()) return@map false + val episodeNumbers = current.map { ep -> ep.episode!! } + var updateCount = 0 + map.forEach { (episode, node) -> + episodeNumbers.binarySearch(episode).let { index -> + current.getOrNull(index)?.let { currentEp -> + current[index] = currentEp.apply { + updateCount++ + val currentBack = this + this.description = this.description ?: node.description?.en + this.name = this.name ?: node.titles?.canonical + this.episode = + this.episode ?: node.num ?: episodeNumbers[index] + this.posterUrl = + this.posterUrl ?: node.thumbnail?.original?.url } } } - this.episodes[dubStatus] = current - updateCount > 0 - }.any { it } - }) + } + this.episodes[dubStatus] = current + updateCount > 0 + }.any { it } + }) } return out to updateEpisodes } @@ -1717,16 +1627,6 @@ class ResultViewModel2 : ViewModel() { postResume() } - private fun postSubscription(loadResponse: LoadResponse) { - if (loadResponse.isEpisodeBased()) { - val id = loadResponse.getId() - val data = DataStoreHelper.getSubscribedData(id) - DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse) - val isSubscribed = data != null - _subscribeStatus.postValue(isSubscribed) - } - } - private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { if (range == null || indexer == null) { return @@ -1863,7 +1763,6 @@ class ResultViewModel2 : ViewModel() { ) { currentResponse = loadResponse postPage(loadResponse, apiRepository) - postSubscription(loadResponse) if (updateEpisodes) postEpisodes(loadResponse, updateFillers) } @@ -2225,7 +2124,7 @@ class ResultViewModel2 : ViewModel() { autostart: AutoResume?, loadTrailers: Boolean = true, ) = - ioSafe { + viewModelScope.launchSafe { _page.postValue(Resource.Loading(url)) _episodes.postValue(ResourceSome.Loading()) @@ -2243,7 +2142,7 @@ class ResultViewModel2 : ViewModel() { "This provider does not exist" ) ) - return@ioSafe + return@launchSafe } @@ -2254,15 +2153,21 @@ class ResultViewModel2 : ViewModel() { api ) } - + // TODO: fix + // val validUrlResource = safeApiCall { + // SyncRedirector.redirect( + // url, + // api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime") + // .replace(GogoanimeProvider().mainUrl, "gogoanime") + // ) + // } if (validUrlResource !is Resource.Success) { if (validUrlResource is Resource.Failure) { _page.postValue(validUrlResource) } - return@ioSafe + return@launchSafe } - val validUrl = validUrlResource.value val repo = APIRepository(api) currentRepo = repo @@ -2272,11 +2177,11 @@ class ResultViewModel2 : ViewModel() { _page.postValue(data) } is Resource.Success -> { - if (!isActive) return@ioSafe + if (!isActive) return@launchSafe val loadResponse = ioWork { applyMeta(data.value, currentMeta, currentSync).first } - if (!isActive) return@ioSafe + if (!isActive) return@launchSafe val mainId = loadResponse.getId() preferDubStatus = getDub(mainId) ?: preferDubStatus @@ -2304,7 +2209,7 @@ class ResultViewModel2 : ViewModel() { updateFillers = showFillers, apiRepository = repo ) - if (!isActive) return@ioSafe + if (!isActive) return@launchSafe handleAutoStart(activity, autostart) } is Resource.Loading -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 1ef3cb55..f9627e46 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -157,28 +157,6 @@ class SettingsAccount : PreferenceFragmentCompat() { ) dialog.dismissSafe() } - - val displayedItems = listOf( - dialog.login_username_input, - dialog.login_email_input, - dialog.login_server_input, - dialog.login_password_input - ).filter { it.isVisible } - - displayedItems.foldRight(displayedItems.firstOrNull()) { item, previous -> - item?.id?.let { previous?.nextFocusDownId = it } - previous?.id?.let { item?.nextFocusUpId = it } - item - } - - displayedItems.firstOrNull()?.let { - dialog.create_account?.nextFocusDownId = it.id - it.nextFocusUpId = dialog.create_account.id - } - dialog.apply_btt?.id?.let { - displayedItems.lastOrNull()?.nextFocusDownId = it - } - dialog.text1?.text = api.name if (api.storesPasswordInPlainText) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 4aa859aa..3f1c781a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -56,50 +56,48 @@ fun getCurrentLocale(context: Context): String { // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto val appLanguages = arrayListOf( /* begin language list */ - Triple("", "العربية", "ar"), - Triple("", "български", "bg"), - Triple("", "বাংলা", "bn"), - Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"), - Triple("", "čeština", "cs"), - Triple("", "Deutsch", "de"), - Triple("", "Ελληνικά", "el"), + Triple("", "Arabic", "ar"), + Triple("", "Bulgarian", "bg"), + Triple("", "Bengali", "bn"), + Triple("\uD83C\uDDE7\uD83C\uDDF7", "Brazilian Portuguese", "bp"), + Triple("", "Czech", "cs"), + Triple("", "German", "de"), + Triple("", "Greek", "el"), Triple("", "English", "en"), Triple("", "Esperanto", "eo"), - Triple("", "español", "es"), - Triple("", "فارسی", "fa"), - Triple("", "français", "fr"), - Triple("", "हिन्दी", "hi"), - Triple("", "hrvatski", "hr"), - Triple("", "magyar", "hu"), - Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"), - Triple("", "italiano", "it"), - Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"), - Triple("", "日本語 (にほんご)", "ja"), - Triple("", "ಕನ್ನಡ", "kn"), - Triple("", "македонски", "mk"), - Triple("", "മലയാളം", "ml"), - Triple("", "bahasa Melayu", "ms"), - Triple("", "Nederlands", "nl"), - Triple("", "norsk nynorsk", "nn"), - Triple("", "norsk bokmål", "no"), - Triple("", "polski", "pl"), - Triple("\uD83C\uDDF5\uD83C\uDDF9", "português", "pt"), - Triple("\uD83E\uDD8D", "mmmm... monke", "qt"), - Triple("", "română", "ro"), - Triple("", "русский", "ru"), - Triple("", "slovenčina", "sk"), - Triple("", "Soomaaliga", "so"), - Triple("", "svenska", "sv"), - Triple("", "தமிழ்", "ta"), + Triple("", "Spanish", "es"), + Triple("", "Farsi", "fa"), + Triple("", "French", "fr"), + Triple("", "Hindi", "hi"), + Triple("", "Croatian", "hr"), + Triple("", "Hungarian", "hu"), + Triple("\uD83C\uDDEE\uD83C\uDDE9", "Indonesian", "in"), + Triple("", "Italian", "it"), + Triple("\uD83C\uDDEE\uD83C\uDDF1", "Hebrew", "iw"), + Triple("", "Kannada", "kn"), + Triple("", "Macedonian", "mk"), + Triple("", "Malayalam", "ml"), + Triple("", "Moldavian", "mo"), + Triple("", "Dutch", "nl"), + Triple("", "Norwegian Nynorsk", "nn"), + Triple("", "Norwegian", "no"), + Triple("", "Polish", "pl"), + Triple("\uD83C\uDDF5\uD83C\uDDF9", "Portuguese", "pt"), + Triple("", "Romanian", "ro"), + Triple("", "Russian", "ru"), + Triple("", "Slovak", "sk"), + Triple("", "Somali", "so"), + Triple("", "Swedish", "sv"), + Triple("", "Tamil", "ta"), Triple("", "Tagalog", "tl"), - Triple("", "Türkçe", "tr"), - Triple("", "українська", "uk"), - Triple("", "اردو", "ur"), - Triple("", "Tiếng Việt", "vi"), - Triple("", "中文", "zh"), - Triple("\uD83C\uDDF9\uD83C\uDDFC", "文言", "zh-rTW"), + Triple("", "Turkish", "tr"), + Triple("", "Ukrainian", "uk"), + Triple("", "Urdu", "ur"), + Triple("", "Viet Nam", "vi"), + Triple("", "Chinese Simplified", "zh"), + Triple("\uD83C\uDDF9\uD83C\uDDFC", "Chinese Traditional", "zh-rTW"), /* end language list */ -).sortedBy { it.second.lowercase() } //ye, we go alphabetical, so ppl don't put their lang on top +).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top class SettingsGeneral : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -159,6 +157,9 @@ class SettingsGeneral : PreferenceFragmentCompat() { getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> val tempLangs = appLanguages.toMutableList() + //if (beneneCount > 100) { + // tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo")) + //} val current = getCurrentLocale(pref.context) val languageCodes = tempLangs.map { (_, _, iso) -> iso } val languageNames = tempLangs.map { (emoji, name, iso) -> @@ -315,12 +316,6 @@ class SettingsGeneral : PreferenceFragmentCompat() { } ?: emptyList() } - settingsManager.edit().putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false).apply() - getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue -> - setKey(getString(R.string.jsdelivr_proxy_key), newValue) - return@setOnPreferenceChangeListener true - } - getPref(R.string.download_path_key)?.setOnPreferenceClickListener { val dirs = getDownloadDirs() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index e10a5a1a..33d41934 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -113,30 +113,6 @@ class SettingsPlayer : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.quality_pref_mobile_data_key)?.setOnPreferenceClickListener { - val prefValues = Qualities.values().map { it.value }.reversed().toMutableList() - prefValues.remove(Qualities.Unknown.value) - - val prefNames = prefValues.map { Qualities.getStringByInt(it) } - - val currentQuality = - settingsManager.getInt( - getString(R.string.quality_pref_mobile_data_key), - Qualities.values().last().value - ) - - activity?.showBottomDialog( - prefNames.toList(), - prefValues.indexOf(currentQuality), - getString(R.string.watch_quality_pref_data), - true, - {}) { - settingsManager.edit().putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it]) - .apply() - } - return@setOnPreferenceClickListener true - } - getPref(R.string.player_pref_key)?.setOnPreferenceClickListener { val prefNames = resources.getStringArray(R.array.player_pref_names) val prefValues = resources.getIntArray(R.array.player_pref_values) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index 42a864a6..3b01508d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -2,8 +2,6 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.view.View -import androidx.navigation.NavOptions -import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.* @@ -18,7 +16,6 @@ import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard -import com.lagradost.cloudstream3.utils.UIHelper.navigate class SettingsProviders : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -59,20 +56,6 @@ class SettingsProviders : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.test_providers_key)?.setOnPreferenceClickListener { - // Somehow animations do not work without this. - val options = NavOptions.Builder() - .setEnterAnim(R.anim.enter_anim) - .setExitAnim(R.anim.exit_anim) - .setPopEnterAnim(R.anim.pop_enter) - .setPopExitAnim(R.anim.pop_exit) - .build() - - this@SettingsProviders.findNavController() - .navigate(R.id.navigation_test_providers, null, options) - true - } - getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener { val names = enumValues().sorted().map { it.name } val default = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt deleted file mode 100644 index 34cd67cd..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.fragment.app.activityViewModels -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.observe -import com.lagradost.cloudstream3.mvvm.observeNullable -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar -import kotlinx.android.synthetic.main.fragment_testing.* -import kotlinx.android.synthetic.main.view_test.* - - -class TestFragment : Fragment() { - - private val testViewModel: TestViewModel by activityViewModels() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - setUpToolbar(R.string.category_provider_test) - super.onViewCreated(view, savedInstanceState) - - provider_test_recycler_view?.adapter = TestResultAdapter( - mutableListOf() - ) - - testViewModel.init() - if (testViewModel.isRunningTest) { - provider_test?.setState(TestView.TestState.Running) - } - - observe(testViewModel.providerProgress) { (passed, failed, total) -> - provider_test?.setProgress(passed, failed, total) - } - - observeNullable(testViewModel.providerResults) { - normalSafeApiCall { - val newItems = it.sortedBy { api -> api.first.name } - (provider_test_recycler_view?.adapter as? TestResultAdapter)?.updateList( - newItems - ) - } - } - - provider_test?.setOnPlayButtonListener { state -> - when (state) { - TestView.TestState.Stopped -> testViewModel.stopTest() - TestView.TestState.Running -> testViewModel.startTest() - TestView.TestState.None -> testViewModel.startTest() - } - } - - if (isTrueTvSettings()) { - tests_play_pause?.isFocusableInTouchMode = true - tests_play_pause?.requestFocus() - } - - provider_test?.playPauseButton?.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - provider_test_appbar?.setExpanded(true, true) - } - } - - fun focusRecyclerView() { - // Hack to make it possible to focus the recyclerview. - if (isTrueTvSettings()) { - provider_test_recycler_view?.requestFocus() - provider_test_appbar?.setExpanded(false, true) - } - } - - provider_test?.setOnMainClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All) - focusRecyclerView() - } - provider_test?.setOnFailedClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed) - focusRecyclerView() - } - provider_test?.setOnPassedClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed) - focusRecyclerView() - } - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_testing, container, false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt deleted file mode 100644 index d04e2379..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import android.app.AlertDialog -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.getAllMessages -import com.lagradost.cloudstream3.mvvm.getStackTracePretty -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso -import com.lagradost.cloudstream3.utils.TestingUtils -import kotlinx.android.synthetic.main.provider_test_item.view.* - -class TestResultAdapter(override val items: MutableList>) : - AppUtils.DiffAdapter>(items) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return ProviderTestViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.provider_test_item, parent, false), - ) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is ProviderTestViewHolder -> { - val item = items[position] - holder.bind(item.first, item.second) - } - } - } - - inner class ProviderTestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - private val languageText: TextView = itemView.lang_icon - private val providerTitle: TextView = itemView.main_text - private val statusText: TextView = itemView.passed_failed_marker - private val failDescription: TextView = itemView.fail_description - private val logButton: ImageView = itemView.action_button - - private fun String.lastLine(): String? { - return this.lines().lastOrNull { it.isNotBlank() } - } - - fun bind(api: MainAPI, result: TestingUtils.TestResultProvider) { - languageText.text = getFlagFromIso(api.lang) - providerTitle.text = api.name - - val (resultText, resultColor) = if (result.success) { - R.string.test_passed to R.color.colorTestPass - } else { - R.string.test_failed to R.color.colorTestFail - } - - statusText.setText(resultText) - statusText.setTextColor(ContextCompat.getColor(itemView.context, resultColor)) - - val stackTrace = result.exception?.getStackTracePretty(false)?.ifBlank { null } - val messages = result.exception?.getAllMessages()?.ifBlank { null } - val fullLog = - result.log + (messages?.let { "\n\n$it" } ?: "") + (stackTrace?.let { "\n\n$it" } ?: "") - - failDescription.text = messages?.lastLine() ?: result.log.lastLine() - - logButton.setOnClickListener { - val builder: AlertDialog.Builder = - AlertDialog.Builder(it.context, R.style.AlertDialogCustom) - builder.setMessage(fullLog) - .setTitle(R.string.test_log) - .show() - } - } - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestView.kt deleted file mode 100644 index 26513f4a..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestView.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.TextView -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.cardview.widget.CardView -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.core.widget.ContentLoadingProgressBar -import com.google.android.material.button.MaterialButton -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.AppUtils.animateProgressTo - -class TestView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : CardView(context, attrs) { - enum class TestState(@StringRes val stringRes: Int, @DrawableRes val icon: Int) { - None(R.string.start, R.drawable.ic_baseline_play_arrow_24), - - // Paused(R.string.resume, R.drawable.ic_baseline_play_arrow_24), - Stopped(R.string.restart, R.drawable.ic_baseline_play_arrow_24), - Running(R.string.stop, R.drawable.pause_to_play), - } - - var mainSection: View? = null - var testsPassedSection: View? = null - var testsFailedSection: View? = null - - var mainSectionText: TextView? = null - var mainSectionHeader: TextView? = null - var testsPassedSectionText: TextView? = null - var testsFailedSectionText: TextView? = null - var totalProgressBar: ContentLoadingProgressBar? = null - - var playPauseButton: MaterialButton? = null - var stateListener: (TestState) -> Unit = {} - - private var state = TestState.None - - init { - LayoutInflater.from(context).inflate(R.layout.view_test, this, true) - - mainSection = findViewById(R.id.main_test_section) - testsPassedSection = findViewById(R.id.passed_test_section) - testsFailedSection = findViewById(R.id.failed_test_section) - - mainSectionHeader = findViewById(R.id.main_test_header) - mainSectionText = findViewById(R.id.main_test_section_progress) - testsPassedSectionText = findViewById(R.id.passed_test_section_progress) - testsFailedSectionText = findViewById(R.id.failed_test_section_progress) - - totalProgressBar = findViewById(R.id.test_total_progress) - playPauseButton = findViewById(R.id.tests_play_pause) - - attrs?.let { - val typedArray = context.obtainStyledAttributes(it, R.styleable.TestView) - val headerText = typedArray.getString(R.styleable.TestView_header_text) - mainSectionHeader?.text = headerText - typedArray.recycle() - } - - playPauseButton?.setOnClickListener { - val newState = when (state) { - TestState.None -> TestState.Running - TestState.Running -> TestState.Stopped - TestState.Stopped -> TestState.Running - } - setState(newState) - } - } - - fun setOnPlayButtonListener(listener: (TestState) -> Unit) { - stateListener = listener - } - - fun setState(newState: TestState) { - state = newState - stateListener.invoke(newState) - playPauseButton?.setText(newState.stringRes) - playPauseButton?.icon = ContextCompat.getDrawable(context, newState.icon) - } - - fun setProgress(passed: Int, failed: Int, total: Int?) { - val totalProgress = passed + failed - mainSectionText?.text = "$totalProgress / ${total?.toString() ?: "?"}" - testsPassedSectionText?.text = passed.toString() - testsFailedSectionText?.text = failed.toString() - - totalProgressBar?.max = (total ?: 0) * 1000 - totalProgressBar?.animateProgressTo(totalProgress * 1000) - - totalProgressBar?.isVisible = !(totalProgress == 0 || (total ?: 0) == 0) - if (totalProgress == total) { - setState(TestState.Stopped) - } - } - - fun setMainHeader(@StringRes header: Int) { - mainSectionHeader?.setText(header) - } - - fun setOnMainClick(listener: OnClickListener) { - mainSection?.setOnClickListener(listener) - } - - fun setOnPassedClick(listener: OnClickListener) { - testsPassedSection?.setOnClickListener(listener) - } - - fun setOnFailedClick(listener: OnClickListener) { - testsFailedSection?.setOnClickListener(listener) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt deleted file mode 100644 index 2e05baff..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.APIHolder -import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf -import com.lagradost.cloudstream3.utils.TestingUtils -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel - -class TestViewModel : ViewModel() { - data class TestProgress( - val passed: Int, - val failed: Int, - val total: Int - ) - - enum class ProviderFilter { - All, - Passed, - Failed - } - - private val _providerProgress = MutableLiveData(null) - val providerProgress: LiveData = _providerProgress - - private val _providerResults = - MutableLiveData>>( - emptyList() - ) - - val providerResults: LiveData>> = - _providerResults - - private var scope: CoroutineScope? = null - val isRunningTest - get() = scope != null - - private var filter = ProviderFilter.All - private val providers = threadSafeListOf>() - private var passed = 0 - private var failed = 0 - private var total = 0 - - private fun updateProgress() { - _providerProgress.postValue(TestProgress(passed, failed, total)) - postProviders() - } - - private fun postProviders() { - synchronized(providers) { - val filtered = when (filter) { - ProviderFilter.All -> providers - ProviderFilter.Passed -> providers.filter { it.second.success } - ProviderFilter.Failed -> providers.filter { !it.second.success } - } - _providerResults.postValue(filtered) - } - } - - fun setFilterMethod(filter: ProviderFilter) { - if (this.filter == filter) return - this.filter = filter - postProviders() - } - - private fun addProvider(api: MainAPI, results: TestingUtils.TestResultProvider) { - synchronized(providers) { - val index = providers.indexOfFirst { it.first == api } - if (index == -1) { - providers.add(api to results) - if (results.success) passed++ else failed++ - } else { - providers[index] = api to results - } - updateProgress() - } - } - - fun init() { - val apis = APIHolder.allProviders - total = apis.size - updateProgress() - } - - fun startTest() { - scope = CoroutineScope(Dispatchers.Default) - - val apis = APIHolder.allProviders - total = apis.size - failed = 0 - passed = 0 - providers.clear() - updateProgress() - - TestingUtils.getDeferredProviderTests(scope ?: return, apis, ::println) { api, result -> - addProvider(api, result) - } - } - - fun stopTest() { - scope?.cancel() - scope = null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index a76b62fd..00dee9b2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -1,11 +1,8 @@ package com.lagradost.cloudstream3.utils -import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.app.Activity import android.app.Activity.RESULT_CANCELED -import android.app.NotificationChannel -import android.app.NotificationManager import android.content.* import android.content.pm.PackageManager import android.database.Cursor @@ -20,7 +17,6 @@ import android.os.* import android.provider.MediaStore import android.text.Spanned import android.util.Log -import android.view.animation.DecelerateInterpolator import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi @@ -29,7 +25,6 @@ import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned -import androidx.core.widget.ContentLoadingProgressBar import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.findNavController @@ -184,36 +179,6 @@ object AppUtils { touchSlopField.set(recyclerView, touchSlop * f) // "8" was obtained experimentally } - fun ContentLoadingProgressBar?.animateProgressTo(to: Int) { - if (this == null) return - val animation: ObjectAnimator = ObjectAnimator.ofInt( - this, - "progress", - this.progress, - to - ) - animation.duration = 500 - animation.setAutoCancel(true) - animation.interpolator = DecelerateInterpolator() - animation.start() - } - - fun Context.createNotificationChannel(channelId: String, channelName: String, description: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val importance = NotificationManager.IMPORTANCE_DEFAULT - val channel = - NotificationChannel(channelId, channelName, importance).apply { - this.description = description - } - - // Register the channel with the system. - val notificationManager: NotificationManager = - this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - notificationManager.createNotificationChannel(channel) - } - } - @SuppressLint("RestrictedApi") fun getAllWatchNextPrograms(context: Context): Set { val COLUMN_WATCH_NEXT_ID_INDEX = 0 @@ -491,12 +456,6 @@ object AppUtils { } } - fun Context.isNetworkAvailable(): Boolean { - val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetworkInfo = manager.activeNetworkInfo - return activeNetworkInfo != null && activeNetworkInfo.isConnected || manager.allNetworkInfo?.any { it.isConnected } ?: false - } - fun splitQuery(url: URL): Map { val queryPairs: MutableMap = LinkedHashMap() val query: String = url.query @@ -776,13 +735,8 @@ object AppUtils { return networkInfo.any { conManager.getNetworkCapabilities(it) ?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true - } && - !networkInfo.any { - conManager.getNetworkCapabilities(it) - ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true - } } - + } private fun Activity?.cacheClass(clazz: String?) { clazz?.let { c -> @@ -826,4 +780,4 @@ object AppUtils { } return currentAudioFocusRequest } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 2318fda6..8d51e5ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -9,7 +9,6 @@ import android.provider.MediaStore import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.WorkerThread import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue @@ -28,8 +27,6 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_T import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.mapper @@ -120,7 +117,6 @@ object BackupUtils { ) } - @WorkerThread fun Context.restore( backupFile: BackupFile, restoreSettings: Boolean, @@ -223,29 +219,31 @@ object BackupUtils { try { restoreFileSelector = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - val activity = this - ioSafe { - try { - val input = activity.contentResolver.openInputStream(uri) - ?: return@ioSafe + this.let { activity -> + uri?.let { + try { + val input = + activity.contentResolver.openInputStream(uri) + ?: return@registerForActivityResult - val restoredValue = - mapper.readValue(input) - - activity.restore( - restoredValue, - restoreSettings = true, - restoreDataStore = true - ) - activity.runOnUiThread { activity.recreate() } - } catch (e: Exception) { - logError(e) - main { // smth can fail in .format - showToast( - activity, - getString(R.string.restore_failed_format).format(e.toString()) + val restoredValue = + mapper.readValue(input) + activity.restore( + restoredValue, + restoreSettings = true, + restoreDataStore = true ) + activity.recreate() + } catch (e: Exception) { + logError(e) + try { // smth can fail in .format + showToast( + activity, + getString(R.string.restore_failed_format).format(e.toString()) + ) + } catch (e: Exception) { + logError(e) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 516cd990..281c9c44 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,13 +1,16 @@ package com.lagradost.cloudstream3.utils import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.unixTimeMS +import com.lagradost.cloudstream3.APIHolder.capitalize import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.WatchType @@ -17,7 +20,6 @@ const val VIDEO_POS_DUR = "video_pos_dur" const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" -const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" @@ -40,37 +42,6 @@ object DataStoreHelper { return this } - /** - * Used to display notifications on new episodes and posters in library. - **/ - data class SubscribedData( - @JsonProperty("id") override var id: Int?, - @JsonProperty("subscribedTime") val bookmarkedTime: Long, - @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, - @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, - @JsonProperty("name") override val name: String, - @JsonProperty("url") override val url: String, - @JsonProperty("apiName") override val apiName: String, - @JsonProperty("type") override var type: TvType? = null, - @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("year") val year: Int?, - @JsonProperty("quality") override var quality: SearchQuality? = null, - @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, - ) : SearchResponse { - fun toLibraryItem(): SyncAPI.LibraryItem? { - return SyncAPI.LibraryItem( - name, - url, - id?.toString() ?: return null, - null, - null, - null, - latestUpdatedTime, - apiName, type, posterUrl, posterHeaders, quality, this.id - ) - } - } - data class BookmarkedData( @JsonProperty("id") override var id: Int?, @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, @@ -92,7 +63,7 @@ object DataStoreHelper { null, null, null, - latestUpdatedTime, + null, apiName, type, posterUrl, posterHeaders, quality, this.id ) } @@ -104,7 +75,9 @@ object DataStoreHelper { @JsonProperty("apiName") override val apiName: String, @JsonProperty("type") override var type: TvType? = null, @JsonProperty("posterUrl") override var posterUrl: String?, + @JsonProperty("watchPos") val watchPos: PosDur?, + @JsonProperty("id") override var id: Int?, @JsonProperty("parentId") val parentId: Int?, @JsonProperty("episode") val episode: Int?, @@ -231,41 +204,6 @@ object DataStoreHelper { return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) } - fun getAllSubscriptions(): List { - return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull { - getKey(it) - } ?: emptyList() - } - - fun removeSubscribedData(id: Int?) { - if (id == null) return - AccountManager.localListApi.requireLibraryRefresh = true - removeKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) - } - - /** - * Set new seen episodes and update time - **/ - fun updateSubscribedData(id: Int?, data: SubscribedData?, episodeResponse: EpisodeResponse?) { - if (id == null || data == null || episodeResponse == null) return - val newData = data.copy( - latestUpdatedTime = unixTimeMS, - lastSeenEpisodeCount = episodeResponse.getLatestEpisodes() - ) - setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), newData) - } - - fun setSubscribedData(id: Int?, data: SubscribedData) { - if (id == null) return - setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), data) - AccountManager.localListApi.requireLibraryRefresh = true - } - - fun getSubscribedData(id: Int?): SubscribedData? { - if (id == null) return null - return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) - } - fun setViewPos(id: Int?, pos: Long, dur: Long) { if (id == null) return if (dur < 30_000) return // too short diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index b03c9fb7..bd4f8705 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -52,7 +52,7 @@ data class ExtractorLinkPlayList( ) -open class ExtractorLink constructor( +open class ExtractorLink( open val source: String, open val name: String, override val url: String, @@ -62,24 +62,7 @@ open class ExtractorLink constructor( override val headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ open val extractorData: String? = null, - open val isDash: Boolean = false, ) : VideoDownloadManager.IDownloadableMinimum { - /** - * Old constructor without isDash, allows for backwards compatibility with extensions. - * Should be removed after all extensions have updated their cloudstream.jar - **/ - constructor( - source: String, - name: String, - url: String, - referer: String, - quality: Int, - isM3u8: Boolean = false, - headers: Map = mapOf(), - /** Used for getExtractorVerifierJob() */ - extractorData: String? = null - ) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false) - override fun toString(): String { return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)" } @@ -246,7 +229,6 @@ val extractorApis: MutableList = arrayListOf( StreamSB8(), StreamSB9(), StreamSB10(), - StreamSB11(), SBfull(), // Streamhub(), cause Streamhub2() works Streamhub2(), @@ -272,7 +254,6 @@ val extractorApis: MutableList = arrayListOf( // WatchSB(), 'cause StreamSB.kt works Uqload(), Uqload1(), - Uqload2(), Evoload(), Evoload1(), VoeExtractor(), @@ -284,7 +265,6 @@ val extractorApis: MutableList = arrayListOf( OkRu(), OkRuHttps(), Okrulink(), - Sendvid(), // dood extractors DoodCxExtractor(), @@ -296,7 +276,6 @@ val extractorApis: MutableList = arrayListOf( DoodShExtractor(), DoodWatchExtractor(), DoodWfExtractor(), - DoodYtExtractor(), AsianLoad(), @@ -312,7 +291,6 @@ val extractorApis: MutableList = arrayListOf( Supervideo(), GuardareStream(), CineGrabber(), - Vanfem(), // StreamSB.kt works // SBPlay(), @@ -343,9 +321,6 @@ val extractorApis: MutableList = arrayListOf( DesuDrive(), Filesim(), - FileMoon(), - FileMoonSx(), - Vido(), Linkbox(), Acefile(), SpeedoStream(), @@ -387,7 +362,6 @@ val extractorApis: MutableList = arrayListOf( Cda(), Dailymotion(), ByteShare(), - Ztreamhub() ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt index bc81a5b9..6b6d3928 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt @@ -91,7 +91,7 @@ class ApkInstaller(private val service: PackageInstallerService) { session.openWrite(context.packageName, 0, size) .use { outputStream -> - val buffer = ByteArray(4 * 1024) + val buffer = ByteArray(1024) var bytesRead = inputStream.read(buffer) while (bytesRead >= 0) { @@ -100,7 +100,6 @@ class ApkInstaller(private val service: PackageInstallerService) { installProgress.invoke(bytesRead) } - session.fsync(outputStream) inputStream.close() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt index dcb1e047..1625981e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt @@ -14,7 +14,6 @@ import androidx.core.app.NotificationCompat import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import kotlinx.coroutines.delay @@ -48,12 +47,24 @@ class PackageInstallerService : Service() { .setSmallIcon(R.drawable.rdload) } + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = + NotificationChannel(UPDATE_CHANNEL_ID, UPDATE_CHANNEL_NAME, importance).apply { + description = UPDATE_CHANNEL_DESCRIPTION + } + + // Register the channel with the system + val notificationManager: NotificationManager = + this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + notificationManager.createNotificationChannel(channel) + } + } + override fun onCreate() { - this.createNotificationChannel( - UPDATE_CHANNEL_ID, - UPDATE_CHANNEL_NAME, - UPDATE_CHANNEL_DESCRIPTION - ) + createNotificationChannel() startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build()) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 2dc6846c..1f6d726d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -250,17 +250,6 @@ object SingleSelectionHelper { ) } - fun showBottomDialog( - items: List, - selectedIndex: Int, - name: String, - showApply: Boolean, - dismissCallback: () -> Unit, - callback: (Int) -> Unit, - ) { - - } - /** Only for a low amount of items */ fun Activity?.showBottomDialog( items: List, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt deleted file mode 100644 index 66e1e504..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt +++ /dev/null @@ -1,267 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.mvvm.logError -import kotlinx.coroutines.* -import org.junit.Assert - -object TestingUtils { - open class TestResult(val success: Boolean) { - companion object { - val Pass = TestResult(true) - val Fail = TestResult(false) - } - } - - class TestResultSearch(val results: List) : TestResult(true) - class TestResultLoad(val extractorData: String) : TestResult(true) - - class TestResultProvider(success: Boolean, val log: String, val exception: Throwable?) : - TestResult(success) - - @Throws(AssertionError::class, CancellationException::class) - suspend fun testHomepage( - api: MainAPI, - logger: (String) -> Unit - ): TestResult { - if (api.hasMainPage) { - try { - val f = api.mainPage.first() - val homepage = - api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages)) - when { - homepage == null -> { - logger.invoke("Homepage provider ${api.name} did not correctly load homepage!") - } - homepage.items.isEmpty() -> { - logger.invoke("Homepage provider ${api.name} does not contain any items!") - } - homepage.items.any { it.list.isEmpty() } -> { - logger.invoke("Homepage provider ${api.name} does not have any items on result!") - } - } - } catch (e: Throwable) { - if (e is NotImplementedError) { - Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") - } else if (e is CancellationException) { - throw e - } - logError(e) - } - } - return TestResult.Pass - } - - @Throws(AssertionError::class, CancellationException::class) - private suspend fun testSearch( - api: MainAPI - ): TestResult { - val searchQueries = listOf("over", "iron", "guy") - val searchResults = searchQueries.firstNotNullOfOrNull { query -> - try { - api.search(query).takeIf { !it.isNullOrEmpty() } - } catch (e: Throwable) { - if (e is NotImplementedError) { - Assert.fail("Provider has not implemented search()") - } else if (e is CancellationException) { - throw e - } - logError(e) - null - } - } - - return if (searchResults.isNullOrEmpty()) { - Assert.fail("Api ${api.name} did not return any valid search responses") - TestResult.Fail // Should not be reached - } else { - TestResultSearch(searchResults) - } - - } - - - @Throws(AssertionError::class, CancellationException::class) - private suspend fun testLoad( - api: MainAPI, - result: SearchResponse, - logger: (String) -> Unit - ): TestResult { - try { - Assert.assertEquals( - "Invalid apiName on SearchResponse on ${api.name}", - result.apiName, - api.name - ) - - val loadResponse = api.load(result.url) - - if (loadResponse == null) { - logger.invoke("Returned null loadResponse on ${result.url} on ${api.name}") - return TestResult.Fail - } - - Assert.assertEquals( - "Invalid apiName on LoadResponse on ${api.name}", - loadResponse.apiName, - result.apiName - ) - Assert.assertTrue( - "Api ${api.name} on load does not contain any of the supportedTypes: ${loadResponse.type}", - api.supportedTypes.contains(loadResponse.type) - ) - - val url = when (loadResponse) { - is AnimeLoadResponse -> { - val gotNoEpisodes = - loadResponse.episodes.keys.isEmpty() || loadResponse.episodes.keys.any { loadResponse.episodes[it].isNullOrEmpty() } - - if (gotNoEpisodes) { - logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}") - return TestResult.Fail - } - - (loadResponse.episodes[loadResponse.episodes.keys.firstOrNull()])?.firstOrNull()?.data - } - is MovieLoadResponse -> { - val gotNoEpisodes = loadResponse.dataUrl.isBlank() - if (gotNoEpisodes) { - logger.invoke("Api ${api.name} got no movie on ${loadResponse.url}") - return TestResult.Fail - } - - loadResponse.dataUrl - } - is TvSeriesLoadResponse -> { - val gotNoEpisodes = loadResponse.episodes.isEmpty() - if (gotNoEpisodes) { - logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}") - return TestResult.Fail - } - loadResponse.episodes.firstOrNull()?.data - } - is LiveStreamLoadResponse -> { - loadResponse.dataUrl - } - else -> { - logger.invoke("Unknown load response: ${loadResponse.javaClass.name}") - return TestResult.Fail - } - } ?: return TestResult.Fail - - return TestResultLoad(url) - -// val loadTest = testLoadResponse(api, load, logger) -// if (loadTest is TestResultLoad) { -// testLinkLoading(api, loadTest.extractorData, logger).success -// } else { -// false -// } -// if (!validResults) { -// logger("Api ${api.name} did not load on the first search results: ${smallSearchResults.map { it.name }}") -// } - -// return TestResult(validResults) - } catch (e: Throwable) { - if (e is NotImplementedError) { - Assert.fail("Provider has not implemented load()") - } - throw e - } - } - - @Throws(AssertionError::class, CancellationException::class) - private suspend fun testLinkLoading( - api: MainAPI, - url: String?, - logger: (String) -> Unit - ): TestResult { - Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) - if (url == null) return TestResult.Fail // Should never trigger - - var linksLoaded = 0 - try { - val success = api.loadLinks(url, false, {}) { link -> - logger.invoke("Video loaded: ${link.name}") - Assert.assertTrue( - "Api ${api.name} returns link with invalid url ${link.url}", - link.url.length > 4 - ) - linksLoaded++ - } - if (success) { - logger.invoke("Links loaded: $linksLoaded") - return TestResult(linksLoaded > 0) - } else { - Assert.fail("Api ${api.name} returns false on loadLinks() with $linksLoaded links loaded") - } - } catch (e: Throwable) { - when (e) { - is NotImplementedError -> { - Assert.fail("Provider has not implemented loadLinks()") - } - else -> { - logger.invoke("Failed link loading on ${api.name} using data: $url") - throw e - } - } - } - return TestResult.Pass - } - - fun getDeferredProviderTests( - scope: CoroutineScope, - providers: List, - logger: (String) -> Unit, - callback: (MainAPI, TestResultProvider) -> Unit - ) { - providers.forEach { api -> - scope.launch { - var log = "" - fun addToLog(string: String) { - log += string + "\n" - logger.invoke(string) - } - fun getLog(): String { - return log.removeSuffix("\n") - } - - val result = try { - addToLog("Trying ${api.name}") - - // Test Homepage - val homepage = testHomepage(api, logger).success - Assert.assertTrue("Homepage failed to load", homepage) - - // Test Search Results - val searchResults = testSearch(api) - Assert.assertTrue("Failed to get search results", searchResults.success) - searchResults as TestResultSearch - - // Test Load and LoadLinks - // Only try the first 3 search results to prevent spamming - val success = searchResults.results.take(3).any { searchResponse -> - addToLog("Testing search result: ${searchResponse.url}") - val loadResponse = testLoad(api, searchResponse, ::addToLog) - if (loadResponse !is TestResultLoad) { - false - } else { - testLinkLoading(api, loadResponse.extractorData, ::addToLog).success - } - } - - if (success) { - logger.invoke("Success ${api.name}") - TestResultProvider(true, getLog(), null) - } else { - logger.invoke("Error ${api.name}") - TestResultProvider(false, getLog(), null) - } - } catch (e: Throwable) { - TestResultProvider(false, getLog(), e) - } - callback.invoke(api, result) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 2902b76b..a629dad9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -20,7 +20,6 @@ import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager -import com.bumptech.glide.load.model.GlideUrl import com.fasterxml.jackson.annotation.JsonProperty import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull @@ -214,7 +213,7 @@ object VideoDownloadManager { } private val cachedBitmaps = hashMapOf() - fun Context.getImageBitmapFromUrl(url: String, headers: Map? = null): Bitmap? { + private fun Context.getImageBitmapFromUrl(url: String): Bitmap? { try { if (cachedBitmaps.containsKey(url)) { return cachedBitmaps[url] @@ -222,14 +221,12 @@ object VideoDownloadManager { val bitmap = GlideApp.with(this) .asBitmap() - .load(GlideUrl(url) { headers ?: emptyMap() }) - .into(720, 720) + .load(url).into(720, 720) .get() - if (bitmap != null) { cachedBitmaps[url] = bitmap } - return bitmap + return null } catch (e: Exception) { logError(e) return null @@ -429,7 +426,7 @@ object VideoDownloadManager { } private const val reservedChars = "|\\?*<\":>+[]/\'" - fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String { + fun sanitizeFilename(name: String, removeSpaces: Boolean= false): String { var tempName = name for (c in reservedChars) { tempName = tempName.replace(c, ' ') @@ -1615,7 +1612,7 @@ object VideoDownloadManager { .mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } .toTypedArray() setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) - } catch (t: Throwable) { + } catch (t : Throwable) { logError(t) } } diff --git a/app/src/main/res/drawable/baseline_network_ping_24.xml b/app/src/main/res/drawable/baseline_network_ping_24.xml deleted file mode 100644 index 1caae667..00000000 --- a/app/src/main/res/drawable/baseline_network_ping_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_notifications_none_24.xml b/app/src/main/res/drawable/baseline_notifications_none_24.xml deleted file mode 100644 index cf589c6d..00000000 --- a/app/src/main/res/drawable/baseline_notifications_none_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_text_snippet_24.xml b/app/src/main/res/drawable/baseline_text_snippet_24.xml deleted file mode 100644 index c1f3654b..00000000 --- a/app/src/main/res/drawable/baseline_text_snippet_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml b/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml deleted file mode 100644 index 4b8964f8..00000000 --- a/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_selection_dialog_direct.xml b/app/src/main/res/layout/bottom_selection_dialog_direct.xml index cf31ba1f..0d179ebb 100644 --- a/app/src/main/res/layout/bottom_selection_dialog_direct.xml +++ b/app/src/main/res/layout/bottom_selection_dialog_direct.xml @@ -1,34 +1,34 @@ + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:id="@+id/text1" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:layout_marginTop="20dp" + android:layout_marginBottom="10dp" + android:textStyle="bold" + android:textSize="20sp" + android:textColor="?attr/textColor" + android:layout_width="match_parent" + android:layout_rowWeight="1" + tools:text="Test" + android:layout_height="wrap_content" /> + android:nextFocusRight="@id/cancel_btt" + android:nextFocusLeft="@id/apply_btt" + + android:id="@+id/listview1" + android:layout_marginBottom="60dp" + android:paddingTop="10dp" + android:requiresFadingEdge="vertical" + tools:listitem="@layout/sort_bottom_single_choice_no_checkmark" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_rowWeight="1" /> diff --git a/app/src/main/res/layout/fragment_home_head.xml b/app/src/main/res/layout/fragment_home_head.xml index 603621f7..0ee50042 100644 --- a/app/src/main/res/layout/fragment_home_head.xml +++ b/app/src/main/res/layout/fragment_home_head.xml @@ -1,6 +1,7 @@ + @@ -145,16 +148,17 @@ + app:drawableTint="?attr/white" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/home_more_info"/> @@ -180,9 +184,9 @@ + android:layout_height="wrap_content"> + app:drawableTint="?attr/white" + android:contentDescription="@string/home_more_info"/> diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 985d055d..f9012148 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -166,12 +166,12 @@ app:layout_scrollFlags="noScroll" app:tabGravity="center" app:tabIndicator="@drawable/indicator_background" - app:tabIndicatorColor="?attr/white" + app:tabIndicatorColor="@color/textColor" app:tabIndicatorGravity="center" app:tabIndicatorHeight="30dp" app:tabMode="scrollable" - app:tabSelectedTextColor="?attr/primaryBlackBackground" + app:tabSelectedTextColor="@color/lightTextColor" app:tabTextAppearance="@style/TabNoCaps" - app:tabTextColor="?attr/textColor" /> + app:tabTextColor="@color/textColor" /> diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index a481ed6b..afbf735d 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -129,9 +129,9 @@ + android:layout_height="wrap_content"> @@ -515,8 +516,8 @@ android:visibility="gone" /> ملصق @@ -103,7 +103,7 @@ مزيد من المعلومات قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة - لا يتم توفير البيانات الوصفية بواسطة الموقع، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع. + لا يتم توفير البيانات الوصفية بواسطة الموقع ، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع. الوصف لم يتم العثور على وصف لم يتم العثور على وصف @@ -119,16 +119,16 @@ وضع إيغنغرافي يضيف خيار السرعة في المُشغل السحب لتقديم - اسحب من جانب إلى آخر للتحكم في موضعك في مقطع فيديو + إسحب إلى اليسار أو اليمين للتحكم في الوقت في مُشغل الفيديو السحب لتغيير الإعدادات - مرر لأعلى أو لأسفل على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت + إسحب على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت تشغيل الحلقة التالية تلقائيًا تبدأ الحلقة التالية عندما تنتهي الحالية النقر مرتان للتقديم للأمام أو للخلف الضغط مرتان لإيقاف مؤقت - التحكم في مدى تقديم المُشغل(ثوان) + التحكم في مدى تقديم المُشغل إضغط مرتين على الجانب الأيمن أو الأيسر للتقديم للأمام أو للخلف - اضغط مرتين في المنتصف للتوقف + إضغط في الوسط لإيقاف مؤقت استخدم سطوع النظام استخدم سطوع النظام في مُشغل التطبيق بدلاً من التراكب الداكن تحديث تقدم المشاهدة @@ -155,7 +155,7 @@ تحديث الإضافات تلقائيًا تنزيل الإضافات تلقائيًا التحديث التلقائي - ابحث تلقائيا عن التحديثات الجديدة بعد بدء التطبيق. + البحث تلقائيًا عن التحديثات الجديدة عند البداية التحديث إلى الاصدارات التجريبية (بيتا) البحث عن التحديثات التجريبية بدلاً من الإصدارات الكاملة فقط غيت هاب @@ -170,7 +170,7 @@ تم نسخ الرابط إلى الحافظة تشغيل الحلقة إعادة التعيين إلى القيمة الافتراضية - عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين + عذرا ، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين موسم لا موسم حلقة @@ -218,8 +218,8 @@ فيلم مسلسل كرتون - أنيمي - أوفا + أنمي + اوفا تورنت وثائقي دراما آسيوية @@ -259,15 +259,15 @@ لا تظهر مرة أخرى تخطي هذا التحديث تحديث - جودة المشاهدة المفضلة (WiFi) + جودة المشاهدة المفضلة أقصى عدد لحروف عنوان مُشغل الفيديو أبعاد مُشغل الفيديو حجم ذاكرة التخزين المؤقت للفيديو طول التخزين المؤقت التخزين المؤقت للفيديو على القرص مسح التخزين المؤقت للصورة والفيديو - يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة، مثل تلفزيون أندرويد. - يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة، مثل تلفزيون أندرويد. + يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة ، مثل Android TV. + يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة ، مثل Android TV. إستخدام DNS بدلا من HTTPS مفيد لتجاوز حجب مزود خدمة الإنترنت موقع بديل (نسخة) @@ -284,7 +284,7 @@ Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. عام زر العشوائي - يظهر الزر على الصفحة الرئيسية والذي يمكنه اختيار فيلم عشوائي أو مسلسل تلفزيوني من الصفحة الرئيسية + إظهار زر العشوائي على الصفحة الرئيسية لغات المزود واجهة التطبيق المحتوى المفضل @@ -342,7 +342,7 @@ الكل الحد الاقصي الحد الأدنى - @string/none + \@string/none الخطوط المحيطة النمط المنخفض ظل @@ -360,7 +360,7 @@ https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog --> نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق - مُوصى به + مُوصي به تم تحميل %s إختيار ملف تحميل من الانترنت @@ -521,44 +521,4 @@ بدأ التحديث تم تنزيل الإضافة إزالة من المشاهدة - الترتيب الأبجدي (من الألف إلى الياء) - اختر المكتبة - المتصفح - محدث (من الأحدث إلى الأقدم) - يبدو أن هذه القائمة فارغة ، حاول التبديل إلى قائمة أخرى - التقييم (من الأعلى إلى الأدنى) - التقييم (من الأدنى إلى الأعلى) - الترتيب الأبجدي (من ي إلى أ) - يبدو أن مكتبتك فارغة :( -\nتسجيل الدخول إلى حساب مكتبة أو إضافة عروض إلى مكتبتك المحلية - محدث (من القديم إلى الجديد) - فرز حسب - افرز - فتح بواسطة - المكتبة - تم العثور على ملف الوضع الآمن! -\nلا يتم تحميل أي ملحقات عند بدء التشغيل حتى تتم إزالة الملف. - مدة التقديم عنما يكون المشغل مخفيا - مدة التقديم - المشغل مخفي - تلفزيون أندرويد - مدة التقديم عنما يكون المشغل مرئيا - مدة التقديم- المشغل المرئي - فشل - نجح - إختبار المزود - إعادة التشغيل - سجل - بَدأ - إيقاف - تحديث العروض التي تم الاشتراك فيها - إلغاء الاشتراك من %s - تم إصدار الحلقة %d! - مشترك - مشترك في %s - تجاوز مزود خدمة الإنترنت - استرجاع - فشل الوصول إلى GitHub ، وتمكين وكيل jsdelivr. - باستخدام jsdelivr ، يمكن تجاوز حظر GitHub. قد يؤخر التحديثات لبضعة أيام. - وكيل raw.githubusercontent.com - جودة المشاهدة المفضلة (بيانات الجوال) - + \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 301242cd..9f95eb3f 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -1,6 +1,5 @@ - - - + + %s еп. %d Актьори: %s @@ -9,6 +8,7 @@ %dh %dm %dm Poster + \@string/result_poster_img_des Episode Poster Main Poster Следващ произволен @@ -106,7 +106,7 @@ Продължете да гледате Премахване Повече информация - @string/home_play + \@string/home_play Може да е необходим VPN, за да работи правилно този доставчик Този доставчик е торент, препоръчва се VPN Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта. @@ -224,8 +224,8 @@ Филм Серия Анимационен филм - @string/anime - @string/ova + \@string/anime + \@string/ova Торент Документален филм Азиатска драма @@ -498,4 +498,4 @@ Приложението ще се актуализира при изход от него Започна Актуализация Премахване от гледани - + \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 71d5d6d0..7e0448d6 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -1,4 +1,4 @@ - + পোস্টার ক্লাউডস্ট্রিম দিয়ে চালান @@ -143,9 +143,9 @@ হালনাগাদ ও ব্যাকআপ অ্যাপ এর হালনাগাদ দেখান খুঁজতে সোয়াইপ করুন - @string/result_poster_img_des - @string/home_play + \@string/result_poster_img_des + \@string/home_play আগাতে ডবল ট্যাপ করুন আইজেনগ্রাভি মোড আপডেট শুরু হয়েছে - + \ No newline at end of file diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 13b34872..2c2e1303 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Ep %d @@ -11,7 +10,7 @@ %dm Poster - @string/result_poster_img_des + \@string/result_poster_img_des Episode Poster Main Poster Next Random @@ -109,7 +108,7 @@ Continue Assistindo Remover Mais Info - @string/home_play + \@string/home_play Uma VPN pode ser necessária para esse fornecedor funcionar corretamente Esse fornecedor é um torrent, uma VPN é recomendada Metadados não são oferecidas pelo site, o carregamento do video pode falhar se ele não existir no site. @@ -223,8 +222,8 @@ Filme Série Desenho Animado - @string/anime - @string/ova + \@string/anime + \@string/ova Torrent Documentário Drama Asiático @@ -429,4 +428,4 @@ Começa o próximo episódio quando o atual termina Ativar NSFW em fornecedores compatíveis Fornecedores - + \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1501a5d9..87d5195e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,13 +1,12 @@ - - - + + %s Ep %d Hrají: %s Plakát - Plakát + \@string/result_poster_img_des Episode Poster Main Poster Next Random @@ -104,7 +103,7 @@ Pokračovat ve sledování Odebrat Další informace - @string/home_play + \@string/home_play Aby tento poskytovatel fungoval správně, budete možná potřebovat VPN Tento poskytovatel je torrent, je doporučená VPN Web neposkytnul žádná metadata, načítání videa selže, pokud na webu neexistuje. @@ -123,14 +122,14 @@ Rychlostní režim Přidá do přehrávače možnost rychlosti Přejet pro posun - Přejeďte prstem ze strany na stranu pro ovládání své pozice ve videu + Přejeďte prstem vlevo nebo vpravo pro ovládání času v přehrávači Přejet pro změnu nastavení - Přejeďte prstem nahoru nebo dolů na levé nebo pravé straně pro změnu jasu nebo hlasitosti + Přejeďte prstem na levé nebo pravé straně pro změnu jasu nebo hlasitosti Dvojité klepnutí pro posun Dvojité klepnutí pro pozastavení - Množství času k posunu (sekundy) + Množství času k posunu Klepněte dvakrát vpravo nebo vlevo pro posun vpřed nebo vzad - Klepněte dvakrát doprostřed pro pozastavení + Klepněte doprostřed pro pozastavení Použít systémový jas V přehrávači použít systémov překrytí Aktualizovat postup sledování @@ -139,8 +138,8 @@ Zálohovat data Načten soubor zálohy Nepodařilo se obnovit data ze soubory %s - Data uložena - Chybí oprávnění k úložišti. Zkuste to prosím znovu. + Data úspěšně uložena + Chybí oprávnění k úložišti, zkuste to prosím znovu Chyba při zálohování %s Search Účty @@ -152,7 +151,7 @@ Nebude odesílat žádná data Zobrazit výplňové epizody u anime Zobrazit aktualizace aplikace - Při spuštění aplikace automaticky zkontrolovat nové aktualizace. + Při spuštění automaticky zkontrolovat nové aktualizace Aktualizovat na předběžná vydání Kontrolovat aktualizace předběžných vydání, místo normálních plných vydání GitHub @@ -212,8 +211,8 @@ Film Seriál Animovaný - Anime - OVA + \@string/anime + \@string/ova Torrent Dokument Asijské drama @@ -246,19 +245,19 @@ Již nezobrazovat Přeskočit tuto aktualizace Aktualizovat - Upřednostněná kvalita sledování (WiFi) + Upřednostněná kvalita sledování Maximální počet znaků v názvu přehrávače Rozlišení přehrávače Velikost vyrovnávací paměti videa Délka vyrovnávací paměti videa Mezipaměť videa na disku Vymazat mezipamět videí a obrázků - Při nastavení příliš vysoké hodnoty na zařízeních s malou pamětí, jako je například Android TV, může způsobit pády. - Při nastavení příliš vysoké hodnoty na zařízeních s malým dostupným úložištěm, jako je například Android TV, může způsobit pády. + Při nastavení příliš vysoké hodnoty způsobí náhodné pády. Neměňte, pokud máte málo paměti RAM, například u televize s Androidem nebo starého telefonu. + Pokud ji nastavíte příliš vysoko, může způsobit problémy v systémech s malým úložným prostorem, například v zařízeních Android TV. DNS přes HTTPS Užitečné pro obcházení blokací ISP Cesta stahování - URL serveru NGINX + URL serveru Nginx Zobrazit dabované anime/anime s titulky Vyplnit na obrazovku Roztáhnout @@ -267,7 +266,7 @@ Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí. Obecné Náhodné tlačítko - Zobrazit na domovské stránce tlačítko, kterým lze vybrat náhodný film nebo seriál z domovské stránky + Zobrazit na domovské stránce náhodné tlačítko Jazyk poskytovatelů Rozložení aplikace Preferovaná média @@ -321,10 +320,10 @@ Stín Vyvýšené Synch. titulky - 1000 ms + 1000ms Zpoždění titulků - Toto použijte, pokud jsou titulky zobrazeny o %d ms dříve - Toto použijte, pokud jsou titulky zobrazeny o %d ms později + Toto použijte, pokud jsou titulky zobrazeny o %dms dříve + Toto použijte, pokud jsou titulky zobrazeny o %dms později Žádné zpoždění titulků - + + CloudStream Αρχική Αναζήτηση @@ -151,7 +150,7 @@ Επεισόδια %d-%d %d %s - Σ + Κ E Δεν βρέθηκαν επεισόδια Διαγραφή αρχείου @@ -315,7 +314,7 @@ Αναφορά κατάρρευσης Τι θα θέλατε να δείτε Έγινε - Extensions + Πρόσθετα Προσθήκη αποθετηρίου Όνομα αποθετηρίου Σύνδεσμος αποθετηρίου @@ -388,7 +387,7 @@ Κλείσιμο Εκκαθάριση Γλώσσα υποτίτλων - @string/home_play + \@string/home_play Δεν έχουν παρασχεθεί μεταδεδομένα από τον ιστότοπο, η φόρτωση του βίντεο θα αποτύχει αν δεν υπάρχει στον ιστότοπο. Διπλό πάτημα για παύση Μέγεθος αναζήτησης στο πρόγραμμα αναπαραγωγής @@ -453,7 +452,7 @@ Ανάμεικτοι τίτλοι τέλους -30 Κριτική - @string/ova + \@string/ova Ενημερώσεις εφαρμογής Αντίγραφο ασφαλείας Extensions @@ -465,7 +464,7 @@ Προεπιλεγμένα %s %s Μέγεθος γραμματοσειράς - @string/anime + \@string/anime Σύνδεσμοι Εμφάνιση Χαρακτηριστικά @@ -491,22 +490,4 @@ Το πρόσθετο κατέβει Ενημέρωση ξεκίνησε Η εφαρμογή θα ενημερωθεί κατά την έξοδο - Αλφαβητικά (Ω προς Α) - Ταξινόμηση - Κριτική (Χαμηλή προς Υψηλή) - Ενημερωμένο (Καινούριο προς παλιό) - Ενημερωμένο (Παλιό προς Καινούργιο) - Βιβλιοθήκη - Κριτική (Υψηλή προς χαμηλή) - Ταξινόμηση με βάση - Αλφαβητικά (Α προς Ω) - Διάλεξε βιβλιοθήκη - Φαίνεται πως η λίστα είναι άδεια, δοκίμασε να μεταβείς σε μία άλλη - Αφαίρεση από παρακολουθημένα - Περιηγητής - Άνοιγμα με - Φαίνεται πως η βιβλιοθήκη σου είναι άδεια :( -\nΣυνδέσου σε έναν λογαριασμό που έχει βιβλιοθήκη, ή πρόσθεσε σειρές στην τοπική βιβλιοθήκη σου - Βρέθηκε αρχείο Ασφαλούς Λειτουργίας! -\nΔεν πρόκειται να φορτωθούν extensions κατά το ξεκίνημα μέχρι να διαγραφεί το αρχείο. - + \ No newline at end of file diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 5eac8686..09e6941d 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -1,4 +1,4 @@ - + Reen Hejmo @@ -78,4 +78,4 @@ Rapido (%.2fx) Serĉi… Elŝuti - + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 06c20aa5..08ae5bf1 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,4 +1,4 @@ - + Extensiones Descargue la lista de sitios que quiera utilizar @@ -24,7 +24,7 @@ Ocultar la calidad de video en los resultados de búsqueda Diseño Diseño - Calidad de visualización preferida (WiFi) + Calidad de visualización preferida Reproductor de video preferido Diseño para emulador Diseño de la aplicación @@ -51,10 +51,10 @@ Elevado Use esto si los subtítulos se muestran %d ms muy pronto Use esto si los subtítulos se muestran %d ms tarde - Desliza el dedo de lado a lado para controlar la posición en un video + Desliza el dedo hacia la izquierda o hacia la derecha para controlar el tiempo en el reproductor de video Filtrar por idioma de medios preferido Eliminar Closed Captions (CC) de los subtítulos - Cantidad de búsquedas del reproductor (segundos) + Cantidad de tiempo de búsqueda en el reproductor (en segundos) Use el brillo del sistema en el reproductor de la app en lugar de una superposición oscura Resolución del reproductor de video MPV @@ -94,7 +94,7 @@ Poster Principal Idioma de la aplicación Ver videos en estos idiomas - Cartel + \@string/result_poster_img_des Siguiente al azar Todos los Idiomas Volver @@ -194,7 +194,7 @@ Continuar Viendo Remover Más info - @string/home_play + \@string/home_play Una VPN puede ser necesaria para que este proveedor funcione correctamente Este proveedor es un torrent, se recomienda una VPN El sitio no proporciona los metadatos, la carga del video fallará si no existe en el sitio. @@ -205,16 +205,16 @@ Modo Eigengravy Deslice para avanzar/retroceder Deslice para cambiar la configuración - Deslice hacia arriba o hacia abajo en el lado izquierdo o derecho para cambiar el brillo o el volumen + Deslice el dedo hacia la izquierda o hacia la derecha para cambiar el brillo o el volumen Toca dos veces para buscar Tocar dos veces para pausar Toque dos veces en el lado derecho o izquierdo para buscar hacia adelante o hacia atrás - Toque dos veces en el medio para hacer una pausa + Toque en el medio para pausar Usar brillo del sistema Restaurar datos desde el backup Hacer copia de los datos (backup) Archivo de backup cargado - Busque automáticamente nuevas actualizaciones después de iniciar la aplicación. + Buscar automáticamente nuevas actualizaciones al inicio Rehacer el proceso de configuración inicial Mostrar episodio de relleno para Anime Reproducir Episodio @@ -306,7 +306,7 @@ Aspecto Características Botón de Al azar - Muestra un botón de reproducción \"al azar\" en la página de inicio para poelículas y series + Muestra un botón de reproducción \"al azar\" en la página de inicio cuenta Cerrar sesión Cambiar cuenta @@ -363,8 +363,8 @@ Película Serie Dibujo animado - Anime - OVA + \@string/anime + \@string/ova Torrent Documental Drama asiático @@ -489,44 +489,4 @@ Actualización iniciada Complemento descargado Quitar de visto - Ordenar por - Ordenar - Valoración (más a menos) - Valoración (menos a más) - Actualizado (nuevo a viejo) - Actualizado (viejo a nuevo) - Alfabéticamente (A a Z) - Navegador - Biblioteca - Parece que esta lista está vacía, intenta cambiar a otra - Alfabéticamente (Z a A) - Seleccionar biblioteca - Abrir con - Parece que tu biblioteca está vacía :( -\nInicia sesión en una cuenta de biblioteca o añade series desde tu biblioteca local - ¡Se encontró un archivo en modo seguro! -\nNo cargar ninguna extensión al inicio hasta que se elimine el archivo. - Reproductor visible - buscar cantidad - Reproductor oculto - buscar cantidad - Android TV - Tiempo de búsqueda usado (en segundos) cuando el reproductor está visible - Tiempo de búsqueda usado (en segundos) cuando el reproductor está oculto - Parar - Falló - Registro - Empezar - Aprobado - Prueba del proveedor - Reiniciar - Suscrito - Suscrito a %s - Darse de baja de %s - Actualizando los programas suscritos - ¡Episodio %d publicado! - Proxy raw.githubusercontent.com - No se ha podido acceder a GitHub, activando el proxy jsdelivr. - Con jsdelivr, se puede omitir el bloqueo de GitHub. Puede retrasar las actualizaciones unos días. - Revertir - ISP Bypasses - Calidad de visualización preferida (Datos móviles) - + \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index e4c23628..a34c5a05 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,4 +1,4 @@ - + حذف مکث @@ -23,14 +23,4 @@ هیچ‌کدام عنوان تاریخچه - پوستر - پوستر - پوستر قسمت - %dروز %dساعت %dدقیقه - %s قسمت %d - بازیگران: %s - قسمت %d پخش خواهد شد - %dساعت %dدقیقه - %dدقیقه - پوستر اصلی - + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b96ff0cd..f0e112a8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,18 +1,17 @@ - - - + + CloudStream Accueil Rechercher Téléchargements Paramètres Rechercher… - Affiche + Miniature Aucune Donnée Plus d\'options Retour Épisode suivant - Affiche + Miniature Genres Partager Ouvrir dans le navigateur @@ -30,7 +29,7 @@ Sous-titres Réessayer la connection… Retour - Affiche de l\'épisode + Miniature de l\'Épisode Lire l\'Épisode Télécharger @@ -52,10 +51,10 @@ Désactiver le rapport de bug automatique Plus d\'informations Cacher - Affiche principale + Poster principal Lecture - Infos - Aléatoire suivant + Info + Suivant Aléatoire Changer le fournisseur Filtrer les marques-pages Marque-pages @@ -131,7 +130,7 @@ Nouvelle mise à jour trouvée ! \n%s -> %s Épisode spécial - Qualité de visionnage préférée (WiFi) + Qualité de visionnage préférée Taille de la mémoire cache Étendre Non-responsabilité @@ -212,7 +211,7 @@ Arrière plan Source Aléatoire - Bientôt disponible… + À venir … Image de l\'affiche %s Connecté Définir le statut de visionage @@ -241,7 +240,7 @@ Continuer à regarder Retirer Plus d\'informations - @string/home_play + \@string/home_play Un VPN peut être nécessaire pour que ce fournisseur fonctionne correctement Ce fournisseur est un torrent, un VPN est recommandé Les métadonnées ne sont pas fournies par le site, le chargement de la vidéo échouera si elles n\'existent pas sur le site. @@ -386,8 +385,8 @@ 4K Web -30 - @string/anime - @string/ova + \@string/anime + \@string/ova NSFW %s %s Filtrez par langue préférée @@ -491,37 +490,4 @@ L\'application sera mise à jour dès la fin de la session Plugin Téléchargé Retirer de la vue - Bibliothèque - Navigateur - Trier - Note (basse à haute) - Note (haut à bas) - Alphabétique (A à Z) - On dirait que votre bibliothèque est vide :( -\nConnectez-vous à un compte ou ajoutez des séries à votre bibliothèque locale - Il semble que cette liste soit vide, essayez d\'en choisir une autre - Android TV - Trié par - Alphabétique (Z à A) - Sélectionnez la bibliothèque - Ouvrir avec - Mis à jour (Nouveau vers ancien) - Mis à jour (ancien vers nouveau) - Fichier du mode sans échec trouvé ! -\nAucune extension ne sera chargée au démarrage avant que le fichier ne soit enlevé. - Arrêter - Revenir à - Enregistrer - Qualité de visionnage préférée (données mobiles) - Abonné à %s - Démarrer - Test des fournisseurs - Réussi - Désabonné de %s - Redémarrer - Abonné - raw.githubusercontent.com Proxy - Contournements de FAI - L\'épisode %d est sorti ! - Échouer - + \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 833b76f4..f33a2336 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,6 +1,5 @@ - - - + + रफ्तार (%.2fx) नया अपडेट आया है! @@ -147,4 +146,4 @@ %dh %dm %dm विज्ञापन - + \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index b4931377..837a2201 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1,6 +1,5 @@ - - - + + %d %s | %s %s • %s @@ -20,7 +19,7 @@ %dm Poster - Poster + \@string/result_poster_img_des Episode Poster Main Poster Next Random @@ -120,7 +119,7 @@ Nastavite s gledanjem Makni Više informacija - @string/home_play + \@string/home_play Za ispravan rad ovog pružatelja usluga može biti potreban VPN Ovaj pružatelj usluga je torrent, preporučuje se VPN Stranica ne daje metapodatke, učitavanje videozapisa neće uspjeti ako ne postoji na stranici. @@ -139,16 +138,16 @@ Eigengravy način Dodaje opciju brzine u playeru Prijeđi prstom za traženje - Prijeđite prstom ulijevo ili udesno kako biste kontrolirali player + Prijeđi prstom ulijevo ili udesno za kontrolu vremena u videoplayeru Klizni za promjenu postavki - Kliznite prstom ulijevo ili udesno za promjenu svjetline ili glasnoće + Prijeđi prstom ulijevo ili udesno za promjenu svjetline ili glasnoće Automatski započni sljedeću epizodu Započne sljedeću epizodu kad trenutna završi Dodirni dvaput za traženje Dodirni dvaput za pauziranje - Iznos preskakanja u playeru (Sekunde) + Iznos preskakanja u playeru Dvaput dodirni desnu ili lijevu stranu ekrana za pomicanje naprijed ili natrag - Dodirnite dvaput u sredinu zaslona za pauziranje + Dodirni u sredinu zaslona za pauziranje Koristi svijetlinu u sustavu Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog preklopa Ažuriraj napredak gledanja @@ -174,7 +173,7 @@ Sakrij odabranu kvalitetu videozapisa u rezultatima pretraživanja Automatsko ažuriranje dodataka Prikaži ažuriranja aplikacije - Automatski traži nova ažuriranja nakon pokretanja aplikacije + Automatski traži nova ažuriranja pri pokretanju aplikacije Ažuriranje na predizdanja Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja Github @@ -239,8 +238,8 @@ Film Serija Crtić - Anime - OVA + \@string/anime + \@string/ova Torrent Dokumentarac Azijska drama @@ -300,7 +299,7 @@ Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Općenito Random gumb - Prikazuje gumb na početnoj stranici koji može odabrati nasumični film ili TV seriju s početne stranice + Prikaži random gumb na početnoj stranici Jezici pružatelja usluga Izgled aplikacije Preferirani mediji @@ -515,44 +514,4 @@ Program če se aktualizirati tijekom zatvaranja programa Dodatak preuzet Ukloni iz pogledanog - Preglednik - Biblioteka - Ocjena (Veća do manje) - Sortiraj prema - Sortiraj - Ocjena (Manja do veće) - Ažurirano (Od novog do starog) - Ažurirano (Od starog do novog) - Abecedno (A do Ž) - Abecedno (Ž do A) - Odaberite biblioteku - Otvori sa - Čini se da vam je biblioteka prazna :( -\nPrijavite se na račun biblioteke ili dodajte serije u svoju lokalnu biblioteku - Čini se da je ova lista prazna, pokušajte se prebaciti na drugu - Pronađena datoteka sigurnog načina rada! -\nNe učitavaju se ekstenzije pri pokretanju dok se datoteka ne ukloni. - Prikazan player- iznos preskakanja - Količina preskakanja koja se koristi kada je player vidljiv - Player skriven - Količina preskakanja - Količina preskakanja koja se koristi kada je player skriven - Android TV - Prošlo - Restart - Log - Početak - Neuspješno - Stop - Test pružatelja usluga - Ažuriram pretplaćene serije - Epizoda %d izbačena! - Pretplaćeno - Pretplaćen na %s - Otkazana pretplata sa %s - Vraćanje - ISP zaobilaznice - raw.githubusercontent.com Proxy - Neuspješno dohvaćanje GitHuba, omogućavanje jsdelivr proxyja. - Koristeći jsdelivr, GitHub blokiranje se može zaobići. Može odgoditi ažuriranja za nekoliko dana. - Preferirana kvaliteta gledanja (podatkovna mobilna mreža) - + \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 1389dff0..5b42fd6a 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1,4 +1,4 @@ - + Stáblista: %s %dn %dó%dp @@ -57,7 +57,7 @@ Megnyitás böngészőben Betöltés kihagyása Poster - @string/result_poster_img_des + \@string/result_poster_img_des Nézés Befejezve Később megnézés @@ -111,7 +111,7 @@ Betűtípusok importálása %s Eltávolítás Több információ - @string/home_play + \@string/home_play VPN szükséges lehet ehhez a szolgáltató megfelelő működéséhez Ez a szolgáltató torrent, VPN ajánlott Leírás @@ -172,11 +172,11 @@ OVA Egyebek Sorozat - @string/anime + \@string/anime Forráshiba NSFW Rajzfilm - @string/ova + \@string/ova Élőadás NSFW Videó @@ -275,4 +275,4 @@ Minőségi jelzés Szinkroncímke Alcímke - + \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 02234c49..83dc6ee9 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -1,12 +1,11 @@ - - - + + %s Ep %d Pemeran: %s Poster - Poster + \@string/result_poster_img_des Episode Poster Main Poster Next Random @@ -36,7 +35,7 @@ Skip Loading Loading… Sedang Menonton - Tertunda + Tertahan Selesai Dihentikan Rencana untuk Menonton @@ -102,7 +101,7 @@ Lanjutkan Menonton Hapus Info lebih lanjut - @string/home_play + \@string/home_play Sebuah VPN mungkin diperlukan agar provider ini bisa bekerja dengan benar Provider ini adalah sebuah torrent, VPN direkomendasikan Metadata tidak disediakan oleh situs, loading video akan gagal jika tidak ada di situs. @@ -121,14 +120,14 @@ Mode Eigengravy Menambahkan opsi kecepatan di pemutar Geser untuk mengubah waktu - Geser dari sisi ke sisi untuk mengontrol posisi dalam video + Geser ke kiri atau kanan untuk mengontrol waktu di pemutar video Geser untuk mengubah pengaturan - Geser ke atas atau ke bawah di sisi kiri atau kanan untuk mengubah kecerahan atau volume + Geser ke sisi kiri atau kanan untuk mengubah pencerahan atau volume Tekan dua kali untuk mengubah waktu Tekan dua kali untuk menjeda - Jumlah pengubah waktu pemutar (Detik) + Jumlah pengubah waktu pemutar Tekan dua kali di sisi kanan atau kiri untuk mengubah waktu ke depan atau ke belakang - Tekan dua kali di tengah untuk menjeda + Tekan di tengah untuk menjeda Gunakan pencerahan sistem Gunakan pencerahan sistem di pemutar aplikasi dari pada hamparan gelap Update progres tontonan @@ -150,7 +149,7 @@ Tidak mengirim data Tampilkan episode filler untuk anime Tampilkan update aplikasi - Secara otomatis mencari update terbaru setelah aplikasi dibuka. + Secara otomatis mencari update terbaru saat aplikasi dibuka Update ke prarilis Hanya mencari update prarilis daripada rilis penuh Github @@ -210,8 +209,8 @@ Movie Seri Kartun - Anime - OVA + \@string/anime + \@string/ova Torrent Film Dokumenter Drama Asia @@ -244,7 +243,7 @@ Jangan tunjukkan lagi Skip Update ini Update - Kualitas tontonan yang lebih diinginkan (WIFI) + Kualitas tontonan yang lebih diinginkan Karakter maksimal judul pemutar video Resolusi pemutar video Ukuran buffer video @@ -265,7 +264,7 @@ Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Umum Tombol Acak - Tampilkan tombol di halaman utama yang dapat memilih seri film atau TV acak dari halaman utama + Tampilkan tombol acak di Beranda Bahasa provider Tata Letak Aplikasi Media yang lebih diinginkan @@ -388,7 +387,7 @@ %d %s 17+ Lainnya - Video + Vidio Duplikasi Website Duplikasi website yang telah ada, dengan alamat berbeda Tautan @@ -396,7 +395,7 @@ Cadangkan Fitur Tambahan Putar di CloudStream - Sembunyikan kualitas video terpilih di pencarian + Sembunyikan kualitas vidio terpilih di pencarian %s %d%s Siaran langsung Hapus Website @@ -445,7 +444,7 @@ Peringkat: %s Pembuat Bahasa - Pemutar video utama + Pemutar vidio utama Pemutar Bawaan VLC MPV @@ -454,7 +453,7 @@ Semua fitur tambahkan dimatikan karena crash, untuk memudahkanmu mencari penyebab crash. Kode bahasa (en) Ambil dari internet - Putar video di bahasa ini + Putar vidio di bahasa ini Tambah Repositori Pilih ini untuk menghapus semua repositori plugin Lewati pengaturan @@ -476,7 +475,7 @@ Hapus teks tertutup dari subtitel Hapus karakter sampah dari subtitel Audio Trek - Video Trek + Vidio Trek Dukungan Daftar putar HLS Penginstal APK @@ -484,7 +483,7 @@ Gerakan Beberapa perangkat tidak mendukung penginstal paket mode baru. Coba mode lama jika pembaruan tidak dapat diinstal. Aksi - Referer + Referensi Ya Pasang dulu fitur tambahan Semua Bahasa @@ -513,44 +512,4 @@ Aplikasi akan diperbaharui pada saat keluar Pembaharuan Dimulai Hapus dari tontonan - Browser - Pilih pustaka - Yahh daftar pustaka kamu kosong :( -\nMasuk ke akun pustaka atau tambah perlihatkan ke lokal pustaka kamu - Pustaka - Urutkan berdasar - Urutkan - Peringkat (Rendah ke Tinggi) - Update (Lama ke Terbaru) - Peringkat (Tinggi ke Rendah) - Update (Terbaru ke Lama) - Abjad (A ke Z) - Abjad (Z ke A) - Buka dengan - Yahh daftar ini kosong, coba ganti ke yang lain - Mode aman file ditemukan! -\nTidak memuat ekstensi pada startup sampai berkas dihapus. - Sembunyikan Pemutaran - Geser - Pemutar terlihat - Geser - Geser untuk menghilangkan - Geser untuk menghilangkan - Android TV - Log - Berhasil - Tes provider - Berhenti - Mulai - Mulai lagi - Gagal - Memperbarui acara langganan - Berlangganan - Berlangganan ke %s - Berhenti berlangganan di %s - Episode %d telah rilis! - raw.githubusercontent.com Proksi - Gagal mencapai GitHub, mengaktifkan proksi jsdelivr. - Mengunakan jsdelivers, bisa melewati pemblokiran GitHub. Mungkin dapat menyebabkan pembaruan tertunda dalam beberapa hari. - Bypass ISP - Pulihkan - Nonton dengan kualitas yang di inginkan (Data Seluler) - + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index eca60da1..b4ba292e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Ep %d Cast: %s @@ -10,7 +9,7 @@ %d min Poster - Poster + \@string/result_poster_img_des Poster episodio Poster principale Prossimo casuale @@ -109,7 +108,7 @@ Continua a guardare Rimuovi Più info - @string/home_play + \@string/home_play Potrebbe essere necessaria una VPN per far funzionare correttamente questo provider Questo provider è un torrent, si raccomanda una VPN I metadati non sono forniti dal sito, il caricamento del video fallirà se non esiste sul sito. @@ -266,7 +265,7 @@ Non mostrare di nuovo Salta questo aggiornamento Aggiorna - Qualità di visualizzazione preferita (WiFi) + Risoluzione preferita Limita i caratteri del titolo nel player Risoluzione video player Dimensione cache video @@ -512,44 +511,4 @@ Aggiornamento avviato Plugin scaricato Rimuovi dai già visti - Browser - Ordina per - Punteggio (Decrescente) - Punteggio (Crescente) - Aggiornato (Da nuovo a vecchio) - Aggiornato (Da vecchio a nuovo) - Alfabetico (A - Z) - Alfabetico (Z - A) - Sembra che la tua libreria sia vuota :( -\nAccedi a un account di libreria o aggiungi degli show alla tua libreria locale - Seleziona libreria - Apri con - Libreria - Ordina - Sembra che questa lista sia vuota, prova a passare a un\'altra - File \"safe mode\" trovato! -\nAll\'avvio non sarà caricata alcuna estensione finchè il file non verrà rimosso. - Quantità di ricerca usata quando il player è nascosto - TV Android - Quantità di ricerca usata quando il player è visibile - Player visibile - Quantità di ricerca - Player nascosto - Quantità di ricerca - Registro - Avvia - Test del provider - Riavvia - Ferma - Superato - Fallito - Proxy raw.githubusercontent.com - Disiscritto da %s - Iscritto - Iscritto a %s - Impossibile contattare GitHub, abilitazione proxy jsdelivr avviata. - Bypassa il blocco di GitHub utilizzando jsdelivr, potrebbe causare un ritardo di alcuni giorni. - Baypass ISP - Ripristina - Aggiornando shows a cui sei iscritto - L\'episodio %d è stato rilasciato! - Qualità di visualizzazione preferita (Dati mobili) - + \ No newline at end of file diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index b24f0c60..cb8d5945 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -1,4 +1,4 @@ - + הרקע של ההצגה לפני צוות שחקנים: %s @@ -34,13 +34,13 @@ הושלם בתכנון לצפייה צופה מחדש - הפעל סרט - הפעל טריילר - הפעל שידור חי + נגן סרט + נגן קדימון + נגן שידור חי מקורות נסיון חדש לחיבור… - הפסיק - שדר טורנט + נעצר + שידור טורנט חזרה נגן פרק הורד @@ -75,7 +75,7 @@ נגן עם קלאודסטרים פרק הבא טוען… - צופה + צופה ב כתוביות בהמתנה ללא @@ -102,11 +102,11 @@ הספק הזה הוא טורנט, שימוש ב-VPN הוא מומלץ תיאור עלילה לא נמצאה - התיאור לא נמצאה + העלילה לא נמצאה מצב תמונה בתמונה כפתור שינוי גודל הנגן הסר את הגבולות השחורים - %d בנינים שניתנו למפתחים + %d ניתן למפתחים הגדרות כתוביות הנגן הגדרות כתוביות כרומקאסט גופן @@ -116,7 +116,7 @@ כתוביות כרומקאסט ממשיך ניגון בנגן מינימלי מעל ישומים אחרים כתוביות - @string/home_play + \@string/home_play היסטוריה מורשת לא @@ -129,381 +129,4 @@ אין הכל כותרת - העדכון התחיל - מידע - התקן אוטומטית את כל התוספים שטרם הותקנו ממאגרים שנוספו. - חפש אוטומטית עדכונים חדשים בפתיחת האפליקציה - הצג עדכונים לאפליקציה - בצע מחדש את תהליך ההגדרה - עדכן למהדורות מוקדמות - חפש עדכוני מהדרות מוקדמות במקום מהדורות מלאות בלבד - פרקים - %d-%d - פרק - %d %s - עו - פר - לא נמצאו פרקים - מחק קובץ - מחק - עצור - המשך - -30 - +30 - %dדקות -\nנותרו - ‬פעולה זאת תמחק לצמיתות את %s -\nהאם אתם בטוחים\? - מתמשך - משך זמן - דירוג - שנה - ללא כתוביות - ברירת מחדל - חינם - משומש - סדרת טלוויזיה - סדרות/סרטים מצוירים - @string/anime - @string/ova - דרמה אסייתית - כרומקאסט את הפרק - כרומקאסט את המראה - נגן בדפדפן - תווית כתוביות - החלף רכיבי ממשק משתמש בפוסטר - דלג על הפתיח - אל תראה שוב - דלג על עדכון זה - עדכון - איכות צפייה מועדפת - נגן וידאו כותרת מקסימום תווים - רזולוציית נגן וידאו - הוסף מעקב - צור חשבון - הבא - ספריה - מטא-דאטה לא מסופק על ידי האתר, טעינת הסרטון תיכשל אם הוא לא קיים באתר. - החלק על הצד השמאלי או הימני כדי לשנות את הבהירות או עוצמת הקול - שחזור הנתונים מהקובץ נכשל %s - עדכונים וגיבויים - נותן לך את תוצאות החיפוש מופרדות לפי ספק - הצג פרק פילר (לא הכרחי לעלילה) לאנימה - הורדה אוטומטית של תוספים - עדכוני תוספים אוטומטיים - כמות בנינים שניתנו - עונה - מצטערים, האפליקציה קרסה. דוח באג אנונימי יישלח למפתחים - טורנט - NSFW - שגיאת מעבד - הורד מראה - הורד כתוביות - הצג כפתור אקראי בדף הבית - תווית דיבוב - גורם לקריסות אם מוגדר גבוה מדי במכשירים עם זיכרון נמוך, כגון Android TV. - מקור - ספקים - הסר אתר - נתיב הורדה - נראה - אוטומטי - פריסת טלוויזיה - %s %s - סנן לפי שפת מדיה מועדפת - נושא אפליקציה - מיקום כותרת פוסטר - מה אתם רוצים לראות - איכות גבוהה - HD - SDR - תוסף טעון - DVD - עדכוני אפליקציה - 4K - WP - דלג על ההגדרה - תכונות נגן - עדכן התקדמות צפייה - DNS מעל HTTPS - לחץ באמצע כדי לעצור - החלק שמאלה או ימינה כדי לשלוט על זמן בנגן הסרטונים - נגן אוטומטית את הפרק הבא - התחל את הפרק הבא כאשר הפרק הנוכחי נגמר - לחץ פעמיים על צד ימין או שמאל כדי להציץ קדימה או אחורה - השתמש בבהירות המערכת - גבה נתונים - סנכרן את הכתוביות - עיכוב כתוביות - אין עיכוב בכתוביות - מומלץ - נטען %s - השתמש בזה אם הכתוביות מוצגות %d מילישניות מוקדם יותר - אקראי - רזולוציה - איכות מצלמה - כתובת אתר לא מזוהה - קישור לשידור - המפנה - TC - SD - UHD - כתובת אתר למאגר - מדיה מועדפת - /%d - נגן את הפרק - לא נמצאו קישורים - קוד שפה (אנגלית) - שגיאה - שפות ספק - קישורים - פריסת אפליקציה - חיפוש מתקדם - 18+ - פוסטר - איכות מצלמה - החלק כדי להציץ - גיבוי - כמות הצצת הנגן - סרטון - גיטהאב - מצלמה - החלק לשינוי ההגדרות - ‪דפדפן - צבע חלון - הצג לוג - הוסף אפשרות מהירות בנגן - לחץ פעמיים כדי להציץ - לחץ פעמיים כדי לעצור - התשתמש בבהירות המערכת בנגן האפליקציה במקום שכבת-על כהה - סנכרן אוטומטית את התקדמות הפרק הנוכחית שלך - שחזר נתונים מגיבוי - קובץ הגיבוי נטען - נתונים מאוחסנים - חסרות הרשאות אחסון. אנא נסה שוב. - שגיאה בגיבוי %s - חיפוש - חשבונות - לא ניתנו בנינים - מצב איגנגראבי - שולח נתונים רק בקריסות - לא שולח נתונים - הצג טריילרים - הצג פוסטרים מKitsu - הסתר את איכות הסרטון שנבחרה בתוצאות החיפוש - מתקין APK - חלק מהטלפונים אינם תומכים במתקין החבילות החדש. נסו את האפשרות מדור קודם אם העדכונים לא מתקינים. - אפליקצית רומנים קלים על ידי אותו מפתחים - אפליקצית אנימה על ידי אותו מפתחים - הצטרפות לדיסקורד - שפת האפליקציה - לספק זה אין תמיכה בכרומקאסט - הקישור הועתק ללוח - אפס לערך ברירת המחדל - אין עונה - הושלם - אתר - תקציר - בתור - לתת בניני למפתחים - %s %d%s - אפליקציה - סרטים - אנימה - סרטים תיעודיים - אנימציית וידאו מקורית - דרמות אסייתיות - שידורי חי - אחר - סרט - סדרה - סדרה/סרט מצויר - טורנט - דוקומנטרי - שידור חי - NSFW - שגיאת מקור - שגיאה מרחוק - שגיאת נגן לא צפויה - שגיאת הורדה, בדוק הרשאות אחסון - נגן באפליקציה - נגן ב %s - העתק קישור - הורדה אוטומטית - טען מחדש קישורים - תווית איכות - לא נמצא עדכון - בדוק אם יש עדכון - נעל - שינוי גודל - גודל מאגר וידאו - אורך מאגר וידאו - מטמון וידאו בכונן - נקה מטמון וידאו ותמונה - גורם לקריסות אם מוגדר גבוה מדי במכשירים עם מקום נמוך, כגון Android TV. - שימושי עבור עקיפת מחסומי ספק שירותי אינטרנט - אתר העתק - הוסף העתק של אתר קיים, עם כתובת אתר אחרת - NGINX שרת כתובת אתר - הצג אנימות מדובבות/מתורגמות - התאם למסך - מתוח - זום - כתב ויתור - הרחבות - פעולות - מטמון - מחוות - כתוביות - פריסה - ברירות מחדל - תכונות - כללי - כפתור אקראי - הרשה NSFW בספקים נתמכים - קידוד כתוביות - פריסה - פריסת טלפון - פריסת אמולטור - צבע ראשוני - שים את הכותרת מתחת לפוסטר - סיסמה123 - שםהמשתמשהמגניבשלי - hello@world.com - 127.0.0.1 - האתרהמגניבשלי - דוגמה.com - חשבון - החלף חשבון - הוסף חשבון - נוסף %s - סנכרן - מדורג - %d / 10 - /\?\? - %s מאומת - לא ניתן להתחבר ב %s - רגיל - מקסימום - מינימום - מתאר - מדוכא - צל - הורם - 1000 מילישניות - השתמש בזה אם הכתוביות מוצגות %d מילישניות מאוחר יותר - השועל החום המהיר קופץ מעל הכלב העצלן - טען מקובץ - טען מהאינטרנט - קובץ שהורד - ראשי - משנה - רקע - מקור - בקרוב… - TS - Blu-ray - HDR - Web - תמונת פוסטר - נגן - רזולוציה וכותרת - לא מזוהה ID - נתונים לא מזוהים - הסר כיתובים סגורים מהכתוביות - הסר נפיחות מכתוביות - תוספות - טריילר - צפה בסרטונים בשפות אלה - קודם - שנה את מראה האפליקציה כך שיתאים למכשירך - דיווח על קריסה - סוים - הרחבות - הוסף מאגר - שם מאגר - תוסף הורד - התוסף נמחק - לא יכול לטעון %s - התחיל להוריד %d %s… - הוריד %d %s - הצג חלונות קופצים של דילוג בשביל פתיח/שיר סיום - מחק מאגר - פתיח מעורב - כל %s כבר הורד - מחברים - שפה - MPV - קרדיטים - מיין - בחר ספרייה - נראה שהספרייה שלכם ריקה :( -\nהתחברו לחשבון ספריה או הוסף סדרות לספרייה המקומית שלך - קובץ מצב בטוח נמצא! -\nלא טוען שום תוספות בהפעלה עד להסרת הקובץ. - לא ניתן להתקין את הגרסה החדשה של האפליקציה - הורדת אצווה - תוסף - הורד את כל התוספים ממאגר זה\? - רצועות שמע - מסלולים - Web Video Cast - דפדפן אינטרנט - כל התוספים נכבו עקב התרסקות כדי לעזור לך למצוא את האחד הגורם לצרות. - מוריד החבילות - עודכן %d תוספים - תוספים - פעולה זו תמחק גם את כל תוספי המאגר - הורד את רשימת האתרים שבהם ברצונך להשתמש - הורד: %d - מוגבל: %d - לא מורד: %d - לקלאודסטרים אין אתרים מותקנים כברירת מחדל. עליכם להתקין את האתרים ממאגרים. -\n -\nבגלל הסרת DMCA על ידי Sky UK LImited 🤮 אנחנו לא יכולים לקשר את אתר המאגרים באפליקציה. -\n -\nתצטרפו לדיסקורד שלנו או תחפשו באינטרנט. - הצג מאגרים קהילתיים - רשימה ציבורית - לשים את הכתוביות באותיות רישיות - %s (מוגבל) - רצועות וידאו - החל על הפעלה מחדש - מצב בטוח מופעל - הצג מידע על ההתרסקות - דירוג: %s - תיאור - גרסה - מצב - גודל - תומך - התקן תחילה את התוסף - פלייליסט HLS - נגן וידאו מועדף - נגן פנימי - VLC - האפליקציה לא נמצאה - כל השפות - דלג %s - פתיח - שיר סיום - סיכום - שיר סיום מעורב - מבוא - נקה היסטוריה - יותר מדי טקסט. לא ניתן לשמור ללוח. - הסר מצפייה - האם אתם בטוחים שאתם רוצים לצאת\? - מוריד עדכון אפליקציה… - מתקין עדכון אפליקציה… - האפליקציה תעודכן עם היציאה - מיין לפי - דירוג (גבוה לנמוך) - דירוג (נמוך לגבוה) - מעודכן (חדש לישן) - מעודכן (ישן לחדש) - אלפביתי (א \'עד ת\') - אלפביתי (ת\' עד א\') - פתח עם - נראה שהרשימה הזו ריקה, נסו לעבור לרשימה אחרת - + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml deleted file mode 100644 index 20641b20..00000000 --- a/app/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,185 +0,0 @@ - - - %d分 - ダウンロード - 検索 - 設定 - シェア - 映画 - ホーム - ライブラリ - 再生 - %d日 %d時間%d分 - %d時間%d分 - 検索… - ダウンロード - 情報 - シーズン - 予告編 - シリーズ - エピソード - 再生速度 (%.2fx) - 次のエピソード - 適用 - アカウント - カートゥーン - TVシリーズ - トレント - ドキュメンタリー - OVA - アジアドラマ - ライブ配信 - 映画 - その他 - カートゥーン - トレント - ドキュメンタリー - アジアドラマ - ライブ配信 - NSFW - キャンセル - アニメ - ロック - ソース - NSFW - 履歴を削除 - 視聴中コンテンツ - 全般 - 動画 - プレーヤー - 懐う - 予告編を再生 - エピソード - 視聴 - ジャンル - 映画を再生 - 字幕 - CloudStream - CloudStreamで再生 - ブラウザ - 完成 - 放置 - 保留 - ローディング… - ブラウザで開く - シーズン - 残り -\n%d分 - 再生エピソード - ダウンロード済 - バックアップ - ソース - 履歴 - ポスター - なし - コピー - 閉じる - 保存 - 消去 - %sエピ%d - 出演者:%s - ポスター - エピソードポスター - 主要ポスター - 次のランダム - 戻り - 視聴率 %.1f - 新しいアップデートを発見! -\n%s -> %s - %d分 - %sを検索… - ソース - ろくごうきじ - 接続を再試行… - 戻り - 削除 - 詳細情報 - 閉じる - アップデート・バックアップ - アプリ言語 - GitHub(ギットハブ) - -30 - +30 - 免責 - 拡張機能 - アプリ更新 - 提供者 - 字幕 - 特徴 - デフォルト - 自動 - 任意 - 拡張機能 - リンク - Android TV - ログイン - ログアウト - 最大 - 最小 - なし - - 18+ - - で開く - エピソード - 時間 - 概要 - サイト - 使用 - アプリ - 詳細情報 - 削除 - ピクチャーインピクチャー - 字幕 - 情報 - 一時停止 - 再生エピソード - 削除 - 開始 - 状態 - - 再開 - 失敗 - 合格 - 空き - 完成 - 進行中 - デフォルト - ウェブブラウザ - VLC - MPV - 言語 - 作成者 - サイズ - 状態 - バージョン - 視聴率 %s - 視聴率 - デフォルト - ダウンロード失敗 - ダウンロード開始 - ダウンロード完了 - ダウンロード終了 - ストリーム - アップデート開始 - シーズンなし - 字幕なし - アスペクト比 - ロードをスキップする - その他のオプション - データなし - ダウンロード中 - ブックマーク - 内部記憶装置 - ダウンロードが一時停止 - メタデータはこのサイトでは提供されません。メタデータがサイト上に存在しない場合、ビデオの読み込みに失敗します。 - 記述 - Logcat 🐈を表示 - ログ - 検索 - Discordに参加 - アップデート - アップデートを確認 - 作品名 - アプリのアップデートをインストール中… - diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 4b7b6869..efe0a1d8 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -1,128 +1,3 @@ - + - %sಎಪಿ%d - ಕ್ಯಾಸ್ಟ್:%s - ಹಿಂದೆ ಹೋಗು - ಫಿಲ್ಲರ್ - ಹುಡುಕು - ಡೌನ್ಲೋಡ್ - ಫಾಂಟ್ - ಪೂರೈಕೆದಾರರನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ - ಪ್ರಕಾರಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ - ಯಾವುದೇ ಬೆನೆನ್ಸ್ ನೀಡಿಲ್ಲ - ಸ್ವಯಂ-ಆಯ್ಕೆ ಭಾಷೆ - ಹೆಚ್ಚಿನ ಮಾಹಿತಿ - \@ಸ್ಟ್ರಿಂಗ್/ಹೋಮ್_ಪ್ಲೇ - ಈ ಪೂರೈಕೆದಾರರು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡಲು VPN ಬೇಕಾಗಬಹುದು - ಕಪ್ಪು ಗಡಿಗಳನ್ನು ತೆಗೆದುಹಾಕಿ - ಸಂಚಿಕೆ%d ಬಿಡುಗಡೆಯಾಗಲಿದೆ - %dh %dm - ಪೋಸ್ಟರ್ - ಪೋಸ್ಟರ್ - ಸಂಚಿಕೆ ಪೋಸ್ಟರ್ - ಮೇನ್ ಪೋಸ್ಟರ್ - ಅಪ್ಡೇಟ್ ಪ್ರಾರಂಭವಾಗಿದೆ - ಲೋಡಿಂಗ್ ಲಿಂಕ್ ಎರರ್ ಬಂದಿದೆ - ಇಂಟರ್ನಲ್ ಸ್ಟೋರೇಜ್ - ಡಬ್ - ಸಬ್ - ಸ್ವಯಂಚಾಲಿತ ದೋಷ ವರದಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ - ಹೈಡ್ - ಪ್ಲೇ - ಮಾಹಿತಿ - ಸೆಟ್ ವಾಚ್ ಸ್ಟೇಟಸ್ - ಅನ್ವಯಿಸು - ರದ್ದುಮಾಡು - ಸಬ್ ಟೈಟಲ್ಸ್ ಎಲೆವಷನ್ - ಫಾಂಟ್ ಸೈಜ್ - ಸಬ್ ಟೈಟಲ್ಸ್ ಭಾಷೆ - ತೆಗೆದುಹಾಕಿ - ಈ ಪೂರೈಕೆದಾರರು ಟೊರೆಂಟ್ ಆಗಿದೆ, VPN ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ - ಯಾವುದೇ ಪ್ಲಾಟ್ ಕಂಡುಬಂದಿಲ್ಲ - ಲಾಗ್‌ಕ್ಯಾಟ್ 🐈 ತೋರಿಸಿ - ಲಾಗ್ - ಚಿತ್ರದಲ್ಲಿ-ಚಿತ್ರದಲ್ಲಿ - ಪ್ಲೇಯರ್ ಮರುಗಾತ್ರಗೊಳಿಸಿ ಬಟನ್ - ಸಬ್ ಟೈಟಲ್ಸ್ - ಪ್ಲೇಯರ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು - ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್ - ಹಿಂದೆ ಹೋಗು - ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಿ - ಬುಕ್‌ಮಾರ್ಕ್‌ - ಬ್ಯಾಕ್ ಗ್ರೌಂಡ್ ಕಲರ್ - %d ಡೇವ್‌ಗಳಿಗೆ ಬೆನೆನೆಸ್ ನೀಡಲಾಗಿದೆ - ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸಲು ಹಿಡಿದುಕೊಳ್ಳಿ - ಸೈಟ್‌ನಿಂದ ಮೆಟಾಡೇಟಾವನ್ನು ಒದಗಿಸಲಾಗಿಲ್ಲ, ಅದು ಸೈಟ್‌ನಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲದಿದ್ದರೆ ವೀಡಿಯೊ ಲೋಡಿಂಗ್ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ. - ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಮೇಲೆ ಚಿಕಣಿ ಪ್ಲೇಯರ್‌ನಲ್ಲಿ ಪ್ಲೇಬ್ಯಾಕ್ ಅನ್ನು ಮುಂದುವರಿಸುತ್ತದೆ - ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ - ರೇಟೆಡ್:%.1f - ತೆಗೆದುಹಾಕಿ - ಡೌನ್‌ಲೋಡ್ ಅನ್ನು ಪುನರಾರಂಭಿಸಿ - ಕ್ಲೋಸ್ - ಕ್ಲಿಯರ್ - ಸೇವ್ - ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್ - ಫೈಲ್ ಪ್ಲೇ - ಟೆಕ್ಸ್ಟ್ ಕಲರ್ - ಔಟ್ ಲೈನ್ ಕಲರ್ - ವಿಂಡೋ ಕಲರ್ - ಎಡ್ಜ್ ಟೈಪ್ - ಪ್ರೊವೈಡರ್ ಬದಲಾಯಿಸಿ - %dಮಿನ - ವಿವರಣೆ - ಸ್ಪೀಡ್(%.2fx) - ಹೋಂ - ಸಬ್ ಟೈಟಲ್ಸ್ - ಸೆಟ್ಟಿಂಗ್ಸ್ - ಬುಕ್‌ಮಾರ್ಕ್‌ಗಳನ್ನು ಫಿಲ್ಟರ್ ಮಾಡಿ - ಹುಡುಕು… - ಚಲನಚಿತ್ರವನ್ನು ಪ್ಲೇ ಮಾಡಿ - ಪ್ರಿವ್ಯೂ ಹಿನ್ನೆಲೆ - ಮುಂದಿನ ಸಂಚಿಕೆ - ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ - ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ - ಸ್ಟ್ರೀಮ್ - ಶೇರ್ - ಫೈಲ್ ಅಳಿಸಿ - ಹೆಚ್ಚಿನ ಮಾಹಿತಿ - ಹೊಸ ಅಪ್ಡೇಟ್ ಬಂದಿದೆ -\n%s-%s - ಲೋಡಿಂಗ್… - ಡೌನ್‌ಲೋಡ್ ಭಾಷೆಗಳನ್ನು ಮಾಡಿ - ಲೈವ್‌ಸ್ಟ್ರೀಮ್ ಪ್ಲೇ ಮಾಡಿ - ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ ಇದರೊಂದಿಗೆ ಪ್ಲೇ ಮಾಡಿ - ವೀಕ್ಷಿಸಲು ಯೋಜನೆ - ಸಂಚಿಕೆಯನ್ನು ಪ್ಲೇ ಮಾಡಿ - ಕಂಟಿನ್ಯೂ ವಾಟಚಿಂಗ್ - ಯಾವುದೇ ವಿವರಣೆ ಕಂಡುಬಂದಿಲ್ಲ - ಸ್ಟ್ರೀಮ್ ಟೊರೆಂಟ್ - ಡೌನ್‌ಲೋಡ್ - ಕಾಪಿ - ನೋ ಡೇಟಾ - ಪ್ಲೇಯರ್ ಸ್ಪೀಡ್ - %d %dh %dm - ಹುಡುಕು %s… - ಹೆಚ್ಚಿನ ಆಯ್ಕೆ - ಫಾಂಟ್‌ಗಳನ್ನು ಇರಿಸುವ ಮೂಲಕ ಆಮದು ಮಾಡಿ %s - %dm - ಪ್ರಕಾರಗಳು - ಬ್ರೌಸರ್ ತೆರೆಯಿರಿ - ಆನ್-ಹೋಲ್ಡ್ - ನನ್ - ಸಂಪರ್ಕವನ್ನು ಮರುಪ್ರಯತ್ನಿಸಿ… - ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ವಿಫಲವಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ಮುಗಿದಿದೆ - ಬ್ರೌಸರ್ - ಸ್ಕಿಪ್ ಲೋಡಿಂಗ್ - ವಾಚಿಂಗ್ - ಪೂರ್ಣಗೊಂಡಿದೆ - ಕೈಬಿಡಲಾಯಿತು - ಪುನಃ ವೀಕ್ಷಿಸುತ್ತಿದೆ - ಟ್ರೈಲರ್ ಪ್ಲೇ ಮಾಡಿ - ಮೂಲಗಳು - ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ಪ್ರಾರಂಭವಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ - ಮುಂದಿನ ರಾಂಡಮ್ - + \ No newline at end of file diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 811a09c5..7251d0d7 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -1,6 +1,5 @@ - - - + + Брзина (%.2fx) Оценето: %.1f @@ -214,4 +213,4 @@ Сенка Подигнат Историја - + \ No newline at end of file diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index d430d7cc..b6ad3a80 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -1,6 +1,5 @@ - - - + + വേഗം (%.2fx) റേറ്റിംഗ്: %.1f @@ -170,4 +169,4 @@ ഔചിത്യ വീഡിയോ ക്വാളിറ്റി ചരിത്രം കണ്ടതാണെന്ന് അടയാളപ്പെടുത്തുക - + \ No newline at end of file diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-mo/string.xml similarity index 73% rename from app/src/main/res/values-qt/strings.xml rename to app/src/main/res/values-mo/string.xml index eee28785..361aaf56 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-mo/string.xml @@ -1,15 +1,15 @@ - + aauugghhaauuh ooh oouuh - ouuhhuhooh ooh + ouuhhhooh ooh aaaghhoh aauugghh - haaooh ooo + haaooh aaaaaah oooohhoouuh - aaaaoouuuhahhh ahh + aaaaaoouuhahhh ahh oooohh aauuh ahhhaaaghh aaaaa ahaauugghh @@ -122,7 +122,7 @@ A ooha ohahaaahoooa ahahooo oooooah - aahhaaaaaahooo aauuh aaahhuaooo-ahahoooohh aoouuhoohoohooo-ahah %s + aahhaaaaaahooo aauuh aaahhuaooo-ahahoooohh aoouuhoohoohooo-ahah aaaghhaaaaa aauuhaauuh ahhhaaaaa @@ -145,7 +145,7 @@ aauugghhooo-ahah ohaaauugghh aoohaaahhu ahouuhhh ooo-ahahaauuh aaahhu - ooo-ahah ohaauuh %s + ooo-ahah ohaauuh ahoha ooo-ahahohoohah oooohh aauugghhahhaauugghh aaaghhoooohh aaahhu ahooo @@ -172,80 +172,28 @@ oouuh haa oohahaha hahha ooooohaha oohahaha hahha ooooohaha haaoou - u ahhu uuuh hau uaohuau + u ahhu uuuh hau uaohuau aahuuouhh ouh hhhah hhaohuhha a auoo ohauh - uhaauauau ahuuouaha + uhaauauau ahuuouaha auuuha h a ahuhaaaa - uaoh uhu uahaaaaoo - uauhah u aao u oah - h u ahahh aoou ha + uaoh uhu uahaaaaoo + uauhah u aao u oah + h u ahahh aoou ha haoooo aaoou uou ah oahuouooaouoa ouuhh o ouou uhauuuoaah h ou aouhouo aaooao hh - hhauhohhuu au aaohu - uhuoh o a ohahuhohoa hah + hhauhohhuu au aaohu + uhuoh o a ohahuhohoa hah ua hu ouo o aoau hah ah - ah huu oouhhau aoaoaaohoo ha - a ahu uoo uoahuo uo + ah huu oouhhau aoaoaaohoo ha + a ahu uoo uoahuo uo uo u ohouao uuoouhh hhuhuuh ouhoaao hau aouo - uha uh huo uooaah u + uha uh huo uooaah u u ooah uo ahauao huhuu hauu h a ou oh ouhuouhoaaha aaooohhouhhha hauauuu - aaaaaaa uuuuuu -\n%s -> %s - %s aaou %d - oouaaahh %s - aaaaaaugh ouh %d uuoogahaaah ooua-h-ha - %daaa %duuu %dhhhg - %daaaaaaauuhh %doouuahaaha - %dmmmm... - aaaaaaaaaaaaahh (%.2foouo) - aghaaaooo-ough %.1f - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaooooohhuhuhuhuhuhhuhuhhhaaagaha-agagaoooo - %d ooaaaugha - oooooh aaah aauuuggghauuh - aaoohhu %s aoouu - oooaaaauhgaaaa - aaaaaah-ooooooooouuagh - auugggguuuuuuhhh uuuu hhhhhhhhuhggghggg - ggaaaahhhhhhh gaauuuuuuuaaaau - aaauuuuggggguu - ooo aagg hhhh - ooo aagg hhhh - uuuuhhhoouuooog ooaaahhhh - uuu ugggg - ooo guggg ooh - auuuooohaaaaagh - uuuuuuuh aaaoo o - ooooooouuuua aa aaagh agh - AAAAUUUOH - aoughoooaaaa - oooouuuh - ahaough aaouuuuh-h - auughooo - ooooooa aauoh - aaaaagh oouoo aaaaaaa - aaaaaagh uuohuoh - aaaaaauo agghhhhhhaoouu - uuuuuuuuh - ouaaahh - ooough aaoough aooou %s aaaa - ouooooouuuu oooooo - aaaaaaaaaaahhhgh-aooohoooo - aau aooooghaao - aagh aaaaaaaaaaaa oooh, aaough, ooga oguuu aaaaaaaaaaa ooooooohghh a-a-aaauo - %dmmmmmm.. -\naaaaooughugh - aooohuohaaaa ooooagh - oooooogh-aaaaaogh - guuuaaaahhhhhhhaaa - woooaaahh ahahaaaauu 🦍 - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOOOGGAGHAGHAAA - aoaaaaaoooghhh - oooooh uuaagh - @string/home_play - + aaaaaaa uuuuuu\n%s -> %s + \ No newline at end of file diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/app/src/main/res/values-ms/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 766bcdc7..c2561914 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Ep %d Cast: %s @@ -10,7 +9,7 @@ %dm Poster - Poster + \@string/result_poster_img_des Aflevering Poster Hoofdposter Volgende willekeurig @@ -110,7 +109,7 @@ Doorgaan met kijken Verwijder Meer Info - @string/home_play + \@string/home_play Een VPN kan nodig zijn om deze provider correct te laten werken Deze provider is een torrent, een VPN wordt aanbevolen Metadata wordt niet geleverd door de site, het laden van video\'s zal mislukken als deze niet op de site bestaat. @@ -129,14 +128,14 @@ Eigengravy Modus Voegt een snelheidsoptie toe in de speler Swipe to seek - Veeg naar links of rechts om de tijd in de videospeler te regelen + Veeg naar links of rechts om de tijd in de videoplayer te regelen Veeg om instellingen te wijzigen Veeg naar links of rechts om de helderheid of het volume te wijzigen Dubbeltik om te zien Dubbeltik om te pauzeren - Videospeler aantal zoeken + Speler zoeken bedrag Tik twee keer aan de rechter- of linkerkant om vooruit of achteruit te zoeken - Tik twee keer in het midden om te pauzeren + Tik in het midden om te pauzeren Systeemhelderheid gebruiken Gebruik systeemhelderheid in de app-speler in plaats van een donkere overlay Kijkvoortgang bijwerken @@ -223,8 +222,8 @@ Film Serie Tekenfilm - @string/anime - @string/ova + \@string/anime + \@string/ova Torrent Documentaire Aziatisch drama @@ -406,7 +405,4 @@ Start de volgende episode wanneer deze afgelopen is Volgende episode automatisch afspelen De update is gestart - Bibliotheek - Browser - Logboek - + \ No newline at end of file diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 43738665..b5132028 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -1,4 +1,4 @@ - + Fleire val Heim @@ -183,4 +183,4 @@ Varigheit Direktesendingar Programoppdateringar - + \ No newline at end of file diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index fddd4919..41bf704d 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -1,8 +1,8 @@ - + Plakat - @string/result_poster_img_des + \@string/result_poster_img_des Episode Plakat Main Plakat Neste tilfeldig @@ -412,7 +412,7 @@ Slå av/på grensesnittselementer på plakat Hopp over denne oppdateringen Forårsaker tilfeldige krasj hvis satt for høyt. Ikke endre dette hvis du ikke har lite minne. - @string/home_play + \@string/home_play Sikkerhetskopier data Data lagret Kunne ikke logge inn på %s @@ -422,11 +422,11 @@ Sensurerbart Vev Lenke til strøm - @string/anime + \@string/anime Skjul valgt videokvalitet i søkeresultater Lastet inn sikkerhetkopifil Oppdateringer og sikkerhetskopi - @string/ova + \@string/ova Avslutt\? Sensurerbart Alle %s er allerede nedlastet @@ -492,4 +492,4 @@ Oppdatering startet Programtillegg nedlastet Programmet vil oppgraderes når du avslutter det - + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a2a07dd7..244ae2e1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,6 +1,5 @@ - - - + + Prędkość (%.2fx) Ocena: %.1f Znaleziono nową aktualizację! @@ -255,7 +254,7 @@ Nie pokazuj ponownie Pomiń tę aktualizację Aktualizacja - Domyślna jakość (WiFi) + Domyślna jakość Maksymalna ilość znaków w tytule odtwarzacza Rozdzielczość odtwarzacza wideo Rozmiar bufora wideo @@ -374,7 +373,7 @@ Pomiń setup Dostosuj wygląd aplikacji do urządzenia Zgłaszanie błędów - Co chciałbyś obejrzeć + Co chciałbyś obejrzeć\? Gotowe Rozszerzenia Dodaj repozytorium @@ -451,12 +450,12 @@ /%d Obsada: %s Automatycznie instaluj wszystkie jeszcze niezainstalowane wtyczki z dodanych repozytoriów. - Poster + \@string/result_poster_img_des Podsumowanie Instalator APK Niektóre telefony nie obsługują nowego instalatora pakietów. Wypróbuj tryb legacy, jeśli aktualizacje nie zostaną zainstalowane. password123 - @string/ova + \@string/ova MojaFajnaWitryna MyCoolUsername 127.0.0.1 @@ -464,9 +463,9 @@ przyklad.pl /\?\? Instalator pakietów - @string/home_play + \@string/home_play hello@world.com - @string/anime + \@string/anime Opening Ending Mixed opening @@ -493,44 +492,4 @@ Rozpoczęto aktualizację Pobrano rozszerzenie Usuń z obejrzanych - Przeglądarka - Data aktualizacji (od nowego do starego) - Sortuj według - Sortuj - Otwórz za pomocą - Ocena (od najwyższej do najniższej) - Ocena (od najniższej do najwyższej) - Data aktualizacji (od starego do nowego) - Alfabetycznie (od A do Z) - Alfabetycznie (od Z do A) - Wybierz bibliotekę - Biblioteka - Wygląda na to, że twoja biblioteka jest pusta :( -\nZaloguj się na swoje konto lub dodaj programy do swojej lokalnej biblioteki - Wygląda na to, że ta lista jest pusta, spróbuj przełączyć się na inną - Znaleziono plik trybu bezpiecznego. -\nRozszerzenia nie zostaną wczytane, dopóki plik nie zostanie usunięty. - Używana ilość przewijania, gdy widoczny jest odtwarzacz - Ukryty odtwarzacz - ilość przewijania - Android TV - Pokazany odtwarzacz — ilość przewijania - Używana ilość przewijania, gdy ukryty jest odtwarzacz - Dziennik - Uruchom ponownie - Rozpocznij - Nie powiodło się - Ukończone powodzeniem - Serwer pośredniczący raw.githubusercontent.com - Obejścia ISP - Test dostawcy - Zatrzymaj - Przywróć - Aktualizowanie subskrybowanych programów - Zasubskrybowano - Zasubskrybowano %s - Anulowano subskrypcję %s - Został wydany odcinek %d! - Obchodzi blokadę GitHuba za pomocą jsdelivr, może spowodować opóźnienie aktualizacji o kilka dni. - Nie udało się połączyć z GitHub, włączono serwer pośredniczący jsdelivr. - Domyślna jakość (dane mobilne) - + \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index dd722f62..9353664e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,65 +1,65 @@ - + %s Ep %d %dh %dm %dm - O episódio %d será lançado em + Episódio %d será lançado em Poster - Pôster do episódio - Poster - Pôster Principal + Capa do Episódio + \@string/result_poster_img_des + Capa Principal Próximo Aleatório Voltar - Alterar Provedor + Trocar Provedor %dd %dh %dm Fonte Resolução Extras - Visualizar plano de fundo + Preview Background Velocidade (%.2fx) Classificado: %.1f Nova atualização encontrada! \n%s -> %s - Preenchimento + Enchimento CloudStream - Assistir com o CloudStream + Reproduzir com CloudStream Início - Pesquisar - Downloads - Configurações + Pesquisa + Transferências + Opções Procurar… - Pesquisar %s… - Sem dados - Mais opções + Procurar em %s… + Sem Dados + Mais Opções Próximo episódio - Gêneros - Compartilhar - Abrir no navegador - Pular carregamento + Géneros + Partilhar + Abrir no Navegador + Saltar Carga Carregando… Assistindo - Em espera + Em Espera Concluído - Desistido - Pretendo assistir - Nenhum - Reassistindo - Reproduzir filme - Reproduzir transmissão ao vivo + Abandonado + Planeio Assistir + Nenhuma + Assistindo de Novo + Reproduzir Filme + Reproduzir Livestream Transmitir Torrent Fontes Legendas - Tentar conexão novamente… - Voltar - Reproduzir episódio - Download - Baixado - Baixando - Download Pausado - Download Iniciado - Falha no Download - Download cancelado - Download concluído + Voltar a tentar ligação… + Voltar Atrás + Reproduzir Episódio + Transferir + Transferido + A Transferir + Transferência em Pausa + Transferência Iniciada + Transferência Falhou + Transferência Cancelada + Transferência Completa Stream Erro a Carregar Links Armazenamento Interno @@ -123,16 +123,16 @@ Modo Eigengravy Acrescenta uma opção de velocidade no player Deslize para andar - Deslize para os lados para controlar a posição em um vídeo + Deslize para a esq. ou dir. para controlar o tempo no player Deslize para mudar as configurações - Deslize para cima ou para baixo, no lado esquerdo ou direito, para ajustar brilho ou volume + Deslize do lado esq. ou dir. para ajustar brilho ou volume Reproduzir automaticamente próximo episódio Começa o próximo episódio quando o atual termina Toque duplo para avançar Toque duplo para pôr em pausa - Tempo de busca no player (Segundos) + Segundos avançados no player Toque duplo no lado esq. ou dir. para andar para trás ou para a frente - Toque duas vezes no meio para pausar + Toque no meio para pôr em pausa Usar brilho da sistema Usar brilho do sistema no player em vez de uma sobreposição escura Atualizar progresso @@ -142,7 +142,7 @@ Arquivo de backup carregado Falha ao restaurar dados do ficheiro %s Dados guardados com sucesso - Permissão de armazenamento não encontrada, por favor tente novamente. + Permissões de armazenamento em falta, por favor tente de novo Erro no backup de %s Procurar Contas @@ -158,7 +158,7 @@ Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa Atualizações de plugin automáticas Mostrar atualizações da app - Procurar automaticamente por novas atualizações depois de iniciar o app. + Procurar novas atualizações automaticamente ao iniciar Atualizar para pré-lançamentos Procura atualizações de pré-lançamento em vez de só lançamentos oficiais Github @@ -250,15 +250,15 @@ Não mostrar de novo Saltar esta Atualização Atualizar - Qualidade Preferida (WiFi) - Máximo de caracteres do título no player de video + Qualidade Preferida + Máximo de caracteres do título de vídeos Resolução do player de vídeo Tamanho do buffer do vídeo Comprimento do buffer do vídeo Cache do vídeo em disco Limpar cache de vídeo e imagem - Causará travamentos em dispositivos com pouca memória se definido muito alto , como uma Android TV. - Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como uma Android TV. + Causará travamentos aleatórios se definido muito alto. Não mude se tiver pouca memória RAM, como um Android TV ou um telefone antigo + Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como em dispositivos Android TV DNS sobre HTTPS Útil para contornar bloqueios do fornecedor de internet Clonar site @@ -273,7 +273,7 @@ Aviso Legal Geral Botão Aleatório - Mostra o botão Aleatório na página inicial, que pode escolher aleatoriamente um filme ou série + Mostra o botão Aleatório na página inicial Idioma dos fornecedores Layout da App Mídia preferida @@ -363,7 +363,7 @@ Plugin Carregado Plugin Apagado Falha ao carregar %s - Download iniciado %d %s… + Iniciada a transferência %d %s Transferido %d %s com sucesso Tudo %s já transferido Transferência em batch @@ -375,158 +375,10 @@ Transferido: %d Desativado: %d Não transferido: %d - O CloudStream não possui sites instalados por padrão. Você precisa instalar os sites a partir de repositórios. -\n -\nDevido a uma restrição sem sentido de direitos autorais (DMCA) pela Sky UK Limited 🤮 não podemos vincular o site do repositório no aplicativo. -\n -\nJunte-se ao nosso Discord ou pesquise online. + Adicionar um repositório para instalar extensões de sites Ver repositórios da comunidade Lista pública Todas as legendas em maiúsculas Transferir todos os plugins deste repositório\? %s (Desativado) - Instalador APK - %d min - Assistir Trailer - Marcar como visto/não visto - Reproduzir - Instalar automaticamente todos os plugins ainda não instalados dos repositórios adicionados. - Baixar extensões automaticamente - Refazer o processo de configuração - -30 - Vídeo - +30 - %s %d%s - Elenco: %s - Atualização iniciada - Log - Alguns aparelhos não suportam o novo instalador de pacotes. Use a opção legado caso não esteja conseguindo atualizar. - %d-%d - %d %s - Iniciar - Falha - Sucesso - Biblioteca - Navegar - Aplicativo de Anime pelos mesmos desenvolvedores - Ova - Anime - Player visível - Procurar valor - Instalando atualização do app… - Você tem certeza que deseja sair\? - Versão - Encerramento - Limpar histórico - Abertura - Não - Ordenar por - Sim - Baixando atualização do app… - Episódio %d lançado! - Créditos - Descrição - Tamanho - Parar - Modo seguro ligado - Histórico - Ordenar - Player interno - Autores - Suportado - Idioma - Instalar a extensão primeiro - Playlist HLS - Player de vídeo preferido - Estado - Gestos - Faixas - WP - Cam - Abertura - Selecionar Biblioteca - Usando jsdelivr o bloqueio do GitHub pode ser contornado. Pode atrasar atualizações em alguns dias. - VLC - Todas as linguagens - Atualizado (Novo para Antigo) - Inscrito - HDR - Reiniciar - Navegador Web - Atualizado (Antigo para Novo) - Web Video Cast - DVD - Instalador de pacotes - MPV - Remover dos assistidos - Não foi possível instalar a nova versão do aplicativo - Inscrição cancelada em %s - Final misto - Avaliações (Decrescente) - Aplicar ao reiniciar - Referente - Player oculto - Quantidade de Busca - raw.githubusercontent.com Proxy - Blu-ray - Aparência - 1000 ms - SDR - 18+ - Abrir com - Teste de provedor - UHD - Ver informações sobre falha - Aplicativo não encontrado - Reverter - Link para transmitir - Plugins baixados - %d plugins atualizados - Pular %s - Abertura mista - Alfabético (Z a A) - Parece que esta lista está vazia, tente trocar para outra - Inscrito em %s - 4K - Faixas de vídeo - O aplicativo será atualizado ao sair - Atualizando shows inscritos - Alfabético (A a Z) - Avaliações (Crescente) - Parece que a sua biblioteca está vazia :( -\nFaça login em uma conta de biblioteca ou adicione shows à sua biblioteca local - Arquivo de modo de segurança encontrado! -\nNenhuma extensão será carregada na inicialização do app até que o arquivo seja removido. - Contorno do provedor de serviço de internet (ISP) - Links - Recursos do Player - Recursos - Atualizações de aplicativos - Qualidade Preferida (Dados Móveis) - Quantidade de busca (em segundos) usada quando o player de video está visível - Quantidade de busca (em segundos) usada quando o player de video está oculto - Falha ao conectar com GitHub, ativando proxy jsdelivr. - Cache - Android TV - Legendas - %s %s - TS - Cam - Cam - HQ - HD - TC - Web - Nota: %s - Legado - Todas as extensões foram desativadas devido a uma falha para ajudá-lo a encontrar a que está causando o problema. - Recapitular - Mostrar pop-ups para pular abertura/encerramento - Muito texto. Não é possível salvar na área de transferência. - Marcar como assistido - Backup - Extensões - Ações - Layout - Configurações padrão - SD - Faixas de áudio - + \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index aa443783..982546bc 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Ep %d Distribuție: %s @@ -10,7 +9,7 @@ %dm Poster - Poster + \@string/result_poster_img_des Poster Episod Poster Principal Următorul la Întâmplare @@ -107,7 +106,7 @@ Continuați să urmăriți Eliminați Mai multe informații - @string/home_play + \@string/home_play Există probabilitatea necesitații unui VPN pentru ca acest furnizor să funcționeze corespunzător Acest furnizor este un torrent, se recomandă un VPN Metadatele nu sunt furnizate de către site, există posibilitatea ca încărcarea videoclipului să eșueze. @@ -143,7 +142,7 @@ Fișier de rezervă încărcat Imposibilitatea de a restaura datele din %s Date stocate - Permisiunea de arhivare lipșe, vă rugăm să încercați din nou. + Permisiuni de arhivare lipsă, vă rugăm să încercați din nou Eroare de backup %s Căutare Conturi și credite @@ -155,7 +154,7 @@ Nu trimiteți niciun fel de date Afișează etichetele [filler] pentru anime Arată trailerul - Arată afișele de la Kitsu + Arată posterele de la Kitsu Afișați actualizările aplicației Căutați automat noi actualizări la pornire Actualizați la prerelease @@ -385,8 +384,4 @@ Începe următorul episod când se termină episodul curent Ascundeți calitatea video selectată în rezultatele căutării Redare Livestream - Librărie - Log - Browser - Joacă cu CloudStream - + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9d8f6895..39e74794 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,4 +1,4 @@ - + История Нет @@ -24,13 +24,13 @@ Выйти Серия %d будет выпущен в Плакат - Плакат - Плакат эпизода + \@нить/результат_плокат_картинка_ + Серия плакат Главный плакат Следующий случайный Вернуться Изменить поставщика - Предпросмотр фона + Фон предпросмотр Скорость (%.2fx) Оценили: %.1f Новое обновление найдено! @@ -48,31 +48,31 @@ Поиск %s… Нет данных Дополнительные опции - Следующий эпизод + Следующий серия Жанры Поделиться - Открыть в браузере + Открыть в браузер Пропустить загрузку Просмотр Приостановленно Завершено Брошенный - План посмотреть - Нет + План по смотреть + Никто Пересмотрю Смотреть фильм - Смотреть трейлер - Смотреть Livestream + Проиграть трейлер + Воспроизвести Livestream Источники Субтитры - Смотреть эпизод + Проиграть серия Повторная попытка подключение… Вернуться - Скачано + Скачали Скачивание - Скачать остановлена + Скачать приостановленный Скачать начатый - Скачать отменённый + Скачать отменено Скачать выполнено Инфо Обновление началось @@ -90,7 +90,7 @@ Скорость проигрыватель Воспроизвести Эпизод %dд %dч %dм - %d мин. + %d мин Dub Sub Установите смотреть состояние @@ -114,10 +114,10 @@ Шрифт Размер шрифта Удалить файл - Воспроизвести файл + Проиграть файл Внутренняя память - Продолжить Скачать - Остановить скачивание + Скачать резюме + Приостановить скачать Отключить автоматическое информирование об ошибках Импортируйте шрифты поместив их в %s Продолжить смотреть @@ -142,10 +142,10 @@ Добавляет опцию скорости в проигрывателе Проведите пальцем для поиска Проведите пальцем для изменения настроек - Проведите вверх или вниз по левой или правой стороне, чтобы изменить яркость или громкость + Проведите пальцем по левой или правой стороне для изменения яркости или громкости Автопроиграть следующего серия Поток торрент - Проведите пальцем из стороны в сторону, чтобы управлять свое место в видеоролике + Проведите пальцем влево или вправо, чтобы управлять временем в видеоплеере Начните следующий серию, когда закончится текущий Загружена резервная копия Не удалось восстановить данные из %s @@ -159,7 +159,7 @@ Автоматическое обновление плагинов Автоматическая загрузка плагинов Показать обновления приложения - Автоматически проверять обновления при старте приложения. + Автоматически проверять обновления при старте Обновится до пре-релиза APK установщик Github @@ -169,17 +169,17 @@ Восстановить по умолчанию Извините, приложение прекратило работу. Анонимный отчет об ошибке будет отправлен разработчикам Эпизод - Эпизодов + Эпизоды С Э Эпизоды не найдены Удалить файл - Продолжить + Возобновить -30 +30 Это будет удалено безвозвратно%s \nВы уверены\? - %d мин. + %dм \nосталось Завершено Год @@ -193,7 +193,7 @@ Другое Ошибка загрузки, проверьте разрешения хранилища Копировать ссылку - Автоскачивание + Автоматическая загрузка Загрузка. Зеркало Сезон Аниме приложение от тех же разработчиков @@ -213,11 +213,11 @@ NSFW NSFW Фильм - Сериал + Серия Торрент Документальный Азиатская драма - Общие + Общий Провайдеры Макет Расширения @@ -227,7 +227,7 @@ Использовано Двойное нажатие для паузы Коснитесь дважды правой или левой стороны для поиска вперед или назад - Нажмите дважды в центре, чтобы сделать паузу + Нажмите в центре для паузы Использовать системную яркость Автоматически синхронизировать текущий прогресс эпизода Ошибка резервного копирования %s @@ -240,7 +240,7 @@ Обновление не найдено Изменить размер Источник - Проверить обновления + Проверьте наличие обновления Клон сайта DNS через HTTPS Удалить сайт @@ -248,8 +248,8 @@ Синхронизация субтитров Добавить клон существующего сайта с другим URL-адресом Используется для обхода блокировок интернет провайдера - Путь скачивания - Давал бенен + Путь загрузки + учитывая бенен Обновить Основной цвет Языки поставщиков @@ -268,7 +268,7 @@ Плагин удалён Описание Версия - Статус + Состояние Размер Авторы Поддерживается @@ -302,15 +302,15 @@ Пропустить OP Больше не показывать Пропустить это обновление - URL сервера NGINX + URL-адрес NGINX-сервера Создать учётную запись - Добавить слежение + Добавить трекинг Добавлено %s Синхронизировать Оценено %d из 10 Посмотреть информацию о сбое - Предпочитаемый видеоплеер + Предпочитаемый видео-проигрыватель Веб-браузер Приложение не найдено Все языки @@ -318,7 +318,7 @@ Титры Отметить как просмотренное Разрешение видеоплеера - Предпочтительное качество видео (WiFi) + Желаемое качество видео Максимум символов Длинна буфера Кеш видео на диске @@ -328,7 +328,7 @@ Вызывает проблемы, если установлено слишком высокое значение на устройствах с небольшим объемом памяти, таких как Android TV. Легкая новелла от тех же разработчиков Язык - Плейлист HLS + HLS Плейлист Сначала установить расширение Внутренний проигрыватель Синопсис @@ -340,7 +340,7 @@ Возможности плеера Субтитры Расположение названия плаката - ТV (телевидение) + TV Телефон Эмулятор Под плакатом @@ -363,8 +363,8 @@ Расширения URL репозитория Плагин загружен - @string/home_play - Перемотка двойным нажатием + \@string/home_play + Перемотка двойным касанием /\?\? /%d 18+ @@ -393,140 +393,43 @@ Отключено: %d %s %s %s аутентифицировано - Не удается логин на %s + Не удалось перейти к %s Макс - Мин - Контурный - С тенью + Минимум + Очертание + Тень 1000 мс Задержка субтитров Без задержки субтитров Используйте, если субтитры отображаются на %d мс слишком рано Используйте, если субтитры отображаются %d мс слишком поздно Нормально - Поднятые + Поднятие Съешь ещё этих мягких французских булок, да выпей же чаю Рекомендуется Загружено %s - Аниме - OVA + \@нить/аниме + \@нить/ova Этикетка Dub Сайт Функции Главное Источник Случайный - Скоро… + Скоро будет… Этикетка Sub Фон - Oтoбpaжeниe + Вид Трейлер %s (отключено) Следующий В CloudStream по умолчанию не установлены сайты. Вам необходимо установить сайты из репозиториев. \n -\nИз-за безмозглой DMCA-атаки со стороны Sky UK Limited 🤮 мы не можем привязать сайт репозитория в приложении. +\nИз-за безмозглой DMCA-атаки со стороны Sky UK Limited 🤮 мы не можем связать сайт репозитория в приложении. \n \nПрисоединяйтесь к нашему Discord или ищите в интернете. Недопустимые данные Разрешение и название Предыдущий Разрешение - Браузер - Библиотека - Обновленный (старый - новый) - Алфавитный (А до Я) - Алфавитный (Я до А) - Выбрать библиотеку - Открыть с - Похоже, ваша библиотека пуста :( -\nВойдите в аккаунт с библиотекой или добавьте сериалы в локальную библиотеку - Сортировка - Открытый список - Рейтинг (высокий - низкий) - Рейтинг (низкий - высокий) - Обновленный (новый - старый) - Сортировать по - PackageInstaller - Кодировка субтитров - Загрузить из файла - Рейтинг: %s - Скачано %d %s - Все %s уже скачаны - Начата загрузка %d %s… - Не скачано: %d - Скачать все плагины из этого репозитория\? - Включен безопасный режим - Скачано: %d - Обновлено %d плагинов - Загрузить из интернета - Загрузка обновления приложения… - Недопустимый URL - Применить при перезапуске - Отчеты ошибках - Что вы хотите увидеть - Смотрите видео на этих языках - Скачано файл - Изображение постера - Пакетная загрузка - Скачайте список сайтов, который вы хотите использовать - Отображать Аниме с Дубляжом/Субтитрами - Включить NSFW на поддерживаемых провайдерах - Удалять скрытые субтитры из субтитров - Дополнительно - Изменить вид интерфейса, чтобы соответствовать устройству - Аудио дорожки - Это также удалит все плагины репозитория - Просмотреть репозитории сообщества - Видео дорожки - Все расширения были отключены из-за сбоя, чтобы помочь вам найти то, которое вызывает проблемы. - Повтор - Слишком много текста. Не удалось сохранить в буфер обмена. - Установка обновления приложения… - Не удалось установить новую версию приложения - Файл безопасного режима найден! -\nНе загружаются никакие расширения при запуске, пока файл не будет удален. - Приложение будет обновлено после выхода - Похоже, этот список пуст, попробуйте переключиться на другой - Все субтитры заглавными - Показывать всплывающие окна для пропуска вступления/заключения - Фильтровать по предпочитаемому языку медиа - Неверный ID - Ссылка на стрим - Показывает кнопку на главной странице, с помощью которой можно выбрать случайный фильм или сериал с главной страницы - Рандомная кнопка - Legacy (старый) - Веб видеокаст - Не отправляет данные - Перезагрузить ссылки - Предпочтительные медиа - Опущенные - Объем перемотки плеера (секундах) - Объем перемотка, используемый, когда плеер виден - Плеер показан - Перемотки объем - Плеер спрятан - Перемотки объем - Удалять лишнее из субтитров - Местоположение ползунка, когда игрок скрыт - Android TV - Второго планa - Смешанный опенинг - Смешанный конец - Тест провайдер - Журнал - Запустить - Выполнено - Неудачный - Прекратить - Перезапустить - Вернуться - Подписался на %s - Предпочтительное качество видео (Мобильный интернет) - raw.githubusercontent.com Прокси-сервер - Не удалось подключиться к GitHub. Будет выполнен прокси jsdelivr. - Эпизод %d выпущен! - Обходы провайдера - Обновление подписки на фильмы и сериалы - Обход ограничения доступа к GitHub с помощью jsdelivr может задержать обновления на несколько дней. - Подписные - Отказались от подписки на %s - + \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index a1afd6d9..97039233 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -1,4 +1,4 @@ - + Našla sa nová aktualizácia! \n%s -> %s @@ -17,7 +17,7 @@ %dd %dh %dm %dm %d min - Plagát + \@string/result_poster_img_des Plagát epizódy Hlavný plagát Prehrať s CloudStream @@ -99,9 +99,9 @@ Tento poskytovateľ je torrent, odporúča sa VPN Importovať písma ich umiestnením do %s Viac informácií - @string/home_play + \@string/home_play Pokračovať v sledovaní Na správne fungovanie tohto poskytovateľa môže byť potrebná VPN Stránka neposkytla žiadne metadáta, načítanie videa zlyhá, ak na stránke neexistuje. Popis - + \ No newline at end of file diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index ce7d557a..b944b6b3 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -1,4 +1,4 @@ - + Metalaya: %s %dm %ds %dd @@ -487,4 +487,4 @@ Bilowga Bilow isku qasan Qoraalka dhamaadka - + \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 0b7ba89e..32336b66 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1,6 +1,5 @@ - - - + + Betygsatt: %.1f Hastighet (%.2fx) Ny uppdatering hittad! @@ -34,7 +33,7 @@ Undertexter Försök ansluta igen… Gå tillbaka - @string/result_poster_img_des + \@string/result_poster_img_des Spela Avsnitt Ladda ner Intern lagring @@ -45,7 +44,7 @@ Inaktivera automatisk felrapportering Mer information Hide - @string/result_poster_img_des + \@string/result_poster_img_des Spela upp Info Nästa @@ -236,7 +235,7 @@ Episod %d kommer släppas om %d min Visa trailers - @string/home_play + \@string/home_play OVA %d-%d %d %s @@ -245,7 +244,7 @@ %dm \nåterstår NSFW - @string/ova + \@string/ova Torrent NSFW +30 @@ -274,7 +273,7 @@ Asiatiska draman Andra Tecknade serier - @string/anime + \@string/anime Dokumentär Asiatisk drama Video @@ -369,4 +368,4 @@ Titta på videor på dessa språk Föregående Spår - + \ No newline at end of file diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 4370e760..b2334c5f 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -1,4 +1,4 @@ - + தேடுக தேடல் %s… @@ -107,4 +107,4 @@ இடைநிறுத்துவதற்கு இருமுறை தட்டவும் Chromecast வசன அமைப்புகள் இருண்ட மேலடுக்குக்குப் பதிலாக ஆப் பிளேயரில் சிஸ்டம் பிரகாசத்தைப் பயன்படுத்தவும் - + \ No newline at end of file diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index cf3b1263..9e5b29d4 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -1,12 +1,11 @@ - - - + + %s Ep %d Poster - @string/result_poster_img_des + \@string/result_poster_img_des Episode Poster Main Poster Next Random @@ -264,4 +263,4 @@ Magdagdag ng Account Kasaysayan I-tanda bilang napanood na - + \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 74754008..bd245a61 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,6 +1,5 @@ - - - + + %d %s | %s %s • %s @@ -20,13 +19,13 @@ %dm Poster - Afiş + \@string/result_poster_img_des Bölüm Posteri Ana Poster Sonraki Rastgele - @string/play_episode + \@string/play_episode Geri git - @string/home_change_provider_img_des + \@string/home_change_provider_img_des Change Provider Preview Background @@ -47,7 +46,7 @@ Veri yok Daha fazla seçenek Sonraki bölüm - @string/synopsis + \@string/synopsis Türler Paylaş Tarayıcıda aç @@ -118,47 +117,47 @@ Hiç muz verilmedi Otomatik seçilecek dil İndirilecek diller - Altyazı dili - Sıfırlamak için basılı tut + Alt yazı dili + Varsayılana döndürmek için basılı tut Fontları içe aktarmak için %s konumuna yerleştirin İzlemeye devam et Kaldır Daha fazla bilgi - @string/home_play + \@string/home_play Bu sağlayıcının düzgün çalışması için bir VPN gerekebilir - Bu sağlayıcı torrent kullanıyor, bir VPN önerilir + Bu sağlayıcı bir torrent. VPN önerilir Metadata site tarafından sağlanmamış, veri site\'de bulunmuyorsa video yüklenmesi başarısız olacak. Açıklama Konu bulunamadı Açıklama bulunamadı - Logcat\'i görüntüle 🐈 - Görüntü içinde görüntü - İçerik diğer uygulamaların üzerinde küçük bir pencerede oynatılmaya devam eder + Logcat\'i göster 🐈 + Resim-içinde-resim + Diğer uygulamaların üzerinde minyatür bir oynatıcıda oynatmaya devam eder Oynatıcı yeniden boyutlandırma butonu - Siyah sınır çizgilerini kaldır - Alt yazılar + Siyah sınırları kaldır + Alt yazı Oynatıcı alt yazı ayarları - Chromecast alt yazıları + Chromecast alt yazı Chromecast alt yazı ayarları - Eigengrau modu - Oynatıcıya hız seçeneği ekler - Atlamak için kaydır - Zamanı ayarlamak için yanlardan kaydır + Eigengravy modu + Oynatıcıya bir hız seçeneği ekle + Gözlemek için kaydır + Zamanı ayarlamak için sağa veya sola kaydır Ayarları değiştirmek için kaydır - Sol ve sağ taraftan yukarı kaydırarak ekran parlaklığı ve sesi ayarla + Sol ve sağ taraftan kaydırarak parlaklık ve sesi ayarla Sonraki bölümü otomatik oynat Mevcut bölüm bittiğinde sonraki bölüme başla - Çift dokunarak atla - İki kez dokunarak duraklat - Atlanacak süre (Saniye) - İleri ve geri atlamak için sağa ve sola iki kez dokun - Durdurmak için ekranın ortasına çift dokun + Gözlemek için çift tıkla + Durdurmak için çift tıkla + Oynatıcı gözleme miktarı + İleri ve geri atlamak için sağa ve sola çift tıkla + Durdurmak için ortaya tıkla Sistem parlaklığını kullan - Oynatıcıyı karartmak yerine sistem parlaklığını kullan + Oynatıcıda karanlık kaplama yerine sistem parlaklığını kullan İzleme ilerlemesini güncelle Mevcut bölüm ilerlemesini otomatik güncelle - Verileri yedekten geri yükle - Verileri yedekle + Yedekten geri yükle + Verileri yedekleyin Yedek dosyası yüklendi Geri yükleme başarısız oldu: %s Başarıyla yedeklendi @@ -166,21 +165,21 @@ %s yedeklenirken hata Ara Hesaplar - Güncellemeler ve yedekleme + Güncellemeler ve yedek Bilgi Gelişmiş arama - Arama sonuçlarını sağlayıcıya göre ayırır + Sağlayıcılara göre ayrılmış arama sonuçlarını ver Yalnızca çökmelerle ilgili verileri gönderir - Veri göndermez - Anime için filler bölümleri göster + Hiç veri göndermez + Anime için filler bölümleri gösterir Fragmanları göster Kitsu\'dan posterleri göster - Seçilen video kalitelerini arama sonuçlarında gösterme + Seçilen video kalitelerini arama sonuçlarında gizle Otomatik eklenti güncellemeleri Uygulama güncellemelerini göster - Uygulama başlatıldıktan sonra güncellemeleri otomatik olarak kontrol et. - Deneysel sürümlere güncelle - Yalnızca tam sürümler yerine deneysel güncellemeleri de ara + Başlangıçta yeni güncellemeleri otomatik olarak ara + Ön sürümlere güncelle + Sadece tam sürümler yerine ön sürüm güncellemelerini de ara GitHub Aynı geliştiriciler tarafından LightNovel uygulaması Aynı geliştiriciler tarafından anime uygulaması @@ -192,8 +191,8 @@ Bağlantı bulunamadı Bağlantı panoya kopyalandı Bölümü oynat - Varsayılan değere sıfırla - Üzgünüz, uygulama çöktü. Geliştiricilere anonim bir hata raporu gönderilecek + Varsayılana sıfırla + Üzgünüz, uygulama çöktü. Geliştiricilere isimsiz bir hata raporu gönderilecek Sezon %s %d%s Sezon yok @@ -206,13 +205,13 @@ Bölüm bulunamadı Dosyayı sil Sil - @string/sort_cancel + \@string/sort_cancel Durdur Sürdür -30 +30 - %s tamamen silinecek -\nEmin misiniz\? + %s dosyası tamamen silinecek +\nEmins misiniz\? %dm \nkaldı Devam ediyor @@ -237,21 +236,21 @@ Torrentler Belgeseller OVA - Asya dizileri + Asya dramaları Canlı yayınlar - +18 + NSFW Diğerleri Film Dizi Çizgi film - @string/anime - @string/ova + \@string/anime + \@string/ova Torrent Belgesel - Asya dizisi + Asya draması Canlı yayın - +18 + NSFW Video Kaynak hatası Sunucu hatası @@ -260,10 +259,10 @@ İndirme hatası, depolama izinlerini kontrol edin Bölümü Chromecast ile yayınla Bağlantıyı Chromecast ile yayınla - Burada oynat - %s üzerinden oynat + Uygulamada oynat + %s\'deda oynat Tarayıcıda oynat - Bağlantıyı kopyala + Linki kopyala Otomatik indir Şu kaynaktan indir Bağlantıları yenile @@ -282,22 +281,22 @@ Kilitle Yeniden boyutlandır Kaynak - Jeneriği geç + OP\'yi geç Bir daha gösterme Bu güncellemeyi atla Güncelle - Tercih edilen görüntü kalitesi (WiFi) - Video oynatıcı başlığı karakter üst sınırı - Oynatıcının çözünürlüğü + Tercih edilen izleme kalitesi + Oynatıcıdaki maksimum başlık karakter sayısı + Oynatıcının üst tarafındaki öğeler Video arabelleği boyutu Video arabelleği uzunluğu - Hafızadaki video önbelleği + Diskteki video önbelleği Video ve resim önbelleğini temizle - Çok yükseğe ayarlanırsa düşük belleğe sahip cihazlarda çökmelere neden olur (örn. Android TV). - Çok yükseğe ayarlanırsa düşük depolama alanına sahip sistemlerde sorunlara neden olur (örn. Android TV). - HTTPS üzerinden DNS (DoH) - İnternet Servis Sağlayıcısı (İSS) kısıtlamalarını aşmak için kullanışlıdır - Siteyi kopyala + Android TV gibi düşük belleğe sahip cihazlarda çok yükseğe ayarlanırsa çökmelere neden olur. + Çok yükseğe ayarlanırsa, Android TV cihazları gibi düşük depolama alanına sahip sistemlerde sorunlara neden olabilir. + HTTPS üzerinden DNS + ISP bloklarını atlatmak için kullanışlıdır + Klon site Siteyi kaldır Farklı bir URL ile mevcut bir sitenin klonunu ekleyin İndirme konumu @@ -306,16 +305,16 @@ Ekrana sığdır Uzat Yakınlaştır - Yasal Uyarı + Disclaimer legal_notice_key Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Genel - Rastgele İçerik - Ana sayfada rastgele bir film veya dizi seçen bir tuş gösterir + Rastgele butonu + Ana sayfada rastgele butonunu göster Sağlayıcı dilleri Uygulama düzeni Tercih edilen medya - Desteklenen sağlayıcılarda +18 içeriği etkinleştir + Desteklenen sağlayıcılarda NSFW\'yi etkinleştir Alt yazı kodlaması Sağlayıcılar Düzen @@ -337,7 +336,7 @@ hello@world.com 127.0.0.1 MyCoolSite - ornek.com + example.com Dil kodu (tr) Hiçbiri Normal Hepsi Maksimum Minimum - @string/none + \@string/none Dış hat Çökmüş Gölge @@ -377,7 +376,7 @@ Alt yazı senkronu 1000 ms Alt yazı gecikmesi - Alt yazılar %d ms erken görüntüleniyorsa bunu kullanın + Alt yazılar %d ms erken gözüküyorsa bunu kullanın Alt yazılar %d ms geç gözüküyorsa bunu kullanın Alt yazı gecikmesi yok Pijamalı hasta yağız şoföre çabucak güvendi Önerilen - %s eklendi + %s yüklendi Dosyadan yükle İnternetten yükle İndirilen dosya @@ -423,10 +422,10 @@ Geçersiz veri Geçersiz URL Hata - Alt yazılardan seçmeli alt yazıyı (CC) kaldır + Alt yazılardan seçmeli alt yazıyı kaldır Alt yazılardaki şişkinliği kaldır Tercih edilen medya diline göre filtrele - Ek içerikler + Ekstralar Fragman Yayına bağlan Yönlendiren @@ -434,7 +433,7 @@ Videoları bu dillerde izle Geri Kurulumu atla - Cihazınıza uygun uygulama görünümünü seçin + Cihazınıza uygun görünümü seçin Çökme raporları Ne izlemek istiyorsunuz Bitti @@ -446,7 +445,7 @@ Eklenti silindi %s yüklenemedi +18 - %d %s indirilmeye başlandı… + %d %s … indirilmeye başlandı %d %s indirildi %s\'nin tamamı zaten indirildi Toplu indir @@ -478,7 +477,7 @@ Çökme bilgisini göster Puan: %s Açıklama - Sürüm + Versiyon Durum Boyut Geliştiriciler @@ -500,14 +499,14 @@ Fragmanı oynat Eklenen depolardan henüz yüklenmemiş tüm eklentileri otomatik olarak yükleyin. Güncelleme başladı - Bazı cihazlar yeni paket yükleyiciyi desteklemez.. Güncellemeler yüklenmezse eski seçeneği deneyin. + Bazı cihazlar yeni paket yükleyiciyi desteklemez.. Güncellemele yüklenmezse eski seçeneği deneyin. Eklentileri otomatik olarak indir APK indirici - Bağlantılar + Linkler Uygulama güncellemeleri Yedek Oynatıcı özellikleri - Alt yazılar + Altyazılar Düzen Varsayılanlar Eklentiler @@ -532,50 +531,10 @@ İzlenenlerden kaldır Karışık son Karışık başlangıç - Katkıda Bulunanlar + Kredi Giriş Eklenti İndirildi - Eylemler - Açılış/bitiş için atlama açılır pencerelerini göster + Aksiyonlar + Açma/bitiş için atlama açılır pencerelerini göster Çok fazla metin. Panoya kaydedilemiyor. - Kütüphane - Tarayıcı - Görünüşe göre kütüphaneniz boş :( -\nBir kütüphane hesabına giriş yapın veya yerel kütüphanenize içerik ekleyin - Güvenli mod dosyası bulundu! -\nDosya kaldırılana kadar başlangıçta herhangi bir uzantı yüklenmiyor. - Sırala - Sırala - Güncellenme (Yeniden Eskiye) - Güncellenme (Eskiden Yeniye) - Alfabetik (A\'dan Z’ye) - Alfabetik (Z - A) - Kütüphane Seçin - Şununla aç - Görünüşe göre bu liste boş, başka bir listeye geçmeyi deneyin - Derecelendirme (Yüksekten Düşüğe) - Derecelendirme (Düşükten Yükseğe) - Yeniden başlat - Oynatıcı gizlenmişken atlanacak süre - İSS Kısıtlamaları - GitHub\'a ulaşılamadı, jsdelivr vekil sunucusu etkinleştiriliyor. - Başlat - Başarılı oldu - raw.githubusercontent.com vekil sunucusu (proxy) - Tercih edilen görüntü kalitesi (Mobil veri) - Oynatıcı görünürken atlanacak süre - Oynatıcı gizli durumdayken atlanacak süre miktarı - jsdelivr kullanarak GitHub kısıtlamasını aşar. Güncellemeler birkaç gün gecikebilir. - Android TV - Yeni bölüm %d yayınlandı! - Sağlayıcıyı kontrol et - Başarısız oldu - Durdur - Geri al - Abone olunan gösteriler güncelleniyor - Abone olunan - %s kanalına abone olundu - %s kanalı aboneliğinden çıkıldı - Günlük - Oynatıcı görünür durumdayken atlanacak süre miktarı - + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index bd062394..93e51c84 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,13 +1,13 @@ - + - Постер - Постер до епізоду + \@string/result_poster_img_des + Постер епізоду Завантаження скасовано Змінити постачальника Назад Рейтинг: %.1f Актори: %s - Епізод %d вийде через + Епізод %d буде випущено в Poster %s Еп. %d %dд %dгод %dхв @@ -15,16 +15,16 @@ %dхв Головний постер Наступний випадковий - Попередній перегляд фону + Перегляд фону Швидкість (%.2fx) - Знайдено нове оновлення! + Нове оновлення знайдено! \n%s -> %s Пошук Завантаження %d хв - Налаштування + Параметри Пошук… - Пошук на %s… + Пошук %s… Дані відсутні Більше опцій Наступний епізод @@ -35,12 +35,12 @@ Завершено Заплановано Покинуто - Переглянути фільм - Переглянути трейлер - Трансляція через торрент + Відтворити фільм + Відтворити трейлер + Відтворити торрент Повторити підключення… Назад - Переглянути епізод + Відтворити епізод Завантажено Завантаження Завантаження завершено @@ -50,8 +50,8 @@ Відновити завантаження Вимкнути відправку повідомлень про помилки Сховати - Переглянути - Інфо + Відтворити + Інформація Закладки Вилучити Змінити статус перегляду @@ -87,19 +87,19 @@ Налаштування субтитрів Chromecast Режим Eigengravy Проведіть пальцем, щоб змінити налаштування - Проведіть пальцем вгору або вниз ліворуч або праворуч, щоб змінити яскравість або гучність + Проведіть пальцем ліворуч або праворуч, щоб змінити яскравість або гучність Відтворення наступного епізоду після закінчення поточного Головна CloudStream Філер Програти в CloudStream - Трансляція + Потік Переглядаю Поділитися Відкладено Повторний перегляд Завантажити - Переглянути трансляцію + Відтворити трансляцію Джерела Субтитри Внутрішнє сховище @@ -109,7 +109,7 @@ Оновлення розпочато Помилка завантаження посилань Призупинити завантаження - Переглянути файл + Відтворити файл Детальніше Фільтр закладок Нічого @@ -121,10 +121,10 @@ Колір тексту Колір контуру Автовідтворення наступного епізоду - Проведіть пальцем з боку в бік, щоб керувати своїм положенням у відео + Проведіть пальцем ліворуч або праворуч, щоб керувати часом у відеоплеєрі %d Бананів для розробників Кнопка зміни розміру плеєра - @string/home_play + \@string/home_play Для коректної роботи цього постачальника може знадобитися VPN Метадані не надаються сайтом, завантаження відео не відбудеться, якщо їх немає на сайті. Картинка в картинці @@ -133,8 +133,8 @@ Проведіть пальцем, щоб перемотати Двічі торкніться, щоб перемотати Двічі торкніться для паузи - Крок перемотки (Секунди) - Натисніть двічі посередині, щоб призупинити + Крок перемотки + Натисніть посередині, щоб поставити на паузу Використовувати яскравість системи Оновити прогрес перегляду Відновлення даних з резервної копії @@ -165,10 +165,10 @@ Мова програми Цей постачальник не має підтримки Chromecast Посилань не знайдено - Переглянути епізод + Відтворити епізод Скинути до значення за замовчуванням Немає сезону - епізодів + Епізоди %d %s С Е @@ -208,8 +208,8 @@ Віддалена помилка Помилка рендеринга Дзеркало Chromecast - Переглянути в програмі - Переглянути в %s + Відтворити в програмі + Відтворити в %s Автозавантаження Завантажити дзеркало Перевірити наявність оновлень @@ -217,7 +217,7 @@ Пропустити OP Не показувати знову Оновити - Бажана якість перегляду (WiFi) + Бажана якість перегляду Заголовок Перемикання елементів інтерфейсу на плакаті Оновлення не знайдено @@ -230,7 +230,7 @@ Показати постери від Kitsu Автоматичне оновлення плагінів Автоматично встановлювати всі ще не встановлені плагіни з доданих репозиторіїв. - Автоматично шукати нові оновлення після запуску застосунку. + Автоматичний пошук нових оновлень при запуску Оновлення до бета-версій Посилання скопійовано в буфер обміну Деякі телефони не підтримують новий інсталятор пакетів. Спробуйте стару версію, якщо оновлення не встановлюються. @@ -255,11 +255,11 @@ Документальні фільми NSFW Фільм - OVA + \@string/ova Торрент Мітка якості NSFW - Переглянути в браузері + Відтворити в браузері Несподівана помилка плеєра Помилка завантаження, перевірте дозволи на зберігання Епізод Chromecast @@ -283,7 +283,7 @@ Особливості Загальне Випадкова кнопка - Показує кнопку на Головній сторінці, яка може вибрати випадковий фільм або серіал на Головній сторінці + Показати випадкову кнопку на Головній сторінці Мови постачальника Макет програми Бажані медіа @@ -489,44 +489,4 @@ Програму не знайдено Змішаний опенінг Видалити з переглянутого - За оновленням (від старого до нового) - За оновленням (від нового до старого) - Бібліотека - Сортувати - За рейтингом (від високого до низького) - Сортувати за - За алфавітом (від А до Я) - За рейтингом (від низького до високого) - Схоже, ваша бібліотека порожня :( -\nУвійдіть в обліковий запис бібліотеки або додайте серіали до вашої локальної бібліотеки - За алфавітом (від Я до А) - Виберіть бібліотеку - Відкрити з - Браузер - Схоже, цей список порожній, спробуйте перейти до іншого - Файл безпечного режиму знайдено! -\nРозширеня не завантажуються під час запуску, доки файл не буде видалено. - Android TV - Плеєр сховано - обсяг пошуку - Плеєр показано - обсяг пошуку - Обсяг пошуку, який використовується, коли плеєр видимий - Обсяг пошуку, який використовується, коли гравець прихований - Не вдалося - Пройдено - Перезапуск - Журнал - Старт - Стоп - Тест постачальника - Оновлення підписаних шоу - Підписано - Підписано на %s - Відписатися від %s - Епізод %d випущено! - Повернути - raw.githubusercontent.com Proxy - Не вдалося зв\'язатися з GitHub, увімкнувши проксі-сервер jsdelivr. - Обходи ISP - За допомогою jsdelivr можна обійти блокування GitHub. Можлива затримка оновлень на кілька днів. - Бажана якість перегляду (Мобільні дані) - + \ No newline at end of file diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index c19c6472..f733addc 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -1,4 +1,4 @@ - + کاسٹ: %s قسط %d جاری کیا جائے گا @@ -230,7 +230,7 @@ سلسلہ کارٹون انیمی - اووا + \@string/اووا ٹورینٹ دستاویزی فلم ایشیائی ڈرامے @@ -356,4 +356,4 @@ %d / 10 اٹھایا اگر سب ٹائٹلز %d ms بہت جلد دکھائے جائیں تو اسے استعمال کریں - + \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 520cfaa4..3aa5cf69 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Tập %d @@ -11,7 +10,7 @@ %dm Poster - Ảnh bìa + \@string/result_poster_img_des Episode Poster Main Poster Next Random @@ -67,7 +66,7 @@ Tải thành công Trực tiếp Đã có lỗi xảy ra - Bộ nhớ trong + Bộ nhớ máy Lồng Tiếng Phụ Đề Xóa Tệp @@ -111,7 +110,7 @@ Tiếp tục xem Loại bỏ Thông tin thêm - @string/home_play + \@string/home_play Bạn có thể sẽ cần sử dụng VPN để xem phim này Phim này được chiếu dưới dạng Torrent. Hãy sử dụng VPN để xem Thông tin phim @@ -119,7 +118,7 @@ Không tìm thấy thông tin Hiển thị Logcat 🐈 Chế độ cửa sổ nhỏ - Tiếp tục xem phim khi thoát ứng dụng hoặc khi tìm kiếm + Tiếp tục xem phim khi thoát app hoặc đang tìm kiếm Bật nút thu phóng khi xem Xóa khoảng đen của phim Phụ đề @@ -160,7 +159,7 @@ Không gửi dữ liệu Hiển thị tập phụ cho anime Hiển thị trailer - Hiển thị poster từ Kitsu + Hiển thị poster từ kitsu Ẩn chất lượng video khi tìm kiếm Tự động cập nhật plugin Hiển thị thông báo cập nhật App @@ -211,7 +210,7 @@ Không có phụ đề Mặc Định Còn trống - Đã sử dụng + Đã dùng App Phim Lẻ @@ -229,8 +228,8 @@ Phim Lẻ Phim Bộ Hoạt Hình - Anime - @string/ova + \@string/anime + \@string/ova Torrent Phim Tài Liệu Truyền Hình Châu Á @@ -261,20 +260,20 @@ Kiểm tra cập nhật Khóa Thu Phóng - Nguồn - Bỏ qua OP + Tuỳ chọn + Tập tiếp Không hiện lại - Bỏ qua bản cập nhật này + Bỏ qua Cập nhật - Chất lượng xem ưu tiên (WiFi) + Tự động chọn chất lượng phim Kí tự tối đa trên tiêu đề - Độ phân giải trình phát video - Kích thước bộ nhớ đệm video + Định dạng trình phát + Dung lượng video cache Thời lượng bộ nhớ đệm - Lưu bộ nhớ đệm video trên ổ cứng - Xoá bộ nhớ đệm hình ảnh và video - Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng ram thấp như Android TV. - Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng lưu trữ thấp như Android TV. + Dung lượng video cache + Xoá hình ảnh và video + Sẽ gây lỗi nếu đặt quá cao. Không thay đổi nếu máy có dung lượng ram thấp, chẳng hạn như Android TV hoặc điện thoại cũ + Sẽ thể gây lỗi trên các máy có dung lượng lưu trữ thấp, chẳng hạn như thiết bị Android TV nếu bạn đặt nó quá cao DNS over HTTPS Rất hữu ích để bỏ chặn ISP Sao chép trang web @@ -411,7 +410,7 @@ Đã xoá plugin Không tải được %s 18+ - Bắt đầu tải %d %s… + Bắt đầu tải %d %s Tải xuống %d %s thành công Toàn bộ %s đã được tải xuống Tải hàng loạt @@ -423,11 +422,7 @@ Đã tải: %d Đã vô hiệu: %d Không tải: %d - CloudStream không có sẵn trang web nào. Bạn cần cài đặt các trang web từ kho lưu trữ. -\n -\nDo Sky UK Limited đã gỡ xuống theo DMCA một cách thiếu suy nghĩ 🤮 chúng tôi không thể cài sẵn trang web. -\n -\nHãy tham gia Discord của chúng tôi hoặc tìm kiếm trực tuyến. + Thêm kho lưu trữ để cài tiện ích Xem kho lưu trữ của cộng đồng Danh sách công khai In hoa toàn bộ phụ đề @@ -442,18 +437,18 @@ Xem thông tin sự cố Lịch sử Đánh dấu là đã xem - Tự động tải xuống plugin + Tự động tải plugin Thiết lập lại Bộ cài APK Một số máy không hỗ trợ trình cài đặt gói mới. Hãy thử tùy chọn cũ nếu các bản cập nhật không cài đặt. %s %d%s - Xem giới thiệu - Tự động tải plugin còn thiếu. + Xem Trailer + Tự động tải plugins còn thiếu. Bắt đầu cập nhật Liên kết Danh sách HLS Trình phát ưu tiên - Trình phát mặc định + Trình phát mặc địng Đánh giá: %s Không Phiên bản @@ -492,7 +487,7 @@ Danh đề Giới thiệu Xoá lịch sử - Hiển thị nút tua nhanh cho mở đầu/kết thúc + Hiển thị cửa sổ tua cho mở đầu/kết thúc Văn bản quá dài. Không thể lưu vào bộ nhớ tạm. Xoá khỏi đã xem Bạn có chắc muốn thoát\? @@ -501,28 +496,4 @@ Đang cài bản cập nhật… Không thể cài đặt phiên bản mới Ứng dụng sẽ được cập nhật khi thoát - Thư viện - Trình duyệt - Plugin đã tải - Mặc định - Tải lên (Mới đến Cũ) - Tải lên (Cũ đến Mới) - Thư viện của bạn đang trống :( -\nHãy đăng nhập vào thư viện hoặc thêm phim vào thư viện cục bộ - Mở với - Siêu dữ liệu không có sẵn, video sẽ không được tải nếu nó không tồn tại trên trang web. - PackageInstaller - Sắp xếp - Xếp hạng (Cao đến Thấp) - Xếp hạng (Thấp đến Cao) - Chữ cái (Z đến A) - Sắp xếp - Có vẻ như danh sách này trống, hãy thử chuyển sang danh sách khác - Chữ cái (A đến Z) - Chọn Thư viện - Nhật ký - Chất lượng xem ưu tiên (Dữ liệu di động) - Thất bại - Thành công - Bắt đầu - + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3364ea86..a8341d46 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,6 +1,5 @@ - - - + + %d %s | %s %s • %s @@ -20,13 +19,13 @@ %dm 封面 - 封面 + \@string/result_poster_img_des 劇集封面 主封面 隨機下一個 - @string/play_episode + \@string/play_episode 返回 - @string/home_change_provider_img_des + \@string/home_change_provider_img_des 更改片源 預覽背景 @@ -47,7 +46,7 @@ 無資料 更多選項 下一集 - @string/synopsis + \@string/synopsis 類型 分享 在瀏覽器中打開 @@ -124,7 +123,7 @@ 繼續觀看 移除 更多資訊 - @string/home_play + \@string/home_play 此片源可能需要 VPN 才能正常使用 此片源是種子,建議使用 VPN 站點不提供元數據,如果站點上不存在元數據,影片載入將失敗。 @@ -206,7 +205,7 @@ 未找到劇集 刪除文件 刪除 - @string/sort_cancel + \@string/sort_cancel 暫停 繼續 -30 @@ -245,8 +244,8 @@ 電影 電視劇 卡通 - @string/anime - @string/ova + \@string/anime + \@string/ova 種子 紀錄片 亞洲劇 @@ -369,7 +368,7 @@ 全部 最大 最小 - @string/none + \@string/none 輪廓 凹陷 陰影 @@ -534,5 +533,4 @@ 預設 外觀 功能 - 瀏覽器 - + \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 44b93430..97a48597 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1,6 +1,5 @@ - - - + + %d %s | %s %s • %s @@ -20,13 +19,13 @@ %dm 封面 - 封面 + \@string/result_poster_img_des 剧集封面 主封面 随机下一个 - @string/play_episode + \@string/play_episode 返回 - @string/home_change_provider_img_des + \@string/home_change_provider_img_des 更改片源 预览背景 @@ -47,7 +46,7 @@ 无数据 更多选项 下一集 - @string/synopsis + \@string/synopsis 类型 分享 在浏览器中打开 @@ -124,7 +123,7 @@ 继续观看 移除 更多信息 - @string/home_play + \@string/home_play 此片源可能需要 VPN 才能正常使用 此片源为种子文件,建议使用 VPN 站点不提供元数据,如果站点上不存在元数据,视频加载将失败。 @@ -143,14 +142,14 @@ 倍速模式 在播放器中添加播放速度选项 滑动控制进度 - 左右滑动以控制视频中的位置 + 左右滑动控制播放进度 滑动更改设置 上下滑动修改亮度或音量 自动播放下一集 播放完毕后播放下一集 双击控制进度 双击暂停 - 双击控制进度时间 (秒) + 双击控制进度时间 在左右侧双击快进或快退 双击中间暂停 使用系统亮度 @@ -179,7 +178,7 @@ 自动更新插件 自动下载插件 显示应用更新 - 启动应用后自动搜索更新。 + 启动时自动搜索更新 更新至预览版 搜索预览版更新替代仅搜索完整版本 Github @@ -207,7 +206,7 @@ 未找到剧集 删除文件 删除 - @string/sort_cancel + \@string/sort_cancel 暂停 继续 -30 @@ -246,8 +245,8 @@ 电影 电视剧 卡通 - 动漫 - OVA + \@string/anime + \@string/ova 种子 纪录片 亚洲剧 @@ -287,7 +286,7 @@ 不再显示 跳过此更新 更新 - 首选播放画质(WiFi) + 首选播放画质 视频播放器标题最多字符 视频播放器标题 视频缓冲大小 @@ -312,7 +311,7 @@ Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. 通用 随机按钮 - 在主页上显示按钮,可以从主页上随机选择电影或电视剧 + 在主页中显示随机按钮 片源语言 应用布局 首选类型 @@ -370,7 +369,7 @@ 全部 最大 最小 - @string/none + \@string/none 轮廓 凹陷 阴影 @@ -538,44 +537,4 @@ 应用退出后将会更新 插件已下载 从已观看中移除 - 发现安全模式文件! -\n启动时不加载任何扩展,直到文件被删除。 - 浏览器 - - 排序方式 - 排序 - 评分(从高到低) - 评分(从低到高) - 更新(从新到旧) - 更新(从旧到新) - 字母排序(从 A 到 Z) - 字母排序(从 Z 到 A) - 选择库 - 打开方式 - 看来您的库是空的 :( -\n登录库账户或添加节目到您的本地库 - 看来此列表是空的,请尝试切换到另一个 - 播放器显示 - 快进快退秒数 - 播放器可见时使用的快进快退秒数 - 播放器隐藏 - 快进快退秒数 - 播放器隐藏时使用的快进快退秒数 - Android TV - 失败 - 片源测试 - 重启 - 停止 - 正在更新订阅节目 - 已订阅 - 已订阅 %s - 已取消订阅 %s - 开始 - 第 %d 集已发布! - 成功 - 日志 - raw.githubusercontent.com 代理 - 连接 Github 失败,正在启用 jsdelivr 代理。 - 使用 jsdelivr,可以绕过 GitHub 的封锁。可能会延迟几天的更新。 - ISP 绕过 - 还原 - 首选播放画质(移动数据) - + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 95094311..fc718008 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,7 +1,7 @@ - + @@ -13,10 +13,6 @@ ?attr/colorPrimary - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7dd4c989..61ff0c2b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -82,7 +82,4 @@ #515151 #FFFFFF #622C00 - - #48E484 - #ea596e \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 911c0d07..cee6bccc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ - + + search_providers_list app_locale @@ -13,10 +14,8 @@ fast_forward_button_time benene_count subtitle_settings_key - test_providers_key subtitle_settings_chromecast_key quality_pref_key - quality_pref_mobile_data_key player_pref_key prefer_limit_title_key prefer_limit_title_rez_key @@ -33,8 +32,6 @@ double_tap_enabled_key double_tap_pause_enabled_key double_tap_seek_time_key2 - android_tv_interface_off_seek_key - android_tv_interface_on_seek_key swipe_vertical_enabled_key autoplay_next_key display_sub_key @@ -44,7 +41,6 @@ random_button_key provider_lang_key dns_key - jsdelivr_proxy_key download_path_key Cloudstream app_layout_key @@ -84,7 +80,7 @@ %dm Poster - Poster + @string/result_poster_img_des Episode Poster Main Poster Next Random @@ -198,7 +194,6 @@ No Plot Found No Description Found Show Logcat 🐈 - Log Picture-in-picture Continues playback in a miniature player on top of other apps Player resize button @@ -210,17 +205,17 @@ Eigengravy Mode Adds a speed option in the player Swipe to seek - Swipe from side to side to control your position in a video + Swipe left or right to control time in the videoplayer Swipe to change settings - Slide up or down on the left or right side to change brightness or volume + Swipe on the left or right side to change brightness or volume Autoplay next episode Start the next episode when the current one ends Double tap to seek Double tap to pause - Player seek amount (Seconds) + Player seek amount Tap twice on the right or left side to seek forwards or backwards - Tap twice in the middle to pause + Tap in the middle to pause Use system brightness Use system brightness in the app player instead of a dark overlay @@ -251,7 +246,7 @@ Automatically download plugins Automatically install all not yet installed plugins from added repositories. Show app updates - Automatically search for new updates after starting the app. + Automatically search for new updates on start Redo setup process Update to prereleases Search for prerelease updates instead of full releases only @@ -286,9 +281,6 @@ Delete @string/sort_cancel Pause - Start - Failed - Passed Resume -30 +30 @@ -324,8 +316,8 @@ Movie Series Cartoon - Anime - OVA + @string/anime + @string/ova Torrent Documentary Asian Drama @@ -365,25 +357,17 @@ Don\'t show again Skip this Update Update - Preferred watch quality (WiFi) - Preferred watch quality (Mobile Data) + Preferred watch quality Video player title max chars Video player resolution Video buffer size Video buffer length Video cache on disk Clear video and image cache - Player Shown - Seek Amount - The seek amount used when the player is visible - Player Hidden - Seek Amount - The seek amount used when the player is hidden Causes crashes if set too high on devices with low memory, such as Android TV. Causes problems if set too high on devices with low storage space, such as Android TV. DNS over HTTPS Useful for bypassing ISP blocks - raw.githubusercontent.com Proxy - Failed to reach GitHub, enabling jsdelivr proxy. - Using jsdelivr, GitHub blocking can be bypassed. May delay updates by a few days. Clone site Remove site Add a clone of an existing site, with a different URL @@ -411,14 +395,12 @@ responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. - ISP Bypasses Links App updates Backup Extensions Actions Cache - Android TV Gestures Player features Subtitles @@ -428,14 +410,13 @@ Features General Random Button - Shows button on Homepage which can choose a random movie or TV series from the Homepage + Show random button on Homepage Provider languages App Layout Preferred media Enable NSFW on supported providers Subtitle encoding Providers - Provider test Layout Auto TV layout @@ -592,8 +573,6 @@ Audio tracks Video tracks Apply on Restart - Restart - Stop Safe mode on All extensions were turned off due to a crash to help you find the one causing trouble. View crash info @@ -635,6 +614,7 @@ Downloading app update… Installing app update… Could not install the new version of the app + Legacy PackageInstaller App will be updated upon exit @@ -650,11 +630,4 @@ Open with Looks like your library is empty :(\nLogin to a library account or add shows to your local library Looks like this list is empty, try switching to another one - Safe mode file found!\nNot loading any extensions on startup until file is removed. - Revert - Updating subscribed shows - Subscribed - Subscribed to %s - Unsubscribed from %s - Episode %d released! - \ No newline at end of file + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b9648162..2540bf34 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -40,6 +40,7 @@ ?attr/textColor ?attr/grayTextColor + ?attr/grayTextColor ?attr/grayTextColor ?attr/textColor @@ -99,7 +100,6 @@