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..1c79c093 100644 --- a/.github/locales.py +++ b/.github/locales.py @@ -1,7 +1,6 @@ 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" @@ -46,18 +45,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/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..3c855d28 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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,8 +184,8 @@ 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") 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..4014e34d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -17,10 +17,8 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import okhttp3.Interceptor -import org.mozilla.javascript.Scriptable import java.text.SimpleDateFormat import java.util.* import kotlin.math.absoluteValue @@ -84,7 +82,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 } } } @@ -736,19 +734,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 +1312,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 +1336,6 @@ interface EpisodeResponse { var showStatus: ShowStatus? var nextAiring: NextAiring? var seasonNames: List? - fun getLatestEpisodes(): Map } @JvmName("addSeasonNamesString") @@ -1420,18 +1404,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 +1602,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, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index d054f504..eddec15e 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,9 +63,6 @@ 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 @@ -86,7 +79,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 +86,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 +166,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 +265,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 +315,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() @@ -436,7 +402,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,29 +716,6 @@ 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 { @@ -1147,15 +1089,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..bc910a7e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -1,25 +1,18 @@ package com.lagradost.cloudstream3.extractors +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 +import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import java.net.URI - -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" -} - open class Filesim : ExtractorApi() { override val name = "Filesim" override val mainUrl = "https://files.im" @@ -31,27 +24,34 @@ open class Filesim : ExtractorApi() { 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) + with(app.get(url).document) { + this.select("script").forEach { script -> + if (script.data().contains("eval(function(p,a,c,k,e,d)")) { + val data = getAndUnpack(script.data()) + val foundData = Regex("""sources:\[(.*?)]""").find(data)?.groupValues?.get(1) ?: return@forEach + val fixedData = foundData.replace("file:", """"file":""") + + parseJson>("[$fixedData]").forEach { + callback.invoke( + ExtractorLink( + name, + name, + it.file, + "$mainUrl/", + Qualities.Unknown.value, + URI(it.file).path.endsWith(".m3u8") + ) + ) + } } } } } - /* 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/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/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..3533d6a8 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 @@ -166,11 +165,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 +199,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 +209,7 @@ object PluginManager { })?.let { savedData -> // OnlinePluginData(savedData, onlineData) loadPlugin( - context, + activity, File(savedData.filePath), savedData ) @@ -372,11 +371,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 +398,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,7 +416,7 @@ 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 @@ -442,14 +441,14 @@ object PluginManager { /** * @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 +492,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/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..8d28fd9d 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() { @@ -1121,20 +1119,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 -> @@ -1257,8 +1246,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..67f58195 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 @@ -13,6 +13,7 @@ import android.view.View import android.view.ViewGroup import android.widget.* 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 @@ -733,17 +734,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 +781,7 @@ class GeneratorPlayer : FullScreenPlayer() { videosList.setItemChecked(which, true) } - trackDialog.setOnDismissListener { + tracksDialog.setOnDismissListener { dismiss() // selectTracksDialog = null } @@ -808,11 +811,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 +828,7 @@ class GeneratorPlayer : FullScreenPlayer() { player.setMaxVideoSize(width, height, currentVideo?.id) } - trackDialog.dismissSafe(activity) + tracksDialog.dismissSafe(activity) } } } catch (e: Exception) { @@ -1142,7 +1145,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..68dd1c0e 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) 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..649aa634 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 @@ -57,49 +57,47 @@ fun getCurrentLocale(context: Context): String { val appLanguages = arrayListOf( /* begin language list */ Triple("", "العربية", "ar"), - Triple("", "български", "bg"), + Triple("", "български език", "bg"), Triple("", "বাংলা", "bn"), - Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"), + Triple("\uD83C\uDDE7\uD83C\uDDF7", "Brazilian Portuguese", "bp"), Triple("", "čeština", "cs"), Triple("", "Deutsch", "de"), - Triple("", "Ελληνικά", "el"), + Triple("", "ελληνικά", "el"), Triple("", "English", "en"), Triple("", "Esperanto", "eo"), - Triple("", "español", "es"), + Triple("", "Español", "es"), Triple("", "فارسی", "fa"), Triple("", "français", "fr"), Triple("", "हिन्दी", "hi"), - Triple("", "hrvatski", "hr"), + Triple("", "hrvatski jezik", "hr"), Triple("", "magyar", "hu"), - Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"), - Triple("", "italiano", "it"), - Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"), - Triple("", "日本語 (にほんご)", "ja"), + Triple("\uD83C\uDDEE\uD83C\uDDE9", "Indonesian", "in"), + Triple("", "Italiano", "it"), + Triple("\uD83C\uDDEE\uD83C\uDDF1", "עִברִית", "iw"), Triple("", "ಕನ್ನಡ", "kn"), - Triple("", "македонски", "mk"), + 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("", "Norsk nynorsk", "nn"), + Triple("", "Norsk", "no"), + Triple("", "język polski", "pl"), + Triple("\uD83C\uDDF5\uD83C\uDDF9", "Português", "pt"), + Triple("🦍", "mmmm... monke", "qt"), + Triple("", "Română", "ro"), + Triple("", "Русский", "ru"), Triple("", "slovenčina", "sk"), Triple("", "Soomaaliga", "so"), Triple("", "svenska", "sv"), Triple("", "தமிழ்", "ta"), - Triple("", "Tagalog", "tl"), + Triple("", "Wikang Tagalog", "tl"), Triple("", "Türkçe", "tr"), - Triple("", "українська", "uk"), + Triple("", "Українська", "uk"), Triple("", "اردو", "ur"), Triple("", "Tiếng Việt", "vi"), - Triple("", "中文", "zh"), - Triple("\uD83C\uDDF9\uD83C\uDDFC", "文言", "zh-rTW"), + Triple("", "中文 (Zhōngwén)", "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/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..1ad3639b 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(), @@ -344,8 +323,6 @@ val extractorApis: MutableList = arrayListOf( Filesim(), FileMoon(), - FileMoonSx(), - Vido(), Linkbox(), Acefile(), SpeedoStream(), @@ -387,7 +364,6 @@ val extractorApis: MutableList = arrayListOf( Cda(), Dailymotion(), ByteShare(), - Ztreamhub() ) 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/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_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 إختيار ملف تحميل من الانترنت @@ -538,27 +538,4 @@ المكتبة تم العثور على ملف الوضع الآمن! \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..f1f512a1 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 @@ -106,7 +105,7 @@ Продължете да гледате Премахване Повече информация - @string/home_play + \@string/home_play Може да е необходим VPN, за да работи правилно този доставчик Този доставчик е торент, препоръчва се VPN Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта. @@ -224,8 +223,8 @@ Филм Серия Анимационен филм - @string/anime - @string/ova + \@string/anime + \@string/ova Торент Документален филм Азиатска драма @@ -498,4 +497,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..214cb86d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Ep %d @@ -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í @@ -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,7 +245,7 @@ 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 @@ -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 @@ -531,27 +530,4 @@ Odstranit repozitář Nestaženo: %d Ne - Skrytý přehrávač - doba hledání - Množství vyhledávané doby při skrytém přehrávači - Zobrazený přehrávač - doba hledání - Android TV - Množství vyhledávané doby při zobrazeném přehrávači - Protokol - Test poskytovatele - Neúspěšné - Úspěšné - Restart - Spustit - Zastavit - Aktualizace odebíraných pořadů - Přihlášeno k odběru %s - Odhlášen odběr od %s - Byla vydána epizoda %d! - Odebíráno - Proxy raw.githubusercontent.com - Nepodařilo se připojit ke GitHubu, povolování proxy jsdelivr. - Upřednostněná kvalita sledování (mobilní data) - Vrátit zpět - Pomocí jsdelivr lze obejít blokování GitHubu. Může dojít ke zpoždění aktualizací o několik dní. - Obcházení ISP - + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8fbcc2d0..e190aa9c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,4 +1,4 @@ - + %s Ep %d Besetzung: %s @@ -41,7 +41,7 @@ Suche %s… Keine Daten vorhanden Mehr Optionen - Nächste Episode + Nächste Epsisode Genres Teilen In Browser öffnen @@ -53,7 +53,7 @@ Abgebrochen Geplant Nichts - Erneut schauen + Erneut anschauen Film abspielen Livestream abspielen Torrent streamen @@ -115,7 +115,7 @@ Weiterschauen Entfernen Mehr Infos - @string/home_play + \@string/home_play Damit dieser Anbieter korrekt funktioniert, ist möglicherweise ein VPN erforderlich Dieser Anbieter bietet Torrents an, ein VPN wird dringend empfohlen Metadaten werden nicht von der Website bereitgestellt, das Laden des Videos schlägt fehl, wenn sie auf der Website nicht vorhanden sind. @@ -136,14 +136,14 @@ Wischen zum vor- und zurückspulen Nach links oder rechts wischen, um die Zeit im Videoplayer zu steuern Wischen, um Einstellungen zu ändern - Links oder rechts nach oben oder unten wischen, um die Helligkeit oder Lautstärke zu ändern + Links oder rechts wischen, um die Helligkeit oder Lautstärke zu ändern Nächste Episode automatisch abspielen Nächste Episode wird gestartet, sobald die aktuelle Episode endet Doppeltippen zum vor- und zurückspulen Doppeltippen zum Pausieren - Zeit für vor- und zurückspulen im Player (Sekunden) + Zeit für vor- und zurückspulen im Player Zweimal auf die rechte oder linke Seite tippen, um vor- oder zurückzuspulen - Doppelt in die Mitte tippen, um zu pausieren + In die Mitte tippen, um zu pausieren Systemhelligkeit verwenden Systemhelligkeit anstelle eines dunklen Overlay im Player verwenden Episodenfortschritt aktualisieren @@ -166,7 +166,7 @@ Ausgewählte Videoqualität bei Suchergebnissen ausblenden Automatische Plugin-Updates App-Updates anzeigen - Automatisches Suchen nach neuen Updates nach dem Start + Automatisches Suchen nach neuen Updates beim Start Auf Vorabversionen updaten Suche nach Vorabversionen statt nur nach Vollversionen Github @@ -212,7 +212,7 @@ Keine Untertitel Standard Frei - Belegt + Benutzt App Filme TV-Serien @@ -246,7 +246,7 @@ In Browser wiedergeben Link kopieren Auto-Download - Alternativer Download + Download-Mirror Links neu laden Untertitel herunterladen Qualitätsanzeige @@ -263,7 +263,7 @@ Nicht mehr anzeigen Update ignorieren Update - Bevorzugte Videoqualität (WLAN) + Bevorzugte Auflösung Videoplayertitel max. Zeichen Videoplayer Auflösung Videopuffergröße @@ -284,9 +284,9 @@ Strecken Vergrößern Haftungsausschluss - Allgemein + General Zufalls-Button - Zeigt einen Zufallsbutton auf der Startseite an, mit welchem eine Serie oder ein Film von der Website zufällig ausgewählt wird + Zufallsbutton auf der Startseite anzeigen Anbieter-Sprachen App-Layout Bevorzugte Medien @@ -460,11 +460,11 @@ Automatische Installation aller noch nicht installierten Plugins aus hinzugefügten Repositories. Einrichtungsvorgang wiederholen APK-Installer - Einige Telefone unterstützen den neuen Package-Installer nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen. + Einige Telefone unterstützen das neue Installationsprogramm für Pakete nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen. %s %d%s Links App-Updates - Sicherung + Back-Up Erweiterungen Wartung Cache @@ -506,27 +506,4 @@ Diese Liste scheint leer zu sein. Versuche, zu einer anderen Liste zu wechseln. Datei für abgesicherten Modus gefunden! \nBeim Start werden keine Erweiterungen geladen, bis die Datei entfernt wird. - Player ausgeblendet - Betrag zum vor- und zurückspulen - Der Betrag, welcher verwendet wird, wenn der Player eingeblendet ist - Der Betrag, welcher verwendet wird, wenn der Player ausgeblendet ist - Android-TV - Player eingeblendet - Betrag zum vor- und zurückspulen - Fehlgeschlagen - Erfolgreich - Anbieter-Test - Stopp - Log - Start - Neustarten - Bevorzugte Videoqualität (mobile Daten) - Umgehung der GitHub Sperre mit jsdelivr. Kann zu einigen Tagen Verzögerung bei Updates führen. - %s abonniert - %s deabonniert - Episode %d erschienen! - raw.githubusercontent.com Proxy - GitHub kann nicht erreicht werden, der jsdelivr-Proxy wird aktiviert. - Abonnierte Serien werden aktualisiert - Rückgängig - Abonniert - ISP-Umgehungen - + \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f07ce43c..0d0b7fb2 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,6 +1,5 @@ - - - + + CloudStream Αρχική Αναζήτηση @@ -151,7 +150,7 @@ Επεισόδια %d-%d %d %s - Σ + Κ E Δεν βρέθηκαν επεισόδια Διαγραφή αρχείου @@ -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 Σύνδεσμοι Εμφάνιση Χαρακτηριστικά @@ -509,4 +508,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..d253e256 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 @@ -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 @@ -506,27 +506,8 @@ \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 + Jugadora mostrada - buscar cantidad + Jugadora oculta - 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) - + La cantidad de búsqueda utilizada cuando la jugadora es visible. + \ 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..0f3e36bc 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 @@ -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 @@ -532,27 +531,4 @@ Č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..d54e4fa9 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -1,6 +1,5 @@ - - - + + %s Ep %d Pemeran: %s @@ -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 @@ -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 @@ -530,27 +529,4 @@ 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..9dbc627f 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 @@ -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 @@ -529,27 +528,4 @@ 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..11cf77ce 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -1,4 +1,4 @@ - + הרקע של ההצגה לפני צוות שחקנים: %s @@ -116,7 +116,7 @@ כתוביות כרומקאסט ממשיך ניגון בנגן מינימלי מעל ישומים אחרים כתוביות - @string/home_play + \@string/home_play היסטוריה מורשת לא @@ -164,8 +164,8 @@ משומש סדרת טלוויזיה סדרות/סרטים מצוירים - @string/anime - @string/ova + \@string/אנימה + \@string/אנימציית וידאו מקורית דרמה אסייתית כרומקאסט את הפרק כרומקאסט את המראה @@ -506,4 +506,4 @@ אלפביתי (ת\' עד א\') פתח עם נראה שהרשימה הזו ריקה, נסו לעבור לרשימה אחרת - + \ 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-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..c709f124 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 @@ -456,7 +455,7 @@ 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 @@ -510,27 +509,4 @@ 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-qt/strings.xml b/app/src/main/res/values-qt/strings.xml index eee28785..24c51a05 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.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 @@ -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-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..29e98598 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,4 +1,4 @@ - + История Нет @@ -25,8 +25,8 @@ Серия %d будет выпущен в Плакат Плакат - Плакат эпизода - Главный плакат + Постер Эпизода + Главный постер Следующий случайный Вернуться Изменить поставщика @@ -61,11 +61,11 @@ Нет Пересмотрю Смотреть фильм - Смотреть трейлер - Смотреть Livestream + Воспроизвести трейлер + Воспроизвести Livestream Источники Субтитры - Смотреть эпизод + Воспроизвести эпизод Повторная попытка подключение… Вернуться Скачано @@ -90,7 +90,7 @@ Скорость проигрыватель Воспроизвести Эпизод %dд %dч %dм - %d мин. + %d мин Dub Sub Установите смотреть состояние @@ -114,7 +114,7 @@ Шрифт Размер шрифта Удалить файл - Воспроизвести файл + Проиграть файл Внутренняя память Продолжить Скачать Остановить скачивание @@ -142,10 +142,10 @@ Добавляет опцию скорости в проигрывателе Проведите пальцем для поиска Проведите пальцем для изменения настроек - Проведите вверх или вниз по левой или правой стороне, чтобы изменить яркость или громкость + Проведите пальцем по левой или правой стороне для изменения яркости или громкости Автопроиграть следующего серия Поток торрент - Проведите пальцем из стороны в сторону, чтобы управлять свое место в видеоролике + Проведите пальцем влево или вправо, чтобы управлять временем в видеоплеере Начните следующий серию, когда закончится текущий Загружена резервная копия Не удалось восстановить данные из %s @@ -159,7 +159,7 @@ Автоматическое обновление плагинов Автоматическая загрузка плагинов Показать обновления приложения - Автоматически проверять обновления при старте приложения. + Автоматически проверять обновления при старте Обновится до пре-релиза APK установщик Github @@ -213,7 +213,7 @@ NSFW NSFW Фильм - Сериал + Серия Торрент Документальный Азиатская драма @@ -227,7 +227,7 @@ Использовано Двойное нажатие для паузы Коснитесь дважды правой или левой стороны для поиска вперед или назад - Нажмите дважды в центре, чтобы сделать паузу + Нажмите в центре для паузы Использовать системную яркость Автоматически синхронизировать текущий прогресс эпизода Ошибка резервного копирования %s @@ -318,7 +318,7 @@ Титры Отметить как просмотренное Разрешение видеоплеера - Предпочтительное качество видео (WiFi) + Желаемое качество видео Максимум символов Длинна буфера Кеш видео на диске @@ -363,7 +363,7 @@ Расширения URL репозитория Плагин загружен - @string/home_play + \@string/home_play Перемотка двойным нажатием /\?\? /%d @@ -408,8 +408,8 @@ Съешь ещё этих мягких французских булок, да выпей же чаю Рекомендуется Загружено %s - Аниме - OVA + \@нить/аниме + \@нить/ova Этикетка Dub Сайт Функции @@ -472,7 +472,7 @@ Скачайте список сайтов, который вы хотите использовать Отображать Аниме с Дубляжом/Субтитрами Включить NSFW на поддерживаемых провайдерах - Удалять скрытые субтитры из субтитров + Убрать скрытые субтитры из субтитров Дополнительно Изменить вид интерфейса, чтобы соответствовать устройству Аудио дорожки @@ -493,7 +493,7 @@ Фильтровать по предпочитаемому языку медиа Неверный ID Ссылка на стрим - Показывает кнопку на главной странице, с помощью которой можно выбрать случайный фильм или сериал с главной страницы + Отображать рандомную кнопку на Главной странице Рандомная кнопка Legacy (старый) Веб видеокаст @@ -501,32 +501,5 @@ Перезагрузить ссылки Предпочтительные медиа Опущенные - Объем перемотки плеера (секундах) - Объем перемотка, используемый, когда плеер виден - Плеер показан - Перемотки объем - Плеер спрятан - Перемотки объем - Удалять лишнее из субтитров - Местоположение ползунка, когда игрок скрыт - 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..807716d8 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 @@ -24,9 +23,9 @@ 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,22 +531,22 @@ İ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 +\nBir kütüphane hesabına giriş yapın veya yerel kütüphanenize gösteri 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) + Güncel (Yeniden Eskiye) + Güncel (Eskiden Yeniye) Alfabetik (A\'dan Z’ye) Alfabetik (Z - A) Kütüphane Seçin @@ -555,27 +554,4 @@ 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..82baa6b4 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,13 +1,13 @@ - + Постер - Постер до епізоду + Постер епізоду Завантаження скасовано Змінити постачальника Назад Рейтинг: %.1f Актори: %s - Епізод %d вийде через + Епізод %d буде випущено через Poster %s Еп. %d %dд %dгод %dхв @@ -15,16 +15,16 @@ %dхв Головний постер Наступний випадковий - Попередній перегляд фону + Перегляд фону Швидкість (%.2fx) - Знайдено нове оновлення! + Нове оновлення знайдено! \n%s -> %s Пошук Завантаження %d хв - Налаштування + Параметри Пошук… - Пошук на %s… + Пошук %s… Дані відсутні Більше опцій Наступний епізод @@ -37,7 +37,7 @@ Покинуто Переглянути фільм Переглянути трейлер - Трансляція через торрент + Переглянути торрент Повторити підключення… Назад Переглянути епізод @@ -87,13 +87,13 @@ Налаштування субтитрів Chromecast Режим Eigengravy Проведіть пальцем, щоб змінити налаштування - Проведіть пальцем вгору або вниз ліворуч або праворуч, щоб змінити яскравість або гучність + Проведіть пальцем ліворуч або праворуч, щоб змінити яскравість або гучність Відтворення наступного епізоду після закінчення поточного Головна CloudStream Філер Програти в CloudStream - Трансляція + Потік Переглядаю Поділитися Відкладено @@ -121,10 +121,10 @@ Колір тексту Колір контуру Автовідтворення наступного епізоду - Проведіть пальцем з боку в бік, щоб керувати своїм положенням у відео + Проведіть пальцем ліворуч або праворуч, щоб керувати часом у відеоплеєрі %d Бананів для розробників Кнопка зміни розміру плеєра - @string/home_play + \@string/home_play Для коректної роботи цього постачальника може знадобитися VPN Метадані не надаються сайтом, завантаження відео не відбудеться, якщо їх немає на сайті. Картинка в картинці @@ -133,8 +133,8 @@ Проведіть пальцем, щоб перемотати Двічі торкніться, щоб перемотати Двічі торкніться для паузи - Крок перемотки (Секунди) - Натисніть двічі посередині, щоб призупинити + Крок перемотки + Натисніть посередині, щоб поставити на паузу Використовувати яскравість системи Оновити прогрес перегляду Відновлення даних з резервної копії @@ -217,7 +217,7 @@ Пропустити OP Не показувати знову Оновити - Бажана якість перегляду (WiFi) + Бажана якість перегляду Заголовок Перемикання елементів інтерфейсу на плакаті Оновлення не знайдено @@ -230,7 +230,7 @@ Показати постери від Kitsu Автоматичне оновлення плагінів Автоматично встановлювати всі ще не встановлені плагіни з доданих репозиторіїв. - Автоматично шукати нові оновлення після запуску застосунку. + Автоматичний пошук нових оновлень при запуску Оновлення до бета-версій Посилання скопійовано в буфер обміну Деякі телефони не підтримують новий інсталятор пакетів. Спробуйте стару версію, якщо оновлення не встановлюються. @@ -255,7 +255,7 @@ Документальні фільми NSFW Фільм - OVA + \@string/ova Торрент Мітка якості NSFW @@ -283,7 +283,7 @@ Особливості Загальне Випадкова кнопка - Показує кнопку на Головній сторінці, яка може вибрати випадковий фільм або серіал на Головній сторінці + Показати випадкову кнопку на Головній сторінці Мови постачальника Макет програми Бажані медіа @@ -506,27 +506,4 @@ Схоже, цей список порожній, спробуйте перейти до іншого Файл безпечного режиму знайдено! \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..db647b5d 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 @@ -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 @@ -230,7 +229,7 @@ Phim Bộ Hoạt Hình Anime - @string/ova + \@string/ova Torrent Phim Tài Liệu Truyền Hình Châu Á @@ -261,18 +260,18 @@ Kiểm tra cập nhật Khóa Thu Phóng - Nguồn - Bỏ qua OP + Tuỳ chọn + Tua nhanh 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 + Dung lượng video cache + Xoá 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. DNS over HTTPS @@ -520,9 +519,4 @@ 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..c57e3ca1 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 @@ -24,9 +23,9 @@ 剧集封面 主封面 随机下一个 - @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 轮廓 凹陷 阴影 @@ -555,27 +554,4 @@ 看来您的库是空的 :( \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..778f34c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,10 +13,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 @@ -44,7 +42,6 @@ random_button_key provider_lang_key dns_key - jsdelivr_proxy_key download_path_key Cloudstream app_layout_key @@ -198,7 +195,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 +206,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 +247,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 +282,6 @@ Delete @string/sort_cancel Pause - Start - Failed - Passed Resume -30 +30 @@ -324,8 +317,8 @@ Movie Series Cartoon - Anime - OVA + @string/anime + @string/ova Torrent Documentary Asian Drama @@ -365,8 +358,7 @@ 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 @@ -381,9 +373,6 @@ 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,7 +400,6 @@ 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 @@ -428,14 +416,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 +579,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 @@ -651,10 +636,4 @@ 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..78c62c69 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 diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml index 2d2905ea..0e5bd84f 100644 --- a/app/src/main/res/xml/settings_player.xml +++ b/app/src/main/res/xml/settings_player.xml @@ -15,10 +15,6 @@ android:icon="@drawable/ic_baseline_hd_24" android:key="@string/quality_pref_key" android:title="@string/watch_quality_pref" /> - + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:icon="@drawable/ic_baseline_language_24" + android:key="@string/provider_lang_key" + android:title="@string/provider_lang_settings" /> + android:key="@string/prefer_media_type_key" + android:title="@string/preferred_media_settings" + android:icon="@drawable/ic_baseline_play_arrow_24" /> + android:key="@string/display_sub_key" + android:title="@string/display_subbed_dubbed_settings" + android:icon="@drawable/ic_outline_voice_over_off_24" /> - - - + android:key="@string/enable_nsfw_on_providers_key" + android:title="@string/enable_nsfw_on_providers" + android:icon="@drawable/ic_baseline_extension_24" + android:summary="@string/apply_on_restart" + app:defaultValue="false"/> \ No newline at end of file diff --git a/app/src/main/res/xml/settins_general.xml b/app/src/main/res/xml/settins_general.xml index c4900bca..726f3fd0 100644 --- a/app/src/main/res/xml/settins_general.xml +++ b/app/src/main/res/xml/settins_general.xml @@ -6,6 +6,18 @@ android:title="@string/app_language" android:icon="@drawable/ic_baseline_language_24" /> + + + + - - - - - - - - - -