forked from recloudstream/cloudstream
Compare commits
33 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7a1cfc2f2b | ||
|
a8bcf2dcb2 | ||
|
df95931d0a | ||
|
46732ea917 | ||
|
cd166cb0d3 | ||
|
cbeeff5cca | ||
|
e7732cc15f | ||
|
e875d520de | ||
|
ef7e0ecf0d | ||
|
7512dbcbf8 | ||
|
1f67644290 | ||
|
0aca996bc0 | ||
|
26320bb535 | ||
|
9bc90438ec | ||
|
dd293e9564 | ||
|
f6c609cfdf | ||
|
4a0989c9c5 | ||
|
847c70a127 | ||
|
20ada8da0d | ||
|
daaab9d35b | ||
|
c4afb5e673 | ||
|
0dab9959e1 | ||
|
e945600940 | ||
|
9edb7f8999 | ||
|
7053d77ca1 | ||
|
30dbd3920e | ||
|
019e9a0c4f | ||
|
7cd6dd3fe6 | ||
|
8cdc31ffcd | ||
|
2a2a0a26e7 | ||
|
67a1c447ae | ||
|
05dc032df6 | ||
|
67fe6730e0 |
130 changed files with 1302 additions and 4970 deletions
BIN
.github/downloads.jpg
vendored
Normal file
BIN
.github/downloads.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
.github/home.jpg
vendored
Normal file
BIN
.github/home.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
20
.github/locales.py
vendored
20
.github/locales.py
vendored
|
@ -1,14 +1,13 @@
|
|||
import re
|
||||
import glob
|
||||
import requests
|
||||
import lxml.etree as ET # builtin library doesn't preserve comments
|
||||
|
||||
|
||||
SETTINGS_PATH = "app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt"
|
||||
START_MARKER = "/* begin language list */"
|
||||
END_MARKER = "/* end language list */"
|
||||
XML_NAME = "app/src/main/res/values-"
|
||||
ISO_MAP_URL = "https://raw.githubusercontent.com/haliaeetus/iso-639/master/data/iso_639-1.min.json"
|
||||
ISO_MAP_URL = "https://gist.githubusercontent.com/Josantonius/b455e315bc7f790d14b136d61d9ae469/raw"
|
||||
INDENT = " "*4
|
||||
|
||||
iso_map = requests.get(ISO_MAP_URL, timeout=300).json()
|
||||
|
@ -28,8 +27,7 @@ for lang in re.finditer(r'Triple\("(.*)", "(.*)", "(.*)"\)', rest):
|
|||
for folder in glob.glob(f"{XML_NAME}*"):
|
||||
iso = folder[len(XML_NAME):]
|
||||
if iso not in languages.keys():
|
||||
entry = iso_map.get(iso.lower(),{'nativeName':iso})
|
||||
languages[iso] = ("", entry['nativeName'].split(',')[0])
|
||||
languages[iso] = ("", iso_map.get(iso.lower(),iso))
|
||||
|
||||
# Create triples
|
||||
triples = []
|
||||
|
@ -47,17 +45,3 @@ open(SETTINGS_PATH, "w+",encoding='utf-8').write(
|
|||
END_MARKER +
|
||||
after_src
|
||||
)
|
||||
|
||||
# Go through each values.xml file and fix escaped \@string
|
||||
for file in glob.glob(f"{XML_NAME}*/strings.xml"):
|
||||
try:
|
||||
tree = ET.parse(file)
|
||||
for child in tree.getroot():
|
||||
if child.text.startswith("\\@string/"):
|
||||
print(f"[{file}] fixing {child.attrib['name']}")
|
||||
child.text = child.text.replace("\\@string/", "@string/")
|
||||
with open(file, 'wb') as fp:
|
||||
fp.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
|
||||
tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False)
|
||||
except ET.ParseError as ex:
|
||||
print(f"[{file}] {ex}")
|
||||
|
|
BIN
.github/player.jpg
vendored
Normal file
BIN
.github/player.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
.github/results.jpg
vendored
Normal file
BIN
.github/results.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
BIN
.github/search.jpg
vendored
Normal file
BIN
.github/search.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 149 KiB |
25
.github/workflows/issue_action.yml
vendored
25
.github/workflows/issue_action.yml
vendored
|
@ -15,7 +15,6 @@ jobs:
|
|||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
- name: Similarity analysis
|
||||
id: similarity
|
||||
uses: actions-cool/issues-similarity-analysis@v1
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
@ -25,18 +24,6 @@ jobs:
|
|||
### Your issue looks similar to these issues:
|
||||
Please close if duplicate.
|
||||
comment-body: '${index}. ${similarity} #${number}'
|
||||
- name: Label if possible duplicate
|
||||
if: steps.similarity.outputs.similar-issues-found =='true'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ["possible duplicate"]
|
||||
})
|
||||
- uses: actions/checkout@v2
|
||||
- name: Automatically close issues that dont follow the issue template
|
||||
uses: lucasbento/auto-close-issues@v1.0.2
|
||||
|
@ -66,18 +53,6 @@ jobs:
|
|||
Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM).
|
||||
|
||||
Found provider name: `${{ steps.provider_check.outputs.name }}`
|
||||
- name: Label if mentions provider
|
||||
if: steps.provider_check.outputs.name != 'none'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ["possible provider issue"]
|
||||
})
|
||||
- name: Add eyes reaction to all issues
|
||||
uses: actions-cool/emoji-helper@v1.0.0
|
||||
with:
|
||||
|
|
9
.github/workflows/update_locales.yml
vendored
9
.github/workflows/update_locales.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Fix locale issues
|
||||
name: Update locale lists
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
@ -9,7 +9,7 @@ on:
|
|||
- master
|
||||
|
||||
concurrency:
|
||||
group: "locale"
|
||||
group: "locale-list"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
@ -26,9 +26,6 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip3 install lxml
|
||||
- name: Edit files
|
||||
run: |
|
||||
python3 .github/locales.py
|
||||
|
@ -38,5 +35,5 @@ jobs:
|
|||
git config --local user.name "recloudstream[bot]"
|
||||
git add .
|
||||
# "echo" returns true so the build succeeds, even if no changed files
|
||||
git commit -m 'chore(locales): fix locale issues' || echo
|
||||
git commit -m 'update list of locales' || echo
|
||||
git push
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
+ Download and stream movies, tv-shows and anime
|
||||
+ Chromecast
|
||||
|
||||
### Screenshots:
|
||||
|
||||
<img src="./.github/home.jpg" height="400"/><img src="./.github/search.jpg" height="400"/><img src="./.github/downloads.jpg" height="400"/><img src="./.github/results.jpg" height="400"/>
|
||||
<img src="./.github/player.jpg" height="200"/>
|
||||
|
||||
### Supported languages:
|
||||
<a href="https://hosted.weblate.org/engage/cloudstream/">
|
||||
<img src="https://hosted.weblate.org/widgets/cloudstream/-/app/multi-auto.svg" alt="Translation status" />
|
||||
|
|
|
@ -47,8 +47,8 @@ android {
|
|||
minSdk = 21
|
||||
targetSdk = 33
|
||||
|
||||
versionCode = 57
|
||||
versionName = "4.0.0"
|
||||
versionCode = 56
|
||||
versionName = "3.5.0"
|
||||
|
||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||
|
||||
|
@ -159,8 +159,6 @@ dependencies {
|
|||
implementation("com.google.android.exoplayer:extension-cast:2.18.2")
|
||||
implementation("com.google.android.exoplayer:extension-mediasession:2.18.2")
|
||||
implementation("com.google.android.exoplayer:extension-okhttp:2.18.2")
|
||||
// Use the Jellyfin ffmpeg extension for easy ffmpeg audio decoding in exoplayer. Thank you Jellyfin <3
|
||||
// implementation("org.jellyfin.exoplayer:exoplayer-ffmpeg-extension:2.18.2+1")
|
||||
|
||||
//implementation("com.google.android.exoplayer:extension-leanback:2.14.0")
|
||||
|
||||
|
@ -186,13 +184,13 @@ dependencies {
|
|||
//implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0")
|
||||
|
||||
// Downloading
|
||||
implementation("androidx.work:work-runtime:2.8.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.0")
|
||||
implementation("androidx.work:work-runtime:2.7.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.7.1")
|
||||
|
||||
// Networking
|
||||
// implementation("com.squareup.okhttp3:okhttp:4.9.2")
|
||||
// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
|
||||
implementation("com.github.Blatzar:NiceHttp:0.4.2")
|
||||
implementation("com.github.Blatzar:NiceHttp:0.4.1")
|
||||
// To fix SSL fuckery on android 9
|
||||
implementation("org.conscrypt:conscrypt-android:2.2.1")
|
||||
// Util to skip the URI file fuckery 🙏
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||
import com.lagradost.cloudstream3.utils.TestingUtils
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
@ -15,11 +16,142 @@ import org.junit.runner.RunWith
|
|||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
//@Test
|
||||
//fun useAppContext() {
|
||||
// // Context of the app under test.
|
||||
// val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
// assertEquals("com.lagradost.cloudstream3", appContext.packageName)
|
||||
//}
|
||||
|
||||
private fun getAllProviders(): List<MainAPI> {
|
||||
println("Providers: ${APIHolder.allProviders.size}")
|
||||
return APIHolder.allProviders //.filter { !it.usesWebView }
|
||||
}
|
||||
|
||||
private suspend fun loadLinks(api: MainAPI, url: String?): Boolean {
|
||||
Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
|
||||
if (url == null) return true
|
||||
var linksLoaded = 0
|
||||
try {
|
||||
val success = api.loadLinks(url, false, {}) { link ->
|
||||
Assert.assertTrue(
|
||||
"Api ${api.name} returns link with invalid Quality",
|
||||
Qualities.values().map { it.value }.contains(link.quality)
|
||||
)
|
||||
Assert.assertTrue(
|
||||
"Api ${api.name} returns link with invalid url ${link.url}",
|
||||
link.url.length > 4
|
||||
)
|
||||
linksLoaded++
|
||||
}
|
||||
if (success) {
|
||||
return linksLoaded > 0
|
||||
}
|
||||
Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success)
|
||||
} catch (e: Exception) {
|
||||
if (e.cause is NotImplementedError) {
|
||||
Assert.fail("Provider has not implemented .loadLinks")
|
||||
}
|
||||
logError(e)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun testSingleProviderApi(api: MainAPI): Boolean {
|
||||
val searchQueries = listOf("over", "iron", "guy")
|
||||
var correctResponses = 0
|
||||
var searchResult: List<SearchResponse>? = null
|
||||
for (query in searchQueries) {
|
||||
val response = try {
|
||||
api.search(query)
|
||||
} catch (e: Exception) {
|
||||
if (e.cause is NotImplementedError) {
|
||||
Assert.fail("Provider has not implemented .search")
|
||||
}
|
||||
logError(e)
|
||||
null
|
||||
}
|
||||
if (!response.isNullOrEmpty()) {
|
||||
correctResponses++
|
||||
if (searchResult == null) {
|
||||
searchResult = response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (correctResponses == 0 || searchResult == null) {
|
||||
System.err.println("Api ${api.name} did not return any valid search responses")
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
var validResults = false
|
||||
for (result in searchResult) {
|
||||
Assert.assertEquals(
|
||||
"Invalid apiName on response on ${api.name}",
|
||||
result.apiName,
|
||||
api.name
|
||||
)
|
||||
val load = api.load(result.url) ?: continue
|
||||
Assert.assertEquals(
|
||||
"Invalid apiName on load on ${api.name}",
|
||||
load.apiName,
|
||||
result.apiName
|
||||
)
|
||||
Assert.assertTrue(
|
||||
"Api ${api.name} on load does not contain any of the supportedTypes",
|
||||
api.supportedTypes.contains(load.type)
|
||||
)
|
||||
when (load) {
|
||||
is AnimeLoadResponse -> {
|
||||
val gotNoEpisodes =
|
||||
load.episodes.keys.isEmpty() || load.episodes.keys.any { load.episodes[it].isNullOrEmpty() }
|
||||
|
||||
if (gotNoEpisodes) {
|
||||
println("Api ${api.name} got no episodes on ${load.url}")
|
||||
continue
|
||||
}
|
||||
|
||||
val url = (load.episodes[load.episodes.keys.first()])?.first()?.data
|
||||
validResults = loadLinks(api, url)
|
||||
if (!validResults) continue
|
||||
}
|
||||
is MovieLoadResponse -> {
|
||||
val gotNoEpisodes = load.dataUrl.isBlank()
|
||||
if (gotNoEpisodes) {
|
||||
println("Api ${api.name} got no movie on ${load.url}")
|
||||
continue
|
||||
}
|
||||
|
||||
validResults = loadLinks(api, load.dataUrl)
|
||||
if (!validResults) continue
|
||||
}
|
||||
is TvSeriesLoadResponse -> {
|
||||
val gotNoEpisodes = load.episodes.isEmpty()
|
||||
if (gotNoEpisodes) {
|
||||
println("Api ${api.name} got no episodes on ${load.url}")
|
||||
continue
|
||||
}
|
||||
|
||||
validResults = loadLinks(api, load.episodes.first().data)
|
||||
if (!validResults) continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
if (!validResults) {
|
||||
System.err.println("Api ${api.name} did not load on any")
|
||||
}
|
||||
|
||||
return validResults
|
||||
} catch (e: Exception) {
|
||||
if (e.cause is NotImplementedError) {
|
||||
Assert.fail("Provider has not implemented .load")
|
||||
}
|
||||
logError(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun providersExist() {
|
||||
Assert.assertTrue(getAllProviders().isNotEmpty())
|
||||
|
@ -27,7 +159,6 @@ class ExampleInstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Throws(AssertionError::class)
|
||||
fun providerCorrectData() {
|
||||
val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 }
|
||||
Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty())
|
||||
|
@ -50,20 +181,67 @@ class ExampleInstrumentedTest {
|
|||
fun providerCorrectHomepage() {
|
||||
runBlocking {
|
||||
getAllProviders().amap { api ->
|
||||
TestingUtils.testHomepage(api, ::println)
|
||||
if (api.hasMainPage) {
|
||||
try {
|
||||
val f = api.mainPage.first()
|
||||
val homepage =
|
||||
api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages))
|
||||
when {
|
||||
homepage == null -> {
|
||||
System.err.println("Homepage provider ${api.name} did not correctly load homepage!")
|
||||
}
|
||||
homepage.items.isEmpty() -> {
|
||||
System.err.println("Homepage provider ${api.name} does not contain any items!")
|
||||
}
|
||||
homepage.items.any { it.list.isEmpty() } -> {
|
||||
System.err.println("Homepage provider ${api.name} does not have any items on result!")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e.cause is NotImplementedError) {
|
||||
Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented")
|
||||
}
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println("Done providerCorrectHomepage")
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun testSingleProvider() {
|
||||
// testSingleProviderApi(ThenosProvider())
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun testAllProvidersCorrect() {
|
||||
fun providerCorrect() {
|
||||
runBlocking {
|
||||
TestingUtils.getDeferredProviderTests(
|
||||
this,
|
||||
getAllProviders(),
|
||||
::println
|
||||
) { _, _ -> }
|
||||
val invalidProvider = ArrayList<Pair<MainAPI, Exception?>>()
|
||||
val providers = getAllProviders()
|
||||
providers.amap { api ->
|
||||
try {
|
||||
println("Trying $api")
|
||||
if (testSingleProviderApi(api)) {
|
||||
println("Success $api")
|
||||
} else {
|
||||
System.err.println("Error $api")
|
||||
invalidProvider.add(Pair(api, null))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
invalidProvider.add(Pair(api, e))
|
||||
}
|
||||
}
|
||||
if (invalidProvider.isEmpty()) {
|
||||
println("No Invalid providers! :D")
|
||||
} else {
|
||||
println("Invalid providers are: ")
|
||||
for (provider in invalidProvider) {
|
||||
println("${provider.first}")
|
||||
}
|
||||
}
|
||||
}
|
||||
println("Done providerCorrect")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,16 +98,6 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- cloudstreamplayer://encodedUrl?name=Dune -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="cloudstreamplayer" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
|
@ -15,12 +15,13 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniList
|
|||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.ui.result.UiText
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import okhttp3.Interceptor
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
@ -84,7 +85,7 @@ object APIHolder {
|
|||
initMap()
|
||||
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
|
||||
// Leave the ?. null check, it can crash regardless
|
||||
?: allProviders.firstOrNull { it.name == apiName }
|
||||
?: allProviders.firstOrNull { it?.name == apiName }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,53 +163,6 @@ object APIHolder {
|
|||
return null
|
||||
}
|
||||
|
||||
private var trackerCache: HashMap<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> {
|
||||
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
|
@ -736,19 +690,6 @@ fun fixTitle(str: String): String {
|
|||
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get rhino context in a safe way as it needs to be initialized on the main thread.
|
||||
* Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects()
|
||||
* Use like the following: rhino.evaluateString(scope, js, "JavaScript", 1, null)
|
||||
**/
|
||||
suspend fun getRhinoContext(): org.mozilla.javascript.Context {
|
||||
return Coroutines.mainWork {
|
||||
val rhino = org.mozilla.javascript.Context.enter()
|
||||
rhino.initSafeStandardObjects()
|
||||
rhino.optimizationLevel = -1
|
||||
rhino
|
||||
}
|
||||
}
|
||||
|
||||
/** https://www.imdb.com/title/tt2861424/ -> tt2861424 */
|
||||
fun imdbUrlToId(url: String): String? {
|
||||
|
@ -1327,7 +1268,7 @@ fun LoadResponse?.isAnimeBased(): Boolean {
|
|||
|
||||
fun TvType?.isEpisodeBased(): Boolean {
|
||||
if (this == null) return false
|
||||
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
|
||||
return (this == TvType.TvSeries || this == TvType.Anime)
|
||||
}
|
||||
|
||||
|
||||
|
@ -1351,7 +1292,6 @@ interface EpisodeResponse {
|
|||
var showStatus: ShowStatus?
|
||||
var nextAiring: NextAiring?
|
||||
var seasonNames: List<SeasonData>?
|
||||
fun getLatestEpisodes(): Map<DubStatus, Int?>
|
||||
}
|
||||
|
||||
@JvmName("addSeasonNamesString")
|
||||
|
@ -1420,18 +1360,7 @@ data class AnimeLoadResponse(
|
|||
override var nextAiring: NextAiring? = null,
|
||||
override var seasonNames: List<SeasonData>? = null,
|
||||
override var backgroundPosterUrl: String? = null,
|
||||
) : LoadResponse, EpisodeResponse {
|
||||
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
||||
return episodes.map { (status, episodes) ->
|
||||
val maxSeason = episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }
|
||||
.takeUnless { it == Int.MIN_VALUE }
|
||||
status to episodes
|
||||
.filter { it.season == maxSeason }
|
||||
.maxOfOrNull { it.episode ?: Int.MIN_VALUE }
|
||||
.takeUnless { it == Int.MIN_VALUE }
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
) : LoadResponse, EpisodeResponse
|
||||
|
||||
/**
|
||||
* If episodes already exist appends the list.
|
||||
|
@ -1629,17 +1558,7 @@ data class TvSeriesLoadResponse(
|
|||
override var nextAiring: NextAiring? = null,
|
||||
override var seasonNames: List<SeasonData>? = null,
|
||||
override var backgroundPosterUrl: String? = null,
|
||||
) : LoadResponse, EpisodeResponse {
|
||||
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
||||
val maxSeason =
|
||||
episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }.takeUnless { it == Int.MIN_VALUE }
|
||||
val max = episodes
|
||||
.filter { it.season == maxSeason }
|
||||
.maxOfOrNull { it.episode ?: Int.MIN_VALUE }
|
||||
.takeUnless { it == Int.MIN_VALUE }
|
||||
return mapOf(DubStatus.None to max)
|
||||
}
|
||||
}
|
||||
) : LoadResponse, EpisodeResponse
|
||||
|
||||
suspend fun MainAPI.newTvSeriesLoadResponse(
|
||||
name: String,
|
||||
|
@ -1671,61 +1590,3 @@ fun fetchUrls(text: String?): List<String> {
|
|||
|
||||
fun String?.toRatingInt(): Int? =
|
||||
this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt()
|
||||
|
||||
data class Tracker(
|
||||
val malId: Int? = null,
|
||||
val aniId: String? = null,
|
||||
val image: String? = null,
|
||||
val cover: String? = null,
|
||||
)
|
||||
|
||||
data class Title(
|
||||
@JsonProperty("romaji") val romaji: String? = null,
|
||||
@JsonProperty("english") val english: String? = null,
|
||||
) {
|
||||
fun isMatchingTitles(title: String?): Boolean {
|
||||
if (title == null) return false
|
||||
return english.equals(title, true) || romaji.equals(title, true)
|
||||
}
|
||||
}
|
||||
|
||||
data class Results(
|
||||
@JsonProperty("id") val aniId: String? = null,
|
||||
@JsonProperty("malId") val malId: Int? = null,
|
||||
@JsonProperty("title") val title: Title? = null,
|
||||
@JsonProperty("releaseDate") val releaseDate: Int? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
@JsonProperty("image") val image: String? = null,
|
||||
@JsonProperty("cover") val cover: String? = null,
|
||||
)
|
||||
|
||||
data class AniSearch(
|
||||
@JsonProperty("results") val results: ArrayList<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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
|
@ -34,7 +32,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|||
import com.google.android.gms.cast.framework.*
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.navigationrail.NavigationRailView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
|
||||
import com.lagradost.cloudstream3.APIHolder.allProviders
|
||||
import com.lagradost.cloudstream3.APIHolder.apis
|
||||
|
@ -58,7 +55,6 @@ import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
|||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
|
||||
|
@ -67,13 +63,7 @@ import com.lagradost.cloudstream3.ui.APIRepository
|
|||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||
import com.lagradost.cloudstream3.ui.home.HomeViewModel
|
||||
import com.lagradost.cloudstream3.ui.player.BasicLink
|
||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
||||
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
|
||||
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
||||
import com.lagradost.cloudstream3.ui.result.setImage
|
||||
import com.lagradost.cloudstream3.ui.result.setText
|
||||
import com.lagradost.cloudstream3.ui.result.*
|
||||
import com.lagradost.cloudstream3.ui.search.SearchFragment
|
||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
||||
|
@ -86,7 +76,6 @@ import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
|||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||
|
@ -94,7 +83,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
||||
|
@ -175,12 +163,7 @@ open class ResultResume(
|
|||
|
||||
val VLC = object : ResultResume(
|
||||
VLC_PACKAGE,
|
||||
// Android 13 intent restrictions fucks up specifically launching the VLC player
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
"org.videolan.vlc.player.result"
|
||||
} else {
|
||||
Intent.ACTION_VIEW
|
||||
},
|
||||
"org.videolan.vlc.player.result",
|
||||
"extra_position",
|
||||
"extra_duration",
|
||||
) {
|
||||
|
@ -279,8 +262,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
isWebview: Boolean
|
||||
): Boolean =
|
||||
with(activity) {
|
||||
// TODO MUCH BETTER HANDLING
|
||||
|
||||
// Invalid URIs can crash
|
||||
fun safeURI(uri: String) = normalSafeApiCall { URI(uri) }
|
||||
|
||||
|
@ -331,25 +312,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
} else if (safeURI(str)?.scheme == appStringSearch) {
|
||||
nextSearchQuery =
|
||||
URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8")
|
||||
|
||||
// Use both navigation views to support both layouts.
|
||||
// It might be better to use the QuickSearch.
|
||||
nav_view?.selectedItemId = R.id.navigation_search
|
||||
nav_rail_view?.selectedItemId = R.id.navigation_search
|
||||
} else if (safeURI(str)?.scheme == appStringPlayer) {
|
||||
val uri = Uri.parse(str)
|
||||
val name = uri.getQueryParameter("name")
|
||||
val url = URLDecoder.decode(uri.authority, "UTF-8")
|
||||
|
||||
navigate(
|
||||
R.id.global_to_navigation_player,
|
||||
GeneratorPlayer.newInstance(
|
||||
LinkGenerator(
|
||||
listOf(BasicLink(url, name)),
|
||||
extract = true,
|
||||
)
|
||||
)
|
||||
)
|
||||
nav_view.selectedItemId = R.id.navigation_search
|
||||
} else if (safeURI(str)?.scheme == appStringResumeWatching) {
|
||||
val id =
|
||||
str.substringAfter("$appStringResumeWatching://").toIntOrNull()
|
||||
|
@ -381,7 +344,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
}
|
||||
|
||||
var lastPopup: SearchResponse? = null
|
||||
var lastPopup : SearchResponse? = null
|
||||
fun loadPopup(result: SearchResponse) {
|
||||
lastPopup = result
|
||||
viewModel.load(
|
||||
|
@ -436,7 +399,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
R.id.navigation_settings_general,
|
||||
R.id.navigation_settings_extensions,
|
||||
R.id.navigation_settings_plugins,
|
||||
R.id.navigation_test_providers,
|
||||
).contains(destination.id)
|
||||
|
||||
|
||||
|
@ -751,35 +713,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
|
||||
changeStatusBarState(isEmulatorSettings())
|
||||
|
||||
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
|
||||
if (this.getKey<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) {
|
||||
if (lastError == null) {
|
||||
ioSafe {
|
||||
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
|
||||
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
|
||||
|
@ -860,7 +794,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
resultview_preview_meta_duration.setText(d.durationText)
|
||||
resultview_preview_meta_rating.setText(d.ratingText)
|
||||
|
||||
resultview_preview_description?.setText(d.plotText)
|
||||
resultview_preview_description?.setTextHtml(d.plotText)
|
||||
resultview_preview_poster?.setImage(
|
||||
d.posterImage ?: d.posterBackgroundImage
|
||||
)
|
||||
|
@ -1147,15 +1081,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
// }
|
||||
|
||||
}
|
||||
|
||||
suspend fun checkGithubConnectivity(): Boolean {
|
||||
return try {
|
||||
app.get(
|
||||
"https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck",
|
||||
timeout = 5
|
||||
).text.trim() == "ok"
|
||||
} catch (t: Throwable) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.lagradost.cloudstream3.app
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import java.net.URL
|
||||
|
||||
|
@ -43,9 +42,18 @@ open class Dailymotion : ExtractorApi() {
|
|||
)
|
||||
val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = cookies)
|
||||
.parsedSafe<MetaData>() ?: return
|
||||
metaData.qualities.forEach { (_, video) ->
|
||||
metaData.qualities.forEach { (key, video) ->
|
||||
video.forEach {
|
||||
getStream(it.url, this.name, callback)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"$name $key",
|
||||
it.url,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,17 +75,6 @@ open class Dailymotion : ExtractorApi() {
|
|||
return null
|
||||
}
|
||||
|
||||
private suspend fun getStream(
|
||||
streamLink: String,
|
||||
name: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
return generateM3u8(
|
||||
name,
|
||||
streamLink,
|
||||
"",
|
||||
).forEach(callback)
|
||||
}
|
||||
data class Config(
|
||||
val context: Context,
|
||||
val dmInternalData: InternalData
|
||||
|
|
|
@ -38,9 +38,6 @@ class DoodWsExtractor : DoodLaExtractor() {
|
|||
override var mainUrl = "https://dood.ws"
|
||||
}
|
||||
|
||||
class DoodYtExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.yt"
|
||||
}
|
||||
|
||||
open class DoodLaExtractor : ExtractorApi() {
|
||||
override var name = "DoodStream"
|
||||
|
|
|
@ -16,7 +16,26 @@ open class Evoload : ExtractorApi() {
|
|||
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val id = url.replace("https://evoload.io/e/", "") // wanted media id
|
||||
val lang = url.substring(0, 2)
|
||||
val flag =
|
||||
if (lang == "vo") {
|
||||
" \uD83C\uDDEC\uD83C\uDDE7"
|
||||
}
|
||||
else if (lang == "vf"){
|
||||
" \uD83C\uDDE8\uD83C\uDDF5"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http://
|
||||
url
|
||||
} else {
|
||||
url.substring(2, url.length)
|
||||
}
|
||||
//println(lang)
|
||||
//println(cleaned_url)
|
||||
|
||||
val id = cleaned_url.replace("https://evoload.io/e/", "") // wanted media id
|
||||
val csrv_token = app.get("https://csrv.evosrv.com/captcha?m412548=").text // whatever that is
|
||||
val captchaPass = app.get("https://cd2.evosrv.com/html/jsx/e.jsx").text.take(300).split("captcha_pass = '")[1].split("\'")[0] //extract the captcha pass from the js response (located in the 300 first chars)
|
||||
val payload = mapOf("code" to id, "csrv_token" to csrv_token, "pass" to captchaPass)
|
||||
|
@ -25,9 +44,9 @@ open class Evoload : ExtractorApi() {
|
|||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
name + flag,
|
||||
link,
|
||||
url,
|
||||
cleaned_url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,57 +1,38 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
|
||||
|
||||
class Ztreamhub : Filesim() {
|
||||
override val mainUrl: String = "https://ztreamhub.com" //Here 'cause works
|
||||
override val name = "Zstreamhub"
|
||||
}
|
||||
class FileMoon : Filesim() {
|
||||
override val mainUrl = "https://filemoon.to"
|
||||
override val name = "FileMoon"
|
||||
}
|
||||
|
||||
class FileMoonSx : Filesim() {
|
||||
override val mainUrl = "https://filemoon.sx"
|
||||
override val name = "FileMoonSx"
|
||||
}
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
|
||||
open class Filesim : ExtractorApi() {
|
||||
override val name = "Filesim"
|
||||
override val mainUrl = "https://files.im"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val response = app.get(url, referer = mainUrl).document
|
||||
response.select("script[type=text/javascript]").map { script ->
|
||||
if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) {
|
||||
val unpackedscript = getAndUnpack(script.data())
|
||||
val m3u8Regex = Regex("file.\\\"(.*?m3u8.*?)\\\"")
|
||||
val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: ""
|
||||
if (m3u8.isNotEmpty()) {
|
||||
generateM3u8(
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
with(app.get(url).document) {
|
||||
this.select("script").map { script ->
|
||||
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
|
||||
val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]")
|
||||
tryParseJson<List<ResponseSource>>("[$data]")?.map {
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8,
|
||||
mainUrl
|
||||
).forEach(callback)
|
||||
it.file,
|
||||
"$mainUrl/",
|
||||
).forEach { m3uData -> sources.add(m3uData) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
/* private data class ResponseSource(
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("label") val label: String?
|
||||
) */
|
||||
)
|
||||
|
||||
}
|
|
@ -6,11 +6,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
|||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
class Vanfem : GuardareStream() {
|
||||
override var name = "Vanfem"
|
||||
override var mainUrl = "https://vanfem.com/"
|
||||
}
|
||||
|
||||
class CineGrabber : GuardareStream() {
|
||||
override var name = "CineGrabber"
|
||||
override var mainUrl = "https://cinegrabber.com"
|
||||
|
|
|
@ -18,14 +18,13 @@ open class Linkbox : ExtractorApi() {
|
|||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val id = Regex("""(?:/f/|/file/|\?id=)(\w+)""").find(url)?.groupValues?.get(1)
|
||||
app.get("$mainUrl/api/file/detail?itemId=$id", referer = url)
|
||||
.parsedSafe<Responses>()?.data?.itemInfo?.resolutionList?.map { link ->
|
||||
val id = Regex("""(/file/|id=)(\S+)[&/?]""").find(url)?.groupValues?.get(2)
|
||||
app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe<Responses>()?.data?.rList?.map { link ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
link.url ?: return@map null,
|
||||
link.url,
|
||||
url,
|
||||
getQualityFromName(link.resolution)
|
||||
)
|
||||
|
@ -33,21 +32,17 @@ open class Linkbox : ExtractorApi() {
|
|||
}
|
||||
}
|
||||
|
||||
data class Resolutions(
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("resolution") val resolution: String? = null,
|
||||
)
|
||||
|
||||
data class ItemInfo(
|
||||
@JsonProperty("resolutionList") val resolutionList: ArrayList<Resolutions>? = arrayListOf(),
|
||||
data class RList(
|
||||
@JsonProperty("url") val url: String,
|
||||
@JsonProperty("resolution") val resolution: String?,
|
||||
)
|
||||
|
||||
data class Data(
|
||||
@JsonProperty("itemInfo") val itemInfo: ItemInfo? = null,
|
||||
@JsonProperty("rList") val rList: List<RList>?,
|
||||
)
|
||||
|
||||
data class Responses(
|
||||
@JsonProperty("data") val data: Data? = null,
|
||||
@JsonProperty("data") val data: Data?,
|
||||
)
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
|
||||
open class Sendvid : ExtractorApi() {
|
||||
override var name = "Sendvid"
|
||||
override val mainUrl = "https://sendvid.com"
|
||||
override val requiresReferer = false
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val doc = app.get(url).document
|
||||
val urlString = doc.select("head meta[property=og:video:secure_url]").attr("content")
|
||||
if (urlString.contains("m3u8")) {
|
||||
generateM3u8(
|
||||
name,
|
||||
urlString,
|
||||
mainUrl,
|
||||
).forEach(callback)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,10 +77,6 @@ class StreamSB10 : StreamSB() {
|
|||
override var mainUrl = "https://sbplay2.xyz"
|
||||
}
|
||||
|
||||
class StreamSB11 : StreamSB() {
|
||||
override var mainUrl = "https://sbbrisk.com"
|
||||
}
|
||||
|
||||
// This is a modified version of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/genoanime/src/eu/kanade/tachiyomi/animeextension/en/genoanime/extractors/StreamSBExtractor.kt
|
||||
// The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
|
||||
open class StreamSB : ExtractorApi() {
|
||||
|
@ -134,7 +130,7 @@ open class StreamSB : ExtractorApi() {
|
|||
it.value.replace(Regex("(embed-|/e/)"), "")
|
||||
}.first()
|
||||
// val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362"
|
||||
val master = "$mainUrl/sources15/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/"
|
||||
val master = "$mainUrl/sources50/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/"
|
||||
val headers = mapOf(
|
||||
"watchsb" to "sbstream",
|
||||
)
|
||||
|
|
|
@ -7,10 +7,6 @@ class Uqload1 : Uqload() {
|
|||
override var mainUrl = "https://uqload.com"
|
||||
}
|
||||
|
||||
class Uqload2 : Uqload() {
|
||||
override var mainUrl = "https://uqload.co"
|
||||
}
|
||||
|
||||
open class Uqload : ExtractorApi() {
|
||||
override val name: String = "Uqload"
|
||||
override val mainUrl: String = "https://www.uqload.com"
|
||||
|
@ -19,14 +15,30 @@ open class Uqload : ExtractorApi() {
|
|||
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
with(app.get(url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile"
|
||||
val lang = url.substring(0, 2)
|
||||
val flag =
|
||||
if (lang == "vo") {
|
||||
" \uD83C\uDDEC\uD83C\uDDE7"
|
||||
}
|
||||
else if (lang == "vf"){
|
||||
" \uD83C\uDDE8\uD83C\uDDF5"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http://
|
||||
url
|
||||
} else {
|
||||
url.substring(2, url.length)
|
||||
}
|
||||
with(app.get(cleaned_url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile"
|
||||
srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link ->
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
name + flag,
|
||||
link,
|
||||
url,
|
||||
cleaned_url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -59,8 +59,8 @@ open class VidSrcExtractor : ExtractorApi() {
|
|||
if (datahash.isNotBlank()) {
|
||||
val links = try {
|
||||
app.get(
|
||||
"$absoluteUrl/srcrcp/$datahash",
|
||||
referer = "https://rcp.vidsrc.me/"
|
||||
"$absoluteUrl/src/$datahash",
|
||||
referer = "https://source.vidsrc.me/"
|
||||
).url
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
|
@ -71,7 +71,7 @@ open class VidSrcExtractor : ExtractorApi() {
|
|||
|
||||
serverslist.amap { server ->
|
||||
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
|
||||
if (linkfixed.contains("/prorcp")) {
|
||||
if (linkfixed.contains("/pro")) {
|
||||
val srcresponse = app.get(server, referer = absoluteUrl).text
|
||||
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)")
|
||||
val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.getAndUnpack
|
||||
|
||||
class Vido : ExtractorApi() {
|
||||
override var name = "Vido"
|
||||
override var mainUrl = "https://vido.lol"
|
||||
private val srcRegex = Regex("""sources:\s*\["(.*?)"\]""")
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val methode = app.get(url.replace("/e/", "/embed-")) // fix wiflix and mesfilms
|
||||
with(methode) {
|
||||
if (!methode.isSuccessful) return null
|
||||
//val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull()
|
||||
srcRegex.find(this.text)?.groupValues?.get(1)?.let { link ->
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
link,
|
||||
url,
|
||||
Qualities.Unknown.value,
|
||||
true,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -121,21 +121,13 @@ suspend fun <T> suspendSafeApiCall(apiCall: suspend () -> T): T? {
|
|||
}
|
||||
}
|
||||
|
||||
fun Throwable.getAllMessages(): String {
|
||||
return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "")
|
||||
}
|
||||
|
||||
fun Throwable.getStackTracePretty(showMessage: Boolean = true): String {
|
||||
val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else ""
|
||||
return prefix + this.stackTrace.joinToString(
|
||||
fun <T> safeFail(throwable: Throwable): Resource<T> {
|
||||
val stackTraceMsg =
|
||||
(throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
|
||||
separator = "\n"
|
||||
) {
|
||||
"${it.fileName} ${it.lineNumber}"
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> safeFail(throwable: Throwable): Resource<T> {
|
||||
val stackTraceMsg = throwable.getStackTracePretty()
|
||||
return Resource.Failure(false, null, null, stackTraceMsg)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import com.google.gson.Gson
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.removePluginMapping
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
|
@ -145,10 +144,8 @@ object PluginManager {
|
|||
return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray()
|
||||
}
|
||||
|
||||
private val CLOUD_STREAM_FOLDER =
|
||||
Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/"
|
||||
|
||||
private val LOCAL_PLUGINS_PATH = CLOUD_STREAM_FOLDER + "plugins"
|
||||
private val LOCAL_PLUGINS_PATH =
|
||||
Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins"
|
||||
|
||||
public var currentlyLoading: String? = null
|
||||
|
||||
|
@ -166,11 +163,11 @@ object PluginManager {
|
|||
private var loadedLocalPlugins = false
|
||||
private val gson = Gson()
|
||||
|
||||
private suspend fun maybeLoadPlugin(context: Context, file: File) {
|
||||
private suspend fun maybeLoadPlugin(activity: Activity, file: File) {
|
||||
val name = file.name
|
||||
if (file.extension == "zip" || file.extension == "cs3") {
|
||||
loadPlugin(
|
||||
context,
|
||||
activity,
|
||||
file,
|
||||
PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET)
|
||||
)
|
||||
|
@ -200,7 +197,7 @@ object PluginManager {
|
|||
|
||||
// var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet()
|
||||
|
||||
suspend fun loadSinglePlugin(context: Context, apiName: String): Boolean {
|
||||
suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean {
|
||||
return (getPluginsOnline().firstOrNull {
|
||||
// Most of the time the provider ends with Provider which isn't part of the api name
|
||||
it.internalName.replace("provider", "", ignoreCase = true) == apiName
|
||||
|
@ -210,7 +207,7 @@ object PluginManager {
|
|||
})?.let { savedData ->
|
||||
// OnlinePluginData(savedData, onlineData)
|
||||
loadPlugin(
|
||||
context,
|
||||
activity,
|
||||
File(savedData.filePath),
|
||||
savedData
|
||||
)
|
||||
|
@ -372,11 +369,11 @@ object PluginManager {
|
|||
/**
|
||||
* Use updateAllOnlinePluginsAndLoadThem
|
||||
* */
|
||||
fun loadAllOnlinePlugins(context: Context) {
|
||||
fun loadAllOnlinePlugins(activity: Activity) {
|
||||
// Load all plugins as fast as possible!
|
||||
(getPluginsOnline()).toList().apmap { pluginData ->
|
||||
loadPlugin(
|
||||
context,
|
||||
activity,
|
||||
File(pluginData.filePath),
|
||||
pluginData
|
||||
)
|
||||
|
@ -399,7 +396,7 @@ object PluginManager {
|
|||
* @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins
|
||||
* and reload all pages even if they are previously valid
|
||||
**/
|
||||
fun loadAllLocalPlugins(context: Context, forceReload: Boolean) {
|
||||
fun loadAllLocalPlugins(activity: Activity, forceReload: Boolean) {
|
||||
val dir = File(LOCAL_PLUGINS_PATH)
|
||||
removeKey(PLUGINS_KEY_LOCAL)
|
||||
|
||||
|
@ -417,39 +414,24 @@ object PluginManager {
|
|||
Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins")
|
||||
|
||||
sortedPlugins?.sortedBy { it.name }?.apmap { file ->
|
||||
maybeLoadPlugin(context, file)
|
||||
maybeLoadPlugin(activity, file)
|
||||
}
|
||||
|
||||
loadedLocalPlugins = true
|
||||
afterPluginsLoadedEvent.invoke(forceReload)
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to override any extension loading to fix crashes!
|
||||
* @return true if safe mode file is present
|
||||
**/
|
||||
fun checkSafeModeFile(): Boolean {
|
||||
return normalSafeApiCall {
|
||||
val folder = File(CLOUD_STREAM_FOLDER)
|
||||
if (!folder.exists()) return@normalSafeApiCall false
|
||||
val files = folder.listFiles { _, name ->
|
||||
name.equals("safe", ignoreCase = true)
|
||||
}
|
||||
files?.any()
|
||||
} ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if successful, false if not
|
||||
* */
|
||||
private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean {
|
||||
private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean {
|
||||
val fileName = file.nameWithoutExtension
|
||||
val filePath = file.absolutePath
|
||||
currentlyLoading = fileName
|
||||
Log.i(TAG, "Loading plugin: $data")
|
||||
|
||||
return try {
|
||||
val loader = PathClassLoader(filePath, context.classLoader)
|
||||
val loader = PathClassLoader(filePath, activity.classLoader)
|
||||
var manifest: Plugin.Manifest
|
||||
loader.getResourceAsStream("manifest.json").use { stream ->
|
||||
if (stream == null) {
|
||||
|
@ -493,22 +475,22 @@ object PluginManager {
|
|||
addAssetPath.invoke(assets, file.absolutePath)
|
||||
pluginInstance.resources = Resources(
|
||||
assets,
|
||||
context.resources.displayMetrics,
|
||||
context.resources.configuration
|
||||
activity.resources.displayMetrics,
|
||||
activity.resources.configuration
|
||||
)
|
||||
}
|
||||
plugins[filePath] = pluginInstance
|
||||
classLoaders[loader] = pluginInstance
|
||||
urlPlugins[data.url ?: filePath] = pluginInstance
|
||||
pluginInstance.load(context)
|
||||
pluginInstance.load(activity)
|
||||
Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
|
||||
currentlyLoading = null
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}")
|
||||
showToast(
|
||||
context.getActivity(),
|
||||
context.getString(R.string.plugin_load_fail).format(fileName),
|
||||
activity,
|
||||
activity.getString(R.string.plugin_load_fail).format(fileName),
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
currentlyLoading = null
|
||||
|
|
|
@ -2,10 +2,8 @@ package com.lagradost.cloudstream3.plugins
|
|||
|
||||
import android.content.Context
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.amap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -73,15 +71,6 @@ object RepositoryManager {
|
|||
val PREBUILT_REPOSITORIES: Array<RepositoryData> by lazy {
|
||||
getKey("PREBUILT_REPOSITORIES") ?: emptyArray()
|
||||
}
|
||||
val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$")
|
||||
|
||||
/* Convert raw.githubusercontent.com urls to cdn.jsdelivr.net if enabled in settings */
|
||||
fun convertRawGitUrl(url: String): String {
|
||||
if (getKey<Boolean>(context!!.getString(R.string.jsdelivr_proxy_key)) != true) return url
|
||||
val match = GH_REGEX.find(url) ?: return url
|
||||
val (user, repo, rest) = match.destructured
|
||||
return "https://cdn.jsdelivr.net/gh/$user/$repo@$rest"
|
||||
}
|
||||
|
||||
suspend fun parseRepoUrl(url: String): String? {
|
||||
val fixedUrl = url.trim()
|
||||
|
@ -95,15 +84,10 @@ object RepositoryManager {
|
|||
}
|
||||
} else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) {
|
||||
suspendSafeApiCall {
|
||||
app.get("https://l.cloudstream.cf/${fixedUrl}", allowRedirects = false).let {
|
||||
it.headers["Location"]?.let { url ->
|
||||
return@suspendSafeApiCall if (!url.startsWith("https://cutt.ly/branded-domains")) url
|
||||
else null
|
||||
}
|
||||
app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 ->
|
||||
it2.headers["Location"]?.let { url ->
|
||||
return@suspendSafeApiCall if (url.startsWith("https://cutt.ly/404")) url else null
|
||||
}
|
||||
app.get("https://l.cloudstream.cf/${fixedUrl}").let {
|
||||
return@let if (it.isSuccessful && !it.url.startsWith("https://cutt.ly/branded-domains")) it.url
|
||||
else app.get("https://cutt.ly/${fixedUrl}").let let2@{ it2 ->
|
||||
return@let2 if (it2.isSuccessful) it2.url else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,14 +97,14 @@ object RepositoryManager {
|
|||
suspend fun parseRepository(url: String): Repository? {
|
||||
return suspendSafeApiCall {
|
||||
// Take manifestVersion and such into account later
|
||||
app.get(convertRawGitUrl(url)).parsedSafe()
|
||||
app.get(url).parsedSafe()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parsePlugins(pluginUrls: String): List<SitePlugin> {
|
||||
// Take manifestVersion and such into account later
|
||||
return try {
|
||||
val response = app.get(convertRawGitUrl(pluginUrls))
|
||||
val response = app.get(pluginUrls)
|
||||
// Normal parsed function not working?
|
||||
// return response.parsedSafe()
|
||||
tryParseJson<Array<SitePlugin>>(response.text)?.toList() ?: emptyList()
|
||||
|
@ -155,7 +139,7 @@ object RepositoryManager {
|
|||
}
|
||||
file.createNewFile()
|
||||
|
||||
val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body
|
||||
val body = app.get(pluginUrl).okhttpResponse.body
|
||||
write(body.byteStream(), file.outputStream())
|
||||
file
|
||||
}
|
||||
|
|
|
@ -1,224 +0,0 @@
|
|||
package com.lagradost.cloudstream3.services
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.work.*
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getImageBitmapFromUrl
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
const val SUBSCRIPTION_CHANNEL_ID = "cloudstream3.subscriptions"
|
||||
const val SUBSCRIPTION_WORK_NAME = "work_subscription"
|
||||
const val SUBSCRIPTION_CHANNEL_NAME = "Subscriptions"
|
||||
const val SUBSCRIPTION_CHANNEL_DESCRIPTION = "Notifications for new episodes on subscribed shows"
|
||||
const val SUBSCRIPTION_NOTIFICATION_ID = 938712897 // Random unique
|
||||
|
||||
class SubscriptionWorkManager(val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
companion object {
|
||||
fun enqueuePeriodicWork(context: Context?) {
|
||||
if (context == null) return
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val periodicSyncDataWork =
|
||||
PeriodicWorkRequest.Builder(SubscriptionWorkManager::class.java, 6, TimeUnit.HOURS)
|
||||
.addTag(SUBSCRIPTION_WORK_NAME)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||
SUBSCRIPTION_WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
periodicSyncDataWork
|
||||
)
|
||||
|
||||
// Uncomment below for testing
|
||||
|
||||
// val oneTimeSyncDataWork =
|
||||
// OneTimeWorkRequest.Builder(SubscriptionWorkManager::class.java)
|
||||
// .addTag(SUBSCRIPTION_WORK_NAME)
|
||||
// .setConstraints(constraints)
|
||||
// .build()
|
||||
//
|
||||
// WorkManager.getInstance(context).enqueue(oneTimeSyncDataWork)
|
||||
}
|
||||
}
|
||||
|
||||
private val progressNotificationBuilder =
|
||||
NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID)
|
||||
.setAutoCancel(false)
|
||||
.setColorized(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSilent(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
||||
.setContentTitle(context.getString(R.string.subscription_in_progress_notification))
|
||||
.setSmallIcon(R.drawable.quantum_ic_refresh_white_24)
|
||||
.setProgress(0, 0, true)
|
||||
|
||||
private val updateNotificationBuilder =
|
||||
NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID)
|
||||
.setColorized(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
||||
.setSmallIcon(R.drawable.ic_cloudstream_monochrome_big)
|
||||
|
||||
private val notificationManager: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
private fun updateProgress(max: Int, progress: Int, indeterminate: Boolean) {
|
||||
notificationManager.notify(
|
||||
SUBSCRIPTION_NOTIFICATION_ID, progressNotificationBuilder
|
||||
.setProgress(max, progress, indeterminate)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
// println("Update subscriptions!")
|
||||
context.createNotificationChannel(
|
||||
SUBSCRIPTION_CHANNEL_ID,
|
||||
SUBSCRIPTION_CHANNEL_NAME,
|
||||
SUBSCRIPTION_CHANNEL_DESCRIPTION
|
||||
)
|
||||
|
||||
setForeground(
|
||||
ForegroundInfo(
|
||||
SUBSCRIPTION_NOTIFICATION_ID,
|
||||
progressNotificationBuilder.build()
|
||||
)
|
||||
)
|
||||
|
||||
val subscriptions = getAllSubscriptions()
|
||||
|
||||
if (subscriptions.isEmpty()) {
|
||||
WorkManager.getInstance(context).cancelWorkById(this.id)
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val max = subscriptions.size
|
||||
var progress = 0
|
||||
|
||||
updateProgress(max, progress, true)
|
||||
|
||||
// We need all plugins loaded.
|
||||
PluginManager.loadAllOnlinePlugins(context)
|
||||
PluginManager.loadAllLocalPlugins(context, false)
|
||||
|
||||
subscriptions.apmap { savedData ->
|
||||
try {
|
||||
val id = savedData.id ?: return@apmap null
|
||||
val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null
|
||||
|
||||
// Reasonable timeout to prevent having this worker run forever.
|
||||
val response = withTimeoutOrNull(60_000) {
|
||||
api.load(savedData.url) as? EpisodeResponse
|
||||
} ?: return@apmap null
|
||||
|
||||
val dubPreference =
|
||||
getDub(id) ?: if (
|
||||
context.getApiDubstatusSettings().contains(DubStatus.Dubbed)
|
||||
) {
|
||||
DubStatus.Dubbed
|
||||
} else {
|
||||
DubStatus.Subbed
|
||||
}
|
||||
|
||||
val latestEpisodes = response.getLatestEpisodes()
|
||||
val latestPreferredEpisode = latestEpisodes[dubPreference]
|
||||
|
||||
val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) {
|
||||
val latestSeenEpisode =
|
||||
savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE
|
||||
val shouldUpdate = latestPreferredEpisode > latestSeenEpisode
|
||||
shouldUpdate to latestPreferredEpisode
|
||||
} else {
|
||||
val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE
|
||||
val latestSeenEpisode =
|
||||
savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE
|
||||
val shouldUpdate = latestEpisode > latestSeenEpisode
|
||||
shouldUpdate to latestEpisode
|
||||
}
|
||||
|
||||
DataStoreHelper.updateSubscribedData(
|
||||
id,
|
||||
savedData,
|
||||
response
|
||||
)
|
||||
|
||||
if (shouldUpdate) {
|
||||
val updateHeader = savedData.name
|
||||
val updateDescription = txt(
|
||||
R.string.subscription_episode_released,
|
||||
latestEpisode,
|
||||
savedData.name
|
||||
).asString(context)
|
||||
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
data = savedData.url.toUri()
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
|
||||
val pendingIntent =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
} else {
|
||||
PendingIntent.getActivity(context, 0, intent, 0)
|
||||
}
|
||||
|
||||
val poster = ioWork {
|
||||
savedData.posterUrl?.let { url ->
|
||||
context.getImageBitmapFromUrl(
|
||||
url,
|
||||
savedData.posterHeaders
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val updateNotification =
|
||||
updateNotificationBuilder.setContentTitle(updateHeader)
|
||||
.setContentText(updateDescription)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setLargeIcon(poster)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(id, updateNotification)
|
||||
}
|
||||
|
||||
// You can probably get some issues here since this is async but it does not matter much.
|
||||
updateProgress(max, ++progress, false)
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
}
|
|
@ -1,22 +1,11 @@
|
|||
package com.lagradost.cloudstream3.services
|
||||
import android.app.Service
|
||||
|
||||
import android.app.IntentService
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class VideoDownloadService : Service() {
|
||||
|
||||
private val downloadScope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
class VideoDownloadService : IntentService("VideoDownloadService") {
|
||||
override fun onHandleIntent(intent: Intent?) {
|
||||
if (intent != null) {
|
||||
val id = intent.getIntExtra("id", -1)
|
||||
val type = intent.getStringExtra("type")
|
||||
|
@ -25,36 +14,10 @@ class VideoDownloadService : Service() {
|
|||
"resume" -> VideoDownloadManager.DownloadActionType.Resume
|
||||
"pause" -> VideoDownloadManager.DownloadActionType.Pause
|
||||
"stop" -> VideoDownloadManager.DownloadActionType.Stop
|
||||
else -> return START_NOT_STICKY
|
||||
else -> return
|
||||
}
|
||||
|
||||
downloadScope.launch {
|
||||
VideoDownloadManager.downloadEvent.invoke(Pair(id, state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
downloadScope.coroutineContext.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
// override fun onHandleIntent(intent: Intent?) {
|
||||
// if (intent != null) {
|
||||
// val id = intent.getIntExtra("id", -1)
|
||||
// val type = intent.getStringExtra("type")
|
||||
// if (id != -1 && type != null) {
|
||||
// val state = when (type) {
|
||||
// "resume" -> VideoDownloadManager.DownloadActionType.Resume
|
||||
// "pause" -> VideoDownloadManager.DownloadActionType.Pause
|
||||
// "stop" -> VideoDownloadManager.DownloadActionType.Stop
|
||||
// else -> return
|
||||
// }
|
||||
// VideoDownloadManager.downloadEvent.invoke(Pair(id, state))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -45,7 +45,6 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
|
||||
const val appString = "cloudstreamapp"
|
||||
const val appStringRepo = "cloudstreamrepo"
|
||||
const val appStringPlayer = "cloudstreamplayer"
|
||||
|
||||
// Instantly start the search given a query
|
||||
const val appStringSearch = "cloudstreamsearch"
|
||||
|
|
|
@ -759,11 +759,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return data != ""
|
||||
}
|
||||
|
||||
/** Used to query a saved MediaItem on the list to get the id for removal */
|
||||
data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null)
|
||||
data class MediaListItem(@JsonProperty("MediaList") val MediaList: MediaListId? = null)
|
||||
data class MediaListId(@JsonProperty("id") val id: Long? = null)
|
||||
|
||||
private suspend fun postDataAboutId(
|
||||
id: Int,
|
||||
type: AniListStatusType,
|
||||
|
@ -771,28 +766,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
progress: Int?
|
||||
): Boolean {
|
||||
val q =
|
||||
// Delete item if status type is None
|
||||
if (type == AniListStatusType.None) {
|
||||
val userID = getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.id ?: return false
|
||||
// Get list ID for deletion
|
||||
val idQuery = """
|
||||
query MediaList(${'$'}userId: Int = $userID, ${'$'}mediaId: Int = $id) {
|
||||
MediaList(userId: ${'$'}userId, mediaId: ${'$'}mediaId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
"""
|
||||
val response = postApi(idQuery)
|
||||
val listId =
|
||||
tryParseJson<MediaListItemRoot>(response)?.data?.MediaList?.id ?: return false
|
||||
"""
|
||||
mutation(${'$'}id: Int = $listId) {
|
||||
DeleteMediaListEntry(id: ${'$'}id) {
|
||||
deleted
|
||||
}
|
||||
}
|
||||
"""
|
||||
} else {
|
||||
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
|
||||
aniListStatusString[maxOf(
|
||||
0,
|
||||
|
@ -806,8 +779,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
score
|
||||
}
|
||||
}"""
|
||||
}
|
||||
|
||||
val data = postApi(q)
|
||||
return data != ""
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import com.lagradost.cloudstream3.ui.WatchType
|
|||
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||
|
@ -75,16 +74,13 @@ class LocalList : SyncAPI {
|
|||
group.value.mapNotNull {
|
||||
getBookmarkedData(it.first)?.toLibraryItem(it.first.toString())
|
||||
}
|
||||
} + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull {
|
||||
it.toLibraryItem()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
|
||||
// None is not something to display
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
} + mapOf(R.string.subscription_list_name to emptyList())
|
||||
|
||||
}
|
||||
return SyncAPI.LibraryMetadata(
|
||||
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
|
||||
setOf(
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.mvvm.observe
|
|||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
||||
|
@ -39,7 +40,6 @@ import kotlinx.android.synthetic.main.stream_input.*
|
|||
import android.text.format.Formatter.formatShortFileSize
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.ui.player.BasicLink
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import java.net.URI
|
||||
|
||||
|
@ -225,7 +225,7 @@ class DownloadFragment : Fragment() {
|
|||
R.id.global_to_navigation_player,
|
||||
GeneratorPlayer.newInstance(
|
||||
LinkGenerator(
|
||||
listOf(BasicLink(url)),
|
||||
listOf(url),
|
||||
extract = true,
|
||||
referer = referer,
|
||||
isM3u8 = dialog.hls_switch?.isChecked
|
||||
|
|
|
@ -185,7 +185,7 @@ open class ParentItemAdapter(
|
|||
) :
|
||||
RecyclerView.ViewHolder(itemView) {
|
||||
val title: TextView = itemView.home_child_more_info
|
||||
private val recyclerView: RecyclerView = itemView.home_child_recyclerview
|
||||
val recyclerView: RecyclerView = itemView.home_child_recyclerview
|
||||
|
||||
fun update(expand: HomeViewModel.ExpandableHomepageList) {
|
||||
val info = expand.list
|
||||
|
|
|
@ -333,10 +333,8 @@ class LibraryFragment : Fragment() {
|
|||
handler.postDelayed(stopLoading, 300)
|
||||
|
||||
savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos ->
|
||||
if (currentPos < 0) return@let
|
||||
viewpager?.setCurrentItem(currentPos, false)
|
||||
// Using remove() sets the key to 0 instead of removing it
|
||||
savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1)
|
||||
savedInstanceState.remove(VIEWPAGER_ITEM_KEY)
|
||||
}
|
||||
|
||||
// Since the animation to scroll multiple items is so much its better to just hide
|
||||
|
|
|
@ -9,11 +9,8 @@ import android.widget.FrameLayout
|
|||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.C.*
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
|
||||
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
|
||||
import com.google.android.exoplayer2.source.*
|
||||
import com.google.android.exoplayer2.text.TextRenderer
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||
|
@ -38,13 +35,11 @@ import com.lagradost.cloudstream3.mvvm.logError
|
|||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||
import java.io.File
|
||||
import java.time.Duration
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSession
|
||||
|
@ -540,17 +535,15 @@ class CS3IPlayer : IPlayer {
|
|||
OkHttpDataSource.Factory(client).setUserAgent(USER_AGENT)
|
||||
}
|
||||
|
||||
// Do no include empty referer, if the provider wants those they can use the header map.
|
||||
val refererMap =
|
||||
if (link.referer.isBlank()) emptyMap() else mapOf("referer" to link.referer)
|
||||
val headers = mapOf(
|
||||
"referer" to link.referer,
|
||||
"accept" to "*/*",
|
||||
"sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"",
|
||||
"sec-ch-ua-mobile" to "?0",
|
||||
"sec-fetch-user" to "?1",
|
||||
"sec-fetch-mode" to "navigate",
|
||||
"sec-fetch-dest" to "video"
|
||||
) + refererMap + link.headers // Adds the headers from the provider, e.g Authorization
|
||||
) + link.headers // Adds the headers from the provider, e.g Authorization
|
||||
|
||||
return source.apply {
|
||||
setDefaultRequestProperties(headers)
|
||||
|
@ -673,11 +666,7 @@ class CS3IPlayer : IPlayer {
|
|||
val exoPlayerBuilder =
|
||||
ExoPlayer.Builder(context)
|
||||
.setRenderersFactory { eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput ->
|
||||
DefaultRenderersFactory(context).apply {
|
||||
// setEnableDecoderFallback(true)
|
||||
// Enable Ffmpeg extension
|
||||
// setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON)
|
||||
}.createRenderers(
|
||||
DefaultRenderersFactory(context).createRenderers(
|
||||
eventHandler,
|
||||
videoRendererEventListener,
|
||||
audioRendererEventListener,
|
||||
|
@ -858,7 +847,7 @@ class CS3IPlayer : IPlayer {
|
|||
Log.i(TAG, "loadExo")
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val maxVideoHeight = settingsManager.getInt(
|
||||
context.getString(if (context.isUsingMobileData()) com.lagradost.cloudstream3.R.string.quality_pref_mobile_data_key else com.lagradost.cloudstream3.R.string.quality_pref_key),
|
||||
context.getString(com.lagradost.cloudstream3.R.string.quality_pref_key),
|
||||
Int.MAX_VALUE
|
||||
)
|
||||
|
||||
|
@ -1204,10 +1193,10 @@ class CS3IPlayer : IPlayer {
|
|||
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
|
||||
}
|
||||
|
||||
val mime = when {
|
||||
link.isM3u8 -> MimeTypes.APPLICATION_M3U8
|
||||
link.isDash -> MimeTypes.APPLICATION_MPD
|
||||
else -> MimeTypes.VIDEO_MP4
|
||||
val mime = if (link.isM3u8) {
|
||||
MimeTypes.APPLICATION_M3U8
|
||||
} else {
|
||||
MimeTypes.VIDEO_MP4
|
||||
}
|
||||
|
||||
val mediaItems = if (link is ExtractorLinkPlayList) {
|
||||
|
|
|
@ -4,16 +4,13 @@ import android.content.Context
|
|||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.exoplayer2.Format
|
||||
import com.google.android.exoplayer2.text.*
|
||||
import com.google.android.exoplayer2.text.cea.Cea608Decoder
|
||||
import com.google.android.exoplayer2.text.cea.Cea708Decoder
|
||||
import com.google.android.exoplayer2.text.dvb.DvbDecoder
|
||||
import com.google.android.exoplayer2.text.pgs.PgsDecoder
|
||||
import com.google.android.exoplayer2.text.SubtitleDecoder
|
||||
import com.google.android.exoplayer2.text.SubtitleDecoderFactory
|
||||
import com.google.android.exoplayer2.text.SubtitleInputBuffer
|
||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer
|
||||
import com.google.android.exoplayer2.text.ssa.SsaDecoder
|
||||
import com.google.android.exoplayer2.text.subrip.SubripDecoder
|
||||
import com.google.android.exoplayer2.text.ttml.TtmlDecoder
|
||||
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder
|
||||
import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder
|
||||
import com.google.android.exoplayer2.text.webvtt.WebvttDecoder
|
||||
import com.google.android.exoplayer2.util.MimeTypes
|
||||
import com.lagradost.cloudstream3.R
|
||||
|
@ -22,11 +19,7 @@ import org.mozilla.universalchardet.UniversalDetector
|
|||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* @param fallbackFormat used to create a decoder based on mimetype if the subtitle string is not
|
||||
* enough to identify the subtitle format.
|
||||
**/
|
||||
class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
|
||||
class CustomDecoder : SubtitleDecoder {
|
||||
companion object {
|
||||
fun updateForcedEncoding(context: Context) {
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
@ -146,7 +139,7 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
|
|||
val inputString = getStr(inputBuffer)
|
||||
if (realDecoder == null && !inputString.isNullOrBlank()) {
|
||||
var str: String = inputString
|
||||
// this way we read the subtitle file and decide what decoder to use instead of relying fully on mimetype
|
||||
// this way we read the subtitle file and decide what decoder to use instead of relying on mimetype
|
||||
Log.i(TAG, "Got data from queueInputBuffer")
|
||||
//https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388
|
||||
realDecoder = when {
|
||||
|
@ -155,31 +148,8 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
|
|||
(str.startsWith(
|
||||
"[Script Info]",
|
||||
ignoreCase = true
|
||||
) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder(fallbackFormat?.initializationData)
|
||||
) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder()
|
||||
str.startsWith("1", ignoreCase = true) -> SubripDecoder()
|
||||
fallbackFormat != null -> {
|
||||
when (val mimeType = fallbackFormat.sampleMimeType) {
|
||||
MimeTypes.TEXT_VTT -> WebvttDecoder()
|
||||
MimeTypes.TEXT_SSA -> SsaDecoder(fallbackFormat.initializationData)
|
||||
MimeTypes.APPLICATION_MP4VTT -> Mp4WebvttDecoder()
|
||||
MimeTypes.APPLICATION_TTML -> TtmlDecoder()
|
||||
MimeTypes.APPLICATION_SUBRIP -> SubripDecoder()
|
||||
MimeTypes.APPLICATION_TX3G -> Tx3gDecoder(fallbackFormat.initializationData)
|
||||
MimeTypes.APPLICATION_CEA608, MimeTypes.APPLICATION_MP4CEA608 -> Cea608Decoder(
|
||||
mimeType,
|
||||
fallbackFormat.accessibilityChannel,
|
||||
Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS
|
||||
)
|
||||
MimeTypes.APPLICATION_CEA708 -> Cea708Decoder(
|
||||
fallbackFormat.accessibilityChannel,
|
||||
fallbackFormat.initializationData
|
||||
)
|
||||
MimeTypes.APPLICATION_DVBSUBS -> DvbDecoder(fallbackFormat.initializationData)
|
||||
MimeTypes.APPLICATION_PGS -> PgsDecoder()
|
||||
MimeTypes.TEXT_EXOPLAYER_CUES -> ExoplayerCuesDecoder()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
Log.i(
|
||||
|
@ -276,6 +246,28 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory {
|
|||
}
|
||||
|
||||
override fun createDecoder(format: Format): SubtitleDecoder {
|
||||
return CustomDecoder(format)
|
||||
return CustomDecoder()
|
||||
//return when (val mimeType = format.sampleMimeType) {
|
||||
// MimeTypes.TEXT_VTT -> WebvttDecoder()
|
||||
// MimeTypes.TEXT_SSA -> SsaDecoder(format.initializationData)
|
||||
// MimeTypes.APPLICATION_MP4VTT -> Mp4WebvttDecoder()
|
||||
// MimeTypes.APPLICATION_TTML -> TtmlDecoder()
|
||||
// MimeTypes.APPLICATION_SUBRIP -> SubripDecoder()
|
||||
// MimeTypes.APPLICATION_TX3G -> Tx3gDecoder(format.initializationData)
|
||||
// MimeTypes.APPLICATION_CEA608, MimeTypes.APPLICATION_MP4CEA608 -> return Cea608Decoder(
|
||||
// mimeType,
|
||||
// format.accessibilityChannel,
|
||||
// Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS
|
||||
// )
|
||||
// MimeTypes.APPLICATION_CEA708 -> Cea708Decoder(
|
||||
// format.accessibilityChannel,
|
||||
// format.initializationData
|
||||
// )
|
||||
// MimeTypes.APPLICATION_DVBSUBS -> DvbDecoder(format.initializationData)
|
||||
// MimeTypes.APPLICATION_PGS -> PgsDecoder()
|
||||
// MimeTypes.TEXT_EXOPLAYER_CUES -> ExoplayerCuesDecoder()
|
||||
// // Default WebVttDecoder
|
||||
// else -> WebvttDecoder()
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ class DownloadedPlayerActivity : AppCompatActivity() {
|
|||
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
|
||||
LinkGenerator(
|
||||
listOf(
|
||||
BasicLink(url)
|
||||
url
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
|
@ -84,7 +83,6 @@ const val HORIZONTAL_MULTIPLIER = 2.0f
|
|||
const val DOUBLE_TAB_MAXIMUM_HOLD_TIME = 200L
|
||||
const val DOUBLE_TAB_MINIMUM_TIME_BETWEEN = 200L // this also affects the UI show response time
|
||||
const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions
|
||||
private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay"
|
||||
|
||||
// All the UI Logic for the player
|
||||
open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||
|
@ -111,8 +109,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
protected var currentPrefQuality =
|
||||
Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
|
||||
protected var fastForwardTime = 10000L
|
||||
protected var androidTVInterfaceOffSeekTime = 10000L;
|
||||
protected var androidTVInterfaceOnSeekTime = 30000L;
|
||||
protected var swipeHorizontalEnabled = false
|
||||
protected var swipeVerticalEnabled = false
|
||||
protected var playBackSpeedEnabled = false
|
||||
|
@ -1055,19 +1051,19 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
}
|
||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||
if (!isShowing && !isLocked) {
|
||||
player.seekTime(-androidTVInterfaceOffSeekTime)
|
||||
player.seekTime(-10000L)
|
||||
return true
|
||||
} else if (player_pause_play?.isFocused == true) {
|
||||
player.seekTime(-androidTVInterfaceOnSeekTime)
|
||||
player.seekTime(-30000L)
|
||||
return true
|
||||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||
if (!isShowing && !isLocked) {
|
||||
player.seekTime(androidTVInterfaceOffSeekTime)
|
||||
player.seekTime(10000L)
|
||||
return true
|
||||
} else if (player_pause_play?.isFocused == true) {
|
||||
player.seekTime(androidTVInterfaceOnSeekTime)
|
||||
player.seekTime(30000L)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1121,20 +1117,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
resetRewindText()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
// As this is video specific it is better to not do any setKey/getKey
|
||||
outState.putLong(SUBTITLE_DELAY_BUNDLE_KEY, subtitleDelay)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
// init variables
|
||||
setPlayBackSpeed(getKey(PLAYBACK_SPEED_KEY) ?: 1.0f)
|
||||
savedInstanceState?.getLong(SUBTITLE_DELAY_BUNDLE_KEY)?.let {
|
||||
subtitleDelay = it
|
||||
}
|
||||
|
||||
// handle tv controls
|
||||
playerEventListener = { eventType ->
|
||||
|
@ -1220,13 +1207,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
settingsManager.getInt(ctx.getString(R.string.double_tap_seek_time_key), 10)
|
||||
.toLong() * 1000L
|
||||
|
||||
androidTVInterfaceOffSeekTime =
|
||||
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_off_seek_key), 10)
|
||||
.toLong() * 1000L
|
||||
androidTVInterfaceOnSeekTime =
|
||||
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_on_seek_key), 10)
|
||||
.toLong() * 1000L
|
||||
|
||||
navigationBarHeight = ctx.getNavigationBarHeight()
|
||||
statusBarHeight = ctx.getStatusBarHeight()
|
||||
|
||||
|
@ -1257,8 +1237,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
ctx.getString(R.string.double_tap_pause_enabled_key),
|
||||
false
|
||||
)
|
||||
|
||||
currentPrefQuality = settingsManager.getInt(
|
||||
ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
|
||||
ctx.getString(R.string.quality_pref_key),
|
||||
currentPrefQuality
|
||||
)
|
||||
// useSystemBrightness =
|
||||
|
|
|
@ -11,8 +11,11 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.*
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isGone
|
||||
|
@ -525,7 +528,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
var selectSourceDialog: Dialog? = null
|
||||
var selectSourceDialog: AlertDialog? = null
|
||||
// var selectTracksDialog: AlertDialog? = null
|
||||
|
||||
override fun showMirrorsDialogue() {
|
||||
|
@ -537,8 +540,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
player.handleEvent(CSPlayerEvent.Pause)
|
||||
val currentSubtitles = sortSubs(currentSubs)
|
||||
|
||||
val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
|
||||
sourceDialog.setContentView(R.layout.player_select_source_and_subs)
|
||||
val sourceBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack)
|
||||
.setView(R.layout.player_select_source_and_subs)
|
||||
|
||||
val sourceDialog = sourceBuilder.create()
|
||||
|
||||
selectSourceDialog = sourceDialog
|
||||
|
||||
|
@ -733,17 +738,19 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
val currentAudioTracks = tracks.allAudioTracks
|
||||
|
||||
val trackDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
|
||||
trackDialog.setContentView(R.layout.player_select_tracks)
|
||||
trackDialog.show()
|
||||
val trackBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack)
|
||||
.setView(R.layout.player_select_tracks)
|
||||
|
||||
val tracksDialog = trackBuilder.create()
|
||||
|
||||
// selectTracksDialog = tracksDialog
|
||||
|
||||
val videosList = trackDialog.video_tracks_list
|
||||
val audioList = trackDialog.auto_tracks_list
|
||||
tracksDialog.show()
|
||||
val videosList = tracksDialog.video_tracks_list
|
||||
val audioList = tracksDialog.auto_tracks_list
|
||||
|
||||
trackDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1
|
||||
trackDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1
|
||||
tracksDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1
|
||||
tracksDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1
|
||||
|
||||
fun dismiss() {
|
||||
if (isPlaying) {
|
||||
|
@ -778,7 +785,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
videosList.setItemChecked(which, true)
|
||||
}
|
||||
|
||||
trackDialog.setOnDismissListener {
|
||||
tracksDialog.setOnDismissListener {
|
||||
dismiss()
|
||||
// selectTracksDialog = null
|
||||
}
|
||||
|
@ -808,11 +815,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
audioList.setItemChecked(which, true)
|
||||
}
|
||||
|
||||
trackDialog.cancel_btt?.setOnClickListener {
|
||||
trackDialog.dismissSafe(activity)
|
||||
tracksDialog.cancel_btt?.setOnClickListener {
|
||||
tracksDialog.dismissSafe(activity)
|
||||
}
|
||||
|
||||
trackDialog.apply_btt?.setOnClickListener {
|
||||
tracksDialog.apply_btt?.setOnClickListener {
|
||||
val currentTrack = currentAudioTracks.getOrNull(audioIndexStart)
|
||||
player.setPreferredAudioTrack(
|
||||
currentTrack?.language, currentTrack?.id
|
||||
|
@ -825,7 +832,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
player.setMaxVideoSize(width, height, currentVideo?.id)
|
||||
}
|
||||
|
||||
trackDialog.dismissSafe(activity)
|
||||
tracksDialog.dismissSafe(activity)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -5,15 +5,8 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|||
import com.lagradost.cloudstream3.utils.*
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Used to open the player more easily with the LinkGenerator
|
||||
**/
|
||||
data class BasicLink(
|
||||
val url: String,
|
||||
val name: String? = null,
|
||||
)
|
||||
class LinkGenerator(
|
||||
private val links: List<BasicLink>,
|
||||
private val links: List<String>,
|
||||
private val extract: Boolean = true,
|
||||
private val referer: String? = null,
|
||||
private val isM3u8: Boolean? = null
|
||||
|
@ -54,7 +47,7 @@ class LinkGenerator(
|
|||
offset: Int
|
||||
): Boolean {
|
||||
links.amap { link ->
|
||||
if (!extract || !loadExtractor(link.url, referer, {
|
||||
if (!extract || !loadExtractor(link, referer, {
|
||||
subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it))
|
||||
}) {
|
||||
callback(it to null)
|
||||
|
@ -64,11 +57,11 @@ class LinkGenerator(
|
|||
callback(
|
||||
ExtractorLink(
|
||||
"",
|
||||
link.name ?: link.url,
|
||||
unshortenLinkSafe(link.url), // unshorten because it might be a raw link
|
||||
link,
|
||||
unshortenLinkSafe(link), // unshorten because it might be a raw link
|
||||
referer ?: "",
|
||||
Qualities.Unknown.value, isM3u8 ?: normalSafeApiCall {
|
||||
URI(link.url).path?.substringAfterLast(".")?.contains("m3u")
|
||||
URI(link).path?.substringAfterLast(".")?.contains("m3u")
|
||||
} ?: false
|
||||
) to null
|
||||
)
|
||||
|
|
|
@ -7,13 +7,13 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
||||
fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) {
|
||||
if (this == null) return
|
||||
if(this == null) return
|
||||
this.layoutManager =
|
||||
this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } }
|
||||
?: this.layoutManager
|
||||
}
|
||||
|
||||
open class LinearListLayout(context: Context?) :
|
||||
class LinearListLayout(context: Context?) :
|
||||
LinearLayoutManager(context) {
|
||||
|
||||
fun setHorizontal() {
|
||||
|
@ -24,8 +24,7 @@ open class LinearListLayout(context: Context?) :
|
|||
orientation = VERTICAL
|
||||
}
|
||||
|
||||
private fun getCorrectParent(focused: View?): View? {
|
||||
if (focused == null) return null
|
||||
private fun getCorrectParent(focused: View): View? {
|
||||
var current: View? = focused
|
||||
val last: ArrayList<View> = arrayListOf(focused)
|
||||
while (current != null && current !is RecyclerView) {
|
||||
|
@ -55,17 +54,10 @@ open class LinearListLayout(context: Context?) :
|
|||
linearSmoothScroller.targetPosition = position
|
||||
startSmoothScroll(linearSmoothScroller)
|
||||
}*/
|
||||
|
||||
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
|
||||
val dir = if (orientation == HORIZONTAL) {
|
||||
if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) {
|
||||
// This scrolls the recyclerview before doing focus search, which
|
||||
// allows the focus search to work better.
|
||||
|
||||
// Without this the recyclerview focus location on the screen
|
||||
// would change when scrolling between recyclerviews.
|
||||
(focused.parent as? RecyclerView)?.focusSearch(direction)
|
||||
return null
|
||||
}
|
||||
if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) return null
|
||||
if (direction == View.FOCUS_RIGHT) 1 else -1
|
||||
} else {
|
||||
if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null
|
||||
|
|
|
@ -15,28 +15,24 @@ import android.view.ViewGroup
|
|||
import android.widget.AbsListView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.discord.panels.OverlappingPanelsLayout
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipDrawable
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||
|
@ -532,25 +528,6 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
result_cast_items?.layoutManager = object : LinearListLayout(view.context) {
|
||||
override fun onRequestChildFocus(
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State,
|
||||
child: View,
|
||||
focused: View?
|
||||
): Boolean {
|
||||
// Make the cast always focus the first visible item when focused
|
||||
// from somewhere else. Otherwise it jumps to the last item.
|
||||
return if (parent.focusedChild == null) {
|
||||
scrollToPosition(this.findFirstCompletelyVisibleItemPosition())
|
||||
true
|
||||
} else {
|
||||
super.onRequestChildFocus(parent, state, child, focused)
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
this.orientation = RecyclerView.HORIZONTAL
|
||||
}
|
||||
result_cast_items?.adapter = ActorAdaptor()
|
||||
|
||||
updateUIListener = ::updateUI
|
||||
|
@ -873,7 +850,7 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
observe(viewModel.page) { data ->
|
||||
if (data == null) return@observe
|
||||
if(data == null) return@observe
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
val d = data.value
|
||||
|
@ -927,36 +904,6 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
updateList(d.actors ?: emptyList())
|
||||
}
|
||||
|
||||
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
|
||||
result_subscribe?.isVisible = isSubscribed != null
|
||||
if (isSubscribed == null) return@observeNullable
|
||||
|
||||
val drawable = if (isSubscribed) {
|
||||
R.drawable.ic_baseline_notifications_active_24
|
||||
} else {
|
||||
R.drawable.baseline_notifications_none_24
|
||||
}
|
||||
|
||||
result_subscribe?.setImageResource(drawable)
|
||||
}
|
||||
|
||||
result_subscribe?.setOnClickListener {
|
||||
val isSubscribed =
|
||||
viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
|
||||
|
||||
val message = if (isSubscribed) {
|
||||
// Kinda icky to have this here, but it works.
|
||||
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
||||
R.string.subscription_new
|
||||
} else {
|
||||
R.string.subscription_deleted
|
||||
}
|
||||
|
||||
val name = (viewModel.page.value as? Resource.Success)?.value?.title
|
||||
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
||||
showToast(activity, txt(message, name), Toast.LENGTH_SHORT)
|
||||
}
|
||||
|
||||
result_open_in_browser?.isVisible = d.url.startsWith("http")
|
||||
result_open_in_browser?.setOnClickListener {
|
||||
val i = Intent(ACTION_VIEW)
|
||||
|
@ -1027,7 +974,6 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
chip.isCheckable = false
|
||||
chip.isFocusable = false
|
||||
chip.isClickable = false
|
||||
chip.setTextColor(context.colorFromAttribute(R.attr.textColor))
|
||||
addView(chip)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -322,7 +322,9 @@ class ResultFragmentPhone : ResultFragment() {
|
|||
// it?.dismiss()
|
||||
//}
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
|
||||
builder.show()
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,7 +176,8 @@ class ResultFragmentTv : ResultFragment() {
|
|||
loadingDialog = null
|
||||
}
|
||||
loadingDialog = loadingDialog ?: context?.let { ctx ->
|
||||
val builder = BottomSheetDialog(ctx)
|
||||
val builder =
|
||||
BottomSheetDialog(ctx)
|
||||
builder.setContentView(R.layout.bottom_loading)
|
||||
builder.setOnDismissListener {
|
||||
loadingDialog = null
|
||||
|
@ -186,7 +187,9 @@ class ResultFragmentTv : ResultFragment() {
|
|||
// it?.dismiss()
|
||||
//}
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
|
||||
builder.show()
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
|
|||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
|
@ -17,7 +16,6 @@ import com.lagradost.cloudstream3.*
|
|||
import com.lagradost.cloudstream3.APIHolder.apis
|
||||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
|
@ -27,7 +25,6 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
|
|||
import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
|
||||
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
|
@ -416,9 +413,6 @@ class ResultViewModel2 : ViewModel() {
|
|||
private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null)
|
||||
val episodeSynopsis: LiveData<String?> = _episodeSynopsis
|
||||
|
||||
private val _subscribeStatus: MutableLiveData<Boolean?> = MutableLiveData(null)
|
||||
val subscribeStatus: LiveData<Boolean?> = _subscribeStatus
|
||||
|
||||
companion object {
|
||||
const val TAG = "RVM2"
|
||||
private const val EPISODE_RANGE_SIZE = 20
|
||||
|
@ -820,42 +814,6 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the new status is Subscribed, false if not. Null if not possible to subscribe.
|
||||
**/
|
||||
fun toggleSubscriptionStatus(): Boolean? {
|
||||
val isSubscribed = _subscribeStatus.value ?: return null
|
||||
val response = currentResponse ?: return null
|
||||
if (response !is EpisodeResponse) return null
|
||||
|
||||
val currentId = response.getId()
|
||||
|
||||
if (isSubscribed) {
|
||||
DataStoreHelper.removeSubscribedData(currentId)
|
||||
} else {
|
||||
val current = DataStoreHelper.getSubscribedData(currentId)
|
||||
|
||||
DataStoreHelper.setSubscribedData(
|
||||
currentId,
|
||||
DataStoreHelper.SubscribedData(
|
||||
currentId,
|
||||
current?.bookmarkedTime ?: unixTimeMS,
|
||||
unixTimeMS,
|
||||
response.getLatestEpisodes(),
|
||||
response.name,
|
||||
response.url,
|
||||
response.apiName,
|
||||
response.type,
|
||||
response.posterUrl,
|
||||
response.year
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
_subscribeStatus.postValue(!isSubscribed)
|
||||
return !isSubscribed
|
||||
}
|
||||
|
||||
private fun startChromecast(
|
||||
activity: Activity?,
|
||||
result: ResultEpisode,
|
||||
|
@ -1126,12 +1084,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
1L
|
||||
}
|
||||
|
||||
// Component no longer safe to use in A13 for VLC
|
||||
// https://code.videolan.org/videolan/vlc-android/-/issues/2776
|
||||
// This will likely need to be updated once VLC fixes their documentation.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
component = VLC_COMPONENT
|
||||
}
|
||||
|
||||
putExtra("from_start", !resume)
|
||||
putExtra("position", position)
|
||||
|
@ -1469,12 +1422,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
meta: SyncAPI.SyncResult?,
|
||||
syncs: Map<String, String>? = null
|
||||
): Pair<LoadResponse, Boolean> {
|
||||
//if (meta == null) return resp to false
|
||||
if (meta == null) return resp to false
|
||||
var updateEpisodes = false
|
||||
val out = resp.apply {
|
||||
Log.i(TAG, "applyMeta")
|
||||
|
||||
if (meta != null) {
|
||||
duration = duration ?: meta.duration
|
||||
rating = rating ?: meta.publicScore
|
||||
tags = tags ?: meta.genres
|
||||
|
@ -1486,7 +1438,12 @@ class ResultViewModel2 : ViewModel() {
|
|||
nextAiring = nextAiring ?: meta.nextAiring
|
||||
}
|
||||
|
||||
for ((k, v) in syncs ?: emptyMap()) {
|
||||
syncData[k] = v
|
||||
}
|
||||
|
||||
val realRecommendations = ArrayList<SearchResponse>()
|
||||
// TODO: fix
|
||||
val apiNames = apis.filter {
|
||||
it.name.contains("gogoanime", true) ||
|
||||
it.name.contains("9anime", true)
|
||||
|
@ -1502,56 +1459,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
|
||||
recommendations = recommendations?.union(realRecommendations)?.toList()
|
||||
?: realRecommendations
|
||||
}
|
||||
|
||||
for ((k, v) in syncs ?: emptyMap()) {
|
||||
syncData[k] = v
|
||||
}
|
||||
|
||||
argamap(
|
||||
{
|
||||
if (this !is AnimeLoadResponse) return@argamap
|
||||
// already exist, no need to run getTracker
|
||||
if (this.getAniListId() != null && this.getMalId() != null) return@argamap
|
||||
|
||||
val res = APIHolder.getTracker(
|
||||
listOfNotNull(
|
||||
this.engName,
|
||||
this.name,
|
||||
this.japName
|
||||
).filter { it.length > 2 }
|
||||
.distinct(), // the reason why we filter is due to not wanting smth like " " or "?"
|
||||
TrackerType.getTypes(this.type),
|
||||
this.year
|
||||
)
|
||||
|
||||
val ids = arrayOf(
|
||||
AccountManager.malApi.idPrefix to res?.malId?.toString(),
|
||||
AccountManager.aniListApi.idPrefix to res?.aniId
|
||||
)
|
||||
|
||||
if (ids.any { (id, new) ->
|
||||
val current = syncData[id]
|
||||
new != null && current != null && current != new
|
||||
}
|
||||
) {
|
||||
// getTracker fucked up as it conflicts with current implementation
|
||||
return@argamap
|
||||
}
|
||||
|
||||
// set all the new data, prioritise old correct data
|
||||
ids.forEach { (id, new) ->
|
||||
new?.let {
|
||||
syncData[id] = syncData[id] ?: it
|
||||
}
|
||||
}
|
||||
|
||||
// set posters, might fuck up due to headers idk
|
||||
posterUrl = posterUrl ?: res?.image
|
||||
backgroundPosterUrl = backgroundPosterUrl ?: res?.cover
|
||||
},
|
||||
{
|
||||
if (meta == null) return@argamap
|
||||
argamap({
|
||||
addTrailer(meta.trailers)
|
||||
}, {
|
||||
if (this !is AnimeLoadResponse) return@argamap
|
||||
|
@ -1577,6 +1486,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
current.getOrNull(index)?.let { currentEp ->
|
||||
current[index] = currentEp.apply {
|
||||
updateCount++
|
||||
val currentBack = this
|
||||
this.description = this.description ?: node.description?.en
|
||||
this.name = this.name ?: node.titles?.canonical
|
||||
this.episode =
|
||||
|
@ -1717,16 +1627,6 @@ class ResultViewModel2 : ViewModel() {
|
|||
postResume()
|
||||
}
|
||||
|
||||
private fun postSubscription(loadResponse: LoadResponse) {
|
||||
if (loadResponse.isEpisodeBased()) {
|
||||
val id = loadResponse.getId()
|
||||
val data = DataStoreHelper.getSubscribedData(id)
|
||||
DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
|
||||
val isSubscribed = data != null
|
||||
_subscribeStatus.postValue(isSubscribed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
|
||||
if (range == null || indexer == null) {
|
||||
return
|
||||
|
@ -1863,7 +1763,6 @@ class ResultViewModel2 : ViewModel() {
|
|||
) {
|
||||
currentResponse = loadResponse
|
||||
postPage(loadResponse, apiRepository)
|
||||
postSubscription(loadResponse)
|
||||
if (updateEpisodes)
|
||||
postEpisodes(loadResponse, updateFillers)
|
||||
}
|
||||
|
@ -2225,7 +2124,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
autostart: AutoResume?,
|
||||
loadTrailers: Boolean = true,
|
||||
) =
|
||||
ioSafe {
|
||||
viewModelScope.launchSafe {
|
||||
_page.postValue(Resource.Loading(url))
|
||||
_episodes.postValue(ResourceSome.Loading())
|
||||
|
||||
|
@ -2243,7 +2142,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
"This provider does not exist"
|
||||
)
|
||||
)
|
||||
return@ioSafe
|
||||
return@launchSafe
|
||||
}
|
||||
|
||||
|
||||
|
@ -2254,15 +2153,21 @@ class ResultViewModel2 : ViewModel() {
|
|||
api
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: fix
|
||||
// val validUrlResource = safeApiCall {
|
||||
// SyncRedirector.redirect(
|
||||
// url,
|
||||
// api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
|
||||
// .replace(GogoanimeProvider().mainUrl, "gogoanime")
|
||||
// )
|
||||
// }
|
||||
if (validUrlResource !is Resource.Success) {
|
||||
if (validUrlResource is Resource.Failure) {
|
||||
_page.postValue(validUrlResource)
|
||||
}
|
||||
|
||||
return@ioSafe
|
||||
return@launchSafe
|
||||
}
|
||||
|
||||
val validUrl = validUrlResource.value
|
||||
val repo = APIRepository(api)
|
||||
currentRepo = repo
|
||||
|
@ -2272,11 +2177,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
_page.postValue(data)
|
||||
}
|
||||
is Resource.Success -> {
|
||||
if (!isActive) return@ioSafe
|
||||
if (!isActive) return@launchSafe
|
||||
val loadResponse = ioWork {
|
||||
applyMeta(data.value, currentMeta, currentSync).first
|
||||
}
|
||||
if (!isActive) return@ioSafe
|
||||
if (!isActive) return@launchSafe
|
||||
val mainId = loadResponse.getId()
|
||||
|
||||
preferDubStatus = getDub(mainId) ?: preferDubStatus
|
||||
|
@ -2304,7 +2209,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
updateFillers = showFillers,
|
||||
apiRepository = repo
|
||||
)
|
||||
if (!isActive) return@ioSafe
|
||||
if (!isActive) return@launchSafe
|
||||
handleAutoStart(activity, autostart)
|
||||
}
|
||||
is Resource.Loading -> {
|
||||
|
|
|
@ -157,28 +157,6 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
|||
)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
|
||||
val displayedItems = listOf(
|
||||
dialog.login_username_input,
|
||||
dialog.login_email_input,
|
||||
dialog.login_server_input,
|
||||
dialog.login_password_input
|
||||
).filter { it.isVisible }
|
||||
|
||||
displayedItems.foldRight(displayedItems.firstOrNull()) { item, previous ->
|
||||
item?.id?.let { previous?.nextFocusDownId = it }
|
||||
previous?.id?.let { item?.nextFocusUpId = it }
|
||||
item
|
||||
}
|
||||
|
||||
displayedItems.firstOrNull()?.let {
|
||||
dialog.create_account?.nextFocusDownId = it.id
|
||||
it.nextFocusUpId = dialog.create_account.id
|
||||
}
|
||||
dialog.apply_btt?.id?.let {
|
||||
displayedItems.lastOrNull()?.nextFocusDownId = it
|
||||
}
|
||||
|
||||
dialog.text1?.text = api.name
|
||||
|
||||
if (api.storesPasswordInPlainText) {
|
||||
|
|
|
@ -56,50 +56,48 @@ fun getCurrentLocale(context: Context): String {
|
|||
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto
|
||||
val appLanguages = arrayListOf(
|
||||
/* begin language list */
|
||||
Triple("", "العربية", "ar"),
|
||||
Triple("", "български", "bg"),
|
||||
Triple("", "বাংলা", "bn"),
|
||||
Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"),
|
||||
Triple("", "čeština", "cs"),
|
||||
Triple("", "Deutsch", "de"),
|
||||
Triple("", "Ελληνικά", "el"),
|
||||
Triple("", "Arabic", "ar"),
|
||||
Triple("", "Bulgarian", "bg"),
|
||||
Triple("", "Bengali", "bn"),
|
||||
Triple("\uD83C\uDDE7\uD83C\uDDF7", "Brazilian Portuguese", "bp"),
|
||||
Triple("", "Czech", "cs"),
|
||||
Triple("", "German", "de"),
|
||||
Triple("", "Greek", "el"),
|
||||
Triple("", "English", "en"),
|
||||
Triple("", "Esperanto", "eo"),
|
||||
Triple("", "español", "es"),
|
||||
Triple("", "فارسی", "fa"),
|
||||
Triple("", "français", "fr"),
|
||||
Triple("", "हिन्दी", "hi"),
|
||||
Triple("", "hrvatski", "hr"),
|
||||
Triple("", "magyar", "hu"),
|
||||
Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"),
|
||||
Triple("", "italiano", "it"),
|
||||
Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"),
|
||||
Triple("", "日本語 (にほんご)", "ja"),
|
||||
Triple("", "ಕನ್ನಡ", "kn"),
|
||||
Triple("", "македонски", "mk"),
|
||||
Triple("", "മലയാളം", "ml"),
|
||||
Triple("", "bahasa Melayu", "ms"),
|
||||
Triple("", "Nederlands", "nl"),
|
||||
Triple("", "norsk nynorsk", "nn"),
|
||||
Triple("", "norsk bokmål", "no"),
|
||||
Triple("", "polski", "pl"),
|
||||
Triple("\uD83C\uDDF5\uD83C\uDDF9", "português", "pt"),
|
||||
Triple("\uD83E\uDD8D", "mmmm... monke", "qt"),
|
||||
Triple("", "română", "ro"),
|
||||
Triple("", "русский", "ru"),
|
||||
Triple("", "slovenčina", "sk"),
|
||||
Triple("", "Soomaaliga", "so"),
|
||||
Triple("", "svenska", "sv"),
|
||||
Triple("", "தமிழ்", "ta"),
|
||||
Triple("", "Spanish", "es"),
|
||||
Triple("", "Farsi", "fa"),
|
||||
Triple("", "French", "fr"),
|
||||
Triple("", "Hindi", "hi"),
|
||||
Triple("", "Croatian", "hr"),
|
||||
Triple("", "Hungarian", "hu"),
|
||||
Triple("\uD83C\uDDEE\uD83C\uDDE9", "Indonesian", "in"),
|
||||
Triple("", "Italian", "it"),
|
||||
Triple("\uD83C\uDDEE\uD83C\uDDF1", "Hebrew", "iw"),
|
||||
Triple("", "Kannada", "kn"),
|
||||
Triple("", "Macedonian", "mk"),
|
||||
Triple("", "Malayalam", "ml"),
|
||||
Triple("", "Moldavian", "mo"),
|
||||
Triple("", "Dutch", "nl"),
|
||||
Triple("", "Norwegian Nynorsk", "nn"),
|
||||
Triple("", "Norwegian", "no"),
|
||||
Triple("", "Polish", "pl"),
|
||||
Triple("\uD83C\uDDF5\uD83C\uDDF9", "Portuguese", "pt"),
|
||||
Triple("", "Romanian", "ro"),
|
||||
Triple("", "Russian", "ru"),
|
||||
Triple("", "Slovak", "sk"),
|
||||
Triple("", "Somali", "so"),
|
||||
Triple("", "Swedish", "sv"),
|
||||
Triple("", "Tamil", "ta"),
|
||||
Triple("", "Tagalog", "tl"),
|
||||
Triple("", "Türkçe", "tr"),
|
||||
Triple("", "українська", "uk"),
|
||||
Triple("", "اردو", "ur"),
|
||||
Triple("", "Tiếng Việt", "vi"),
|
||||
Triple("", "中文", "zh"),
|
||||
Triple("\uD83C\uDDF9\uD83C\uDDFC", "文言", "zh-rTW"),
|
||||
Triple("", "Turkish", "tr"),
|
||||
Triple("", "Ukrainian", "uk"),
|
||||
Triple("", "Urdu", "ur"),
|
||||
Triple("", "Viet Nam", "vi"),
|
||||
Triple("", "Chinese Simplified", "zh"),
|
||||
Triple("\uD83C\uDDF9\uD83C\uDDFC", "Chinese Traditional", "zh-rTW"),
|
||||
/* end language list */
|
||||
).sortedBy { it.second.lowercase() } //ye, we go alphabetical, so ppl don't put their lang on top
|
||||
).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
|
||||
|
||||
class SettingsGeneral : PreferenceFragmentCompat() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -159,6 +157,9 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
|||
|
||||
getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref ->
|
||||
val tempLangs = appLanguages.toMutableList()
|
||||
//if (beneneCount > 100) {
|
||||
// tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo"))
|
||||
//}
|
||||
val current = getCurrentLocale(pref.context)
|
||||
val languageCodes = tempLangs.map { (_, _, iso) -> iso }
|
||||
val languageNames = tempLangs.map { (emoji, name, iso) ->
|
||||
|
@ -315,12 +316,6 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
|||
} ?: emptyList()
|
||||
}
|
||||
|
||||
settingsManager.edit().putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false).apply()
|
||||
getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue ->
|
||||
setKey(getString(R.string.jsdelivr_proxy_key), newValue)
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
|
||||
getPref(R.string.download_path_key)?.setOnPreferenceClickListener {
|
||||
val dirs = getDownloadDirs()
|
||||
|
||||
|
|
|
@ -113,30 +113,6 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.quality_pref_mobile_data_key)?.setOnPreferenceClickListener {
|
||||
val prefValues = Qualities.values().map { it.value }.reversed().toMutableList()
|
||||
prefValues.remove(Qualities.Unknown.value)
|
||||
|
||||
val prefNames = prefValues.map { Qualities.getStringByInt(it) }
|
||||
|
||||
val currentQuality =
|
||||
settingsManager.getInt(
|
||||
getString(R.string.quality_pref_mobile_data_key),
|
||||
Qualities.values().last().value
|
||||
)
|
||||
|
||||
activity?.showBottomDialog(
|
||||
prefNames.toList(),
|
||||
prefValues.indexOf(currentQuality),
|
||||
getString(R.string.watch_quality_pref_data),
|
||||
true,
|
||||
{}) {
|
||||
settingsManager.edit().putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it])
|
||||
.apply()
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.player_pref_key)?.setOnPreferenceClickListener {
|
||||
val prefNames = resources.getStringArray(R.array.player_pref_names)
|
||||
val prefValues = resources.getIntArray(R.array.player_pref_values)
|
||||
|
|
|
@ -2,8 +2,6 @@ package com.lagradost.cloudstream3.ui.settings
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.lagradost.cloudstream3.*
|
||||
|
@ -18,7 +16,6 @@ import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
|||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
|
||||
class SettingsProviders : PreferenceFragmentCompat() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -59,20 +56,6 @@ class SettingsProviders : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.test_providers_key)?.setOnPreferenceClickListener {
|
||||
// Somehow animations do not work without this.
|
||||
val options = NavOptions.Builder()
|
||||
.setEnterAnim(R.anim.enter_anim)
|
||||
.setExitAnim(R.anim.exit_anim)
|
||||
.setPopEnterAnim(R.anim.pop_enter)
|
||||
.setPopExitAnim(R.anim.pop_exit)
|
||||
.build()
|
||||
|
||||
this@SettingsProviders.findNavController()
|
||||
.navigate(R.id.navigation_test_providers, null, options)
|
||||
true
|
||||
}
|
||||
|
||||
getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
|
||||
val names = enumValues<TvType>().sorted().map { it.name }
|
||||
val default =
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.testing
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import kotlinx.android.synthetic.main.fragment_testing.*
|
||||
import kotlinx.android.synthetic.main.view_test.*
|
||||
|
||||
|
||||
class TestFragment : Fragment() {
|
||||
|
||||
private val testViewModel: TestViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
setUpToolbar(R.string.category_provider_test)
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
provider_test_recycler_view?.adapter = TestResultAdapter(
|
||||
mutableListOf()
|
||||
)
|
||||
|
||||
testViewModel.init()
|
||||
if (testViewModel.isRunningTest) {
|
||||
provider_test?.setState(TestView.TestState.Running)
|
||||
}
|
||||
|
||||
observe(testViewModel.providerProgress) { (passed, failed, total) ->
|
||||
provider_test?.setProgress(passed, failed, total)
|
||||
}
|
||||
|
||||
observeNullable(testViewModel.providerResults) {
|
||||
normalSafeApiCall {
|
||||
val newItems = it.sortedBy { api -> api.first.name }
|
||||
(provider_test_recycler_view?.adapter as? TestResultAdapter)?.updateList(
|
||||
newItems
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
provider_test?.setOnPlayButtonListener { state ->
|
||||
when (state) {
|
||||
TestView.TestState.Stopped -> testViewModel.stopTest()
|
||||
TestView.TestState.Running -> testViewModel.startTest()
|
||||
TestView.TestState.None -> testViewModel.startTest()
|
||||
}
|
||||
}
|
||||
|
||||
if (isTrueTvSettings()) {
|
||||
tests_play_pause?.isFocusableInTouchMode = true
|
||||
tests_play_pause?.requestFocus()
|
||||
}
|
||||
|
||||
provider_test?.playPauseButton?.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
provider_test_appbar?.setExpanded(true, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun focusRecyclerView() {
|
||||
// Hack to make it possible to focus the recyclerview.
|
||||
if (isTrueTvSettings()) {
|
||||
provider_test_recycler_view?.requestFocus()
|
||||
provider_test_appbar?.setExpanded(false, true)
|
||||
}
|
||||
}
|
||||
|
||||
provider_test?.setOnMainClick {
|
||||
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All)
|
||||
focusRecyclerView()
|
||||
}
|
||||
provider_test?.setOnFailedClick {
|
||||
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed)
|
||||
focusRecyclerView()
|
||||
}
|
||||
provider_test?.setOnPassedClick {
|
||||
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed)
|
||||
focusRecyclerView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_testing, container, false)
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.testing
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.getAllMessages
|
||||
import com.lagradost.cloudstream3.mvvm.getStackTracePretty
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
||||
import com.lagradost.cloudstream3.utils.TestingUtils
|
||||
import kotlinx.android.synthetic.main.provider_test_item.view.*
|
||||
|
||||
class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUtils.TestResultProvider>>) :
|
||||
AppUtils.DiffAdapter<Pair<MainAPI, TestingUtils.TestResultProvider>>(items) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return ProviderTestViewHolder(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.provider_test_item, parent, false),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is ProviderTestViewHolder -> {
|
||||
val item = items[position]
|
||||
holder.bind(item.first, item.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class ProviderTestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val languageText: TextView = itemView.lang_icon
|
||||
private val providerTitle: TextView = itemView.main_text
|
||||
private val statusText: TextView = itemView.passed_failed_marker
|
||||
private val failDescription: TextView = itemView.fail_description
|
||||
private val logButton: ImageView = itemView.action_button
|
||||
|
||||
private fun String.lastLine(): String? {
|
||||
return this.lines().lastOrNull { it.isNotBlank() }
|
||||
}
|
||||
|
||||
fun bind(api: MainAPI, result: TestingUtils.TestResultProvider) {
|
||||
languageText.text = getFlagFromIso(api.lang)
|
||||
providerTitle.text = api.name
|
||||
|
||||
val (resultText, resultColor) = if (result.success) {
|
||||
R.string.test_passed to R.color.colorTestPass
|
||||
} else {
|
||||
R.string.test_failed to R.color.colorTestFail
|
||||
}
|
||||
|
||||
statusText.setText(resultText)
|
||||
statusText.setTextColor(ContextCompat.getColor(itemView.context, resultColor))
|
||||
|
||||
val stackTrace = result.exception?.getStackTracePretty(false)?.ifBlank { null }
|
||||
val messages = result.exception?.getAllMessages()?.ifBlank { null }
|
||||
val fullLog =
|
||||
result.log + (messages?.let { "\n\n$it" } ?: "") + (stackTrace?.let { "\n\n$it" } ?: "")
|
||||
|
||||
failDescription.text = messages?.lastLine() ?: result.log.lastLine()
|
||||
|
||||
logButton.setOnClickListener {
|
||||
val builder: AlertDialog.Builder =
|
||||
AlertDialog.Builder(it.context, R.style.AlertDialogCustom)
|
||||
builder.setMessage(fullLog)
|
||||
.setTitle(R.string.test_log)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.testing
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.animateProgressTo
|
||||
|
||||
class TestView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : CardView(context, attrs) {
|
||||
enum class TestState(@StringRes val stringRes: Int, @DrawableRes val icon: Int) {
|
||||
None(R.string.start, R.drawable.ic_baseline_play_arrow_24),
|
||||
|
||||
// Paused(R.string.resume, R.drawable.ic_baseline_play_arrow_24),
|
||||
Stopped(R.string.restart, R.drawable.ic_baseline_play_arrow_24),
|
||||
Running(R.string.stop, R.drawable.pause_to_play),
|
||||
}
|
||||
|
||||
var mainSection: View? = null
|
||||
var testsPassedSection: View? = null
|
||||
var testsFailedSection: View? = null
|
||||
|
||||
var mainSectionText: TextView? = null
|
||||
var mainSectionHeader: TextView? = null
|
||||
var testsPassedSectionText: TextView? = null
|
||||
var testsFailedSectionText: TextView? = null
|
||||
var totalProgressBar: ContentLoadingProgressBar? = null
|
||||
|
||||
var playPauseButton: MaterialButton? = null
|
||||
var stateListener: (TestState) -> Unit = {}
|
||||
|
||||
private var state = TestState.None
|
||||
|
||||
init {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_test, this, true)
|
||||
|
||||
mainSection = findViewById(R.id.main_test_section)
|
||||
testsPassedSection = findViewById(R.id.passed_test_section)
|
||||
testsFailedSection = findViewById(R.id.failed_test_section)
|
||||
|
||||
mainSectionHeader = findViewById(R.id.main_test_header)
|
||||
mainSectionText = findViewById(R.id.main_test_section_progress)
|
||||
testsPassedSectionText = findViewById(R.id.passed_test_section_progress)
|
||||
testsFailedSectionText = findViewById(R.id.failed_test_section_progress)
|
||||
|
||||
totalProgressBar = findViewById(R.id.test_total_progress)
|
||||
playPauseButton = findViewById(R.id.tests_play_pause)
|
||||
|
||||
attrs?.let {
|
||||
val typedArray = context.obtainStyledAttributes(it, R.styleable.TestView)
|
||||
val headerText = typedArray.getString(R.styleable.TestView_header_text)
|
||||
mainSectionHeader?.text = headerText
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
playPauseButton?.setOnClickListener {
|
||||
val newState = when (state) {
|
||||
TestState.None -> TestState.Running
|
||||
TestState.Running -> TestState.Stopped
|
||||
TestState.Stopped -> TestState.Running
|
||||
}
|
||||
setState(newState)
|
||||
}
|
||||
}
|
||||
|
||||
fun setOnPlayButtonListener(listener: (TestState) -> Unit) {
|
||||
stateListener = listener
|
||||
}
|
||||
|
||||
fun setState(newState: TestState) {
|
||||
state = newState
|
||||
stateListener.invoke(newState)
|
||||
playPauseButton?.setText(newState.stringRes)
|
||||
playPauseButton?.icon = ContextCompat.getDrawable(context, newState.icon)
|
||||
}
|
||||
|
||||
fun setProgress(passed: Int, failed: Int, total: Int?) {
|
||||
val totalProgress = passed + failed
|
||||
mainSectionText?.text = "$totalProgress / ${total?.toString() ?: "?"}"
|
||||
testsPassedSectionText?.text = passed.toString()
|
||||
testsFailedSectionText?.text = failed.toString()
|
||||
|
||||
totalProgressBar?.max = (total ?: 0) * 1000
|
||||
totalProgressBar?.animateProgressTo(totalProgress * 1000)
|
||||
|
||||
totalProgressBar?.isVisible = !(totalProgress == 0 || (total ?: 0) == 0)
|
||||
if (totalProgress == total) {
|
||||
setState(TestState.Stopped)
|
||||
}
|
||||
}
|
||||
|
||||
fun setMainHeader(@StringRes header: Int) {
|
||||
mainSectionHeader?.setText(header)
|
||||
}
|
||||
|
||||
fun setOnMainClick(listener: OnClickListener) {
|
||||
mainSection?.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
fun setOnPassedClick(listener: OnClickListener) {
|
||||
testsPassedSection?.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
fun setOnFailedClick(listener: OnClickListener) {
|
||||
testsFailedSection?.setOnClickListener(listener)
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.testing
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
|
||||
import com.lagradost.cloudstream3.utils.TestingUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
class TestViewModel : ViewModel() {
|
||||
data class TestProgress(
|
||||
val passed: Int,
|
||||
val failed: Int,
|
||||
val total: Int
|
||||
)
|
||||
|
||||
enum class ProviderFilter {
|
||||
All,
|
||||
Passed,
|
||||
Failed
|
||||
}
|
||||
|
||||
private val _providerProgress = MutableLiveData<TestProgress>(null)
|
||||
val providerProgress: LiveData<TestProgress> = _providerProgress
|
||||
|
||||
private val _providerResults =
|
||||
MutableLiveData<List<Pair<MainAPI, TestingUtils.TestResultProvider>>>(
|
||||
emptyList()
|
||||
)
|
||||
|
||||
val providerResults: LiveData<List<Pair<MainAPI, TestingUtils.TestResultProvider>>> =
|
||||
_providerResults
|
||||
|
||||
private var scope: CoroutineScope? = null
|
||||
val isRunningTest
|
||||
get() = scope != null
|
||||
|
||||
private var filter = ProviderFilter.All
|
||||
private val providers = threadSafeListOf<Pair<MainAPI, TestingUtils.TestResultProvider>>()
|
||||
private var passed = 0
|
||||
private var failed = 0
|
||||
private var total = 0
|
||||
|
||||
private fun updateProgress() {
|
||||
_providerProgress.postValue(TestProgress(passed, failed, total))
|
||||
postProviders()
|
||||
}
|
||||
|
||||
private fun postProviders() {
|
||||
synchronized(providers) {
|
||||
val filtered = when (filter) {
|
||||
ProviderFilter.All -> providers
|
||||
ProviderFilter.Passed -> providers.filter { it.second.success }
|
||||
ProviderFilter.Failed -> providers.filter { !it.second.success }
|
||||
}
|
||||
_providerResults.postValue(filtered)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFilterMethod(filter: ProviderFilter) {
|
||||
if (this.filter == filter) return
|
||||
this.filter = filter
|
||||
postProviders()
|
||||
}
|
||||
|
||||
private fun addProvider(api: MainAPI, results: TestingUtils.TestResultProvider) {
|
||||
synchronized(providers) {
|
||||
val index = providers.indexOfFirst { it.first == api }
|
||||
if (index == -1) {
|
||||
providers.add(api to results)
|
||||
if (results.success) passed++ else failed++
|
||||
} else {
|
||||
providers[index] = api to results
|
||||
}
|
||||
updateProgress()
|
||||
}
|
||||
}
|
||||
|
||||
fun init() {
|
||||
val apis = APIHolder.allProviders
|
||||
total = apis.size
|
||||
updateProgress()
|
||||
}
|
||||
|
||||
fun startTest() {
|
||||
scope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
val apis = APIHolder.allProviders
|
||||
total = apis.size
|
||||
failed = 0
|
||||
passed = 0
|
||||
providers.clear()
|
||||
updateProgress()
|
||||
|
||||
TestingUtils.getDeferredProviderTests(scope ?: return, apis, ::println) { api, result ->
|
||||
addProvider(api, result)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopTest() {
|
||||
scope?.cancel()
|
||||
scope = null
|
||||
}
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.Cursor
|
||||
|
@ -20,7 +17,6 @@ import android.os.*
|
|||
import android.provider.MediaStore
|
||||
import android.text.Spanned
|
||||
import android.util.Log
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
|
@ -29,7 +25,6 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.toSpanned
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.fragment.findNavController
|
||||
|
@ -184,36 +179,6 @@ object AppUtils {
|
|||
touchSlopField.set(recyclerView, touchSlop * f) // "8" was obtained experimentally
|
||||
}
|
||||
|
||||
fun ContentLoadingProgressBar?.animateProgressTo(to: Int) {
|
||||
if (this == null) return
|
||||
val animation: ObjectAnimator = ObjectAnimator.ofInt(
|
||||
this,
|
||||
"progress",
|
||||
this.progress,
|
||||
to
|
||||
)
|
||||
animation.duration = 500
|
||||
animation.setAutoCancel(true)
|
||||
animation.interpolator = DecelerateInterpolator()
|
||||
animation.start()
|
||||
}
|
||||
|
||||
fun Context.createNotificationChannel(channelId: String, channelName: String, description: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val channel =
|
||||
NotificationChannel(channelId, channelName, importance).apply {
|
||||
this.description = description
|
||||
}
|
||||
|
||||
// Register the channel with the system.
|
||||
val notificationManager: NotificationManager =
|
||||
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun getAllWatchNextPrograms(context: Context): Set<Long> {
|
||||
val COLUMN_WATCH_NEXT_ID_INDEX = 0
|
||||
|
@ -491,12 +456,6 @@ object AppUtils {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.isNetworkAvailable(): Boolean {
|
||||
val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val activeNetworkInfo = manager.activeNetworkInfo
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected || manager.allNetworkInfo?.any { it.isConnected } ?: false
|
||||
}
|
||||
|
||||
fun splitQuery(url: URL): Map<String, String> {
|
||||
val queryPairs: MutableMap<String, String> = LinkedHashMap()
|
||||
val query: String = url.query
|
||||
|
@ -776,14 +735,9 @@ object AppUtils {
|
|||
return networkInfo.any {
|
||||
conManager.getNetworkCapabilities(it)
|
||||
?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
|
||||
} &&
|
||||
!networkInfo.any {
|
||||
conManager.getNetworkCapabilities(it)
|
||||
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun Activity?.cacheClass(clazz: String?) {
|
||||
clazz?.let { c ->
|
||||
this?.cacheDir?.let {
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.provider.MediaStore
|
|||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
|
@ -28,8 +27,6 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_T
|
|||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs
|
||||
import com.lagradost.cloudstream3.utils.DataStore.mapper
|
||||
|
@ -120,7 +117,6 @@ object BackupUtils {
|
|||
)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun Context.restore(
|
||||
backupFile: BackupFile,
|
||||
restoreSettings: Boolean,
|
||||
|
@ -223,29 +219,31 @@ object BackupUtils {
|
|||
try {
|
||||
restoreFileSelector =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
|
||||
if (uri == null) return@registerForActivityResult
|
||||
val activity = this
|
||||
ioSafe {
|
||||
this.let { activity ->
|
||||
uri?.let {
|
||||
try {
|
||||
val input = activity.contentResolver.openInputStream(uri)
|
||||
?: return@ioSafe
|
||||
val input =
|
||||
activity.contentResolver.openInputStream(uri)
|
||||
?: return@registerForActivityResult
|
||||
|
||||
val restoredValue =
|
||||
mapper.readValue<BackupFile>(input)
|
||||
|
||||
activity.restore(
|
||||
restoredValue,
|
||||
restoreSettings = true,
|
||||
restoreDataStore = true
|
||||
)
|
||||
activity.runOnUiThread { activity.recreate() }
|
||||
activity.recreate()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
main { // smth can fail in .format
|
||||
try { // smth can fail in .format
|
||||
showToast(
|
||||
activity,
|
||||
getString(R.string.restore_failed_format).format(e.toString())
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.APIHolder.capitalize
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.SearchQuality
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
|
@ -17,7 +20,6 @@ const val VIDEO_POS_DUR = "video_pos_dur"
|
|||
const val VIDEO_WATCH_STATE = "video_watch_state"
|
||||
const val RESULT_WATCH_STATE = "result_watch_state"
|
||||
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
||||
const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data"
|
||||
const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes
|
||||
const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching"
|
||||
const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated"
|
||||
|
@ -40,37 +42,6 @@ object DataStoreHelper {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to display notifications on new episodes and posters in library.
|
||||
**/
|
||||
data class SubscribedData(
|
||||
@JsonProperty("id") override var id: Int?,
|
||||
@JsonProperty("subscribedTime") val bookmarkedTime: Long,
|
||||
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
|
||||
@JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map<DubStatus, Int?>,
|
||||
@JsonProperty("name") override val name: String,
|
||||
@JsonProperty("url") override val url: String,
|
||||
@JsonProperty("apiName") override val apiName: String,
|
||||
@JsonProperty("type") override var type: TvType? = null,
|
||||
@JsonProperty("posterUrl") override var posterUrl: String?,
|
||||
@JsonProperty("year") val year: Int?,
|
||||
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
||||
) : SearchResponse {
|
||||
fun toLibraryItem(): SyncAPI.LibraryItem? {
|
||||
return SyncAPI.LibraryItem(
|
||||
name,
|
||||
url,
|
||||
id?.toString() ?: return null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
latestUpdatedTime,
|
||||
apiName, type, posterUrl, posterHeaders, quality, this.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class BookmarkedData(
|
||||
@JsonProperty("id") override var id: Int?,
|
||||
@JsonProperty("bookmarkedTime") val bookmarkedTime: Long,
|
||||
|
@ -92,7 +63,7 @@ object DataStoreHelper {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
latestUpdatedTime,
|
||||
null,
|
||||
apiName, type, posterUrl, posterHeaders, quality, this.id
|
||||
)
|
||||
}
|
||||
|
@ -104,7 +75,9 @@ object DataStoreHelper {
|
|||
@JsonProperty("apiName") override val apiName: String,
|
||||
@JsonProperty("type") override var type: TvType? = null,
|
||||
@JsonProperty("posterUrl") override var posterUrl: String?,
|
||||
|
||||
@JsonProperty("watchPos") val watchPos: PosDur?,
|
||||
|
||||
@JsonProperty("id") override var id: Int?,
|
||||
@JsonProperty("parentId") val parentId: Int?,
|
||||
@JsonProperty("episode") val episode: Int?,
|
||||
|
@ -231,41 +204,6 @@ object DataStoreHelper {
|
|||
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString())
|
||||
}
|
||||
|
||||
fun getAllSubscriptions(): List<SubscribedData> {
|
||||
return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull {
|
||||
getKey(it)
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun removeSubscribedData(id: Int?) {
|
||||
if (id == null) return
|
||||
AccountManager.localListApi.requireLibraryRefresh = true
|
||||
removeKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new seen episodes and update time
|
||||
**/
|
||||
fun updateSubscribedData(id: Int?, data: SubscribedData?, episodeResponse: EpisodeResponse?) {
|
||||
if (id == null || data == null || episodeResponse == null) return
|
||||
val newData = data.copy(
|
||||
latestUpdatedTime = unixTimeMS,
|
||||
lastSeenEpisodeCount = episodeResponse.getLatestEpisodes()
|
||||
)
|
||||
setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), newData)
|
||||
}
|
||||
|
||||
fun setSubscribedData(id: Int?, data: SubscribedData) {
|
||||
if (id == null) return
|
||||
setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), data)
|
||||
AccountManager.localListApi.requireLibraryRefresh = true
|
||||
}
|
||||
|
||||
fun getSubscribedData(id: Int?): SubscribedData? {
|
||||
if (id == null) return null
|
||||
return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString())
|
||||
}
|
||||
|
||||
fun setViewPos(id: Int?, pos: Long, dur: Long) {
|
||||
if (id == null) return
|
||||
if (dur < 30_000) return // too short
|
||||
|
|
|
@ -52,7 +52,7 @@ data class ExtractorLinkPlayList(
|
|||
)
|
||||
|
||||
|
||||
open class ExtractorLink constructor(
|
||||
open class ExtractorLink(
|
||||
open val source: String,
|
||||
open val name: String,
|
||||
override val url: String,
|
||||
|
@ -62,24 +62,7 @@ open class ExtractorLink constructor(
|
|||
override val headers: Map<String, String> = mapOf(),
|
||||
/** Used for getExtractorVerifierJob() */
|
||||
open val extractorData: String? = null,
|
||||
open val isDash: Boolean = false,
|
||||
) : VideoDownloadManager.IDownloadableMinimum {
|
||||
/**
|
||||
* Old constructor without isDash, allows for backwards compatibility with extensions.
|
||||
* Should be removed after all extensions have updated their cloudstream.jar
|
||||
**/
|
||||
constructor(
|
||||
source: String,
|
||||
name: String,
|
||||
url: String,
|
||||
referer: String,
|
||||
quality: Int,
|
||||
isM3u8: Boolean = false,
|
||||
headers: Map<String, String> = mapOf(),
|
||||
/** Used for getExtractorVerifierJob() */
|
||||
extractorData: String? = null
|
||||
) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)"
|
||||
}
|
||||
|
@ -246,7 +229,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
StreamSB8(),
|
||||
StreamSB9(),
|
||||
StreamSB10(),
|
||||
StreamSB11(),
|
||||
SBfull(),
|
||||
// Streamhub(), cause Streamhub2() works
|
||||
Streamhub2(),
|
||||
|
@ -272,7 +254,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
// WatchSB(), 'cause StreamSB.kt works
|
||||
Uqload(),
|
||||
Uqload1(),
|
||||
Uqload2(),
|
||||
Evoload(),
|
||||
Evoload1(),
|
||||
VoeExtractor(),
|
||||
|
@ -284,7 +265,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
OkRu(),
|
||||
OkRuHttps(),
|
||||
Okrulink(),
|
||||
Sendvid(),
|
||||
|
||||
// dood extractors
|
||||
DoodCxExtractor(),
|
||||
|
@ -296,7 +276,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
DoodShExtractor(),
|
||||
DoodWatchExtractor(),
|
||||
DoodWfExtractor(),
|
||||
DoodYtExtractor(),
|
||||
|
||||
AsianLoad(),
|
||||
|
||||
|
@ -312,7 +291,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
Supervideo(),
|
||||
GuardareStream(),
|
||||
CineGrabber(),
|
||||
Vanfem(),
|
||||
|
||||
// StreamSB.kt works
|
||||
// SBPlay(),
|
||||
|
@ -343,9 +321,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
DesuDrive(),
|
||||
|
||||
Filesim(),
|
||||
FileMoon(),
|
||||
FileMoonSx(),
|
||||
Vido(),
|
||||
Linkbox(),
|
||||
Acefile(),
|
||||
SpeedoStream(),
|
||||
|
@ -387,7 +362,6 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
Cda(),
|
||||
Dailymotion(),
|
||||
ByteShare(),
|
||||
Ztreamhub()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ class ApkInstaller(private val service: PackageInstallerService) {
|
|||
|
||||
session.openWrite(context.packageName, 0, size)
|
||||
.use { outputStream ->
|
||||
val buffer = ByteArray(4 * 1024)
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead = inputStream.read(buffer)
|
||||
|
||||
while (bytesRead >= 0) {
|
||||
|
@ -100,7 +100,6 @@ class ApkInstaller(private val service: PackageInstallerService) {
|
|||
installProgress.invoke(bytesRead)
|
||||
}
|
||||
|
||||
session.fsync(outputStream)
|
||||
inputStream.close()
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import androidx.core.app.NotificationCompat
|
|||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -48,12 +47,24 @@ class PackageInstallerService : Service() {
|
|||
.setSmallIcon(R.drawable.rdload)
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val channel =
|
||||
NotificationChannel(UPDATE_CHANNEL_ID, UPDATE_CHANNEL_NAME, importance).apply {
|
||||
description = UPDATE_CHANNEL_DESCRIPTION
|
||||
}
|
||||
|
||||
// Register the channel with the system
|
||||
val notificationManager: NotificationManager =
|
||||
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
this.createNotificationChannel(
|
||||
UPDATE_CHANNEL_ID,
|
||||
UPDATE_CHANNEL_NAME,
|
||||
UPDATE_CHANNEL_DESCRIPTION
|
||||
)
|
||||
createNotificationChannel()
|
||||
startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build())
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
fun Activity?.showBottomDialog(
|
||||
items: List<String>,
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import kotlinx.coroutines.*
|
||||
import org.junit.Assert
|
||||
|
||||
object TestingUtils {
|
||||
open class TestResult(val success: Boolean) {
|
||||
companion object {
|
||||
val Pass = TestResult(true)
|
||||
val Fail = TestResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
class TestResultSearch(val results: List<SearchResponse>) : TestResult(true)
|
||||
class TestResultLoad(val extractorData: String) : TestResult(true)
|
||||
|
||||
class TestResultProvider(success: Boolean, val log: String, val exception: Throwable?) :
|
||||
TestResult(success)
|
||||
|
||||
@Throws(AssertionError::class, CancellationException::class)
|
||||
suspend fun testHomepage(
|
||||
api: MainAPI,
|
||||
logger: (String) -> Unit
|
||||
): TestResult {
|
||||
if (api.hasMainPage) {
|
||||
try {
|
||||
val f = api.mainPage.first()
|
||||
val homepage =
|
||||
api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages))
|
||||
when {
|
||||
homepage == null -> {
|
||||
logger.invoke("Homepage provider ${api.name} did not correctly load homepage!")
|
||||
}
|
||||
homepage.items.isEmpty() -> {
|
||||
logger.invoke("Homepage provider ${api.name} does not contain any items!")
|
||||
}
|
||||
homepage.items.any { it.list.isEmpty() } -> {
|
||||
logger.invoke("Homepage provider ${api.name} does not have any items on result!")
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (e is NotImplementedError) {
|
||||
Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented")
|
||||
} else if (e is CancellationException) {
|
||||
throw e
|
||||
}
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
return TestResult.Pass
|
||||
}
|
||||
|
||||
@Throws(AssertionError::class, CancellationException::class)
|
||||
private suspend fun testSearch(
|
||||
api: MainAPI
|
||||
): TestResult {
|
||||
val searchQueries = listOf("over", "iron", "guy")
|
||||
val searchResults = searchQueries.firstNotNullOfOrNull { query ->
|
||||
try {
|
||||
api.search(query).takeIf { !it.isNullOrEmpty() }
|
||||
} catch (e: Throwable) {
|
||||
if (e is NotImplementedError) {
|
||||
Assert.fail("Provider has not implemented search()")
|
||||
} else if (e is CancellationException) {
|
||||
throw e
|
||||
}
|
||||
logError(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return if (searchResults.isNullOrEmpty()) {
|
||||
Assert.fail("Api ${api.name} did not return any valid search responses")
|
||||
TestResult.Fail // Should not be reached
|
||||
} else {
|
||||
TestResultSearch(searchResults)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Throws(AssertionError::class, CancellationException::class)
|
||||
private suspend fun testLoad(
|
||||
api: MainAPI,
|
||||
result: SearchResponse,
|
||||
logger: (String) -> Unit
|
||||
): TestResult {
|
||||
try {
|
||||
Assert.assertEquals(
|
||||
"Invalid apiName on SearchResponse on ${api.name}",
|
||||
result.apiName,
|
||||
api.name
|
||||
)
|
||||
|
||||
val loadResponse = api.load(result.url)
|
||||
|
||||
if (loadResponse == null) {
|
||||
logger.invoke("Returned null loadResponse on ${result.url} on ${api.name}")
|
||||
return TestResult.Fail
|
||||
}
|
||||
|
||||
Assert.assertEquals(
|
||||
"Invalid apiName on LoadResponse on ${api.name}",
|
||||
loadResponse.apiName,
|
||||
result.apiName
|
||||
)
|
||||
Assert.assertTrue(
|
||||
"Api ${api.name} on load does not contain any of the supportedTypes: ${loadResponse.type}",
|
||||
api.supportedTypes.contains(loadResponse.type)
|
||||
)
|
||||
|
||||
val url = when (loadResponse) {
|
||||
is AnimeLoadResponse -> {
|
||||
val gotNoEpisodes =
|
||||
loadResponse.episodes.keys.isEmpty() || loadResponse.episodes.keys.any { loadResponse.episodes[it].isNullOrEmpty() }
|
||||
|
||||
if (gotNoEpisodes) {
|
||||
logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}")
|
||||
return TestResult.Fail
|
||||
}
|
||||
|
||||
(loadResponse.episodes[loadResponse.episodes.keys.firstOrNull()])?.firstOrNull()?.data
|
||||
}
|
||||
is MovieLoadResponse -> {
|
||||
val gotNoEpisodes = loadResponse.dataUrl.isBlank()
|
||||
if (gotNoEpisodes) {
|
||||
logger.invoke("Api ${api.name} got no movie on ${loadResponse.url}")
|
||||
return TestResult.Fail
|
||||
}
|
||||
|
||||
loadResponse.dataUrl
|
||||
}
|
||||
is TvSeriesLoadResponse -> {
|
||||
val gotNoEpisodes = loadResponse.episodes.isEmpty()
|
||||
if (gotNoEpisodes) {
|
||||
logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}")
|
||||
return TestResult.Fail
|
||||
}
|
||||
loadResponse.episodes.firstOrNull()?.data
|
||||
}
|
||||
is LiveStreamLoadResponse -> {
|
||||
loadResponse.dataUrl
|
||||
}
|
||||
else -> {
|
||||
logger.invoke("Unknown load response: ${loadResponse.javaClass.name}")
|
||||
return TestResult.Fail
|
||||
}
|
||||
} ?: return TestResult.Fail
|
||||
|
||||
return TestResultLoad(url)
|
||||
|
||||
// val loadTest = testLoadResponse(api, load, logger)
|
||||
// if (loadTest is TestResultLoad) {
|
||||
// testLinkLoading(api, loadTest.extractorData, logger).success
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// if (!validResults) {
|
||||
// logger("Api ${api.name} did not load on the first search results: ${smallSearchResults.map { it.name }}")
|
||||
// }
|
||||
|
||||
// return TestResult(validResults)
|
||||
} catch (e: Throwable) {
|
||||
if (e is NotImplementedError) {
|
||||
Assert.fail("Provider has not implemented load()")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(AssertionError::class, CancellationException::class)
|
||||
private suspend fun testLinkLoading(
|
||||
api: MainAPI,
|
||||
url: String?,
|
||||
logger: (String) -> Unit
|
||||
): TestResult {
|
||||
Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
|
||||
if (url == null) return TestResult.Fail // Should never trigger
|
||||
|
||||
var linksLoaded = 0
|
||||
try {
|
||||
val success = api.loadLinks(url, false, {}) { link ->
|
||||
logger.invoke("Video loaded: ${link.name}")
|
||||
Assert.assertTrue(
|
||||
"Api ${api.name} returns link with invalid url ${link.url}",
|
||||
link.url.length > 4
|
||||
)
|
||||
linksLoaded++
|
||||
}
|
||||
if (success) {
|
||||
logger.invoke("Links loaded: $linksLoaded")
|
||||
return TestResult(linksLoaded > 0)
|
||||
} else {
|
||||
Assert.fail("Api ${api.name} returns false on loadLinks() with $linksLoaded links loaded")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
when (e) {
|
||||
is NotImplementedError -> {
|
||||
Assert.fail("Provider has not implemented loadLinks()")
|
||||
}
|
||||
else -> {
|
||||
logger.invoke("Failed link loading on ${api.name} using data: $url")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
return TestResult.Pass
|
||||
}
|
||||
|
||||
fun getDeferredProviderTests(
|
||||
scope: CoroutineScope,
|
||||
providers: List<MainAPI>,
|
||||
logger: (String) -> Unit,
|
||||
callback: (MainAPI, TestResultProvider) -> Unit
|
||||
) {
|
||||
providers.forEach { api ->
|
||||
scope.launch {
|
||||
var log = ""
|
||||
fun addToLog(string: String) {
|
||||
log += string + "\n"
|
||||
logger.invoke(string)
|
||||
}
|
||||
fun getLog(): String {
|
||||
return log.removeSuffix("\n")
|
||||
}
|
||||
|
||||
val result = try {
|
||||
addToLog("Trying ${api.name}")
|
||||
|
||||
// Test Homepage
|
||||
val homepage = testHomepage(api, logger).success
|
||||
Assert.assertTrue("Homepage failed to load", homepage)
|
||||
|
||||
// Test Search Results
|
||||
val searchResults = testSearch(api)
|
||||
Assert.assertTrue("Failed to get search results", searchResults.success)
|
||||
searchResults as TestResultSearch
|
||||
|
||||
// Test Load and LoadLinks
|
||||
// Only try the first 3 search results to prevent spamming
|
||||
val success = searchResults.results.take(3).any { searchResponse ->
|
||||
addToLog("Testing search result: ${searchResponse.url}")
|
||||
val loadResponse = testLoad(api, searchResponse, ::addToLog)
|
||||
if (loadResponse !is TestResultLoad) {
|
||||
false
|
||||
} else {
|
||||
testLinkLoading(api, loadResponse.extractorData, ::addToLog).success
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
logger.invoke("Success ${api.name}")
|
||||
TestResultProvider(true, getLog(), null)
|
||||
} else {
|
||||
logger.invoke("Error ${api.name}")
|
||||
TestResultProvider(false, getLog(), null)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
TestResultProvider(false, getLog(), e)
|
||||
}
|
||||
callback.invoke(api, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
|
@ -214,7 +213,7 @@ object VideoDownloadManager {
|
|||
}
|
||||
|
||||
private val cachedBitmaps = hashMapOf<String, Bitmap>()
|
||||
fun Context.getImageBitmapFromUrl(url: String, headers: Map<String, String>? = null): Bitmap? {
|
||||
private fun Context.getImageBitmapFromUrl(url: String): Bitmap? {
|
||||
try {
|
||||
if (cachedBitmaps.containsKey(url)) {
|
||||
return cachedBitmaps[url]
|
||||
|
@ -222,14 +221,12 @@ object VideoDownloadManager {
|
|||
|
||||
val bitmap = GlideApp.with(this)
|
||||
.asBitmap()
|
||||
.load(GlideUrl(url) { headers ?: emptyMap() })
|
||||
.into(720, 720)
|
||||
.load(url).into(720, 720)
|
||||
.get()
|
||||
|
||||
if (bitmap != null) {
|
||||
cachedBitmaps[url] = bitmap
|
||||
}
|
||||
return bitmap
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
return null
|
||||
|
@ -429,7 +426,7 @@ object VideoDownloadManager {
|
|||
}
|
||||
|
||||
private const val reservedChars = "|\\?*<\":>+[]/\'"
|
||||
fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String {
|
||||
fun sanitizeFilename(name: String, removeSpaces: Boolean= false): String {
|
||||
var tempName = name
|
||||
for (c in reservedChars) {
|
||||
tempName = tempName.replace(c, ' ')
|
||||
|
@ -1615,7 +1612,7 @@ object VideoDownloadManager {
|
|||
.mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
|
||||
.toTypedArray()
|
||||
setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
|
||||
} catch (t: Throwable) {
|
||||
} catch (t : Throwable) {
|
||||
logError(t)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:height="24dp" android:tint="?attr/white"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,14.67L3.41,6.09L2,7.5l8.5,8.5H4v2h16v-2h-6.5l5.15,-5.15C18.91,10.95 19.2,11 19.5,11c1.38,0 2.5,-1.12 2.5,-2.5S20.88,6 19.5,6S17,7.12 17,8.5c0,0.35 0.07,0.67 0.2,0.97L12,14.67z"/>
|
||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:height="24dp" android:tint="?attr/white"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"/>
|
||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="?attr/white" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/white" android:pathData="M20.41,8.41l-4.83,-4.83C15.21,3.21 14.7,3 14.17,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V9.83C21,9.3 20.79,8.79 20.41,8.41zM7,7h7v2H7V7zM17,17H7v-2h10V17zM17,13H7v-2h10V13z"/>
|
||||
</vector>
|
|
@ -1,27 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="50"
|
||||
android:viewportHeight="50"
|
||||
android:name="vector">
|
||||
<group android:scaleX="0.1755477"
|
||||
android:scaleY="0.1755477"
|
||||
android:translateX="0"
|
||||
android:translateY="0">
|
||||
<path android:name="path"
|
||||
|
||||
android:pathData="M 245.05 148.63 C 242.249 148.627 239.463 149.052 236.79 149.89 C 235.151 141.364 230.698 133.63 224.147 127.931 C 217.597 122.233 209.321 118.893 200.65 118.45 C 195.913 105.431 186.788 94.458 174.851 87.427 C 162.914 80.396 148.893 77.735 135.21 79.905 C 121.527 82.074 109.017 88.941 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.615 148.64 23.582 151.553 18.403 156.733 C 13.223 161.912 10.31 168.945 10.31 176.27 C 10.31 183.595 13.223 190.628 18.403 195.807 C 23.582 200.987 30.615 203.9 37.94 203.9 L 245.05 203.9 C 252.375 203.9 259.408 200.987 264.587 195.807 C 269.767 190.628 272.68 183.595 272.68 176.27 C 272.68 168.945 269.767 161.912 264.587 156.733 C 259.408 151.553 252.375 148.64 245.05 148.64 Z"
|
||||
android:fillColor="#FFFFFF" android:strokeWidth="1"
|
||||
tools:ignore="VectorPath"
|
||||
android:fillAlpha="0.55"/>
|
||||
<path android:name="path_1" android:pathData="M 208.61 125 C 208.61 123.22 208.55 121.45 208.48 119.69 C 205.919 119.01 203.296 118.595 200.65 118.45 C 195.913 105.431 186.788 94.458 174.851 87.427 C 162.914 80.396 148.893 77.735 135.21 79.905 C 121.527 82.074 109.017 88.941 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.615 148.64 23.582 151.553 18.403 156.733 C 13.223 161.912 10.31 168.945 10.31 176.27 C 10.31 183.595 13.223 190.628 18.403 195.807 C 23.582 200.987 30.615 203.9 37.94 203.9 L 179 203.9 C 198.116 182.073 208.646 154.015 208.61 125 Z"
|
||||
android:fillColor="#FFFFFF" android:strokeWidth="1"
|
||||
android:fillAlpha="0.55"/>
|
||||
<path android:name="path_2" android:pathData="M 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.783 148.665 23.909 151.471 18.779 156.461 C 13.648 161.452 10.653 168.246 10.43 175.399 C 10.207 182.553 12.773 189.52 17.583 194.82 C 22.392 200.121 29.079 203.349 36.22 203.82 C 67.216 202.93 96.673 189.98 118.284 167.742 C 139.895 145.504 151.997 115.689 152 84.68 C 152 83 151.94 81.33 151.87 79.68 C 149.443 79.361 146.998 79.194 144.55 79.18 C 136.095 79.171 127.735 80.962 120.026 84.434 C 112.317 87.907 105.435 92.982 99.84 99.32 Z"
|
||||
android:fillColor="#FFFFFF" android:strokeWidth="1"
|
||||
android:fillAlpha="1"/>
|
||||
</group>
|
||||
|
||||
</vector>
|
|
@ -1,34 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Test" />
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
tools:text="Test"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/listview1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:nestedScrollingEnabled="true"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
|
||||
android:id="@+id/listview1"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:paddingTop="10dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
tools:listitem="@layout/sort_bottom_single_choice_no_checkmark" />
|
||||
tools:listitem="@layout/sort_bottom_single_choice_no_checkmark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_rowWeight="1" />
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/home_header"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -85,6 +86,7 @@
|
|||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -113,6 +115,7 @@
|
|||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
||||
android:text="@string/home_play"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24" />
|
||||
|
||||
|
@ -145,16 +148,17 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/home_watch_parent_item_title"
|
||||
|
||||
style="@style/WatchHeaderText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/home_more_info"
|
||||
android:padding="12dp"
|
||||
android:text="@string/continue_watching"
|
||||
app:drawableRightCompat="@drawable/ic_baseline_arrow_forward_24"
|
||||
app:drawableTint="?attr/white" />
|
||||
app:drawableTint="?attr/white"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/home_more_info"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/home_watch_child_recyclerview"
|
||||
|
@ -163,7 +167,7 @@
|
|||
android:clipToPadding="false"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/home_result_grid" />
|
||||
|
||||
|
@ -180,9 +184,9 @@
|
|||
<FrameLayout
|
||||
android:id="@+id/home_bookmark_parent_item_title"
|
||||
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -258,13 +262,14 @@
|
|||
</HorizontalScrollView>
|
||||
|
||||
<ImageView
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_gravity="end"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:contentDescription="@string/home_more_info"
|
||||
android:src="@drawable/ic_baseline_arrow_forward_24"
|
||||
app:drawableTint="?attr/white" />
|
||||
app:drawableTint="?attr/white"
|
||||
android:contentDescription="@string/home_more_info"/>
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -272,9 +277,10 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/home_result_grid" />
|
||||
</LinearLayout>
|
||||
|
|
|
@ -166,12 +166,12 @@
|
|||
app:layout_scrollFlags="noScroll"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicator="@drawable/indicator_background"
|
||||
app:tabIndicatorColor="?attr/white"
|
||||
app:tabIndicatorColor="@color/textColor"
|
||||
app:tabIndicatorGravity="center"
|
||||
app:tabIndicatorHeight="30dp"
|
||||
app:tabMode="scrollable"
|
||||
app:tabSelectedTextColor="?attr/primaryBlackBackground"
|
||||
app:tabSelectedTextColor="@color/lightTextColor"
|
||||
app:tabTextAppearance="@style/TabNoCaps"
|
||||
app:tabTextColor="?attr/textColor" />
|
||||
app:tabTextColor="@color/textColor" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
@ -129,9 +129,9 @@
|
|||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/result_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="100dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -326,12 +326,13 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/result_poster"
|
||||
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:contentDescription="@string/result_poster_img_des"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_gravity="bottom"
|
||||
tools:src="@drawable/example_poster" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
@ -515,8 +516,8 @@
|
|||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/result_tag"
|
||||
style="@style/ChipParent"
|
||||
android:id="@+id/result_tag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
<!--<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
|
@ -817,13 +818,10 @@
|
|||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
android:nextFocusLeft="@id/result_episode_select"
|
||||
android:nextFocusRight="@id/result_episode_select"
|
||||
android:nextFocusUp="@id/result_description"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
tools:text="Season 1"
|
||||
tools:visibility="visible" />
|
||||
|
@ -831,16 +829,16 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_episode_select"
|
||||
style="@style/MultiSelectButton"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
|
||||
android:nextFocusLeft="@id/result_season_button"
|
||||
android:nextFocusRight="@id/result_season_button"
|
||||
|
||||
android:nextFocusUp="@id/result_description"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
tools:text="50-100"
|
||||
tools:visibility="visible" />
|
||||
|
@ -848,16 +846,15 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_dub_select"
|
||||
style="@style/MultiSelectButton"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||
android:nextFocusLeft="@id/result_season_button"
|
||||
android:nextFocusRight="@id/result_season_button"
|
||||
|
||||
android:nextFocusUp="@id/result_description"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
tools:text="Dubbed"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:id="@+id/media_route_button_holder"
|
||||
android:animateLayoutChanges="true"
|
||||
android:layout_gravity="center_vertical|end">
|
||||
|
||||
<androidx.mediarouter.app.MediaRouteButton
|
||||
|
@ -70,35 +69,15 @@
|
|||
app:mediaRouteButtonTint="?attr/textColor" />
|
||||
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:nextFocusLeft="@id/result_add_sync"
|
||||
android:nextFocusRight="@id/result_share"
|
||||
|
||||
tools:visibility="visible"
|
||||
|
||||
android:id="@+id/result_subscribe"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/baseline_notifications_none_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
app:tint="?attr/textColor" />
|
||||
|
||||
<ImageView
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:nextFocusLeft="@id/result_subscribe"
|
||||
android:nextFocusRight="@id/result_open_in_browser"
|
||||
|
||||
android:id="@+id/result_share"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
|
|
|
@ -199,13 +199,17 @@
|
|||
android:id="@+id/result_back"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="10dp"
|
||||
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/go_back"
|
||||
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
app:tint="?attr/white" />
|
||||
|
@ -381,8 +385,8 @@
|
|||
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/result_tag"
|
||||
style="@style/ChipParent"
|
||||
android:id="@+id/result_tag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
@ -419,11 +423,11 @@
|
|||
|
||||
|
||||
<LinearLayout
|
||||
android:animateLayoutChanges="true"
|
||||
android:id="@+id/result_movie_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
@ -564,7 +568,6 @@
|
|||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusLeft="@id/result_movie_progress_downloaded_holder"
|
||||
android:nextFocusRight="@id/result_bookmark_button"
|
||||
android:nextFocusDown="@id/result_resume_series_button_play"
|
||||
android:text="@string/type_none"
|
||||
android:visibility="visible" />
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:backgroundTint="?attr/primaryBlackBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/provider_test_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/settings_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
android:paddingTop="@dimen/navbar_height"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:navigationIconTint="?attr/iconColor"
|
||||
app:titleTextColor="?attr/textColor"
|
||||
tools:title="@string/category_provider_test">
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<com.lagradost.cloudstream3.ui.settings.testing.TestView
|
||||
android:id="@+id/provider_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusDown="@id/provider_test_recycler_view"
|
||||
app:header_text="@string/category_provider_test"
|
||||
app:layout_scrollFlags="scroll|enterAlways" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/provider_test_recycler_view"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="60dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/provider_test_item" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -23,7 +23,9 @@
|
|||
android:nextFocusUp="@id/home_child_more_info"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:clipToPadding="false"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:id="@+id/home_child_recyclerview"
|
||||
android:orientation="horizontal"
|
||||
|
|
|
@ -2,30 +2,33 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_marginStart="@dimen/navbar_width"
|
||||
android:id="@+id/home_child_more_info"
|
||||
style="@style/WatchHeaderText"
|
||||
android:padding="12dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/navbar_width"
|
||||
style="@style/WatchHeaderText"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:padding="12dp"
|
||||
tools:text="Trending" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:nextFocusUp="@id/home_child_more_info"
|
||||
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingStart="@dimen/navbar_width"
|
||||
android:clipToPadding="false"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:id="@+id/home_child_recyclerview"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:nextFocusUp="@id/home_child_more_info"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/navbar_width"
|
||||
android:paddingEnd="5dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/home_result_grid" />
|
||||
</LinearLayout>
|
|
@ -44,7 +44,6 @@
|
|||
android:nextFocusLeft="@id/sort_subtitles"
|
||||
android:nextFocusRight="@id/apply_btt"
|
||||
android:requiresFadingEdge="vertical"
|
||||
tools:layout_height="100dp"
|
||||
tools:listitem="@layout/sort_bottom_single_choice" />
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -118,7 +117,6 @@
|
|||
android:nextFocusLeft="@id/sort_providers"
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:requiresFadingEdge="vertical"
|
||||
tools:layout_height="200dp"
|
||||
tools:listfooter="@layout/sort_bottom_footer_add_choice"
|
||||
tools:listitem="@layout/sort_bottom_single_choice" />
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:nextFocusRight="@id/action_button"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="16sp"
|
||||
tools:text="Test repository" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lang_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="5dp"
|
||||
tools:text="🇷🇼"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/passed_failed_marker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Failed"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fail_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:text="Unable to load videos"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginStart="10dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/baseline_text_snippet_24"
|
||||
app:tint="?attr/white" />
|
||||
</LinearLayout>
|
|
@ -23,7 +23,7 @@
|
|||
android:elevation="10dp"
|
||||
app:cardBackgroundColor="?attr/primaryGrayBackground"
|
||||
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_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?attr/primaryBlackBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_test_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/outline_drawable_less"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_test_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="17sp"
|
||||
tools:text="Homepage test" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_test_section_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textSize="17sp"
|
||||
tools:text="67 / 120 " />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
app:cardBackgroundColor="?attr/primaryGrayBackground"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/passed_test_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/outline_drawable_less"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Tests passed"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/passed_test_section_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textSize="17sp"
|
||||
tools:text="55" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/failed_test_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/outline_drawable_less"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Tests failed"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/failed_test_section_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textSize="17sp"
|
||||
tools:text="12" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/tests_play_pause"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:text="@string/start"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24">
|
||||
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/test_total_progress"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="5dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="-1.5dp"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
android:progressTint="?attr/colorPrimary"
|
||||
android:visibility="gone"
|
||||
tools:progress="50" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -519,23 +519,6 @@
|
|||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_player" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_test_providers"
|
||||
android:name="com.lagradost.cloudstream3.ui.settings.testing.TestFragment"
|
||||
android:layout_height="match_parent"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_testing">
|
||||
<action
|
||||
android:id="@+id/action_navigation_global_to_navigation_test_providers"
|
||||
app:destination="@id/navigation_test_providers"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_setup_language"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">ملصق</string>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<string name="action_open_watching">مزيد من المعلومات</string>
|
||||
<string name="vpn_might_be_needed">قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح</string>
|
||||
<string name="vpn_torrent">هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة</string>
|
||||
<string name="provider_info_meta">لا يتم توفير البيانات الوصفية بواسطة الموقع، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع.</string>
|
||||
<string name="provider_info_meta">لا يتم توفير البيانات الوصفية بواسطة الموقع ، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع.</string>
|
||||
<string name="torrent_plot">الوصف</string>
|
||||
<string name="normal_no_plot">لم يتم العثور على وصف</string>
|
||||
<string name="torrent_no_plot">لم يتم العثور على وصف</string>
|
||||
|
@ -119,16 +119,16 @@
|
|||
<string name="eigengraumode_settings">وضع إيغنغرافي</string>
|
||||
<string name="eigengraumode_settings_des">يضيف خيار السرعة في المُشغل</string>
|
||||
<string name="swipe_to_seek_settings">السحب لتقديم</string>
|
||||
<string name="swipe_to_seek_settings_des">اسحب من جانب إلى آخر للتحكم في موضعك في مقطع فيديو</string>
|
||||
<string name="swipe_to_seek_settings_des">إسحب إلى اليسار أو اليمين للتحكم في الوقت في مُشغل الفيديو</string>
|
||||
<string name="swipe_to_change_settings">السحب لتغيير الإعدادات</string>
|
||||
<string name="swipe_to_change_settings_des">مرر لأعلى أو لأسفل على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت</string>
|
||||
<string name="swipe_to_change_settings_des">إسحب على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت</string>
|
||||
<string name="autoplay_next_settings">تشغيل الحلقة التالية تلقائيًا</string>
|
||||
<string name="autoplay_next_settings_des">تبدأ الحلقة التالية عندما تنتهي الحالية</string>
|
||||
<string name="double_tap_to_seek_settings">النقر مرتان للتقديم للأمام أو للخلف</string>
|
||||
<string name="double_tap_to_pause_settings">الضغط مرتان لإيقاف مؤقت</string>
|
||||
<string name="double_tap_to_seek_amount_settings">التحكم في مدى تقديم المُشغل(ثوان)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">التحكم في مدى تقديم المُشغل</string>
|
||||
<string name="double_tap_to_seek_settings_des">إضغط مرتين على الجانب الأيمن أو الأيسر للتقديم للأمام أو للخلف</string>
|
||||
<string name="double_tap_to_pause_settings_des">اضغط مرتين في المنتصف للتوقف</string>
|
||||
<string name="double_tap_to_pause_settings_des">إضغط في الوسط لإيقاف مؤقت</string>
|
||||
<string name="use_system_brightness_settings">استخدم سطوع النظام</string>
|
||||
<string name="use_system_brightness_settings_des">استخدم سطوع النظام في مُشغل التطبيق بدلاً من التراكب الداكن</string>
|
||||
<string name="episode_sync_settings">تحديث تقدم المشاهدة</string>
|
||||
|
@ -155,7 +155,7 @@
|
|||
<string name="automatic_plugin_updates">تحديث الإضافات تلقائيًا</string>
|
||||
<string name="automatic_plugin_download">تنزيل الإضافات تلقائيًا</string>
|
||||
<string name="updates_settings">التحديث التلقائي</string>
|
||||
<string name="updates_settings_des">ابحث تلقائيا عن التحديثات الجديدة بعد بدء التطبيق.</string>
|
||||
<string name="updates_settings_des">البحث تلقائيًا عن التحديثات الجديدة عند البداية</string>
|
||||
<string name="uprereleases_settings">التحديث إلى الاصدارات التجريبية (بيتا)</string>
|
||||
<string name="uprereleases_settings_des">البحث عن التحديثات التجريبية بدلاً من الإصدارات الكاملة فقط</string>
|
||||
<string name="github">غيت هاب</string>
|
||||
|
@ -170,7 +170,7 @@
|
|||
<string name="copy_link_toast">تم نسخ الرابط إلى الحافظة</string>
|
||||
<string name="play_episode_toast">تشغيل الحلقة</string>
|
||||
<string name="subs_default_reset_toast">إعادة التعيين إلى القيمة الافتراضية</string>
|
||||
<string name="acra_report_toast">عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين</string>
|
||||
<string name="acra_report_toast">عذرا ، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين</string>
|
||||
<string name="season">موسم</string>
|
||||
<string name="no_season">لا موسم</string>
|
||||
<string name="episode">حلقة</string>
|
||||
|
@ -218,8 +218,8 @@
|
|||
<string name="movies_singular">فيلم</string>
|
||||
<string name="tv_series_singular">مسلسل</string>
|
||||
<string name="cartoons_singular">كرتون</string>
|
||||
<string name="anime_singular">أنيمي</string>
|
||||
<string name="ova_singular">أوفا</string>
|
||||
<string name="anime_singular">أنمي</string>
|
||||
<string name="ova_singular">اوفا</string>
|
||||
<string name="torrent_singular">تورنت</string>
|
||||
<string name="documentaries_singular">وثائقي</string>
|
||||
<string name="asian_drama_singular">دراما آسيوية</string>
|
||||
|
@ -259,15 +259,15 @@
|
|||
<string name="dont_show_again">لا تظهر مرة أخرى</string>
|
||||
<string name="skip_update">تخطي هذا التحديث</string>
|
||||
<string name="update">تحديث</string>
|
||||
<string name="watch_quality_pref">جودة المشاهدة المفضلة (WiFi)</string>
|
||||
<string name="watch_quality_pref">جودة المشاهدة المفضلة</string>
|
||||
<string name="limit_title">أقصى عدد لحروف عنوان مُشغل الفيديو</string>
|
||||
<string name="limit_title_rez">أبعاد مُشغل الفيديو</string>
|
||||
<string name="video_buffer_size_settings">حجم ذاكرة التخزين المؤقت للفيديو</string>
|
||||
<string name="video_buffer_length_settings">طول التخزين المؤقت</string>
|
||||
<string name="video_buffer_disk_settings">التخزين المؤقت للفيديو على القرص</string>
|
||||
<string name="video_buffer_clear_settings">مسح التخزين المؤقت للصورة والفيديو</string>
|
||||
<string name="video_ram_description">يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة، مثل تلفزيون أندرويد.</string>
|
||||
<string name="video_disk_description">يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة، مثل تلفزيون أندرويد.</string>
|
||||
<string name="video_ram_description">يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة ، مثل Android TV.</string>
|
||||
<string name="video_disk_description">يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة ، مثل Android TV.</string>
|
||||
<string name="dns_pref">إستخدام DNS بدلا من HTTPS</string>
|
||||
<string name="dns_pref_summary">مفيد لتجاوز حجب مزود خدمة الإنترنت</string>
|
||||
<string name="add_site_pref">موقع بديل (نسخة)</string>
|
||||
|
@ -284,7 +284,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">عام</string>
|
||||
<string name="random_button_settings">زر العشوائي</string>
|
||||
<string name="random_button_settings_desc">يظهر الزر على الصفحة الرئيسية والذي يمكنه اختيار فيلم عشوائي أو مسلسل تلفزيوني من الصفحة الرئيسية</string>
|
||||
<string name="random_button_settings_desc">إظهار زر العشوائي على الصفحة الرئيسية</string>
|
||||
<string name="provider_lang_settings">لغات المزود</string>
|
||||
<string name="app_layout">واجهة التطبيق</string>
|
||||
<string name="preferred_media_settings">المحتوى المفضل</string>
|
||||
|
@ -342,7 +342,7 @@
|
|||
<string name="all">الكل</string>
|
||||
<string name="max">الحد الاقصي</string>
|
||||
<string name="min">الحد الأدنى</string>
|
||||
<string name="subtitles_none" translatable="false">@string/none</string>
|
||||
<string name="subtitles_none" translatable="false">\@string/none</string>
|
||||
<string name="subtitles_outline">الخطوط المحيطة</string>
|
||||
<string name="subtitles_depressed">النمط المنخفض</string>
|
||||
<string name="subtitles_shadow">ظل</string>
|
||||
|
@ -360,7 +360,7 @@
|
|||
https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog
|
||||
-->
|
||||
<string name="subtitles_example_text">نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق</string>
|
||||
<string name="recommended">مُوصى به</string>
|
||||
<string name="recommended">مُوصي به</string>
|
||||
<string name="player_loaded_subtitles" formatted="true">تم تحميل %s</string>
|
||||
<string name="player_load_subtitles">إختيار ملف</string>
|
||||
<string name="player_load_subtitles_online">تحميل من الانترنت</string>
|
||||
|
@ -521,44 +521,4 @@
|
|||
<string name="update_started">بدأ التحديث</string>
|
||||
<string name="plugin_downloaded">تم تنزيل الإضافة</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>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string formatted="true" name="app_dub_sub_episode_text_format">%s еп. %d</string>
|
||||
<string formatted="true" name="cast_format">Актьори: %s</string>
|
||||
|
@ -9,6 +8,7 @@
|
|||
<string formatted="true" name="next_episode_time_hour_format">%dh %dm</string>
|
||||
<string formatted="true" name="next_episode_time_min_format">%dm</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="home_main_poster_img_des">Main Poster</string>
|
||||
<string name="home_next_random_img_des">Следващ произволен</string>
|
||||
|
@ -106,7 +106,7 @@
|
|||
<string name="continue_watching">Продължете да гледате</string>
|
||||
<string name="action_remove_watching">Премахване</string>
|
||||
<string name="action_open_watching">Повече информация</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Може да е необходим VPN, за да работи правилно този доставчик</string>
|
||||
<string name="vpn_torrent">Този доставчик е торент, препоръчва се VPN</string>
|
||||
<string name="provider_info_meta">Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта.</string>
|
||||
|
@ -224,8 +224,8 @@
|
|||
<string name="movies_singular">Филм</string>
|
||||
<string name="tv_series_singular">Серия</string>
|
||||
<string name="cartoons_singular">Анимационен филм</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Торент</string>
|
||||
<string name="documentaries_singular">Документален филм</string>
|
||||
<string name="asian_drama_singular">Азиатска драма</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="result_poster_img_des">পোস্টার</string>
|
||||
<string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string>
|
||||
|
@ -143,8 +143,8 @@
|
|||
<string name="category_updates">হালনাগাদ ও ব্যাকআপ</string>
|
||||
<string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string>
|
||||
<string name="swipe_to_seek_settings">খুঁজতে সোয়াইপ করুন</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="double_tap_to_seek_settings">আগাতে ডবল ট্যাপ করুন</string>
|
||||
<string name="eigengraumode_settings">আইজেনগ্রাভি মোড</string>
|
||||
<string name="update_started">আপডেট শুরু হয়েছে</string>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- KEYS DON'T TRANSLATE -->
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
|
@ -11,7 +10,7 @@
|
|||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Episode Poster</string>
|
||||
<string name="home_main_poster_img_des">Main Poster</string>
|
||||
<string name="home_next_random_img_des">Next Random</string>
|
||||
|
@ -109,7 +108,7 @@
|
|||
<string name="continue_watching">Continue Assistindo</string>
|
||||
<string name="action_remove_watching">Remover</string>
|
||||
<string name="action_open_watching">Mais Info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Uma VPN pode ser necessária para esse fornecedor funcionar corretamente</string>
|
||||
<string name="vpn_torrent">Esse fornecedor é um torrent, uma VPN é recomendada</string>
|
||||
<string name="provider_info_meta">Metadados não são oferecidas pelo site, o carregamento do video pode falhar se ele não existir no site.</string>
|
||||
|
@ -223,8 +222,8 @@
|
|||
<string name="movies_singular">Filme</string>
|
||||
<string name="tv_series_singular">Série</string>
|
||||
<string name="cartoons_singular">Desenho Animado</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Documentário</string>
|
||||
<string name="asian_drama_singular">Drama Asiático</string>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- KEYS DON'T TRANSLATE -->
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<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 -->
|
||||
<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="home_main_poster_img_des">Main Poster</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="action_remove_watching">Odebrat</string>
|
||||
<string name="action_open_watching">Další informace</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Aby tento poskytovatel fungoval správně, budete možná potřebovat VPN</string>
|
||||
<string name="vpn_torrent">Tento poskytovatel je torrent, je doporučená VPN</string>
|
||||
<string name="provider_info_meta">Web neposkytnul žádná metadata, načítání videa selže, pokud na webu neexistuje.</string>
|
||||
|
@ -123,14 +122,14 @@
|
|||
<string name="eigengraumode_settings">Rychlostní režim</string>
|
||||
<string name="eigengraumode_settings_des">Přidá do přehrávače možnost rychlosti</string>
|
||||
<string name="swipe_to_seek_settings">Přejet pro posun</string>
|
||||
<string name="swipe_to_seek_settings_des">Přejeďte prstem ze strany na stranu pro ovládání své pozice ve videu</string>
|
||||
<string name="swipe_to_seek_settings_des">Přejeďte prstem vlevo nebo vpravo pro ovládání času v přehrávači</string>
|
||||
<string name="swipe_to_change_settings">Přejet pro změnu nastavení</string>
|
||||
<string name="swipe_to_change_settings_des">Přejeďte prstem nahoru nebo dolů na levé nebo pravé straně pro změnu jasu nebo hlasitosti</string>
|
||||
<string name="swipe_to_change_settings_des">Přejeďte prstem na levé nebo pravé straně pro změnu jasu nebo hlasitosti</string>
|
||||
<string name="double_tap_to_seek_settings">Dvojité klepnutí pro posun</string>
|
||||
<string name="double_tap_to_pause_settings">Dvojité klepnutí pro pozastavení</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Množství času k posunu (sekundy)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Množství času k posunu</string>
|
||||
<string name="double_tap_to_seek_settings_des">Klepněte dvakrát vpravo nebo vlevo pro posun vpřed nebo vzad</string>
|
||||
<string name="double_tap_to_pause_settings_des">Klepněte dvakrát doprostřed pro pozastavení</string>
|
||||
<string name="double_tap_to_pause_settings_des">Klepněte doprostřed pro pozastavení</string>
|
||||
<string name="use_system_brightness_settings">Použít systémový jas</string>
|
||||
<string name="use_system_brightness_settings_des">V přehrávači použít systémov překrytí</string>
|
||||
<string name="episode_sync_settings">Aktualizovat postup sledování</string>
|
||||
|
@ -139,8 +138,8 @@
|
|||
<string name="backup_settings">Zálohovat data</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="backup_success">Data uložena</string>
|
||||
<string name="backup_failed">Chybí oprávnění k úložišti. Zkuste to prosím znovu.</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_error_format">Chyba při zálohování %s</string>
|
||||
<string name="search">Search</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="show_fillers_settings">Zobrazit výplňové epizody u anime</string>
|
||||
<string name="updates_settings">Zobrazit aktualizace aplikace</string>
|
||||
<string name="updates_settings_des">Při spuštění aplikace automaticky zkontrolovat nové aktualizace.</string>
|
||||
<string name="updates_settings_des">Při spuštění automaticky zkontrolovat nové aktualizace</string>
|
||||
<string name="uprereleases_settings">Aktualizovat na předběžná vydání</string>
|
||||
<string name="uprereleases_settings_des">Kontrolovat aktualizace předběžných vydání, místo normálních plných vydání</string>
|
||||
<string name="github">GitHub</string>
|
||||
|
@ -212,8 +211,8 @@
|
|||
<string name="movies_singular">Film</string>
|
||||
<string name="tv_series_singular">Seriál</string>
|
||||
<string name="cartoons_singular">Animovaný</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Dokument</string>
|
||||
<string name="asian_drama_singular">Asijské drama</string>
|
||||
|
@ -246,19 +245,19 @@
|
|||
<string name="dont_show_again">Již nezobrazovat</string>
|
||||
<string name="skip_update">Přeskočit tuto aktualizace</string>
|
||||
<string name="update">Aktualizovat</string>
|
||||
<string name="watch_quality_pref">Upřednostněná kvalita sledování (WiFi)</string>
|
||||
<string name="watch_quality_pref">Upřednostněná kvalita sledování</string>
|
||||
<string name="limit_title">Maximální počet znaků v názvu přehrávače</string>
|
||||
<string name="limit_title_rez">Rozlišení přehrávače</string>
|
||||
<string name="video_buffer_size_settings">Velikost vyrovnávací paměti videa</string>
|
||||
<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_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_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_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">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_summary">Užitečné pro obcházení blokací ISP</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="resize_fit">Vyplnit na obrazovku</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="category_general">Obecné</string>
|
||||
<string name="random_button_settings">Náhodné tlačítko</string>
|
||||
<string name="random_button_settings_desc">Zobrazit na domovské stránce tlačítko, kterým lze vybrat náhodný film nebo seriál z domovské stránky</string>
|
||||
<string name="random_button_settings_desc">Zobrazit na domovské stránce náhodné tlačítko</string>
|
||||
<string name="provider_lang_settings">Jazyk poskytovatelů</string>
|
||||
<string name="app_layout">Rozložení aplikace</string>
|
||||
<string name="preferred_media_settings">Preferovaná média</string>
|
||||
|
@ -321,10 +320,10 @@
|
|||
<string name="subtitles_shadow">Stín</string>
|
||||
<string name="subtitles_raised">Vyvýšené</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_extra_hint_later_format">Toto použijte, pokud jsou titulky zobrazeny o %d ms 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_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 %dms později</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,
|
||||
|
@ -371,187 +370,4 @@
|
|||
<string name="extras">Extra</string>
|
||||
<string name="action_mark_as_watched">Označit jako zhlédnuté</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>
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Besetzung: %s</string>
|
||||
<string name="next_episode_format" formatted="true">Episode %d wird veröffentlicht in</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="restore_failed_format" formatted="true">Wiederherstellung der Daten aus der Datei %s fehlgeschlagen</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="no_data">Keine Daten vorhanden</string>
|
||||
<string name="episode_more_options_des">Mehr Optionen</string>
|
||||
<string name="next_episode">Nächste Episode</string>
|
||||
<string name="next_episode">Nächste Epsisode</string>
|
||||
<string name="result_tags">Genres</string>
|
||||
<string name="result_share">Teilen</string>
|
||||
<string name="result_open_in_browser">In Browser öffnen</string>
|
||||
|
@ -53,7 +53,7 @@
|
|||
<string name="type_dropped">Abgebrochen</string>
|
||||
<string name="type_plan_to_watch">Geplant</string>
|
||||
<string name="type_none">Nichts</string>
|
||||
<string name="type_re_watching">Erneut schauen</string>
|
||||
<string name="type_re_watching">Erneut anschauen</string>
|
||||
<string name="play_movie_button">Film abspielen</string>
|
||||
<string name="play_livestream_button">Livestream abspielen</string>
|
||||
<string name="play_torrent_button">Torrent streamen</string>
|
||||
|
@ -115,7 +115,7 @@
|
|||
<string name="continue_watching">Weiterschauen</string>
|
||||
<string name="action_remove_watching">Entfernen</string>
|
||||
<string name="action_open_watching">Mehr Infos</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Damit dieser Anbieter korrekt funktioniert, ist möglicherweise ein VPN erforderlich</string>
|
||||
<string name="vpn_torrent">Dieser Anbieter bietet Torrents an, ein VPN wird dringend empfohlen</string>
|
||||
<string name="provider_info_meta">Metadaten werden nicht von der Website bereitgestellt, das Laden des Videos schlägt fehl, wenn sie auf der Website nicht vorhanden sind.</string>
|
||||
|
@ -136,14 +136,14 @@
|
|||
<string name="swipe_to_seek_settings">Wischen zum vor- und zurückspulen</string>
|
||||
<string name="swipe_to_seek_settings_des">Nach links oder rechts wischen, um die Zeit im Videoplayer zu steuern</string>
|
||||
<string name="swipe_to_change_settings">Wischen, um Einstellungen zu ändern</string>
|
||||
<string name="swipe_to_change_settings_des">Links oder rechts nach oben oder unten wischen, um die Helligkeit oder Lautstärke zu ändern</string>
|
||||
<string name="swipe_to_change_settings_des">Links oder rechts wischen, um die Helligkeit oder Lautstärke zu ändern</string>
|
||||
<string name="autoplay_next_settings">Nächste Episode automatisch abspielen</string>
|
||||
<string name="autoplay_next_settings_des">Nächste Episode wird gestartet, sobald die aktuelle Episode endet</string>
|
||||
<string name="double_tap_to_seek_settings">Doppeltippen zum vor- und zurückspulen</string>
|
||||
<string name="double_tap_to_pause_settings">Doppeltippen zum Pausieren</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Zeit für vor- und zurückspulen im Player (Sekunden)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Zeit für vor- und zurückspulen im Player</string>
|
||||
<string name="double_tap_to_seek_settings_des">Zweimal auf die rechte oder linke Seite tippen, um vor- oder zurückzuspulen</string>
|
||||
<string name="double_tap_to_pause_settings_des">Doppelt in die Mitte tippen, um zu pausieren</string>
|
||||
<string name="double_tap_to_pause_settings_des">In die Mitte tippen, um zu pausieren</string>
|
||||
<string name="use_system_brightness_settings">Systemhelligkeit verwenden</string>
|
||||
<string name="use_system_brightness_settings_des">Systemhelligkeit anstelle eines dunklen Overlay im Player verwenden</string>
|
||||
<string name="episode_sync_settings">Episodenfortschritt aktualisieren</string>
|
||||
|
@ -166,7 +166,7 @@
|
|||
<string name="pref_filter_search_quality">Ausgewählte Videoqualität bei Suchergebnissen ausblenden</string>
|
||||
<string name="automatic_plugin_updates">Automatische Plugin-Updates</string>
|
||||
<string name="updates_settings">App-Updates anzeigen</string>
|
||||
<string name="updates_settings_des">Automatisches Suchen nach neuen Updates nach dem Start</string>
|
||||
<string name="updates_settings_des">Automatisches Suchen nach neuen Updates beim Start</string>
|
||||
<string name="uprereleases_settings">Auf Vorabversionen updaten</string>
|
||||
<string name="uprereleases_settings_des">Suche nach Vorabversionen statt nur nach Vollversionen</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -212,7 +212,7 @@
|
|||
<string name="no_subtitles">Keine Untertitel</string>
|
||||
<string name="default_subtitles">Standard</string>
|
||||
<string name="free_storage">Frei</string>
|
||||
<string name="used_storage">Belegt</string>
|
||||
<string name="used_storage">Benutzt</string>
|
||||
<string name="app_storage">App</string>
|
||||
<string name="movies">Filme</string>
|
||||
<string name="tv_series">TV-Serien</string>
|
||||
|
@ -228,8 +228,8 @@
|
|||
<string name="movies_singular">Film</string>
|
||||
<string name="tv_series_singular">Serie</string>
|
||||
<string name="cartoons_singular">Trickfilm</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Dokumentation</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_copy_link">Link kopieren</string>
|
||||
<string name="episode_action_auto_download">Auto-Download</string>
|
||||
<string name="episode_action_download_mirror">Alternativer Download</string>
|
||||
<string name="episode_action_download_mirror">Download-Mirror</string>
|
||||
<string name="episode_action_reload_links">Links neu laden</string>
|
||||
<string name="episode_action_download_subtitle">Untertitel herunterladen</string>
|
||||
<string name="show_hd">Qualitätsanzeige</string>
|
||||
|
@ -263,7 +263,7 @@
|
|||
<string name="dont_show_again">Nicht mehr anzeigen</string>
|
||||
<string name="skip_update">Update ignorieren</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="watch_quality_pref">Bevorzugte Videoqualität (WLAN)</string>
|
||||
<string name="watch_quality_pref">Bevorzugte Auflösung</string>
|
||||
<string name="limit_title">Videoplayertitel max. Zeichen</string>
|
||||
<string name="limit_title_rez">Videoplayer Auflösung</string>
|
||||
<string name="video_buffer_size_settings">Videopuffergröße</string>
|
||||
|
@ -284,9 +284,9 @@
|
|||
<string name="resize_fill">Strecken</string>
|
||||
<string name="resize_zoom">Vergrößern</string>
|
||||
<string name="legal_notice">Haftungsausschluss</string>
|
||||
<string name="category_general">Allgemein</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="random_button_settings">Zufalls-Button</string>
|
||||
<string name="random_button_settings_desc">Zeigt einen Zufallsbutton auf der Startseite an, mit welchem eine Serie oder ein Film von der Website zufällig ausgewählt wird</string>
|
||||
<string name="random_button_settings_desc">Zufallsbutton auf der Startseite anzeigen</string>
|
||||
<string name="provider_lang_settings">Anbieter-Sprachen</string>
|
||||
<string name="app_layout">App-Layout</string>
|
||||
<string name="preferred_media_settings">Bevorzugte Medien</string>
|
||||
|
@ -460,11 +460,11 @@
|
|||
<string name="automatic_plugin_download_summary">Automatische Installation aller noch nicht installierten Plugins aus hinzugefügten Repositories.</string>
|
||||
<string name="redo_setup_process">Einrichtungsvorgang wiederholen</string>
|
||||
<string name="apk_installer_settings">APK-Installer</string>
|
||||
<string name="apk_installer_settings_des">Einige Telefone unterstützen den neuen Package-Installer nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen.</string>
|
||||
<string name="apk_installer_settings_des">Einige Telefone unterstützen das neue Installationsprogramm für Pakete nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen.</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="pref_category_links">Links</string>
|
||||
<string name="pref_category_app_updates">App-Updates</string>
|
||||
<string name="pref_category_backup">Sicherung</string>
|
||||
<string name="pref_category_backup">Back-Up</string>
|
||||
<string name="pref_category_extensions">Erweiterungen</string>
|
||||
<string name="pref_category_actions">Wartung</string>
|
||||
<string name="pref_category_cache">Cache</string>
|
||||
|
@ -489,44 +489,4 @@
|
|||
<string name="delayed_update_notice">Die Anwendung wird beim Beenden aktualisiert</string>
|
||||
<string name="plugin_downloaded">Das Plugin wurde heruntergeladen</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>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="title_home">Αρχική</string>
|
||||
<string name="title_search">Αναζήτηση</string>
|
||||
|
@ -151,7 +150,7 @@
|
|||
<string name="episodes">Επεισόδια</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="season_short">Σ</string>
|
||||
<string name="season_short">Κ</string>
|
||||
<string name="episode_short">E</string>
|
||||
<string name="no_episodes_found">Δεν βρέθηκαν επεισόδια</string>
|
||||
<string name="delete_file">Διαγραφή αρχείου</string>
|
||||
|
@ -315,7 +314,7 @@
|
|||
<string name="crash_reporting_title">Αναφορά κατάρρευσης</string>
|
||||
<string name="preferred_media_subtext">Τι θα θέλατε να δείτε</string>
|
||||
<string name="setup_done">Έγινε</string>
|
||||
<string name="extensions">Extensions</string>
|
||||
<string name="extensions">Πρόσθετα</string>
|
||||
<string name="add_repository">Προσθήκη αποθετηρίου</string>
|
||||
<string name="repository_name_hint">Όνομα αποθετηρίου</string>
|
||||
<string name="repository_url_hint">Σύνδεσμος αποθετηρίου</string>
|
||||
|
@ -388,7 +387,7 @@
|
|||
<string name="sort_close">Κλείσιμο</string>
|
||||
<string name="sort_clear">Εκκαθάριση</string>
|
||||
<string name="subs_subtitle_languages">Γλώσσα υποτίτλων</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="provider_info_meta">Δεν έχουν παρασχεθεί μεταδεδομένα από τον ιστότοπο, η φόρτωση του βίντεο θα αποτύχει αν δεν υπάρχει στον ιστότοπο.</string>
|
||||
<string name="double_tap_to_pause_settings">Διπλό πάτημα για παύση</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Μέγεθος αναζήτησης στο πρόγραμμα αναπαραγωγής</string>
|
||||
|
@ -453,7 +452,7 @@
|
|||
<string name="skip_type_mixed_ed">Ανάμεικτοι τίτλοι τέλους</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="rating">Κριτική</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="pref_category_app_updates">Ενημερώσεις εφαρμογής</string>
|
||||
<string name="pref_category_backup">Αντίγραφο ασφαλείας</string>
|
||||
<string name="pref_category_extensions">Extensions</string>
|
||||
|
@ -465,7 +464,7 @@
|
|||
<string name="pref_category_defaults">Προεπιλεγμένα</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="subs_font_size">Μέγεθος γραμματοσειράς</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="pref_category_links">Σύνδεσμοι</string>
|
||||
<string name="pref_category_looks">Εμφάνιση</string>
|
||||
<string name="pref_category_ui_features">Χαρακτηριστικά</string>
|
||||
|
@ -491,22 +490,4 @@
|
|||
<string name="plugin_downloaded">Το πρόσθετο κατέβει</string>
|
||||
<string name="update_started">Ενημέρωση ξεκίνησε</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>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="go_back_img_des">Reen</string>
|
||||
<string name="title_home">Hejmo</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pref_category_extensions">Extensiones</string>
|
||||
<string name="setup_extensions_subtext">Descargue la lista de sitios que quiera utilizar</string>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<string name="pref_filter_search_quality">Ocultar la calidad de video en los resultados de búsqueda</string>
|
||||
<string name="pref_category_player_layout">Diseño</string>
|
||||
<string name="category_ui">Diseño</string>
|
||||
<string name="watch_quality_pref">Calidad de visualización preferida (WiFi)</string>
|
||||
<string name="watch_quality_pref">Calidad de visualización preferida</string>
|
||||
<string name="player_pref">Reproductor de video preferido</string>
|
||||
<string name="emulator_layout">Diseño para emulador</string>
|
||||
<string name="app_layout">Diseño de la aplicación</string>
|
||||
|
@ -51,10 +51,10 @@
|
|||
<string name="subtitles_raised">Elevado</string>
|
||||
<string name="subtitle_offset_extra_hint_later_format">Use esto si los subtítulos se muestran %d ms muy pronto</string>
|
||||
<string name="subtitle_offset_extra_hint_before_format">Use esto si los subtítulos se muestran %d ms tarde</string>
|
||||
<string name="swipe_to_seek_settings_des">Desliza el dedo de lado a lado para controlar la posición en un video</string>
|
||||
<string name="swipe_to_seek_settings_des">Desliza el dedo hacia la izquierda o hacia la derecha para controlar el tiempo en el reproductor de video</string>
|
||||
<string name="subtitles_filter_lang">Filtrar por idioma de medios preferido</string>
|
||||
<string name="subtitles_remove_captions">Eliminar Closed Captions (CC) de los subtítulos</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Cantidad de búsquedas del reproductor (segundos)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Cantidad de tiempo de búsqueda en el reproductor (en segundos)</string>
|
||||
<string name="use_system_brightness_settings_des">Use el brillo del sistema en el reproductor de la app en lugar de una superposición oscura</string>
|
||||
<string name="limit_title_rez">Resolución del reproductor de video</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
|
@ -94,7 +94,7 @@
|
|||
<string name="home_main_poster_img_des">Poster Principal</string>
|
||||
<string name="app_language">Idioma de la aplicación</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="all_languages_preference">Todos los Idiomas</string>
|
||||
<string name="go_back_img_des">Volver</string>
|
||||
|
@ -194,7 +194,7 @@
|
|||
<string name="continue_watching">Continuar Viendo</string>
|
||||
<string name="action_remove_watching">Remover</string>
|
||||
<string name="action_open_watching">Más info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Una VPN puede ser necesaria para que este proveedor funcione correctamente</string>
|
||||
<string name="vpn_torrent">Este proveedor es un torrent, se recomienda una VPN</string>
|
||||
<string name="provider_info_meta">El sitio no proporciona los metadatos, la carga del video fallará si no existe en el sitio.</string>
|
||||
|
@ -205,16 +205,16 @@
|
|||
<string name="eigengraumode_settings">Modo Eigengravy</string>
|
||||
<string name="swipe_to_seek_settings">Deslice para avanzar/retroceder</string>
|
||||
<string name="swipe_to_change_settings">Deslice para cambiar la configuración</string>
|
||||
<string name="swipe_to_change_settings_des">Deslice hacia arriba o hacia abajo en el lado izquierdo o derecho para cambiar el brillo o el volumen</string>
|
||||
<string name="swipe_to_change_settings_des">Deslice el dedo hacia la izquierda o hacia la derecha para cambiar el brillo o el volumen</string>
|
||||
<string name="double_tap_to_seek_settings">Toca dos veces para buscar</string>
|
||||
<string name="double_tap_to_pause_settings">Tocar dos veces para pausar</string>
|
||||
<string name="double_tap_to_seek_settings_des">Toque dos veces en el lado derecho o izquierdo para buscar hacia adelante o hacia atrás</string>
|
||||
<string name="double_tap_to_pause_settings_des">Toque dos veces en el medio para hacer una pausa</string>
|
||||
<string name="double_tap_to_pause_settings_des">Toque en el medio para pausar</string>
|
||||
<string name="use_system_brightness_settings">Usar brillo del sistema</string>
|
||||
<string name="restore_settings">Restaurar datos desde el backup</string>
|
||||
<string name="backup_settings">Hacer copia de los datos (backup)</string>
|
||||
<string name="restore_success">Archivo de backup cargado</string>
|
||||
<string name="updates_settings_des">Busque automáticamente nuevas actualizaciones después de iniciar la aplicación.</string>
|
||||
<string name="updates_settings_des">Buscar automáticamente nuevas actualizaciones al inicio</string>
|
||||
<string name="redo_setup_process">Rehacer el proceso de configuración inicial</string>
|
||||
<string name="show_fillers_settings">Mostrar episodio de relleno para Anime</string>
|
||||
<string name="play_episode_toast">Reproducir Episodio</string>
|
||||
|
@ -306,7 +306,7 @@
|
|||
<string name="pref_category_looks">Aspecto</string>
|
||||
<string name="pref_category_ui_features">Características</string>
|
||||
<string name="random_button_settings">Botón de Al azar</string>
|
||||
<string name="random_button_settings_desc">Muestra un botón de reproducción \"al azar\" en la página de inicio para poelículas y series</string>
|
||||
<string name="random_button_settings_desc">Muestra un botón de reproducción \"al azar\" en la página de inicio</string>
|
||||
<string name="account">cuenta</string>
|
||||
<string name="logout">Cerrar sesión</string>
|
||||
<string name="switch_account">Cambiar cuenta</string>
|
||||
|
@ -363,8 +363,8 @@
|
|||
<string name="movies_singular">Película</string>
|
||||
<string name="tv_series_singular">Serie</string>
|
||||
<string name="cartoons_singular">Dibujo animado</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Documental</string>
|
||||
<string name="asian_drama_singular">Drama asiático</string>
|
||||
|
@ -489,44 +489,4 @@
|
|||
<string name="update_started">Actualización iniciada</string>
|
||||
<string name="plugin_downloaded">Complemento descargado</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>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="pause">مکث</string>
|
||||
|
@ -23,14 +23,4 @@
|
|||
<string name="none">هیچکدام</string>
|
||||
<string name="title">عنوان</string>
|
||||
<string name="history">تاریخچه</string>
|
||||
<string name="search_poster_img_des">پوستر</string>
|
||||
<string name="result_poster_img_des">پوستر</string>
|
||||
<string name="episode_poster_img_des">پوستر قسمت</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dروز %dساعت %dدقیقه</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s قسمت %d</string>
|
||||
<string name="cast_format" formatted="true">بازیگران: %s</string>
|
||||
<string name="next_episode_format" formatted="true">قسمت %d پخش خواهد شد</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dساعت %dدقیقه</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dدقیقه</string>
|
||||
<string name="home_main_poster_img_des">پوستر اصلی</string>
|
||||
</resources>
|
|
@ -1,18 +1,17 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="title_home">Accueil</string>
|
||||
<string name="title_search">Rechercher</string>
|
||||
<string name="title_downloads">Téléchargements</string>
|
||||
<string name="title_settings">Paramètres</string>
|
||||
<string name="search_hint">Rechercher…</string>
|
||||
<string name="search_poster_img_des">Affiche</string>
|
||||
<string name="search_poster_img_des">Miniature</string>
|
||||
<string name="no_data">Aucune Donnée</string>
|
||||
<string name="episode_more_options_des">Plus d\'options</string>
|
||||
<string name="go_back_img_des">Retour</string>
|
||||
<string name="next_episode">Épisode suivant</string>
|
||||
<string name="result_poster_img_des">Affiche</string>
|
||||
<string name="result_poster_img_des">Miniature</string>
|
||||
<string name="result_tags">Genres</string>
|
||||
<string name="result_share">Partager</string>
|
||||
<string name="result_open_in_browser">Ouvrir dans le navigateur</string>
|
||||
|
@ -30,7 +29,7 @@
|
|||
<string name="pick_subtitle">Sous-titres</string>
|
||||
<string name="reload_error">Réessayer la connection…</string>
|
||||
<string name="go_back">Retour</string>
|
||||
<string name="episode_poster_img_des">Affiche de l\'épisode</string>
|
||||
<string name="episode_poster_img_des">Miniature de l\'Épisode</string>
|
||||
<string name="play_episode">Lire l\'Épisode</string>
|
||||
<!--<string name="need_storage">Permet de télécharger les épisodes</string>-->
|
||||
<string name="download">Télécharger</string>
|
||||
|
@ -52,10 +51,10 @@
|
|||
<string name="pref_disable_acra">Désactiver le rapport de bug automatique</string>
|
||||
<string name="home_more_info">Plus d\'informations</string>
|
||||
<string name="home_expanded_hide">Cacher</string>
|
||||
<string name="home_main_poster_img_des">Affiche principale</string>
|
||||
<string name="home_main_poster_img_des">Poster principal</string>
|
||||
<string name="home_play">Lecture</string>
|
||||
<string name="home_info">Infos</string>
|
||||
<string name="home_next_random_img_des">Aléatoire suivant</string>
|
||||
<string name="home_info">Info</string>
|
||||
<string name="home_next_random_img_des">Suivant Aléatoire</string>
|
||||
<string name="home_change_provider_img_des">Changer le fournisseur</string>
|
||||
<string name="filter_bookmarks">Filtrer les marques-pages</string>
|
||||
<string name="error_bookmarks_text">Marque-pages</string>
|
||||
|
@ -131,7 +130,7 @@
|
|||
<string name="new_update_format" formatted="true">Nouvelle mise à jour trouvée !
|
||||
\n%s -> %s</string>
|
||||
<string name="filler" formatted="true">Épisode spécial</string>
|
||||
<string name="watch_quality_pref">Qualité de visionnage préférée (WiFi)</string>
|
||||
<string name="watch_quality_pref">Qualité de visionnage préférée</string>
|
||||
<string name="video_buffer_size_settings">Taille de la mémoire cache</string>
|
||||
<string name="resize_fill">Étendre</string>
|
||||
<string name="legal_notice">Non-responsabilité</string>
|
||||
|
@ -212,7 +211,7 @@
|
|||
<string name="actor_background">Arrière plan</string>
|
||||
<string name="home_source">Source</string>
|
||||
<string name="home_random">Aléatoire</string>
|
||||
<string name="coming_soon">Bientôt disponible…</string>
|
||||
<string name="coming_soon">À venir …</string>
|
||||
<string name="poster_image">Image de l\'affiche</string>
|
||||
<string name="authenticated_user">%s Connecté</string>
|
||||
<string name="action_add_to_bookmarks">Définir le statut de visionage</string>
|
||||
|
@ -241,7 +240,7 @@
|
|||
<string name="continue_watching">Continuer à regarder</string>
|
||||
<string name="action_remove_watching">Retirer</string>
|
||||
<string name="action_open_watching">Plus d\'informations</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Un VPN peut être nécessaire pour que ce fournisseur fonctionne correctement</string>
|
||||
<string name="vpn_torrent">Ce fournisseur est un torrent, un VPN est recommandé</string>
|
||||
<string name="provider_info_meta">Les métadonnées ne sont pas fournies par le site, le chargement de la vidéo échouera si elles n\'existent pas sur le site.</string>
|
||||
|
@ -386,8 +385,8 @@
|
|||
<string name="quality_4k">4K</string>
|
||||
<string name="quality_webrip">Web</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="subtitles_filter_lang">Filtrez par langue préférée</string>
|
||||
|
@ -491,37 +490,4 @@
|
|||
<string name="delayed_update_notice">L\'application sera mise à jour dès la fin de la session</string>
|
||||
<string name="plugin_downloaded">Plugin Téléchargé</string>
|
||||
<string name="action_remove_from_watched">Retirer de la vue</string>
|
||||
<string name="library">Bibliothèque</string>
|
||||
<string name="browser">Navigateur</string>
|
||||
<string name="sort">Trier</string>
|
||||
<string name="sort_rating_asc">Note (basse à haute)</string>
|
||||
<string name="sort_rating_desc">Note (haut à bas)</string>
|
||||
<string name="sort_alphabetical_a">Alphabétique (A à Z)</string>
|
||||
<string name="empty_library_no_accounts_message">On dirait que votre bibliothèque est vide :(
|
||||
\nConnectez-vous à un compte ou ajoutez des séries à votre bibliothèque locale</string>
|
||||
<string name="empty_library_logged_in_message">Il semble que cette liste soit vide, essayez d\'en choisir une autre</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="sort_by">Trié par</string>
|
||||
<string name="sort_alphabetical_z">Alphabétique (Z à A)</string>
|
||||
<string name="select_library">Sélectionnez la bibliothèque</string>
|
||||
<string name="open_with">Ouvrir avec</string>
|
||||
<string name="sort_updated_new">Mis à jour (Nouveau vers ancien)</string>
|
||||
<string name="sort_updated_old">Mis à jour (ancien vers nouveau)</string>
|
||||
<string name="safe_mode_file">Fichier du mode sans échec trouvé !
|
||||
\nAucune extension ne sera chargée au démarrage avant que le fichier ne soit enlevé.</string>
|
||||
<string name="stop">Arrêter</string>
|
||||
<string name="revert">Revenir à</string>
|
||||
<string name="test_log">Enregistrer</string>
|
||||
<string name="watch_quality_pref_data">Qualité de visionnage préférée (données mobiles)</string>
|
||||
<string name="subscription_new">Abonné à %s</string>
|
||||
<string name="start">Démarrer</string>
|
||||
<string name="category_provider_test">Test des fournisseurs</string>
|
||||
<string name="test_passed">Réussi</string>
|
||||
<string name="subscription_deleted">Désabonné de %s</string>
|
||||
<string name="restart">Redémarrer</string>
|
||||
<string name="subscription_list_name">Abonné</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
|
||||
<string name="pref_category_bypass">Contournements de FAI</string>
|
||||
<string name="subscription_episode_released">L\'épisode %d est sorti !</string>
|
||||
<string name="test_failed">Échouer</string>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
|
||||
<string name="player_speed_text_format" formatted="true">रफ्तार (%.2fx)</string>
|
||||
<string name="new_update_format" formatted="true">नया अपडेट आया है!
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
|
||||
<string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
|
||||
|
@ -20,7 +19,7 @@
|
|||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Episode Poster</string>
|
||||
<string name="home_main_poster_img_des">Main Poster</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="action_remove_watching">Makni</string>
|
||||
<string name="action_open_watching">Više informacija</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Za ispravan rad ovog pružatelja usluga može biti potreban VPN</string>
|
||||
<string name="vpn_torrent">Ovaj pružatelj usluga je torrent, preporučuje se VPN</string>
|
||||
<string name="provider_info_meta">Stranica ne daje metapodatke, učitavanje videozapisa neće uspjeti ako ne postoji na stranici.</string>
|
||||
|
@ -139,16 +138,16 @@
|
|||
<string name="eigengraumode_settings">Eigengravy način</string>
|
||||
<string name="eigengraumode_settings_des">Dodaje opciju brzine u playeru</string>
|
||||
<string name="swipe_to_seek_settings">Prijeđi prstom za traženje</string>
|
||||
<string name="swipe_to_seek_settings_des">Prijeđite prstom ulijevo ili udesno kako biste kontrolirali player</string>
|
||||
<string name="swipe_to_seek_settings_des">Prijeđi prstom ulijevo ili udesno za kontrolu vremena u videoplayeru</string>
|
||||
<string name="swipe_to_change_settings">Klizni za promjenu postavki</string>
|
||||
<string name="swipe_to_change_settings_des">Kliznite prstom ulijevo ili udesno za promjenu svjetline ili glasnoće</string>
|
||||
<string name="swipe_to_change_settings_des">Prijeđi prstom ulijevo ili udesno za promjenu svjetline ili glasnoće</string>
|
||||
<string name="autoplay_next_settings">Automatski započni sljedeću epizodu</string>
|
||||
<string name="autoplay_next_settings_des">Započne sljedeću epizodu kad trenutna završi</string>
|
||||
<string name="double_tap_to_seek_settings">Dodirni dvaput za traženje</string>
|
||||
<string name="double_tap_to_pause_settings">Dodirni dvaput za pauziranje</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Iznos preskakanja u playeru (Sekunde)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Iznos preskakanja u playeru</string>
|
||||
<string name="double_tap_to_seek_settings_des">Dvaput dodirni desnu ili lijevu stranu ekrana za pomicanje naprijed ili natrag</string>
|
||||
<string name="double_tap_to_pause_settings_des">Dodirnite dvaput u sredinu zaslona za pauziranje</string>
|
||||
<string name="double_tap_to_pause_settings_des">Dodirni u sredinu zaslona za pauziranje</string>
|
||||
<string name="use_system_brightness_settings">Koristi svijetlinu u sustavu</string>
|
||||
<string name="use_system_brightness_settings_des">Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog preklopa</string>
|
||||
<string name="episode_sync_settings">Ažuriraj napredak gledanja</string>
|
||||
|
@ -174,7 +173,7 @@
|
|||
<string name="pref_filter_search_quality">Sakrij odabranu kvalitetu videozapisa u rezultatima pretraživanja</string>
|
||||
<string name="automatic_plugin_updates">Automatsko ažuriranje dodataka</string>
|
||||
<string name="updates_settings">Prikaži ažuriranja aplikacije</string>
|
||||
<string name="updates_settings_des">Automatski traži nova ažuriranja nakon pokretanja aplikacije</string>
|
||||
<string name="updates_settings_des">Automatski traži nova ažuriranja pri pokretanju aplikacije</string>
|
||||
<string name="uprereleases_settings">Ažuriranje na predizdanja</string>
|
||||
<string name="uprereleases_settings_des">Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -239,8 +238,8 @@
|
|||
<string name="movies_singular">Film</string>
|
||||
<string name="tv_series_singular">Serija</string>
|
||||
<string name="cartoons_singular">Crtić</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Dokumentarac</string>
|
||||
<string name="asian_drama_singular">Azijska drama</string>
|
||||
|
@ -300,7 +299,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">Općenito</string>
|
||||
<string name="random_button_settings">Random gumb</string>
|
||||
<string name="random_button_settings_desc">Prikazuje gumb na početnoj stranici koji može odabrati nasumični film ili TV seriju s početne stranice</string>
|
||||
<string name="random_button_settings_desc">Prikaži random gumb na početnoj stranici</string>
|
||||
<string name="provider_lang_settings">Jezici pružatelja usluga</string>
|
||||
<string name="app_layout">Izgled aplikacije</string>
|
||||
<string name="preferred_media_settings">Preferirani mediji</string>
|
||||
|
@ -515,44 +514,4 @@
|
|||
<string name="delayed_update_notice">Program če se aktualizirati tijekom zatvaranja programa</string>
|
||||
<string name="plugin_downloaded">Dodatak preuzet</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>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cast_format" formatted="true">Stáblista: %s</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dn %dó%dp</string>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<string name="result_open_in_browser">Megnyitás böngészőben</string>
|
||||
<string name="skip_loading">Betöltés kihagyása</string>
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="type_watching">Nézés</string>
|
||||
<string name="type_completed">Befejezve</string>
|
||||
<string name="type_plan_to_watch">Később megnézés</string>
|
||||
|
@ -111,7 +111,7 @@
|
|||
<string name="subs_import_text" formatted="true">Betűtípusok importálása %s</string>
|
||||
<string name="action_remove_watching">Eltávolítás</string>
|
||||
<string name="action_open_watching">Több információ</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">VPN szükséges lehet ehhez a szolgáltató megfelelő működéséhez</string>
|
||||
<string name="vpn_torrent">Ez a szolgáltató torrent, VPN ajánlott</string>
|
||||
<string name="torrent_plot">Leírás</string>
|
||||
|
@ -172,11 +172,11 @@
|
|||
<string name="ova">OVA</string>
|
||||
<string name="others">Egyebek</string>
|
||||
<string name="tv_series_singular">Sorozat</string>
|
||||
<string name="anime_singular">@string/anime</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="source_error">Forráshiba</string>
|
||||
<string name="nsfw">NSFW</string>
|
||||
<string name="cartoons_singular">Rajzfilm</string>
|
||||
<string name="ova_singular">@string/ova</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="live_singular">Élőadás</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="other_singular">Videó</string>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Pemeran: %s</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Episode Poster</string>
|
||||
<string name="home_main_poster_img_des">Main Poster</string>
|
||||
<string name="home_next_random_img_des">Next Random</string>
|
||||
|
@ -36,7 +35,7 @@
|
|||
<string name="skip_loading">Skip Loading</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="type_watching">Sedang Menonton</string>
|
||||
<string name="type_on_hold">Tertunda</string>
|
||||
<string name="type_on_hold">Tertahan</string>
|
||||
<string name="type_completed">Selesai</string>
|
||||
<string name="type_dropped">Dihentikan</string>
|
||||
<string name="type_plan_to_watch">Rencana untuk Menonton</string>
|
||||
|
@ -102,7 +101,7 @@
|
|||
<string name="continue_watching">Lanjutkan Menonton</string>
|
||||
<string name="action_remove_watching">Hapus</string>
|
||||
<string name="action_open_watching">Info lebih lanjut</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Sebuah VPN mungkin diperlukan agar provider ini bisa bekerja dengan benar</string>
|
||||
<string name="vpn_torrent">Provider ini adalah sebuah torrent, VPN direkomendasikan</string>
|
||||
<string name="provider_info_meta">Metadata tidak disediakan oleh situs, loading video akan gagal jika tidak ada di situs.</string>
|
||||
|
@ -121,14 +120,14 @@
|
|||
<string name="eigengraumode_settings">Mode Eigengravy</string>
|
||||
<string name="eigengraumode_settings_des">Menambahkan opsi kecepatan di pemutar</string>
|
||||
<string name="swipe_to_seek_settings">Geser untuk mengubah waktu</string>
|
||||
<string name="swipe_to_seek_settings_des">Geser dari sisi ke sisi untuk mengontrol posisi dalam video</string>
|
||||
<string name="swipe_to_seek_settings_des">Geser ke kiri atau kanan untuk mengontrol waktu di pemutar video</string>
|
||||
<string name="swipe_to_change_settings">Geser untuk mengubah pengaturan</string>
|
||||
<string name="swipe_to_change_settings_des">Geser ke atas atau ke bawah di sisi kiri atau kanan untuk mengubah kecerahan atau volume</string>
|
||||
<string name="swipe_to_change_settings_des">Geser ke sisi kiri atau kanan untuk mengubah pencerahan atau volume</string>
|
||||
<string name="double_tap_to_seek_settings">Tekan dua kali untuk mengubah waktu</string>
|
||||
<string name="double_tap_to_pause_settings">Tekan dua kali untuk menjeda</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Jumlah pengubah waktu pemutar (Detik)</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Jumlah pengubah waktu pemutar</string>
|
||||
<string name="double_tap_to_seek_settings_des">Tekan dua kali di sisi kanan atau kiri untuk mengubah waktu ke depan atau ke belakang</string>
|
||||
<string name="double_tap_to_pause_settings_des">Tekan dua kali di tengah untuk menjeda</string>
|
||||
<string name="double_tap_to_pause_settings_des">Tekan di tengah untuk menjeda</string>
|
||||
<string name="use_system_brightness_settings">Gunakan pencerahan sistem</string>
|
||||
<string name="use_system_brightness_settings_des">Gunakan pencerahan sistem di pemutar aplikasi dari pada hamparan gelap</string>
|
||||
<string name="episode_sync_settings">Update progres tontonan</string>
|
||||
|
@ -150,7 +149,7 @@
|
|||
<string name="bug_report_settings_on">Tidak mengirim data</string>
|
||||
<string name="show_fillers_settings">Tampilkan episode filler untuk anime</string>
|
||||
<string name="updates_settings">Tampilkan update aplikasi</string>
|
||||
<string name="updates_settings_des">Secara otomatis mencari update terbaru setelah aplikasi dibuka.</string>
|
||||
<string name="updates_settings_des">Secara otomatis mencari update terbaru saat aplikasi dibuka</string>
|
||||
<string name="uprereleases_settings">Update ke prarilis</string>
|
||||
<string name="uprereleases_settings_des">Hanya mencari update prarilis daripada rilis penuh</string>
|
||||
<string name="github">Github</string>
|
||||
|
@ -210,8 +209,8 @@
|
|||
<string name="movies_singular">Movie</string>
|
||||
<string name="tv_series_singular">Seri</string>
|
||||
<string name="cartoons_singular">Kartun</string>
|
||||
<string name="anime_singular">Anime</string>
|
||||
<string name="ova_singular">OVA</string>
|
||||
<string name="anime_singular">\@string/anime</string>
|
||||
<string name="ova_singular">\@string/ova</string>
|
||||
<string name="torrent_singular">Torrent</string>
|
||||
<string name="documentaries_singular">Film Dokumenter</string>
|
||||
<string name="asian_drama_singular">Drama Asia</string>
|
||||
|
@ -244,7 +243,7 @@
|
|||
<string name="dont_show_again">Jangan tunjukkan lagi</string>
|
||||
<string name="skip_update">Skip Update ini</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="watch_quality_pref">Kualitas tontonan yang lebih diinginkan (WIFI)</string>
|
||||
<string name="watch_quality_pref">Kualitas tontonan yang lebih diinginkan</string>
|
||||
<string name="limit_title">Karakter maksimal judul pemutar video</string>
|
||||
<string name="limit_title_rez">Resolusi pemutar video</string>
|
||||
<string name="video_buffer_size_settings">Ukuran buffer video</string>
|
||||
|
@ -265,7 +264,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">Umum</string>
|
||||
<string name="random_button_settings">Tombol Acak</string>
|
||||
<string name="random_button_settings_desc">Tampilkan tombol di halaman utama yang dapat memilih seri film atau TV acak dari halaman utama</string>
|
||||
<string name="random_button_settings_desc">Tampilkan tombol acak di Beranda</string>
|
||||
<string name="provider_lang_settings">Bahasa provider</string>
|
||||
<string name="app_layout">Tata Letak Aplikasi</string>
|
||||
<string name="preferred_media_settings">Media yang lebih diinginkan</string>
|
||||
|
@ -388,7 +387,7 @@
|
|||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="nsfw">17+</string>
|
||||
<string name="others">Lainnya</string>
|
||||
<string name="other_singular">Video</string>
|
||||
<string name="other_singular">Vidio</string>
|
||||
<string name="add_site_pref">Duplikasi Website</string>
|
||||
<string name="add_site_summary">Duplikasi website yang telah ada, dengan alamat berbeda</string>
|
||||
<string name="pref_category_links">Tautan</string>
|
||||
|
@ -396,7 +395,7 @@
|
|||
<string name="pref_category_backup">Cadangkan</string>
|
||||
<string name="pref_category_extensions">Fitur Tambahan</string>
|
||||
<string name="play_with_app_name">Putar di CloudStream</string>
|
||||
<string name="pref_filter_search_quality">Sembunyikan kualitas video terpilih di pencarian</string>
|
||||
<string name="pref_filter_search_quality">Sembunyikan kualitas vidio terpilih di pencarian</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="livestreams">Siaran langsung</string>
|
||||
<string name="remove_site_pref">Hapus Website</string>
|
||||
|
@ -445,7 +444,7 @@
|
|||
<string name="extension_rating" formatted="true">Peringkat: %s</string>
|
||||
<string name="extension_authors">Pembuat</string>
|
||||
<string name="extension_language">Bahasa</string>
|
||||
<string name="player_pref">Pemutar video utama</string>
|
||||
<string name="player_pref">Pemutar vidio utama</string>
|
||||
<string name="player_settings_play_in_app">Pemutar Bawaan</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
|
@ -454,7 +453,7 @@
|
|||
<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="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="delete_repository_plugins">Pilih ini untuk menghapus semua repositori plugin</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_bloat">Hapus karakter sampah dari subtitel</string>
|
||||
<string name="audio_tracks">Audio Trek</string>
|
||||
<string name="video_tracks">Video Trek</string>
|
||||
<string name="video_tracks">Vidio Trek</string>
|
||||
<string name="extension_types">Dukungan</string>
|
||||
<string name="hls_playlist">Daftar putar HLS</string>
|
||||
<string name="apk_installer_settings">Penginstal APK</string>
|
||||
|
@ -484,7 +483,7 @@
|
|||
<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="pref_category_actions">Aksi</string>
|
||||
<string name="referer">Referer</string>
|
||||
<string name="referer">Referensi</string>
|
||||
<string name="yes">Ya</string>
|
||||
<string name="extension_install_first">Pasang dulu fitur tambahan</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="update_started">Pembaharuan Dimulai</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>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--><resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Cast: %s</string>
|
||||
|
@ -10,7 +9,7 @@
|
|||
<string name="next_episode_time_min_format" formatted="true">%d min</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">Poster</string>
|
||||
<string name="search_poster_img_des">\@string/result_poster_img_des</string>
|
||||
<string name="episode_poster_img_des">Poster episodio</string>
|
||||
<string name="home_main_poster_img_des">Poster principale</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="action_remove_watching">Rimuovi</string>
|
||||
<string name="action_open_watching">Più info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Potrebbe essere necessaria una VPN per far funzionare correttamente questo provider</string>
|
||||
<string name="vpn_torrent">Questo provider è un torrent, si raccomanda una VPN</string>
|
||||
<string name="provider_info_meta">I metadati non sono forniti dal sito, il caricamento del video fallirà se non esiste sul sito.</string>
|
||||
|
@ -266,7 +265,7 @@
|
|||
<string name="dont_show_again">Non mostrare di nuovo</string>
|
||||
<string name="skip_update">Salta questo aggiornamento</string>
|
||||
<string name="update">Aggiorna</string>
|
||||
<string name="watch_quality_pref">Qualità di visualizzazione preferita (WiFi)</string>
|
||||
<string name="watch_quality_pref">Risoluzione preferita</string>
|
||||
<string name="limit_title">Limita i caratteri del titolo nel player</string>
|
||||
<string name="limit_title_rez">Risoluzione video player</string>
|
||||
<string name="video_buffer_size_settings">Dimensione cache video</string>
|
||||
|
@ -512,44 +511,4 @@
|
|||
<string name="update_started">Aggiornamento avviato</string>
|
||||
<string name="plugin_downloaded">Plugin scaricato</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>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="preview_background_img_des">הרקע של ההצגה לפני</string>
|
||||
<string name="cast_format" formatted="true">צוות שחקנים: %s</string>
|
||||
|
@ -34,13 +34,13 @@
|
|||
<string name="type_completed">הושלם</string>
|
||||
<string name="type_plan_to_watch">בתכנון לצפייה</string>
|
||||
<string name="type_re_watching">צופה מחדש</string>
|
||||
<string name="play_movie_button">הפעל סרט</string>
|
||||
<string name="play_trailer_button">הפעל טריילר</string>
|
||||
<string name="play_livestream_button">הפעל שידור חי</string>
|
||||
<string name="play_movie_button">נגן סרט</string>
|
||||
<string name="play_trailer_button">נגן קדימון</string>
|
||||
<string name="play_livestream_button">נגן שידור חי</string>
|
||||
<string name="pick_source">מקורות</string>
|
||||
<string name="reload_error">נסיון חדש לחיבור…</string>
|
||||
<string name="type_dropped">הפסיק</string>
|
||||
<string name="play_torrent_button">שדר טורנט</string>
|
||||
<string name="type_dropped">נעצר</string>
|
||||
<string name="play_torrent_button">שידור טורנט</string>
|
||||
<string name="go_back">חזרה</string>
|
||||
<string name="play_episode">נגן פרק</string>
|
||||
<string name="downloaded">הורד</string>
|
||||
|
@ -75,7 +75,7 @@
|
|||
<string name="play_with_app_name">נגן עם קלאודסטרים</string>
|
||||
<string name="next_episode">פרק הבא</string>
|
||||
<string name="loading">טוען…</string>
|
||||
<string name="type_watching">צופה</string>
|
||||
<string name="type_watching">צופה ב</string>
|
||||
<string name="pick_subtitle">כתוביות</string>
|
||||
<string name="type_on_hold">בהמתנה</string>
|
||||
<string name="type_none">ללא</string>
|
||||
|
@ -102,11 +102,11 @@
|
|||
<string name="vpn_torrent">הספק הזה הוא טורנט, שימוש ב-VPN הוא מומלץ</string>
|
||||
<string name="torrent_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="player_size_settings">כפתור שינוי גודל הנגן</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="chromecast_subtitles_settings_des">הגדרות כתוביות כרומקאסט</string>
|
||||
<string name="subs_font">גופן</string>
|
||||
|
@ -116,7 +116,7 @@
|
|||
<string name="chromecast_subtitles_settings">כתוביות כרומקאסט</string>
|
||||
<string name="picture_in_picture_des">ממשיך ניגון בנגן מינימלי מעל ישומים אחרים</string>
|
||||
<string name="player_subtitles_settings">כתוביות</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="history">היסטוריה</string>
|
||||
<string name="apk_installer_legacy">מורשת</string>
|
||||
<string name="no">לא</string>
|
||||
|
@ -129,381 +129,4 @@
|
|||
<string name="none">אין</string>
|
||||
<string name="all">הכל</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>
|
|
@ -1,185 +0,0 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="next_episode_time_min_format" formatted="true">%d分</string>
|
||||
<string name="title_downloads">ダウンロード</string>
|
||||
<string name="title_search">検索</string>
|
||||
<string name="title_settings">設定</string>
|
||||
<string name="result_share">シェア</string>
|
||||
<string name="movies">映画</string>
|
||||
<string name="title_home">ホーム</string>
|
||||
<string name="library">ライブラリ</string>
|
||||
<string name="home_play">再生</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%d日 %d時間%d分</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%d時間%d分</string>
|
||||
<string name="search_hint">検索…</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="home_info">情報</string>
|
||||
<string name="season">シーズン</string>
|
||||
<string name="trailer">予告編</string>
|
||||
<string name="tv_series_singular">シリーズ</string>
|
||||
<string name="episodes">エピソード</string>
|
||||
<string name="player_speed_text_format" formatted="true">再生速度 (%.2fx)</string>
|
||||
<string name="next_episode">次のエピソード</string>
|
||||
<string name="sort_apply">適用</string>
|
||||
<string name="category_account">アカウント</string>
|
||||
<string name="cartoons">カートゥーン</string>
|
||||
<string name="tv_series">TVシリーズ</string>
|
||||
<string name="torrent">トレント</string>
|
||||
<string name="documentaries">ドキュメンタリー</string>
|
||||
<string name="ova">OVA</string>
|
||||
<string name="asian_drama">アジアドラマ</string>
|
||||
<string name="livestreams">ライブ配信</string>
|
||||
<string name="movies_singular">映画</string>
|
||||
<string name="others">その他</string>
|
||||
<string name="cartoons_singular">カートゥーン</string>
|
||||
<string name="torrent_singular">トレント</string>
|
||||
<string name="documentaries_singular">ドキュメンタリー</string>
|
||||
<string name="asian_drama_singular">アジアドラマ</string>
|
||||
<string name="live_singular">ライブ配信</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="sort_cancel">キャンセル</string>
|
||||
<string name="anime">アニメ</string>
|
||||
<string name="video_lock">ロック</string>
|
||||
<string name="video_source">ソース</string>
|
||||
<string name="nsfw">NSFW</string>
|
||||
<string name="clear_history">履歴を削除</string>
|
||||
<string name="continue_watching">視聴中コンテンツ</string>
|
||||
<string name="category_general">全般</string>
|
||||
<string name="other_singular">動画</string>
|
||||
<string name="category_player">プレーヤー</string>
|
||||
<string name="type_plan_to_watch">懐う</string>
|
||||
<string name="play_trailer_button">予告編を再生</string>
|
||||
<string name="episode_short">エピソード</string>
|
||||
<string name="type_watching">視聴</string>
|
||||
<string name="result_tags">ジャンル</string>
|
||||
<string name="play_movie_button">映画を再生</string>
|
||||
<string name="pick_subtitle">字幕</string>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="play_with_app_name">CloudStreamで再生</string>
|
||||
<string name="browser">ブラウザ</string>
|
||||
<string name="type_completed">完成</string>
|
||||
<string name="type_dropped">放置</string>
|
||||
<string name="type_on_hold">保留</string>
|
||||
<string name="loading">ローディング…</string>
|
||||
<string name="result_open_in_browser">ブラウザで開く</string>
|
||||
<string name="season_short">シーズン</string>
|
||||
<string name="resume_time_left" formatted="true">残り
|
||||
\n%d分</string>
|
||||
<string name="play_episode">再生エピソード</string>
|
||||
<string name="downloaded">ダウンロード済</string>
|
||||
<string name="pref_category_backup">バックアップ</string>
|
||||
<string name="home_source">ソース</string>
|
||||
<string name="history">履歴</string>
|
||||
<string name="result_poster_img_des">ポスター</string>
|
||||
<string name="type_none">なし</string>
|
||||
<string name="sort_copy">コピー</string>
|
||||
<string name="sort_close">閉じる</string>
|
||||
<string name="sort_save">保存</string>
|
||||
<string name="sort_clear">消去</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%sエピ%d</string>
|
||||
<string name="cast_format" formatted="true">出演者:%s</string>
|
||||
<string name="search_poster_img_des">ポスター</string>
|
||||
<string name="episode_poster_img_des">エピソードポスター</string>
|
||||
<string name="home_main_poster_img_des">主要ポスター</string>
|
||||
<string name="home_next_random_img_des">次のランダム</string>
|
||||
<string name="go_back_img_des">戻り</string>
|
||||
<string name="rated_format" formatted="true">視聴率 %.1f</string>
|
||||
<string name="new_update_format" formatted="true">新しいアップデートを発見!
|
||||
\n%s -> %s</string>
|
||||
<string name="duration_format" formatted="true">%d分</string>
|
||||
<string name="search_hint_site" formatted="true">%sを検索…</string>
|
||||
<string name="pick_source">ソース</string>
|
||||
<string name="filler" formatted="true">ろくごうきじ</string>
|
||||
<string name="reload_error">接続を再試行…</string>
|
||||
<string name="go_back">戻り</string>
|
||||
<string name="action_remove_from_bookmarks">削除</string>
|
||||
<string name="home_more_info">詳細情報</string>
|
||||
<string name="home_expanded_hide">閉じる</string>
|
||||
<string name="category_updates">アップデート・バックアップ</string>
|
||||
<string name="app_language">アプリ言語</string>
|
||||
<string name="github">GitHub(ギットハブ)</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
<string name="go_forward_30">+30</string>
|
||||
<string name="legal_notice">免責</string>
|
||||
<string name="pref_category_extensions">拡張機能</string>
|
||||
<string name="pref_category_app_updates">アプリ更新</string>
|
||||
<string name="category_providers">提供者</string>
|
||||
<string name="pref_category_subtitles">字幕</string>
|
||||
<string name="pref_category_ui_features">特徴</string>
|
||||
<string name="pref_category_defaults">デフォルト</string>
|
||||
<string name="automatic">自動</string>
|
||||
<string name="home_random">任意</string>
|
||||
<string name="extensions">拡張機能</string>
|
||||
<string name="pref_category_links">リンク</string>
|
||||
<string name="pref_category_android_tv">Android TV</string>
|
||||
<string name="login">ログイン</string>
|
||||
<string name="logout">ログアウト</string>
|
||||
<string name="max">最大</string>
|
||||
<string name="min">最小</string>
|
||||
<string name="none">なし</string>
|
||||
<string name="next">次</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="no">否</string>
|
||||
<string name="open_with">で開く</string>
|
||||
<string name="episode">エピソード</string>
|
||||
<string name="duration">時間</string>
|
||||
<string name="synopsis">概要</string>
|
||||
<string name="site">サイト</string>
|
||||
<string name="used_storage">使用</string>
|
||||
<string name="app_storage">アプリ</string>
|
||||
<string name="action_open_watching">詳細情報</string>
|
||||
<string name="action_remove_watching">削除</string>
|
||||
<string name="picture_in_picture">ピクチャーインピクチャー</string>
|
||||
<string name="player_subtitles_settings">字幕</string>
|
||||
<string name="settings_info">情報</string>
|
||||
<string name="pause">一時停止</string>
|
||||
<string name="play_episode_toast">再生エピソード</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="start">開始</string>
|
||||
<string name="status">状態</string>
|
||||
<string name="year">年</string>
|
||||
<string name="resume">再開</string>
|
||||
<string name="test_failed">失敗</string>
|
||||
<string name="test_passed">合格</string>
|
||||
<string name="free_storage">空き</string>
|
||||
<string name="status_completed">完成</string>
|
||||
<string name="status_ongoing">進行中</string>
|
||||
<string name="normal">デフォルト</string>
|
||||
<string name="player_settings_play_in_browser">ウェブブラウザ</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
<string name="extension_language">言語</string>
|
||||
<string name="extension_authors">作成者</string>
|
||||
<string name="extension_size">サイズ</string>
|
||||
<string name="extension_status">状態</string>
|
||||
<string name="extension_version">バージョン</string>
|
||||
<string name="extension_rating" formatted="true">視聴率 %s</string>
|
||||
<string name="rating">視聴率</string>
|
||||
<string name="default_subtitles">デフォルト</string>
|
||||
<string name="download_failed">ダウンロード失敗</string>
|
||||
<string name="download_started">ダウンロード開始</string>
|
||||
<string name="download_done">ダウンロード完了</string>
|
||||
<string name="download_canceled">ダウンロード終了</string>
|
||||
<string name="stream">ストリーム</string>
|
||||
<string name="update_started">アップデート開始</string>
|
||||
<string name="no_season">シーズンなし</string>
|
||||
<string name="no_subtitles">字幕なし</string>
|
||||
<string name="video_aspect_ratio_resize">アスペクト比</string>
|
||||
<string name="skip_loading">ロードをスキップする</string>
|
||||
<string name="episode_more_options_des">その他のオプション</string>
|
||||
<string name="no_data">データなし</string>
|
||||
<string name="downloading">ダウンロード中</string>
|
||||
<string name="error_bookmarks_text">ブックマーク</string>
|
||||
<string name="download_storage_text">内部記憶装置</string>
|
||||
<string name="download_paused">ダウンロードが一時停止</string>
|
||||
<string name="provider_info_meta">メタデータはこのサイトでは提供されません。メタデータがサイト上に存在しない場合、ビデオの読み込みに失敗します。</string>
|
||||
<string name="torrent_plot">記述</string>
|
||||
<string name="show_log_cat">Logcat 🐈を表示</string>
|
||||
<string name="test_log">ログ</string>
|
||||
<string name="search">検索</string>
|
||||
<string name="discord">Discordに参加</string>
|
||||
<string name="update">アップデート</string>
|
||||
<string name="check_for_update">アップデートを確認</string>
|
||||
<string name="show_title">作品名</string>
|
||||
<string name="update_notification_installing">アプリのアップデートをインストール中…</string>
|
||||
</resources>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue