Compare commits

..

33 commits

Author SHA1 Message Date
reduplicated
7a1cfc2f2b mini fix to text in popup 2023-02-02 18:30:48 +01:00
Blatzar
a8bcf2dcb2 Merge remote-tracking branch 'origin/anilist' into anilist 2023-01-28 23:36:30 +01:00
Blatzar
df95931d0a future proofed SyncID 2023-01-28 23:36:24 +01:00
LagradOst
46732ea917
Merge branch 'master' into anilist 2023-01-28 22:21:44 +00:00
reduplicated
cd166cb0d3 mini fix 2023-01-28 23:08:55 +01:00
Blatzar
cbeeff5cca fix library statusbar 2023-01-28 22:49:58 +01:00
Blatzar
e7732cc15f Smoother fast scrolling in library & no labels 2023-01-28 14:57:06 +01:00
Blatzar
e875d520de Fixed loading padding 2023-01-28 01:11:13 +01:00
Blatzar
ef7e0ecf0d Fixed rotation and loading delay 2023-01-28 00:54:47 +01:00
Blatzar
7512dbcbf8 Library loading fixes 2023-01-27 22:26:24 +01:00
Blatzar
1f67644290 Fix crash and sorting methods in library 2023-01-27 14:54:24 +01:00
Blatzar
0aca996bc0 Better notice when not logged in in library 2023-01-26 18:09:22 +01:00
Blatzar
26320bb535 Reworked backend to make list titles use UiText 2023-01-25 19:20:53 +01:00
reduplicated
9bc90438ec colors go brrr 2023-01-25 16:54:57 +01:00
Blatzar
dd293e9564 Added visible loading in library and fix reloading on sync 2023-01-25 12:54:51 +01:00
Blatzar
f6c609cfdf Fix embarrassing crash when using different languages in library 2023-01-25 00:55:01 +01:00
Blatzar
4a0989c9c5 fixed buggy page transform in library 2023-01-24 23:04:03 +01:00
Blatzar
847c70a127 Add search option in library 2023-01-24 22:38:06 +01:00
Blatzar
20ada8da0d Fix library bug and remove it from TV 2023-01-24 22:31:31 +01:00
Blatzar
daaab9d35b More library fixes 2023-01-24 20:27:22 +01:00
Blatzar
c4afb5e673 More library fixes 2023-01-22 23:25:29 +01:00
Blatzar
0dab9959e1 Fixes for breakages in the latest merge 2023-01-22 22:28:51 +01:00
LagradOst
e945600940
Merge pull request #325 from recloudstream/library
Bump anilist base to latest
2023-01-22 18:18:14 +00:00
LagradOst
9edb7f8999
Merge branch 'anilist' into library 2023-01-22 18:17:22 +00:00
Blatzar
7053d77ca1 Moved stuff to R.strings and fixed reloading the page 2023-01-22 19:09:37 +01:00
Blatzar
30dbd3920e Fixed "open with" in library 2023-01-22 18:54:16 +01:00
Blatzar
019e9a0c4f More work on local list support 2023-01-21 14:21:36 +01:00
Blatzar
7cd6dd3fe6 WIP local list support 2023-01-21 13:14:17 +01:00
Blatzar
8cdc31ffcd WIP adding open with specified extension 2022-12-28 20:26:59 +01:00
Blatzar
2a2a0a26e7 Add list switcher 2022-12-18 19:22:35 +01:00
Blatzar
67a1c447ae backend fixes for opening from sync urls 2022-12-05 20:49:45 +01:00
Blatzar
05dc032df6 library fixes 2022-11-20 17:05:11 +01:00
Blatzar
67fe6730e0 library work in progress 2022-11-19 20:44:12 +01:00
130 changed files with 1302 additions and 4970 deletions

BIN
.github/downloads.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
.github/home.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

20
.github/locales.py vendored
View file

@ -1,14 +1,13 @@
import re import re
import glob import glob
import requests 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" SETTINGS_PATH = "app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt"
START_MARKER = "/* begin language list */" START_MARKER = "/* begin language list */"
END_MARKER = "/* end language list */" END_MARKER = "/* end language list */"
XML_NAME = "app/src/main/res/values-" XML_NAME = "app/src/main/res/values-"
ISO_MAP_URL = "https://raw.githubusercontent.com/haliaeetus/iso-639/master/data/iso_639-1.min.json" ISO_MAP_URL = "https://gist.githubusercontent.com/Josantonius/b455e315bc7f790d14b136d61d9ae469/raw"
INDENT = " "*4 INDENT = " "*4
iso_map = requests.get(ISO_MAP_URL, timeout=300).json() iso_map = requests.get(ISO_MAP_URL, timeout=300).json()
@ -28,8 +27,7 @@ for lang in re.finditer(r'Triple\("(.*)", "(.*)", "(.*)"\)', rest):
for folder in glob.glob(f"{XML_NAME}*"): for folder in glob.glob(f"{XML_NAME}*"):
iso = folder[len(XML_NAME):] iso = folder[len(XML_NAME):]
if iso not in languages.keys(): if iso not in languages.keys():
entry = iso_map.get(iso.lower(),{'nativeName':iso}) languages[iso] = ("", iso_map.get(iso.lower(),iso))
languages[iso] = ("", entry['nativeName'].split(',')[0])
# Create triples # Create triples
triples = [] triples = []
@ -47,17 +45,3 @@ open(SETTINGS_PATH, "w+",encoding='utf-8').write(
END_MARKER + END_MARKER +
after_src 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
.github/results.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
.github/search.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View file

@ -15,7 +15,6 @@ jobs:
app_id: ${{ secrets.GH_APP_ID }} app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }} private_key: ${{ secrets.GH_APP_KEY }}
- name: Similarity analysis - name: Similarity analysis
id: similarity
uses: actions-cool/issues-similarity-analysis@v1 uses: actions-cool/issues-similarity-analysis@v1
with: with:
token: ${{ steps.generate_token.outputs.token }} token: ${{ steps.generate_token.outputs.token }}
@ -25,18 +24,6 @@ jobs:
### Your issue looks similar to these issues: ### Your issue looks similar to these issues:
Please close if duplicate. Please close if duplicate.
comment-body: '${index}. ${similarity} #${number}' comment-body: '${index}. ${similarity} #${number}'
- name: Label if possible duplicate
if: steps.similarity.outputs.similar-issues-found =='true'
uses: actions/github-script@v6
with:
github-token: ${{ steps.generate_token.outputs.token }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["possible duplicate"]
})
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Automatically close issues that dont follow the issue template - name: Automatically close issues that dont follow the issue template
uses: lucasbento/auto-close-issues@v1.0.2 uses: lucasbento/auto-close-issues@v1.0.2
@ -66,18 +53,6 @@ jobs:
Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM). Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM).
Found provider name: `${{ steps.provider_check.outputs.name }}` Found provider name: `${{ steps.provider_check.outputs.name }}`
- name: Label if mentions provider
if: steps.provider_check.outputs.name != 'none'
uses: actions/github-script@v6
with:
github-token: ${{ steps.generate_token.outputs.token }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["possible provider issue"]
})
- name: Add eyes reaction to all issues - name: Add eyes reaction to all issues
uses: actions-cool/emoji-helper@v1.0.0 uses: actions-cool/emoji-helper@v1.0.0
with: with:

View file

@ -1,4 +1,4 @@
name: Fix locale issues name: Update locale lists
on: on:
workflow_dispatch: workflow_dispatch:
@ -9,7 +9,7 @@ on:
- master - master
concurrency: concurrency:
group: "locale" group: "locale-list"
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
@ -26,9 +26,6 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
token: ${{ steps.generate_token.outputs.token }} token: ${{ steps.generate_token.outputs.token }}
- name: Install dependencies
run: |
pip3 install lxml
- name: Edit files - name: Edit files
run: | run: |
python3 .github/locales.py python3 .github/locales.py
@ -38,5 +35,5 @@ jobs:
git config --local user.name "recloudstream[bot]" git config --local user.name "recloudstream[bot]"
git add . git add .
# "echo" returns true so the build succeeds, even if no changed files # "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 git push

View file

@ -12,6 +12,11 @@
+ Download and stream movies, tv-shows and anime + Download and stream movies, tv-shows and anime
+ Chromecast + 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: ### Supported languages:
<a href="https://hosted.weblate.org/engage/cloudstream/"> <a href="https://hosted.weblate.org/engage/cloudstream/">
<img src="https://hosted.weblate.org/widgets/cloudstream/-/app/multi-auto.svg" alt="Translation status" /> <img src="https://hosted.weblate.org/widgets/cloudstream/-/app/multi-auto.svg" alt="Translation status" />

View file

@ -47,8 +47,8 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 33 targetSdk = 33
versionCode = 57 versionCode = 56
versionName = "4.0.0" versionName = "3.5.0"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
@ -159,8 +159,6 @@ dependencies {
implementation("com.google.android.exoplayer:extension-cast:2.18.2") implementation("com.google.android.exoplayer:extension-cast:2.18.2")
implementation("com.google.android.exoplayer:extension-mediasession:2.18.2") implementation("com.google.android.exoplayer:extension-mediasession:2.18.2")
implementation("com.google.android.exoplayer:extension-okhttp: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") //implementation("com.google.android.exoplayer:extension-leanback:2.14.0")
@ -186,13 +184,13 @@ dependencies {
//implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0") //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0")
// Downloading // Downloading
implementation("androidx.work:work-runtime:2.8.0") implementation("androidx.work:work-runtime:2.7.1")
implementation("androidx.work:work-runtime-ktx:2.8.0") implementation("androidx.work:work-runtime-ktx:2.7.1")
// Networking // Networking
// implementation("com.squareup.okhttp3:okhttp:4.9.2") // implementation("com.squareup.okhttp3:okhttp:4.9.2")
// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1") // implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
implementation("com.github.Blatzar:NiceHttp:0.4.2") implementation("com.github.Blatzar:NiceHttp:0.4.1")
// To fix SSL fuckery on android 9 // To fix SSL fuckery on android 9
implementation("org.conscrypt:conscrypt-android:2.2.1") implementation("org.conscrypt:conscrypt-android:2.2.1")
// Util to skip the URI file fuckery 🙏 // Util to skip the URI file fuckery 🙏

View file

@ -1,8 +1,9 @@
package com.lagradost.cloudstream3 package com.lagradost.cloudstream3
import androidx.test.ext.junit.runners.AndroidJUnit4 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.SubtitleHelper
import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -15,11 +16,142 @@ import org.junit.runner.RunWith
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest { 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> { private fun getAllProviders(): List<MainAPI> {
println("Providers: ${APIHolder.allProviders.size}")
return APIHolder.allProviders //.filter { !it.usesWebView } 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 @Test
fun providersExist() { fun providersExist() {
Assert.assertTrue(getAllProviders().isNotEmpty()) Assert.assertTrue(getAllProviders().isNotEmpty())
@ -27,7 +159,6 @@ class ExampleInstrumentedTest {
} }
@Test @Test
@Throws(AssertionError::class)
fun providerCorrectData() { fun providerCorrectData() {
val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 } val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 }
Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty()) Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty())
@ -50,20 +181,67 @@ class ExampleInstrumentedTest {
fun providerCorrectHomepage() { fun providerCorrectHomepage() {
runBlocking { runBlocking {
getAllProviders().amap { api -> 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") println("Done providerCorrectHomepage")
} }
// @Test
// fun testSingleProvider() {
// testSingleProviderApi(ThenosProvider())
// }
@Test @Test
fun testAllProvidersCorrect() { fun providerCorrect() {
runBlocking { runBlocking {
TestingUtils.getDeferredProviderTests( val invalidProvider = ArrayList<Pair<MainAPI, Exception?>>()
this, val providers = getAllProviders()
getAllProviders(), providers.amap { api ->
::println 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")
} }
} }

View file

@ -98,16 +98,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter> </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> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View file

@ -15,12 +15,13 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniList
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.syncproviders.SyncIdName
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLink
import okhttp3.Interceptor import okhttp3.Interceptor
import org.mozilla.javascript.Scriptable
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -84,7 +85,7 @@ object APIHolder {
initMap() initMap()
return apiMap?.get(apiName)?.let { apis.getOrNull(it) } return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
// Leave the ?. null check, it can crash regardless // Leave the ?. null check, it can crash regardless
?: allProviders.firstOrNull { it.name == apiName } ?: allProviders.firstOrNull { it?.name == apiName }
} }
} }
@ -162,53 +163,6 @@ object APIHolder {
return null return null
} }
private var trackerCache: HashMap<String, AniSearch> = hashMapOf()
/**
* Get anime tracker information based on title, year and type.
* Both titles are attempted to be matched with both Romaji and English title.
* Uses the consumet api.
*
* @param titles uses first index to search, but if you have multiple titles and want extra guarantee to match you can also have that
* @param types Optional parameter to narrow down the scope to Movies, TV, etc. See TrackerType.getTypes()
* @param year Optional parameter to only get anime with a specific year
**/
suspend fun getTracker(
titles: List<String>,
types: Set<TrackerType>?,
year: Int?
): Tracker? {
return try {
require(titles.isNotEmpty()) { "titles must no be empty when calling getTracker" }
val mainTitle = titles[0]
val search =
trackerCache[mainTitle]
?: app.get("https://api.consumet.org/meta/anilist/$mainTitle")
.parsedSafe<AniSearch>()?.also {
trackerCache[mainTitle] = it
} ?: return null
val res = search.results?.find { media ->
val matchingYears = year == null || media.releaseDate == year
val matchingTitles = media.title?.let { title ->
titles.any { userTitle ->
title.isMatchingTitles(userTitle)
}
} ?: false
val matchingTypes = types?.any { it.name.equals(media.type, true) } == true
matchingTitles && matchingTypes && matchingYears
} ?: return null
Tracker(res.malId, res.aniId, res.image, res.cover)
} catch (t: Throwable) {
logError(t)
null
}
}
fun Context.getApiSettings(): HashSet<String> { fun Context.getApiSettings(): HashSet<String> {
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
@ -736,19 +690,6 @@ fun fixTitle(str: String): String {
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it } .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 */ /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */
fun imdbUrlToId(url: String): String? { fun imdbUrlToId(url: String): String? {
@ -1327,7 +1268,7 @@ fun LoadResponse?.isAnimeBased(): Boolean {
fun TvType?.isEpisodeBased(): Boolean { fun TvType?.isEpisodeBased(): Boolean {
if (this == null) return false if (this == null) return false
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama) return (this == TvType.TvSeries || this == TvType.Anime)
} }
@ -1351,7 +1292,6 @@ interface EpisodeResponse {
var showStatus: ShowStatus? var showStatus: ShowStatus?
var nextAiring: NextAiring? var nextAiring: NextAiring?
var seasonNames: List<SeasonData>? var seasonNames: List<SeasonData>?
fun getLatestEpisodes(): Map<DubStatus, Int?>
} }
@JvmName("addSeasonNamesString") @JvmName("addSeasonNamesString")
@ -1420,18 +1360,7 @@ data class AnimeLoadResponse(
override var nextAiring: NextAiring? = null, override var nextAiring: NextAiring? = null,
override var seasonNames: List<SeasonData>? = null, override var seasonNames: List<SeasonData>? = null,
override var backgroundPosterUrl: String? = null, override var backgroundPosterUrl: String? = null,
) : LoadResponse, EpisodeResponse { ) : 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()
}
}
/** /**
* If episodes already exist appends the list. * If episodes already exist appends the list.
@ -1629,17 +1558,7 @@ data class TvSeriesLoadResponse(
override var nextAiring: NextAiring? = null, override var nextAiring: NextAiring? = null,
override var seasonNames: List<SeasonData>? = null, override var seasonNames: List<SeasonData>? = null,
override var backgroundPosterUrl: String? = null, override var backgroundPosterUrl: String? = null,
) : LoadResponse, EpisodeResponse { ) : 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)
}
}
suspend fun MainAPI.newTvSeriesLoadResponse( suspend fun MainAPI.newTvSeriesLoadResponse(
name: String, name: String,
@ -1671,61 +1590,3 @@ fun fetchUrls(text: String?): List<String> {
fun String?.toRatingInt(): Int? = fun String?.toRatingInt(): Int? =
this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt() this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt()
data class Tracker(
val malId: Int? = null,
val aniId: String? = null,
val image: String? = null,
val cover: String? = null,
)
data class Title(
@JsonProperty("romaji") val romaji: String? = null,
@JsonProperty("english") val english: String? = null,
) {
fun isMatchingTitles(title: String?): Boolean {
if (title == null) return false
return english.equals(title, true) || romaji.equals(title, true)
}
}
data class Results(
@JsonProperty("id") val aniId: String? = null,
@JsonProperty("malId") val malId: Int? = null,
@JsonProperty("title") val title: Title? = null,
@JsonProperty("releaseDate") val releaseDate: Int? = null,
@JsonProperty("type") val type: String? = null,
@JsonProperty("image") val image: String? = null,
@JsonProperty("cover") val cover: String? = null,
)
data class AniSearch(
@JsonProperty("results") val results: ArrayList<Results>? = arrayListOf()
)
/**
* used for the getTracker() method
**/
enum class TrackerType {
MOVIE,
TV,
TV_SHORT,
ONA,
OVA,
SPECIAL,
MUSIC;
companion object {
fun getTypes(type: TvType): Set<TrackerType> {
return when (type) {
TvType.Movie -> setOf(MOVIE)
TvType.AnimeMovie -> setOf(MOVIE)
TvType.TvSeries -> setOf(TV, TV_SHORT)
TvType.Anime -> setOf(TV, TV_SHORT, ONA, OVA)
TvType.OVA -> setOf(OVA, SPECIAL, ONA)
TvType.Others -> setOf(MUSIC)
else -> emptySet()
}
}
}
}

View file

@ -5,8 +5,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log 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.gms.cast.framework.*
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.navigationrail.NavigationRailView
import com.google.android.material.snackbar.Snackbar
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.allProviders
import com.lagradost.cloudstream3.APIHolder.apis 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.OAuth2Apis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString 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.appStringRepo
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
@ -67,13 +63,7 @@ import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.ui.home.HomeViewModel
import com.lagradost.cloudstream3.ui.player.BasicLink import com.lagradost.cloudstream3.ui.result.*
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
@ -86,7 +76,6 @@ import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable 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.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
@ -94,7 +83,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe 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.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
@ -175,12 +163,7 @@ open class ResultResume(
val VLC = object : ResultResume( val VLC = object : ResultResume(
VLC_PACKAGE, VLC_PACKAGE,
// Android 13 intent restrictions fucks up specifically launching the VLC player "org.videolan.vlc.player.result",
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
"org.videolan.vlc.player.result"
} else {
Intent.ACTION_VIEW
},
"extra_position", "extra_position",
"extra_duration", "extra_duration",
) { ) {
@ -279,8 +262,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
isWebview: Boolean isWebview: Boolean
): Boolean = ): Boolean =
with(activity) { with(activity) {
// TODO MUCH BETTER HANDLING
// Invalid URIs can crash // Invalid URIs can crash
fun safeURI(uri: String) = normalSafeApiCall { URI(uri) } fun safeURI(uri: String) = normalSafeApiCall { URI(uri) }
@ -331,25 +312,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} else if (safeURI(str)?.scheme == appStringSearch) { } else if (safeURI(str)?.scheme == appStringSearch) {
nextSearchQuery = nextSearchQuery =
URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8") URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8")
nav_view.selectedItemId = R.id.navigation_search
// 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,
)
)
)
} else if (safeURI(str)?.scheme == appStringResumeWatching) { } else if (safeURI(str)?.scheme == appStringResumeWatching) {
val id = val id =
str.substringAfter("$appStringResumeWatching://").toIntOrNull() str.substringAfter("$appStringResumeWatching://").toIntOrNull()
@ -381,7 +344,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
} }
var lastPopup: SearchResponse? = null var lastPopup : SearchResponse? = null
fun loadPopup(result: SearchResponse) { fun loadPopup(result: SearchResponse) {
lastPopup = result lastPopup = result
viewModel.load( viewModel.load(
@ -436,7 +399,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
R.id.navigation_settings_general, R.id.navigation_settings_general,
R.id.navigation_settings_extensions, R.id.navigation_settings_extensions,
R.id.navigation_settings_plugins, R.id.navigation_settings_plugins,
R.id.navigation_test_providers,
).contains(destination.id) ).contains(destination.id)
@ -751,35 +713,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
changeStatusBarState(isEmulatorSettings()) changeStatusBarState(isEmulatorSettings())
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com if (lastError == null) {
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 {
showToast(this, R.string.safe_mode_file, Toast.LENGTH_LONG)
}
} else if (lastError == null) {
ioSafe { ioSafe {
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
@ -860,7 +794,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
resultview_preview_meta_duration.setText(d.durationText) resultview_preview_meta_duration.setText(d.durationText)
resultview_preview_meta_rating.setText(d.ratingText) resultview_preview_meta_rating.setText(d.ratingText)
resultview_preview_description?.setText(d.plotText) resultview_preview_description?.setTextHtml(d.plotText)
resultview_preview_poster?.setImage( resultview_preview_poster?.setImage(
d.posterImage ?: d.posterBackgroundImage d.posterImage ?: d.posterBackgroundImage
) )
@ -1147,15 +1081,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// } // }
} }
suspend fun checkGithubConnectivity(): Boolean {
return try {
app.get(
"https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck",
timeout = 5
).text.trim() == "ok"
} catch (t: Throwable) {
false
}
}
} }

View file

@ -6,7 +6,6 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import java.net.URL import java.net.URL
@ -43,9 +42,18 @@ open class Dailymotion : ExtractorApi() {
) )
val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = cookies) val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = cookies)
.parsedSafe<MetaData>() ?: return .parsedSafe<MetaData>() ?: return
metaData.qualities.forEach { (_, video) -> metaData.qualities.forEach { (key, video) ->
video.forEach { 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 return null
} }
private suspend fun getStream(
streamLink: String,
name: String,
callback: (ExtractorLink) -> Unit
) {
return generateM3u8(
name,
streamLink,
"",
).forEach(callback)
}
data class Config( data class Config(
val context: Context, val context: Context,
val dmInternalData: InternalData val dmInternalData: InternalData

View file

@ -38,9 +38,6 @@ class DoodWsExtractor : DoodLaExtractor() {
override var mainUrl = "https://dood.ws" override var mainUrl = "https://dood.ws"
} }
class DoodYtExtractor : DoodLaExtractor() {
override var mainUrl = "https://dood.yt"
}
open class DoodLaExtractor : ExtractorApi() { open class DoodLaExtractor : ExtractorApi() {
override var name = "DoodStream" override var name = "DoodStream"

View file

@ -16,7 +16,26 @@ open class Evoload : ExtractorApi() {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { 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 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 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) val payload = mapOf("code" to id, "csrv_token" to csrv_token, "pass" to captchaPass)
@ -25,9 +44,9 @@ open class Evoload : ExtractorApi() {
return listOf( return listOf(
ExtractorLink( ExtractorLink(
name, name,
name, name + flag,
link, link,
url, cleaned_url,
Qualities.Unknown.value, Qualities.Unknown.value,
) )
) )

View file

@ -1,57 +1,38 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
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() { open class Filesim : ExtractorApi() {
override val name = "Filesim" override val name = "Filesim"
override val mainUrl = "https://files.im" override val mainUrl = "https://files.im"
override val requiresReferer = false override val requiresReferer = false
override suspend fun getUrl( override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
url: String, val sources = mutableListOf<ExtractorLink>()
referer: String?, with(app.get(url).document) {
subtitleCallback: (SubtitleFile) -> Unit, this.select("script").map { script ->
callback: (ExtractorLink) -> Unit if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
) { val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]")
val response = app.get(url, referer = mainUrl).document tryParseJson<List<ResponseSource>>("[$data]")?.map {
response.select("script[type=text/javascript]").map { script -> M3u8Helper.generateM3u8(
if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) { name,
val unpackedscript = getAndUnpack(script.data()) it.file,
val m3u8Regex = Regex("file.\\\"(.*?m3u8.*?)\\\"") "$mainUrl/",
val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: "" ).forEach { m3uData -> sources.add(m3uData) }
if (m3u8.isNotEmpty()) { }
generateM3u8(
name,
m3u8,
mainUrl
).forEach(callback)
} }
} }
} }
return sources
} }
/* private data class ResponseSource( private data class ResponseSource(
@JsonProperty("file") val file: String, @JsonProperty("file") val file: String,
@JsonProperty("type") val type: String?, @JsonProperty("type") val type: String?,
@JsonProperty("label") val label: String? @JsonProperty("label") val label: String?
) */ )
} }

View file

@ -6,11 +6,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
class Vanfem : GuardareStream() {
override var name = "Vanfem"
override var mainUrl = "https://vanfem.com/"
}
class CineGrabber : GuardareStream() { class CineGrabber : GuardareStream() {
override var name = "CineGrabber" override var name = "CineGrabber"
override var mainUrl = "https://cinegrabber.com" override var mainUrl = "https://cinegrabber.com"

View file

@ -18,36 +18,31 @@ open class Linkbox : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val id = Regex("""(?:/f/|/file/|\?id=)(\w+)""").find(url)?.groupValues?.get(1) val id = Regex("""(/file/|id=)(\S+)[&/?]""").find(url)?.groupValues?.get(2)
app.get("$mainUrl/api/file/detail?itemId=$id", referer = url) app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe<Responses>()?.data?.rList?.map { link ->
.parsedSafe<Responses>()?.data?.itemInfo?.resolutionList?.map { link -> callback.invoke(
callback.invoke( ExtractorLink(
ExtractorLink( name,
name, name,
name, link.url,
link.url ?: return@map null, url,
url, getQualityFromName(link.resolution)
getQualityFromName(link.resolution)
)
) )
} )
}
} }
data class Resolutions( data class RList(
@JsonProperty("url") val url: String? = null, @JsonProperty("url") val url: String,
@JsonProperty("resolution") val resolution: String? = null, @JsonProperty("resolution") val resolution: String?,
)
data class ItemInfo(
@JsonProperty("resolutionList") val resolutionList: ArrayList<Resolutions>? = arrayListOf(),
) )
data class Data( data class Data(
@JsonProperty("itemInfo") val itemInfo: ItemInfo? = null, @JsonProperty("rList") val rList: List<RList>?,
) )
data class Responses( data class Responses(
@JsonProperty("data") val data: Data? = null, @JsonProperty("data") val data: Data?,
) )
} }

View file

@ -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)
}
}
}

View file

@ -77,10 +77,6 @@ class StreamSB10 : StreamSB() {
override var mainUrl = "https://sbplay2.xyz" 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 // 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 // The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
open class StreamSB : ExtractorApi() { open class StreamSB : ExtractorApi() {
@ -134,7 +130,7 @@ open class StreamSB : ExtractorApi() {
it.value.replace(Regex("(embed-|/e/)"), "") it.value.replace(Regex("(embed-|/e/)"), "")
}.first() }.first()
// val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" // 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( val headers = mapOf(
"watchsb" to "sbstream", "watchsb" to "sbstream",
) )

View file

@ -7,10 +7,6 @@ class Uqload1 : Uqload() {
override var mainUrl = "https://uqload.com" override var mainUrl = "https://uqload.com"
} }
class Uqload2 : Uqload() {
override var mainUrl = "https://uqload.co"
}
open class Uqload : ExtractorApi() { open class Uqload : ExtractorApi() {
override val name: String = "Uqload" override val name: String = "Uqload"
override val mainUrl: String = "https://www.uqload.com" 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>? { 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 -> srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link ->
return listOf( return listOf(
ExtractorLink( ExtractorLink(
name, name,
name, name + flag,
link, link,
url, cleaned_url,
Qualities.Unknown.value, Qualities.Unknown.value,
) )
) )

View file

@ -59,8 +59,8 @@ open class VidSrcExtractor : ExtractorApi() {
if (datahash.isNotBlank()) { if (datahash.isNotBlank()) {
val links = try { val links = try {
app.get( app.get(
"$absoluteUrl/srcrcp/$datahash", "$absoluteUrl/src/$datahash",
referer = "https://rcp.vidsrc.me/" referer = "https://source.vidsrc.me/"
).url ).url
} catch (e: Exception) { } catch (e: Exception) {
"" ""
@ -71,7 +71,7 @@ open class VidSrcExtractor : ExtractorApi() {
serverslist.amap { server -> serverslist.amap { server ->
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/") val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
if (linkfixed.contains("/prorcp")) { if (linkfixed.contains("/pro")) {
val srcresponse = app.get(server, referer = absoluteUrl).text val srcresponse = app.get(server, referer = absoluteUrl).text
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)") val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)")
val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap

View file

@ -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
}
}

View file

@ -49,7 +49,7 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
} }
} }
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) { fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this) { it?.let { t -> action(t) } } liveData.observe(this) { it?.let { t -> action(t) } }
} }
@ -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(
separator = "\n"
) {
"${it.fileName} ${it.lineNumber}"
}
}
fun <T> safeFail(throwable: Throwable): Resource<T> { fun <T> safeFail(throwable: Throwable): Resource<T> {
val stackTraceMsg = throwable.getStackTracePretty() val stackTraceMsg =
(throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
separator = "\n"
) {
"${it.fileName} ${it.lineNumber}"
}
return Resource.Failure(false, null, null, stackTraceMsg) return Resource.Failure(false, null, null, stackTraceMsg)
} }

View file

@ -16,7 +16,6 @@ import com.google.gson.Gson
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.APIHolder.removePluginMapping 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.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
@ -145,10 +144,8 @@ object PluginManager {
return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray() return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray()
} }
private val CLOUD_STREAM_FOLDER = private val LOCAL_PLUGINS_PATH =
Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/" Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins"
private val LOCAL_PLUGINS_PATH = CLOUD_STREAM_FOLDER + "plugins"
public var currentlyLoading: String? = null public var currentlyLoading: String? = null
@ -166,11 +163,11 @@ object PluginManager {
private var loadedLocalPlugins = false private var loadedLocalPlugins = false
private val gson = Gson() private val gson = Gson()
private suspend fun maybeLoadPlugin(context: Context, file: File) { private suspend fun maybeLoadPlugin(activity: Activity, file: File) {
val name = file.name val name = file.name
if (file.extension == "zip" || file.extension == "cs3") { if (file.extension == "zip" || file.extension == "cs3") {
loadPlugin( loadPlugin(
context, activity,
file, file,
PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET) PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET)
) )
@ -200,7 +197,7 @@ object PluginManager {
// var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet() // var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet()
suspend fun loadSinglePlugin(context: Context, apiName: String): Boolean { suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean {
return (getPluginsOnline().firstOrNull { return (getPluginsOnline().firstOrNull {
// Most of the time the provider ends with Provider which isn't part of the api name // Most of the time the provider ends with Provider which isn't part of the api name
it.internalName.replace("provider", "", ignoreCase = true) == apiName it.internalName.replace("provider", "", ignoreCase = true) == apiName
@ -210,7 +207,7 @@ object PluginManager {
})?.let { savedData -> })?.let { savedData ->
// OnlinePluginData(savedData, onlineData) // OnlinePluginData(savedData, onlineData)
loadPlugin( loadPlugin(
context, activity,
File(savedData.filePath), File(savedData.filePath),
savedData savedData
) )
@ -372,11 +369,11 @@ object PluginManager {
/** /**
* Use updateAllOnlinePluginsAndLoadThem * Use updateAllOnlinePluginsAndLoadThem
* */ * */
fun loadAllOnlinePlugins(context: Context) { fun loadAllOnlinePlugins(activity: Activity) {
// Load all plugins as fast as possible! // Load all plugins as fast as possible!
(getPluginsOnline()).toList().apmap { pluginData -> (getPluginsOnline()).toList().apmap { pluginData ->
loadPlugin( loadPlugin(
context, activity,
File(pluginData.filePath), File(pluginData.filePath),
pluginData pluginData
) )
@ -399,7 +396,7 @@ object PluginManager {
* @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins * @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins
* and reload all pages even if they are previously valid * 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) val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL) removeKey(PLUGINS_KEY_LOCAL)
@ -417,39 +414,24 @@ object PluginManager {
Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins") Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins")
sortedPlugins?.sortedBy { it.name }?.apmap { file -> sortedPlugins?.sortedBy { it.name }?.apmap { file ->
maybeLoadPlugin(context, file) maybeLoadPlugin(activity, file)
} }
loadedLocalPlugins = true loadedLocalPlugins = true
afterPluginsLoadedEvent.invoke(forceReload) afterPluginsLoadedEvent.invoke(forceReload)
} }
/**
* This can be used to override any extension loading to fix crashes!
* @return true if safe mode file is present
**/
fun checkSafeModeFile(): Boolean {
return normalSafeApiCall {
val folder = File(CLOUD_STREAM_FOLDER)
if (!folder.exists()) return@normalSafeApiCall false
val files = folder.listFiles { _, name ->
name.equals("safe", ignoreCase = true)
}
files?.any()
} ?: false
}
/** /**
* @return True if successful, false if not * @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 fileName = file.nameWithoutExtension
val filePath = file.absolutePath val filePath = file.absolutePath
currentlyLoading = fileName currentlyLoading = fileName
Log.i(TAG, "Loading plugin: $data") Log.i(TAG, "Loading plugin: $data")
return try { return try {
val loader = PathClassLoader(filePath, context.classLoader) val loader = PathClassLoader(filePath, activity.classLoader)
var manifest: Plugin.Manifest var manifest: Plugin.Manifest
loader.getResourceAsStream("manifest.json").use { stream -> loader.getResourceAsStream("manifest.json").use { stream ->
if (stream == null) { if (stream == null) {
@ -493,22 +475,22 @@ object PluginManager {
addAssetPath.invoke(assets, file.absolutePath) addAssetPath.invoke(assets, file.absolutePath)
pluginInstance.resources = Resources( pluginInstance.resources = Resources(
assets, assets,
context.resources.displayMetrics, activity.resources.displayMetrics,
context.resources.configuration activity.resources.configuration
) )
} }
plugins[filePath] = pluginInstance plugins[filePath] = pluginInstance
classLoaders[loader] = pluginInstance classLoaders[loader] = pluginInstance
urlPlugins[data.url ?: filePath] = pluginInstance urlPlugins[data.url ?: filePath] = pluginInstance
pluginInstance.load(context) pluginInstance.load(activity)
Log.i(TAG, "Loaded plugin ${data.internalName} successfully") Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
currentlyLoading = null currentlyLoading = null
true true
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}")
showToast( showToast(
context.getActivity(), activity,
context.getString(R.string.plugin_load_fail).format(fileName), activity.getString(R.string.plugin_load_fail).format(fileName),
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
currentlyLoading = null currentlyLoading = null

View file

@ -2,10 +2,8 @@ package com.lagradost.cloudstream3.plugins
import android.content.Context import android.content.Context
import com.fasterxml.jackson.annotation.JsonProperty 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.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
@ -73,15 +71,6 @@ object RepositoryManager {
val PREBUILT_REPOSITORIES: Array<RepositoryData> by lazy { val PREBUILT_REPOSITORIES: Array<RepositoryData> by lazy {
getKey("PREBUILT_REPOSITORIES") ?: emptyArray() 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? { suspend fun parseRepoUrl(url: String): String? {
val fixedUrl = url.trim() val fixedUrl = url.trim()
@ -95,15 +84,10 @@ object RepositoryManager {
} }
} else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) {
suspendSafeApiCall { suspendSafeApiCall {
app.get("https://l.cloudstream.cf/${fixedUrl}", allowRedirects = false).let { app.get("https://l.cloudstream.cf/${fixedUrl}").let {
it.headers["Location"]?.let { url -> return@let if (it.isSuccessful && !it.url.startsWith("https://cutt.ly/branded-domains")) it.url
return@suspendSafeApiCall if (!url.startsWith("https://cutt.ly/branded-domains")) url else app.get("https://cutt.ly/${fixedUrl}").let let2@{ it2 ->
else null return@let2 if (it2.isSuccessful) it2.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
}
} }
} }
} }
@ -113,14 +97,14 @@ object RepositoryManager {
suspend fun parseRepository(url: String): Repository? { suspend fun parseRepository(url: String): Repository? {
return suspendSafeApiCall { return suspendSafeApiCall {
// Take manifestVersion and such into account later // Take manifestVersion and such into account later
app.get(convertRawGitUrl(url)).parsedSafe() app.get(url).parsedSafe()
} }
} }
private suspend fun parsePlugins(pluginUrls: String): List<SitePlugin> { private suspend fun parsePlugins(pluginUrls: String): List<SitePlugin> {
// Take manifestVersion and such into account later // Take manifestVersion and such into account later
return try { return try {
val response = app.get(convertRawGitUrl(pluginUrls)) val response = app.get(pluginUrls)
// Normal parsed function not working? // Normal parsed function not working?
// return response.parsedSafe() // return response.parsedSafe()
tryParseJson<Array<SitePlugin>>(response.text)?.toList() ?: emptyList() tryParseJson<Array<SitePlugin>>(response.text)?.toList() ?: emptyList()
@ -155,7 +139,7 @@ object RepositoryManager {
} }
file.createNewFile() file.createNewFile()
val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body val body = app.get(pluginUrl).okhttpResponse.body
write(body.byteStream(), file.outputStream()) write(body.byteStream(), file.outputStream())
file file
} }

View 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()
}
}

View file

@ -1,22 +1,11 @@
package com.lagradost.cloudstream3.services package com.lagradost.cloudstream3.services
import android.app.Service
import android.app.IntentService
import android.content.Intent import android.content.Intent
import android.os.IBinder
import com.lagradost.cloudstream3.utils.VideoDownloadManager 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() { class VideoDownloadService : IntentService("VideoDownloadService") {
override fun onHandleIntent(intent: Intent?) {
private val downloadScope = CoroutineScope(Dispatchers.Default)
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) { if (intent != null) {
val id = intent.getIntExtra("id", -1) val id = intent.getIntExtra("id", -1)
val type = intent.getStringExtra("type") val type = intent.getStringExtra("type")
@ -25,36 +14,10 @@ class VideoDownloadService : Service() {
"resume" -> VideoDownloadManager.DownloadActionType.Resume "resume" -> VideoDownloadManager.DownloadActionType.Resume
"pause" -> VideoDownloadManager.DownloadActionType.Pause "pause" -> VideoDownloadManager.DownloadActionType.Pause
"stop" -> VideoDownloadManager.DownloadActionType.Stop "stop" -> VideoDownloadManager.DownloadActionType.Stop
else -> return START_NOT_STICKY else -> return
}
downloadScope.launch {
VideoDownloadManager.downloadEvent.invoke(Pair(id, state))
} }
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))
// }
// }
// }
//}

View file

@ -45,7 +45,6 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
const val appString = "cloudstreamapp" const val appString = "cloudstreamapp"
const val appStringRepo = "cloudstreamrepo" const val appStringRepo = "cloudstreamrepo"
const val appStringPlayer = "cloudstreamplayer"
// Instantly start the search given a query // Instantly start the search given a query
const val appStringSearch = "cloudstreamsearch" const val appStringSearch = "cloudstreamsearch"

View file

@ -759,11 +759,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return data != "" 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( private suspend fun postDataAboutId(
id: Int, id: Int,
type: AniListStatusType, type: AniListStatusType,
@ -771,43 +766,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
progress: Int? progress: Int?
): Boolean { ): Boolean {
val q = val q =
// Delete item if status type is None """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
if (type == AniListStatusType.None) { aniListStatusString[maxOf(
val userID = getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.id ?: return false 0,
// Get list ID for deletion type.value
val idQuery = """ )]
query MediaList(${'$'}userId: Int = $userID, ${'$'}mediaId: Int = $id) { }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
MediaList(userId: ${'$'}userId, mediaId: ${'$'}mediaId) { SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
id id
} status
} progress
""" score
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,
type.value
)]
}, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
id
status
progress
score
}
}""" }"""
}
val data = postApi(q) val data = postApi(q)
return data != "" return data != ""
} }

View file

@ -9,7 +9,6 @@ import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.library.ListSorting
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.Coroutines.ioWork 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.getAllWatchStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
@ -75,16 +74,13 @@ class LocalList : SyncAPI {
group.value.mapNotNull { group.value.mapNotNull {
getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) 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 { val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
// None is not something to display // None is not something to display
it.stringRes to emptyList<SyncAPI.LibraryItem>() it.stringRes to emptyList<SyncAPI.LibraryItem>()
} + mapOf(R.string.subscription_list_name to emptyList()) }
return SyncAPI.LibraryMetadata( return SyncAPI.LibraryMetadata(
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
setOf( setOf(

View file

@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator 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.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE 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 android.text.format.Formatter.formatShortFileSize
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import java.net.URI import java.net.URI
@ -225,7 +225,7 @@ class DownloadFragment : Fragment() {
R.id.global_to_navigation_player, R.id.global_to_navigation_player,
GeneratorPlayer.newInstance( GeneratorPlayer.newInstance(
LinkGenerator( LinkGenerator(
listOf(BasicLink(url)), listOf(url),
extract = true, extract = true,
referer = referer, referer = referer,
isM3u8 = dialog.hls_switch?.isChecked isM3u8 = dialog.hls_switch?.isChecked

View file

@ -185,7 +185,7 @@ open class ParentItemAdapter(
) : ) :
RecyclerView.ViewHolder(itemView) { RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.home_child_more_info 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) { fun update(expand: HomeViewModel.ExpandableHomepageList) {
val info = expand.list val info = expand.list

View file

@ -333,10 +333,8 @@ class LibraryFragment : Fragment() {
handler.postDelayed(stopLoading, 300) handler.postDelayed(stopLoading, 300)
savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos -> savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos ->
if (currentPos < 0) return@let
viewpager?.setCurrentItem(currentPos, false) viewpager?.setCurrentItem(currentPos, false)
// Using remove() sets the key to 0 instead of removing it savedInstanceState.remove(VIEWPAGER_ITEM_KEY)
savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1)
} }
// Since the animation to scroll multiple items is so much its better to just hide // Since the animation to scroll multiple items is so much its better to just hide

View file

@ -9,11 +9,8 @@ import android.widget.FrameLayout
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.C.* 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.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource 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.source.*
import com.google.android.exoplayer2.text.TextRenderer import com.google.android.exoplayer2.text.TextRenderer
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector 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.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
import java.io.File import java.io.File
import java.time.Duration
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession import javax.net.ssl.SSLSession
@ -540,17 +535,15 @@ class CS3IPlayer : IPlayer {
OkHttpDataSource.Factory(client).setUserAgent(USER_AGENT) 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( val headers = mapOf(
"referer" to link.referer,
"accept" to "*/*", "accept" to "*/*",
"sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"", "sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"",
"sec-ch-ua-mobile" to "?0", "sec-ch-ua-mobile" to "?0",
"sec-fetch-user" to "?1", "sec-fetch-user" to "?1",
"sec-fetch-mode" to "navigate", "sec-fetch-mode" to "navigate",
"sec-fetch-dest" to "video" "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 { return source.apply {
setDefaultRequestProperties(headers) setDefaultRequestProperties(headers)
@ -673,27 +666,23 @@ class CS3IPlayer : IPlayer {
val exoPlayerBuilder = val exoPlayerBuilder =
ExoPlayer.Builder(context) ExoPlayer.Builder(context)
.setRenderersFactory { eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput -> .setRenderersFactory { eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput ->
DefaultRenderersFactory(context).apply { DefaultRenderersFactory(context).createRenderers(
// setEnableDecoderFallback(true) eventHandler,
// Enable Ffmpeg extension videoRendererEventListener,
// setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON) audioRendererEventListener,
}.createRenderers( textRendererOutput,
eventHandler, metadataRendererOutput
videoRendererEventListener, ).map {
audioRendererEventListener, if (it is TextRenderer) {
textRendererOutput, currentTextRenderer = CustomTextRenderer(
metadataRendererOutput subtitleOffset,
).map { textRendererOutput,
if (it is TextRenderer) { eventHandler.looper,
currentTextRenderer = CustomTextRenderer( CustomSubtitleDecoderFactory()
subtitleOffset, )
textRendererOutput, currentTextRenderer!!
eventHandler.looper, } else it
CustomSubtitleDecoderFactory() }.toTypedArray()
)
currentTextRenderer!!
} else it
}.toTypedArray()
} }
.setTrackSelector( .setTrackSelector(
trackSelector ?: getTrackSelector( trackSelector ?: getTrackSelector(
@ -858,7 +847,7 @@ class CS3IPlayer : IPlayer {
Log.i(TAG, "loadExo") Log.i(TAG, "loadExo")
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val maxVideoHeight = settingsManager.getInt( 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 Int.MAX_VALUE
) )
@ -1204,10 +1193,10 @@ class CS3IPlayer : IPlayer {
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
} }
val mime = when { val mime = if (link.isM3u8) {
link.isM3u8 -> MimeTypes.APPLICATION_M3U8 MimeTypes.APPLICATION_M3U8
link.isDash -> MimeTypes.APPLICATION_MPD } else {
else -> MimeTypes.VIDEO_MP4 MimeTypes.VIDEO_MP4
} }
val mediaItems = if (link is ExtractorLinkPlayList) { val mediaItems = if (link is ExtractorLinkPlayList) {

View file

@ -4,16 +4,13 @@ import android.content.Context
import android.util.Log import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.Format import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.text.* import com.google.android.exoplayer2.text.SubtitleDecoder
import com.google.android.exoplayer2.text.cea.Cea608Decoder import com.google.android.exoplayer2.text.SubtitleDecoderFactory
import com.google.android.exoplayer2.text.cea.Cea708Decoder import com.google.android.exoplayer2.text.SubtitleInputBuffer
import com.google.android.exoplayer2.text.dvb.DvbDecoder import com.google.android.exoplayer2.text.SubtitleOutputBuffer
import com.google.android.exoplayer2.text.pgs.PgsDecoder
import com.google.android.exoplayer2.text.ssa.SsaDecoder import com.google.android.exoplayer2.text.ssa.SsaDecoder
import com.google.android.exoplayer2.text.subrip.SubripDecoder import com.google.android.exoplayer2.text.subrip.SubripDecoder
import com.google.android.exoplayer2.text.ttml.TtmlDecoder 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.text.webvtt.WebvttDecoder
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
@ -22,11 +19,7 @@ import org.mozilla.universalchardet.UniversalDetector
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.charset.Charset import java.nio.charset.Charset
/** class CustomDecoder : SubtitleDecoder {
* @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 {
companion object { companion object {
fun updateForcedEncoding(context: Context) { fun updateForcedEncoding(context: Context) {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
@ -146,7 +139,7 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
val inputString = getStr(inputBuffer) val inputString = getStr(inputBuffer)
if (realDecoder == null && !inputString.isNullOrBlank()) { if (realDecoder == null && !inputString.isNullOrBlank()) {
var str: String = inputString 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") Log.i(TAG, "Got data from queueInputBuffer")
//https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388 //https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388
realDecoder = when { realDecoder = when {
@ -155,31 +148,8 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
(str.startsWith( (str.startsWith(
"[Script Info]", "[Script Info]",
ignoreCase = true ignoreCase = true
) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder(fallbackFormat?.initializationData) ) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder()
str.startsWith("1", ignoreCase = true) -> SubripDecoder() 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 else -> null
} }
Log.i( Log.i(
@ -276,6 +246,28 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory {
} }
override fun createDecoder(format: Format): SubtitleDecoder { 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()
//}
} }
} }

View file

@ -42,7 +42,7 @@ class DownloadedPlayerActivity : AppCompatActivity() {
R.id.global_to_navigation_player, GeneratorPlayer.newInstance( R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
LinkGenerator( LinkGenerator(
listOf( listOf(
BasicLink(url) url
) )
) )
) )

View file

@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive
import com.lagradost.cloudstream3.utils.Qualities 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.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe 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_MAXIMUM_HOLD_TIME = 200L
const val DOUBLE_TAB_MINIMUM_TIME_BETWEEN = 200L // this also affects the UI show response time 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 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 // All the UI Logic for the player
open class FullScreenPlayer : AbstractPlayerFragment() { open class FullScreenPlayer : AbstractPlayerFragment() {
@ -111,8 +109,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
protected var currentPrefQuality = protected var currentPrefQuality =
Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
protected var fastForwardTime = 10000L protected var fastForwardTime = 10000L
protected var androidTVInterfaceOffSeekTime = 10000L;
protected var androidTVInterfaceOnSeekTime = 30000L;
protected var swipeHorizontalEnabled = false protected var swipeHorizontalEnabled = false
protected var swipeVerticalEnabled = false protected var swipeVerticalEnabled = false
protected var playBackSpeedEnabled = false protected var playBackSpeedEnabled = false
@ -1055,19 +1051,19 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
if (!isShowing && !isLocked) { if (!isShowing && !isLocked) {
player.seekTime(-androidTVInterfaceOffSeekTime) player.seekTime(-10000L)
return true return true
} else if (player_pause_play?.isFocused == true) { } else if (player_pause_play?.isFocused == true) {
player.seekTime(-androidTVInterfaceOnSeekTime) player.seekTime(-30000L)
return true return true
} }
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (!isShowing && !isLocked) { if (!isShowing && !isLocked) {
player.seekTime(androidTVInterfaceOffSeekTime) player.seekTime(10000L)
return true return true
} else if (player_pause_play?.isFocused == true) { } else if (player_pause_play?.isFocused == true) {
player.seekTime(androidTVInterfaceOnSeekTime) player.seekTime(30000L)
return true return true
} }
} }
@ -1121,20 +1117,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
resetRewindText() 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") @SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// init variables // init variables
setPlayBackSpeed(getKey(PLAYBACK_SPEED_KEY) ?: 1.0f) setPlayBackSpeed(getKey(PLAYBACK_SPEED_KEY) ?: 1.0f)
savedInstanceState?.getLong(SUBTITLE_DELAY_BUNDLE_KEY)?.let {
subtitleDelay = it
}
// handle tv controls // handle tv controls
playerEventListener = { eventType -> playerEventListener = { eventType ->
@ -1220,13 +1207,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
settingsManager.getInt(ctx.getString(R.string.double_tap_seek_time_key), 10) settingsManager.getInt(ctx.getString(R.string.double_tap_seek_time_key), 10)
.toLong() * 1000L .toLong() * 1000L
androidTVInterfaceOffSeekTime =
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_off_seek_key), 10)
.toLong() * 1000L
androidTVInterfaceOnSeekTime =
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_on_seek_key), 10)
.toLong() * 1000L
navigationBarHeight = ctx.getNavigationBarHeight() navigationBarHeight = ctx.getNavigationBarHeight()
statusBarHeight = ctx.getStatusBarHeight() statusBarHeight = ctx.getStatusBarHeight()
@ -1257,8 +1237,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
ctx.getString(R.string.double_tap_pause_enabled_key), ctx.getString(R.string.double_tap_pause_enabled_key),
false false
) )
currentPrefQuality = settingsManager.getInt( 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 currentPrefQuality
) )
// useSystemBrightness = // useSystemBrightness =

View file

@ -11,8 +11,11 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.* import android.widget.*
import android.widget.TextView.OnEditorActionListener
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.animation.addListener import androidx.core.animation.addListener
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isGone import androidx.core.view.isGone
@ -525,7 +528,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
} }
var selectSourceDialog: Dialog? = null var selectSourceDialog: AlertDialog? = null
// var selectTracksDialog: AlertDialog? = null // var selectTracksDialog: AlertDialog? = null
override fun showMirrorsDialogue() { override fun showMirrorsDialogue() {
@ -537,8 +540,10 @@ class GeneratorPlayer : FullScreenPlayer() {
player.handleEvent(CSPlayerEvent.Pause) player.handleEvent(CSPlayerEvent.Pause)
val currentSubtitles = sortSubs(currentSubs) val currentSubtitles = sortSubs(currentSubs)
val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) val sourceBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack)
sourceDialog.setContentView(R.layout.player_select_source_and_subs) .setView(R.layout.player_select_source_and_subs)
val sourceDialog = sourceBuilder.create()
selectSourceDialog = sourceDialog selectSourceDialog = sourceDialog
@ -733,17 +738,19 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
val currentAudioTracks = tracks.allAudioTracks val currentAudioTracks = tracks.allAudioTracks
val trackDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) val trackBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack)
trackDialog.setContentView(R.layout.player_select_tracks) .setView(R.layout.player_select_tracks)
trackDialog.show()
val tracksDialog = trackBuilder.create()
// selectTracksDialog = tracksDialog // selectTracksDialog = tracksDialog
val videosList = trackDialog.video_tracks_list tracksDialog.show()
val audioList = trackDialog.auto_tracks_list val videosList = tracksDialog.video_tracks_list
val audioList = tracksDialog.auto_tracks_list
trackDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1 tracksDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1
trackDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1 tracksDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1
fun dismiss() { fun dismiss() {
if (isPlaying) { if (isPlaying) {
@ -778,7 +785,7 @@ class GeneratorPlayer : FullScreenPlayer() {
videosList.setItemChecked(which, true) videosList.setItemChecked(which, true)
} }
trackDialog.setOnDismissListener { tracksDialog.setOnDismissListener {
dismiss() dismiss()
// selectTracksDialog = null // selectTracksDialog = null
} }
@ -808,11 +815,11 @@ class GeneratorPlayer : FullScreenPlayer() {
audioList.setItemChecked(which, true) audioList.setItemChecked(which, true)
} }
trackDialog.cancel_btt?.setOnClickListener { tracksDialog.cancel_btt?.setOnClickListener {
trackDialog.dismissSafe(activity) tracksDialog.dismissSafe(activity)
} }
trackDialog.apply_btt?.setOnClickListener { tracksDialog.apply_btt?.setOnClickListener {
val currentTrack = currentAudioTracks.getOrNull(audioIndexStart) val currentTrack = currentAudioTracks.getOrNull(audioIndexStart)
player.setPreferredAudioTrack( player.setPreferredAudioTrack(
currentTrack?.language, currentTrack?.id currentTrack?.language, currentTrack?.id
@ -825,7 +832,7 @@ class GeneratorPlayer : FullScreenPlayer() {
player.setMaxVideoSize(width, height, currentVideo?.id) player.setMaxVideoSize(width, height, currentVideo?.id)
} }
trackDialog.dismissSafe(activity) tracksDialog.dismissSafe(activity)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -1142,7 +1149,7 @@ class GeneratorPlayer : FullScreenPlayer() {
val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL" val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL"
val title = when (titleRez) { val title = when (titleRez) {
0 -> "" 0 -> ""
1 -> extra 1 -> extra
2 -> source 2 -> source

View file

@ -5,15 +5,8 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import java.net.URI 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( class LinkGenerator(
private val links: List<BasicLink>, private val links: List<String>,
private val extract: Boolean = true, private val extract: Boolean = true,
private val referer: String? = null, private val referer: String? = null,
private val isM3u8: Boolean? = null private val isM3u8: Boolean? = null
@ -54,7 +47,7 @@ class LinkGenerator(
offset: Int offset: Int
): Boolean { ): Boolean {
links.amap { link -> links.amap { link ->
if (!extract || !loadExtractor(link.url, referer, { if (!extract || !loadExtractor(link, referer, {
subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it)) subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it))
}) { }) {
callback(it to null) callback(it to null)
@ -64,11 +57,11 @@ class LinkGenerator(
callback( callback(
ExtractorLink( ExtractorLink(
"", "",
link.name ?: link.url, link,
unshortenLinkSafe(link.url), // unshorten because it might be a raw link unshortenLinkSafe(link), // unshorten because it might be a raw link
referer ?: "", referer ?: "",
Qualities.Unknown.value, isM3u8 ?: normalSafeApiCall { Qualities.Unknown.value, isM3u8 ?: normalSafeApiCall {
URI(link.url).path?.substringAfterLast(".")?.contains("m3u") URI(link).path?.substringAfterLast(".")?.contains("m3u")
} ?: false } ?: false
) to null ) to null
) )

View file

@ -7,13 +7,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) { fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) {
if (this == null) return if(this == null) return
this.layoutManager = this.layoutManager =
this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } } this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } }
?: this.layoutManager ?: this.layoutManager
} }
open class LinearListLayout(context: Context?) : class LinearListLayout(context: Context?) :
LinearLayoutManager(context) { LinearLayoutManager(context) {
fun setHorizontal() { fun setHorizontal() {
@ -24,8 +24,7 @@ open class LinearListLayout(context: Context?) :
orientation = VERTICAL orientation = VERTICAL
} }
private fun getCorrectParent(focused: View?): View? { private fun getCorrectParent(focused: View): View? {
if (focused == null) return null
var current: View? = focused var current: View? = focused
val last: ArrayList<View> = arrayListOf(focused) val last: ArrayList<View> = arrayListOf(focused)
while (current != null && current !is RecyclerView) { while (current != null && current !is RecyclerView) {
@ -55,17 +54,10 @@ open class LinearListLayout(context: Context?) :
linearSmoothScroller.targetPosition = position linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller) startSmoothScroll(linearSmoothScroller)
}*/ }*/
override fun onInterceptFocusSearch(focused: View, direction: Int): View? { override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
val dir = if (orientation == HORIZONTAL) { val dir = if (orientation == HORIZONTAL) {
if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) { if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) return null
// 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_RIGHT) 1 else -1 if (direction == View.FOCUS_RIGHT) 1 else -1
} else { } else {
if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null

View file

@ -15,28 +15,24 @@ import android.view.ViewGroup
import android.widget.AbsListView import android.widget.AbsListView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.discord.panels.OverlappingPanelsLayout import com.discord.panels.OverlappingPanelsLayout
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable import com.google.android.material.chip.ChipDrawable
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
@ -532,25 +528,6 @@ open class ResultFragment : ResultTrailerPlayer() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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() result_cast_items?.adapter = ActorAdaptor()
updateUIListener = ::updateUI updateUIListener = ::updateUI
@ -873,7 +850,7 @@ open class ResultFragment : ResultTrailerPlayer() {
} }
observe(viewModel.page) { data -> observe(viewModel.page) { data ->
if (data == null) return@observe if(data == null) return@observe
when (data) { when (data) {
is Resource.Success -> { is Resource.Success -> {
val d = data.value val d = data.value
@ -927,36 +904,6 @@ open class ResultFragment : ResultTrailerPlayer() {
updateList(d.actors ?: emptyList()) 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?.isVisible = d.url.startsWith("http")
result_open_in_browser?.setOnClickListener { result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW) val i = Intent(ACTION_VIEW)
@ -1027,7 +974,6 @@ open class ResultFragment : ResultTrailerPlayer() {
chip.isCheckable = false chip.isCheckable = false
chip.isFocusable = false chip.isFocusable = false
chip.isClickable = false chip.isClickable = false
chip.setTextColor(context.colorFromAttribute(R.attr.textColor))
addView(chip) addView(chip)
} }
} }

View file

@ -322,7 +322,9 @@ class ResultFragmentPhone : ResultFragment() {
// it?.dismiss() // it?.dismiss()
//} //}
builder.setCanceledOnTouchOutside(true) builder.setCanceledOnTouchOutside(true)
builder.show() builder.show()
builder builder
} }
} }

View file

@ -176,7 +176,8 @@ class ResultFragmentTv : ResultFragment() {
loadingDialog = null loadingDialog = null
} }
loadingDialog = loadingDialog ?: context?.let { ctx -> loadingDialog = loadingDialog ?: context?.let { ctx ->
val builder = BottomSheetDialog(ctx) val builder =
BottomSheetDialog(ctx)
builder.setContentView(R.layout.bottom_loading) builder.setContentView(R.layout.bottom_loading)
builder.setOnDismissListener { builder.setOnDismissListener {
loadingDialog = null loadingDialog = null
@ -186,7 +187,9 @@ class ResultFragmentTv : ResultFragment() {
// it?.dismiss() // it?.dismiss()
//} //}
builder.setCanceledOnTouchOutside(true) builder.setCanceledOnTouchOutside(true)
builder.show() builder.show()
builder builder
} }
} }

View file

@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
import android.app.Activity import android.app.Activity
import android.content.* import android.content.*
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
@ -17,7 +16,6 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.getCastSession
import com.lagradost.cloudstream3.CommonActivity.showToast 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.LoadResponse.Companion.isMovie
import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
@ -416,9 +413,6 @@ class ResultViewModel2 : ViewModel() {
private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null) private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null)
val episodeSynopsis: LiveData<String?> = _episodeSynopsis val episodeSynopsis: LiveData<String?> = _episodeSynopsis
private val _subscribeStatus: MutableLiveData<Boolean?> = MutableLiveData(null)
val subscribeStatus: LiveData<Boolean?> = _subscribeStatus
companion object { companion object {
const val TAG = "RVM2" const val TAG = "RVM2"
private const val EPISODE_RANGE_SIZE = 20 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( private fun startChromecast(
activity: Activity?, activity: Activity?,
result: ResultEpisode, result: ResultEpisode,
@ -1126,12 +1084,7 @@ class ResultViewModel2 : ViewModel() {
1L 1L
} }
// Component no longer safe to use in A13 for VLC component = VLC_COMPONENT
// 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("from_start", !resume)
putExtra("position", position) putExtra("position", position)
@ -1469,128 +1422,85 @@ class ResultViewModel2 : ViewModel() {
meta: SyncAPI.SyncResult?, meta: SyncAPI.SyncResult?,
syncs: Map<String, String>? = null syncs: Map<String, String>? = null
): Pair<LoadResponse, Boolean> { ): Pair<LoadResponse, Boolean> {
//if (meta == null) return resp to false if (meta == null) return resp to false
var updateEpisodes = false var updateEpisodes = false
val out = resp.apply { val out = resp.apply {
Log.i(TAG, "applyMeta") Log.i(TAG, "applyMeta")
if (meta != null) { duration = duration ?: meta.duration
duration = duration ?: meta.duration rating = rating ?: meta.publicScore
rating = rating ?: meta.publicScore tags = tags ?: meta.genres
tags = tags ?: meta.genres plot = if (plot.isNullOrBlank()) meta.synopsis else plot
plot = if (plot.isNullOrBlank()) meta.synopsis else plot posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl actors = actors ?: meta.actors
actors = actors ?: meta.actors
if (this is EpisodeResponse) { if (this is EpisodeResponse) {
nextAiring = nextAiring ?: meta.nextAiring nextAiring = nextAiring ?: meta.nextAiring
}
val realRecommendations = ArrayList<SearchResponse>()
val apiNames = apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}
meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))
}
}
recommendations = recommendations?.union(realRecommendations)?.toList()
?: realRecommendations
} }
for ((k, v) in syncs ?: emptyMap()) { for ((k, v) in syncs ?: emptyMap()) {
syncData[k] = v syncData[k] = v
} }
argamap( val realRecommendations = ArrayList<SearchResponse>()
{ // TODO: fix
if (this !is AnimeLoadResponse) return@argamap val apiNames = apis.filter {
// already exist, no need to run getTracker it.name.contains("gogoanime", true) ||
if (this.getAniListId() != null && this.getMalId() != null) return@argamap it.name.contains("9anime", true)
}.map {
it.name
}
val res = APIHolder.getTracker( meta.recommendations?.forEach { rec ->
listOfNotNull( apiNames.forEach { name ->
this.engName, realRecommendations.add(rec.copy(apiName = name))
this.name, }
this.japName }
).filter { it.length > 2 }
.distinct(), // the reason why we filter is due to not wanting smth like " " or "?" recommendations = recommendations?.union(realRecommendations)?.toList()
TrackerType.getTypes(this.type), ?: realRecommendations
this.year
argamap({
addTrailer(meta.trailers)
}, {
if (this !is AnimeLoadResponse) return@argamap
val map =
Kitsu.getEpisodesDetails(
getMalId(),
getAniListId(),
isResponseRequired = false
) )
if (map.isNullOrEmpty()) return@argamap
val ids = arrayOf( updateEpisodes = DubStatus.values().map { dubStatus ->
AccountManager.malApi.idPrefix to res?.malId?.toString(), val current =
AccountManager.aniListApi.idPrefix to res?.aniId this.episodes[dubStatus]?.mapIndexed { index, episode ->
) episode.apply {
this.episode = this.episode ?: (index + 1)
if (ids.any { (id, new) -> }
val current = syncData[id] }?.sortedBy { it.episode ?: 0 }?.toMutableList()
new != null && current != null && current != new if (current.isNullOrEmpty()) return@map false
} val episodeNumbers = current.map { ep -> ep.episode!! }
) { var updateCount = 0
// getTracker fucked up as it conflicts with current implementation map.forEach { (episode, node) ->
return@argamap episodeNumbers.binarySearch(episode).let { index ->
} current.getOrNull(index)?.let { currentEp ->
current[index] = currentEp.apply {
// set all the new data, prioritise old correct data updateCount++
ids.forEach { (id, new) -> val currentBack = this
new?.let { this.description = this.description ?: node.description?.en
syncData[id] = syncData[id] ?: it this.name = this.name ?: node.titles?.canonical
} this.episode =
} this.episode ?: node.num ?: episodeNumbers[index]
this.posterUrl =
// set posters, might fuck up due to headers idk this.posterUrl ?: node.thumbnail?.original?.url
posterUrl = posterUrl ?: res?.image
backgroundPosterUrl = backgroundPosterUrl ?: res?.cover
},
{
if (meta == null) return@argamap
addTrailer(meta.trailers)
}, {
if (this !is AnimeLoadResponse) return@argamap
val map =
Kitsu.getEpisodesDetails(
getMalId(),
getAniListId(),
isResponseRequired = false
)
if (map.isNullOrEmpty()) return@argamap
updateEpisodes = DubStatus.values().map { dubStatus ->
val current =
this.episodes[dubStatus]?.mapIndexed { index, episode ->
episode.apply {
this.episode = this.episode ?: (index + 1)
}
}?.sortedBy { it.episode ?: 0 }?.toMutableList()
if (current.isNullOrEmpty()) return@map false
val episodeNumbers = current.map { ep -> ep.episode!! }
var updateCount = 0
map.forEach { (episode, node) ->
episodeNumbers.binarySearch(episode).let { index ->
current.getOrNull(index)?.let { currentEp ->
current[index] = currentEp.apply {
updateCount++
this.description = this.description ?: node.description?.en
this.name = this.name ?: node.titles?.canonical
this.episode =
this.episode ?: node.num ?: episodeNumbers[index]
this.posterUrl =
this.posterUrl ?: node.thumbnail?.original?.url
}
} }
} }
} }
this.episodes[dubStatus] = current }
updateCount > 0 this.episodes[dubStatus] = current
}.any { it } updateCount > 0
}) }.any { it }
})
} }
return out to updateEpisodes return out to updateEpisodes
} }
@ -1717,16 +1627,6 @@ class ResultViewModel2 : ViewModel() {
postResume() 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?) { private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
if (range == null || indexer == null) { if (range == null || indexer == null) {
return return
@ -1863,7 +1763,6 @@ class ResultViewModel2 : ViewModel() {
) { ) {
currentResponse = loadResponse currentResponse = loadResponse
postPage(loadResponse, apiRepository) postPage(loadResponse, apiRepository)
postSubscription(loadResponse)
if (updateEpisodes) if (updateEpisodes)
postEpisodes(loadResponse, updateFillers) postEpisodes(loadResponse, updateFillers)
} }
@ -2225,7 +2124,7 @@ class ResultViewModel2 : ViewModel() {
autostart: AutoResume?, autostart: AutoResume?,
loadTrailers: Boolean = true, loadTrailers: Boolean = true,
) = ) =
ioSafe { viewModelScope.launchSafe {
_page.postValue(Resource.Loading(url)) _page.postValue(Resource.Loading(url))
_episodes.postValue(ResourceSome.Loading()) _episodes.postValue(ResourceSome.Loading())
@ -2243,7 +2142,7 @@ class ResultViewModel2 : ViewModel() {
"This provider does not exist" "This provider does not exist"
) )
) )
return@ioSafe return@launchSafe
} }
@ -2254,15 +2153,21 @@ class ResultViewModel2 : ViewModel() {
api 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.Success) {
if (validUrlResource is Resource.Failure) { if (validUrlResource is Resource.Failure) {
_page.postValue(validUrlResource) _page.postValue(validUrlResource)
} }
return@ioSafe return@launchSafe
} }
val validUrl = validUrlResource.value val validUrl = validUrlResource.value
val repo = APIRepository(api) val repo = APIRepository(api)
currentRepo = repo currentRepo = repo
@ -2272,11 +2177,11 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(data) _page.postValue(data)
} }
is Resource.Success -> { is Resource.Success -> {
if (!isActive) return@ioSafe if (!isActive) return@launchSafe
val loadResponse = ioWork { val loadResponse = ioWork {
applyMeta(data.value, currentMeta, currentSync).first applyMeta(data.value, currentMeta, currentSync).first
} }
if (!isActive) return@ioSafe if (!isActive) return@launchSafe
val mainId = loadResponse.getId() val mainId = loadResponse.getId()
preferDubStatus = getDub(mainId) ?: preferDubStatus preferDubStatus = getDub(mainId) ?: preferDubStatus
@ -2304,7 +2209,7 @@ class ResultViewModel2 : ViewModel() {
updateFillers = showFillers, updateFillers = showFillers,
apiRepository = repo apiRepository = repo
) )
if (!isActive) return@ioSafe if (!isActive) return@launchSafe
handleAutoStart(activity, autostart) handleAutoStart(activity, autostart)
} }
is Resource.Loading -> { is Resource.Loading -> {

View file

@ -157,28 +157,6 @@ class SettingsAccount : PreferenceFragmentCompat() {
) )
dialog.dismissSafe() 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 dialog.text1?.text = api.name
if (api.storesPasswordInPlainText) { if (api.storesPasswordInPlainText) {

View file

@ -56,50 +56,48 @@ fun getCurrentLocale(context: Context): String {
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto
val appLanguages = arrayListOf( val appLanguages = arrayListOf(
/* begin language list */ /* begin language list */
Triple("", "العربية", "ar"), Triple("", "Arabic", "ar"),
Triple("", "български", "bg"), Triple("", "Bulgarian", "bg"),
Triple("", "বাংলা", "bn"), Triple("", "Bengali", "bn"),
Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"), Triple("\uD83C\uDDE7\uD83C\uDDF7", "Brazilian Portuguese", "bp"),
Triple("", "čeština", "cs"), Triple("", "Czech", "cs"),
Triple("", "Deutsch", "de"), Triple("", "German", "de"),
Triple("", "Ελληνικά", "el"), Triple("", "Greek", "el"),
Triple("", "English", "en"), Triple("", "English", "en"),
Triple("", "Esperanto", "eo"), Triple("", "Esperanto", "eo"),
Triple("", "español", "es"), Triple("", "Spanish", "es"),
Triple("", "فارسی", "fa"), Triple("", "Farsi", "fa"),
Triple("", "français", "fr"), Triple("", "French", "fr"),
Triple("", "हिन्दी", "hi"), Triple("", "Hindi", "hi"),
Triple("", "hrvatski", "hr"), Triple("", "Croatian", "hr"),
Triple("", "magyar", "hu"), Triple("", "Hungarian", "hu"),
Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"), Triple("\uD83C\uDDEE\uD83C\uDDE9", "Indonesian", "in"),
Triple("", "italiano", "it"), Triple("", "Italian", "it"),
Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"), Triple("\uD83C\uDDEE\uD83C\uDDF1", "Hebrew", "iw"),
Triple("", "日本語 (にほんご)", "ja"), Triple("", "Kannada", "kn"),
Triple("", "ಕನ್ನಡ", "kn"), Triple("", "Macedonian", "mk"),
Triple("", "македонски", "mk"), Triple("", "Malayalam", "ml"),
Triple("", "മലയാളം", "ml"), Triple("", "Moldavian", "mo"),
Triple("", "bahasa Melayu", "ms"), Triple("", "Dutch", "nl"),
Triple("", "Nederlands", "nl"), Triple("", "Norwegian Nynorsk", "nn"),
Triple("", "norsk nynorsk", "nn"), Triple("", "Norwegian", "no"),
Triple("", "norsk bokmål", "no"), Triple("", "Polish", "pl"),
Triple("", "polski", "pl"), Triple("\uD83C\uDDF5\uD83C\uDDF9", "Portuguese", "pt"),
Triple("\uD83C\uDDF5\uD83C\uDDF9", "português", "pt"), Triple("", "Romanian", "ro"),
Triple("\uD83E\uDD8D", "mmmm... monke", "qt"), Triple("", "Russian", "ru"),
Triple("", "română", "ro"), Triple("", "Slovak", "sk"),
Triple("", "русский", "ru"), Triple("", "Somali", "so"),
Triple("", "slovenčina", "sk"), Triple("", "Swedish", "sv"),
Triple("", "Soomaaliga", "so"), Triple("", "Tamil", "ta"),
Triple("", "svenska", "sv"),
Triple("", "தமிழ்", "ta"),
Triple("", "Tagalog", "tl"), Triple("", "Tagalog", "tl"),
Triple("", "Türkçe", "tr"), Triple("", "Turkish", "tr"),
Triple("", "українська", "uk"), Triple("", "Ukrainian", "uk"),
Triple("", "اردو", "ur"), Triple("", "Urdu", "ur"),
Triple("", "Tiếng Việt", "vi"), Triple("", "Viet Nam", "vi"),
Triple("", "中文", "zh"), Triple("", "Chinese Simplified", "zh"),
Triple("\uD83C\uDDF9\uD83C\uDDFC", "文言", "zh-rTW"), Triple("\uD83C\uDDF9\uD83C\uDDFC", "Chinese Traditional", "zh-rTW"),
/* end language list */ /* 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() { class SettingsGeneral : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -159,6 +157,9 @@ class SettingsGeneral : PreferenceFragmentCompat() {
getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref ->
val tempLangs = appLanguages.toMutableList() val tempLangs = appLanguages.toMutableList()
//if (beneneCount > 100) {
// tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo"))
//}
val current = getCurrentLocale(pref.context) val current = getCurrentLocale(pref.context)
val languageCodes = tempLangs.map { (_, _, iso) -> iso } val languageCodes = tempLangs.map { (_, _, iso) -> iso }
val languageNames = tempLangs.map { (emoji, name, iso) -> val languageNames = tempLangs.map { (emoji, name, iso) ->
@ -315,12 +316,6 @@ class SettingsGeneral : PreferenceFragmentCompat() {
} ?: emptyList() } ?: 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 { getPref(R.string.download_path_key)?.setOnPreferenceClickListener {
val dirs = getDownloadDirs() val dirs = getDownloadDirs()

View file

@ -113,30 +113,6 @@ class SettingsPlayer : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true 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 { getPref(R.string.player_pref_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.player_pref_names) val prefNames = resources.getStringArray(R.array.player_pref_names)
val prefValues = resources.getIntArray(R.array.player_pref_values) val prefValues = resources.getIntArray(R.array.player_pref_values)

View file

@ -2,8 +2,6 @@ package com.lagradost.cloudstream3.ui.settings
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.* 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.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate
class SettingsProviders : PreferenceFragmentCompat() { class SettingsProviders : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -59,20 +56,6 @@ class SettingsProviders : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true 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 { getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
val names = enumValues<TvType>().sorted().map { it.name } val names = enumValues<TvType>().sorted().map { it.name }
val default = val default =

View file

@ -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)
}
}

View file

@ -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()
}
}
}
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -1,11 +1,8 @@
package com.lagradost.cloudstream3.utils package com.lagradost.cloudstream3.utils
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.Activity.RESULT_CANCELED import android.app.Activity.RESULT_CANCELED
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.* import android.content.*
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.database.Cursor import android.database.Cursor
@ -20,7 +17,6 @@ import android.os.*
import android.provider.MediaStore import android.provider.MediaStore
import android.text.Spanned import android.text.Spanned
import android.util.Log import android.util.Log
import android.view.animation.DecelerateInterpolator
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@ -29,7 +25,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.core.widget.ContentLoadingProgressBar
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -184,36 +179,6 @@ object AppUtils {
touchSlopField.set(recyclerView, touchSlop * f) // "8" was obtained experimentally 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") @SuppressLint("RestrictedApi")
fun getAllWatchNextPrograms(context: Context): Set<Long> { fun getAllWatchNextPrograms(context: Context): Set<Long> {
val COLUMN_WATCH_NEXT_ID_INDEX = 0 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> { fun splitQuery(url: URL): Map<String, String> {
val queryPairs: MutableMap<String, String> = LinkedHashMap() val queryPairs: MutableMap<String, String> = LinkedHashMap()
val query: String = url.query val query: String = url.query
@ -776,13 +735,8 @@ object AppUtils {
return networkInfo.any { return networkInfo.any {
conManager.getNetworkCapabilities(it) conManager.getNetworkCapabilities(it)
?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true ?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
} &&
!networkInfo.any {
conManager.getNetworkCapabilities(it)
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
}
} }
}
private fun Activity?.cacheClass(clazz: String?) { private fun Activity?.cacheClass(clazz: String?) {
clazz?.let { c -> clazz?.let { c ->

View file

@ -9,7 +9,6 @@ import android.provider.MediaStore
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.WorkerThread
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
@ -28,8 +27,6 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_T
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY
import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs
import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs
import com.lagradost.cloudstream3.utils.DataStore.mapper import com.lagradost.cloudstream3.utils.DataStore.mapper
@ -120,7 +117,6 @@ object BackupUtils {
) )
} }
@WorkerThread
fun Context.restore( fun Context.restore(
backupFile: BackupFile, backupFile: BackupFile,
restoreSettings: Boolean, restoreSettings: Boolean,
@ -223,29 +219,31 @@ object BackupUtils {
try { try {
restoreFileSelector = restoreFileSelector =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
if (uri == null) return@registerForActivityResult this.let { activity ->
val activity = this uri?.let {
ioSafe { try {
try { val input =
val input = activity.contentResolver.openInputStream(uri) activity.contentResolver.openInputStream(uri)
?: return@ioSafe ?: return@registerForActivityResult
val restoredValue = val restoredValue =
mapper.readValue<BackupFile>(input) mapper.readValue<BackupFile>(input)
activity.restore(
activity.restore( restoredValue,
restoredValue, restoreSettings = true,
restoreSettings = true, restoreDataStore = true
restoreDataStore = true
)
activity.runOnUiThread { activity.recreate() }
} catch (e: Exception) {
logError(e)
main { // smth can fail in .format
showToast(
activity,
getString(R.string.restore_failed_format).format(e.toString())
) )
activity.recreate()
} catch (e: Exception) {
logError(e)
try { // smth can fail in .format
showToast(
activity,
getString(R.string.restore_failed_format).format(e.toString())
)
} catch (e: Exception) {
logError(e)
}
} }
} }
} }

View file

@ -1,13 +1,16 @@
package com.lagradost.cloudstream3.utils package com.lagradost.cloudstream3.utils
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.capitalize
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey 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.AccountManager
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.WatchType 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 VIDEO_WATCH_STATE = "video_watch_state"
const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE = "result_watch_state"
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" 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 = "result_resume_watching_2" // changed due to id changes
const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching"
const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated"
@ -40,37 +42,6 @@ object DataStoreHelper {
return this 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( data class BookmarkedData(
@JsonProperty("id") override var id: Int?, @JsonProperty("id") override var id: Int?,
@JsonProperty("bookmarkedTime") val bookmarkedTime: Long, @JsonProperty("bookmarkedTime") val bookmarkedTime: Long,
@ -92,7 +63,7 @@ object DataStoreHelper {
null, null,
null, null,
null, null,
latestUpdatedTime, null,
apiName, type, posterUrl, posterHeaders, quality, this.id apiName, type, posterUrl, posterHeaders, quality, this.id
) )
} }
@ -104,7 +75,9 @@ object DataStoreHelper {
@JsonProperty("apiName") override val apiName: String, @JsonProperty("apiName") override val apiName: String,
@JsonProperty("type") override var type: TvType? = null, @JsonProperty("type") override var type: TvType? = null,
@JsonProperty("posterUrl") override var posterUrl: String?, @JsonProperty("posterUrl") override var posterUrl: String?,
@JsonProperty("watchPos") val watchPos: PosDur?, @JsonProperty("watchPos") val watchPos: PosDur?,
@JsonProperty("id") override var id: Int?, @JsonProperty("id") override var id: Int?,
@JsonProperty("parentId") val parentId: Int?, @JsonProperty("parentId") val parentId: Int?,
@JsonProperty("episode") val episode: Int?, @JsonProperty("episode") val episode: Int?,
@ -231,41 +204,6 @@ object DataStoreHelper {
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) 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) { fun setViewPos(id: Int?, pos: Long, dur: Long) {
if (id == null) return if (id == null) return
if (dur < 30_000) return // too short if (dur < 30_000) return // too short

View file

@ -52,7 +52,7 @@ data class ExtractorLinkPlayList(
) )
open class ExtractorLink constructor( open class ExtractorLink(
open val source: String, open val source: String,
open val name: String, open val name: String,
override val url: String, override val url: String,
@ -62,24 +62,7 @@ open class ExtractorLink constructor(
override val headers: Map<String, String> = mapOf(), override val headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */ /** Used for getExtractorVerifierJob() */
open val extractorData: String? = null, open val extractorData: String? = null,
open val isDash: Boolean = false,
) : VideoDownloadManager.IDownloadableMinimum { ) : 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 { override fun toString(): String {
return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)" return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)"
} }
@ -246,7 +229,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
StreamSB8(), StreamSB8(),
StreamSB9(), StreamSB9(),
StreamSB10(), StreamSB10(),
StreamSB11(),
SBfull(), SBfull(),
// Streamhub(), cause Streamhub2() works // Streamhub(), cause Streamhub2() works
Streamhub2(), Streamhub2(),
@ -272,7 +254,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
// WatchSB(), 'cause StreamSB.kt works // WatchSB(), 'cause StreamSB.kt works
Uqload(), Uqload(),
Uqload1(), Uqload1(),
Uqload2(),
Evoload(), Evoload(),
Evoload1(), Evoload1(),
VoeExtractor(), VoeExtractor(),
@ -284,7 +265,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
OkRu(), OkRu(),
OkRuHttps(), OkRuHttps(),
Okrulink(), Okrulink(),
Sendvid(),
// dood extractors // dood extractors
DoodCxExtractor(), DoodCxExtractor(),
@ -296,7 +276,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
DoodShExtractor(), DoodShExtractor(),
DoodWatchExtractor(), DoodWatchExtractor(),
DoodWfExtractor(), DoodWfExtractor(),
DoodYtExtractor(),
AsianLoad(), AsianLoad(),
@ -312,7 +291,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Supervideo(), Supervideo(),
GuardareStream(), GuardareStream(),
CineGrabber(), CineGrabber(),
Vanfem(),
// StreamSB.kt works // StreamSB.kt works
// SBPlay(), // SBPlay(),
@ -343,9 +321,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
DesuDrive(), DesuDrive(),
Filesim(), Filesim(),
FileMoon(),
FileMoonSx(),
Vido(),
Linkbox(), Linkbox(),
Acefile(), Acefile(),
SpeedoStream(), SpeedoStream(),
@ -387,7 +362,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Cda(), Cda(),
Dailymotion(), Dailymotion(),
ByteShare(), ByteShare(),
Ztreamhub()
) )

View file

@ -91,7 +91,7 @@ class ApkInstaller(private val service: PackageInstallerService) {
session.openWrite(context.packageName, 0, size) session.openWrite(context.packageName, 0, size)
.use { outputStream -> .use { outputStream ->
val buffer = ByteArray(4 * 1024) val buffer = ByteArray(1024)
var bytesRead = inputStream.read(buffer) var bytesRead = inputStream.read(buffer)
while (bytesRead >= 0) { while (bytesRead >= 0) {
@ -100,7 +100,6 @@ class ApkInstaller(private val service: PackageInstallerService) {
installProgress.invoke(bytesRead) installProgress.invoke(bytesRead)
} }
session.fsync(outputStream)
inputStream.close() inputStream.close()
} }

View file

@ -14,7 +14,6 @@ import androidx.core.app.NotificationCompat
import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -48,12 +47,24 @@ class PackageInstallerService : Service() {
.setSmallIcon(R.drawable.rdload) .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() { override fun onCreate() {
this.createNotificationChannel( createNotificationChannel()
UPDATE_CHANNEL_ID,
UPDATE_CHANNEL_NAME,
UPDATE_CHANNEL_DESCRIPTION
)
startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build()) startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build())
} }

View file

@ -250,17 +250,6 @@ object SingleSelectionHelper {
) )
} }
fun showBottomDialog(
items: List<String>,
selectedIndex: Int,
name: String,
showApply: Boolean,
dismissCallback: () -> Unit,
callback: (Int) -> Unit,
) {
}
/** Only for a low amount of items */ /** Only for a low amount of items */
fun Activity?.showBottomDialog( fun Activity?.showBottomDialog(
items: List<String>, items: List<String>,

View file

@ -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)
}
}
}
}

View file

@ -20,7 +20,6 @@ import androidx.work.Data
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import com.bumptech.glide.load.model.GlideUrl
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
@ -214,7 +213,7 @@ object VideoDownloadManager {
} }
private val cachedBitmaps = hashMapOf<String, Bitmap>() 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 { try {
if (cachedBitmaps.containsKey(url)) { if (cachedBitmaps.containsKey(url)) {
return cachedBitmaps[url] return cachedBitmaps[url]
@ -222,14 +221,12 @@ object VideoDownloadManager {
val bitmap = GlideApp.with(this) val bitmap = GlideApp.with(this)
.asBitmap() .asBitmap()
.load(GlideUrl(url) { headers ?: emptyMap() }) .load(url).into(720, 720)
.into(720, 720)
.get() .get()
if (bitmap != null) { if (bitmap != null) {
cachedBitmaps[url] = bitmap cachedBitmaps[url] = bitmap
} }
return bitmap return null
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
return null return null
@ -429,7 +426,7 @@ object VideoDownloadManager {
} }
private const val reservedChars = "|\\?*<\":>+[]/\'" private const val reservedChars = "|\\?*<\":>+[]/\'"
fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String { fun sanitizeFilename(name: String, removeSpaces: Boolean= false): String {
var tempName = name var tempName = name
for (c in reservedChars) { for (c in reservedChars) {
tempName = tempName.replace(c, ' ') tempName = tempName.replace(c, ' ')
@ -1615,7 +1612,7 @@ object VideoDownloadManager {
.mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } .mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
.toTypedArray() .toTypedArray()
setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
} catch (t: Throwable) { } catch (t : Throwable) {
logError(t) logError(t)
} }
} }

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -1,34 +1,34 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:orientation="vertical"
android:layout_height="match_parent" android:layout_width="match_parent"
android:orientation="vertical"> android:layout_height="match_parent">
<TextView <TextView
android:id="@+id/text1" android:id="@+id/text1"
android:layout_width="match_parent" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:layout_height="wrap_content" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:layout_rowWeight="1" android:layout_marginTop="20dp"
android:layout_marginTop="20dp" android:layout_marginBottom="10dp"
android:layout_marginBottom="10dp" android:textStyle="bold"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:textSize="20sp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:textColor="?attr/textColor"
android:textColor="?attr/textColor" android:layout_width="match_parent"
android:textSize="20sp" android:layout_rowWeight="1"
android:textStyle="bold" tools:text="Test"
tools:text="Test" /> android:layout_height="wrap_content" />
<ListView <ListView
android:id="@+id/listview1" android:nextFocusRight="@id/cancel_btt"
android:layout_width="match_parent" android:nextFocusLeft="@id/apply_btt"
android:layout_height="match_parent"
android:layout_rowWeight="1" android:id="@+id/listview1"
android:layout_marginBottom="60dp" android:layout_marginBottom="60dp"
android:nestedScrollingEnabled="true" android:paddingTop="10dp"
android:nextFocusLeft="@id/apply_btt" android:requiresFadingEdge="vertical"
android:nextFocusRight="@id/cancel_btt" tools:listitem="@layout/sort_bottom_single_choice_no_checkmark"
android:paddingTop="10dp" android:layout_width="match_parent"
android:requiresFadingEdge="vertical" android:layout_height="match_parent"
tools:listitem="@layout/sort_bottom_single_choice_no_checkmark" /> android:layout_rowWeight="1" />
</LinearLayout> </LinearLayout>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/home_header" android:id="@+id/home_header"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -85,6 +86,7 @@
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -113,6 +115,7 @@
style="@style/WhiteButton" style="@style/WhiteButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/home_play" android:text="@string/home_play"
app:icon="@drawable/ic_baseline_play_arrow_24" /> app:icon="@drawable/ic_baseline_play_arrow_24" />
@ -145,16 +148,17 @@
<TextView <TextView
android:id="@+id/home_watch_parent_item_title" android:id="@+id/home_watch_parent_item_title"
style="@style/WatchHeaderText" style="@style/WatchHeaderText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/home_more_info"
android:padding="12dp" android:padding="12dp"
android:text="@string/continue_watching" android:text="@string/continue_watching"
app:drawableRightCompat="@drawable/ic_baseline_arrow_forward_24" 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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_watch_child_recyclerview" android:id="@+id/home_watch_child_recyclerview"
@ -163,7 +167,7 @@
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="5dp" android:paddingEnd="5dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/home_result_grid" /> tools:listitem="@layout/home_result_grid" />
@ -180,9 +184,9 @@
<FrameLayout <FrameLayout
android:id="@+id/home_bookmark_parent_item_title" android:id="@+id/home_bookmark_parent_item_title"
android:background="?android:attr/selectableItemBackground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:background="?android:attr/selectableItemBackground">
<HorizontalScrollView <HorizontalScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -258,13 +262,14 @@
</HorizontalScrollView> </HorizontalScrollView>
<ImageView <ImageView
android:layout_marginEnd="12dp"
android:layout_gravity="end"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" 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" android:src="@drawable/ic_baseline_arrow_forward_24"
app:drawableTint="?attr/white" /> app:drawableTint="?attr/white"
android:contentDescription="@string/home_more_info"/>
</FrameLayout> </FrameLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -272,9 +277,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="5dp" android:paddingEnd="5dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/home_result_grid" /> tools:listitem="@layout/home_result_grid" />
</LinearLayout> </LinearLayout>

View file

@ -166,12 +166,12 @@
app:layout_scrollFlags="noScroll" app:layout_scrollFlags="noScroll"
app:tabGravity="center" app:tabGravity="center"
app:tabIndicator="@drawable/indicator_background" app:tabIndicator="@drawable/indicator_background"
app:tabIndicatorColor="?attr/white" app:tabIndicatorColor="@color/textColor"
app:tabIndicatorGravity="center" app:tabIndicatorGravity="center"
app:tabIndicatorHeight="30dp" app:tabIndicatorHeight="30dp"
app:tabMode="scrollable" app:tabMode="scrollable"
app:tabSelectedTextColor="?attr/primaryBlackBackground" app:tabSelectedTextColor="@color/lightTextColor"
app:tabTextAppearance="@style/TabNoCaps" app:tabTextAppearance="@style/TabNoCaps"
app:tabTextColor="?attr/textColor" /> app:tabTextColor="@color/textColor" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -129,9 +129,9 @@
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/result_scroll" android:id="@+id/result_scroll"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:paddingBottom="100dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp"> android:layout_height="wrap_content">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -326,12 +326,13 @@
<ImageView <ImageView
android:id="@+id/result_poster" android:id="@+id/result_poster"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="140dp" android:layout_height="140dp"
android:layout_gravity="bottom"
android:contentDescription="@string/result_poster_img_des" android:contentDescription="@string/result_poster_img_des"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:layout_gravity="bottom"
tools:src="@drawable/example_poster" /> tools:src="@drawable/example_poster" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
@ -515,8 +516,8 @@
android:visibility="gone" /> android:visibility="gone" />
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/result_tag"
style="@style/ChipParent" style="@style/ChipParent"
android:id="@+id/result_tag"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<!--<com.lagradost.cloudstream3.widget.FlowLayout <!--<com.lagradost.cloudstream3.widget.FlowLayout
@ -817,13 +818,10 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
android:nextFocusLeft="@id/result_episode_select" android:nextFocusLeft="@id/result_episode_select"
android:nextFocusRight="@id/result_episode_select" android:nextFocusRight="@id/result_episode_select"
android:nextFocusUp="@id/result_description" android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_episodes" android:nextFocusDown="@id/result_episodes"
android:paddingStart="10dp"
android:paddingEnd="5dp"
android:visibility="gone" android:visibility="gone"
tools:text="Season 1" tools:text="Season 1"
tools:visibility="visible" /> tools:visibility="visible" />
@ -831,16 +829,16 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/result_episode_select" android:id="@+id/result_episode_select"
style="@style/MultiSelectButton" style="@style/MultiSelectButton"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
android:nextFocusLeft="@id/result_season_button" android:nextFocusLeft="@id/result_season_button"
android:nextFocusRight="@id/result_season_button" android:nextFocusRight="@id/result_season_button"
android:nextFocusUp="@id/result_description" android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_episodes" android:nextFocusDown="@id/result_episodes"
android:paddingStart="10dp"
android:paddingEnd="5dp"
android:visibility="gone" android:visibility="gone"
tools:text="50-100" tools:text="50-100"
tools:visibility="visible" /> tools:visibility="visible" />
@ -848,16 +846,15 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/result_dub_select" android:id="@+id/result_dub_select"
style="@style/MultiSelectButton" style="@style/MultiSelectButton"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
android:nextFocusLeft="@id/result_season_button" android:nextFocusLeft="@id/result_season_button"
android:nextFocusRight="@id/result_season_button" android:nextFocusRight="@id/result_season_button"
android:nextFocusUp="@id/result_description" android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_episodes" android:nextFocusDown="@id/result_episodes"
android:paddingStart="10dp"
android:paddingEnd="5dp"
android:visibility="gone" android:visibility="gone"
tools:text="Dubbed" tools:text="Dubbed"
tools:visibility="visible" /> tools:visibility="visible" />

View file

@ -57,7 +57,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="50dp"
android:id="@+id/media_route_button_holder" android:id="@+id/media_route_button_holder"
android:animateLayoutChanges="true"
android:layout_gravity="center_vertical|end"> android:layout_gravity="center_vertical|end">
<androidx.mediarouter.app.MediaRouteButton <androidx.mediarouter.app.MediaRouteButton
@ -70,35 +69,15 @@
app:mediaRouteButtonTint="?attr/textColor" /> app:mediaRouteButtonTint="?attr/textColor" />
<ImageView <ImageView
android:visibility="gone"
android:nextFocusUp="@id/result_back" android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_description" android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_add_sync" 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:nextFocusRight="@id/result_open_in_browser"
android:id="@+id/result_share" android:id="@+id/result_share"
android:layout_width="25dp" android:layout_width="25dp"
android:layout_height="25dp" android:layout_height="25dp"
android:layout_margin="5dp" android:layout_marginEnd="10dp"
android:elevation="10dp" android:elevation="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"

View file

@ -199,13 +199,17 @@
android:id="@+id/result_back" android:id="@+id/result_back"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:contentDescription="@string/go_back" android:contentDescription="@string/go_back"
android:focusable="true" android:focusable="true"
android:gravity="center_vertical" android:gravity="center_vertical"
android:nextFocusDown="@id/result_description" android:nextFocusDown="@id/result_description"
android:src="@drawable/ic_baseline_arrow_back_24" android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="?attr/white" /> app:tint="?attr/white" />
@ -381,8 +385,8 @@
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/result_tag"
style="@style/ChipParent" style="@style/ChipParent"
android:id="@+id/result_tag"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@ -419,11 +423,11 @@
<LinearLayout <LinearLayout
android:animateLayoutChanges="true"
android:id="@+id/result_movie_parent" android:id="@+id/result_movie_parent"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:animateLayoutChanges="true"
android:orientation="horizontal" android:orientation="horizontal"
tools:visibility="visible"> tools:visibility="visible">
@ -564,7 +568,6 @@
android:layout_weight="1" android:layout_weight="1"
android:minWidth="250dp" android:minWidth="250dp"
android:nextFocusLeft="@id/result_movie_progress_downloaded_holder" android:nextFocusLeft="@id/result_movie_progress_downloaded_holder"
android:nextFocusRight="@id/result_bookmark_button"
android:nextFocusDown="@id/result_resume_series_button_play" android:nextFocusDown="@id/result_resume_series_button_play"
android:text="@string/type_none" android:text="@string/type_none"
android:visibility="visible" /> android:visibility="visible" />

View file

@ -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>

View file

@ -23,7 +23,9 @@
android:nextFocusUp="@id/home_child_more_info" android:nextFocusUp="@id/home_child_more_info"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:id="@+id/home_child_recyclerview" android:id="@+id/home_child_recyclerview"
android:orientation="horizontal" android:orientation="horizontal"

View file

@ -2,30 +2,33 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:orientation="vertical">
<TextView <TextView
android:layout_marginStart="@dimen/navbar_width"
android:id="@+id/home_child_more_info" android:id="@+id/home_child_more_info"
style="@style/WatchHeaderText" android:padding="12dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/navbar_width" style="@style/WatchHeaderText"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
android:padding="12dp"
tools:text="Trending" /> tools:text="Trending" />
<androidx.recyclerview.widget.RecyclerView <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:id="@+id/home_child_recyclerview"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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" /> tools:listitem="@layout/home_result_grid" />
</LinearLayout> </LinearLayout>

View file

@ -44,7 +44,6 @@
android:nextFocusLeft="@id/sort_subtitles" android:nextFocusLeft="@id/sort_subtitles"
android:nextFocusRight="@id/apply_btt" android:nextFocusRight="@id/apply_btt"
android:requiresFadingEdge="vertical" android:requiresFadingEdge="vertical"
tools:layout_height="100dp"
tools:listitem="@layout/sort_bottom_single_choice" /> tools:listitem="@layout/sort_bottom_single_choice" />
</LinearLayout> </LinearLayout>
@ -118,7 +117,6 @@
android:nextFocusLeft="@id/sort_providers" android:nextFocusLeft="@id/sort_providers"
android:nextFocusRight="@id/cancel_btt" android:nextFocusRight="@id/cancel_btt"
android:requiresFadingEdge="vertical" android:requiresFadingEdge="vertical"
tools:layout_height="200dp"
tools:listfooter="@layout/sort_bottom_footer_add_choice" tools:listfooter="@layout/sort_bottom_footer_add_choice"
tools:listitem="@layout/sort_bottom_single_choice" /> tools:listitem="@layout/sort_bottom_single_choice" />
</LinearLayout> </LinearLayout>

View file

@ -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>

View file

@ -23,7 +23,7 @@
android:elevation="10dp" android:elevation="10dp"
app:cardBackgroundColor="?attr/primaryGrayBackground" app:cardBackgroundColor="?attr/primaryGrayBackground"
app:cardCornerRadius="@dimen/rounded_image_radius" app:cardCornerRadius="@dimen/rounded_image_radius"
app:layout_constraintDimensionRatio="1:1.5" app:layout_constraintDimensionRatio="1:1.414"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">

View file

@ -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>

View file

@ -519,23 +519,6 @@
app:popExitAnim="@anim/exit_anim" app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_player" /> 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 <fragment
android:id="@+id/navigation_setup_language" android:id="@+id/navigation_setup_language"

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS --> <!-- 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> <string name="result_poster_img_des">ملصق</string>
@ -103,7 +103,7 @@
<string name="action_open_watching">مزيد من المعلومات</string> <string name="action_open_watching">مزيد من المعلومات</string>
<string name="vpn_might_be_needed">قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح</string> <string name="vpn_might_be_needed">قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح</string>
<string name="vpn_torrent">هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة</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="torrent_plot">الوصف</string>
<string name="normal_no_plot">لم يتم العثور على وصف</string> <string name="normal_no_plot">لم يتم العثور على وصف</string>
<string name="torrent_no_plot">لم يتم العثور على وصف</string> <string name="torrent_no_plot">لم يتم العثور على وصف</string>
@ -119,16 +119,16 @@
<string name="eigengraumode_settings">وضع إيغنغرافي</string> <string name="eigengraumode_settings">وضع إيغنغرافي</string>
<string name="eigengraumode_settings_des">يضيف خيار السرعة في المُشغل</string> <string name="eigengraumode_settings_des">يضيف خيار السرعة في المُشغل</string>
<string name="swipe_to_seek_settings">السحب لتقديم</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">السحب لتغيير الإعدادات</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">تشغيل الحلقة التالية تلقائيًا</string>
<string name="autoplay_next_settings_des">تبدأ الحلقة التالية عندما تنتهي الحالية</string> <string name="autoplay_next_settings_des">تبدأ الحلقة التالية عندما تنتهي الحالية</string>
<string name="double_tap_to_seek_settings">النقر مرتان للتقديم للأمام أو للخلف</string> <string name="double_tap_to_seek_settings">النقر مرتان للتقديم للأمام أو للخلف</string>
<string name="double_tap_to_pause_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_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">استخدم سطوع النظام</string>
<string name="use_system_brightness_settings_des">استخدم سطوع النظام في مُشغل التطبيق بدلاً من التراكب الداكن</string> <string name="use_system_brightness_settings_des">استخدم سطوع النظام في مُشغل التطبيق بدلاً من التراكب الداكن</string>
<string name="episode_sync_settings">تحديث تقدم المشاهدة</string> <string name="episode_sync_settings">تحديث تقدم المشاهدة</string>
@ -155,7 +155,7 @@
<string name="automatic_plugin_updates">تحديث الإضافات تلقائيًا</string> <string name="automatic_plugin_updates">تحديث الإضافات تلقائيًا</string>
<string name="automatic_plugin_download">تنزيل الإضافات تلقائيًا</string> <string name="automatic_plugin_download">تنزيل الإضافات تلقائيًا</string>
<string name="updates_settings">التحديث التلقائي</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">التحديث إلى الاصدارات التجريبية (بيتا)</string>
<string name="uprereleases_settings_des">البحث عن التحديثات التجريبية بدلاً من الإصدارات الكاملة فقط</string> <string name="uprereleases_settings_des">البحث عن التحديثات التجريبية بدلاً من الإصدارات الكاملة فقط</string>
<string name="github">غيت هاب</string> <string name="github">غيت هاب</string>
@ -170,7 +170,7 @@
<string name="copy_link_toast">تم نسخ الرابط إلى الحافظة</string> <string name="copy_link_toast">تم نسخ الرابط إلى الحافظة</string>
<string name="play_episode_toast">تشغيل الحلقة</string> <string name="play_episode_toast">تشغيل الحلقة</string>
<string name="subs_default_reset_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="season">موسم</string>
<string name="no_season">لا موسم</string> <string name="no_season">لا موسم</string>
<string name="episode">حلقة</string> <string name="episode">حلقة</string>
@ -218,8 +218,8 @@
<string name="movies_singular">فيلم</string> <string name="movies_singular">فيلم</string>
<string name="tv_series_singular">مسلسل</string> <string name="tv_series_singular">مسلسل</string>
<string name="cartoons_singular">كرتون</string> <string name="cartoons_singular">كرتون</string>
<string name="anime_singular">أنيمي</string> <string name="anime_singular">أنمي</string>
<string name="ova_singular">أوفا</string> <string name="ova_singular">اوفا</string>
<string name="torrent_singular">تورنت</string> <string name="torrent_singular">تورنت</string>
<string name="documentaries_singular">وثائقي</string> <string name="documentaries_singular">وثائقي</string>
<string name="asian_drama_singular">دراما آسيوية</string> <string name="asian_drama_singular">دراما آسيوية</string>
@ -259,15 +259,15 @@
<string name="dont_show_again">لا تظهر مرة أخرى</string> <string name="dont_show_again">لا تظهر مرة أخرى</string>
<string name="skip_update">تخطي هذا التحديث</string> <string name="skip_update">تخطي هذا التحديث</string>
<string name="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">أقصى عدد لحروف عنوان مُشغل الفيديو</string>
<string name="limit_title_rez">أبعاد مُشغل الفيديو</string> <string name="limit_title_rez">أبعاد مُشغل الفيديو</string>
<string name="video_buffer_size_settings">حجم ذاكرة التخزين المؤقت للفيديو</string> <string name="video_buffer_size_settings">حجم ذاكرة التخزين المؤقت للفيديو</string>
<string name="video_buffer_length_settings">طول التخزين المؤقت</string> <string name="video_buffer_length_settings">طول التخزين المؤقت</string>
<string name="video_buffer_disk_settings">التخزين المؤقت للفيديو على القرص</string> <string name="video_buffer_disk_settings">التخزين المؤقت للفيديو على القرص</string>
<string name="video_buffer_clear_settings">مسح التخزين المؤقت للصورة والفيديو</string> <string name="video_buffer_clear_settings">مسح التخزين المؤقت للصورة والفيديو</string>
<string name="video_ram_description">يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة، مثل تلفزيون أندرويد.</string> <string name="video_ram_description">يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة ، مثل Android TV.</string>
<string name="video_disk_description">يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة، مثل تلفزيون أندرويد.</string> <string name="video_disk_description">يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة ، مثل Android TV.</string>
<string name="dns_pref">إستخدام DNS بدلا من HTTPS</string> <string name="dns_pref">إستخدام DNS بدلا من HTTPS</string>
<string name="dns_pref_summary">مفيد لتجاوز حجب مزود خدمة الإنترنت</string> <string name="dns_pref_summary">مفيد لتجاوز حجب مزود خدمة الإنترنت</string>
<string name="add_site_pref">موقع بديل (نسخة)</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="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="category_general">عام</string>
<string name="random_button_settings">زر العشوائي</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="provider_lang_settings">لغات المزود</string>
<string name="app_layout">واجهة التطبيق</string> <string name="app_layout">واجهة التطبيق</string>
<string name="preferred_media_settings">المحتوى المفضل</string> <string name="preferred_media_settings">المحتوى المفضل</string>
@ -342,7 +342,7 @@
<string name="all">الكل</string> <string name="all">الكل</string>
<string name="max">الحد الاقصي</string> <string name="max">الحد الاقصي</string>
<string name="min">الحد الأدنى</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_outline">الخطوط المحيطة</string>
<string name="subtitles_depressed">النمط المنخفض</string> <string name="subtitles_depressed">النمط المنخفض</string>
<string name="subtitles_shadow">ظل</string> <string name="subtitles_shadow">ظل</string>
@ -360,7 +360,7 @@
https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog
--> -->
<string name="subtitles_example_text">نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق</string> <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_loaded_subtitles" formatted="true">تم تحميل %s</string>
<string name="player_load_subtitles">إختيار ملف</string> <string name="player_load_subtitles">إختيار ملف</string>
<string name="player_load_subtitles_online">تحميل من الانترنت</string> <string name="player_load_subtitles_online">تحميل من الانترنت</string>
@ -521,44 +521,4 @@
<string name="update_started">بدأ التحديث</string> <string name="update_started">بدأ التحديث</string>
<string name="plugin_downloaded">تم تنزيل الإضافة</string> <string name="plugin_downloaded">تم تنزيل الإضافة</string>
<string name="action_remove_from_watched">إزالة من المشاهدة</string> <string name="action_remove_from_watched">إزالة من المشاهدة</string>
<string name="sort_alphabetical_a">الترتيب الأبجدي (من الألف إلى الياء)</string>
<string name="select_library">اختر المكتبة</string>
<string name="browser">المتصفح</string>
<string name="sort_updated_new">محدث (من الأحدث إلى الأقدم)</string>
<string name="empty_library_logged_in_message">يبدو أن هذه القائمة فارغة ، حاول التبديل إلى قائمة أخرى</string>
<string name="sort_rating_desc">التقييم (من الأعلى إلى الأدنى)</string>
<string name="sort_rating_asc">التقييم (من الأدنى إلى الأعلى)</string>
<string name="sort_alphabetical_z">الترتيب الأبجدي (من ي إلى أ)</string>
<string name="empty_library_no_accounts_message">يبدو أن مكتبتك فارغة :(
\nتسجيل الدخول إلى حساب مكتبة أو إضافة عروض إلى مكتبتك المحلية</string>
<string name="sort_updated_old">محدث (من القديم إلى الجديد)</string>
<string name="sort_by">فرز حسب</string>
<string name="sort">افرز</string>
<string name="open_with">فتح بواسطة</string>
<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> </resources>

View file

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- 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="app_dub_sub_episode_text_format">%s еп. %d</string>
<string formatted="true" name="cast_format">Актьори: %s</string> <string formatted="true" name="cast_format">Актьори: %s</string>
@ -9,6 +8,7 @@
<string formatted="true" name="next_episode_time_hour_format">%dh %dm</string> <string formatted="true" name="next_episode_time_hour_format">%dh %dm</string>
<string formatted="true" name="next_episode_time_min_format">%dm</string> <string formatted="true" name="next_episode_time_min_format">%dm</string>
<string name="result_poster_img_des">Poster</string> <string name="result_poster_img_des">Poster</string>
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
<string name="episode_poster_img_des">Episode Poster</string> <string name="episode_poster_img_des">Episode Poster</string>
<string name="home_main_poster_img_des">Main Poster</string> <string name="home_main_poster_img_des">Main Poster</string>
<string name="home_next_random_img_des">Следващ произволен</string> <string name="home_next_random_img_des">Следващ произволен</string>
@ -106,7 +106,7 @@
<string name="continue_watching">Продължете да гледате</string> <string name="continue_watching">Продължете да гледате</string>
<string name="action_remove_watching">Премахване</string> <string name="action_remove_watching">Премахване</string>
<string name="action_open_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_might_be_needed">Може да е необходим VPN, за да работи правилно този доставчик</string>
<string name="vpn_torrent">Този доставчик е торент, препоръчва се VPN</string> <string name="vpn_torrent">Този доставчик е торент, препоръчва се VPN</string>
<string name="provider_info_meta">Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта.</string> <string name="provider_info_meta">Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта.</string>
@ -224,8 +224,8 @@
<string name="movies_singular">Филм</string> <string name="movies_singular">Филм</string>
<string name="tv_series_singular">Серия</string> <string name="tv_series_singular">Серия</string>
<string name="cartoons_singular">Анимационен филм</string> <string name="cartoons_singular">Анимационен филм</string>
<string name="anime_singular">@string/anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">@string/ova</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Торент</string> <string name="torrent_singular">Торент</string>
<string name="documentaries_singular">Документален филм</string> <string name="documentaries_singular">Документален филм</string>
<string name="asian_drama_singular">Азиатска драма</string> <string name="asian_drama_singular">Азиатска драма</string>

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="result_poster_img_des">পোস্টার</string> <string name="result_poster_img_des">পোস্টার</string>
<string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string> <string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string>
@ -143,8 +143,8 @@
<string name="category_updates">হালনাগাদ ও ব্যাকআপ</string> <string name="category_updates">হালনাগাদ ও ব্যাকআপ</string>
<string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string> <string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string>
<string name="swipe_to_seek_settings">খুঁজতে সোয়াইপ করুন</string> <string name="swipe_to_seek_settings">খুঁজতে সোয়াইপ করুন</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="action_open_play">@string/home_play</string> <string name="action_open_play">\@string/home_play</string>
<string name="double_tap_to_seek_settings">আগাতে ডবল ট্যাপ করুন</string> <string name="double_tap_to_seek_settings">আগাতে ডবল ট্যাপ করুন</string>
<string name="eigengraumode_settings">আইজেনগ্রাভি মোড</string> <string name="eigengraumode_settings">আইজেনগ্রাভি মোড</string>
<string name="update_started">আপডেট শুরু হয়েছে</string> <string name="update_started">আপডেট শুরু হয়েছে</string>

View file

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- KEYS DON'T TRANSLATE --> <!-- KEYS DON'T TRANSLATE -->
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- 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="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> <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 --> <!-- 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="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="episode_poster_img_des">Episode Poster</string>
<string name="home_main_poster_img_des">Main Poster</string> <string name="home_main_poster_img_des">Main Poster</string>
<string name="home_next_random_img_des">Next Random</string> <string name="home_next_random_img_des">Next Random</string>
@ -109,7 +108,7 @@
<string name="continue_watching">Continue Assistindo</string> <string name="continue_watching">Continue Assistindo</string>
<string name="action_remove_watching">Remover</string> <string name="action_remove_watching">Remover</string>
<string name="action_open_watching">Mais Info</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_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="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> <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="movies_singular">Filme</string>
<string name="tv_series_singular">Série</string> <string name="tv_series_singular">Série</string>
<string name="cartoons_singular">Desenho Animado</string> <string name="cartoons_singular">Desenho Animado</string>
<string name="anime_singular">@string/anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">@string/ova</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Documentário</string> <string name="documentaries_singular">Documentário</string>
<string name="asian_drama_singular">Drama Asiático</string> <string name="asian_drama_singular">Drama Asiático</string>

View file

@ -1,13 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- KEYS DON'T TRANSLATE --> <!-- KEYS DON'T TRANSLATE -->
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- 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="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
<string name="cast_format" formatted="true">Hrají: %s</string> <string name="cast_format" formatted="true">Hrají: %s</string>
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS --> <!-- 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">Plakát</string> <string name="result_poster_img_des">Plakát</string>
<string name="search_poster_img_des">Plakát</string> <string name="search_poster_img_des">\@string/result_poster_img_des</string>
<string name="episode_poster_img_des">Episode Poster</string> <string name="episode_poster_img_des">Episode Poster</string>
<string name="home_main_poster_img_des">Main Poster</string> <string name="home_main_poster_img_des">Main Poster</string>
<string name="home_next_random_img_des">Next Random</string> <string name="home_next_random_img_des">Next Random</string>
@ -104,7 +103,7 @@
<string name="continue_watching">Pokračovat ve sledování</string> <string name="continue_watching">Pokračovat ve sledování</string>
<string name="action_remove_watching">Odebrat</string> <string name="action_remove_watching">Odebrat</string>
<string name="action_open_watching">Další informace</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_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="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> <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">Rychlostní režim</string>
<string name="eigengraumode_settings_des">Přidá do přehrávače možnost rychlosti</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">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">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_seek_settings">Dvojité klepnutí pro posun</string>
<string name="double_tap_to_pause_settings">Dvojité klepnutí pro pozastavení</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_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">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="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> <string name="episode_sync_settings">Aktualizovat postup sledování</string>
@ -139,8 +138,8 @@
<string name="backup_settings">Zálohovat data</string> <string name="backup_settings">Zálohovat data</string>
<string name="restore_success">Načten soubor zálohy</string> <string name="restore_success">Načten soubor zálohy</string>
<string name="restore_failed_format" formatted="true">Nepodařilo se obnovit data ze soubory %s</string> <string name="restore_failed_format" formatted="true">Nepodařilo se obnovit data ze soubory %s</string>
<string name="backup_success">Data uložena</string> <string name="backup_success">Data úspěšně uložena</string>
<string name="backup_failed">Chybí oprávnění k úložišti. Zkuste to prosím znovu.</string> <string name="backup_failed">Chybí oprávnění k úložišti, zkuste to prosím znovu</string>
<string name="backup_failed_error_format">Chyba při zálohování %s</string> <string name="backup_failed_error_format">Chyba při zálohování %s</string>
<string name="search">Search</string> <string name="search">Search</string>
<string name="category_account">Účty</string> <string name="category_account">Účty</string>
@ -152,7 +151,7 @@
<string name="bug_report_settings_on">Nebude odesílat žádná data</string> <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="show_fillers_settings">Zobrazit výplňové epizody u anime</string>
<string name="updates_settings">Zobrazit aktualizace aplikace</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">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="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> <string name="github">GitHub</string>
@ -212,8 +211,8 @@
<string name="movies_singular">Film</string> <string name="movies_singular">Film</string>
<string name="tv_series_singular">Seriál</string> <string name="tv_series_singular">Seriál</string>
<string name="cartoons_singular">Animovaný</string> <string name="cartoons_singular">Animovaný</string>
<string name="anime_singular">Anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">OVA</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Dokument</string> <string name="documentaries_singular">Dokument</string>
<string name="asian_drama_singular">Asijské drama</string> <string name="asian_drama_singular">Asijské drama</string>
@ -246,19 +245,19 @@
<string name="dont_show_again">Již nezobrazovat</string> <string name="dont_show_again">Již nezobrazovat</string>
<string name="skip_update">Přeskočit tuto aktualizace</string> <string name="skip_update">Přeskočit tuto aktualizace</string>
<string name="update">Aktualizovat</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">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="limit_title_rez">Rozlišení přehrávače</string>
<string name="video_buffer_size_settings">Velikost vyrovnávací paměti videa</string> <string name="video_buffer_size_settings">Velikost vyrovnávací paměti videa</string>
<string name="video_buffer_length_settings">Délka vyrovnávací paměti videa</string> <string name="video_buffer_length_settings">Délka vyrovnávací paměti videa</string>
<string name="video_buffer_disk_settings">Mezipaměť videa na disku</string> <string name="video_buffer_disk_settings">Mezipaměť videa na disku</string>
<string name="video_buffer_clear_settings">Vymazat mezipamět videí a obrázků</string> <string name="video_buffer_clear_settings">Vymazat mezipamět videí a obrázků</string>
<string name="video_ram_description">Při nastavení příliš vysoké hodnoty na zařízeních s malou pamětí, jako je například Android TV, může způsobit pády.</string> <string name="video_ram_description">Při nastavení příliš vysoké hodnoty způsobí náhodné pády. Neměňte, pokud máte málo paměti RAM, například u televize s Androidem nebo starého telefonu.</string>
<string name="video_disk_description">Při nastavení příliš vysoké hodnoty na zařízeních s malým dostupným úložištěm, jako je například Android TV, může způsobit pády.</string> <string name="video_disk_description">Pokud ji nastavíte příliš vysoko, může způsobit problémy v systémech s malým úložným prostorem, například v zařízeních Android TV.</string>
<string name="dns_pref">DNS přes HTTPS</string> <string name="dns_pref">DNS přes HTTPS</string>
<string name="dns_pref_summary">Užitečné pro obcházení blokací ISP</string> <string name="dns_pref_summary">Užitečné pro obcházení blokací ISP</string>
<string name="download_path_pref">Cesta stahování</string> <string name="download_path_pref">Cesta stahování</string>
<string name="nginx_url_pref">URL serveru NGINX</string> <string name="nginx_url_pref">URL serveru Nginx</string>
<string name="display_subbed_dubbed_settings">Zobrazit dabované anime/anime s titulky</string> <string name="display_subbed_dubbed_settings">Zobrazit dabované anime/anime s titulky</string>
<string name="resize_fit">Vyplnit na obrazovku</string> <string name="resize_fit">Vyplnit na obrazovku</string>
<string name="resize_fill">Roztáhnout</string> <string name="resize_fill">Roztáhnout</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="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="category_general">Obecné</string>
<string name="random_button_settings">Náhodné tlačítko</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="provider_lang_settings">Jazyk poskytovatelů</string>
<string name="app_layout">Rozložení aplikace</string> <string name="app_layout">Rozložení aplikace</string>
<string name="preferred_media_settings">Preferovaná média</string> <string name="preferred_media_settings">Preferovaná média</string>
@ -321,10 +320,10 @@
<string name="subtitles_shadow">Stín</string> <string name="subtitles_shadow">Stín</string>
<string name="subtitles_raised">Vyvýšené</string> <string name="subtitles_raised">Vyvýšené</string>
<string name="subtitle_offset">Synch. titulky</string> <string name="subtitle_offset">Synch. titulky</string>
<string name="subtitle_offset_hint">1000 ms</string> <string name="subtitle_offset_hint">1000ms</string>
<string name="subtitle_offset_title">Zpoždění titulků</string> <string name="subtitle_offset_title">Zpoždění titulků</string>
<string name="subtitle_offset_extra_hint_later_format">Toto použijte, pokud jsou titulky zobrazeny o %d ms dříve</string> <string name="subtitle_offset_extra_hint_later_format">Toto použijte, pokud jsou titulky zobrazeny o %dms dříve</string>
<string name="subtitle_offset_extra_hint_before_format">Toto použijte, pokud jsou titulky zobrazeny o %d ms později</string> <string name="subtitle_offset_extra_hint_before_format">Toto použijte, pokud jsou titulky zobrazeny o %dms později</string>
<string name="subtitle_offset_extra_hint_none_format">Žádné zpoždění titulků</string> <string name="subtitle_offset_extra_hint_none_format">Žádné zpoždění titulků</string>
<!-- <!--
Example text (pangram) can optionally be translated; if you do, include all the letters in the alphabet, Example text (pangram) can optionally be translated; if you do, include all the letters in the alphabet,
@ -371,187 +370,4 @@
<string name="extras">Extra</string> <string name="extras">Extra</string>
<string name="action_mark_as_watched">Označit jako zhlédnuté</string> <string name="action_mark_as_watched">Označit jako zhlédnuté</string>
<string name="history">Historie</string> <string name="history">Historie</string>
<string name="next_episode_time_min_format" formatted="true">%dm</string>
<string name="play_with_app_name">Přehrát s CloudStream</string>
<string name="autoplay_next_settings_des">Přehrát další epizodu po skončení aktuální</string>
<string name="show_trailers_settings">Zobrazit trailery</string>
<string name="pref_filter_search_quality">Skrýt vybranou kvalitu videa ve výsledcích vyhledávání</string>
<string name="automatic_plugin_updates">Automatické aktualizace doplňků</string>
<string name="remove_site_pref">Odebrat web</string>
<string name="error_invalid_data">Neplatné údaje</string>
<string name="error">Chyba</string>
<string name="repository_name_hint">Název repozitáře</string>
<string name="plugin_loaded">Doplněk načten</string>
<string name="setup_extensions_subtext">Stáhněte si seznam webů, které chcete používat</string>
<string name="plugins_downloaded" formatted="true">Staženo: %d</string>
<string name="audio_tracks">Zvukové stopy</string>
<string name="video_tracks">Videostopy</string>
<string name="apply_on_restart">Použít při restartu</string>
<string name="safe_mode_title">Bezpečný režim povolen</string>
<string name="extension_size">Velikost</string>
<string name="extension_authors">Autoři</string>
<string name="repository_url_hint">Adresa URL repozitáře</string>
<string name="error_invalid_url">Neplatná adresa URL</string>
<string name="automatic_plugin_download_summary">Automaticky instalovat všechny dosud nenainstalované doplňky z přidaných repozitářů.</string>
<string name="others">Ostatní</string>
<string name="trailer">Trailer</string>
<string name="network_adress_example">Odkaz na stream</string>
<string name="skip_setup">Přeskočit nastavení</string>
<string name="add_repository">Přidat repozitář</string>
<string name="plugin_deleted">Doplněk odstraněn</string>
<string name="plugin_load_fail" formatted="true">Nepodařilo se načíst %s</string>
<string name="view_public_repositories_button_short">Veřejný seznam</string>
<string name="uppercase_all_subtitles">Velká písmena u všech titulků</string>
<string name="hls_playlist">Playlist HLS</string>
<string name="player_settings_play_in_mpv">MPV</string>
<string name="player_settings_play_in_web">Webové vysílání videa</string>
<string name="app_not_found_error">Aplikace nenalezena</string>
<string name="skip_type_format" formatted="true">Přeskočit %s</string>
<string name="skip_type_op">Úvod</string>
<string name="skip_type_ed">Konec</string>
<string name="clipboard_too_large">Příliš mnoho textu. Nepodařilo se uložit do schránky.</string>
<string name="yes">Ano</string>
<string name="browser">Prohlížeč</string>
<string name="episodes_range">%d-%d</string>
<string name="library">Knihovna</string>
<string name="kitsu_settings">Zobrazit plakáty z Kitsu</string>
<string name="automatic_plugin_download">Automaticky stahovat doplňky</string>
<string name="redo_setup_process">Znovu provést proces nastavení</string>
<string name="apk_installer_settings">Instalátor APK</string>
<string name="episode_format" formatted="true">%d %s</string>
<string name="apk_installer_settings_des">Některé telefony nepodporují nový instalátor balíčků. Pokud se aktualizace nenainstalují, zkuste použít starší možnost.</string>
<string name="pref_category_cache">Mezipaměť</string>
<string name="next_episode_format" formatted="true">Epizoda %d bude vydána za</string>
<string name="next_episode_time_hour_format" formatted="true">%dh %dm</string>
<string name="play_livestream_button">Přehrát přímý přenos</string>
<string name="pref_category_extensions">Rozšíření</string>
<string name="pref_category_actions">Akce</string>
<string name="pref_category_looks">Vzhled</string>
<string name="pref_category_links">Odkazy</string>
<string name="pref_category_ui_features">Funkce</string>
<string name="example_site_name">MůjSuperWeb</string>
<string name="enable_nsfw_on_providers">Povolit NSFW u podporovaných poskytovatelů</string>
<string name="category_providers">Poskytovatelé</string>
<string name="crash_reporting_title">Hlášení pádů</string>
<string name="previous">Předchozí</string>
<string name="app_layout_subtext">Změnit vzhled aplikace tak, aby vám vyhovoval</string>
<string name="preferred_media_subtext">Co chcete vidět</string>
<string name="plugin_downloaded">Doplněk stažen</string>
<string name="is_adult">18+</string>
<string name="batch_download_start_format" formatted="true">Spuštěno stahování %d %s…</string>
<string name="blank_repo_message">CloudStream nemá ve výchozím nastavení nainstalované žádné weby. Stránky je třeba nainstalovat z úložišť.
\n
\nKvůli nesmyslnému podání stížnosti DMCA společností Sky UK Limited 🤮 nemůžeme v aplikaci propojit stránky repozitářů.
\n
\nPřipojte se k našemu Discordu nebo hledejte na internetu.</string>
<string name="plugins_disabled" formatted="true">Zakázáno: %d</string>
<string name="plugins_updated" formatted="true">Aktualizováno %d doplňků</string>
<string name="safe_mode_crash_info">Zobrazit informace o pádu</string>
<string name="extension_rating" formatted="true">Hodnocení: %s</string>
<string name="action_remove_from_watched">Odebrat ze zhlédnutých</string>
<string name="update_notification_installing">Instalace aktualizace aplikace…</string>
<string name="safe_mode_description">Všechna rozšíření byla vypnuta z důvodu pádu, abyste mohli najít to, které způsobuje potíže.</string>
<string name="extension_version">Verze</string>
<string name="player_pref">Preferovaný přehrávač videí</string>
<string name="extension_description">Popis</string>
<string name="extension_status">Stav</string>
<string name="extension_install_first">Nejprve nainstalujte rozšíření</string>
<string name="player_settings_play_in_vlc">VLC</string>
<string name="skip_type_mixed_ed">Smíšený konec</string>
<string name="extension_language">Jazyk</string>
<string name="player_settings_play_in_app">Interní přehrávač</string>
<string name="skip_type_recap">Rekapitulace</string>
<string name="clear_history">Vymazat historii</string>
<string name="player_settings_play_in_browser">Webový prohlížeč</string>
<string name="all_languages_preference">Všechny jazyky</string>
<string name="skip_type_mixed_op">Smíšený úvod</string>
<string name="skip_type_creddits">Poděkování</string>
<string name="skip_type_intro">Znělka</string>
<string name="enable_skip_op_from_database_des">Zobrazit vyskakovací okna pro přeskočení úvodu/konce</string>
<string name="update_notification_downloading">Stahování aktualizace aplikace…</string>
<string name="confirm_exit_dialog">Opravdu chcete opustit aplikaci\?</string>
<string name="update_notification_failed">Nepodařilo se nainstalovat novou verzi aplikace</string>
<string name="apk_installer_legacy">Původní</string>
<string name="delayed_update_notice">Aplikace bude po ukončení aktualizována</string>
<string name="empty_library_no_accounts_message">Vypadá to, že vaše knihovna je prázdná :(
\nPřihlaste se k účtu v knihovně nebo přidejte pořady do místní knihovny</string>
<string name="select_library">Vybrat knihovnu</string>
<string name="sort_rating_desc">Hodnocení (od nejvyššího)</string>
<string name="sort_rating_asc">Hodnocení (od nejnižšího)</string>
<string name="sort_alphabetical_z">Abecedně (od Z do A)</string>
<string name="sort_by">Seřadit podle</string>
<string name="sort">Řazení</string>
<string name="empty_library_logged_in_message">Vypadá to, že tento seznam je prázdný, zkuste přepnout na jiný</string>
<string name="safe_mode_file">Nalezen soubor bezpečného režimu!
\nDo odebrání souboru nebudeme načítat žádná rozšíření.</string>
<string name="sort_updated_new">Aktualizováno (od nejnovějšího)</string>
<string name="sort_updated_old">Aktualizováno (od nejstaršího)</string>
<string name="sort_alphabetical_a">Abecedně (od A do Z)</string>
<string name="open_with">Otevřít pomocí</string>
<string name="pref_category_backup">Záloha</string>
<string name="pref_category_gestures">Gesta</string>
<string name="add_site_pref">Klonovat web</string>
<string name="add_site_summary">Přidat klon existujícího webu s jinou adresou URL</string>
<string name="example_site_url">example.com</string>
<string name="example_lang_name">Kód jazyka (cs)</string>
<string name="download_all_plugins_from_repo">Stáhnout všechny doplňky z tohoto repozitáře\?</string>
<string name="single_plugin_disabled" formatted="true">%s (zakázáno)</string>
<string name="tracks">Stopy</string>
<string name="nsfw">NSFW</string>
<string name="other_singular">Video</string>
<string name="pref_category_player_features">Funkce přehrávače</string>
<string name="pref_category_subtitles">Titulky</string>
<string name="pref_category_player_layout">Rozložení</string>
<string name="pref_category_defaults">Výchozí hodnoty</string>
<string name="apk_installer_package_installer">Instalátor balíčků</string>
<string name="pref_category_app_updates">Aktualizace aplikace</string>
<string name="setup_done">Hotovo</string>
<string name="extension_types">Podporováno</string>
<string name="season_format">%s %d%s</string>
<string name="live_singular">Živý přenos</string>
<string name="nsfw_singular">NSFW</string>
<string name="extensions">Rozšíření</string>
<string name="play_trailer_button">Přehrát trailer</string>
<string name="next_episode_time_day_format" formatted="true">%dd %dh %dm</string>
<string name="view_public_repositories_button">Zobrazit komunitní repozitáře</string>
<string name="update_started">Aktualizace zahájena</string>
<string name="stream">Stream</string>
<string name="autoplay_next_settings">Automaticky přehrát další epizodu</string>
<string name="livestreams">Živé přenosy</string>
<string name="subtitles_filter_lang">Filtrování podle preferovaného jazyka médií</string>
<string name="referer">Referent</string>
<string name="next">Další</string>
<string name="provider_languages_tip">Sledovat videa v těchto jazycích</string>
<string name="batch_download_finish_format" formatted="true">Staženo %d %s</string>
<string name="batch_download_nothing_to_download_format" formatted="true">Všechny %s jsou již staženy</string>
<string name="batch_download">Hromadné stahování</string>
<string name="plugin_singular">doplněk</string>
<string name="plugin">doplňků</string>
<string name="delete_repository_plugins">Tímto také odstraníte všechny doplňky repozitářů</string>
<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> </resources>

View file

@ -1,10 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string> <string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
<string name="cast_format" formatted="true">Besetzung: %s</string> <string name="cast_format" formatted="true">Besetzung: %s</string>
<string name="next_episode_format" formatted="true">Episode %d wird veröffentlicht in</string> <string name="next_episode_format" formatted="true">Episode %d wird veröffentlicht in</string>
<string name="result_poster_img_des">Vorschaubild</string> <string name="result_poster_img_des">Vorschaubild</string>
<string name="search_poster_img_des">Vorschaubild</string> <string name="search_poster_img_des">\@string/result_poster_img_des</string>
<string name="subs_hold_to_reset_to_default">Halten, um auf die Standardeinstellungen zurückzusetzen</string> <string name="subs_hold_to_reset_to_default">Halten, um auf die Standardeinstellungen zurückzusetzen</string>
<string name="restore_failed_format" formatted="true">Wiederherstellung der Daten aus der Datei %s fehlgeschlagen</string> <string name="restore_failed_format" formatted="true">Wiederherstellung der Daten aus der Datei %s fehlgeschlagen</string>
<string name="backup_success">Daten erfolgreich gesichert</string> <string name="backup_success">Daten erfolgreich gesichert</string>
@ -41,7 +41,7 @@
<string name="search_hint_site" formatted="true">Suche %s…</string> <string name="search_hint_site" formatted="true">Suche %s…</string>
<string name="no_data">Keine Daten vorhanden</string> <string name="no_data">Keine Daten vorhanden</string>
<string name="episode_more_options_des">Mehr Optionen</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_tags">Genres</string>
<string name="result_share">Teilen</string> <string name="result_share">Teilen</string>
<string name="result_open_in_browser">In Browser öffnen</string> <string name="result_open_in_browser">In Browser öffnen</string>
@ -53,7 +53,7 @@
<string name="type_dropped">Abgebrochen</string> <string name="type_dropped">Abgebrochen</string>
<string name="type_plan_to_watch">Geplant</string> <string name="type_plan_to_watch">Geplant</string>
<string name="type_none">Nichts</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_movie_button">Film abspielen</string>
<string name="play_livestream_button">Livestream abspielen</string> <string name="play_livestream_button">Livestream abspielen</string>
<string name="play_torrent_button">Torrent streamen</string> <string name="play_torrent_button">Torrent streamen</string>
@ -115,7 +115,7 @@
<string name="continue_watching">Weiterschauen</string> <string name="continue_watching">Weiterschauen</string>
<string name="action_remove_watching">Entfernen</string> <string name="action_remove_watching">Entfernen</string>
<string name="action_open_watching">Mehr Infos</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_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="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> <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">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_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">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">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="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_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_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_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">Systemhelligkeit verwenden</string>
<string name="use_system_brightness_settings_des">Systemhelligkeit anstelle eines dunklen Overlay im Player 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> <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="pref_filter_search_quality">Ausgewählte Videoqualität bei Suchergebnissen ausblenden</string>
<string name="automatic_plugin_updates">Automatische Plugin-Updates</string> <string name="automatic_plugin_updates">Automatische Plugin-Updates</string>
<string name="updates_settings">App-Updates anzeigen</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">Auf Vorabversionen updaten</string>
<string name="uprereleases_settings_des">Suche nach Vorabversionen statt nur nach Vollversionen</string> <string name="uprereleases_settings_des">Suche nach Vorabversionen statt nur nach Vollversionen</string>
<string name="github">Github</string> <string name="github">Github</string>
@ -212,7 +212,7 @@
<string name="no_subtitles">Keine Untertitel</string> <string name="no_subtitles">Keine Untertitel</string>
<string name="default_subtitles">Standard</string> <string name="default_subtitles">Standard</string>
<string name="free_storage">Frei</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="app_storage">App</string>
<string name="movies">Filme</string> <string name="movies">Filme</string>
<string name="tv_series">TV-Serien</string> <string name="tv_series">TV-Serien</string>
@ -228,8 +228,8 @@
<string name="movies_singular">Film</string> <string name="movies_singular">Film</string>
<string name="tv_series_singular">Serie</string> <string name="tv_series_singular">Serie</string>
<string name="cartoons_singular">Trickfilm</string> <string name="cartoons_singular">Trickfilm</string>
<string name="anime_singular">Anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">OVA</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Dokumentation</string> <string name="documentaries_singular">Dokumentation</string>
<string name="asian_drama_singular">Asiatisches Drama</string> <string name="asian_drama_singular">Asiatisches Drama</string>
@ -246,7 +246,7 @@
<string name="episode_action_play_in_browser">In Browser wiedergeben</string> <string name="episode_action_play_in_browser">In Browser wiedergeben</string>
<string name="episode_action_copy_link">Link kopieren</string> <string name="episode_action_copy_link">Link kopieren</string>
<string name="episode_action_auto_download">Auto-Download</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_reload_links">Links neu laden</string>
<string name="episode_action_download_subtitle">Untertitel herunterladen</string> <string name="episode_action_download_subtitle">Untertitel herunterladen</string>
<string name="show_hd">Qualitätsanzeige</string> <string name="show_hd">Qualitätsanzeige</string>
@ -263,7 +263,7 @@
<string name="dont_show_again">Nicht mehr anzeigen</string> <string name="dont_show_again">Nicht mehr anzeigen</string>
<string name="skip_update">Update ignorieren</string> <string name="skip_update">Update ignorieren</string>
<string name="update">Update</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">Videoplayertitel max. Zeichen</string>
<string name="limit_title_rez">Videoplayer Auflösung</string> <string name="limit_title_rez">Videoplayer Auflösung</string>
<string name="video_buffer_size_settings">Videopuffergröße</string> <string name="video_buffer_size_settings">Videopuffergröße</string>
@ -284,9 +284,9 @@
<string name="resize_fill">Strecken</string> <string name="resize_fill">Strecken</string>
<string name="resize_zoom">Vergrößern</string> <string name="resize_zoom">Vergrößern</string>
<string name="legal_notice">Haftungsausschluss</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">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="provider_lang_settings">Anbieter-Sprachen</string>
<string name="app_layout">App-Layout</string> <string name="app_layout">App-Layout</string>
<string name="preferred_media_settings">Bevorzugte Medien</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="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="redo_setup_process">Einrichtungsvorgang wiederholen</string>
<string name="apk_installer_settings">APK-Installer</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="season_format">%s %d%s</string>
<string name="pref_category_links">Links</string> <string name="pref_category_links">Links</string>
<string name="pref_category_app_updates">App-Updates</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_extensions">Erweiterungen</string>
<string name="pref_category_actions">Wartung</string> <string name="pref_category_actions">Wartung</string>
<string name="pref_category_cache">Cache</string> <string name="pref_category_cache">Cache</string>
@ -489,44 +489,4 @@
<string name="delayed_update_notice">Die Anwendung wird beim Beenden aktualisiert</string> <string name="delayed_update_notice">Die Anwendung wird beim Beenden aktualisiert</string>
<string name="plugin_downloaded">Das Plugin wurde heruntergeladen</string> <string name="plugin_downloaded">Das Plugin wurde heruntergeladen</string>
<string name="action_remove_from_watched">Von geschaut entfernen</string> <string name="action_remove_from_watched">Von geschaut entfernen</string>
<string name="library">Bibliothek</string>
<string name="browser">Browser</string>
<string name="sort_by">Sortieren nach</string>
<string name="sort">Sortieren</string>
<string name="sort_rating_desc">Bewertung (gut bis schlecht)</string>
<string name="sort_rating_asc">Bewertung (schlecht bis gut)</string>
<string name="sort_updated_new">Aktualisiert (neu bis alt)</string>
<string name="sort_updated_old">Aktualisiert (alt bis neu)</string>
<string name="sort_alphabetical_a">Alphabetisch (A bis Z)</string>
<string name="sort_alphabetical_z">Alphabetisch (Z bis A)</string>
<string name="select_library">Bibliothek auswählen</string>
<string name="open_with">Öffnen mit</string>
<string name="empty_library_no_accounts_message">Sieht aus, als wäre deine Bibliothek leer :(
\nMelde dich mit einem Bibliothekskonto an oder füge Titel zu deiner lokalen Bibliothek hinzu</string>
<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> </resources>

View file

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<string name="app_name">CloudStream</string> <string name="app_name">CloudStream</string>
<string name="title_home">Αρχική</string> <string name="title_home">Αρχική</string>
<string name="title_search">Αναζήτηση</string> <string name="title_search">Αναζήτηση</string>
@ -151,7 +150,7 @@
<string name="episodes">Επεισόδια</string> <string name="episodes">Επεισόδια</string>
<string name="episodes_range">%d-%d</string> <string name="episodes_range">%d-%d</string>
<string name="episode_format" formatted="true">%d %s</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="episode_short">E</string>
<string name="no_episodes_found">Δεν βρέθηκαν επεισόδια</string> <string name="no_episodes_found">Δεν βρέθηκαν επεισόδια</string>
<string name="delete_file">Διαγραφή αρχείου</string> <string name="delete_file">Διαγραφή αρχείου</string>
@ -315,7 +314,7 @@
<string name="crash_reporting_title">Αναφορά κατάρρευσης</string> <string name="crash_reporting_title">Αναφορά κατάρρευσης</string>
<string name="preferred_media_subtext">Τι θα θέλατε να δείτε</string> <string name="preferred_media_subtext">Τι θα θέλατε να δείτε</string>
<string name="setup_done">Έγινε</string> <string name="setup_done">Έγινε</string>
<string name="extensions">Extensions</string> <string name="extensions">Πρόσθετα</string>
<string name="add_repository">Προσθήκη αποθετηρίου</string> <string name="add_repository">Προσθήκη αποθετηρίου</string>
<string name="repository_name_hint">Όνομα αποθετηρίου</string> <string name="repository_name_hint">Όνομα αποθετηρίου</string>
<string name="repository_url_hint">Σύνδεσμος αποθετηρίου</string> <string name="repository_url_hint">Σύνδεσμος αποθετηρίου</string>
@ -388,7 +387,7 @@
<string name="sort_close">Κλείσιμο</string> <string name="sort_close">Κλείσιμο</string>
<string name="sort_clear">Εκκαθάριση</string> <string name="sort_clear">Εκκαθάριση</string>
<string name="subs_subtitle_languages">Γλώσσα υποτίτλων</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="provider_info_meta">Δεν έχουν παρασχεθεί μεταδεδομένα από τον ιστότοπο, η φόρτωση του βίντεο θα αποτύχει αν δεν υπάρχει στον ιστότοπο.</string>
<string name="double_tap_to_pause_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>
@ -453,7 +452,7 @@
<string name="skip_type_mixed_ed">Ανάμεικτοι τίτλοι τέλους</string> <string name="skip_type_mixed_ed">Ανάμεικτοι τίτλοι τέλους</string>
<string name="go_back_30">-30</string> <string name="go_back_30">-30</string>
<string name="rating">Κριτική</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_app_updates">Ενημερώσεις εφαρμογής</string>
<string name="pref_category_backup">Αντίγραφο ασφαλείας</string> <string name="pref_category_backup">Αντίγραφο ασφαλείας</string>
<string name="pref_category_extensions">Extensions</string> <string name="pref_category_extensions">Extensions</string>
@ -465,7 +464,7 @@
<string name="pref_category_defaults">Προεπιλεγμένα</string> <string name="pref_category_defaults">Προεπιλεγμένα</string>
<string name="login_format" formatted="true">%s %s</string> <string name="login_format" formatted="true">%s %s</string>
<string name="subs_font_size">Μέγεθος γραμματοσειράς</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_links">Σύνδεσμοι</string>
<string name="pref_category_looks">Εμφάνιση</string> <string name="pref_category_looks">Εμφάνιση</string>
<string name="pref_category_ui_features">Χαρακτηριστικά</string> <string name="pref_category_ui_features">Χαρακτηριστικά</string>
@ -491,22 +490,4 @@
<string name="plugin_downloaded">Το πρόσθετο κατέβει</string> <string name="plugin_downloaded">Το πρόσθετο κατέβει</string>
<string name="update_started">Ενημέρωση ξεκίνησε</string> <string name="update_started">Ενημέρωση ξεκίνησε</string>
<string name="delayed_update_notice">Η εφαρμογή θα ενημερωθεί κατά την έξοδο</string> <string name="delayed_update_notice">Η εφαρμογή θα ενημερωθεί κατά την έξοδο</string>
<string name="sort_alphabetical_z">Αλφαβητικά (Ω προς Α)</string>
<string name="sort">Ταξινόμηση</string>
<string name="sort_rating_asc">Κριτική (Χαμηλή προς Υψηλή)</string>
<string name="sort_updated_new">Ενημερωμένο (Καινούριο προς παλιό)</string>
<string name="sort_updated_old">Ενημερωμένο (Παλιό προς Καινούργιο)</string>
<string name="library">Βιβλιοθήκη</string>
<string name="sort_rating_desc">Κριτική (Υψηλή προς χαμηλή)</string>
<string name="sort_by">Ταξινόμηση με βάση</string>
<string name="sort_alphabetical_a">Αλφαβητικά (Α προς Ω)</string>
<string name="select_library">Διάλεξε βιβλιοθήκη</string>
<string name="empty_library_logged_in_message">Φαίνεται πως η λίστα είναι άδεια, δοκίμασε να μεταβείς σε μία άλλη</string>
<string name="action_remove_from_watched">Αφαίρεση από παρακολουθημένα</string>
<string name="browser">Περιηγητής</string>
<string name="open_with">Άνοιγμα με</string>
<string name="empty_library_no_accounts_message">Φαίνεται πως η βιβλιοθήκη σου είναι άδεια :(
\nΣυνδέσου σε έναν λογαριασμό που έχει βιβλιοθήκη, ή πρόσθεσε σειρές στην τοπική βιβλιοθήκη σου</string>
<string name="safe_mode_file">Βρέθηκε αρχείο Ασφαλούς Λειτουργίας!
\nΔεν πρόκειται να φορτωθούν extensions κατά το ξεκίνημα μέχρι να διαγραφεί το αρχείο.</string>
</resources> </resources>

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="go_back_img_des">Reen</string> <string name="go_back_img_des">Reen</string>
<string name="title_home">Hejmo</string> <string name="title_home">Hejmo</string>

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pref_category_extensions">Extensiones</string> <string name="pref_category_extensions">Extensiones</string>
<string name="setup_extensions_subtext">Descargue la lista de sitios que quiera utilizar</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_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="pref_category_player_layout">Diseño</string>
<string name="category_ui">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="player_pref">Reproductor de video preferido</string>
<string name="emulator_layout">Diseño para emulador</string> <string name="emulator_layout">Diseño para emulador</string>
<string name="app_layout">Diseño de la aplicación</string> <string name="app_layout">Diseño de la aplicación</string>
@ -51,10 +51,10 @@
<string name="subtitles_raised">Elevado</string> <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_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="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_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="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="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="limit_title_rez">Resolución del reproductor de video</string>
<string name="player_settings_play_in_mpv">MPV</string> <string name="player_settings_play_in_mpv">MPV</string>
@ -94,7 +94,7 @@
<string name="home_main_poster_img_des">Poster Principal</string> <string name="home_main_poster_img_des">Poster Principal</string>
<string name="app_language">Idioma de la aplicación</string> <string name="app_language">Idioma de la aplicación</string>
<string name="provider_languages_tip">Ver videos en estos idiomas</string> <string name="provider_languages_tip">Ver videos en estos idiomas</string>
<string name="search_poster_img_des">Cartel</string> <string name="search_poster_img_des">\@string/result_poster_img_des</string>
<string name="home_next_random_img_des">Siguiente al azar</string> <string name="home_next_random_img_des">Siguiente al azar</string>
<string name="all_languages_preference">Todos los Idiomas</string> <string name="all_languages_preference">Todos los Idiomas</string>
<string name="go_back_img_des">Volver</string> <string name="go_back_img_des">Volver</string>
@ -194,7 +194,7 @@
<string name="continue_watching">Continuar Viendo</string> <string name="continue_watching">Continuar Viendo</string>
<string name="action_remove_watching">Remover</string> <string name="action_remove_watching">Remover</string>
<string name="action_open_watching">Más info</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_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="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> <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="eigengraumode_settings">Modo Eigengravy</string>
<string name="swipe_to_seek_settings">Deslice para avanzar/retroceder</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">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_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_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_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="use_system_brightness_settings">Usar brillo del sistema</string>
<string name="restore_settings">Restaurar datos desde el backup</string> <string name="restore_settings">Restaurar datos desde el backup</string>
<string name="backup_settings">Hacer copia de los datos (backup)</string> <string name="backup_settings">Hacer copia de los datos (backup)</string>
<string name="restore_success">Archivo de backup cargado</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="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="show_fillers_settings">Mostrar episodio de relleno para Anime</string>
<string name="play_episode_toast">Reproducir Episodio</string> <string name="play_episode_toast">Reproducir Episodio</string>
@ -306,7 +306,7 @@
<string name="pref_category_looks">Aspecto</string> <string name="pref_category_looks">Aspecto</string>
<string name="pref_category_ui_features">Características</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">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="account">cuenta</string>
<string name="logout">Cerrar sesión</string> <string name="logout">Cerrar sesión</string>
<string name="switch_account">Cambiar cuenta</string> <string name="switch_account">Cambiar cuenta</string>
@ -363,8 +363,8 @@
<string name="movies_singular">Película</string> <string name="movies_singular">Película</string>
<string name="tv_series_singular">Serie</string> <string name="tv_series_singular">Serie</string>
<string name="cartoons_singular">Dibujo animado</string> <string name="cartoons_singular">Dibujo animado</string>
<string name="anime_singular">Anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">OVA</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Documental</string> <string name="documentaries_singular">Documental</string>
<string name="asian_drama_singular">Drama asiático</string> <string name="asian_drama_singular">Drama asiático</string>
@ -489,44 +489,4 @@
<string name="update_started">Actualización iniciada</string> <string name="update_started">Actualización iniciada</string>
<string name="plugin_downloaded">Complemento descargado</string> <string name="plugin_downloaded">Complemento descargado</string>
<string name="action_remove_from_watched">Quitar de visto</string> <string name="action_remove_from_watched">Quitar de visto</string>
<string name="sort_by">Ordenar por</string>
<string name="sort">Ordenar</string>
<string name="sort_rating_desc">Valoración (más a menos)</string>
<string name="sort_rating_asc">Valoración (menos a más)</string>
<string name="sort_updated_new">Actualizado (nuevo a viejo)</string>
<string name="sort_updated_old">Actualizado (viejo a nuevo)</string>
<string name="sort_alphabetical_a">Alfabéticamente (A a Z)</string>
<string name="browser">Navegador</string>
<string name="library">Biblioteca</string>
<string name="empty_library_logged_in_message">Parece que esta lista está vacía, intenta cambiar a otra</string>
<string name="sort_alphabetical_z">Alfabéticamente (Z a A)</string>
<string name="select_library">Seleccionar biblioteca</string>
<string name="open_with">Abrir con</string>
<string name="empty_library_no_accounts_message">Parece que tu biblioteca está vacía :(
\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="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>
</resources> </resources>

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="delete">حذف</string> <string name="delete">حذف</string>
<string name="pause">مکث</string> <string name="pause">مکث</string>
@ -23,14 +23,4 @@
<string name="none">هیچ‌کدام</string> <string name="none">هیچ‌کدام</string>
<string name="title">عنوان</string> <string name="title">عنوان</string>
<string name="history">تاریخچه</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> </resources>

View file

@ -1,18 +1,17 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<string name="app_name">CloudStream</string> <string name="app_name">CloudStream</string>
<string name="title_home">Accueil</string> <string name="title_home">Accueil</string>
<string name="title_search">Rechercher</string> <string name="title_search">Rechercher</string>
<string name="title_downloads">Téléchargements</string> <string name="title_downloads">Téléchargements</string>
<string name="title_settings">Paramètres</string> <string name="title_settings">Paramètres</string>
<string name="search_hint">Rechercher…</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="no_data">Aucune Donnée</string>
<string name="episode_more_options_des">Plus d\'options</string> <string name="episode_more_options_des">Plus d\'options</string>
<string name="go_back_img_des">Retour</string> <string name="go_back_img_des">Retour</string>
<string name="next_episode">Épisode suivant</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_tags">Genres</string>
<string name="result_share">Partager</string> <string name="result_share">Partager</string>
<string name="result_open_in_browser">Ouvrir dans le navigateur</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="pick_subtitle">Sous-titres</string>
<string name="reload_error">Réessayer la connection…</string> <string name="reload_error">Réessayer la connection…</string>
<string name="go_back">Retour</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="play_episode">Lire l\'Épisode</string>
<!--<string name="need_storage">Permet de télécharger les épisodes</string>--> <!--<string name="need_storage">Permet de télécharger les épisodes</string>-->
<string name="download">Télécharger</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="pref_disable_acra">Désactiver le rapport de bug automatique</string>
<string name="home_more_info">Plus d\'informations</string> <string name="home_more_info">Plus d\'informations</string>
<string name="home_expanded_hide">Cacher</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_play">Lecture</string>
<string name="home_info">Infos</string> <string name="home_info">Info</string>
<string name="home_next_random_img_des">Aléatoire suivant</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="home_change_provider_img_des">Changer le fournisseur</string>
<string name="filter_bookmarks">Filtrer les marques-pages</string> <string name="filter_bookmarks">Filtrer les marques-pages</string>
<string name="error_bookmarks_text">Marque-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 ! <string name="new_update_format" formatted="true">Nouvelle mise à jour trouvée !
\n%s -&gt; %s</string> \n%s -&gt; %s</string>
<string name="filler" formatted="true">Épisode spécial</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="video_buffer_size_settings">Taille de la mémoire cache</string>
<string name="resize_fill">Étendre</string> <string name="resize_fill">Étendre</string>
<string name="legal_notice">Non-responsabilité</string> <string name="legal_notice">Non-responsabilité</string>
@ -212,7 +211,7 @@
<string name="actor_background">Arrière plan</string> <string name="actor_background">Arrière plan</string>
<string name="home_source">Source</string> <string name="home_source">Source</string>
<string name="home_random">Aléatoire</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="poster_image">Image de l\'affiche</string>
<string name="authenticated_user">%s Connecté</string> <string name="authenticated_user">%s Connecté</string>
<string name="action_add_to_bookmarks">Définir le statut de visionage</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="continue_watching">Continuer à regarder</string>
<string name="action_remove_watching">Retirer</string> <string name="action_remove_watching">Retirer</string>
<string name="action_open_watching">Plus d\'informations</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_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="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> <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_4k">4K</string>
<string name="quality_webrip">Web</string> <string name="quality_webrip">Web</string>
<string name="go_back_30">-30</string> <string name="go_back_30">-30</string>
<string name="anime_singular">@string/anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">@string/ova</string> <string name="ova_singular">\@string/ova</string>
<string name="nsfw_singular">NSFW</string> <string name="nsfw_singular">NSFW</string>
<string name="login_format" formatted="true">%s %s</string> <string name="login_format" formatted="true">%s %s</string>
<string name="subtitles_filter_lang">Filtrez par langue préférée</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="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="plugin_downloaded">Plugin Téléchargé</string>
<string name="action_remove_from_watched">Retirer de la vue</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> </resources>

View file

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- TRANSLATE, BUT DON'T FORGET FORMAT --> <!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
<string name="player_speed_text_format" formatted="true">रफ्तार (%.2fx)</string> <string name="player_speed_text_format" formatted="true">रफ्तार (%.2fx)</string>
<string name="new_update_format" formatted="true">नया अपडेट आया है! <string name="new_update_format" formatted="true">नया अपडेट आया है!

View file

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string> <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> <string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
@ -20,7 +19,7 @@
<string name="next_episode_time_min_format" formatted="true">%dm</string> <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 --> <!-- 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="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">Episode Poster</string> <string name="episode_poster_img_des">Episode Poster</string>
<string name="home_main_poster_img_des">Main Poster</string> <string name="home_main_poster_img_des">Main Poster</string>
<string name="home_next_random_img_des">Next Random</string> <string name="home_next_random_img_des">Next Random</string>
@ -120,7 +119,7 @@
<string name="continue_watching">Nastavite s gledanjem</string> <string name="continue_watching">Nastavite s gledanjem</string>
<string name="action_remove_watching">Makni</string> <string name="action_remove_watching">Makni</string>
<string name="action_open_watching">Više informacija</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_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="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> <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">Eigengravy način</string>
<string name="eigengraumode_settings_des">Dodaje opciju brzine u playeru</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">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">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">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="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_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_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_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">Koristi svijetlinu u sustavu</string>
<string name="use_system_brightness_settings_des">Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog preklopa</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> <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="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="automatic_plugin_updates">Automatsko ažuriranje dodataka</string>
<string name="updates_settings">Prikaži ažuriranja aplikacije</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">Ažuriranje na predizdanja</string>
<string name="uprereleases_settings_des">Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja</string> <string name="uprereleases_settings_des">Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja</string>
<string name="github">Github</string> <string name="github">Github</string>
@ -239,8 +238,8 @@
<string name="movies_singular">Film</string> <string name="movies_singular">Film</string>
<string name="tv_series_singular">Serija</string> <string name="tv_series_singular">Serija</string>
<string name="cartoons_singular">Crtić</string> <string name="cartoons_singular">Crtić</string>
<string name="anime_singular">Anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">OVA</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Dokumentarac</string> <string name="documentaries_singular">Dokumentarac</string>
<string name="asian_drama_singular">Azijska drama</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="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="category_general">Općenito</string>
<string name="random_button_settings">Random gumb</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="provider_lang_settings">Jezici pružatelja usluga</string>
<string name="app_layout">Izgled aplikacije</string> <string name="app_layout">Izgled aplikacije</string>
<string name="preferred_media_settings">Preferirani mediji</string> <string name="preferred_media_settings">Preferirani mediji</string>
@ -515,44 +514,4 @@
<string name="delayed_update_notice">Program če se aktualizirati tijekom zatvaranja programa</string> <string name="delayed_update_notice">Program če se aktualizirati tijekom zatvaranja programa</string>
<string name="plugin_downloaded">Dodatak preuzet</string> <string name="plugin_downloaded">Dodatak preuzet</string>
<string name="action_remove_from_watched">Ukloni iz pogledanog</string> <string name="action_remove_from_watched">Ukloni iz pogledanog</string>
<string name="browser">Preglednik</string>
<string name="library">Biblioteka</string>
<string name="sort_rating_desc">Ocjena (Veća do manje)</string>
<string name="sort_by">Sortiraj prema</string>
<string name="sort">Sortiraj</string>
<string name="sort_rating_asc">Ocjena (Manja do veće)</string>
<string name="sort_updated_new">Ažurirano (Od novog do starog)</string>
<string name="sort_updated_old">Ažurirano (Od starog do novog)</string>
<string name="sort_alphabetical_a">Abecedno (A do Ž)</string>
<string name="sort_alphabetical_z">Abecedno (Ž do A)</string>
<string name="select_library">Odaberite biblioteku</string>
<string name="open_with">Otvori sa</string>
<string name="empty_library_no_accounts_message">Čini se da vam je biblioteka prazna :(
\nPrijavite se na račun biblioteke ili dodajte serije u svoju lokalnu biblioteku</string>
<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> </resources>

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="cast_format" formatted="true">Stáblista: %s</string> <string name="cast_format" formatted="true">Stáblista: %s</string>
<string name="next_episode_time_day_format" formatted="true">%dn %dó%dp</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="result_open_in_browser">Megnyitás böngészőben</string>
<string name="skip_loading">Betöltés kihagyása</string> <string name="skip_loading">Betöltés kihagyása</string>
<string name="result_poster_img_des">Poster</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_watching">Nézés</string>
<string name="type_completed">Befejezve</string> <string name="type_completed">Befejezve</string>
<string name="type_plan_to_watch">Később megnézés</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="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_remove_watching">Eltávolítás</string>
<string name="action_open_watching">Több információ</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_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="vpn_torrent">Ez a szolgáltató torrent, VPN ajánlott</string>
<string name="torrent_plot">Leírás</string> <string name="torrent_plot">Leírás</string>
@ -172,11 +172,11 @@
<string name="ova">OVA</string> <string name="ova">OVA</string>
<string name="others">Egyebek</string> <string name="others">Egyebek</string>
<string name="tv_series_singular">Sorozat</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="source_error">Forráshiba</string>
<string name="nsfw">NSFW</string> <string name="nsfw">NSFW</string>
<string name="cartoons_singular">Rajzfilm</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="live_singular">Élőadás</string>
<string name="nsfw_singular">NSFW</string> <string name="nsfw_singular">NSFW</string>
<string name="other_singular">Videó</string> <string name="other_singular">Videó</string>

View file

@ -1,12 +1,11 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- 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="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
<string name="cast_format" formatted="true">Pemeran: %s</string> <string name="cast_format" formatted="true">Pemeran: %s</string>
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS --> <!-- 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="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">Episode Poster</string> <string name="episode_poster_img_des">Episode Poster</string>
<string name="home_main_poster_img_des">Main Poster</string> <string name="home_main_poster_img_des">Main Poster</string>
<string name="home_next_random_img_des">Next Random</string> <string name="home_next_random_img_des">Next Random</string>
@ -36,7 +35,7 @@
<string name="skip_loading">Skip Loading</string> <string name="skip_loading">Skip Loading</string>
<string name="loading">Loading…</string> <string name="loading">Loading…</string>
<string name="type_watching">Sedang Menonton</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_completed">Selesai</string>
<string name="type_dropped">Dihentikan</string> <string name="type_dropped">Dihentikan</string>
<string name="type_plan_to_watch">Rencana untuk Menonton</string> <string name="type_plan_to_watch">Rencana untuk Menonton</string>
@ -102,7 +101,7 @@
<string name="continue_watching">Lanjutkan Menonton</string> <string name="continue_watching">Lanjutkan Menonton</string>
<string name="action_remove_watching">Hapus</string> <string name="action_remove_watching">Hapus</string>
<string name="action_open_watching">Info lebih lanjut</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_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="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> <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">Mode Eigengravy</string>
<string name="eigengraumode_settings_des">Menambahkan opsi kecepatan di pemutar</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">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">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_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_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_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">Gunakan pencerahan sistem</string>
<string name="use_system_brightness_settings_des">Gunakan pencerahan sistem di pemutar aplikasi dari pada hamparan gelap</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> <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="bug_report_settings_on">Tidak mengirim data</string>
<string name="show_fillers_settings">Tampilkan episode filler untuk anime</string> <string name="show_fillers_settings">Tampilkan episode filler untuk anime</string>
<string name="updates_settings">Tampilkan update aplikasi</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">Update ke prarilis</string>
<string name="uprereleases_settings_des">Hanya mencari update prarilis daripada rilis penuh</string> <string name="uprereleases_settings_des">Hanya mencari update prarilis daripada rilis penuh</string>
<string name="github">Github</string> <string name="github">Github</string>
@ -210,8 +209,8 @@
<string name="movies_singular">Movie</string> <string name="movies_singular">Movie</string>
<string name="tv_series_singular">Seri</string> <string name="tv_series_singular">Seri</string>
<string name="cartoons_singular">Kartun</string> <string name="cartoons_singular">Kartun</string>
<string name="anime_singular">Anime</string> <string name="anime_singular">\@string/anime</string>
<string name="ova_singular">OVA</string> <string name="ova_singular">\@string/ova</string>
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Film Dokumenter</string> <string name="documentaries_singular">Film Dokumenter</string>
<string name="asian_drama_singular">Drama Asia</string> <string name="asian_drama_singular">Drama Asia</string>
@ -244,7 +243,7 @@
<string name="dont_show_again">Jangan tunjukkan lagi</string> <string name="dont_show_again">Jangan tunjukkan lagi</string>
<string name="skip_update">Skip Update ini</string> <string name="skip_update">Skip Update ini</string>
<string name="update">Update</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">Karakter maksimal judul pemutar video</string>
<string name="limit_title_rez">Resolusi pemutar video</string> <string name="limit_title_rez">Resolusi pemutar video</string>
<string name="video_buffer_size_settings">Ukuran buffer 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="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="category_general">Umum</string>
<string name="random_button_settings">Tombol Acak</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="provider_lang_settings">Bahasa provider</string>
<string name="app_layout">Tata Letak Aplikasi</string> <string name="app_layout">Tata Letak Aplikasi</string>
<string name="preferred_media_settings">Media yang lebih diinginkan</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="episode_format" formatted="true">%d %s</string>
<string name="nsfw">17+</string> <string name="nsfw">17+</string>
<string name="others">Lainnya</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_pref">Duplikasi Website</string>
<string name="add_site_summary">Duplikasi website yang telah ada, dengan alamat berbeda</string> <string name="add_site_summary">Duplikasi website yang telah ada, dengan alamat berbeda</string>
<string name="pref_category_links">Tautan</string> <string name="pref_category_links">Tautan</string>
@ -396,7 +395,7 @@
<string name="pref_category_backup">Cadangkan</string> <string name="pref_category_backup">Cadangkan</string>
<string name="pref_category_extensions">Fitur Tambahan</string> <string name="pref_category_extensions">Fitur Tambahan</string>
<string name="play_with_app_name">Putar di CloudStream</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="season_format">%s %d%s</string>
<string name="livestreams">Siaran langsung</string> <string name="livestreams">Siaran langsung</string>
<string name="remove_site_pref">Hapus Website</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_rating" formatted="true">Peringkat: %s</string>
<string name="extension_authors">Pembuat</string> <string name="extension_authors">Pembuat</string>
<string name="extension_language">Bahasa</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_app">Pemutar Bawaan</string>
<string name="player_settings_play_in_vlc">VLC</string> <string name="player_settings_play_in_vlc">VLC</string>
<string name="player_settings_play_in_mpv">MPV</string> <string name="player_settings_play_in_mpv">MPV</string>
@ -454,7 +453,7 @@
<string name="safe_mode_description">Semua fitur tambahkan dimatikan karena crash, untuk memudahkanmu mencari penyebab crash.</string> <string name="safe_mode_description">Semua fitur tambahkan dimatikan karena crash, untuk memudahkanmu mencari penyebab crash.</string>
<string name="example_lang_name">Kode bahasa (en)</string> <string name="example_lang_name">Kode bahasa (en)</string>
<string name="player_load_subtitles_online">Ambil dari internet</string> <string name="player_load_subtitles_online">Ambil dari internet</string>
<string name="provider_languages_tip">Putar video di bahasa ini</string> <string name="provider_languages_tip">Putar vidio di bahasa ini</string>
<string name="add_repository">Tambah Repositori</string> <string name="add_repository">Tambah Repositori</string>
<string name="delete_repository_plugins">Pilih ini untuk menghapus semua repositori plugin</string> <string name="delete_repository_plugins">Pilih ini untuk menghapus semua repositori plugin</string>
<string name="skip_setup">Lewati pengaturan</string> <string name="skip_setup">Lewati pengaturan</string>
@ -476,7 +475,7 @@
<string name="subtitles_remove_captions">Hapus teks tertutup dari subtitel</string> <string name="subtitles_remove_captions">Hapus teks tertutup dari subtitel</string>
<string name="subtitles_remove_bloat">Hapus karakter sampah dari subtitel</string> <string name="subtitles_remove_bloat">Hapus karakter sampah dari subtitel</string>
<string name="audio_tracks">Audio Trek</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="extension_types">Dukungan</string>
<string name="hls_playlist">Daftar putar HLS</string> <string name="hls_playlist">Daftar putar HLS</string>
<string name="apk_installer_settings">Penginstal APK</string> <string name="apk_installer_settings">Penginstal APK</string>
@ -484,7 +483,7 @@
<string name="pref_category_gestures">Gerakan</string> <string name="pref_category_gestures">Gerakan</string>
<string name="apk_installer_settings_des">Beberapa perangkat tidak mendukung penginstal paket mode baru. Coba mode lama jika pembaruan tidak dapat diinstal.</string> <string name="apk_installer_settings_des">Beberapa perangkat tidak mendukung penginstal paket mode baru. Coba mode lama jika pembaruan tidak dapat diinstal.</string>
<string name="pref_category_actions">Aksi</string> <string name="pref_category_actions">Aksi</string>
<string name="referer">Referer</string> <string name="referer">Referensi</string>
<string name="yes">Ya</string> <string name="yes">Ya</string>
<string name="extension_install_first">Pasang dulu fitur tambahan</string> <string name="extension_install_first">Pasang dulu fitur tambahan</string>
<string name="all_languages_preference">Semua Bahasa</string> <string name="all_languages_preference">Semua Bahasa</string>
@ -513,44 +512,4 @@
<string name="delayed_update_notice">Aplikasi akan diperbaharui pada saat keluar</string> <string name="delayed_update_notice">Aplikasi akan diperbaharui pada saat keluar</string>
<string name="update_started">Pembaharuan Dimulai</string> <string name="update_started">Pembaharuan Dimulai</string>
<string name="action_remove_from_watched">Hapus dari tontonan</string> <string name="action_remove_from_watched">Hapus dari tontonan</string>
<string name="browser">Browser</string>
<string name="select_library">Pilih pustaka</string>
<string name="empty_library_no_accounts_message">Yahh daftar pustaka kamu kosong :(
\nMasuk ke akun pustaka atau tambah perlihatkan ke lokal pustaka kamu</string>
<string name="library">Pustaka</string>
<string name="sort_by">Urutkan berdasar</string>
<string name="sort">Urutkan</string>
<string name="sort_rating_asc">Peringkat (Rendah ke Tinggi)</string>
<string name="sort_updated_old">Update (Lama ke Terbaru)</string>
<string name="sort_rating_desc">Peringkat (Tinggi ke Rendah)</string>
<string name="sort_updated_new">Update (Terbaru ke Lama)</string>
<string name="sort_alphabetical_a">Abjad (A ke Z)</string>
<string name="sort_alphabetical_z">Abjad (Z ke A)</string>
<string name="open_with">Buka dengan</string>
<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> </resources>

View file

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
<resources>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- 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="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
<string name="cast_format" formatted="true">Cast: %s</string> <string name="cast_format" formatted="true">Cast: %s</string>
@ -10,7 +9,7 @@
<string name="next_episode_time_min_format" formatted="true">%d min</string> <string name="next_episode_time_min_format" formatted="true">%d min</string>
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS --> <!-- 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="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">Poster episodio</string> <string name="episode_poster_img_des">Poster episodio</string>
<string name="home_main_poster_img_des">Poster principale</string> <string name="home_main_poster_img_des">Poster principale</string>
<string name="home_next_random_img_des">Prossimo casuale</string> <string name="home_next_random_img_des">Prossimo casuale</string>
@ -109,7 +108,7 @@
<string name="continue_watching">Continua a guardare</string> <string name="continue_watching">Continua a guardare</string>
<string name="action_remove_watching">Rimuovi</string> <string name="action_remove_watching">Rimuovi</string>
<string name="action_open_watching">Più info</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_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="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> <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="dont_show_again">Non mostrare di nuovo</string>
<string name="skip_update">Salta questo aggiornamento</string> <string name="skip_update">Salta questo aggiornamento</string>
<string name="update">Aggiorna</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">Limita i caratteri del titolo nel player</string>
<string name="limit_title_rez">Risoluzione video player</string> <string name="limit_title_rez">Risoluzione video player</string>
<string name="video_buffer_size_settings">Dimensione cache video</string> <string name="video_buffer_size_settings">Dimensione cache video</string>
@ -512,44 +511,4 @@
<string name="update_started">Aggiornamento avviato</string> <string name="update_started">Aggiornamento avviato</string>
<string name="plugin_downloaded">Plugin scaricato</string> <string name="plugin_downloaded">Plugin scaricato</string>
<string name="action_remove_from_watched">Rimuovi dai già visti</string> <string name="action_remove_from_watched">Rimuovi dai già visti</string>
<string name="browser">Browser</string>
<string name="sort_by">Ordina per</string>
<string name="sort_rating_desc">Punteggio (Decrescente)</string>
<string name="sort_rating_asc">Punteggio (Crescente)</string>
<string name="sort_updated_new">Aggiornato (Da nuovo a vecchio)</string>
<string name="sort_updated_old">Aggiornato (Da vecchio a nuovo)</string>
<string name="sort_alphabetical_a">Alfabetico (A - Z)</string>
<string name="sort_alphabetical_z">Alfabetico (Z - A)</string>
<string name="empty_library_no_accounts_message">Sembra che la tua libreria sia vuota :(
\nAccedi a un account di libreria o aggiungi degli show alla tua libreria locale</string>
<string name="select_library">Seleziona libreria</string>
<string name="open_with">Apri con</string>
<string name="library">Libreria</string>
<string name="sort">Ordina</string>
<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> </resources>

View file

@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="preview_background_img_des">הרקע של ההצגה לפני</string> <string name="preview_background_img_des">הרקע של ההצגה לפני</string>
<string name="cast_format" formatted="true">צוות שחקנים: %s</string> <string name="cast_format" formatted="true">צוות שחקנים: %s</string>
@ -34,13 +34,13 @@
<string name="type_completed">הושלם</string> <string name="type_completed">הושלם</string>
<string name="type_plan_to_watch">בתכנון לצפייה</string> <string name="type_plan_to_watch">בתכנון לצפייה</string>
<string name="type_re_watching">צופה מחדש</string> <string name="type_re_watching">צופה מחדש</string>
<string name="play_movie_button">הפעל סרט</string> <string name="play_movie_button">נגן סרט</string>
<string name="play_trailer_button">הפעל טריילר</string> <string name="play_trailer_button">נגן קדימון</string>
<string name="play_livestream_button">הפעל שידור חי</string> <string name="play_livestream_button">נגן שידור חי</string>
<string name="pick_source">מקורות</string> <string name="pick_source">מקורות</string>
<string name="reload_error">נסיון חדש לחיבור…</string> <string name="reload_error">נסיון חדש לחיבור…</string>
<string name="type_dropped">הפסיק</string> <string name="type_dropped">נעצר</string>
<string name="play_torrent_button">שדר טורנט</string> <string name="play_torrent_button">שידור טורנט</string>
<string name="go_back">חזרה</string> <string name="go_back">חזרה</string>
<string name="play_episode">נגן פרק</string> <string name="play_episode">נגן פרק</string>
<string name="downloaded">הורד</string> <string name="downloaded">הורד</string>
@ -75,7 +75,7 @@
<string name="play_with_app_name">נגן עם קלאודסטרים</string> <string name="play_with_app_name">נגן עם קלאודסטרים</string>
<string name="next_episode">פרק הבא</string> <string name="next_episode">פרק הבא</string>
<string name="loading">טוען…</string> <string name="loading">טוען…</string>
<string name="type_watching">צופה</string> <string name="type_watching">צופה ב</string>
<string name="pick_subtitle">כתוביות</string> <string name="pick_subtitle">כתוביות</string>
<string name="type_on_hold">בהמתנה</string> <string name="type_on_hold">בהמתנה</string>
<string name="type_none">ללא</string> <string name="type_none">ללא</string>
@ -102,11 +102,11 @@
<string name="vpn_torrent">הספק הזה הוא טורנט, שימוש ב-VPN הוא מומלץ</string> <string name="vpn_torrent">הספק הזה הוא טורנט, שימוש ב-VPN הוא מומלץ</string>
<string name="torrent_plot">תיאור</string> <string name="torrent_plot">תיאור</string>
<string name="normal_no_plot">עלילה לא נמצאה</string> <string name="normal_no_plot">עלילה לא נמצאה</string>
<string name="torrent_no_plot">התיאור לא נמצאה</string> <string name="torrent_no_plot">העלילה לא נמצאה</string>
<string name="picture_in_picture">מצב תמונה בתמונה</string> <string name="picture_in_picture">מצב תמונה בתמונה</string>
<string name="player_size_settings">כפתור שינוי גודל הנגן</string> <string name="player_size_settings">כפתור שינוי גודל הנגן</string>
<string name="player_size_settings_des">הסר את הגבולות השחורים</string> <string name="player_size_settings_des">הסר את הגבולות השחורים</string>
<string name="benene_count_text">%d בנינים שניתנו למפתחים</string> <string name="benene_count_text">%d ניתן למפתחים</string>
<string name="player_subtitles_settings_des">הגדרות כתוביות הנגן</string> <string name="player_subtitles_settings_des">הגדרות כתוביות הנגן</string>
<string name="chromecast_subtitles_settings_des">הגדרות כתוביות כרומקאסט</string> <string name="chromecast_subtitles_settings_des">הגדרות כתוביות כרומקאסט</string>
<string name="subs_font">גופן</string> <string name="subs_font">גופן</string>
@ -116,7 +116,7 @@
<string name="chromecast_subtitles_settings">כתוביות כרומקאסט</string> <string name="chromecast_subtitles_settings">כתוביות כרומקאסט</string>
<string name="picture_in_picture_des">ממשיך ניגון בנגן מינימלי מעל ישומים אחרים</string> <string name="picture_in_picture_des">ממשיך ניגון בנגן מינימלי מעל ישומים אחרים</string>
<string name="player_subtitles_settings">כתוביות</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="history">היסטוריה</string>
<string name="apk_installer_legacy">מורשת</string> <string name="apk_installer_legacy">מורשת</string>
<string name="no">לא</string> <string name="no">לא</string>
@ -129,381 +129,4 @@
<string name="none">אין</string> <string name="none">אין</string>
<string name="all">הכל</string> <string name="all">הכל</string>
<string name="title">כותרת</string> <string name="title">כותרת</string>
<string name="update_started">העדכון התחיל</string>
<string name="settings_info">מידע</string>
<string name="automatic_plugin_download_summary">התקן אוטומטית את כל התוספים שטרם הותקנו ממאגרים שנוספו.</string>
<string name="updates_settings_des">חפש אוטומטית עדכונים חדשים בפתיחת האפליקציה</string>
<string name="updates_settings">הצג עדכונים לאפליקציה</string>
<string name="redo_setup_process">בצע מחדש את תהליך ההגדרה</string>
<string name="uprereleases_settings">עדכן למהדורות מוקדמות</string>
<string name="uprereleases_settings_des">חפש עדכוני מהדרות מוקדמות במקום מהדורות מלאות בלבד</string>
<string name="episodes">פרקים</string>
<string name="episodes_range">%d-%d</string>
<string name="episode">פרק</string>
<string name="episode_format" formatted="true">%d %s</string>
<string name="season_short">עו</string>
<string name="episode_short">פר</string>
<string name="no_episodes_found">לא נמצאו פרקים</string>
<string name="delete_file">מחק קובץ</string>
<string name="delete">מחק</string>
<string name="pause">עצור</string>
<string name="resume">המשך</string>
<string name="go_back_30">-30</string>
<string name="go_forward_30">+30</string>
<string name="resume_time_left" formatted="true">%dדקות
\nנותרו</string>
<string name="delete_message" formatted="true">‬פעולה זאת תמחק לצמיתות את %s
\nהאם אתם בטוחים\?</string>
<string name="status_ongoing">מתמשך</string>
<string name="duration">משך זמן</string>
<string name="rating">דירוג</string>
<string name="year">שנה</string>
<string name="no_subtitles">ללא כתוביות</string>
<string name="default_subtitles">ברירת מחדל</string>
<string name="free_storage">חינם</string>
<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="asian_drama_singular">דרמה אסייתית</string>
<string name="episode_action_chromecast_episode">כרומקאסט את הפרק</string>
<string name="episode_action_chromecast_mirror">כרומקאסט את המראה</string>
<string name="episode_action_play_in_browser">נגן בדפדפן</string>
<string name="show_sub">תווית כתוביות</string>
<string name="poster_ui_settings">החלף רכיבי ממשק משתמש בפוסטר</string>
<string name="video_skip_op">דלג על הפתיח</string>
<string name="dont_show_again">אל תראה שוב</string>
<string name="skip_update">דלג על עדכון זה</string>
<string name="update">עדכון</string>
<string name="watch_quality_pref">איכות צפייה מועדפת</string>
<string name="limit_title">נגן וידאו כותרת מקסימום תווים</string>
<string name="limit_title_rez">רזולוציית נגן וידאו</string>
<string name="add_sync">הוסף מעקב</string>
<string name="create_account">צור חשבון</string>
<string name="next">הבא</string>
<string name="library">ספריה</string>
<string name="provider_info_meta">מטא-דאטה לא מסופק על ידי האתר, טעינת הסרטון תיכשל אם הוא לא קיים באתר.</string>
<string name="swipe_to_change_settings_des">החלק על הצד השמאלי או הימני כדי לשנות את הבהירות או עוצמת הקול</string>
<string name="restore_failed_format" formatted="true">שחזור הנתונים מהקובץ נכשל %s</string>
<string name="category_updates">עדכונים וגיבויים</string>
<string name="advanced_search_des">נותן לך את תוצאות החיפוש מופרדות לפי ספק</string>
<string name="show_fillers_settings">הצג פרק פילר (לא הכרחי לעלילה) לאנימה</string>
<string name="automatic_plugin_download">הורדה אוטומטית של תוספים</string>
<string name="automatic_plugin_updates">עדכוני תוספים אוטומטיים</string>
<string name="benene_des">כמות בנינים שניתנו</string>
<string name="season">עונה</string>
<string name="acra_report_toast">מצטערים, האפליקציה קרסה. דוח באג אנונימי יישלח למפתחים</string>
<string name="torrent">טורנט</string>
<string name="nsfw">NSFW</string>
<string name="render_error">שגיאת מעבד</string>
<string name="episode_action_download_mirror">הורד מראה</string>
<string name="episode_action_download_subtitle">הורד כתוביות</string>
<string name="random_button_settings_desc">הצג כפתור אקראי בדף הבית</string>
<string name="show_dub">תווית דיבוב</string>
<string name="video_ram_description">גורם לקריסות אם מוגדר גבוה מדי במכשירים עם זיכרון נמוך, כגון Android TV.</string>
<string name="video_source">מקור</string>
<string name="category_providers">ספקים</string>
<string name="remove_site_pref">הסר אתר</string>
<string name="download_path_pref">נתיב הורדה</string>
<string name="pref_category_looks">נראה</string>
<string name="automatic">אוטומטי</string>
<string name="tv_layout">פריסת טלוויזיה</string>
<string name="login_format" formatted="true">%s %s</string>
<string name="subtitles_filter_lang">סנן לפי שפת מדיה מועדפת</string>
<string name="app_theme_settings">נושא אפליקציה</string>
<string name="bottom_title_settings">מיקום כותרת פוסטר</string>
<string name="preferred_media_subtext">מה אתם רוצים לראות</string>
<string name="quality_hq">איכות גבוהה</string>
<string name="quality_hd">HD</string>
<string name="quality_sdr">SDR</string>
<string name="plugin_loaded">תוסף טעון</string>
<string name="quality_dvd">DVD</string>
<string name="pref_category_app_updates">עדכוני אפליקציה</string>
<string name="quality_4k">4K</string>
<string name="quality_workprint">WP</string>
<string name="skip_setup">דלג על ההגדרה</string>
<string name="pref_category_player_features">תכונות נגן</string>
<string name="episode_sync_settings">עדכן התקדמות צפייה</string>
<string name="dns_pref">DNS מעל HTTPS</string>
<string name="double_tap_to_pause_settings_des">לחץ באמצע כדי לעצור</string>
<string name="swipe_to_seek_settings_des">החלק שמאלה או ימינה כדי לשלוט על זמן בנגן הסרטונים</string>
<string name="autoplay_next_settings">נגן אוטומטית את הפרק הבא</string>
<string name="autoplay_next_settings_des">התחל את הפרק הבא כאשר הפרק הנוכחי נגמר</string>
<string name="double_tap_to_seek_settings_des">לחץ פעמיים על צד ימין או שמאל כדי להציץ קדימה או אחורה</string>
<string name="use_system_brightness_settings">השתמש בבהירות המערכת</string>
<string name="backup_settings">גבה נתונים</string>
<string name="subtitle_offset">סנכרן את הכתוביות</string>
<string name="subtitle_offset_title">עיכוב כתוביות</string>
<string name="subtitle_offset_extra_hint_none_format">אין עיכוב בכתוביות</string>
<string name="recommended">מומלץ</string>
<string name="player_loaded_subtitles" formatted="true">נטען %s</string>
<string name="subtitle_offset_extra_hint_later_format">השתמש בזה אם הכתוביות מוצגות %d מילישניות מוקדם יותר</string>
<string name="home_random">אקראי</string>
<string name="resolution">רזולוציה</string>
<string name="quality_cam_hd">איכות מצלמה</string>
<string name="error_invalid_url">כתובת אתר לא מזוהה</string>
<string name="network_adress_example">קישור לשידור</string>
<string name="referer">המפנה</string>
<string name="quality_tc">TC</string>
<string name="quality_sd">SD</string>
<string name="quality_uhd">UHD</string>
<string name="repository_url_hint">כתובת אתר למאגר</string>
<string name="preferred_media_settings">מדיה מועדפת</string>
<string name="sync_total_episodes_some" formatted="true">/%d</string>
<string name="play_episode_toast">נגן את הפרק</string>
<string name="no_links_found_toast">לא נמצאו קישורים</string>
<string name="example_lang_name">קוד שפה (אנגלית)</string>
<string name="error">שגיאה</string>
<string name="provider_lang_settings">שפות ספק</string>
<string name="pref_category_links">קישורים</string>
<string name="app_layout">פריסת אפליקציה</string>
<string name="advanced_search">חיפוש מתקדם</string>
<string name="is_adult">18+</string>
<string name="search_poster_img_des">פוסטר</string>
<string name="quality_cam">איכות מצלמה</string>
<string name="swipe_to_seek_settings">החלק כדי להציץ</string>
<string name="pref_category_backup">גיבוי</string>
<string name="double_tap_to_seek_amount_settings">כמות הצצת הנגן</string>
<string name="other_singular">סרטון</string>
<string name="github">גיטהאב</string>
<string name="quality_cam_rip">מצלמה</string>
<string name="swipe_to_change_settings">החלק לשינוי ההגדרות</string>
<string name="browser">‪דפדפן</string>
<string name="subs_window_color">צבע חלון</string>
<string name="show_log_cat">הצג לוג</string>
<string name="eigengraumode_settings_des">הוסף אפשרות מהירות בנגן</string>
<string name="double_tap_to_seek_settings">לחץ פעמיים כדי להציץ</string>
<string name="double_tap_to_pause_settings">לחץ פעמיים כדי לעצור</string>
<string name="use_system_brightness_settings_des">התשתמש בבהירות המערכת בנגן האפליקציה במקום שכבת-על כהה</string>
<string name="episode_sync_settings_des">סנכרן אוטומטית את התקדמות הפרק הנוכחית שלך</string>
<string name="restore_settings">שחזר נתונים מגיבוי</string>
<string name="restore_success">קובץ הגיבוי נטען</string>
<string name="backup_success">נתונים מאוחסנים</string>
<string name="backup_failed">חסרות הרשאות אחסון. אנא נסה שוב.</string>
<string name="backup_failed_error_format">שגיאה בגיבוי %s</string>
<string name="search">חיפוש</string>
<string name="category_account">חשבונות</string>
<string name="benene_count_text_none">לא ניתנו בנינים</string>
<string name="eigengraumode_settings">מצב איגנגראבי</string>
<string name="bug_report_settings_off">שולח נתונים רק בקריסות</string>
<string name="bug_report_settings_on">לא שולח נתונים</string>
<string name="show_trailers_settings">הצג טריילרים</string>
<string name="kitsu_settings">הצג פוסטרים מKitsu</string>
<string name="pref_filter_search_quality">הסתר את איכות הסרטון שנבחרה בתוצאות החיפוש</string>
<string name="apk_installer_settings">מתקין APK</string>
<string name="apk_installer_settings_des">חלק מהטלפונים אינם תומכים במתקין החבילות החדש. נסו את האפשרות מדור קודם אם העדכונים לא מתקינים.</string>
<string name="lightnovel">אפליקצית רומנים קלים על ידי אותו מפתחים</string>
<string name="anim">אפליקצית אנימה על ידי אותו מפתחים</string>
<string name="discord">הצטרפות לדיסקורד</string>
<string name="app_language">שפת האפליקציה</string>
<string name="no_chromecast_support_toast">לספק זה אין תמיכה בכרומקאסט</string>
<string name="copy_link_toast">הקישור הועתק ללוח</string>
<string name="subs_default_reset_toast">אפס לערך ברירת המחדל</string>
<string name="no_season">אין עונה</string>
<string name="status_completed">הושלם</string>
<string name="site">אתר</string>
<string name="synopsis">תקציר</string>
<string name="queued">בתור</string>
<string name="benene">לתת בניני למפתחים</string>
<string name="season_format">%s %d%s</string>
<string name="app_storage">אפליקציה</string>
<string name="movies">סרטים</string>
<string name="anime">אנימה</string>
<string name="documentaries">סרטים תיעודיים</string>
<string name="ova">אנימציית וידאו מקורית</string>
<string name="asian_drama">דרמות אסייתיות</string>
<string name="livestreams">שידורי חי</string>
<string name="others">אחר</string>
<string name="movies_singular">סרט</string>
<string name="tv_series_singular">סדרה</string>
<string name="cartoons_singular">סדרה/סרט מצויר</string>
<string name="torrent_singular">טורנט</string>
<string name="documentaries_singular">דוקומנטרי</string>
<string name="live_singular">שידור חי</string>
<string name="nsfw_singular">NSFW</string>
<string name="source_error">שגיאת מקור</string>
<string name="remote_error">שגיאה מרחוק</string>
<string name="unexpected_error">שגיאת נגן לא צפויה</string>
<string name="storage_error">שגיאת הורדה, בדוק הרשאות אחסון</string>
<string name="episode_action_play_in_app">נגן באפליקציה</string>
<string name="episode_action_play_in_format">נגן ב %s</string>
<string name="episode_action_copy_link">העתק קישור</string>
<string name="episode_action_auto_download">הורדה אוטומטית</string>
<string name="episode_action_reload_links">טען מחדש קישורים</string>
<string name="show_hd">תווית איכות</string>
<string name="no_update_found">לא נמצא עדכון</string>
<string name="check_for_update">בדוק אם יש עדכון</string>
<string name="video_lock">נעל</string>
<string name="video_aspect_ratio_resize">שינוי גודל</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_disk_description">גורם לקריסות אם מוגדר גבוה מדי במכשירים עם מקום נמוך, כגון Android TV.</string>
<string name="dns_pref_summary">שימושי עבור עקיפת מחסומי ספק שירותי אינטרנט</string>
<string name="add_site_pref">אתר העתק</string>
<string name="add_site_summary">הוסף העתק של אתר קיים, עם כתובת אתר אחרת</string>
<string name="nginx_url_pref">NGINX שרת כתובת אתר</string>
<string name="display_subbed_dubbed_settings">הצג אנימות מדובבות/מתורגמות</string>
<string name="resize_fit">התאם למסך</string>
<string name="resize_fill">מתוח</string>
<string name="resize_zoom">זום</string>
<string name="legal_notice">כתב ויתור</string>
<string name="pref_category_extensions">הרחבות</string>
<string name="pref_category_actions">פעולות</string>
<string name="pref_category_cache">מטמון</string>
<string name="pref_category_gestures">מחוות</string>
<string name="pref_category_subtitles">כתוביות</string>
<string name="pref_category_player_layout">פריסה</string>
<string name="pref_category_defaults">ברירות מחדל</string>
<string name="pref_category_ui_features">תכונות</string>
<string name="category_general">כללי</string>
<string name="random_button_settings">כפתור אקראי</string>
<string name="enable_nsfw_on_providers">הרשה NSFW בספקים נתמכים</string>
<string name="subtitles_encoding">קידוד כתוביות</string>
<string name="category_ui">פריסה</string>
<string name="phone_layout">פריסת טלפון</string>
<string name="emulator_layout">פריסת אמולטור</string>
<string name="primary_color_settings">צבע ראשוני</string>
<string name="bottom_title_settings_des">שים את הכותרת מתחת לפוסטר</string>
<string name="example_password">סיסמה123</string>
<string name="example_username">שםהמשתמשהמגניבשלי</string>
<string name="example_email">hello@world.com</string>
<string name="example_ip">127.0.0.1</string>
<string name="example_site_name">האתרהמגניבשלי</string>
<string name="example_site_url">דוגמה.com</string>
<string name="account">חשבון</string>
<string name="switch_account">החלף חשבון</string>
<string name="add_account">הוסף חשבון</string>
<string name="added_sync_format" formatted="true">נוסף %s</string>
<string name="upload_sync">סנכרן</string>
<string name="sync_score">מדורג</string>
<string name="sync_score_format" formatted="true">%d / 10</string>
<string name="sync_total_episodes_none">/\?\?</string>
<string name="authenticated_user" formatted="true">%s מאומת</string>
<string name="authenticated_user_fail" formatted="true">לא ניתן להתחבר ב %s</string>
<string name="normal">רגיל</string>
<string name="max">מקסימום</string>
<string name="min">מינימום</string>
<string name="subtitles_outline">מתאר</string>
<string name="subtitles_depressed">מדוכא</string>
<string name="subtitles_shadow">צל</string>
<string name="subtitles_raised">הורם</string>
<string name="subtitle_offset_hint">1000 מילישניות</string>
<string name="subtitle_offset_extra_hint_before_format">השתמש בזה אם הכתוביות מוצגות %d מילישניות מאוחר יותר</string>
<string name="subtitles_example_text">השועל החום המהיר קופץ מעל הכלב העצלן</string>
<string name="player_load_subtitles">טען מקובץ</string>
<string name="player_load_subtitles_online">טען מהאינטרנט</string>
<string name="downloaded_file">קובץ שהורד</string>
<string name="actor_main">ראשי</string>
<string name="actor_supporting">משנה</string>
<string name="actor_background">רקע</string>
<string name="home_source">מקור</string>
<string name="coming_soon">בקרוב…</string>
<string name="quality_ts">TS</string>
<string name="quality_blueray">Blu-ray</string>
<string name="quality_hdr">HDR</string>
<string name="quality_webrip">Web</string>
<string name="poster_image">תמונת פוסטר</string>
<string name="category_player">נגן</string>
<string name="resolution_and_title">רזולוציה וכותרת</string>
<string name="error_invalid_id">לא מזוהה ID</string>
<string name="error_invalid_data">נתונים לא מזוהים</string>
<string name="subtitles_remove_captions">הסר כיתובים סגורים מהכתוביות</string>
<string name="subtitles_remove_bloat">הסר נפיחות מכתוביות</string>
<string name="extras">תוספות</string>
<string name="trailer">טריילר</string>
<string name="provider_languages_tip">צפה בסרטונים בשפות אלה</string>
<string name="previous">קודם</string>
<string name="app_layout_subtext">שנה את מראה האפליקציה כך שיתאים למכשירך</string>
<string name="crash_reporting_title">דיווח על קריסה</string>
<string name="setup_done">סוים</string>
<string name="extensions">הרחבות</string>
<string name="add_repository">הוסף מאגר</string>
<string name="repository_name_hint">שם מאגר</string>
<string name="plugin_downloaded">תוסף הורד</string>
<string name="plugin_deleted">התוסף נמחק</string>
<string name="plugin_load_fail" formatted="true">לא יכול לטעון %s</string>
<string name="batch_download_start_format" formatted="true">התחיל להוריד %d %s…</string>
<string name="batch_download_finish_format" formatted="true">הוריד %d %s</string>
<string name="enable_skip_op_from_database_des">הצג חלונות קופצים של דילוג בשביל פתיח/שיר סיום</string>
<string name="delete_repository">מחק מאגר</string>
<string name="skip_type_mixed_op">פתיח מעורב</string>
<string name="batch_download_nothing_to_download_format" formatted="true">כל %s כבר הורד</string>
<string name="extension_authors">מחברים</string>
<string name="extension_language">שפה</string>
<string name="player_settings_play_in_mpv">MPV</string>
<string name="skip_type_creddits">קרדיטים</string>
<string name="sort">מיין</string>
<string name="select_library">בחר ספרייה</string>
<string name="empty_library_no_accounts_message">נראה שהספרייה שלכם ריקה :(
\nהתחברו לחשבון ספריה או הוסף סדרות לספרייה המקומית שלך</string>
<string name="safe_mode_file">קובץ מצב בטוח נמצא!
\nלא טוען שום תוספות בהפעלה עד להסרת הקובץ.</string>
<string name="update_notification_failed">לא ניתן להתקין את הגרסה החדשה של האפליקציה</string>
<string name="batch_download">הורדת אצווה</string>
<string name="plugin_singular">תוסף</string>
<string name="download_all_plugins_from_repo">הורד את כל התוספים ממאגר זה\?</string>
<string name="audio_tracks">רצועות שמע</string>
<string name="tracks">מסלולים</string>
<string name="player_settings_play_in_web">Web Video Cast</string>
<string name="player_settings_play_in_browser">דפדפן אינטרנט</string>
<string name="safe_mode_description">כל התוספים נכבו עקב התרסקות כדי לעזור לך למצוא את האחד הגורם לצרות.</string>
<string name="apk_installer_package_installer">מוריד החבילות</string>
<string name="plugins_updated" formatted="true">עודכן %d תוספים</string>
<string name="plugin">תוספים</string>
<string name="delete_repository_plugins">פעולה זו תמחק גם את כל תוספי המאגר</string>
<string name="setup_extensions_subtext">הורד את רשימת האתרים שבהם ברצונך להשתמש</string>
<string name="plugins_downloaded" formatted="true">הורד: %d</string>
<string name="plugins_disabled" formatted="true">מוגבל: %d</string>
<string name="plugins_not_downloaded" formatted="true">לא מורד: %d</string>
<string name="blank_repo_message">לקלאודסטרים אין אתרים מותקנים כברירת מחדל. עליכם להתקין את האתרים ממאגרים.
\n
\nבגלל הסרת DMCA על ידי Sky UK LImited 🤮 אנחנו לא יכולים לקשר את אתר המאגרים באפליקציה.
\n
\nתצטרפו לדיסקורד שלנו או תחפשו באינטרנט.</string>
<string name="view_public_repositories_button">הצג מאגרים קהילתיים</string>
<string name="view_public_repositories_button_short">רשימה ציבורית</string>
<string name="uppercase_all_subtitles">לשים את הכתוביות באותיות רישיות</string>
<string name="single_plugin_disabled" formatted="true">%s (מוגבל)</string>
<string name="video_tracks">רצועות וידאו</string>
<string name="apply_on_restart">החל על הפעלה מחדש</string>
<string name="safe_mode_title">מצב בטוח מופעל</string>
<string name="safe_mode_crash_info">הצג מידע על ההתרסקות</string>
<string name="extension_rating" formatted="true">דירוג: %s</string>
<string name="extension_description">תיאור</string>
<string name="extension_version">גרסה</string>
<string name="extension_status">מצב</string>
<string name="extension_size">גודל</string>
<string name="extension_types">תומך</string>
<string name="extension_install_first">התקן תחילה את התוסף</string>
<string name="hls_playlist">פלייליסט HLS</string>
<string name="player_pref">נגן וידאו מועדף</string>
<string name="player_settings_play_in_app">נגן פנימי</string>
<string name="player_settings_play_in_vlc">VLC</string>
<string name="app_not_found_error">האפליקציה לא נמצאה</string>
<string name="all_languages_preference">כל השפות</string>
<string name="skip_type_format" formatted="true">דלג %s</string>
<string name="skip_type_op">פתיח</string>
<string name="skip_type_ed">שיר סיום</string>
<string name="skip_type_recap">סיכום</string>
<string name="skip_type_mixed_ed">שיר סיום מעורב</string>
<string name="skip_type_intro">מבוא</string>
<string name="clear_history">נקה היסטוריה</string>
<string name="clipboard_too_large">יותר מדי טקסט. לא ניתן לשמור ללוח.</string>
<string name="action_remove_from_watched">הסר מצפייה</string>
<string name="confirm_exit_dialog">האם אתם בטוחים שאתם רוצים לצאת\?</string>
<string name="update_notification_downloading">מוריד עדכון אפליקציה…</string>
<string name="update_notification_installing">מתקין עדכון אפליקציה…</string>
<string name="delayed_update_notice">האפליקציה תעודכן עם היציאה</string>
<string name="sort_by">מיין לפי</string>
<string name="sort_rating_desc">דירוג (גבוה לנמוך)</string>
<string name="sort_rating_asc">דירוג (נמוך לגבוה)</string>
<string name="sort_updated_new">מעודכן (חדש לישן)</string>
<string name="sort_updated_old">מעודכן (ישן לחדש)</string>
<string name="sort_alphabetical_a">אלפביתי (א \'עד ת\')</string>
<string name="sort_alphabetical_z">אלפביתי (ת\' עד א\')</string>
<string name="open_with">פתח עם</string>
<string name="empty_library_logged_in_message">נראה שהרשימה הזו ריקה, נסו לעבור לרשימה אחרת</string>
</resources> </resources>

View file

@ -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 -&gt; %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>

Some files were not shown because too many files have changed in this diff Show more