forked from recloudstream/cloudstream
Compare commits
7 commits
master
...
Merge-with
Author | SHA1 | Date | |
---|---|---|---|
|
eac7c2b2e3 | ||
|
eb47f372b2 | ||
|
6a767df0eb | ||
|
58763708f7 | ||
|
b08608d31a | ||
830fd7ee55 | |||
c7c1f12dad |
120 changed files with 1147 additions and 3757 deletions
BIN
.github/downloads.jpg
vendored
Normal file
BIN
.github/downloads.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
.github/home.jpg
vendored
Normal file
BIN
.github/home.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
15
.github/locales.py
vendored
15
.github/locales.py
vendored
|
@ -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"
|
||||
|
@ -47,17 +46,3 @@ open(SETTINGS_PATH, "w+",encoding='utf-8').write(
|
|||
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'<?xml version="1.0" encoding="utf-8"?>\n')
|
||||
tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False)
|
||||
except ET.ParseError as ex:
|
||||
print(f"[{file}] {ex}")
|
||||
|
|
BIN
.github/player.jpg
vendored
Normal file
BIN
.github/player.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
.github/results.jpg
vendored
Normal file
BIN
.github/results.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
BIN
.github/search.jpg
vendored
Normal file
BIN
.github/search.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 149 KiB |
9
.github/workflows/update_locales.yml
vendored
9
.github/workflows/update_locales.yml
vendored
|
@ -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
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
+ Download and stream movies, tv-shows and anime
|
||||
+ Chromecast
|
||||
|
||||
### Screenshots:
|
||||
|
||||
<img src="./.github/home.jpg" height="400"/><img src="./.github/search.jpg" height="400"/><img src="./.github/downloads.jpg" height="400"/><img src="./.github/results.jpg" height="400"/>
|
||||
<img src="./.github/player.jpg" height="200"/>
|
||||
|
||||
### Supported languages:
|
||||
<a href="https://hosted.weblate.org/engage/cloudstream/">
|
||||
<img src="https://hosted.weblate.org/widgets/cloudstream/-/app/multi-auto.svg" alt="Translation status" />
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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<MainAPI> {
|
||||
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<SearchResponse>? = 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<Pair<MainAPI, Exception?>>()
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,16 +98,6 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- cloudstreamplayer://encodedUrl?name=Dune -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="cloudstreamplayer" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
|
@ -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<SeasonData>?
|
||||
fun getLatestEpisodes(): Map<DubStatus, Int?>
|
||||
}
|
||||
|
||||
@JvmName("addSeasonNamesString")
|
||||
|
@ -1420,18 +1404,7 @@ data class AnimeLoadResponse(
|
|||
override var nextAiring: NextAiring? = null,
|
||||
override var seasonNames: List<SeasonData>? = null,
|
||||
override var backgroundPosterUrl: String? = null,
|
||||
) : LoadResponse, EpisodeResponse {
|
||||
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
||||
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<SeasonData>? = null,
|
||||
override var backgroundPosterUrl: String? = null,
|
||||
) : LoadResponse, EpisodeResponse {
|
||||
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
||||
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,
|
||||
|
|
|
@ -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<Boolean>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MetaData>() ?: 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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -16,7 +16,26 @@ open class Evoload : ExtractorApi() {
|
|||
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
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<List<ResponseSource>>("[$fixedData]").forEach {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
m3u8,
|
||||
mainUrl
|
||||
).forEach(callback)
|
||||
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?
|
||||
) */
|
||||
)
|
||||
|
||||
}
|
|
@ -18,14 +18,13 @@ 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<Responses>()?.data?.itemInfo?.resolutionList?.map { link ->
|
||||
val id = Regex("""(/file/|id=)(\S+)[&/?]""").find(url)?.groupValues?.get(2)
|
||||
app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe<Responses>()?.data?.rList?.map { link ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
link.url ?: return@map null,
|
||||
link.url,
|
||||
url,
|
||||
getQualityFromName(link.resolution)
|
||||
)
|
||||
|
@ -33,21 +32,17 @@ open class Linkbox : ExtractorApi() {
|
|||
}
|
||||
}
|
||||
|
||||
data class Resolutions(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("resolution") val resolution: String? = null,
|
||||
)
|
||||
|
||||
data class ItemInfo(
|
||||
@JsonProperty("resolutionList") val resolutionList: ArrayList<Resolutions>? = 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<RList>?,
|
||||
)
|
||||
|
||||
data class Responses(
|
||||
@JsonProperty("data") val data: Data? = null,
|
||||
@JsonProperty("data") val data: Data?,
|
||||
)
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
)
|
||||
|
|
|
@ -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<ExtractorLink>? {
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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<ExtractorLink>? {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -121,21 +121,13 @@ suspend fun <T> 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(
|
||||
fun <T> safeFail(throwable: Throwable): Resource<T> {
|
||||
val stackTraceMsg =
|
||||
(throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
|
||||
separator = "\n"
|
||||
) {
|
||||
"${it.fileName} ${it.lineNumber}"
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> safeFail(throwable: Throwable): Resource<T> {
|
||||
val stackTraceMsg = throwable.getStackTracePretty()
|
||||
return Resource.Failure(false, null, null, stackTraceMsg)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<OnlinePluginData> = 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
|
||||
|
|
|
@ -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<RepositoryData> 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<Boolean>(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<SitePlugin> {
|
||||
// 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<Array<SitePlugin>>(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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
else -> return
|
||||
}
|
||||
|
||||
downloadScope.launch {
|
||||
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))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,28 +766,6 @@ 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<AniListUser>(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<MediaListItemRoot>(response)?.data?.MediaList?.id ?: return false
|
||||
"""
|
||||
mutation(${'$'}id: Int = $listId) {
|
||||
DeleteMediaListEntry(id: ${'$'}id) {
|
||||
deleted
|
||||
}
|
||||
}
|
||||
"""
|
||||
} else {
|
||||
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
|
||||
aniListStatusString[maxOf(
|
||||
0,
|
||||
|
@ -806,8 +779,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
score
|
||||
}
|
||||
}"""
|
||||
}
|
||||
|
||||
val data = postApi(q)
|
||||
return data != ""
|
||||
}
|
||||
|
|
|
@ -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<SyncAPI.LibraryItem>()
|
||||
} + mapOf(R.string.subscription_list_name to emptyList())
|
||||
|
||||
}
|
||||
return SyncAPI.LibraryMetadata(
|
||||
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
|
||||
setOf(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,11 +666,7 @@ 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(
|
||||
DefaultRenderersFactory(context).createRenderers(
|
||||
eventHandler,
|
||||
videoRendererEventListener,
|
||||
audioRendererEventListener,
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ class DownloadedPlayerActivity : AppCompatActivity() {
|
|||
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
|
||||
LinkGenerator(
|
||||
listOf(
|
||||
BasicLink(url)
|
||||
url
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<BasicLink>,
|
||||
private val links: List<String>,
|
||||
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
|
||||
)
|
||||
|
|
|
@ -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<View> = 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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -322,7 +322,9 @@ class ResultFragmentPhone : ResultFragment() {
|
|||
// it?.dismiss()
|
||||
//}
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
|
||||
builder.show()
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String?> = MutableLiveData(null)
|
||||
val episodeSynopsis: LiveData<String?> = _episodeSynopsis
|
||||
|
||||
private val _subscribeStatus: MutableLiveData<Boolean?> = MutableLiveData(null)
|
||||
val subscribeStatus: LiveData<Boolean?> = _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
|
||||
}
|
||||
|
||||
putExtra("from_start", !resume)
|
||||
putExtra("position", position)
|
||||
|
@ -1469,12 +1422,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
meta: SyncAPI.SyncResult?,
|
||||
syncs: Map<String, String>? = null
|
||||
): Pair<LoadResponse, Boolean> {
|
||||
//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
|
||||
|
@ -1486,7 +1438,12 @@ class ResultViewModel2 : ViewModel() {
|
|||
nextAiring = nextAiring ?: meta.nextAiring
|
||||
}
|
||||
|
||||
for ((k, v) in syncs ?: emptyMap()) {
|
||||
syncData[k] = v
|
||||
}
|
||||
|
||||
val realRecommendations = ArrayList<SearchResponse>()
|
||||
// TODO: fix
|
||||
val apiNames = apis.filter {
|
||||
it.name.contains("gogoanime", true) ||
|
||||
it.name.contains("9anime", true)
|
||||
|
@ -1502,56 +1459,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
|
||||
recommendations = recommendations?.union(realRecommendations)?.toList()
|
||||
?: realRecommendations
|
||||
}
|
||||
|
||||
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 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
|
||||
)
|
||||
|
||||
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
|
||||
argamap({
|
||||
addTrailer(meta.trailers)
|
||||
}, {
|
||||
if (this !is AnimeLoadResponse) return@argamap
|
||||
|
@ -1577,6 +1486,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
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 =
|
||||
|
@ -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 -> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<TvType>().sorted().map { it.name }
|
||||
val default =
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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<Pair<MainAPI, TestingUtils.TestResultProvider>>) :
|
||||
AppUtils.DiffAdapter<Pair<MainAPI, TestingUtils.TestResultProvider>>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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<TestProgress>(null)
|
||||
val providerProgress: LiveData<TestProgress> = _providerProgress
|
||||
|
||||
private val _providerResults =
|
||||
MutableLiveData<List<Pair<MainAPI, TestingUtils.TestResultProvider>>>(
|
||||
emptyList()
|
||||
)
|
||||
|
||||
val providerResults: LiveData<List<Pair<MainAPI, TestingUtils.TestResultProvider>>> =
|
||||
_providerResults
|
||||
|
||||
private var scope: CoroutineScope? = null
|
||||
val isRunningTest
|
||||
get() = scope != null
|
||||
|
||||
private var filter = ProviderFilter.All
|
||||
private val providers = threadSafeListOf<Pair<MainAPI, TestingUtils.TestResultProvider>>()
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<Long> {
|
||||
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<String, String> {
|
||||
val queryPairs: MutableMap<String, String> = LinkedHashMap()
|
||||
val query: String = url.query
|
||||
|
@ -776,14 +735,9 @@ 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 ->
|
||||
this?.cacheDir?.let {
|
||||
|
|
|
@ -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<DubStatus, Int?>,
|
||||
@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<String, String>? = 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<SubscribedData> {
|
||||
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
|
||||
|
|
|
@ -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<String, String> = 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<String, String> = 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<ExtractorApi> = arrayListOf(
|
|||
StreamSB8(),
|
||||
StreamSB9(),
|
||||
StreamSB10(),
|
||||
StreamSB11(),
|
||||
SBfull(),
|
||||
// Streamhub(), cause Streamhub2() works
|
||||
Streamhub2(),
|
||||
|
@ -272,7 +254,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
// WatchSB(), 'cause StreamSB.kt works
|
||||
Uqload(),
|
||||
Uqload1(),
|
||||
Uqload2(),
|
||||
Evoload(),
|
||||
Evoload1(),
|
||||
VoeExtractor(),
|
||||
|
@ -284,7 +265,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
OkRu(),
|
||||
OkRuHttps(),
|
||||
Okrulink(),
|
||||
Sendvid(),
|
||||
|
||||
// dood extractors
|
||||
DoodCxExtractor(),
|
||||
|
@ -296,7 +276,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
DoodShExtractor(),
|
||||
DoodWatchExtractor(),
|
||||
DoodWfExtractor(),
|
||||
DoodYtExtractor(),
|
||||
|
||||
AsianLoad(),
|
||||
|
||||
|
@ -344,8 +323,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
|
||||
Filesim(),
|
||||
FileMoon(),
|
||||
FileMoonSx(),
|
||||
Vido(),
|
||||
Linkbox(),
|
||||
Acefile(),
|
||||
SpeedoStream(),
|
||||
|
@ -387,7 +364,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
Cda(),
|
||||
Dailymotion(),
|
||||
ByteShare(),
|
||||
Ztreamhub()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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<SearchResponse>) : 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<MainAPI>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Bitmap>()
|
||||
fun Context.getImageBitmapFromUrl(url: String, headers: Map<String, String>? = 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:height="24dp" android:tint="?attr/white"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,14.67L3.41,6.09L2,7.5l8.5,8.5H4v2h16v-2h-6.5l5.15,-5.15C18.91,10.95 19.2,11 19.5,11c1.38,0 2.5,-1.12 2.5,-2.5S20.88,6 19.5,6S17,7.12 17,8.5c0,0.35 0.07,0.67 0.2,0.97L12,14.67z"/>
|
||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:height="24dp" android:tint="?attr/white"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"/>
|
||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="?attr/white" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/white" android:pathData="M20.41,8.41l-4.83,-4.83C15.21,3.21 14.7,3 14.17,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V9.83C21,9.3 20.79,8.79 20.41,8.41zM7,7h7v2H7V7zM17,17H7v-2h10V17zM17,13H7v-2h10V13z"/>
|
||||
</vector>
|
|
@ -1,27 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="50"
|
||||
android:viewportHeight="50"
|
||||
android:name="vector">
|
||||
<group android:scaleX="0.1755477"
|
||||
android:scaleY="0.1755477"
|
||||
android:translateX="0"
|
||||
android:translateY="0">
|
||||
<path android:name="path"
|
||||
|
||||
android:pathData="M 245.05 148.63 C 242.249 148.627 239.463 149.052 236.79 149.89 C 235.151 141.364 230.698 133.63 224.147 127.931 C 217.597 122.233 209.321 118.893 200.65 118.45 C 195.913 105.431 186.788 94.458 174.851 87.427 C 162.914 80.396 148.893 77.735 135.21 79.905 C 121.527 82.074 109.017 88.941 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.615 148.64 23.582 151.553 18.403 156.733 C 13.223 161.912 10.31 168.945 10.31 176.27 C 10.31 183.595 13.223 190.628 18.403 195.807 C 23.582 200.987 30.615 203.9 37.94 203.9 L 245.05 203.9 C 252.375 203.9 259.408 200.987 264.587 195.807 C 269.767 190.628 272.68 183.595 272.68 176.27 C 272.68 168.945 269.767 161.912 264.587 156.733 C 259.408 151.553 252.375 148.64 245.05 148.64 Z"
|
||||
android:fillColor="#FFFFFF" android:strokeWidth="1"
|
||||
tools:ignore="VectorPath"
|
||||
android:fillAlpha="0.55"/>
|
||||
<path android:name="path_1" android:pathData="M 208.61 125 C 208.61 123.22 208.55 121.45 208.48 119.69 C 205.919 119.01 203.296 118.595 200.65 118.45 C 195.913 105.431 186.788 94.458 174.851 87.427 C 162.914 80.396 148.893 77.735 135.21 79.905 C 121.527 82.074 109.017 88.941 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.615 148.64 23.582 151.553 18.403 156.733 C 13.223 161.912 10.31 168.945 10.31 176.27 C 10.31 183.595 13.223 190.628 18.403 195.807 C 23.582 200.987 30.615 203.9 37.94 203.9 L 179 203.9 C 198.116 182.073 208.646 154.015 208.61 125 Z"
|
||||
android:fillColor="#FFFFFF" android:strokeWidth="1"
|
||||
android:fillAlpha="0.55"/>
|
||||
<path android:name="path_2" android:pathData="M 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.783 148.665 23.909 151.471 18.779 156.461 C 13.648 161.452 10.653 168.246 10.43 175.399 C 10.207 182.553 12.773 189.52 17.583 194.82 C 22.392 200.121 29.079 203.349 36.22 203.82 C 67.216 202.93 96.673 189.98 118.284 167.742 C 139.895 145.504 151.997 115.689 152 84.68 C 152 83 151.94 81.33 151.87 79.68 C 149.443 79.361 146.998 79.194 144.55 79.18 C 136.095 79.171 127.735 80.962 120.026 84.434 C 112.317 87.907 105.435 92.982 99.84 99.32 Z"
|
||||
android:fillColor="#FFFFFF" android:strokeWidth="1"
|
||||
android:fillAlpha="1"/>
|
||||
</group>
|
||||
|
||||
</vector>
|
|
@ -1,34 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Test" />
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
tools:text="Test"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/listview1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:nestedScrollingEnabled="true"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
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" />
|
||||
tools:listitem="@layout/sort_bottom_single_choice_no_checkmark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_rowWeight="1" />
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/home_header"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -85,6 +86,7 @@
|
|||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -113,6 +115,7 @@
|
|||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
||||
android:text="@string/home_play"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24" />
|
||||
|
||||
|
@ -145,16 +148,17 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/home_watch_parent_item_title"
|
||||
|
||||
style="@style/WatchHeaderText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/home_more_info"
|
||||
android:padding="12dp"
|
||||
android:text="@string/continue_watching"
|
||||
app:drawableRightCompat="@drawable/ic_baseline_arrow_forward_24"
|
||||
app:drawableTint="?attr/white" />
|
||||
app:drawableTint="?attr/white"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/home_more_info"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/home_watch_child_recyclerview"
|
||||
|
@ -163,7 +167,7 @@
|
|||
android:clipToPadding="false"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/home_result_grid" />
|
||||
|
||||
|
@ -180,9 +184,9 @@
|
|||
<FrameLayout
|
||||
android:id="@+id/home_bookmark_parent_item_title"
|
||||
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -258,13 +262,14 @@
|
|||
</HorizontalScrollView>
|
||||
|
||||
<ImageView
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_gravity="end"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:contentDescription="@string/home_more_info"
|
||||
android:src="@drawable/ic_baseline_arrow_forward_24"
|
||||
app:drawableTint="?attr/white" />
|
||||
app:drawableTint="?attr/white"
|
||||
android:contentDescription="@string/home_more_info"/>
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -272,9 +277,10 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/home_result_grid" />
|
||||
</LinearLayout>
|
||||
|
|
|
@ -129,9 +129,9 @@
|
|||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/result_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="100dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -326,12 +326,13 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/result_poster"
|
||||
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:contentDescription="@string/result_poster_img_des"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_gravity="bottom"
|
||||
tools:src="@drawable/example_poster" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
@ -515,8 +516,8 @@
|
|||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/result_tag"
|
||||
style="@style/ChipParent"
|
||||
android:id="@+id/result_tag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
<!--<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
|
@ -817,13 +818,10 @@
|
|||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
android:nextFocusLeft="@id/result_episode_select"
|
||||
android:nextFocusRight="@id/result_episode_select"
|
||||
android:nextFocusUp="@id/result_description"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
tools:text="Season 1"
|
||||
tools:visibility="visible" />
|
||||
|
@ -831,16 +829,16 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_episode_select"
|
||||
style="@style/MultiSelectButton"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
|
||||
android:nextFocusLeft="@id/result_season_button"
|
||||
android:nextFocusRight="@id/result_season_button"
|
||||
|
||||
android:nextFocusUp="@id/result_description"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
tools:text="50-100"
|
||||
tools:visibility="visible" />
|
||||
|
@ -848,16 +846,15 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_dub_select"
|
||||
style="@style/MultiSelectButton"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
android:nextFocusLeft="@id/result_season_button"
|
||||
android:nextFocusRight="@id/result_season_button"
|
||||
|
||||
android:nextFocusUp="@id/result_description"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
tools:text="Dubbed"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:id="@+id/media_route_button_holder"
|
||||
android:animateLayoutChanges="true"
|
||||
android:layout_gravity="center_vertical|end">
|
||||
|
||||
<androidx.mediarouter.app.MediaRouteButton
|
||||
|
@ -70,35 +69,15 @@
|
|||
app:mediaRouteButtonTint="?attr/textColor" />
|
||||
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:nextFocusLeft="@id/result_add_sync"
|
||||
android:nextFocusRight="@id/result_share"
|
||||
|
||||
tools:visibility="visible"
|
||||
|
||||
android:id="@+id/result_subscribe"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/baseline_notifications_none_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
app:tint="?attr/textColor" />
|
||||
|
||||
<ImageView
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:nextFocusLeft="@id/result_subscribe"
|
||||
android:nextFocusRight="@id/result_open_in_browser"
|
||||
|
||||
android:id="@+id/result_share"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
|
|
|
@ -199,13 +199,17 @@
|
|||
android:id="@+id/result_back"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="10dp"
|
||||
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/go_back"
|
||||
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
app:tint="?attr/white" />
|
||||
|
@ -381,8 +385,8 @@
|
|||
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/result_tag"
|
||||
style="@style/ChipParent"
|
||||
android:id="@+id/result_tag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
@ -419,11 +423,11 @@
|
|||
|
||||
|
||||
<LinearLayout
|
||||
android:animateLayoutChanges="true"
|
||||
android:id="@+id/result_movie_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
@ -564,7 +568,6 @@
|
|||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusLeft="@id/result_movie_progress_downloaded_holder"
|
||||
android:nextFocusRight="@id/result_bookmark_button"
|
||||
android:nextFocusDown="@id/result_resume_series_button_play"
|
||||
android:text="@string/type_none"
|
||||
android:visibility="visible" />
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:backgroundTint="?attr/primaryBlackBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/provider_test_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/settings_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
android:paddingTop="@dimen/navbar_height"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:navigationIconTint="?attr/iconColor"
|
||||
app:titleTextColor="?attr/textColor"
|
||||
tools:title="@string/category_provider_test">
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<com.lagradost.cloudstream3.ui.settings.testing.TestView
|
||||
android:id="@+id/provider_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusDown="@id/provider_test_recycler_view"
|
||||
app:header_text="@string/category_provider_test"
|
||||
app:layout_scrollFlags="scroll|enterAlways" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/provider_test_recycler_view"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="60dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/provider_test_item" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -23,7 +23,9 @@
|
|||
android:nextFocusUp="@id/home_child_more_info"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:clipToPadding="false"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:id="@+id/home_child_recyclerview"
|
||||
android:orientation="horizontal"
|
||||
|
|
|
@ -2,30 +2,33 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_marginStart="@dimen/navbar_width"
|
||||
android:id="@+id/home_child_more_info"
|
||||
style="@style/WatchHeaderText"
|
||||
android:padding="12dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/navbar_width"
|
||||
style="@style/WatchHeaderText"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:padding="12dp"
|
||||
tools:text="Trending" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:nextFocusUp="@id/home_child_more_info"
|
||||
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingStart="@dimen/navbar_width"
|
||||
android:clipToPadding="false"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:id="@+id/home_child_recyclerview"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:nextFocusUp="@id/home_child_more_info"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/navbar_width"
|
||||
android:paddingEnd="5dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/home_result_grid" />
|
||||
</LinearLayout>
|
|
@ -1,71 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:nextFocusRight="@id/action_button"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="16sp"
|
||||
tools:text="Test repository" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lang_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="5dp"
|
||||
tools:text="🇷🇼"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/passed_failed_marker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Failed"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fail_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:text="Unable to load videos"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginStart="10dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/baseline_text_snippet_24"
|
||||
app:tint="?attr/white" />
|
||||
</LinearLayout>
|
|
@ -1,138 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?attr/primaryBlackBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_test_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/outline_drawable_less"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_test_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="17sp"
|
||||
tools:text="Homepage test" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_test_section_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textSize="17sp"
|
||||
tools:text="67 / 120 " />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
app:cardBackgroundColor="?attr/primaryGrayBackground"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/passed_test_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/outline_drawable_less"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Tests passed"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/passed_test_section_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textSize="17sp"
|
||||
tools:text="55" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/failed_test_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/outline_drawable_less"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Tests failed"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/failed_test_section_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textSize="17sp"
|
||||
tools:text="12" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/tests_play_pause"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:text="@string/start"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24">
|
||||
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/test_total_progress"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="5dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="-1.5dp"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
android:progressTint="?attr/colorPrimary"
|
||||
android:visibility="gone"
|
||||
tools:progress="50" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -519,23 +519,6 @@
|
|||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_player" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_test_providers"
|
||||
android:name="com.lagradost.cloudstream3.ui.settings.testing.TestFragment"
|
||||
android:layout_height="match_parent"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_testing">
|
||||
<action
|
||||
android:id="@+id/action_navigation_global_to_navigation_test_providers"
|
||||
app:destination="@id/navigation_test_providers"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_setup_language"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">ملصق</string>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<string name="action_open_watching">مزيد من المعلومات</string>
|
||||
<string name="vpn_might_be_needed">قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح</string>
|
||||
<string name="vpn_torrent">هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة</string>
|
||||
<string name="provider_info_meta">لا يتم توفير البيانات الوصفية بواسطة الموقع، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع.</string>
|
||||
<string name="provider_info_meta">لا يتم توفير البيانات الوصفية بواسطة الموقع ، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع.</string>
|
||||
<string name="torrent_plot">الوصف</string>
|
||||
<string name="normal_no_plot">لم يتم العثور على وصف</string>
|
||||
<string name="torrent_no_plot">لم يتم العثور على وصف</string>
|
||||
|
@ -119,16 +119,16 @@
|
|||
<string name="eigengraumode_settings">وضع إيغنغرافي</string>
|
||||
<string name="eigengraumode_settings_des">يضيف خيار السرعة في المُشغل</string>
|
||||
<string name="swipe_to_seek_settings">السحب لتقديم</string>
|
||||
<string name="swipe_to_seek_settings_des">اسحب من جانب إلى آخر للتحكم في موضعك في مقطع فيديو</string>
|
||||
<string name="swipe_to_seek_settings_des">إسحب إلى اليسار أو اليمين للتحكم في الوقت في مُشغل الفيديو</string>
|
||||
<string name="swipe_to_change_settings">السحب لتغيير الإعدادات</string>
|
||||
<string name="swipe_to_change_settings_des">مرر لأعلى أو لأسفل على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت</string>
|
||||
<string name="swipe_to_change_settings_des">إسحب على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت</string>
|
||||
<string name="autoplay_next_settings">تشغيل الحلقة التالية تلقائيًا</string>
|
||||
<string name="autoplay_next_settings_des">تبدأ الحلقة التالية عندما تنتهي الحالية</string>
|
||||
<string name="double_tap_to_seek_settings">النقر مرتان للتقديم للأمام أو للخلف</string>
|
||||
<string name="double_tap_to_pause_settings">الضغط مرتان لإيقاف مؤقت</string>
|
||||
<string name="double_tap_to_seek_amount_settings">التحكم في مدى تقديم المُشغل(ثوان)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">التحكم في مدى تقديم المُشغل</string>
|
||||
<string name="double_tap_to_seek_settings_des">إضغط مرتين على الجانب الأيمن أو الأيسر للتقديم للأمام أو للخلف</string>
|
||||
<string name="double_tap_to_pause_settings_des">اضغط مرتين في المنتصف للتوقف</string>
|
||||
<string name="double_tap_to_pause_settings_des">إضغط في الوسط لإيقاف مؤقت</string>
|
||||
<string name="use_system_brightness_settings">استخدم سطوع النظام</string>
|
||||
<string name="use_system_brightness_settings_des">استخدم سطوع النظام في مُشغل التطبيق بدلاً من التراكب الداكن</string>
|
||||
<string name="episode_sync_settings">تحديث تقدم المشاهدة</string>
|
||||
|
@ -155,7 +155,7 @@
|
|||
<string name="automatic_plugin_updates">تحديث الإضافات تلقائيًا</string>
|
||||
<string name="automatic_plugin_download">تنزيل الإضافات تلقائيًا</string>
|
||||
<string name="updates_settings">التحديث التلقائي</string>
|
||||
<string name="updates_settings_des">ابحث تلقائيا عن التحديثات الجديدة بعد بدء التطبيق.</string>
|
||||
<string name="updates_settings_des">البحث تلقائيًا عن التحديثات الجديدة عند البداية</string>
|
||||
<string name="uprereleases_settings">التحديث إلى الاصدارات التجريبية (بيتا)</string>
|
||||
<string name="uprereleases_settings_des">البحث عن التحديثات التجريبية بدلاً من الإصدارات الكاملة فقط</string>
|
||||
<string name="github">غيت هاب</string>
|
||||
|
@ -170,7 +170,7 @@
|
|||
<string name="copy_link_toast">تم نسخ الرابط إلى الحافظة</string>
|
||||
<string name="play_episode_toast">تشغيل الحلقة</string>
|
||||
<string name="subs_default_reset_toast">إعادة التعيين إلى القيمة الافتراضية</string>
|
||||
<string name="acra_report_toast">عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين</string>
|
||||
<string name="acra_report_toast">عذرا ، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين</string>
|
||||
<string name="season">موسم</string>
|
||||
<string name="no_season">لا موسم</string>
|
||||
<string name="episode">حلقة</string>
|
||||
|
@ -218,8 +218,8 @@
|
|||
<string name="movies_singular">فيلم</string>
|
||||
<string name="tv_series_singular">مسلسل</string>
|
||||
<string name="cartoons_singular">كرتون</string>
|
||||
<string name="anime_singular">أنيمي</string>
|
||||
<string name="ova_singular">أوفا</string>
|
||||
<string name="anime_singular">أنمي</string>
|
||||
<string name="ova_singular">اوفا</string>
|
||||
<string name="torrent_singular">تورنت</string>
|
||||
<string name="documentaries_singular">وثائقي</string>
|
||||
<string name="asian_drama_singular">دراما آسيوية</string>
|
||||
|
@ -259,15 +259,15 @@
|
|||
<string name="dont_show_again">لا تظهر مرة أخرى</string>
|
||||
<string name="skip_update">تخطي هذا التحديث</string>
|
||||
<string name="update">تحديث</string>
|
||||
<string name="watch_quality_pref">جودة المشاهدة المفضلة (WiFi)</string>
|
||||
<string name="watch_quality_pref">جودة المشاهدة المفضلة</string>
|
||||
<string name="limit_title">أقصى عدد لحروف عنوان مُشغل الفيديو</string>
|
||||
<string name="limit_title_rez">أبعاد مُشغل الفيديو</string>
|
||||
<string name="video_buffer_size_settings">حجم ذاكرة التخزين المؤقت للفيديو</string>
|
||||
<string name="video_buffer_length_settings">طول التخزين المؤقت</string>
|
||||
<string name="video_buffer_disk_settings">التخزين المؤقت للفيديو على القرص</string>
|
||||
<string name="video_buffer_clear_settings">مسح التخزين المؤقت للصورة والفيديو</string>
|
||||
<string name="video_ram_description">يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة، مثل تلفزيون أندرويد.</string>
|
||||
<string name="video_disk_description">يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة، مثل تلفزيون أندرويد.</string>
|
||||
<string name="video_ram_description">يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة ، مثل Android TV.</string>
|
||||
<string name="video_disk_description">يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة ، مثل Android TV.</string>
|
||||
<string name="dns_pref">إستخدام DNS بدلا من HTTPS</string>
|
||||
<string name="dns_pref_summary">مفيد لتجاوز حجب مزود خدمة الإنترنت</string>
|
||||
<string name="add_site_pref">موقع بديل (نسخة)</string>
|
||||
|
@ -284,7 +284,7 @@
|
|||
<string name="legal_notice_text" translatable="false">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.</string>
|
||||
<string name="category_general">عام</string>
|
||||
<string name="random_button_settings">زر العشوائي</string>
|
||||
<string name="random_button_settings_desc">يظهر الزر على الصفحة الرئيسية والذي يمكنه اختيار فيلم عشوائي أو مسلسل تلفزيوني من الصفحة الرئيسية</string>
|
||||
<string name="random_button_settings_desc">إظهار زر العشوائي على الصفحة الرئيسية</string>
|
||||
<string name="provider_lang_settings">لغات المزود</string>
|
||||
<string name="app_layout">واجهة التطبيق</string>
|
||||
<string name="preferred_media_settings">المحتوى المفضل</string>
|
||||
|
@ -342,7 +342,7 @@
|
|||
<string name="all">الكل</string>
|
||||
<string name="max">الحد الاقصي</string>
|
||||
<string name="min">الحد الأدنى</string>
|
||||
<string name="subtitles_none" translatable="false">@string/none</string>
|
||||
<string name="subtitles_none" translatable="false">\@string/none</string>
|
||||
<string name="subtitles_outline">الخطوط المحيطة</string>
|
||||
<string name="subtitles_depressed">النمط المنخفض</string>
|
||||
<string name="subtitles_shadow">ظل</string>
|
||||
|
@ -360,7 +360,7 @@
|
|||
https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog
|
||||
-->
|
||||
<string name="subtitles_example_text">نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق</string>
|
||||
<string name="recommended">مُوصى به</string>
|
||||
<string name="recommended">مُوصي به</string>
|
||||
<string name="player_loaded_subtitles" formatted="true">تم تحميل %s</string>
|
||||
<string name="player_load_subtitles">إختيار ملف</string>
|
||||
<string name="player_load_subtitles_online">تحميل من الانترنت</string>
|
||||
|
@ -538,27 +538,4 @@
|
|||
<string name="library">المكتبة</string>
|
||||
<string name="safe_mode_file">تم العثور على ملف الوضع الآمن!
|
||||
\nلا يتم تحميل أي ملحقات عند بدء التشغيل حتى تتم إزالة الملف.</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">مدة التقديم عنما يكون المشغل مخفيا</string>
|
||||
<string name="android_tv_interface_off_seek_settings">مدة التقديم - المشغل مخفي</string>
|
||||
<string name="pref_category_android_tv">تلفزيون أندرويد</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">مدة التقديم عنما يكون المشغل مرئيا</string>
|
||||
<string name="android_tv_interface_on_seek_settings">مدة التقديم- المشغل المرئي</string>
|
||||
<string name="test_failed">فشل</string>
|
||||
<string name="test_passed">نجح</string>
|
||||
<string name="category_provider_test">إختبار المزود</string>
|
||||
<string name="restart">إعادة التشغيل</string>
|
||||
<string name="test_log">سجل</string>
|
||||
<string name="start">بَدأ</string>
|
||||
<string name="stop">إيقاف</string>
|
||||
<string name="subscription_in_progress_notification">تحديث العروض التي تم الاشتراك فيها</string>
|
||||
<string name="subscription_deleted">إلغاء الاشتراك من %s</string>
|
||||
<string name="subscription_episode_released">تم إصدار الحلقة %d!</string>
|
||||
<string name="subscription_list_name">مشترك</string>
|
||||
<string name="subscription_new">مشترك في %s</string>
|
||||
<string name="pref_category_bypass">تجاوز مزود خدمة الإنترنت</string>
|
||||
<string name="revert">استرجاع</string>
|
||||
<string name="jsdelivr_enabled">فشل الوصول إلى GitHub ، وتمكين وكيل jsdelivr.</string>
|
||||
<string name="jsdelivr_proxy_summary">باستخدام jsdelivr ، يمكن تجاوز حظر GitHub. قد يؤخر التحديثات لبضعة أيام.</string>
|
||||
<string name="jsdelivr_proxy">وكيل raw.githubusercontent.com</string>
|
||||
<string name="watch_quality_pref_data">جودة المشاهدة المفضلة (بيانات الجوال)</string>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string formatted="true" name="app_dub_sub_episode_text_format">%s еп. %d</string>
|
||||
<string formatted="true" name="cast_format">Актьори: %s</string>
|
||||
|
@ -106,7 +105,7 @@
|
|||
<string name="continue_watching">Продължете да гледате</string>
|
||||
<string name="action_remove_watching">Премахване</string>
|
||||
<string name="action_open_watching">Повече информация</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Може да е необходим VPN, за да работи правилно този доставчик</string>
|
||||
<string name="vpn_torrent">Този доставчик е торент, препоръчва се VPN</string>
|
||||
<string name="provider_info_meta">Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта.</string>
|
||||
|
@ -224,8 +223,8 @@
|
|||
<string name="movies_singular">Филм</string>
|
||||
<string name="tv_series_singular">Серия</string>
|
||||
<string name="cartoons_singular">Анимационен филм</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Торент</string>
|
||||
<string name="documentaries_singular">Документален филм</string>
|
||||
<string name="asian_drama_singular">Азиатска драма</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="result_poster_img_des">পোস্টার</string>
|
||||
<string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string>
|
||||
|
@ -143,8 +143,8 @@
|
|||
<string name="category_updates">হালনাগাদ ও ব্যাকআপ</string>
|
||||
<string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string>
|
||||
<string name="swipe_to_seek_settings">খুঁজতে সোয়াইপ করুন</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="double_tap_to_seek_settings">আগাতে ডবল ট্যাপ করুন</string>
|
||||
<string name="eigengraumode_settings">আইজেনগ্রাভি মোড</string>
|
||||
<string name="update_started">আপডেট শুরু হয়েছে</string>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- KEYS DON'T TRANSLATE -->
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
|
@ -11,7 +10,7 @@
|
|||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Episode Poster</string>
|
||||
<string name="home_main_poster_img_des">Main Poster</string>
|
||||
<string name="home_next_random_img_des">Next Random</string>
|
||||
|
@ -109,7 +108,7 @@
|
|||
<string name="continue_watching">Continue Assistindo</string>
|
||||
<string name="action_remove_watching">Remover</string>
|
||||
<string name="action_open_watching">Mais Info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Uma VPN pode ser necessária para esse fornecedor funcionar corretamente</string>
|
||||
<string name="vpn_torrent">Esse fornecedor é um torrent, uma VPN é recomendada</string>
|
||||
<string name="provider_info_meta">Metadados não são oferecidas pelo site, o carregamento do video pode falhar se ele não existir no site.</string>
|
||||
|
@ -223,8 +222,8 @@
|
|||
<string name="movies_singular">Filme</string>
|
||||
<string name="tv_series_singular">Série</string>
|
||||
<string name="cartoons_singular">Desenho Animado</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Documentário</string>
|
||||
<string name="asian_drama_singular">Drama Asiático</string>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- KEYS DON'T TRANSLATE -->
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
|
@ -104,7 +103,7 @@
|
|||
<string name="continue_watching">Pokračovat ve sledování</string>
|
||||
<string name="action_remove_watching">Odebrat</string>
|
||||
<string name="action_open_watching">Další informace</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Aby tento poskytovatel fungoval správně, budete možná potřebovat VPN</string>
|
||||
<string name="vpn_torrent">Tento poskytovatel je torrent, je doporučená VPN</string>
|
||||
<string name="provider_info_meta">Web neposkytnul žádná metadata, načítání videa selže, pokud na webu neexistuje.</string>
|
||||
|
@ -123,14 +122,14 @@
|
|||
<string name="eigengraumode_settings">Rychlostní režim</string>
|
||||
<string name="eigengraumode_settings_des">Přidá do přehrávače možnost rychlosti</string>
|
||||
<string name="swipe_to_seek_settings">Přejet pro posun</string>
|
||||
<string name="swipe_to_seek_settings_des">Přejeďte prstem ze strany na stranu pro ovládání své pozice ve videu</string>
|
||||
<string name="swipe_to_seek_settings_des">Přejeďte prstem vlevo nebo vpravo pro ovládání času v přehrávači</string>
|
||||
<string name="swipe_to_change_settings">Přejet pro změnu nastavení</string>
|
||||
<string name="swipe_to_change_settings_des">Přejeďte prstem nahoru nebo dolů na levé nebo pravé straně pro změnu jasu nebo hlasitosti</string>
|
||||
<string name="swipe_to_change_settings_des">Přejeďte prstem na levé nebo pravé straně pro změnu jasu nebo hlasitosti</string>
|
||||
<string name="double_tap_to_seek_settings">Dvojité klepnutí pro posun</string>
|
||||
<string name="double_tap_to_pause_settings">Dvojité klepnutí pro pozastavení</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Množství času k posunu (sekundy)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Množství času k posunu</string>
|
||||
<string name="double_tap_to_seek_settings_des">Klepněte dvakrát vpravo nebo vlevo pro posun vpřed nebo vzad</string>
|
||||
<string name="double_tap_to_pause_settings_des">Klepněte dvakrát doprostřed pro pozastavení</string>
|
||||
<string name="double_tap_to_pause_settings_des">Klepněte doprostřed pro pozastavení</string>
|
||||
<string name="use_system_brightness_settings">Použít systémový jas</string>
|
||||
<string name="use_system_brightness_settings_des">V přehrávači použít systémov překrytí</string>
|
||||
<string name="episode_sync_settings">Aktualizovat postup sledování</string>
|
||||
|
@ -152,7 +151,7 @@
|
|||
<string name="bug_report_settings_on">Nebude odesílat žádná data</string>
|
||||
<string name="show_fillers_settings">Zobrazit výplňové epizody u anime</string>
|
||||
<string name="updates_settings">Zobrazit aktualizace aplikace</string>
|
||||
<string name="updates_settings_des">Při spuštění aplikace automaticky zkontrolovat nové aktualizace.</string>
|
||||
<string name="updates_settings_des">Při spuštění automaticky zkontrolovat nové aktualizace</string>
|
||||
<string name="uprereleases_settings">Aktualizovat na předběžná vydání</string>
|
||||
<string name="uprereleases_settings_des">Kontrolovat aktualizace předběžných vydání, místo normálních plných vydání</string>
|
||||
<string name="github">GitHub</string>
|
||||
|
@ -212,8 +211,8 @@
|
|||
<string name="movies_singular">Film</string>
|
||||
<string name="tv_series_singular">Seriál</string>
|
||||
<string name="cartoons_singular">Animovaný</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Dokument</string>
|
||||
<string name="asian_drama_singular">Asijské drama</string>
|
||||
|
@ -246,7 +245,7 @@
|
|||
<string name="dont_show_again">Již nezobrazovat</string>
|
||||
<string name="skip_update">Přeskočit tuto aktualizace</string>
|
||||
<string name="update">Aktualizovat</string>
|
||||
<string name="watch_quality_pref">Upřednostněná kvalita sledování (WiFi)</string>
|
||||
<string name="watch_quality_pref">Upřednostněná kvalita sledování</string>
|
||||
<string name="limit_title">Maximální počet znaků v názvu přehrávače</string>
|
||||
<string name="limit_title_rez">Rozlišení přehrávače</string>
|
||||
<string name="video_buffer_size_settings">Velikost vyrovnávací paměti videa</string>
|
||||
|
@ -267,7 +266,7 @@
|
|||
<string name="legal_notice_text" translatable="false">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čí.</string>
|
||||
<string name="category_general">Obecné</string>
|
||||
<string name="random_button_settings">Náhodné tlačítko</string>
|
||||
<string name="random_button_settings_desc">Zobrazit na domovské stránce tlačítko, kterým lze vybrat náhodný film nebo seriál z domovské stránky</string>
|
||||
<string name="random_button_settings_desc">Zobrazit na domovské stránce náhodné tlačítko</string>
|
||||
<string name="provider_lang_settings">Jazyk poskytovatelů</string>
|
||||
<string name="app_layout">Rozložení aplikace</string>
|
||||
<string name="preferred_media_settings">Preferovaná média</string>
|
||||
|
@ -531,27 +530,4 @@
|
|||
<string name="delete_repository">Odstranit repozitář</string>
|
||||
<string name="plugins_not_downloaded" formatted="true">Nestaženo: %d</string>
|
||||
<string name="no">Ne</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Skrytý přehrávač - doba hledání</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Množství vyhledávané doby při skrytém přehrávači</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Zobrazený přehrávač - doba hledání</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Množství vyhledávané doby při zobrazeném přehrávači</string>
|
||||
<string name="test_log">Protokol</string>
|
||||
<string name="category_provider_test">Test poskytovatele</string>
|
||||
<string name="test_failed">Neúspěšné</string>
|
||||
<string name="test_passed">Úspěšné</string>
|
||||
<string name="restart">Restart</string>
|
||||
<string name="start">Spustit</string>
|
||||
<string name="stop">Zastavit</string>
|
||||
<string name="subscription_in_progress_notification">Aktualizace odebíraných pořadů</string>
|
||||
<string name="subscription_new">Přihlášeno k odběru %s</string>
|
||||
<string name="subscription_deleted">Odhlášen odběr od %s</string>
|
||||
<string name="subscription_episode_released">Byla vydána epizoda %d!</string>
|
||||
<string name="subscription_list_name">Odebíráno</string>
|
||||
<string name="jsdelivr_proxy">Proxy raw.githubusercontent.com</string>
|
||||
<string name="jsdelivr_enabled">Nepodařilo se připojit ke GitHubu, povolování proxy jsdelivr.</string>
|
||||
<string name="watch_quality_pref_data">Upřednostněná kvalita sledování (mobilní data)</string>
|
||||
<string name="revert">Vrátit zpět</string>
|
||||
<string name="jsdelivr_proxy_summary">Pomocí jsdelivr lze obejít blokování GitHubu. Může dojít ke zpoždění aktualizací o několik dní.</string>
|
||||
<string name="pref_category_bypass">Obcházení ISP</string>
|
||||
</resources>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Besetzung: %s</string>
|
||||
|
@ -41,7 +41,7 @@
|
|||
<string name="search_hint_site" formatted="true">Suche %s…</string>
|
||||
<string name="no_data">Keine Daten vorhanden</string>
|
||||
<string name="episode_more_options_des">Mehr Optionen</string>
|
||||
<string name="next_episode">Nächste Episode</string>
|
||||
<string name="next_episode">Nächste Epsisode</string>
|
||||
<string name="result_tags">Genres</string>
|
||||
<string name="result_share">Teilen</string>
|
||||
<string name="result_open_in_browser">In Browser öffnen</string>
|
||||
|
@ -53,7 +53,7 @@
|
|||
<string name="type_dropped">Abgebrochen</string>
|
||||
<string name="type_plan_to_watch">Geplant</string>
|
||||
<string name="type_none">Nichts</string>
|
||||
<string name="type_re_watching">Erneut schauen</string>
|
||||
<string name="type_re_watching">Erneut anschauen</string>
|
||||
<string name="play_movie_button">Film abspielen</string>
|
||||
<string name="play_livestream_button">Livestream abspielen</string>
|
||||
<string name="play_torrent_button">Torrent streamen</string>
|
||||
|
@ -115,7 +115,7 @@
|
|||
<string name="continue_watching">Weiterschauen</string>
|
||||
<string name="action_remove_watching">Entfernen</string>
|
||||
<string name="action_open_watching">Mehr Infos</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Damit dieser Anbieter korrekt funktioniert, ist möglicherweise ein VPN erforderlich</string>
|
||||
<string name="vpn_torrent">Dieser Anbieter bietet Torrents an, ein VPN wird dringend empfohlen</string>
|
||||
<string name="provider_info_meta">Metadaten werden nicht von der Website bereitgestellt, das Laden des Videos schlägt fehl, wenn sie auf der Website nicht vorhanden sind.</string>
|
||||
|
@ -136,14 +136,14 @@
|
|||
<string name="swipe_to_seek_settings">Wischen zum vor- und zurückspulen</string>
|
||||
<string name="swipe_to_seek_settings_des">Nach links oder rechts wischen, um die Zeit im Videoplayer zu steuern</string>
|
||||
<string name="swipe_to_change_settings">Wischen, um Einstellungen zu ändern</string>
|
||||
<string name="swipe_to_change_settings_des">Links oder rechts nach oben oder unten wischen, um die Helligkeit oder Lautstärke zu ändern</string>
|
||||
<string name="swipe_to_change_settings_des">Links oder rechts wischen, um die Helligkeit oder Lautstärke zu ändern</string>
|
||||
<string name="autoplay_next_settings">Nächste Episode automatisch abspielen</string>
|
||||
<string name="autoplay_next_settings_des">Nächste Episode wird gestartet, sobald die aktuelle Episode endet</string>
|
||||
<string name="double_tap_to_seek_settings">Doppeltippen zum vor- und zurückspulen</string>
|
||||
<string name="double_tap_to_pause_settings">Doppeltippen zum Pausieren</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Zeit für vor- und zurückspulen im Player (Sekunden)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Zeit für vor- und zurückspulen im Player</string>
|
||||
<string name="double_tap_to_seek_settings_des">Zweimal auf die rechte oder linke Seite tippen, um vor- oder zurückzuspulen</string>
|
||||
<string name="double_tap_to_pause_settings_des">Doppelt in die Mitte tippen, um zu pausieren</string>
|
||||
<string name="double_tap_to_pause_settings_des">In die Mitte tippen, um zu pausieren</string>
|
||||
<string name="use_system_brightness_settings">Systemhelligkeit verwenden</string>
|
||||
<string name="use_system_brightness_settings_des">Systemhelligkeit anstelle eines dunklen Overlay im Player verwenden</string>
|
||||
<string name="episode_sync_settings">Episodenfortschritt aktualisieren</string>
|
||||
|
@ -166,7 +166,7 @@
|
|||
<string name="pref_filter_search_quality">Ausgewählte Videoqualität bei Suchergebnissen ausblenden</string>
|
||||
<string name="automatic_plugin_updates">Automatische Plugin-Updates</string>
|
||||
<string name="updates_settings">App-Updates anzeigen</string>
|
||||
<string name="updates_settings_des">Automatisches Suchen nach neuen Updates nach dem Start</string>
|
||||
<string name="updates_settings_des">Automatisches Suchen nach neuen Updates beim Start</string>
|
||||
<string name="uprereleases_settings">Auf Vorabversionen updaten</string>
|
||||
<string name="uprereleases_settings_des">Suche nach Vorabversionen statt nur nach Vollversionen</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -212,7 +212,7 @@
|
|||
<string name="no_subtitles">Keine Untertitel</string>
|
||||
<string name="default_subtitles">Standard</string>
|
||||
<string name="free_storage">Frei</string>
|
||||
<string name="used_storage">Belegt</string>
|
||||
<string name="used_storage">Benutzt</string>
|
||||
<string name="app_storage">App</string>
|
||||
<string name="movies">Filme</string>
|
||||
<string name="tv_series">TV-Serien</string>
|
||||
|
@ -246,7 +246,7 @@
|
|||
<string name="episode_action_play_in_browser">In Browser wiedergeben</string>
|
||||
<string name="episode_action_copy_link">Link kopieren</string>
|
||||
<string name="episode_action_auto_download">Auto-Download</string>
|
||||
<string name="episode_action_download_mirror">Alternativer Download</string>
|
||||
<string name="episode_action_download_mirror">Download-Mirror</string>
|
||||
<string name="episode_action_reload_links">Links neu laden</string>
|
||||
<string name="episode_action_download_subtitle">Untertitel herunterladen</string>
|
||||
<string name="show_hd">Qualitätsanzeige</string>
|
||||
|
@ -263,7 +263,7 @@
|
|||
<string name="dont_show_again">Nicht mehr anzeigen</string>
|
||||
<string name="skip_update">Update ignorieren</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="watch_quality_pref">Bevorzugte Videoqualität (WLAN)</string>
|
||||
<string name="watch_quality_pref">Bevorzugte Auflösung</string>
|
||||
<string name="limit_title">Videoplayertitel max. Zeichen</string>
|
||||
<string name="limit_title_rez">Videoplayer Auflösung</string>
|
||||
<string name="video_buffer_size_settings">Videopuffergröße</string>
|
||||
|
@ -284,9 +284,9 @@
|
|||
<string name="resize_fill">Strecken</string>
|
||||
<string name="resize_zoom">Vergrößern</string>
|
||||
<string name="legal_notice">Haftungsausschluss</string>
|
||||
<string name="category_general">Allgemein</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="random_button_settings">Zufalls-Button</string>
|
||||
<string name="random_button_settings_desc">Zeigt einen Zufallsbutton auf der Startseite an, mit welchem eine Serie oder ein Film von der Website zufällig ausgewählt wird</string>
|
||||
<string name="random_button_settings_desc">Zufallsbutton auf der Startseite anzeigen</string>
|
||||
<string name="provider_lang_settings">Anbieter-Sprachen</string>
|
||||
<string name="app_layout">App-Layout</string>
|
||||
<string name="preferred_media_settings">Bevorzugte Medien</string>
|
||||
|
@ -460,11 +460,11 @@
|
|||
<string name="automatic_plugin_download_summary">Automatische Installation aller noch nicht installierten Plugins aus hinzugefügten Repositories.</string>
|
||||
<string name="redo_setup_process">Einrichtungsvorgang wiederholen</string>
|
||||
<string name="apk_installer_settings">APK-Installer</string>
|
||||
<string name="apk_installer_settings_des">Einige Telefone unterstützen den neuen Package-Installer nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen.</string>
|
||||
<string name="apk_installer_settings_des">Einige Telefone unterstützen das neue Installationsprogramm für Pakete nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen.</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="pref_category_links">Links</string>
|
||||
<string name="pref_category_app_updates">App-Updates</string>
|
||||
<string name="pref_category_backup">Sicherung</string>
|
||||
<string name="pref_category_backup">Back-Up</string>
|
||||
<string name="pref_category_extensions">Erweiterungen</string>
|
||||
<string name="pref_category_actions">Wartung</string>
|
||||
<string name="pref_category_cache">Cache</string>
|
||||
|
@ -506,27 +506,4 @@
|
|||
<string name="empty_library_logged_in_message">Diese Liste scheint leer zu sein. Versuche, zu einer anderen Liste zu wechseln.</string>
|
||||
<string name="safe_mode_file">Datei für abgesicherten Modus gefunden!
|
||||
\nBeim Start werden keine Erweiterungen geladen, bis die Datei entfernt wird.</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Player ausgeblendet - Betrag zum vor- und zurückspulen</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Der Betrag, welcher verwendet wird, wenn der Player eingeblendet ist</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Der Betrag, welcher verwendet wird, wenn der Player ausgeblendet ist</string>
|
||||
<string name="pref_category_android_tv">Android-TV</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Player eingeblendet - Betrag zum vor- und zurückspulen</string>
|
||||
<string name="test_failed">Fehlgeschlagen</string>
|
||||
<string name="test_passed">Erfolgreich</string>
|
||||
<string name="category_provider_test">Anbieter-Test</string>
|
||||
<string name="stop">Stopp</string>
|
||||
<string name="test_log">Log</string>
|
||||
<string name="start">Start</string>
|
||||
<string name="restart">Neustarten</string>
|
||||
<string name="watch_quality_pref_data">Bevorzugte Videoqualität (mobile Daten)</string>
|
||||
<string name="jsdelivr_proxy_summary">Umgehung der GitHub Sperre mit jsdelivr. Kann zu einigen Tagen Verzögerung bei Updates führen.</string>
|
||||
<string name="subscription_new">%s abonniert</string>
|
||||
<string name="subscription_deleted">%s deabonniert</string>
|
||||
<string name="subscription_episode_released">Episode %d erschienen!</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
|
||||
<string name="jsdelivr_enabled">GitHub kann nicht erreicht werden, der jsdelivr-Proxy wird aktiviert.</string>
|
||||
<string name="subscription_in_progress_notification">Abonnierte Serien werden aktualisiert</string>
|
||||
<string name="revert">Rückgängig</string>
|
||||
<string name="subscription_list_name">Abonniert</string>
|
||||
<string name="pref_category_bypass">ISP-Umgehungen</string>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="title_home">Αρχική</string>
|
||||
<string name="title_search">Αναζήτηση</string>
|
||||
|
@ -151,7 +150,7 @@
|
|||
<string name="episodes">Επεισόδια</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="season_short">Σ</string>
|
||||
<string name="season_short">Κ</string>
|
||||
<string name="episode_short">E</string>
|
||||
<string name="no_episodes_found">Δεν βρέθηκαν επεισόδια</string>
|
||||
<string name="delete_file">Διαγραφή αρχείου</string>
|
||||
|
@ -388,7 +387,7 @@
|
|||
<string name="sort_close">Κλείσιμο</string>
|
||||
<string name="sort_clear">Εκκαθάριση</string>
|
||||
<string name="subs_subtitle_languages">Γλώσσα υποτίτλων</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="provider_info_meta">Δεν έχουν παρασχεθεί μεταδεδομένα από τον ιστότοπο, η φόρτωση του βίντεο θα αποτύχει αν δεν υπάρχει στον ιστότοπο.</string>
|
||||
<string name="double_tap_to_pause_settings">Διπλό πάτημα για παύση</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Μέγεθος αναζήτησης στο πρόγραμμα αναπαραγωγής</string>
|
||||
|
@ -453,7 +452,7 @@
|
|||
<string name="skip_type_mixed_ed">Ανάμεικτοι τίτλοι τέλους</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="rating">Κριτική</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="pref_category_app_updates">Ενημερώσεις εφαρμογής</string>
|
||||
<string name="pref_category_backup">Αντίγραφο ασφαλείας</string>
|
||||
<string name="pref_category_extensions">Extensions</string>
|
||||
|
@ -465,7 +464,7 @@
|
|||
<string name="pref_category_defaults">Προεπιλεγμένα</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="subs_font_size">Μέγεθος γραμματοσειράς</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="pref_category_links">Σύνδεσμοι</string>
|
||||
<string name="pref_category_looks">Εμφάνιση</string>
|
||||
<string name="pref_category_ui_features">Χαρακτηριστικά</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="go_back_img_des">Reen</string>
|
||||
<string name="title_home">Hejmo</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pref_category_extensions">Extensiones</string>
|
||||
<string name="setup_extensions_subtext">Descargue la lista de sitios que quiera utilizar</string>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<string name="pref_filter_search_quality">Ocultar la calidad de video en los resultados de búsqueda</string>
|
||||
<string name="pref_category_player_layout">Diseño</string>
|
||||
<string name="category_ui">Diseño</string>
|
||||
<string name="watch_quality_pref">Calidad de visualización preferida (WiFi)</string>
|
||||
<string name="watch_quality_pref">Calidad de visualización preferida</string>
|
||||
<string name="player_pref">Reproductor de video preferido</string>
|
||||
<string name="emulator_layout">Diseño para emulador</string>
|
||||
<string name="app_layout">Diseño de la aplicación</string>
|
||||
|
@ -51,10 +51,10 @@
|
|||
<string name="subtitles_raised">Elevado</string>
|
||||
<string name="subtitle_offset_extra_hint_later_format">Use esto si los subtítulos se muestran %d ms muy pronto</string>
|
||||
<string name="subtitle_offset_extra_hint_before_format">Use esto si los subtítulos se muestran %d ms tarde</string>
|
||||
<string name="swipe_to_seek_settings_des">Desliza el dedo de lado a lado para controlar la posición en un video</string>
|
||||
<string name="swipe_to_seek_settings_des">Desliza el dedo hacia la izquierda o hacia la derecha para controlar el tiempo en el reproductor de video</string>
|
||||
<string name="subtitles_filter_lang">Filtrar por idioma de medios preferido</string>
|
||||
<string name="subtitles_remove_captions">Eliminar Closed Captions (CC) de los subtítulos</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Cantidad de búsquedas del reproductor (segundos)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Cantidad de tiempo de búsqueda en el reproductor (en segundos)</string>
|
||||
<string name="use_system_brightness_settings_des">Use el brillo del sistema en el reproductor de la app en lugar de una superposición oscura</string>
|
||||
<string name="limit_title_rez">Resolución del reproductor de video</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
|
@ -194,7 +194,7 @@
|
|||
<string name="continue_watching">Continuar Viendo</string>
|
||||
<string name="action_remove_watching">Remover</string>
|
||||
<string name="action_open_watching">Más info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Una VPN puede ser necesaria para que este proveedor funcione correctamente</string>
|
||||
<string name="vpn_torrent">Este proveedor es un torrent, se recomienda una VPN</string>
|
||||
<string name="provider_info_meta">El sitio no proporciona los metadatos, la carga del video fallará si no existe en el sitio.</string>
|
||||
|
@ -205,16 +205,16 @@
|
|||
<string name="eigengraumode_settings">Modo Eigengravy</string>
|
||||
<string name="swipe_to_seek_settings">Deslice para avanzar/retroceder</string>
|
||||
<string name="swipe_to_change_settings">Deslice para cambiar la configuración</string>
|
||||
<string name="swipe_to_change_settings_des">Deslice hacia arriba o hacia abajo en el lado izquierdo o derecho para cambiar el brillo o el volumen</string>
|
||||
<string name="swipe_to_change_settings_des">Deslice el dedo hacia la izquierda o hacia la derecha para cambiar el brillo o el volumen</string>
|
||||
<string name="double_tap_to_seek_settings">Toca dos veces para buscar</string>
|
||||
<string name="double_tap_to_pause_settings">Tocar dos veces para pausar</string>
|
||||
<string name="double_tap_to_seek_settings_des">Toque dos veces en el lado derecho o izquierdo para buscar hacia adelante o hacia atrás</string>
|
||||
<string name="double_tap_to_pause_settings_des">Toque dos veces en el medio para hacer una pausa</string>
|
||||
<string name="double_tap_to_pause_settings_des">Toque en el medio para pausar</string>
|
||||
<string name="use_system_brightness_settings">Usar brillo del sistema</string>
|
||||
<string name="restore_settings">Restaurar datos desde el backup</string>
|
||||
<string name="backup_settings">Hacer copia de los datos (backup)</string>
|
||||
<string name="restore_success">Archivo de backup cargado</string>
|
||||
<string name="updates_settings_des">Busque automáticamente nuevas actualizaciones después de iniciar la aplicación.</string>
|
||||
<string name="updates_settings_des">Buscar automáticamente nuevas actualizaciones al inicio</string>
|
||||
<string name="redo_setup_process">Rehacer el proceso de configuración inicial</string>
|
||||
<string name="show_fillers_settings">Mostrar episodio de relleno para Anime</string>
|
||||
<string name="play_episode_toast">Reproducir Episodio</string>
|
||||
|
@ -306,7 +306,7 @@
|
|||
<string name="pref_category_looks">Aspecto</string>
|
||||
<string name="pref_category_ui_features">Características</string>
|
||||
<string name="random_button_settings">Botón de Al azar</string>
|
||||
<string name="random_button_settings_desc">Muestra un botón de reproducción \"al azar\" en la página de inicio para poelículas y series</string>
|
||||
<string name="random_button_settings_desc">Muestra un botón de reproducción \"al azar\" en la página de inicio</string>
|
||||
<string name="account">cuenta</string>
|
||||
<string name="logout">Cerrar sesión</string>
|
||||
<string name="switch_account">Cambiar cuenta</string>
|
||||
|
@ -363,8 +363,8 @@
|
|||
<string name="movies_singular">Película</string>
|
||||
<string name="tv_series_singular">Serie</string>
|
||||
<string name="cartoons_singular">Dibujo animado</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Documental</string>
|
||||
<string name="asian_drama_singular">Drama asiático</string>
|
||||
|
@ -506,27 +506,8 @@
|
|||
\nInicia sesión en una cuenta de biblioteca o añade series desde tu biblioteca local</string>
|
||||
<string name="safe_mode_file">¡Se encontró un archivo en modo seguro!
|
||||
\nNo cargar ninguna extensión al inicio hasta que se elimine el archivo.</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Reproductor visible - buscar cantidad</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Reproductor oculto - buscar cantidad</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Jugadora mostrada - buscar cantidad</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Jugadora oculta - buscar cantidad</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Tiempo de búsqueda usado (en segundos) cuando el reproductor está visible</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Tiempo de búsqueda usado (en segundos) cuando el reproductor está oculto</string>
|
||||
<string name="stop">Parar</string>
|
||||
<string name="test_failed">Falló</string>
|
||||
<string name="test_log">Registro</string>
|
||||
<string name="start">Empezar</string>
|
||||
<string name="test_passed">Aprobado</string>
|
||||
<string name="category_provider_test">Prueba del proveedor</string>
|
||||
<string name="restart">Reiniciar</string>
|
||||
<string name="subscription_list_name">Suscrito</string>
|
||||
<string name="subscription_new">Suscrito a %s</string>
|
||||
<string name="subscription_deleted">Darse de baja de %s</string>
|
||||
<string name="subscription_in_progress_notification">Actualizando los programas suscritos</string>
|
||||
<string name="subscription_episode_released">¡Episodio %d publicado!</string>
|
||||
<string name="jsdelivr_proxy">Proxy raw.githubusercontent.com</string>
|
||||
<string name="jsdelivr_enabled">No se ha podido acceder a GitHub, activando el proxy jsdelivr.</string>
|
||||
<string name="jsdelivr_proxy_summary">Con jsdelivr, se puede omitir el bloqueo de GitHub. Puede retrasar las actualizaciones unos días.</string>
|
||||
<string name="revert">Revertir</string>
|
||||
<string name="pref_category_bypass">ISP Bypasses</string>
|
||||
<string name="watch_quality_pref_data">Calidad de visualización preferida (Datos móviles)</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">La cantidad de búsqueda utilizada cuando la jugadora es visible.</string>
|
||||
</resources>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="pause">مکث</string>
|
||||
|
@ -23,14 +23,4 @@
|
|||
<string name="none">هیچکدام</string>
|
||||
<string name="title">عنوان</string>
|
||||
<string name="history">تاریخچه</string>
|
||||
<string name="search_poster_img_des">پوستر</string>
|
||||
<string name="result_poster_img_des">پوستر</string>
|
||||
<string name="episode_poster_img_des">پوستر قسمت</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dروز %dساعت %dدقیقه</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s قسمت %d</string>
|
||||
<string name="cast_format" formatted="true">بازیگران: %s</string>
|
||||
<string name="next_episode_format" formatted="true">قسمت %d پخش خواهد شد</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dساعت %dدقیقه</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dدقیقه</string>
|
||||
<string name="home_main_poster_img_des">پوستر اصلی</string>
|
||||
</resources>
|
|
@ -1,18 +1,17 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="title_home">Accueil</string>
|
||||
<string name="title_search">Rechercher</string>
|
||||
<string name="title_downloads">Téléchargements</string>
|
||||
<string name="title_settings">Paramètres</string>
|
||||
<string name="search_hint">Rechercher…</string>
|
||||
<string name="search_poster_img_des">Affiche</string>
|
||||
<string name="search_poster_img_des">Miniature</string>
|
||||
<string name="no_data">Aucune Donnée</string>
|
||||
<string name="episode_more_options_des">Plus d\'options</string>
|
||||
<string name="go_back_img_des">Retour</string>
|
||||
<string name="next_episode">Épisode suivant</string>
|
||||
<string name="result_poster_img_des">Affiche</string>
|
||||
<string name="result_poster_img_des">Miniature</string>
|
||||
<string name="result_tags">Genres</string>
|
||||
<string name="result_share">Partager</string>
|
||||
<string name="result_open_in_browser">Ouvrir dans le navigateur</string>
|
||||
|
@ -30,7 +29,7 @@
|
|||
<string name="pick_subtitle">Sous-titres</string>
|
||||
<string name="reload_error">Réessayer la connection…</string>
|
||||
<string name="go_back">Retour</string>
|
||||
<string name="episode_poster_img_des">Affiche de l\'épisode</string>
|
||||
<string name="episode_poster_img_des">Miniature de l\'Épisode</string>
|
||||
<string name="play_episode">Lire l\'Épisode</string>
|
||||
<!--<string name="need_storage">Permet de télécharger les épisodes</string>-->
|
||||
<string name="download">Télécharger</string>
|
||||
|
@ -52,10 +51,10 @@
|
|||
<string name="pref_disable_acra">Désactiver le rapport de bug automatique</string>
|
||||
<string name="home_more_info">Plus d\'informations</string>
|
||||
<string name="home_expanded_hide">Cacher</string>
|
||||
<string name="home_main_poster_img_des">Affiche principale</string>
|
||||
<string name="home_main_poster_img_des">Poster principal</string>
|
||||
<string name="home_play">Lecture</string>
|
||||
<string name="home_info">Infos</string>
|
||||
<string name="home_next_random_img_des">Aléatoire suivant</string>
|
||||
<string name="home_info">Info</string>
|
||||
<string name="home_next_random_img_des">Suivant Aléatoire</string>
|
||||
<string name="home_change_provider_img_des">Changer le fournisseur</string>
|
||||
<string name="filter_bookmarks">Filtrer les marques-pages</string>
|
||||
<string name="error_bookmarks_text">Marque-pages</string>
|
||||
|
@ -131,7 +130,7 @@
|
|||
<string name="new_update_format" formatted="true">Nouvelle mise à jour trouvée !
|
||||
\n%s -> %s</string>
|
||||
<string name="filler" formatted="true">Épisode spécial</string>
|
||||
<string name="watch_quality_pref">Qualité de visionnage préférée (WiFi)</string>
|
||||
<string name="watch_quality_pref">Qualité de visionnage préférée</string>
|
||||
<string name="video_buffer_size_settings">Taille de la mémoire cache</string>
|
||||
<string name="resize_fill">Étendre</string>
|
||||
<string name="legal_notice">Non-responsabilité</string>
|
||||
|
@ -212,7 +211,7 @@
|
|||
<string name="actor_background">Arrière plan</string>
|
||||
<string name="home_source">Source</string>
|
||||
<string name="home_random">Aléatoire</string>
|
||||
<string name="coming_soon">Bientôt disponible…</string>
|
||||
<string name="coming_soon">À venir …</string>
|
||||
<string name="poster_image">Image de l\'affiche</string>
|
||||
<string name="authenticated_user">%s Connecté</string>
|
||||
<string name="action_add_to_bookmarks">Définir le statut de visionage</string>
|
||||
|
@ -241,7 +240,7 @@
|
|||
<string name="continue_watching">Continuer à regarder</string>
|
||||
<string name="action_remove_watching">Retirer</string>
|
||||
<string name="action_open_watching">Plus d\'informations</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Un VPN peut être nécessaire pour que ce fournisseur fonctionne correctement</string>
|
||||
<string name="vpn_torrent">Ce fournisseur est un torrent, un VPN est recommandé</string>
|
||||
<string name="provider_info_meta">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.</string>
|
||||
|
@ -386,8 +385,8 @@
|
|||
<string name="quality_4k">4K</string>
|
||||
<string name="quality_webrip">Web</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="subtitles_filter_lang">Filtrez par langue préférée</string>
|
||||
|
@ -491,37 +490,4 @@
|
|||
<string name="delayed_update_notice">L\'application sera mise à jour dès la fin de la session</string>
|
||||
<string name="plugin_downloaded">Plugin Téléchargé</string>
|
||||
<string name="action_remove_from_watched">Retirer de la vue</string>
|
||||
<string name="library">Bibliothèque</string>
|
||||
<string name="browser">Navigateur</string>
|
||||
<string name="sort">Trier</string>
|
||||
<string name="sort_rating_asc">Note (basse à haute)</string>
|
||||
<string name="sort_rating_desc">Note (haut à bas)</string>
|
||||
<string name="sort_alphabetical_a">Alphabétique (A à Z)</string>
|
||||
<string name="empty_library_no_accounts_message">On dirait que votre bibliothèque est vide :(
|
||||
\nConnectez-vous à un compte ou ajoutez des séries à votre bibliothèque locale</string>
|
||||
<string name="empty_library_logged_in_message">Il semble que cette liste soit vide, essayez d\'en choisir une autre</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="sort_by">Trié par</string>
|
||||
<string name="sort_alphabetical_z">Alphabétique (Z à A)</string>
|
||||
<string name="select_library">Sélectionnez la bibliothèque</string>
|
||||
<string name="open_with">Ouvrir avec</string>
|
||||
<string name="sort_updated_new">Mis à jour (Nouveau vers ancien)</string>
|
||||
<string name="sort_updated_old">Mis à jour (ancien vers nouveau)</string>
|
||||
<string name="safe_mode_file">Fichier du mode sans échec trouvé !
|
||||
\nAucune extension ne sera chargée au démarrage avant que le fichier ne soit enlevé.</string>
|
||||
<string name="stop">Arrêter</string>
|
||||
<string name="revert">Revenir à</string>
|
||||
<string name="test_log">Enregistrer</string>
|
||||
<string name="watch_quality_pref_data">Qualité de visionnage préférée (données mobiles)</string>
|
||||
<string name="subscription_new">Abonné à %s</string>
|
||||
<string name="start">Démarrer</string>
|
||||
<string name="category_provider_test">Test des fournisseurs</string>
|
||||
<string name="test_passed">Réussi</string>
|
||||
<string name="subscription_deleted">Désabonné de %s</string>
|
||||
<string name="restart">Redémarrer</string>
|
||||
<string name="subscription_list_name">Abonné</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
|
||||
<string name="pref_category_bypass">Contournements de FAI</string>
|
||||
<string name="subscription_episode_released">L\'épisode %d est sorti !</string>
|
||||
<string name="test_failed">Échouer</string>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
|
||||
<string name="player_speed_text_format" formatted="true">रफ्तार (%.2fx)</string>
|
||||
<string name="new_update_format" formatted="true">नया अपडेट आया है!
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
|
||||
<string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
|
||||
|
@ -120,7 +119,7 @@
|
|||
<string name="continue_watching">Nastavite s gledanjem</string>
|
||||
<string name="action_remove_watching">Makni</string>
|
||||
<string name="action_open_watching">Više informacija</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Za ispravan rad ovog pružatelja usluga može biti potreban VPN</string>
|
||||
<string name="vpn_torrent">Ovaj pružatelj usluga je torrent, preporučuje se VPN</string>
|
||||
<string name="provider_info_meta">Stranica ne daje metapodatke, učitavanje videozapisa neće uspjeti ako ne postoji na stranici.</string>
|
||||
|
@ -139,16 +138,16 @@
|
|||
<string name="eigengraumode_settings">Eigengravy način</string>
|
||||
<string name="eigengraumode_settings_des">Dodaje opciju brzine u playeru</string>
|
||||
<string name="swipe_to_seek_settings">Prijeđi prstom za traženje</string>
|
||||
<string name="swipe_to_seek_settings_des">Prijeđite prstom ulijevo ili udesno kako biste kontrolirali player</string>
|
||||
<string name="swipe_to_seek_settings_des">Prijeđi prstom ulijevo ili udesno za kontrolu vremena u videoplayeru</string>
|
||||
<string name="swipe_to_change_settings">Klizni za promjenu postavki</string>
|
||||
<string name="swipe_to_change_settings_des">Kliznite prstom ulijevo ili udesno za promjenu svjetline ili glasnoće</string>
|
||||
<string name="swipe_to_change_settings_des">Prijeđi prstom ulijevo ili udesno za promjenu svjetline ili glasnoće</string>
|
||||
<string name="autoplay_next_settings">Automatski započni sljedeću epizodu</string>
|
||||
<string name="autoplay_next_settings_des">Započne sljedeću epizodu kad trenutna završi</string>
|
||||
<string name="double_tap_to_seek_settings">Dodirni dvaput za traženje</string>
|
||||
<string name="double_tap_to_pause_settings">Dodirni dvaput za pauziranje</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Iznos preskakanja u playeru (Sekunde)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Iznos preskakanja u playeru</string>
|
||||
<string name="double_tap_to_seek_settings_des">Dvaput dodirni desnu ili lijevu stranu ekrana za pomicanje naprijed ili natrag</string>
|
||||
<string name="double_tap_to_pause_settings_des">Dodirnite dvaput u sredinu zaslona za pauziranje</string>
|
||||
<string name="double_tap_to_pause_settings_des">Dodirni u sredinu zaslona za pauziranje</string>
|
||||
<string name="use_system_brightness_settings">Koristi svijetlinu u sustavu</string>
|
||||
<string name="use_system_brightness_settings_des">Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog preklopa</string>
|
||||
<string name="episode_sync_settings">Ažuriraj napredak gledanja</string>
|
||||
|
@ -174,7 +173,7 @@
|
|||
<string name="pref_filter_search_quality">Sakrij odabranu kvalitetu videozapisa u rezultatima pretraživanja</string>
|
||||
<string name="automatic_plugin_updates">Automatsko ažuriranje dodataka</string>
|
||||
<string name="updates_settings">Prikaži ažuriranja aplikacije</string>
|
||||
<string name="updates_settings_des">Automatski traži nova ažuriranja nakon pokretanja aplikacije</string>
|
||||
<string name="updates_settings_des">Automatski traži nova ažuriranja pri pokretanju aplikacije</string>
|
||||
<string name="uprereleases_settings">Ažuriranje na predizdanja</string>
|
||||
<string name="uprereleases_settings_des">Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -239,8 +238,8 @@
|
|||
<string name="movies_singular">Film</string>
|
||||
<string name="tv_series_singular">Serija</string>
|
||||
<string name="cartoons_singular">Crtić</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Dokumentarac</string>
|
||||
<string name="asian_drama_singular">Azijska drama</string>
|
||||
|
@ -300,7 +299,7 @@
|
|||
<string name="legal_notice_text" translatable="false">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.</string>
|
||||
<string name="category_general">Općenito</string>
|
||||
<string name="random_button_settings">Random gumb</string>
|
||||
<string name="random_button_settings_desc">Prikazuje gumb na početnoj stranici koji može odabrati nasumični film ili TV seriju s početne stranice</string>
|
||||
<string name="random_button_settings_desc">Prikaži random gumb na početnoj stranici</string>
|
||||
<string name="provider_lang_settings">Jezici pružatelja usluga</string>
|
||||
<string name="app_layout">Izgled aplikacije</string>
|
||||
<string name="preferred_media_settings">Preferirani mediji</string>
|
||||
|
@ -532,27 +531,4 @@
|
|||
<string name="empty_library_logged_in_message">Čini se da je ova lista prazna, pokušajte se prebaciti na drugu</string>
|
||||
<string name="safe_mode_file">Pronađena datoteka sigurnog načina rada!
|
||||
\nNe učitavaju se ekstenzije pri pokretanju dok se datoteka ne ukloni.</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Prikazan player- iznos preskakanja</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Količina preskakanja koja se koristi kada je player vidljiv</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Player skriven - Količina preskakanja</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Količina preskakanja koja se koristi kada je player skriven</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="test_passed">Prošlo</string>
|
||||
<string name="restart">Restart</string>
|
||||
<string name="test_log">Log</string>
|
||||
<string name="start">Početak</string>
|
||||
<string name="test_failed">Neuspješno</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="category_provider_test">Test pružatelja usluga</string>
|
||||
<string name="subscription_in_progress_notification">Ažuriram pretplaćene serije</string>
|
||||
<string name="subscription_episode_released">Epizoda %d izbačena!</string>
|
||||
<string name="subscription_list_name">Pretplaćeno</string>
|
||||
<string name="subscription_new">Pretplaćen na %s</string>
|
||||
<string name="subscription_deleted">Otkazana pretplata sa %s</string>
|
||||
<string name="revert">Vraćanje</string>
|
||||
<string name="pref_category_bypass">ISP zaobilaznice</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
|
||||
<string name="jsdelivr_enabled">Neuspješno dohvaćanje GitHuba, omogućavanje jsdelivr proxyja.</string>
|
||||
<string name="jsdelivr_proxy_summary">Koristeći jsdelivr, GitHub blokiranje se može zaobići. Može odgoditi ažuriranja za nekoliko dana.</string>
|
||||
<string name="watch_quality_pref_data">Preferirana kvaliteta gledanja (podatkovna mobilna mreža)</string>
|
||||
</resources>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cast_format" formatted="true">Stáblista: %s</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dn %dó%dp</string>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<string name="result_open_in_browser">Megnyitás böngészőben</string>
|
||||
<string name="skip_loading">Betöltés kihagyása</string>
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="type_watching">Nézés</string>
|
||||
<string name="type_completed">Befejezve</string>
|
||||
<string name="type_plan_to_watch">Később megnézés</string>
|
||||
|
@ -111,7 +111,7 @@
|
|||
<string name="subs_import_text" formatted="true">Betűtípusok importálása %s</string>
|
||||
<string name="action_remove_watching">Eltávolítás</string>
|
||||
<string name="action_open_watching">Több információ</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">VPN szükséges lehet ehhez a szolgáltató megfelelő működéséhez</string>
|
||||
<string name="vpn_torrent">Ez a szolgáltató torrent, VPN ajánlott</string>
|
||||
<string name="torrent_plot">Leírás</string>
|
||||
|
@ -172,11 +172,11 @@
|
|||
<string name="ova">OVA</string>
|
||||
<string name="others">Egyebek</string>
|
||||
<string name="tv_series_singular">Sorozat</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="source_error">Forráshiba</string>
|
||||
<string name="nsfw">NSFW</string>
|
||||
<string name="cartoons_singular">Rajzfilm</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="live_singular">Élőadás</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="other_singular">Videó</string>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Pemeran: %s</string>
|
||||
|
@ -36,7 +35,7 @@
|
|||
<string name="skip_loading">Skip Loading</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="type_watching">Sedang Menonton</string>
|
||||
<string name="type_on_hold">Tertunda</string>
|
||||
<string name="type_on_hold">Tertahan</string>
|
||||
<string name="type_completed">Selesai</string>
|
||||
<string name="type_dropped">Dihentikan</string>
|
||||
<string name="type_plan_to_watch">Rencana untuk Menonton</string>
|
||||
|
@ -102,7 +101,7 @@
|
|||
<string name="continue_watching">Lanjutkan Menonton</string>
|
||||
<string name="action_remove_watching">Hapus</string>
|
||||
<string name="action_open_watching">Info lebih lanjut</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Sebuah VPN mungkin diperlukan agar provider ini bisa bekerja dengan benar</string>
|
||||
<string name="vpn_torrent">Provider ini adalah sebuah torrent, VPN direkomendasikan</string>
|
||||
<string name="provider_info_meta">Metadata tidak disediakan oleh situs, loading video akan gagal jika tidak ada di situs.</string>
|
||||
|
@ -121,14 +120,14 @@
|
|||
<string name="eigengraumode_settings">Mode Eigengravy</string>
|
||||
<string name="eigengraumode_settings_des">Menambahkan opsi kecepatan di pemutar</string>
|
||||
<string name="swipe_to_seek_settings">Geser untuk mengubah waktu</string>
|
||||
<string name="swipe_to_seek_settings_des">Geser dari sisi ke sisi untuk mengontrol posisi dalam video</string>
|
||||
<string name="swipe_to_seek_settings_des">Geser ke kiri atau kanan untuk mengontrol waktu di pemutar video</string>
|
||||
<string name="swipe_to_change_settings">Geser untuk mengubah pengaturan</string>
|
||||
<string name="swipe_to_change_settings_des">Geser ke atas atau ke bawah di sisi kiri atau kanan untuk mengubah kecerahan atau volume</string>
|
||||
<string name="swipe_to_change_settings_des">Geser ke sisi kiri atau kanan untuk mengubah pencerahan atau volume</string>
|
||||
<string name="double_tap_to_seek_settings">Tekan dua kali untuk mengubah waktu</string>
|
||||
<string name="double_tap_to_pause_settings">Tekan dua kali untuk menjeda</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Jumlah pengubah waktu pemutar (Detik)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Jumlah pengubah waktu pemutar</string>
|
||||
<string name="double_tap_to_seek_settings_des">Tekan dua kali di sisi kanan atau kiri untuk mengubah waktu ke depan atau ke belakang</string>
|
||||
<string name="double_tap_to_pause_settings_des">Tekan dua kali di tengah untuk menjeda</string>
|
||||
<string name="double_tap_to_pause_settings_des">Tekan di tengah untuk menjeda</string>
|
||||
<string name="use_system_brightness_settings">Gunakan pencerahan sistem</string>
|
||||
<string name="use_system_brightness_settings_des">Gunakan pencerahan sistem di pemutar aplikasi dari pada hamparan gelap</string>
|
||||
<string name="episode_sync_settings">Update progres tontonan</string>
|
||||
|
@ -150,7 +149,7 @@
|
|||
<string name="bug_report_settings_on">Tidak mengirim data</string>
|
||||
<string name="show_fillers_settings">Tampilkan episode filler untuk anime</string>
|
||||
<string name="updates_settings">Tampilkan update aplikasi</string>
|
||||
<string name="updates_settings_des">Secara otomatis mencari update terbaru setelah aplikasi dibuka.</string>
|
||||
<string name="updates_settings_des">Secara otomatis mencari update terbaru saat aplikasi dibuka</string>
|
||||
<string name="uprereleases_settings">Update ke prarilis</string>
|
||||
<string name="uprereleases_settings_des">Hanya mencari update prarilis daripada rilis penuh</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -210,8 +209,8 @@
|
|||
<string name="movies_singular">Movie</string>
|
||||
<string name="tv_series_singular">Seri</string>
|
||||
<string name="cartoons_singular">Kartun</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Film Dokumenter</string>
|
||||
<string name="asian_drama_singular">Drama Asia</string>
|
||||
|
@ -244,7 +243,7 @@
|
|||
<string name="dont_show_again">Jangan tunjukkan lagi</string>
|
||||
<string name="skip_update">Skip Update ini</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="watch_quality_pref">Kualitas tontonan yang lebih diinginkan (WIFI)</string>
|
||||
<string name="watch_quality_pref">Kualitas tontonan yang lebih diinginkan</string>
|
||||
<string name="limit_title">Karakter maksimal judul pemutar video</string>
|
||||
<string name="limit_title_rez">Resolusi pemutar video</string>
|
||||
<string name="video_buffer_size_settings">Ukuran buffer video</string>
|
||||
|
@ -265,7 +264,7 @@
|
|||
<string name="legal_notice_text" translatable="false">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.</string>
|
||||
<string name="category_general">Umum</string>
|
||||
<string name="random_button_settings">Tombol Acak</string>
|
||||
<string name="random_button_settings_desc">Tampilkan tombol di halaman utama yang dapat memilih seri film atau TV acak dari halaman utama</string>
|
||||
<string name="random_button_settings_desc">Tampilkan tombol acak di Beranda</string>
|
||||
<string name="provider_lang_settings">Bahasa provider</string>
|
||||
<string name="app_layout">Tata Letak Aplikasi</string>
|
||||
<string name="preferred_media_settings">Media yang lebih diinginkan</string>
|
||||
|
@ -388,7 +387,7 @@
|
|||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="nsfw">17+</string>
|
||||
<string name="others">Lainnya</string>
|
||||
<string name="other_singular">Video</string>
|
||||
<string name="other_singular">Vidio</string>
|
||||
<string name="add_site_pref">Duplikasi Website</string>
|
||||
<string name="add_site_summary">Duplikasi website yang telah ada, dengan alamat berbeda</string>
|
||||
<string name="pref_category_links">Tautan</string>
|
||||
|
@ -396,7 +395,7 @@
|
|||
<string name="pref_category_backup">Cadangkan</string>
|
||||
<string name="pref_category_extensions">Fitur Tambahan</string>
|
||||
<string name="play_with_app_name">Putar di CloudStream</string>
|
||||
<string name="pref_filter_search_quality">Sembunyikan kualitas video terpilih di pencarian</string>
|
||||
<string name="pref_filter_search_quality">Sembunyikan kualitas vidio terpilih di pencarian</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="livestreams">Siaran langsung</string>
|
||||
<string name="remove_site_pref">Hapus Website</string>
|
||||
|
@ -445,7 +444,7 @@
|
|||
<string name="extension_rating" formatted="true">Peringkat: %s</string>
|
||||
<string name="extension_authors">Pembuat</string>
|
||||
<string name="extension_language">Bahasa</string>
|
||||
<string name="player_pref">Pemutar video utama</string>
|
||||
<string name="player_pref">Pemutar vidio utama</string>
|
||||
<string name="player_settings_play_in_app">Pemutar Bawaan</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
|
@ -476,7 +475,7 @@
|
|||
<string name="subtitles_remove_captions">Hapus teks tertutup dari subtitel</string>
|
||||
<string name="subtitles_remove_bloat">Hapus karakter sampah dari subtitel</string>
|
||||
<string name="audio_tracks">Audio Trek</string>
|
||||
<string name="video_tracks">Video Trek</string>
|
||||
<string name="video_tracks">Vidio Trek</string>
|
||||
<string name="extension_types">Dukungan</string>
|
||||
<string name="hls_playlist">Daftar putar HLS</string>
|
||||
<string name="apk_installer_settings">Penginstal APK</string>
|
||||
|
@ -530,27 +529,4 @@
|
|||
<string name="empty_library_logged_in_message">Yahh daftar ini kosong, coba ganti ke yang lain</string>
|
||||
<string name="safe_mode_file">Mode aman file ditemukan!
|
||||
\nTidak memuat ekstensi pada startup sampai berkas dihapus.</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Sembunyikan Pemutaran - Geser</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Pemutar terlihat - Geser</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Geser untuk menghilangkan</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Geser untuk menghilangkan</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="test_log">Log</string>
|
||||
<string name="test_passed">Berhasil</string>
|
||||
<string name="category_provider_test">Tes provider</string>
|
||||
<string name="stop">Berhenti</string>
|
||||
<string name="start">Mulai</string>
|
||||
<string name="restart">Mulai lagi</string>
|
||||
<string name="test_failed">Gagal</string>
|
||||
<string name="subscription_in_progress_notification">Memperbarui acara langganan</string>
|
||||
<string name="subscription_list_name">Berlangganan</string>
|
||||
<string name="subscription_new">Berlangganan ke %s</string>
|
||||
<string name="subscription_deleted">Berhenti berlangganan di %s</string>
|
||||
<string name="subscription_episode_released">Episode %d telah rilis!</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proksi</string>
|
||||
<string name="jsdelivr_enabled">Gagal mencapai GitHub, mengaktifkan proksi jsdelivr.</string>
|
||||
<string name="jsdelivr_proxy_summary">Mengunakan jsdelivers, bisa melewati pemblokiran GitHub. Mungkin dapat menyebabkan pembaruan tertunda dalam beberapa hari.</string>
|
||||
<string name="pref_category_bypass">Bypass ISP</string>
|
||||
<string name="revert">Pulihkan</string>
|
||||
<string name="watch_quality_pref_data">Nonton dengan kualitas yang di inginkan (Data Seluler)</string>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Cast: %s</string>
|
||||
|
@ -109,7 +108,7 @@
|
|||
<string name="continue_watching">Continua a guardare</string>
|
||||
<string name="action_remove_watching">Rimuovi</string>
|
||||
<string name="action_open_watching">Più info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Potrebbe essere necessaria una VPN per far funzionare correttamente questo provider</string>
|
||||
<string name="vpn_torrent">Questo provider è un torrent, si raccomanda una VPN</string>
|
||||
<string name="provider_info_meta">I metadati non sono forniti dal sito, il caricamento del video fallirà se non esiste sul sito.</string>
|
||||
|
@ -266,7 +265,7 @@
|
|||
<string name="dont_show_again">Non mostrare di nuovo</string>
|
||||
<string name="skip_update">Salta questo aggiornamento</string>
|
||||
<string name="update">Aggiorna</string>
|
||||
<string name="watch_quality_pref">Qualità di visualizzazione preferita (WiFi)</string>
|
||||
<string name="watch_quality_pref">Risoluzione preferita</string>
|
||||
<string name="limit_title">Limita i caratteri del titolo nel player</string>
|
||||
<string name="limit_title_rez">Risoluzione video player</string>
|
||||
<string name="video_buffer_size_settings">Dimensione cache video</string>
|
||||
|
@ -529,27 +528,4 @@
|
|||
<string name="empty_library_logged_in_message">Sembra che questa lista sia vuota, prova a passare a un\'altra</string>
|
||||
<string name="safe_mode_file">File \"safe mode\" trovato!
|
||||
\nAll\'avvio non sarà caricata alcuna estensione finchè il file non verrà rimosso.</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Quantità di ricerca usata quando il player è nascosto</string>
|
||||
<string name="pref_category_android_tv">TV Android</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Quantità di ricerca usata quando il player è visibile</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Player visibile - Quantità di ricerca</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Player nascosto - Quantità di ricerca</string>
|
||||
<string name="test_log">Registro</string>
|
||||
<string name="start">Avvia</string>
|
||||
<string name="category_provider_test">Test del provider</string>
|
||||
<string name="restart">Riavvia</string>
|
||||
<string name="stop">Ferma</string>
|
||||
<string name="test_passed">Superato</string>
|
||||
<string name="test_failed">Fallito</string>
|
||||
<string name="jsdelivr_proxy">Proxy raw.githubusercontent.com</string>
|
||||
<string name="subscription_deleted">Disiscritto da %s</string>
|
||||
<string name="subscription_list_name">Iscritto</string>
|
||||
<string name="subscription_new">Iscritto a %s</string>
|
||||
<string name="jsdelivr_enabled">Impossibile contattare GitHub, abilitazione proxy jsdelivr avviata.</string>
|
||||
<string name="jsdelivr_proxy_summary">Bypassa il blocco di GitHub utilizzando jsdelivr, potrebbe causare un ritardo di alcuni giorni.</string>
|
||||
<string name="pref_category_bypass">Baypass ISP</string>
|
||||
<string name="revert">Ripristina</string>
|
||||
<string name="subscription_in_progress_notification">Aggiornando shows a cui sei iscritto</string>
|
||||
<string name="subscription_episode_released">L\'episodio %d è stato rilasciato!</string>
|
||||
<string name="watch_quality_pref_data">Qualità di visualizzazione preferita (Dati mobili)</string>
|
||||
</resources>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="preview_background_img_des">הרקע של ההצגה לפני</string>
|
||||
<string name="cast_format" formatted="true">צוות שחקנים: %s</string>
|
||||
|
@ -116,7 +116,7 @@
|
|||
<string name="chromecast_subtitles_settings">כתוביות כרומקאסט</string>
|
||||
<string name="picture_in_picture_des">ממשיך ניגון בנגן מינימלי מעל ישומים אחרים</string>
|
||||
<string name="player_subtitles_settings">כתוביות</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="history">היסטוריה</string>
|
||||
<string name="apk_installer_legacy">מורשת</string>
|
||||
<string name="no">לא</string>
|
||||
|
@ -164,8 +164,8 @@
|
|||
<string name="used_storage">משומש</string>
|
||||
<string name="tv_series">סדרת טלוויזיה</string>
|
||||
<string name="cartoons">סדרות/סרטים מצוירים</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/אנימה</string>
|
||||
<string name="ova_singular">\@string/אנימציית וידאו מקורית</string>
|
||||
<string name="asian_drama_singular">דרמה אסייתית</string>
|
||||
<string name="episode_action_chromecast_episode">כרומקאסט את הפרק</string>
|
||||
<string name="episode_action_chromecast_mirror">כרומקאסט את המראה</string>
|
||||
|
|
|
@ -1,185 +0,0 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="next_episode_time_min_format" formatted="true">%d分</string>
|
||||
<string name="title_downloads">ダウンロード</string>
|
||||
<string name="title_search">検索</string>
|
||||
<string name="title_settings">設定</string>
|
||||
<string name="result_share">シェア</string>
|
||||
<string name="movies">映画</string>
|
||||
<string name="title_home">ホーム</string>
|
||||
<string name="library">ライブラリ</string>
|
||||
<string name="home_play">再生</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%d日 %d時間%d分</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%d時間%d分</string>
|
||||
<string name="search_hint">検索…</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="home_info">情報</string>
|
||||
<string name="season">シーズン</string>
|
||||
<string name="trailer">予告編</string>
|
||||
<string name="tv_series_singular">シリーズ</string>
|
||||
<string name="episodes">エピソード</string>
|
||||
<string name="player_speed_text_format" formatted="true">再生速度 (%.2fx)</string>
|
||||
<string name="next_episode">次のエピソード</string>
|
||||
<string name="sort_apply">適用</string>
|
||||
<string name="category_account">アカウント</string>
|
||||
<string name="cartoons">カートゥーン</string>
|
||||
<string name="tv_series">TVシリーズ</string>
|
||||
<string name="torrent">トレント</string>
|
||||
<string name="documentaries">ドキュメンタリー</string>
|
||||
<string name="ova">OVA</string>
|
||||
<string name="asian_drama">アジアドラマ</string>
|
||||
<string name="livestreams">ライブ配信</string>
|
||||
<string name="movies_singular">映画</string>
|
||||
<string name="others">その他</string>
|
||||
<string name="cartoons_singular">カートゥーン</string>
|
||||
<string name="torrent_singular">トレント</string>
|
||||
<string name="documentaries_singular">ドキュメンタリー</string>
|
||||
<string name="asian_drama_singular">アジアドラマ</string>
|
||||
<string name="live_singular">ライブ配信</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="sort_cancel">キャンセル</string>
|
||||
<string name="anime">アニメ</string>
|
||||
<string name="video_lock">ロック</string>
|
||||
<string name="video_source">ソース</string>
|
||||
<string name="nsfw">NSFW</string>
|
||||
<string name="clear_history">履歴を削除</string>
|
||||
<string name="continue_watching">視聴中コンテンツ</string>
|
||||
<string name="category_general">全般</string>
|
||||
<string name="other_singular">動画</string>
|
||||
<string name="category_player">プレーヤー</string>
|
||||
<string name="type_plan_to_watch">懐う</string>
|
||||
<string name="play_trailer_button">予告編を再生</string>
|
||||
<string name="episode_short">エピソード</string>
|
||||
<string name="type_watching">視聴</string>
|
||||
<string name="result_tags">ジャンル</string>
|
||||
<string name="play_movie_button">映画を再生</string>
|
||||
<string name="pick_subtitle">字幕</string>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="play_with_app_name">CloudStreamで再生</string>
|
||||
<string name="browser">ブラウザ</string>
|
||||
<string name="type_completed">完成</string>
|
||||
<string name="type_dropped">放置</string>
|
||||
<string name="type_on_hold">保留</string>
|
||||
<string name="loading">ローディング…</string>
|
||||
<string name="result_open_in_browser">ブラウザで開く</string>
|
||||
<string name="season_short">シーズン</string>
|
||||
<string name="resume_time_left" formatted="true">残り
|
||||
\n%d分</string>
|
||||
<string name="play_episode">再生エピソード</string>
|
||||
<string name="downloaded">ダウンロード済</string>
|
||||
<string name="pref_category_backup">バックアップ</string>
|
||||
<string name="home_source">ソース</string>
|
||||
<string name="history">履歴</string>
|
||||
<string name="result_poster_img_des">ポスター</string>
|
||||
<string name="type_none">なし</string>
|
||||
<string name="sort_copy">コピー</string>
|
||||
<string name="sort_close">閉じる</string>
|
||||
<string name="sort_save">保存</string>
|
||||
<string name="sort_clear">消去</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%sエピ%d</string>
|
||||
<string name="cast_format" formatted="true">出演者:%s</string>
|
||||
<string name="search_poster_img_des">ポスター</string>
|
||||
<string name="episode_poster_img_des">エピソードポスター</string>
|
||||
<string name="home_main_poster_img_des">主要ポスター</string>
|
||||
<string name="home_next_random_img_des">次のランダム</string>
|
||||
<string name="go_back_img_des">戻り</string>
|
||||
<string name="rated_format" formatted="true">視聴率 %.1f</string>
|
||||
<string name="new_update_format" formatted="true">新しいアップデートを発見!
|
||||
\n%s -> %s</string>
|
||||
<string name="duration_format" formatted="true">%d分</string>
|
||||
<string name="search_hint_site" formatted="true">%sを検索…</string>
|
||||
<string name="pick_source">ソース</string>
|
||||
<string name="filler" formatted="true">ろくごうきじ</string>
|
||||
<string name="reload_error">接続を再試行…</string>
|
||||
<string name="go_back">戻り</string>
|
||||
<string name="action_remove_from_bookmarks">削除</string>
|
||||
<string name="home_more_info">詳細情報</string>
|
||||
<string name="home_expanded_hide">閉じる</string>
|
||||
<string name="category_updates">アップデート・バックアップ</string>
|
||||
<string name="app_language">アプリ言語</string>
|
||||
<string name="github">GitHub(ギットハブ)</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="go_forward_30">+30</string>
|
||||
<string name="legal_notice">免責</string>
|
||||
<string name="pref_category_extensions">拡張機能</string>
|
||||
<string name="pref_category_app_updates">アプリ更新</string>
|
||||
<string name="category_providers">提供者</string>
|
||||
<string name="pref_category_subtitles">字幕</string>
|
||||
<string name="pref_category_ui_features">特徴</string>
|
||||
<string name="pref_category_defaults">デフォルト</string>
|
||||
<string name="automatic">自動</string>
|
||||
<string name="home_random">任意</string>
|
||||
<string name="extensions">拡張機能</string>
|
||||
<string name="pref_category_links">リンク</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="login">ログイン</string>
|
||||
<string name="logout">ログアウト</string>
|
||||
<string name="max">最大</string>
|
||||
<string name="min">最小</string>
|
||||
<string name="none">なし</string>
|
||||
<string name="next">次</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="no">否</string>
|
||||
<string name="open_with">で開く</string>
|
||||
<string name="episode">エピソード</string>
|
||||
<string name="duration">時間</string>
|
||||
<string name="synopsis">概要</string>
|
||||
<string name="site">サイト</string>
|
||||
<string name="used_storage">使用</string>
|
||||
<string name="app_storage">アプリ</string>
|
||||
<string name="action_open_watching">詳細情報</string>
|
||||
<string name="action_remove_watching">削除</string>
|
||||
<string name="picture_in_picture">ピクチャーインピクチャー</string>
|
||||
<string name="player_subtitles_settings">字幕</string>
|
||||
<string name="settings_info">情報</string>
|
||||
<string name="pause">一時停止</string>
|
||||
<string name="play_episode_toast">再生エピソード</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="start">開始</string>
|
||||
<string name="status">状態</string>
|
||||
<string name="year">年</string>
|
||||
<string name="resume">再開</string>
|
||||
<string name="test_failed">失敗</string>
|
||||
<string name="test_passed">合格</string>
|
||||
<string name="free_storage">空き</string>
|
||||
<string name="status_completed">完成</string>
|
||||
<string name="status_ongoing">進行中</string>
|
||||
<string name="normal">デフォルト</string>
|
||||
<string name="player_settings_play_in_browser">ウェブブラウザ</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
<string name="extension_language">言語</string>
|
||||
<string name="extension_authors">作成者</string>
|
||||
<string name="extension_size">サイズ</string>
|
||||
<string name="extension_status">状態</string>
|
||||
<string name="extension_version">バージョン</string>
|
||||
<string name="extension_rating" formatted="true">視聴率 %s</string>
|
||||
<string name="rating">視聴率</string>
|
||||
<string name="default_subtitles">デフォルト</string>
|
||||
<string name="download_failed">ダウンロード失敗</string>
|
||||
<string name="download_started">ダウンロード開始</string>
|
||||
<string name="download_done">ダウンロード完了</string>
|
||||
<string name="download_canceled">ダウンロード終了</string>
|
||||
<string name="stream">ストリーム</string>
|
||||
<string name="update_started">アップデート開始</string>
|
||||
<string name="no_season">シーズンなし</string>
|
||||
<string name="no_subtitles">字幕なし</string>
|
||||
<string name="video_aspect_ratio_resize">アスペクト比</string>
|
||||
<string name="skip_loading">ロードをスキップする</string>
|
||||
<string name="episode_more_options_des">その他のオプション</string>
|
||||
<string name="no_data">データなし</string>
|
||||
<string name="downloading">ダウンロード中</string>
|
||||
<string name="error_bookmarks_text">ブックマーク</string>
|
||||
<string name="download_storage_text">内部記憶装置</string>
|
||||
<string name="download_paused">ダウンロードが一時停止</string>
|
||||
<string name="provider_info_meta">メタデータはこのサイトでは提供されません。メタデータがサイト上に存在しない場合、ビデオの読み込みに失敗します。</string>
|
||||
<string name="torrent_plot">記述</string>
|
||||
<string name="show_log_cat">Logcat 🐈を表示</string>
|
||||
<string name="test_log">ログ</string>
|
||||
<string name="search">検索</string>
|
||||
<string name="discord">Discordに参加</string>
|
||||
<string name="update">アップデート</string>
|
||||
<string name="check_for_update">アップデートを確認</string>
|
||||
<string name="show_title">作品名</string>
|
||||
<string name="update_notification_installing">アプリのアップデートをインストール中…</string>
|
||||
</resources>
|
|
@ -1,128 +1,3 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%sಎಪಿ%d</string>
|
||||
<string name="cast_format" formatted="true">ಕ್ಯಾಸ್ಟ್:%s</string>
|
||||
<string name="go_back_img_des">ಹಿಂದೆ ಹೋಗು</string>
|
||||
<string name="filler" formatted="true">ಫಿಲ್ಲರ್</string>
|
||||
<string name="title_search">ಹುಡುಕು</string>
|
||||
<string name="title_downloads">ಡೌನ್ಲೋಡ್</string>
|
||||
<string name="subs_font">ಫಾಂಟ್</string>
|
||||
<string name="search_provider_text_providers">ಪೂರೈಕೆದಾರರನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ</string>
|
||||
<string name="search_provider_text_types">ಪ್ರಕಾರಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ</string>
|
||||
<string name="benene_count_text_none">ಯಾವುದೇ ಬೆನೆನ್ಸ್ ನೀಡಿಲ್ಲ</string>
|
||||
<string name="subs_auto_select_language">ಸ್ವಯಂ-ಆಯ್ಕೆ ಭಾಷೆ</string>
|
||||
<string name="action_open_watching">ಹೆಚ್ಚಿನ ಮಾಹಿತಿ</string>
|
||||
<string name="action_open_play">\@ಸ್ಟ್ರಿಂಗ್/ಹೋಮ್_ಪ್ಲೇ</string>
|
||||
<string name="vpn_might_be_needed">ಈ ಪೂರೈಕೆದಾರರು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡಲು VPN ಬೇಕಾಗಬಹುದು</string>
|
||||
<string name="player_size_settings_des">ಕಪ್ಪು ಗಡಿಗಳನ್ನು ತೆಗೆದುಹಾಕಿ</string>
|
||||
<string name="next_episode_format" formatted="true">ಸಂಚಿಕೆ%d ಬಿಡುಗಡೆಯಾಗಲಿದೆ</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dh %dm</string>
|
||||
<string name="result_poster_img_des">ಪೋಸ್ಟರ್</string>
|
||||
<string name="search_poster_img_des">ಪೋಸ್ಟರ್</string>
|
||||
<string name="episode_poster_img_des">ಸಂಚಿಕೆ ಪೋಸ್ಟರ್</string>
|
||||
<string name="home_main_poster_img_des">ಮೇನ್ ಪೋಸ್ಟರ್</string>
|
||||
<string name="update_started">ಅಪ್ಡೇಟ್ ಪ್ರಾರಂಭವಾಗಿದೆ</string>
|
||||
<string name="error_loading_links_toast">ಲೋಡಿಂಗ್ ಲಿಂಕ್ ಎರರ್ ಬಂದಿದೆ</string>
|
||||
<string name="download_storage_text">ಇಂಟರ್ನಲ್ ಸ್ಟೋರೇಜ್</string>
|
||||
<string name="app_dubbed_text">ಡಬ್</string>
|
||||
<string name="app_subbed_text">ಸಬ್</string>
|
||||
<string name="pref_disable_acra">ಸ್ವಯಂಚಾಲಿತ ದೋಷ ವರದಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ</string>
|
||||
<string name="home_expanded_hide">ಹೈಡ್</string>
|
||||
<string name="home_play">ಪ್ಲೇ</string>
|
||||
<string name="home_info">ಮಾಹಿತಿ</string>
|
||||
<string name="action_add_to_bookmarks">ಸೆಟ್ ವಾಚ್ ಸ್ಟೇಟಸ್</string>
|
||||
<string name="sort_apply">ಅನ್ವಯಿಸು</string>
|
||||
<string name="sort_cancel">ರದ್ದುಮಾಡು</string>
|
||||
<string name="subs_subtitle_elevation">ಸಬ್ ಟೈಟಲ್ಸ್ ಎಲೆವಷನ್</string>
|
||||
<string name="subs_font_size">ಫಾಂಟ್ ಸೈಜ್</string>
|
||||
<string name="subs_subtitle_languages">ಸಬ್ ಟೈಟಲ್ಸ್ ಭಾಷೆ</string>
|
||||
<string name="action_remove_watching">ತೆಗೆದುಹಾಕಿ</string>
|
||||
<string name="vpn_torrent">ಈ ಪೂರೈಕೆದಾರರು ಟೊರೆಂಟ್ ಆಗಿದೆ, VPN ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="normal_no_plot">ಯಾವುದೇ ಪ್ಲಾಟ್ ಕಂಡುಬಂದಿಲ್ಲ</string>
|
||||
<string name="show_log_cat">ಲಾಗ್ಕ್ಯಾಟ್ 🐈 ತೋರಿಸಿ</string>
|
||||
<string name="test_log">ಲಾಗ್</string>
|
||||
<string name="picture_in_picture">ಚಿತ್ರದಲ್ಲಿ-ಚಿತ್ರದಲ್ಲಿ</string>
|
||||
<string name="player_size_settings">ಪ್ಲೇಯರ್ ಮರುಗಾತ್ರಗೊಳಿಸಿ ಬಟನ್</string>
|
||||
<string name="player_subtitles_settings">ಸಬ್ ಟೈಟಲ್ಸ್</string>
|
||||
<string name="player_subtitles_settings_des">ಪ್ಲೇಯರ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಗಳು</string>
|
||||
<string name="chromecast_subtitles_settings_des">ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್</string>
|
||||
<string name="go_back">ಹಿಂದೆ ಹೋಗು</string>
|
||||
<string name="popup_pause_download">ಡೌನ್ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಿ</string>
|
||||
<string name="error_bookmarks_text">ಬುಕ್ಮಾರ್ಕ್</string>
|
||||
<string name="subs_background_color">ಬ್ಯಾಕ್ ಗ್ರೌಂಡ್ ಕಲರ್</string>
|
||||
<string name="benene_count_text">%d ಡೇವ್ಗಳಿಗೆ ಬೆನೆನೆಸ್ ನೀಡಲಾಗಿದೆ</string>
|
||||
<string name="subs_hold_to_reset_to_default">ಡೀಫಾಲ್ಟ್ಗೆ ಮರುಹೊಂದಿಸಲು ಹಿಡಿದುಕೊಳ್ಳಿ</string>
|
||||
<string name="provider_info_meta">ಸೈಟ್ನಿಂದ ಮೆಟಾಡೇಟಾವನ್ನು ಒದಗಿಸಲಾಗಿಲ್ಲ, ಅದು ಸೈಟ್ನಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲದಿದ್ದರೆ ವೀಡಿಯೊ ಲೋಡಿಂಗ್ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ.</string>
|
||||
<string name="picture_in_picture_des">ಇತರ ಅಪ್ಲಿಕೇಶನ್ಗಳ ಮೇಲೆ ಚಿಕಣಿ ಪ್ಲೇಯರ್ನಲ್ಲಿ ಪ್ಲೇಬ್ಯಾಕ್ ಅನ್ನು ಮುಂದುವರಿಸುತ್ತದೆ</string>
|
||||
<string name="chromecast_subtitles_settings">ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್</string>
|
||||
<string name="rated_format" formatted="true">ರೇಟೆಡ್:%.1f</string>
|
||||
<string name="action_remove_from_bookmarks">ತೆಗೆದುಹಾಕಿ</string>
|
||||
<string name="popup_resume_download">ಡೌನ್ಲೋಡ್ ಅನ್ನು ಪುನರಾರಂಭಿಸಿ</string>
|
||||
<string name="sort_close">ಕ್ಲೋಸ್</string>
|
||||
<string name="sort_clear">ಕ್ಲಿಯರ್</string>
|
||||
<string name="sort_save">ಸೇವ್</string>
|
||||
<string name="subtitles_settings">ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್</string>
|
||||
<string name="popup_play_file">ಫೈಲ್ ಪ್ಲೇ</string>
|
||||
<string name="subs_text_color">ಟೆಕ್ಸ್ಟ್ ಕಲರ್</string>
|
||||
<string name="subs_outline_color">ಔಟ್ ಲೈನ್ ಕಲರ್</string>
|
||||
<string name="subs_window_color">ವಿಂಡೋ ಕಲರ್</string>
|
||||
<string name="subs_edge_type">ಎಡ್ಜ್ ಟೈಪ್</string>
|
||||
<string name="home_change_provider_img_des">ಪ್ರೊವೈಡರ್ ಬದಲಾಯಿಸಿ</string>
|
||||
<string name="duration_format" formatted="true">%dಮಿನ</string>
|
||||
<string name="torrent_plot">ವಿವರಣೆ</string>
|
||||
<string name="player_speed_text_format" formatted="true">ಸ್ಪೀಡ್(%.2fx)</string>
|
||||
<string name="title_home">ಹೋಂ</string>
|
||||
<string name="pick_subtitle">ಸಬ್ ಟೈಟಲ್ಸ್</string>
|
||||
<string name="title_settings">ಸೆಟ್ಟಿಂಗ್ಸ್</string>
|
||||
<string name="filter_bookmarks">ಬುಕ್ಮಾರ್ಕ್ಗಳನ್ನು ಫಿಲ್ಟರ್ ಮಾಡಿ</string>
|
||||
<string name="search_hint">ಹುಡುಕು…</string>
|
||||
<string name="play_movie_button">ಚಲನಚಿತ್ರವನ್ನು ಪ್ಲೇ ಮಾಡಿ</string>
|
||||
<string name="preview_background_img_des">ಪ್ರಿವ್ಯೂ ಹಿನ್ನೆಲೆ</string>
|
||||
<string name="next_episode">ಮುಂದಿನ ಸಂಚಿಕೆ</string>
|
||||
<string name="app_name">ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್</string>
|
||||
<string name="downloading">ಡೌನ್ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ</string>
|
||||
<string name="stream">ಸ್ಟ್ರೀಮ್</string>
|
||||
<string name="result_share">ಶೇರ್</string>
|
||||
<string name="popup_delete_file">ಫೈಲ್ ಅಳಿಸಿ</string>
|
||||
<string name="home_more_info">ಹೆಚ್ಚಿನ ಮಾಹಿತಿ</string>
|
||||
<string name="new_update_format" formatted="true">ಹೊಸ ಅಪ್ಡೇಟ್ ಬಂದಿದೆ
|
||||
\n%s-%s</string>
|
||||
<string name="loading">ಲೋಡಿಂಗ್…</string>
|
||||
<string name="subs_download_languages">ಡೌನ್ಲೋಡ್ ಭಾಷೆಗಳನ್ನು ಮಾಡಿ</string>
|
||||
<string name="play_livestream_button">ಲೈವ್ಸ್ಟ್ರೀಮ್ ಪ್ಲೇ ಮಾಡಿ</string>
|
||||
<string name="play_with_app_name">ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ ಇದರೊಂದಿಗೆ ಪ್ಲೇ ಮಾಡಿ</string>
|
||||
<string name="type_plan_to_watch">ವೀಕ್ಷಿಸಲು ಯೋಜನೆ</string>
|
||||
<string name="play_episode">ಸಂಚಿಕೆಯನ್ನು ಪ್ಲೇ ಮಾಡಿ</string>
|
||||
<string name="continue_watching">ಕಂಟಿನ್ಯೂ ವಾಟಚಿಂಗ್</string>
|
||||
<string name="torrent_no_plot">ಯಾವುದೇ ವಿವರಣೆ ಕಂಡುಬಂದಿಲ್ಲ</string>
|
||||
<string name="play_torrent_button">ಸ್ಟ್ರೀಮ್ ಟೊರೆಂಟ್</string>
|
||||
<string name="download">ಡೌನ್ಲೋಡ್</string>
|
||||
<string name="sort_copy">ಕಾಪಿ</string>
|
||||
<string name="no_data">ನೋ ಡೇಟಾ</string>
|
||||
<string name="player_speed">ಪ್ಲೇಯರ್ ಸ್ಪೀಡ್</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%d %dh %dm</string>
|
||||
<string name="search_hint_site" formatted="true">ಹುಡುಕು %s…</string>
|
||||
<string name="episode_more_options_des">ಹೆಚ್ಚಿನ ಆಯ್ಕೆ</string>
|
||||
<string name="subs_import_text" formatted="true">ಫಾಂಟ್ಗಳನ್ನು ಇರಿಸುವ ಮೂಲಕ ಆಮದು ಮಾಡಿ %s</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<string name="result_tags">ಪ್ರಕಾರಗಳು</string>
|
||||
<string name="result_open_in_browser">ಬ್ರೌಸರ್ ತೆರೆಯಿರಿ</string>
|
||||
<string name="type_on_hold">ಆನ್-ಹೋಲ್ಡ್</string>
|
||||
<string name="type_none">ನನ್</string>
|
||||
<string name="reload_error">ಸಂಪರ್ಕವನ್ನು ಮರುಪ್ರಯತ್ನಿಸಿ…</string>
|
||||
<string name="download_paused">ಡೌನ್ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ</string>
|
||||
<string name="download_failed">ಡೌನ್ಲೋಡ್ ವಿಫಲವಾಗಿದೆ</string>
|
||||
<string name="download_done">ಡೌನ್ಲೋಡ್ ಮುಗಿದಿದೆ</string>
|
||||
<string name="browser">ಬ್ರೌಸರ್</string>
|
||||
<string name="skip_loading">ಸ್ಕಿಪ್ ಲೋಡಿಂಗ್</string>
|
||||
<string name="type_watching">ವಾಚಿಂಗ್</string>
|
||||
<string name="type_completed">ಪೂರ್ಣಗೊಂಡಿದೆ</string>
|
||||
<string name="type_dropped">ಕೈಬಿಡಲಾಯಿತು</string>
|
||||
<string name="type_re_watching">ಪುನಃ ವೀಕ್ಷಿಸುತ್ತಿದೆ</string>
|
||||
<string name="play_trailer_button">ಟ್ರೈಲರ್ ಪ್ಲೇ ಮಾಡಿ</string>
|
||||
<string name="pick_source">ಮೂಲಗಳು</string>
|
||||
<string name="downloaded">ಡೌನ್ಲೋಡ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="download_started">ಡೌನ್ಲೋಡ್ ಪ್ರಾರಂಭವಾಗಿದೆ</string>
|
||||
<string name="download_canceled">ಡೌನ್ಲೋಡ್ ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ</string>
|
||||
<string name="home_next_random_img_des">ಮುಂದಿನ ರಾಂಡಮ್</string>
|
||||
</resources>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
|
||||
<string name="player_speed_text_format" formatted="true">Брзина (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">Оценето: %.1f</string>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
|
||||
<string name="player_speed_text_format" formatted="true">വേഗം (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">റേറ്റിംഗ്: %.1f</string>
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources/>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Cast: %s</string>
|
||||
|
@ -10,7 +9,7 @@
|
|||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Aflevering Poster</string>
|
||||
<string name="home_main_poster_img_des">Hoofdposter</string>
|
||||
<string name="home_next_random_img_des">Volgende willekeurig</string>
|
||||
|
@ -110,7 +109,7 @@
|
|||
<string name="continue_watching">Doorgaan met kijken</string>
|
||||
<string name="action_remove_watching">Verwijder</string>
|
||||
<string name="action_open_watching">Meer Info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Een VPN kan nodig zijn om deze provider correct te laten werken</string>
|
||||
<string name="vpn_torrent">Deze provider is een torrent, een VPN wordt aanbevolen</string>
|
||||
<string name="provider_info_meta">Metadata wordt niet geleverd door de site, het laden van video\'s zal mislukken als deze niet op de site bestaat.</string>
|
||||
|
@ -129,14 +128,14 @@
|
|||
<string name="eigengraumode_settings">Eigengravy Modus</string>
|
||||
<string name="eigengraumode_settings_des">Voegt een snelheidsoptie toe in de speler</string>
|
||||
<string name="swipe_to_seek_settings">Swipe to seek</string>
|
||||
<string name="swipe_to_seek_settings_des">Veeg naar links of rechts om de tijd in de videospeler te regelen</string>
|
||||
<string name="swipe_to_seek_settings_des">Veeg naar links of rechts om de tijd in de videoplayer te regelen</string>
|
||||
<string name="swipe_to_change_settings">Veeg om instellingen te wijzigen</string>
|
||||
<string name="swipe_to_change_settings_des">Veeg naar links of rechts om de helderheid of het volume te wijzigen</string>
|
||||
<string name="double_tap_to_seek_settings">Dubbeltik om te zien</string>
|
||||
<string name="double_tap_to_pause_settings">Dubbeltik om te pauzeren</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Videospeler aantal zoeken</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Speler zoeken bedrag</string>
|
||||
<string name="double_tap_to_seek_settings_des">Tik twee keer aan de rechter- of linkerkant om vooruit of achteruit te zoeken</string>
|
||||
<string name="double_tap_to_pause_settings_des">Tik twee keer in het midden om te pauzeren</string>
|
||||
<string name="double_tap_to_pause_settings_des">Tik in het midden om te pauzeren</string>
|
||||
<string name="use_system_brightness_settings">Systeemhelderheid gebruiken</string>
|
||||
<string name="use_system_brightness_settings_des">Gebruik systeemhelderheid in de app-speler in plaats van een donkere overlay</string>
|
||||
<string name="episode_sync_settings">Kijkvoortgang bijwerken</string>
|
||||
|
@ -223,8 +222,8 @@
|
|||
<string name="movies_singular">Film</string>
|
||||
<string name="tv_series_singular">Serie</string>
|
||||
<string name="cartoons_singular">Tekenfilm</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Documentaire</string>
|
||||
<string name="asian_drama_singular">Aziatisch drama</string>
|
||||
|
@ -406,7 +405,4 @@
|
|||
<string name="autoplay_next_settings_des">Start de volgende episode wanneer deze afgelopen is</string>
|
||||
<string name="autoplay_next_settings">Volgende episode automatisch afspelen</string>
|
||||
<string name="update_started">De update is gestart</string>
|
||||
<string name="library">Bibliotheek</string>
|
||||
<string name="browser">Browser</string>
|
||||
<string name="test_log">Logboek</string>
|
||||
</resources>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="episode_more_options_des">Fleire val</string>
|
||||
<string name="title_home">Heim</string>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Plakat</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Episode Plakat</string>
|
||||
<string name="home_main_poster_img_des">Main Plakat</string>
|
||||
<string name="home_next_random_img_des">Neste tilfeldig</string>
|
||||
|
@ -412,7 +412,7 @@
|
|||
<string name="poster_ui_settings">Slå av/på grensesnittselementer på plakat</string>
|
||||
<string name="skip_update">Hopp over denne oppdateringen</string>
|
||||
<string name="video_ram_description">Forårsaker tilfeldige krasj hvis satt for høyt. Ikke endre dette hvis du ikke har lite minne.</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="backup_settings">Sikkerhetskopier data</string>
|
||||
<string name="backup_success">Data lagret</string>
|
||||
<string name="authenticated_user_fail" formatted="true">Kunne ikke logge inn på %s</string>
|
||||
|
@ -422,11 +422,11 @@
|
|||
<string name="nsfw">Sensurerbart</string>
|
||||
<string name="quality_webrip">Vev</string>
|
||||
<string name="network_adress_example">Lenke til strøm</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="pref_filter_search_quality">Skjul valgt videokvalitet i søkeresultater</string>
|
||||
<string name="restore_success">Lastet inn sikkerhetkopifil</string>
|
||||
<string name="category_updates">Oppdateringer og sikkerhetskopi</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="confirm_exit_dialog">Avslutt\?</string>
|
||||
<string name="nsfw_singular">Sensurerbart</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">Alle %s er allerede nedlastet</string>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<string name="player_speed_text_format" formatted="true">Prędkość (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">Ocena: %.1f</string>
|
||||
<string name="new_update_format" formatted="true">Znaleziono nową aktualizację!
|
||||
|
@ -255,7 +254,7 @@
|
|||
<string name="dont_show_again">Nie pokazuj ponownie</string>
|
||||
<string name="skip_update">Pomiń tę aktualizację</string>
|
||||
<string name="update">Aktualizacja</string>
|
||||
<string name="watch_quality_pref">Domyślna jakość (WiFi)</string>
|
||||
<string name="watch_quality_pref">Domyślna jakość</string>
|
||||
<string name="limit_title">Maksymalna ilość znaków w tytule odtwarzacza</string>
|
||||
<string name="limit_title_rez">Rozdzielczość odtwarzacza wideo</string>
|
||||
<string name="video_buffer_size_settings">Rozmiar bufora wideo</string>
|
||||
|
@ -374,7 +373,7 @@
|
|||
<string name="skip_setup">Pomiń setup</string>
|
||||
<string name="app_layout_subtext">Dostosuj wygląd aplikacji do urządzenia</string>
|
||||
<string name="crash_reporting_title">Zgłaszanie błędów</string>
|
||||
<string name="preferred_media_subtext">Co chciałbyś obejrzeć</string>
|
||||
<string name="preferred_media_subtext">Co chciałbyś obejrzeć\?</string>
|
||||
<string name="setup_done">Gotowe</string>
|
||||
<string name="extensions">Rozszerzenia</string>
|
||||
<string name="add_repository">Dodaj repozytorium</string>
|
||||
|
@ -456,7 +455,7 @@
|
|||
<string name="apk_installer_settings">Instalator APK</string>
|
||||
<string name="apk_installer_settings_des">Niektóre telefony nie obsługują nowego instalatora pakietów. Wypróbuj tryb legacy, jeśli aktualizacje nie zostaną zainstalowane.</string>
|
||||
<string name="example_password">password123</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="example_site_name">MojaFajnaWitryna</string>
|
||||
<string name="example_username">MyCoolUsername</string>
|
||||
<string name="example_ip">127.0.0.1</string>
|
||||
|
@ -464,9 +463,9 @@
|
|||
<string name="example_site_url">przyklad.pl</string>
|
||||
<string name="sync_total_episodes_none">/\?\?</string>
|
||||
<string name="apk_installer_package_installer">Instalator pakietów</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="example_email">hello@world.com</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="skip_type_op">Opening</string>
|
||||
<string name="skip_type_ed">Ending</string>
|
||||
<string name="skip_type_mixed_op">Mixed opening</string>
|
||||
|
@ -510,27 +509,4 @@
|
|||
<string name="empty_library_logged_in_message">Wygląda na to, że ta lista jest pusta, spróbuj przełączyć się na inną</string>
|
||||
<string name="safe_mode_file">Znaleziono plik trybu bezpiecznego.
|
||||
\nRozszerzenia nie zostaną wczytane, dopóki plik nie zostanie usunięty.</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Używana ilość przewijania, gdy widoczny jest odtwarzacz</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Ukryty odtwarzacz - ilość przewijania</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Pokazany odtwarzacz — ilość przewijania</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Używana ilość przewijania, gdy ukryty jest odtwarzacz</string>
|
||||
<string name="test_log">Dziennik</string>
|
||||
<string name="restart">Uruchom ponownie</string>
|
||||
<string name="start">Rozpocznij</string>
|
||||
<string name="test_failed">Nie powiodło się</string>
|
||||
<string name="test_passed">Ukończone powodzeniem</string>
|
||||
<string name="jsdelivr_proxy">Serwer pośredniczący raw.githubusercontent.com</string>
|
||||
<string name="pref_category_bypass">Obejścia ISP</string>
|
||||
<string name="category_provider_test">Test dostawcy</string>
|
||||
<string name="stop">Zatrzymaj</string>
|
||||
<string name="revert">Przywróć</string>
|
||||
<string name="subscription_in_progress_notification">Aktualizowanie subskrybowanych programów</string>
|
||||
<string name="subscription_list_name">Zasubskrybowano</string>
|
||||
<string name="subscription_new">Zasubskrybowano %s</string>
|
||||
<string name="subscription_deleted">Anulowano subskrypcję %s</string>
|
||||
<string name="subscription_episode_released">Został wydany odcinek %d!</string>
|
||||
<string name="jsdelivr_proxy_summary">Obchodzi blokadę GitHuba za pomocą jsdelivr, może spowodować opóźnienie aktualizacji o kilka dni.</string>
|
||||
<string name="jsdelivr_enabled">Nie udało się połączyć z GitHub, włączono serwer pośredniczący jsdelivr.</string>
|
||||
<string name="watch_quality_pref_data">Domyślna jakość (dane mobilne)</string>
|
||||
</resources>
|
|
@ -1,65 +1,65 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dh %dm</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<string name="next_episode_format" formatted="true">O episódio %d será lançado em</string>
|
||||
<string name="next_episode_format" formatted="true">Episódio %d será lançado em</string>
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="episode_poster_img_des">Pôster do episódio</string>
|
||||
<string name="search_poster_img_des">Poster</string>
|
||||
<string name="home_main_poster_img_des">Pôster Principal</string>
|
||||
<string name="episode_poster_img_des">Capa do Episódio</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="home_main_poster_img_des">Capa Principal</string>
|
||||
<string name="home_next_random_img_des">Próximo Aleatório</string>
|
||||
<string name="go_back_img_des">Voltar</string>
|
||||
<string name="home_change_provider_img_des">Alterar Provedor</string>
|
||||
<string name="home_change_provider_img_des">Trocar Provedor</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dd %dh %dm</string>
|
||||
<string name="home_source">Fonte</string>
|
||||
<string name="resolution">Resolução</string>
|
||||
<string name="extras">Extras</string>
|
||||
<string name="preview_background_img_des">Visualizar plano de fundo</string>
|
||||
<string name="preview_background_img_des">Preview Background</string>
|
||||
<string name="player_speed_text_format" formatted="true">Velocidade (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">Classificado: %.1f</string>
|
||||
<string name="new_update_format" formatted="true">Nova atualização encontrada!
|
||||
\n%s -> %s</string>
|
||||
<string name="filler" formatted="true">Preenchimento</string>
|
||||
<string name="filler" formatted="true">Enchimento</string>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="play_with_app_name">Assistir com o CloudStream</string>
|
||||
<string name="play_with_app_name">Reproduzir com CloudStream</string>
|
||||
<string name="title_home">Início</string>
|
||||
<string name="title_search">Pesquisar</string>
|
||||
<string name="title_downloads">Downloads</string>
|
||||
<string name="title_settings">Configurações</string>
|
||||
<string name="title_search">Pesquisa</string>
|
||||
<string name="title_downloads">Transferências</string>
|
||||
<string name="title_settings">Opções</string>
|
||||
<string name="search_hint">Procurar…</string>
|
||||
<string name="search_hint_site" formatted="true">Pesquisar %s…</string>
|
||||
<string name="no_data">Sem dados</string>
|
||||
<string name="episode_more_options_des">Mais opções</string>
|
||||
<string name="search_hint_site" formatted="true">Procurar em %s…</string>
|
||||
<string name="no_data">Sem Dados</string>
|
||||
<string name="episode_more_options_des">Mais Opções</string>
|
||||
<string name="next_episode">Próximo episódio</string>
|
||||
<string name="result_tags">Gêneros</string>
|
||||
<string name="result_share">Compartilhar</string>
|
||||
<string name="result_open_in_browser">Abrir no navegador</string>
|
||||
<string name="skip_loading">Pular carregamento</string>
|
||||
<string name="result_tags">Géneros</string>
|
||||
<string name="result_share">Partilhar</string>
|
||||
<string name="result_open_in_browser">Abrir no Navegador</string>
|
||||
<string name="skip_loading">Saltar Carga</string>
|
||||
<string name="loading">Carregando…</string>
|
||||
<string name="type_watching">Assistindo</string>
|
||||
<string name="type_on_hold">Em espera</string>
|
||||
<string name="type_on_hold">Em Espera</string>
|
||||
<string name="type_completed">Concluído</string>
|
||||
<string name="type_dropped">Desistido</string>
|
||||
<string name="type_plan_to_watch">Pretendo assistir</string>
|
||||
<string name="type_none">Nenhum</string>
|
||||
<string name="type_re_watching">Reassistindo</string>
|
||||
<string name="play_movie_button">Reproduzir filme</string>
|
||||
<string name="play_livestream_button">Reproduzir transmissão ao vivo</string>
|
||||
<string name="type_dropped">Abandonado</string>
|
||||
<string name="type_plan_to_watch">Planeio Assistir</string>
|
||||
<string name="type_none">Nenhuma</string>
|
||||
<string name="type_re_watching">Assistindo de Novo</string>
|
||||
<string name="play_movie_button">Reproduzir Filme</string>
|
||||
<string name="play_livestream_button">Reproduzir Livestream</string>
|
||||
<string name="play_torrent_button">Transmitir Torrent</string>
|
||||
<string name="pick_source">Fontes</string>
|
||||
<string name="pick_subtitle">Legendas</string>
|
||||
<string name="reload_error">Tentar conexão novamente…</string>
|
||||
<string name="go_back">Voltar</string>
|
||||
<string name="play_episode">Reproduzir episódio</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="downloaded">Baixado</string>
|
||||
<string name="downloading">Baixando</string>
|
||||
<string name="download_paused">Download Pausado</string>
|
||||
<string name="download_started">Download Iniciado</string>
|
||||
<string name="download_failed">Falha no Download</string>
|
||||
<string name="download_canceled">Download cancelado</string>
|
||||
<string name="download_done">Download concluído</string>
|
||||
<string name="reload_error">Voltar a tentar ligação…</string>
|
||||
<string name="go_back">Voltar Atrás</string>
|
||||
<string name="play_episode">Reproduzir Episódio</string>
|
||||
<string name="download">Transferir</string>
|
||||
<string name="downloaded">Transferido</string>
|
||||
<string name="downloading">A Transferir</string>
|
||||
<string name="download_paused">Transferência em Pausa</string>
|
||||
<string name="download_started">Transferência Iniciada</string>
|
||||
<string name="download_failed">Transferência Falhou</string>
|
||||
<string name="download_canceled">Transferência Cancelada</string>
|
||||
<string name="download_done">Transferência Completa</string>
|
||||
<string name="stream">Stream</string>
|
||||
<string name="error_loading_links_toast">Erro a Carregar Links</string>
|
||||
<string name="download_storage_text">Armazenamento Interno</string>
|
||||
|
@ -123,16 +123,16 @@
|
|||
<string name="eigengraumode_settings">Modo Eigengravy</string>
|
||||
<string name="eigengraumode_settings_des">Acrescenta uma opção de velocidade no player</string>
|
||||
<string name="swipe_to_seek_settings">Deslize para andar</string>
|
||||
<string name="swipe_to_seek_settings_des">Deslize para os lados para controlar a posição em um vídeo</string>
|
||||
<string name="swipe_to_seek_settings_des">Deslize para a esq. ou dir. para controlar o tempo no player</string>
|
||||
<string name="swipe_to_change_settings">Deslize para mudar as configurações</string>
|
||||
<string name="swipe_to_change_settings_des">Deslize para cima ou para baixo, no lado esquerdo ou direito, para ajustar brilho ou volume</string>
|
||||
<string name="swipe_to_change_settings_des">Deslize do lado esq. ou dir. para ajustar brilho ou volume</string>
|
||||
<string name="autoplay_next_settings">Reproduzir automaticamente próximo episódio</string>
|
||||
<string name="autoplay_next_settings_des">Começa o próximo episódio quando o atual termina</string>
|
||||
<string name="double_tap_to_seek_settings">Toque duplo para avançar</string>
|
||||
<string name="double_tap_to_pause_settings">Toque duplo para pôr em pausa</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Tempo de busca no player (Segundos)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Segundos avançados no player</string>
|
||||
<string name="double_tap_to_seek_settings_des">Toque duplo no lado esq. ou dir. para andar para trás ou para a frente</string>
|
||||
<string name="double_tap_to_pause_settings_des">Toque duas vezes no meio para pausar</string>
|
||||
<string name="double_tap_to_pause_settings_des">Toque no meio para pôr em pausa</string>
|
||||
<string name="use_system_brightness_settings">Usar brilho da sistema</string>
|
||||
<string name="use_system_brightness_settings_des">Usar brilho do sistema no player em vez de uma sobreposição escura</string>
|
||||
<string name="episode_sync_settings">Atualizar progresso</string>
|
||||
|
@ -142,7 +142,7 @@
|
|||
<string name="restore_success">Arquivo de backup carregado</string>
|
||||
<string name="restore_failed_format" formatted="true">Falha ao restaurar dados do ficheiro %s</string>
|
||||
<string name="backup_success">Dados guardados com sucesso</string>
|
||||
<string name="backup_failed">Permissão de armazenamento não encontrada, por favor tente novamente.</string>
|
||||
<string name="backup_failed">Permissões de armazenamento em falta, por favor tente de novo</string>
|
||||
<string name="backup_failed_error_format">Erro no backup de %s</string>
|
||||
<string name="search">Procurar</string>
|
||||
<string name="category_account">Contas</string>
|
||||
|
@ -158,7 +158,7 @@
|
|||
<string name="pref_filter_search_quality">Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa</string>
|
||||
<string name="automatic_plugin_updates">Atualizações de plugin automáticas</string>
|
||||
<string name="updates_settings">Mostrar atualizações da app</string>
|
||||
<string name="updates_settings_des">Procurar automaticamente por novas atualizações depois de iniciar o app.</string>
|
||||
<string name="updates_settings_des">Procurar novas atualizações automaticamente ao iniciar</string>
|
||||
<string name="uprereleases_settings">Atualizar para pré-lançamentos</string>
|
||||
<string name="uprereleases_settings_des">Procura atualizações de pré-lançamento em vez de só lançamentos oficiais</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -250,15 +250,15 @@
|
|||
<string name="dont_show_again">Não mostrar de novo</string>
|
||||
<string name="skip_update">Saltar esta Atualização</string>
|
||||
<string name="update">Atualizar</string>
|
||||
<string name="watch_quality_pref">Qualidade Preferida (WiFi)</string>
|
||||
<string name="limit_title">Máximo de caracteres do título no player de video</string>
|
||||
<string name="watch_quality_pref">Qualidade Preferida</string>
|
||||
<string name="limit_title">Máximo de caracteres do título de vídeos</string>
|
||||
<string name="limit_title_rez">Resolução do player de vídeo</string>
|
||||
<string name="video_buffer_size_settings">Tamanho do buffer do vídeo</string>
|
||||
<string name="video_buffer_length_settings">Comprimento do buffer do vídeo</string>
|
||||
<string name="video_buffer_disk_settings">Cache do vídeo em disco</string>
|
||||
<string name="video_buffer_clear_settings">Limpar cache de vídeo e imagem</string>
|
||||
<string name="video_ram_description">Causará travamentos em dispositivos com pouca memória se definido muito alto , como uma Android TV.</string>
|
||||
<string name="video_disk_description">Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como uma Android TV.</string>
|
||||
<string name="video_ram_description">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</string>
|
||||
<string name="video_disk_description">Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como em dispositivos Android TV</string>
|
||||
<string name="dns_pref">DNS sobre HTTPS</string>
|
||||
<string name="dns_pref_summary">Útil para contornar bloqueios do fornecedor de internet</string>
|
||||
<string name="add_site_pref">Clonar site</string>
|
||||
|
@ -273,7 +273,7 @@
|
|||
<string name="legal_notice">Aviso Legal</string>
|
||||
<string name="category_general">Geral</string>
|
||||
<string name="random_button_settings">Botão Aleatório</string>
|
||||
<string name="random_button_settings_desc">Mostra o botão Aleatório na página inicial, que pode escolher aleatoriamente um filme ou série</string>
|
||||
<string name="random_button_settings_desc">Mostra o botão Aleatório na página inicial</string>
|
||||
<string name="provider_lang_settings">Idioma dos fornecedores</string>
|
||||
<string name="app_layout">Layout da App</string>
|
||||
<string name="preferred_media_settings">Mídia preferida</string>
|
||||
|
@ -363,7 +363,7 @@
|
|||
<string name="plugin_loaded">Plugin Carregado</string>
|
||||
<string name="plugin_deleted">Plugin Apagado</string>
|
||||
<string name="plugin_load_fail" formatted="true">Falha ao carregar %s</string>
|
||||
<string name="batch_download_start_format" formatted="true">Download iniciado %d %s…</string>
|
||||
<string name="batch_download_start_format" formatted="true">Iniciada a transferência %d %s</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Transferido %d %s com sucesso</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">Tudo %s já transferido</string>
|
||||
<string name="batch_download">Transferência em batch</string>
|
||||
|
@ -375,158 +375,10 @@
|
|||
<string name="plugins_downloaded" formatted="true">Transferido: %d</string>
|
||||
<string name="plugins_disabled" formatted="true">Desativado: %d</string>
|
||||
<string name="plugins_not_downloaded" formatted="true">Não transferido: %d</string>
|
||||
<string name="blank_repo_message">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.</string>
|
||||
<string name="blank_repo_message">Adicionar um repositório para instalar extensões de sites</string>
|
||||
<string name="view_public_repositories_button">Ver repositórios da comunidade</string>
|
||||
<string name="view_public_repositories_button_short">Lista pública</string>
|
||||
<string name="uppercase_all_subtitles">Todas as legendas em maiúsculas</string>
|
||||
<string name="download_all_plugins_from_repo">Transferir todos os plugins deste repositório\?</string>
|
||||
<string name="single_plugin_disabled" formatted="true">%s (Desativado)</string>
|
||||
<string name="apk_installer_settings">Instalador APK</string>
|
||||
<string name="duration_format" formatted="true">%d min</string>
|
||||
<string name="play_trailer_button">Assistir Trailer</string>
|
||||
<string name="action_add_to_bookmarks">Marcar como visto/não visto</string>
|
||||
<string name="action_open_play">Reproduzir</string>
|
||||
<string name="automatic_plugin_download_summary">Instalar automaticamente todos os plugins ainda não instalados dos repositórios adicionados.</string>
|
||||
<string name="automatic_plugin_download">Baixar extensões automaticamente</string>
|
||||
<string name="redo_setup_process">Refazer o processo de configuração</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="other_singular">Vídeo</string>
|
||||
<string name="go_forward_30">+30</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="cast_format" formatted="true">Elenco: %s</string>
|
||||
<string name="update_started">Atualização iniciada</string>
|
||||
<string name="test_log">Log</string>
|
||||
<string name="apk_installer_settings_des">Alguns aparelhos não suportam o novo instalador de pacotes. Use a opção legado caso não esteja conseguindo atualizar.</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="start">Iniciar</string>
|
||||
<string name="test_failed">Falha</string>
|
||||
<string name="test_passed">Sucesso</string>
|
||||
<string name="library">Biblioteca</string>
|
||||
<string name="browser">Navegar</string>
|
||||
<string name="anim">Aplicativo de Anime pelos mesmos desenvolvedores</string>
|
||||
<string name="ova_singular">Ova</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Player visível - Procurar valor</string>
|
||||
<string name="update_notification_installing">Instalando atualização do app…</string>
|
||||
<string name="confirm_exit_dialog">Você tem certeza que deseja sair\?</string>
|
||||
<string name="extension_version">Versão</string>
|
||||
<string name="skip_type_ed">Encerramento</string>
|
||||
<string name="clear_history">Limpar histórico</string>
|
||||
<string name="skip_type_op">Abertura</string>
|
||||
<string name="no">Não</string>
|
||||
<string name="sort_by">Ordenar por</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="update_notification_downloading">Baixando atualização do app…</string>
|
||||
<string name="subscription_episode_released">Episódio %d lançado!</string>
|
||||
<string name="skip_type_creddits">Créditos</string>
|
||||
<string name="extension_description">Descrição</string>
|
||||
<string name="extension_size">Tamanho</string>
|
||||
<string name="stop">Parar</string>
|
||||
<string name="safe_mode_title">Modo seguro ligado</string>
|
||||
<string name="history">Histórico</string>
|
||||
<string name="sort">Ordenar</string>
|
||||
<string name="player_settings_play_in_app">Player interno</string>
|
||||
<string name="extension_authors">Autores</string>
|
||||
<string name="extension_types">Suportado</string>
|
||||
<string name="extension_language">Idioma</string>
|
||||
<string name="extension_install_first">Instalar a extensão primeiro</string>
|
||||
<string name="hls_playlist">Playlist HLS</string>
|
||||
<string name="player_pref">Player de vídeo preferido</string>
|
||||
<string name="extension_status">Estado</string>
|
||||
<string name="pref_category_gestures">Gestos</string>
|
||||
<string name="tracks">Faixas</string>
|
||||
<string name="quality_workprint">WP</string>
|
||||
<string name="quality_cam">Cam</string>
|
||||
<string name="skip_type_intro">Abertura</string>
|
||||
<string name="select_library">Selecionar Biblioteca</string>
|
||||
<string name="jsdelivr_proxy_summary">Usando jsdelivr o bloqueio do GitHub pode ser contornado. Pode atrasar atualizações em alguns dias.</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="all_languages_preference">Todas as linguagens</string>
|
||||
<string name="sort_updated_new">Atualizado (Novo para Antigo)</string>
|
||||
<string name="subscription_list_name">Inscrito</string>
|
||||
<string name="quality_hdr">HDR</string>
|
||||
<string name="restart">Reiniciar</string>
|
||||
<string name="player_settings_play_in_browser">Navegador Web</string>
|
||||
<string name="sort_updated_old">Atualizado (Antigo para Novo)</string>
|
||||
<string name="player_settings_play_in_web">Web Video Cast</string>
|
||||
<string name="quality_dvd">DVD</string>
|
||||
<string name="apk_installer_package_installer">Instalador de pacotes</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
<string name="action_remove_from_watched">Remover dos assistidos</string>
|
||||
<string name="update_notification_failed">Não foi possível instalar a nova versão do aplicativo</string>
|
||||
<string name="subscription_deleted">Inscrição cancelada em %s</string>
|
||||
<string name="skip_type_mixed_ed">Final misto</string>
|
||||
<string name="sort_rating_desc">Avaliações (Decrescente)</string>
|
||||
<string name="apply_on_restart">Aplicar ao reiniciar</string>
|
||||
<string name="referer">Referente</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Player oculto - Quantidade de Busca</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
|
||||
<string name="quality_blueray">Blu-ray</string>
|
||||
<string name="pref_category_looks">Aparência</string>
|
||||
<string name="subtitle_offset_hint">1000 ms</string>
|
||||
<string name="quality_sdr">SDR</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="open_with">Abrir com</string>
|
||||
<string name="category_provider_test">Teste de provedor</string>
|
||||
<string name="quality_uhd">UHD</string>
|
||||
<string name="safe_mode_crash_info">Ver informações sobre falha</string>
|
||||
<string name="app_not_found_error">Aplicativo não encontrado</string>
|
||||
<string name="revert">Reverter</string>
|
||||
<string name="network_adress_example">Link para transmitir</string>
|
||||
<string name="plugin_downloaded">Plugins baixados</string>
|
||||
<string name="plugins_updated" formatted="true">%d plugins atualizados</string>
|
||||
<string name="skip_type_format" formatted="true">Pular %s</string>
|
||||
<string name="skip_type_mixed_op">Abertura mista</string>
|
||||
<string name="sort_alphabetical_z">Alfabético (Z a A)</string>
|
||||
<string name="empty_library_logged_in_message">Parece que esta lista está vazia, tente trocar para outra</string>
|
||||
<string name="subscription_new">Inscrito em %s</string>
|
||||
<string name="quality_4k">4K</string>
|
||||
<string name="video_tracks">Faixas de vídeo</string>
|
||||
<string name="delayed_update_notice">O aplicativo será atualizado ao sair</string>
|
||||
<string name="subscription_in_progress_notification">Atualizando shows inscritos</string>
|
||||
<string name="sort_alphabetical_a">Alfabético (A a Z)</string>
|
||||
<string name="sort_rating_asc">Avaliações (Crescente)</string>
|
||||
<string name="empty_library_no_accounts_message">Parece que a sua biblioteca está vazia :(
|
||||
\nFaça login em uma conta de biblioteca ou adicione shows à sua biblioteca local</string>
|
||||
<string name="safe_mode_file">Arquivo de modo de segurança encontrado!
|
||||
\nNenhuma extensão será carregada na inicialização do app até que o arquivo seja removido.</string>
|
||||
<string name="pref_category_bypass">Contorno do provedor de serviço de internet (ISP)</string>
|
||||
<string name="pref_category_links">Links</string>
|
||||
<string name="pref_category_player_features">Recursos do Player</string>
|
||||
<string name="pref_category_ui_features">Recursos</string>
|
||||
<string name="pref_category_app_updates">Atualizações de aplicativos</string>
|
||||
<string name="watch_quality_pref_data">Qualidade Preferida (Dados Móveis)</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Quantidade de busca (em segundos) usada quando o player de video está visível</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Quantidade de busca (em segundos) usada quando o player de video está oculto</string>
|
||||
<string name="jsdelivr_enabled">Falha ao conectar com GitHub, ativando proxy jsdelivr.</string>
|
||||
<string name="pref_category_cache">Cache</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="pref_category_subtitles">Legendas</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="quality_ts">TS</string>
|
||||
<string name="quality_cam_rip">Cam</string>
|
||||
<string name="quality_cam_hd">Cam</string>
|
||||
<string name="quality_hq">HQ</string>
|
||||
<string name="quality_hd">HD</string>
|
||||
<string name="quality_tc">TC</string>
|
||||
<string name="quality_webrip">Web</string>
|
||||
<string name="extension_rating" formatted="true">Nota: %s</string>
|
||||
<string name="apk_installer_legacy">Legado</string>
|
||||
<string name="safe_mode_description">Todas as extensões foram desativadas devido a uma falha para ajudá-lo a encontrar a que está causando o problema.</string>
|
||||
<string name="skip_type_recap">Recapitular</string>
|
||||
<string name="enable_skip_op_from_database_des">Mostrar pop-ups para pular abertura/encerramento</string>
|
||||
<string name="clipboard_too_large">Muito texto. Não é possível salvar na área de transferência.</string>
|
||||
<string name="action_mark_as_watched">Marcar como assistido</string>
|
||||
<string name="pref_category_backup">Backup</string>
|
||||
<string name="pref_category_extensions">Extensões</string>
|
||||
<string name="pref_category_actions">Ações</string>
|
||||
<string name="pref_category_player_layout">Layout</string>
|
||||
<string name="pref_category_defaults">Configurações padrão</string>
|
||||
<string name="quality_sd">SD</string>
|
||||
<string name="audio_tracks">Faixas de áudio</string>
|
||||
</resources>
|
|
@ -1,15 +1,15 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- For true intellectuals -->
|
||||
<string name="app_name">aauugghhaauuh</string>
|
||||
<string name="title_home">ooh</string>
|
||||
<string name="title_search">oouuh</string>
|
||||
<string name="title_downloads">ouuhhuhooh ooh</string>
|
||||
<string name="title_downloads">ouuhhhooh ooh</string>
|
||||
<string name="title_settings">aaaghhoh aauugghh</string>
|
||||
<string name="search_hint">haaooh ooo</string>
|
||||
<string name="search_hint">haaooh</string>
|
||||
<string name="search_poster_img_des">aaaaaah</string>
|
||||
<string name="no_data">oooohhoouuh</string>
|
||||
<string name="episode_more_options_des">aaaaoouuuhahhh ahh</string>
|
||||
<string name="episode_more_options_des">aaaaaoouuhahhh ahh</string>
|
||||
<string name="go_back_img_des">oooohh aauuh</string>
|
||||
<string name="next_episode">ahhhaaaghh aaaaa</string>
|
||||
<string name="result_poster_img_des">ahaauugghh</string>
|
||||
|
@ -195,57 +195,5 @@
|
|||
<string name="phone_layout">u ooah uo ahauao huhuu hauu h</string>
|
||||
<string name="primary_color_settings">a ou oh ouhuouhoaaha</string>
|
||||
<string name="show_fillers_settings">aaooohhouhhha hauauuu</string>
|
||||
<string name="new_update_format">aaaaaaa uuuuuu
|
||||
\n%s -> %s</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s aaou %d</string>
|
||||
<string name="cast_format" formatted="true">oouaaahh %s</string>
|
||||
<string name="next_episode_format" formatted="true">aaaaaaugh ouh %d uuoogahaaah ooua-h-ha</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%daaa %duuu %dhhhg</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%daaaaaaauuhh %doouuahaaha</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dmmmm...</string>
|
||||
<string name="player_speed_text_format" formatted="true">aaaaaaaaaaaaahh (%.2foouo)</string>
|
||||
<string name="rated_format" formatted="true">aghaaaooo-ough %.1f</string>
|
||||
<string name="filler" formatted="true">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaooooohhuhuhuhuhuhhuhuhhhaaagaha-agagaoooo</string>
|
||||
<string name="duration_format" formatted="true">%d ooaaaugha</string>
|
||||
<string name="play_with_app_name">oooooh aaah aauuuggghauuh</string>
|
||||
<string name="search_hint_site" formatted="true">aaoohhu %s aoouu</string>
|
||||
<string name="browser">oooaaaauhgaaaa</string>
|
||||
<string name="type_re_watching">aaaaaah-ooooooooouuagh</string>
|
||||
<string name="action_add_to_bookmarks">auugggguuuuuuhhh uuuu hhhhhhhhuhggghggg</string>
|
||||
<string name="play_trailer_button">ggaaaahhhhhhh gaauuuuuuuaaaau</string>
|
||||
<string name="play_livestream_button">aaauuuuggggguu</string>
|
||||
<string name="update_started">ooo aagg hhhh</string>
|
||||
<string name="stream">ooo aagg hhhh</string>
|
||||
<string name="sort_copy">uuuuhhhoouuooog ooaaahhhh</string>
|
||||
<string name="sort_close">uuu ugggg</string>
|
||||
<string name="sort_clear">ooo guggg ooh</string>
|
||||
<string name="tracks">auuuooohaaaaagh</string>
|
||||
<string name="library">uuuuuuuh aaaoo o</string>
|
||||
<string name="category_updates">ooooooouuuua aa aaagh agh</string>
|
||||
<string name="ova">AAAAUUUOH</string>
|
||||
<string name="subscription_list_name">aoughoooaaaa</string>
|
||||
<string name="movies_singular">oooouuuh</string>
|
||||
<string name="category_player">ahaough aaouuuuh-h</string>
|
||||
<string name="tv_series_singular">auughooo</string>
|
||||
<string name="pref_category_player_layout">ooooooa aauoh</string>
|
||||
<string name="subs_subtitle_languages">aaaaagh oouoo aaaaaaa</string>
|
||||
<string name="category_account">aaaaaagh uuohuoh</string>
|
||||
<string name="extensions">aaaaaauo agghhhhhhaoouu</string>
|
||||
<string name="others">uuuuuuuuh</string>
|
||||
<string name="test_log">ouaaahh</string>
|
||||
<string name="subs_import_text" formatted="true">ooough aaoough aooou %s aaaa</string>
|
||||
<string name="documentaries">ouooooouuuu oooooo</string>
|
||||
<string name="livestreams">aaaaaaaaaaahhhgh-aooohoooo</string>
|
||||
<string name="pref_category_extensions">aau aooooghaao</string>
|
||||
<string name="provider_info_meta">aagh aaaaaaaaaaaa oooh, aaough, ooga oguuu aaaaaaaaaaa ooooooohghh a-a-aaauo</string>
|
||||
<string name="resume_time_left" formatted="true">%dmmmmmm..
|
||||
\naaaaooughugh</string>
|
||||
<string name="asian_drama">aooohuohaaaa ooooagh</string>
|
||||
<string name="subtitle_offset">oooooogh-aaaaaogh</string>
|
||||
<string name="trailer">guuuaaaahhhhhhhaaa</string>
|
||||
<string name="show_log_cat">woooaaahh ahahaaaauu 🦍</string>
|
||||
<string name="nsfw">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOOOGGAGHAGHAAA</string>
|
||||
<string name="clear_history">aoaaaaaoooghhh</string>
|
||||
<string name="sort_save">oooooh uuaagh</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="new_update_format">aaaaaaa uuuuuu\n%s -> %s</string>
|
||||
</resources>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue