Compare commits

..

33 commits

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

BIN
.github/downloads.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
.github/home.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

20
.github/locales.py vendored
View file

@ -1,14 +1,13 @@
import re
import 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
.github/results.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
.github/search.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View file

@ -15,7 +15,6 @@ jobs:
app_id: ${{ secrets.GH_APP_ID }}
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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,12 +15,13 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniList
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.syncproviders.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()
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,57 +1,38 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
class Ztreamhub : Filesim() {
override val mainUrl: String = "https://ztreamhub.com" //Here 'cause works
override val name = "Zstreamhub"
}
class FileMoon : Filesim() {
override val mainUrl = "https://filemoon.to"
override val name = "FileMoon"
}
class FileMoonSx : Filesim() {
override val mainUrl = "https://filemoon.sx"
override val name = "FileMoonSx"
}
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
open class Filesim : ExtractorApi() {
override val name = "Filesim"
override val mainUrl = "https://files.im"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val response = app.get(url, referer = mainUrl).document
response.select("script[type=text/javascript]").map { script ->
if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) {
val unpackedscript = getAndUnpack(script.data())
val m3u8Regex = Regex("file.\\\"(.*?m3u8.*?)\\\"")
val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: ""
if (m3u8.isNotEmpty()) {
generateM3u8(
name,
m3u8,
mainUrl
).forEach(callback)
override suspend fun getUrl(url: String, referer: String?): List<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,
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?
) */
)
}

View file

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

View file

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

View file

@ -1,28 +0,0 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
open class Sendvid : ExtractorApi() {
override var name = "Sendvid"
override val mainUrl = "https://sendvid.com"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val doc = app.get(url).document
val urlString = doc.select("head meta[property=og:video:secure_url]").attr("content")
if (urlString.contains("m3u8")) {
generateM3u8(
name,
urlString,
mainUrl,
).forEach(callback)
}
}
}

View file

@ -77,10 +77,6 @@ class StreamSB10 : StreamSB() {
override var mainUrl = "https://sbplay2.xyz"
}
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",
)

View file

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

View file

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

View file

@ -1,34 +0,0 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getAndUnpack
class Vido : ExtractorApi() {
override var name = "Vido"
override var mainUrl = "https://vido.lol"
private val srcRegex = Regex("""sources:\s*\["(.*?)"\]""")
override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val methode = app.get(url.replace("/e/", "/embed-")) // fix wiflix and mesfilms
with(methode) {
if (!methode.isSuccessful) return null
//val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull()
srcRegex.find(this.text)?.groupValues?.get(1)?.let { link ->
return listOf(
ExtractorLink(
name,
name,
link,
url,
Qualities.Unknown.value,
true,
)
)
}
}
return null
}
}

View file

@ -49,7 +49,7 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
}
}
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this) { it?.let { t -> action(t) } }
}
@ -121,21 +121,13 @@ suspend fun <T> suspendSafeApiCall(apiCall: suspend () -> T): T? {
}
}
fun Throwable.getAllMessages(): String {
return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "")
}
fun Throwable.getStackTracePretty(showMessage: Boolean = true): String {
val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else ""
return prefix + this.stackTrace.joinToString(
separator = "\n"
) {
"${it.fileName} ${it.lineNumber}"
}
}
fun <T> safeFail(throwable: Throwable): Resource<T> {
val stackTraceMsg = throwable.getStackTracePretty()
val stackTraceMsg =
(throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
separator = "\n"
) {
"${it.fileName} ${it.lineNumber}"
}
return Resource.Failure(false, null, null, stackTraceMsg)
}

View file

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

View file

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

View file

@ -1,224 +0,0 @@
package com.lagradost.cloudstream3.services
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.net.toUri
import androidx.work.*
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getImageBitmapFromUrl
import kotlinx.coroutines.withTimeoutOrNull
import java.util.concurrent.TimeUnit
const val SUBSCRIPTION_CHANNEL_ID = "cloudstream3.subscriptions"
const val SUBSCRIPTION_WORK_NAME = "work_subscription"
const val SUBSCRIPTION_CHANNEL_NAME = "Subscriptions"
const val SUBSCRIPTION_CHANNEL_DESCRIPTION = "Notifications for new episodes on subscribed shows"
const val SUBSCRIPTION_NOTIFICATION_ID = 938712897 // Random unique
class SubscriptionWorkManager(val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
companion object {
fun enqueuePeriodicWork(context: Context?) {
if (context == null) return
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val periodicSyncDataWork =
PeriodicWorkRequest.Builder(SubscriptionWorkManager::class.java, 6, TimeUnit.HOURS)
.addTag(SUBSCRIPTION_WORK_NAME)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
SUBSCRIPTION_WORK_NAME,
ExistingPeriodicWorkPolicy.KEEP,
periodicSyncDataWork
)
// Uncomment below for testing
// val oneTimeSyncDataWork =
// OneTimeWorkRequest.Builder(SubscriptionWorkManager::class.java)
// .addTag(SUBSCRIPTION_WORK_NAME)
// .setConstraints(constraints)
// .build()
//
// WorkManager.getInstance(context).enqueue(oneTimeSyncDataWork)
}
}
private val progressNotificationBuilder =
NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID)
.setAutoCancel(false)
.setColorized(true)
.setOnlyAlertOnce(true)
.setSilent(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
.setContentTitle(context.getString(R.string.subscription_in_progress_notification))
.setSmallIcon(R.drawable.quantum_ic_refresh_white_24)
.setProgress(0, 0, true)
private val updateNotificationBuilder =
NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID)
.setColorized(true)
.setOnlyAlertOnce(true)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
.setSmallIcon(R.drawable.ic_cloudstream_monochrome_big)
private val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private fun updateProgress(max: Int, progress: Int, indeterminate: Boolean) {
notificationManager.notify(
SUBSCRIPTION_NOTIFICATION_ID, progressNotificationBuilder
.setProgress(max, progress, indeterminate)
.build()
)
}
override suspend fun doWork(): Result {
// println("Update subscriptions!")
context.createNotificationChannel(
SUBSCRIPTION_CHANNEL_ID,
SUBSCRIPTION_CHANNEL_NAME,
SUBSCRIPTION_CHANNEL_DESCRIPTION
)
setForeground(
ForegroundInfo(
SUBSCRIPTION_NOTIFICATION_ID,
progressNotificationBuilder.build()
)
)
val subscriptions = getAllSubscriptions()
if (subscriptions.isEmpty()) {
WorkManager.getInstance(context).cancelWorkById(this.id)
return Result.success()
}
val max = subscriptions.size
var progress = 0
updateProgress(max, progress, true)
// We need all plugins loaded.
PluginManager.loadAllOnlinePlugins(context)
PluginManager.loadAllLocalPlugins(context, false)
subscriptions.apmap { savedData ->
try {
val id = savedData.id ?: return@apmap null
val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null
// Reasonable timeout to prevent having this worker run forever.
val response = withTimeoutOrNull(60_000) {
api.load(savedData.url) as? EpisodeResponse
} ?: return@apmap null
val dubPreference =
getDub(id) ?: if (
context.getApiDubstatusSettings().contains(DubStatus.Dubbed)
) {
DubStatus.Dubbed
} else {
DubStatus.Subbed
}
val latestEpisodes = response.getLatestEpisodes()
val latestPreferredEpisode = latestEpisodes[dubPreference]
val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) {
val latestSeenEpisode =
savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE
val shouldUpdate = latestPreferredEpisode > latestSeenEpisode
shouldUpdate to latestPreferredEpisode
} else {
val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE
val latestSeenEpisode =
savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE
val shouldUpdate = latestEpisode > latestSeenEpisode
shouldUpdate to latestEpisode
}
DataStoreHelper.updateSubscribedData(
id,
savedData,
response
)
if (shouldUpdate) {
val updateHeader = savedData.name
val updateDescription = txt(
R.string.subscription_episode_released,
latestEpisode,
savedData.name
).asString(context)
val intent = Intent(context, MainActivity::class.java).apply {
data = savedData.url.toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getActivity(context, 0, intent, 0)
}
val poster = ioWork {
savedData.posterUrl?.let { url ->
context.getImageBitmapFromUrl(
url,
savedData.posterHeaders
)
}
}
val updateNotification =
updateNotificationBuilder.setContentTitle(updateHeader)
.setContentText(updateDescription)
.setContentIntent(pendingIntent)
.setLargeIcon(poster)
.build()
notificationManager.notify(id, updateNotification)
}
// You can probably get some issues here since this is async but it does not matter much.
updateProgress(max, ++progress, false)
} catch (_: Throwable) {
}
}
return Result.success()
}
}

View file

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

View file

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

View file

@ -759,11 +759,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return data != ""
}
/** Used to query a saved MediaItem on the list to get the id for removal */
data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null)
data class MediaListItem(@JsonProperty("MediaList") val MediaList: MediaListId? = null)
data class MediaListId(@JsonProperty("id") val id: Long? = null)
private suspend fun postDataAboutId(
id: Int,
type: AniListStatusType,
@ -771,43 +766,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
progress: Int?
): Boolean {
val q =
// Delete item if status type is None
if (type == AniListStatusType.None) {
val userID = getKey<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,
type.value
)]
}, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
id
status
progress
score
}
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
aniListStatusString[maxOf(
0,
type.value
)]
}, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
id
status
progress
score
}
}"""
}
val data = postApi(q)
return data != ""
}

View file

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

View file

@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.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 =

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
}
component = VLC_COMPONENT
putExtra("from_start", !resume)
putExtra("position", position)
@ -1469,128 +1422,85 @@ 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
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
actors = actors ?: meta.actors
duration = duration ?: meta.duration
rating = rating ?: meta.publicScore
tags = tags ?: meta.genres
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
actors = actors ?: meta.actors
if (this is EpisodeResponse) {
nextAiring = nextAiring ?: meta.nextAiring
}
val realRecommendations = ArrayList<SearchResponse>()
val apiNames = apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}
meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))
}
}
recommendations = recommendations?.union(realRecommendations)?.toList()
?: realRecommendations
if (this is EpisodeResponse) {
nextAiring = nextAiring ?: meta.nextAiring
}
for ((k, v) in syncs ?: emptyMap()) {
syncData[k] = v
}
argamap(
{
if (this !is AnimeLoadResponse) return@argamap
// already exist, no need to run getTracker
if (this.getAniListId() != null && this.getMalId() != null) return@argamap
val realRecommendations = ArrayList<SearchResponse>()
// TODO: fix
val apiNames = apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}
val res = APIHolder.getTracker(
listOfNotNull(
this.engName,
this.name,
this.japName
).filter { it.length > 2 }
.distinct(), // the reason why we filter is due to not wanting smth like " " or "?"
TrackerType.getTypes(this.type),
this.year
meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))
}
}
recommendations = recommendations?.union(realRecommendations)?.toList()
?: realRecommendations
argamap({
addTrailer(meta.trailers)
}, {
if (this !is AnimeLoadResponse) return@argamap
val map =
Kitsu.getEpisodesDetails(
getMalId(),
getAniListId(),
isResponseRequired = false
)
val ids = arrayOf(
AccountManager.malApi.idPrefix to res?.malId?.toString(),
AccountManager.aniListApi.idPrefix to res?.aniId
)
if (ids.any { (id, new) ->
val current = syncData[id]
new != null && current != null && current != new
}
) {
// getTracker fucked up as it conflicts with current implementation
return@argamap
}
// set all the new data, prioritise old correct data
ids.forEach { (id, new) ->
new?.let {
syncData[id] = syncData[id] ?: it
}
}
// set posters, might fuck up due to headers idk
posterUrl = posterUrl ?: res?.image
backgroundPosterUrl = backgroundPosterUrl ?: res?.cover
},
{
if (meta == null) return@argamap
addTrailer(meta.trailers)
}, {
if (this !is AnimeLoadResponse) return@argamap
val map =
Kitsu.getEpisodesDetails(
getMalId(),
getAniListId(),
isResponseRequired = false
)
if (map.isNullOrEmpty()) return@argamap
updateEpisodes = DubStatus.values().map { dubStatus ->
val current =
this.episodes[dubStatus]?.mapIndexed { index, episode ->
episode.apply {
this.episode = this.episode ?: (index + 1)
}
}?.sortedBy { it.episode ?: 0 }?.toMutableList()
if (current.isNullOrEmpty()) return@map false
val episodeNumbers = current.map { ep -> ep.episode!! }
var updateCount = 0
map.forEach { (episode, node) ->
episodeNumbers.binarySearch(episode).let { index ->
current.getOrNull(index)?.let { currentEp ->
current[index] = currentEp.apply {
updateCount++
this.description = this.description ?: node.description?.en
this.name = this.name ?: node.titles?.canonical
this.episode =
this.episode ?: node.num ?: episodeNumbers[index]
this.posterUrl =
this.posterUrl ?: node.thumbnail?.original?.url
}
if (map.isNullOrEmpty()) return@argamap
updateEpisodes = DubStatus.values().map { dubStatus ->
val current =
this.episodes[dubStatus]?.mapIndexed { index, episode ->
episode.apply {
this.episode = this.episode ?: (index + 1)
}
}?.sortedBy { it.episode ?: 0 }?.toMutableList()
if (current.isNullOrEmpty()) return@map false
val episodeNumbers = current.map { ep -> ep.episode!! }
var updateCount = 0
map.forEach { (episode, node) ->
episodeNumbers.binarySearch(episode).let { index ->
current.getOrNull(index)?.let { currentEp ->
current[index] = currentEp.apply {
updateCount++
val currentBack = this
this.description = this.description ?: node.description?.en
this.name = this.name ?: node.titles?.canonical
this.episode =
this.episode ?: node.num ?: episodeNumbers[index]
this.posterUrl =
this.posterUrl ?: node.thumbnail?.original?.url
}
}
}
this.episodes[dubStatus] = current
updateCount > 0
}.any { it }
})
}
this.episodes[dubStatus] = current
updateCount > 0
}.any { it }
})
}
return out to updateEpisodes
}
@ -1717,16 +1627,6 @@ class ResultViewModel2 : ViewModel() {
postResume()
}
private fun postSubscription(loadResponse: LoadResponse) {
if (loadResponse.isEpisodeBased()) {
val id = loadResponse.getId()
val data = DataStoreHelper.getSubscribedData(id)
DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
val isSubscribed = data != null
_subscribeStatus.postValue(isSubscribed)
}
}
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
if (range == null || indexer == null) {
return
@ -1863,7 +1763,6 @@ class ResultViewModel2 : ViewModel() {
) {
currentResponse = loadResponse
postPage(loadResponse, apiRepository)
postSubscription(loadResponse)
if (updateEpisodes)
postEpisodes(loadResponse, updateFillers)
}
@ -2225,7 +2124,7 @@ class ResultViewModel2 : ViewModel() {
autostart: AutoResume?,
loadTrailers: Boolean = true,
) =
ioSafe {
viewModelScope.launchSafe {
_page.postValue(Resource.Loading(url))
_episodes.postValue(ResourceSome.Loading())
@ -2243,7 +2142,7 @@ class ResultViewModel2 : ViewModel() {
"This provider does not exist"
)
)
return@ioSafe
return@launchSafe
}
@ -2254,15 +2153,21 @@ class ResultViewModel2 : ViewModel() {
api
)
}
// TODO: fix
// val validUrlResource = safeApiCall {
// SyncRedirector.redirect(
// url,
// api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
// .replace(GogoanimeProvider().mainUrl, "gogoanime")
// )
// }
if (validUrlResource !is Resource.Success) {
if (validUrlResource is Resource.Failure) {
_page.postValue(validUrlResource)
}
return@ioSafe
return@launchSafe
}
val validUrl = validUrlResource.value
val repo = APIRepository(api)
currentRepo = repo
@ -2272,11 +2177,11 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(data)
}
is Resource.Success -> {
if (!isActive) return@ioSafe
if (!isActive) return@launchSafe
val loadResponse = ioWork {
applyMeta(data.value, currentMeta, currentSync).first
}
if (!isActive) return@ioSafe
if (!isActive) return@launchSafe
val mainId = loadResponse.getId()
preferDubStatus = getDub(mainId) ?: preferDubStatus
@ -2304,7 +2209,7 @@ class ResultViewModel2 : ViewModel() {
updateFillers = showFillers,
apiRepository = repo
)
if (!isActive) return@ioSafe
if (!isActive) return@launchSafe
handleAutoStart(activity, autostart)
}
is Resource.Loading -> {

View file

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

View file

@ -56,50 +56,48 @@ fun getCurrentLocale(context: Context): String {
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto
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()

View file

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

View file

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

View file

@ -1,97 +0,0 @@
package com.lagradost.cloudstream3.ui.settings.testing
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.activityViewModels
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import kotlinx.android.synthetic.main.fragment_testing.*
import kotlinx.android.synthetic.main.view_test.*
class TestFragment : Fragment() {
private val testViewModel: TestViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setUpToolbar(R.string.category_provider_test)
super.onViewCreated(view, savedInstanceState)
provider_test_recycler_view?.adapter = TestResultAdapter(
mutableListOf()
)
testViewModel.init()
if (testViewModel.isRunningTest) {
provider_test?.setState(TestView.TestState.Running)
}
observe(testViewModel.providerProgress) { (passed, failed, total) ->
provider_test?.setProgress(passed, failed, total)
}
observeNullable(testViewModel.providerResults) {
normalSafeApiCall {
val newItems = it.sortedBy { api -> api.first.name }
(provider_test_recycler_view?.adapter as? TestResultAdapter)?.updateList(
newItems
)
}
}
provider_test?.setOnPlayButtonListener { state ->
when (state) {
TestView.TestState.Stopped -> testViewModel.stopTest()
TestView.TestState.Running -> testViewModel.startTest()
TestView.TestState.None -> testViewModel.startTest()
}
}
if (isTrueTvSettings()) {
tests_play_pause?.isFocusableInTouchMode = true
tests_play_pause?.requestFocus()
}
provider_test?.playPauseButton?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
provider_test_appbar?.setExpanded(true, true)
}
}
fun focusRecyclerView() {
// Hack to make it possible to focus the recyclerview.
if (isTrueTvSettings()) {
provider_test_recycler_view?.requestFocus()
provider_test_appbar?.setExpanded(false, true)
}
}
provider_test?.setOnMainClick {
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All)
focusRecyclerView()
}
provider_test?.setOnFailedClick {
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed)
focusRecyclerView()
}
provider_test?.setOnPassedClick {
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed)
focusRecyclerView()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_testing, container, false)
}
}

View file

@ -1,80 +0,0 @@
package com.lagradost.cloudstream3.ui.settings.testing
import android.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.getAllMessages
import com.lagradost.cloudstream3.mvvm.getStackTracePretty
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.android.synthetic.main.provider_test_item.view.*
class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUtils.TestResultProvider>>) :
AppUtils.DiffAdapter<Pair<MainAPI, TestingUtils.TestResultProvider>>(items) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ProviderTestViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.provider_test_item, parent, false),
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ProviderTestViewHolder -> {
val item = items[position]
holder.bind(item.first, item.second)
}
}
}
inner class ProviderTestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val languageText: TextView = itemView.lang_icon
private val providerTitle: TextView = itemView.main_text
private val statusText: TextView = itemView.passed_failed_marker
private val failDescription: TextView = itemView.fail_description
private val logButton: ImageView = itemView.action_button
private fun String.lastLine(): String? {
return this.lines().lastOrNull { it.isNotBlank() }
}
fun bind(api: MainAPI, result: TestingUtils.TestResultProvider) {
languageText.text = getFlagFromIso(api.lang)
providerTitle.text = api.name
val (resultText, resultColor) = if (result.success) {
R.string.test_passed to R.color.colorTestPass
} else {
R.string.test_failed to R.color.colorTestFail
}
statusText.setText(resultText)
statusText.setTextColor(ContextCompat.getColor(itemView.context, resultColor))
val stackTrace = result.exception?.getStackTracePretty(false)?.ifBlank { null }
val messages = result.exception?.getAllMessages()?.ifBlank { null }
val fullLog =
result.log + (messages?.let { "\n\n$it" } ?: "") + (stackTrace?.let { "\n\n$it" } ?: "")
failDescription.text = messages?.lastLine() ?: result.log.lastLine()
logButton.setOnClickListener {
val builder: AlertDialog.Builder =
AlertDialog.Builder(it.context, R.style.AlertDialogCustom)
builder.setMessage(fullLog)
.setTitle(R.string.test_log)
.show()
}
}
}
}

View file

@ -1,119 +0,0 @@
package com.lagradost.cloudstream3.ui.settings.testing
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.AppUtils.animateProgressTo
class TestView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : CardView(context, attrs) {
enum class TestState(@StringRes val stringRes: Int, @DrawableRes val icon: Int) {
None(R.string.start, R.drawable.ic_baseline_play_arrow_24),
// Paused(R.string.resume, R.drawable.ic_baseline_play_arrow_24),
Stopped(R.string.restart, R.drawable.ic_baseline_play_arrow_24),
Running(R.string.stop, R.drawable.pause_to_play),
}
var mainSection: View? = null
var testsPassedSection: View? = null
var testsFailedSection: View? = null
var mainSectionText: TextView? = null
var mainSectionHeader: TextView? = null
var testsPassedSectionText: TextView? = null
var testsFailedSectionText: TextView? = null
var totalProgressBar: ContentLoadingProgressBar? = null
var playPauseButton: MaterialButton? = null
var stateListener: (TestState) -> Unit = {}
private var state = TestState.None
init {
LayoutInflater.from(context).inflate(R.layout.view_test, this, true)
mainSection = findViewById(R.id.main_test_section)
testsPassedSection = findViewById(R.id.passed_test_section)
testsFailedSection = findViewById(R.id.failed_test_section)
mainSectionHeader = findViewById(R.id.main_test_header)
mainSectionText = findViewById(R.id.main_test_section_progress)
testsPassedSectionText = findViewById(R.id.passed_test_section_progress)
testsFailedSectionText = findViewById(R.id.failed_test_section_progress)
totalProgressBar = findViewById(R.id.test_total_progress)
playPauseButton = findViewById(R.id.tests_play_pause)
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.TestView)
val headerText = typedArray.getString(R.styleable.TestView_header_text)
mainSectionHeader?.text = headerText
typedArray.recycle()
}
playPauseButton?.setOnClickListener {
val newState = when (state) {
TestState.None -> TestState.Running
TestState.Running -> TestState.Stopped
TestState.Stopped -> TestState.Running
}
setState(newState)
}
}
fun setOnPlayButtonListener(listener: (TestState) -> Unit) {
stateListener = listener
}
fun setState(newState: TestState) {
state = newState
stateListener.invoke(newState)
playPauseButton?.setText(newState.stringRes)
playPauseButton?.icon = ContextCompat.getDrawable(context, newState.icon)
}
fun setProgress(passed: Int, failed: Int, total: Int?) {
val totalProgress = passed + failed
mainSectionText?.text = "$totalProgress / ${total?.toString() ?: "?"}"
testsPassedSectionText?.text = passed.toString()
testsFailedSectionText?.text = failed.toString()
totalProgressBar?.max = (total ?: 0) * 1000
totalProgressBar?.animateProgressTo(totalProgress * 1000)
totalProgressBar?.isVisible = !(totalProgress == 0 || (total ?: 0) == 0)
if (totalProgress == total) {
setState(TestState.Stopped)
}
}
fun setMainHeader(@StringRes header: Int) {
mainSectionHeader?.setText(header)
}
fun setOnMainClick(listener: OnClickListener) {
mainSection?.setOnClickListener(listener)
}
fun setOnPassedClick(listener: OnClickListener) {
testsPassedSection?.setOnClickListener(listener)
}
fun setOnFailedClick(listener: OnClickListener) {
testsFailedSection?.setOnClickListener(listener)
}
}

View file

@ -1,108 +0,0 @@
package com.lagradost.cloudstream3.ui.settings.testing
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
class TestViewModel : ViewModel() {
data class TestProgress(
val passed: Int,
val failed: Int,
val total: Int
)
enum class ProviderFilter {
All,
Passed,
Failed
}
private val _providerProgress = MutableLiveData<TestProgress>(null)
val providerProgress: LiveData<TestProgress> = _providerProgress
private val _providerResults =
MutableLiveData<List<Pair<MainAPI, TestingUtils.TestResultProvider>>>(
emptyList()
)
val providerResults: LiveData<List<Pair<MainAPI, TestingUtils.TestResultProvider>>> =
_providerResults
private var scope: CoroutineScope? = null
val isRunningTest
get() = scope != null
private var filter = ProviderFilter.All
private val providers = threadSafeListOf<Pair<MainAPI, TestingUtils.TestResultProvider>>()
private var passed = 0
private var failed = 0
private var total = 0
private fun updateProgress() {
_providerProgress.postValue(TestProgress(passed, failed, total))
postProviders()
}
private fun postProviders() {
synchronized(providers) {
val filtered = when (filter) {
ProviderFilter.All -> providers
ProviderFilter.Passed -> providers.filter { it.second.success }
ProviderFilter.Failed -> providers.filter { !it.second.success }
}
_providerResults.postValue(filtered)
}
}
fun setFilterMethod(filter: ProviderFilter) {
if (this.filter == filter) return
this.filter = filter
postProviders()
}
private fun addProvider(api: MainAPI, results: TestingUtils.TestResultProvider) {
synchronized(providers) {
val index = providers.indexOfFirst { it.first == api }
if (index == -1) {
providers.add(api to results)
if (results.success) passed++ else failed++
} else {
providers[index] = api to results
}
updateProgress()
}
}
fun init() {
val apis = APIHolder.allProviders
total = apis.size
updateProgress()
}
fun startTest() {
scope = CoroutineScope(Dispatchers.Default)
val apis = APIHolder.allProviders
total = apis.size
failed = 0
passed = 0
providers.clear()
updateProgress()
TestingUtils.getDeferredProviderTests(scope ?: return, apis, ::println) { api, result ->
addProvider(api, result)
}
}
fun stopTest() {
scope?.cancel()
scope = null
}
}

View file

@ -1,11 +1,8 @@
package com.lagradost.cloudstream3.utils
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,13 +735,8 @@ object AppUtils {
return networkInfo.any {
conManager.getNetworkCapabilities(it)
?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
} &&
!networkInfo.any {
conManager.getNetworkCapabilities(it)
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
}
}
}
private fun Activity?.cacheClass(clazz: String?) {
clazz?.let { c ->

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,267 +0,0 @@
package com.lagradost.cloudstream3.utils
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.mvvm.logError
import kotlinx.coroutines.*
import org.junit.Assert
object TestingUtils {
open class TestResult(val success: Boolean) {
companion object {
val Pass = TestResult(true)
val Fail = TestResult(false)
}
}
class TestResultSearch(val results: List<SearchResponse>) : TestResult(true)
class TestResultLoad(val extractorData: String) : TestResult(true)
class TestResultProvider(success: Boolean, val log: String, val exception: Throwable?) :
TestResult(success)
@Throws(AssertionError::class, CancellationException::class)
suspend fun testHomepage(
api: MainAPI,
logger: (String) -> Unit
): TestResult {
if (api.hasMainPage) {
try {
val f = api.mainPage.first()
val homepage =
api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages))
when {
homepage == null -> {
logger.invoke("Homepage provider ${api.name} did not correctly load homepage!")
}
homepage.items.isEmpty() -> {
logger.invoke("Homepage provider ${api.name} does not contain any items!")
}
homepage.items.any { it.list.isEmpty() } -> {
logger.invoke("Homepage provider ${api.name} does not have any items on result!")
}
}
} catch (e: Throwable) {
if (e is NotImplementedError) {
Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented")
} else if (e is CancellationException) {
throw e
}
logError(e)
}
}
return TestResult.Pass
}
@Throws(AssertionError::class, CancellationException::class)
private suspend fun testSearch(
api: MainAPI
): TestResult {
val searchQueries = listOf("over", "iron", "guy")
val searchResults = searchQueries.firstNotNullOfOrNull { query ->
try {
api.search(query).takeIf { !it.isNullOrEmpty() }
} catch (e: Throwable) {
if (e is NotImplementedError) {
Assert.fail("Provider has not implemented search()")
} else if (e is CancellationException) {
throw e
}
logError(e)
null
}
}
return if (searchResults.isNullOrEmpty()) {
Assert.fail("Api ${api.name} did not return any valid search responses")
TestResult.Fail // Should not be reached
} else {
TestResultSearch(searchResults)
}
}
@Throws(AssertionError::class, CancellationException::class)
private suspend fun testLoad(
api: MainAPI,
result: SearchResponse,
logger: (String) -> Unit
): TestResult {
try {
Assert.assertEquals(
"Invalid apiName on SearchResponse on ${api.name}",
result.apiName,
api.name
)
val loadResponse = api.load(result.url)
if (loadResponse == null) {
logger.invoke("Returned null loadResponse on ${result.url} on ${api.name}")
return TestResult.Fail
}
Assert.assertEquals(
"Invalid apiName on LoadResponse on ${api.name}",
loadResponse.apiName,
result.apiName
)
Assert.assertTrue(
"Api ${api.name} on load does not contain any of the supportedTypes: ${loadResponse.type}",
api.supportedTypes.contains(loadResponse.type)
)
val url = when (loadResponse) {
is AnimeLoadResponse -> {
val gotNoEpisodes =
loadResponse.episodes.keys.isEmpty() || loadResponse.episodes.keys.any { loadResponse.episodes[it].isNullOrEmpty() }
if (gotNoEpisodes) {
logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}")
return TestResult.Fail
}
(loadResponse.episodes[loadResponse.episodes.keys.firstOrNull()])?.firstOrNull()?.data
}
is MovieLoadResponse -> {
val gotNoEpisodes = loadResponse.dataUrl.isBlank()
if (gotNoEpisodes) {
logger.invoke("Api ${api.name} got no movie on ${loadResponse.url}")
return TestResult.Fail
}
loadResponse.dataUrl
}
is TvSeriesLoadResponse -> {
val gotNoEpisodes = loadResponse.episodes.isEmpty()
if (gotNoEpisodes) {
logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}")
return TestResult.Fail
}
loadResponse.episodes.firstOrNull()?.data
}
is LiveStreamLoadResponse -> {
loadResponse.dataUrl
}
else -> {
logger.invoke("Unknown load response: ${loadResponse.javaClass.name}")
return TestResult.Fail
}
} ?: return TestResult.Fail
return TestResultLoad(url)
// val loadTest = testLoadResponse(api, load, logger)
// if (loadTest is TestResultLoad) {
// testLinkLoading(api, loadTest.extractorData, logger).success
// } else {
// false
// }
// if (!validResults) {
// logger("Api ${api.name} did not load on the first search results: ${smallSearchResults.map { it.name }}")
// }
// return TestResult(validResults)
} catch (e: Throwable) {
if (e is NotImplementedError) {
Assert.fail("Provider has not implemented load()")
}
throw e
}
}
@Throws(AssertionError::class, CancellationException::class)
private suspend fun testLinkLoading(
api: MainAPI,
url: String?,
logger: (String) -> Unit
): TestResult {
Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
if (url == null) return TestResult.Fail // Should never trigger
var linksLoaded = 0
try {
val success = api.loadLinks(url, false, {}) { link ->
logger.invoke("Video loaded: ${link.name}")
Assert.assertTrue(
"Api ${api.name} returns link with invalid url ${link.url}",
link.url.length > 4
)
linksLoaded++
}
if (success) {
logger.invoke("Links loaded: $linksLoaded")
return TestResult(linksLoaded > 0)
} else {
Assert.fail("Api ${api.name} returns false on loadLinks() with $linksLoaded links loaded")
}
} catch (e: Throwable) {
when (e) {
is NotImplementedError -> {
Assert.fail("Provider has not implemented loadLinks()")
}
else -> {
logger.invoke("Failed link loading on ${api.name} using data: $url")
throw e
}
}
}
return TestResult.Pass
}
fun getDeferredProviderTests(
scope: CoroutineScope,
providers: List<MainAPI>,
logger: (String) -> Unit,
callback: (MainAPI, TestResultProvider) -> Unit
) {
providers.forEach { api ->
scope.launch {
var log = ""
fun addToLog(string: String) {
log += string + "\n"
logger.invoke(string)
}
fun getLog(): String {
return log.removeSuffix("\n")
}
val result = try {
addToLog("Trying ${api.name}")
// Test Homepage
val homepage = testHomepage(api, logger).success
Assert.assertTrue("Homepage failed to load", homepage)
// Test Search Results
val searchResults = testSearch(api)
Assert.assertTrue("Failed to get search results", searchResults.success)
searchResults as TestResultSearch
// Test Load and LoadLinks
// Only try the first 3 search results to prevent spamming
val success = searchResults.results.take(3).any { searchResponse ->
addToLog("Testing search result: ${searchResponse.url}")
val loadResponse = testLoad(api, searchResponse, ::addToLog)
if (loadResponse !is TestResultLoad) {
false
} else {
testLinkLoading(api, loadResponse.extractorData, ::addToLog).success
}
}
if (success) {
logger.invoke("Success ${api.name}")
TestResultProvider(true, getLog(), null)
} else {
logger.invoke("Error ${api.name}")
TestResultProvider(false, getLog(), null)
}
} catch (e: Throwable) {
TestResultProvider(false, getLog(), e)
}
callback.invoke(api, result)
}
}
}
}

View file

@ -20,7 +20,6 @@ import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.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)
}
}

View file

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="?attr/white"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,14.67L3.41,6.09L2,7.5l8.5,8.5H4v2h16v-2h-6.5l5.15,-5.15C18.91,10.95 19.2,11 19.5,11c1.38,0 2.5,-1.12 2.5,-2.5S20.88,6 19.5,6S17,7.12 17,8.5c0,0.35 0.07,0.67 0.2,0.97L12,14.67z"/>
</vector>

View file

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="?attr/white"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"/>
</vector>

View file

@ -1,5 +0,0 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/white" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/white" android:pathData="M20.41,8.41l-4.83,-4.83C15.21,3.21 14.7,3 14.17,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V9.83C21,9.3 20.79,8.79 20.41,8.41zM7,7h7v2H7V7zM17,17H7v-2h10V17zM17,13H7v-2h10V13z"/>
</vector>

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="108dp"
android:height="108dp"
android:viewportWidth="50"
android:viewportHeight="50"
android:name="vector">
<group android:scaleX="0.1755477"
android:scaleY="0.1755477"
android:translateX="0"
android:translateY="0">
<path android:name="path"
android:pathData="M 245.05 148.63 C 242.249 148.627 239.463 149.052 236.79 149.89 C 235.151 141.364 230.698 133.63 224.147 127.931 C 217.597 122.233 209.321 118.893 200.65 118.45 C 195.913 105.431 186.788 94.458 174.851 87.427 C 162.914 80.396 148.893 77.735 135.21 79.905 C 121.527 82.074 109.017 88.941 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.615 148.64 23.582 151.553 18.403 156.733 C 13.223 161.912 10.31 168.945 10.31 176.27 C 10.31 183.595 13.223 190.628 18.403 195.807 C 23.582 200.987 30.615 203.9 37.94 203.9 L 245.05 203.9 C 252.375 203.9 259.408 200.987 264.587 195.807 C 269.767 190.628 272.68 183.595 272.68 176.27 C 272.68 168.945 269.767 161.912 264.587 156.733 C 259.408 151.553 252.375 148.64 245.05 148.64 Z"
android:fillColor="#FFFFFF" android:strokeWidth="1"
tools:ignore="VectorPath"
android:fillAlpha="0.55"/>
<path android:name="path_1" android:pathData="M 208.61 125 C 208.61 123.22 208.55 121.45 208.48 119.69 C 205.919 119.01 203.296 118.595 200.65 118.45 C 195.913 105.431 186.788 94.458 174.851 87.427 C 162.914 80.396 148.893 77.735 135.21 79.905 C 121.527 82.074 109.017 88.941 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.615 148.64 23.582 151.553 18.403 156.733 C 13.223 161.912 10.31 168.945 10.31 176.27 C 10.31 183.595 13.223 190.628 18.403 195.807 C 23.582 200.987 30.615 203.9 37.94 203.9 L 179 203.9 C 198.116 182.073 208.646 154.015 208.61 125 Z"
android:fillColor="#FFFFFF" android:strokeWidth="1"
android:fillAlpha="0.55"/>
<path android:name="path_2" android:pathData="M 99.84 99.32 C 89.871 95.945 79.051 96.024 69.133 99.545 C 59.215 103.065 50.765 109.826 45.155 118.73 C 39.545 127.634 37.094 138.174 38.2 148.64 L 37.94 148.64 C 30.783 148.665 23.909 151.471 18.779 156.461 C 13.648 161.452 10.653 168.246 10.43 175.399 C 10.207 182.553 12.773 189.52 17.583 194.82 C 22.392 200.121 29.079 203.349 36.22 203.82 C 67.216 202.93 96.673 189.98 118.284 167.742 C 139.895 145.504 151.997 115.689 152 84.68 C 152 83 151.94 81.33 151.87 79.68 C 149.443 79.361 146.998 79.194 144.55 79.18 C 136.095 79.171 127.735 80.962 120.026 84.434 C 112.317 87.907 105.435 92.982 99.84 99.32 Z"
android:fillColor="#FFFFFF" android:strokeWidth="1"
android:fillAlpha="1"/>
</group>
</vector>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="?attr/primaryBlackBackground"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/provider_test_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/settings_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryGrayBackground"
android:paddingTop="@dimen/navbar_height"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIconTint="?attr/iconColor"
app:titleTextColor="?attr/textColor"
tools:title="@string/category_provider_test">
</com.google.android.material.appbar.MaterialToolbar>
<com.lagradost.cloudstream3.ui.settings.testing.TestView
android:id="@+id/provider_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusDown="@id/provider_test_recycler_view"
app:header_text="@string/category_provider_test"
app:layout_scrollFlags="scroll|enterAlways" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/provider_test_recycler_view"
android:clipToPadding="false"
android:paddingBottom="60dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/provider_test_item" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

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

View file

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

View file

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

View file

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:nextFocusRight="@id/action_button"
android:orientation="horizontal"
android:padding="12dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/main_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/textColor"
android:textSize="16sp"
tools:text="Test repository" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/lang_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="5dp"
tools:text="🇷🇼"
android:textColor="?attr/grayTextColor"
tools:visibility="visible" />
<TextView
android:id="@+id/passed_failed_marker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Failed"
tools:visibility="visible" />
</LinearLayout>
<TextView
android:id="@+id/fail_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor"
tools:text="Unable to load videos"
tools:visibility="visible" />
</LinearLayout>
<ImageView
android:id="@+id/action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginStart="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="12dp"
android:src="@drawable/baseline_text_snippet_24"
app:tint="?attr/white" />
</LinearLayout>

View file

@ -23,7 +23,7 @@
android:elevation="10dp"
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">

View file

@ -1,138 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?attr/primaryBlackBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="10dp">
<LinearLayout
android:id="@+id/main_test_section"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/outline_drawable_less"
android:gravity="center"
android:orientation="horizontal"
android:paddingHorizontal="10dp"
android:paddingVertical="10dp">
<TextView
android:id="@+id/main_test_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="17sp"
tools:text="Homepage test" />
<TextView
android:id="@+id/main_test_section_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:textSize="17sp"
tools:text="67 / 120 " />
</LinearLayout>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:cardBackgroundColor="?attr/primaryGrayBackground"
app:cardCornerRadius="@dimen/rounded_image_radius">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/passed_test_section"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/outline_drawable_less"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Tests passed"
android:textSize="17sp" />
<TextView
android:id="@+id/passed_test_section_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:textSize="17sp"
tools:text="55" />
</LinearLayout>
<LinearLayout
android:id="@+id/failed_test_section"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/outline_drawable_less"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Tests failed"
android:textSize="17sp" />
<TextView
android:id="@+id/failed_test_section_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:textSize="17sp"
tools:text="12" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/tests_play_pause"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:layout_margin="10dp"
android:text="@string/start"
app:icon="@drawable/ic_baseline_play_arrow_24">
</com.google.android.material.button.MaterialButton>
</LinearLayout>
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/test_total_progress"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="bottom"
android:layout_marginBottom="-1.5dp"
android:progressBackgroundTint="?attr/colorPrimary"
android:progressTint="?attr/colorPrimary"
android:visibility="gone"
tools:progress="50" />
</androidx.cardview.widget.CardView>

View file

@ -519,23 +519,6 @@
app:popExitAnim="@anim/exit_anim"
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"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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">नया अपडेट आया है!

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,185 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="next_episode_time_min_format" formatted="true">%d分</string>
<string name="title_downloads">ダウンロード</string>
<string name="title_search">検索</string>
<string name="title_settings">設定</string>
<string name="result_share">シェア</string>
<string name="movies">映画</string>
<string name="title_home">ホーム</string>
<string name="library">ライブラリ</string>
<string name="home_play">再生</string>
<string name="next_episode_time_day_format" formatted="true">%d日 %d時間%d分</string>
<string name="next_episode_time_hour_format" formatted="true">%d時間%d分</string>
<string name="search_hint">検索…</string>
<string name="download">ダウンロード</string>
<string name="home_info">情報</string>
<string name="season">シーズン</string>
<string name="trailer">予告編</string>
<string name="tv_series_singular">シリーズ</string>
<string name="episodes">エピソード</string>
<string name="player_speed_text_format" formatted="true">再生速度 (%.2fx)</string>
<string name="next_episode">次のエピソード</string>
<string name="sort_apply">適用</string>
<string name="category_account">アカウント</string>
<string name="cartoons">カートゥーン</string>
<string name="tv_series">TVシリーズ</string>
<string name="torrent">トレント</string>
<string name="documentaries">ドキュメンタリー</string>
<string name="ova">OVA</string>
<string name="asian_drama">アジアドラマ</string>
<string name="livestreams">ライブ配信</string>
<string name="movies_singular">映画</string>
<string name="others">その他</string>
<string name="cartoons_singular">カートゥーン</string>
<string name="torrent_singular">トレント</string>
<string name="documentaries_singular">ドキュメンタリー</string>
<string name="asian_drama_singular">アジアドラマ</string>
<string name="live_singular">ライブ配信</string>
<string name="nsfw_singular">NSFW</string>
<string name="sort_cancel">キャンセル</string>
<string name="anime">アニメ</string>
<string name="video_lock">ロック</string>
<string name="video_source">ソース</string>
<string name="nsfw">NSFW</string>
<string name="clear_history">履歴を削除</string>
<string name="continue_watching">視聴中コンテンツ</string>
<string name="category_general">全般</string>
<string name="other_singular">動画</string>
<string name="category_player">プレーヤー</string>
<string name="type_plan_to_watch">懐う</string>
<string name="play_trailer_button">予告編を再生</string>
<string name="episode_short">エピソード</string>
<string name="type_watching">視聴</string>
<string name="result_tags">ジャンル</string>
<string name="play_movie_button">映画を再生</string>
<string name="pick_subtitle">字幕</string>
<string name="app_name">CloudStream</string>
<string name="play_with_app_name">CloudStreamで再生</string>
<string name="browser">ブラウザ</string>
<string name="type_completed">完成</string>
<string name="type_dropped">放置</string>
<string name="type_on_hold">保留</string>
<string name="loading">ローディング…</string>
<string name="result_open_in_browser">ブラウザで開く</string>
<string name="season_short">シーズン</string>
<string name="resume_time_left" formatted="true">残り
\n%d分</string>
<string name="play_episode">再生エピソード</string>
<string name="downloaded">ダウンロード済</string>
<string name="pref_category_backup">バックアップ</string>
<string name="home_source">ソース</string>
<string name="history">履歴</string>
<string name="result_poster_img_des">ポスター</string>
<string name="type_none">なし</string>
<string name="sort_copy">コピー</string>
<string name="sort_close">閉じる</string>
<string name="sort_save">保存</string>
<string name="sort_clear">消去</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%sエピ%d</string>
<string name="cast_format" formatted="true">出演者:%s</string>
<string name="search_poster_img_des">ポスター</string>
<string name="episode_poster_img_des">エピソードポスター</string>
<string name="home_main_poster_img_des">主要ポスター</string>
<string name="home_next_random_img_des">次のランダム</string>
<string name="go_back_img_des">戻り</string>
<string name="rated_format" formatted="true">視聴率 %.1f</string>
<string name="new_update_format" formatted="true">新しいアップデートを発見!
\n%s -&gt; %s</string>
<string name="duration_format" formatted="true">%d分</string>
<string name="search_hint_site" formatted="true">%sを検索…</string>
<string name="pick_source">ソース</string>
<string name="filler" formatted="true">ろくごうきじ</string>
<string name="reload_error">接続を再試行…</string>
<string name="go_back">戻り</string>
<string name="action_remove_from_bookmarks">削除</string>
<string name="home_more_info">詳細情報</string>
<string name="home_expanded_hide">閉じる</string>
<string name="category_updates">アップデート・バックアップ</string>
<string name="app_language">アプリ言語</string>
<string name="github">GitHubギットハブ</string>
<string name="go_back_30">-30</string>
<string name="go_forward_30">+30</string>
<string name="legal_notice">免責</string>
<string name="pref_category_extensions">拡張機能</string>
<string name="pref_category_app_updates">アプリ更新</string>
<string name="category_providers">提供者</string>
<string name="pref_category_subtitles">字幕</string>
<string name="pref_category_ui_features">特徴</string>
<string name="pref_category_defaults">デフォルト</string>
<string name="automatic">自動</string>
<string name="home_random">任意</string>
<string name="extensions">拡張機能</string>
<string name="pref_category_links">リンク</string>
<string name="pref_category_android_tv">Android TV</string>
<string name="login">ログイン</string>
<string name="logout">ログアウト</string>
<string name="max">最大</string>
<string name="min">最小</string>
<string name="none">なし</string>
<string name="next"></string>
<string name="is_adult">18+</string>
<string name="no"></string>
<string name="open_with">で開く</string>
<string name="episode">エピソード</string>
<string name="duration">時間</string>
<string name="synopsis">概要</string>
<string name="site">サイト</string>
<string name="used_storage">使用</string>
<string name="app_storage">アプリ</string>
<string name="action_open_watching">詳細情報</string>
<string name="action_remove_watching">削除</string>
<string name="picture_in_picture">ピクチャーインピクチャー</string>
<string name="player_subtitles_settings">字幕</string>
<string name="settings_info">情報</string>
<string name="pause">一時停止</string>
<string name="play_episode_toast">再生エピソード</string>
<string name="delete">削除</string>
<string name="start">開始</string>
<string name="status">状態</string>
<string name="year"></string>
<string name="resume">再開</string>
<string name="test_failed">失敗</string>
<string name="test_passed">合格</string>
<string name="free_storage">空き</string>
<string name="status_completed">完成</string>
<string name="status_ongoing">進行中</string>
<string name="normal">デフォルト</string>
<string name="player_settings_play_in_browser">ウェブブラウザ</string>
<string name="player_settings_play_in_vlc">VLC</string>
<string name="player_settings_play_in_mpv">MPV</string>
<string name="extension_language">言語</string>
<string name="extension_authors">作成者</string>
<string name="extension_size">サイズ</string>
<string name="extension_status">状態</string>
<string name="extension_version">バージョン</string>
<string name="extension_rating" formatted="true">視聴率 %s</string>
<string name="rating">視聴率</string>
<string name="default_subtitles">デフォルト</string>
<string name="download_failed">ダウンロード失敗</string>
<string name="download_started">ダウンロード開始</string>
<string name="download_done">ダウンロード完了</string>
<string name="download_canceled">ダウンロード終了</string>
<string name="stream">ストリーム</string>
<string name="update_started">アップデート開始</string>
<string name="no_season">シーズンなし</string>
<string name="no_subtitles">字幕なし</string>
<string name="video_aspect_ratio_resize">アスペクト比</string>
<string name="skip_loading">ロードをスキップする</string>
<string name="episode_more_options_des">その他のオプション</string>
<string name="no_data">データなし</string>
<string name="downloading">ダウンロード中</string>
<string name="error_bookmarks_text">ブックマーク</string>
<string name="download_storage_text">内部記憶装置</string>
<string name="download_paused">ダウンロードが一時停止</string>
<string name="provider_info_meta">メタデータはこのサイトでは提供されません。メタデータがサイト上に存在しない場合、ビデオの読み込みに失敗します。</string>
<string name="torrent_plot">記述</string>
<string name="show_log_cat">Logcat 🐈を表示</string>
<string name="test_log">ログ</string>
<string name="search">検索</string>
<string name="discord">Discordに参加</string>
<string name="update">アップデート</string>
<string name="check_for_update">アップデートを確認</string>
<string name="show_title">作品名</string>
<string name="update_notification_installing">アプリのアップデートをインストール中…</string>
</resources>

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