Merge branch 'recloudstream:master' into master

This commit is contained in:
Phisher98 2024-06-26 22:38:09 +05:30 committed by GitHub
commit d30fe2aa52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
149 changed files with 4952 additions and 1943 deletions

2
.github/locales.py vendored
View file

@ -53,6 +53,8 @@ for file in glob.glob(f"{XML_NAME}*/strings.xml"):
try:
tree = ET.parse(file)
for child in tree.getroot():
if not child.text:
continue
if child.text.startswith("\\@string/"):
print(f"[{file}] fixing {child.attrib['name']}")
child.text = child.text.replace("\\@string/", "@string/")

View file

@ -164,7 +164,7 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
@ -174,7 +174,7 @@ dependencies {
// Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("com.google.android.material:material:1.11.0")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
@ -185,7 +185,7 @@ dependencies {
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
implementation("com.google.guava:guava:32.1.3-android")
implementation("com.google.guava:guava:33.2.0-android")
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
// Media 3 (ExoPlayer)
@ -202,7 +202,7 @@ dependencies {
// PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
implementation("com.github.TeamNewPipe.NewPipeExtractor:NewPipeExtractor:6dc25f7b97") /* For Trailers
implementation("com.github.teamnewpipe:NewPipeExtractor:fafd471") /* For Trailers
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
@ -217,11 +217,10 @@ dependencies {
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
implementation("io.github.g0dkar:qrcode-kotlin:4.1.1") // QR code for PIN Auth on TV
// Extensions & Other Libs
implementation("org.mozilla:rhino:1.7.13") /* run JavaScript
^ Don't Bump RhinoJS to 1.7.14,`NoClassDefFoundError` Occurs and Trailers won't play (even with Desugaring)
NewPipeExtractor Issue */
implementation("org.mozilla:rhino:1.7.15") // run JavaScript
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9

View file

@ -29,6 +29,7 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.navigationrail.NavigationRailView
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.MainActivity.Companion.resumeApps
import com.lagradost.cloudstream3.databinding.ToastBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType

View file

@ -31,19 +31,16 @@ import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.absoluteValue
const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
val mapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
/**
* Defines the constant for the all languages preference, if this is set then it is
* the equivalent of all languages being set
**/
const val AllLanguagesName = "universal"
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
val mapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
object APIHolder {
val unixTime: Long
get() = System.currentTimeMillis() / 1000L
@ -121,7 +118,8 @@ object APIHolder {
fun LoadResponse.getId(): Int {
// this fixes an issue with outdated api as getLoadResponseIdFromUrl might be fucked
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null) ?: getLoadResponseIdFromUrl(url, apiName)
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null)
?: getLoadResponseIdFromUrl(url, apiName)
}
/**
@ -222,10 +220,15 @@ object APIHolder {
} ?: false
val matchingTypes = types?.any { it.name.equals(media.format, true) } == true
if(lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
if (lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
} ?: return null
Tracker(res.idMal, res.id.toString(), res.coverImage?.extraLarge ?: res.coverImage?.large, res.bannerImage)
Tracker(
res.idMal,
res.id.toString(),
res.coverImage?.extraLarge ?: res.coverImage?.large,
res.bannerImage
)
} catch (t: Throwable) {
logError(t)
null
@ -619,7 +622,7 @@ abstract class MainAPI {
/**Used for testing and can be used to disable the providers if WebView is not available*/
open val usesWebView = false
/** Determines which plugin a given provider is from */
/** Determines which plugin a given provider is from. This is the full path to the plugin. */
var sourcePlugin: String? = null
open val hasMainPage = false
@ -866,6 +869,7 @@ enum class TvType(value: Int?) {
Others(12),
Music(13),
AudioBook(14),
/** Wont load the built in player, make your own interaction */
CustomMedia(15),
}
@ -1253,13 +1257,15 @@ interface LoadResponse {
fun LoadResponse.getImdbId(): String? {
return normalSafeApiCall {
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Imdb)
SimklApi.readIdFromString(this.syncData[simklIdPrefix])
?.get(SimklApi.Companion.SyncServices.Imdb)
}
}
fun LoadResponse.getTMDbId(): String? {
return normalSafeApiCall {
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Tmdb)
SimklApi.readIdFromString(this.syncData[simklIdPrefix])
?.get(SimklApi.Companion.SyncServices.Tmdb)
}
}
@ -1448,11 +1454,24 @@ fun TvType?.isEpisodeBased(): Boolean {
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
}
data class NextAiring(
val episode: Int,
val unixTime: Long,
)
val season: Int? = null,
) {
/**
* Secondary constructor for backwards compatibility without season.
* TODO Remove this constructor after there is a new stable release and extensions are updated to support season.
*/
constructor(
episode: Int,
unixTime: Long,
) : this (
episode,
unixTime,
null
)
}
/**
* @param season To be mapped with episode season, not shown in UI if displaySeason is defined
@ -1543,8 +1562,26 @@ data class TorrentLoadResponse(
posterHeaders: Map<String, String>? = null,
backgroundPosterUrl: String? = null,
) : this(
name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers,
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
name,
url,
apiName,
magnet,
torrent,
plot,
type,
posterUrl,
year,
rating,
tags,
duration,
trailers,
recommendations,
actors,
comingSoon,
syncData,
posterHeaders,
backgroundPosterUrl,
null
)
}
@ -1596,7 +1633,8 @@ data class AnimeLoadResponse(
return this.episodes.maxOf { (_, episodes) ->
episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
val episodeSeason =
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season.
episodeSeason in 1..<season
}
@ -1633,9 +1671,31 @@ data class AnimeLoadResponse(
seasonNames: List<SeasonData>? = null,
backgroundPosterUrl: String? = null,
) : this(
engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags,
synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders,
nextAiring, seasonNames, backgroundPosterUrl, null
engName,
japName,
name,
url,
apiName,
type,
posterUrl,
year,
episodes,
showStatus,
plot,
tags,
synonyms,
rating,
duration,
trailers,
recommendations,
actors,
comingSoon,
syncData,
posterHeaders,
nextAiring,
seasonNames,
backgroundPosterUrl,
null
)
}
@ -1767,7 +1827,7 @@ data class MovieLoadResponse(
backgroundPosterUrl: String? = null,
) : this(
name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers,
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl,null
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
)
}
@ -1910,7 +1970,8 @@ data class TvSeriesLoadResponse(
return episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
val episodeSeason =
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season.
episodeSeason in 1..<season
} + episode
@ -1943,9 +2004,28 @@ data class TvSeriesLoadResponse(
seasonNames: List<SeasonData>? = null,
backgroundPosterUrl: String? = null,
) : this(
name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration,
trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames,
backgroundPosterUrl, null
name,
url,
apiName,
type,
episodes,
posterUrl,
year,
plot,
showStatus,
rating,
tags,
duration,
trailers,
recommendations,
actors,
comingSoon,
syncData,
posterHeaders,
nextAiring,
seasonNames,
backgroundPosterUrl,
null
)
}
@ -2009,6 +2089,7 @@ data class AniSearch(
@JsonProperty("extraLarge") var extraLarge: String? = null,
@JsonProperty("large") var large: String? = null,
)
data class Title(
@JsonProperty("romaji") var romaji: String? = null,
@JsonProperty("english") var english: String? = null,

View file

@ -134,7 +134,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
@ -161,6 +161,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.fcast.FcastManager
import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser
import com.lagradost.safefile.SafeFile
@ -173,7 +174,6 @@ import java.net.URLDecoder
import java.nio.charset.Charset
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.reflect.KClass
import kotlin.system.exitProcess
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
@ -186,21 +186,23 @@ import kotlin.system.exitProcess
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
const val VLC_PACKAGE = "org.videolan.vlc"
const val MPV_PACKAGE = "is.xyz.mpv"
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
companion object {
const val VLC_PACKAGE = "org.videolan.vlc"
const val MPV_PACKAGE = "is.xyz.mpv"
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
//TODO REFACTOR AF
open class ResultResume(
//TODO REFACTOR AF
open class ResultResume(
val packageString: String,
val action: String = Intent.ACTION_VIEW,
val position: String? = null,
val duration: String? = null,
var launcher: ActivityResultLauncher<Intent>? = null,
) {
) {
val defaultTime = -1L
val lastId get() = "${packageString}_last_open_id"
@ -224,9 +226,9 @@ open class ResultResume(
open fun getDuration(intent: Intent?): Long {
return defaultTime
}
}
}
val VLC = object : 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) {
@ -236,7 +238,7 @@ val VLC = object : ResultResume(
},
"extra_position",
"extra_duration",
) {
) {
override fun getPosition(intent: Intent?): Long {
return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
}
@ -244,59 +246,32 @@ val VLC = object : ResultResume(
override fun getDuration(intent: Intent?): Long {
return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
}
}
}
val MPV = object : ResultResume(
val MPV = object : ResultResume(
MPV_PACKAGE,
//"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
position = "position",
duration = "duration",
) {
) {
override fun getPosition(intent: Intent?): Long {
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong()
?: defaultTime
}
override fun getDuration(intent: Intent?): Long {
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong()
?: defaultTime
}
}
}
val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
val resumeApps = arrayOf(
val resumeApps = arrayOf(
VLC, MPV, WEB_VIDEO
)
// Short name for requests client to make it nicer to use
var app = Requests(responseParser = object : ResponseParser {
val mapper: ObjectMapper = jacksonObjectMapper().configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false
)
override fun <T : Any> parse(text: String, kClass: KClass<T>): T {
return mapper.readValue(text, kClass.java)
}
override fun <T : Any> parseSafe(text: String, kClass: KClass<T>): T? {
return try {
mapper.readValue(text, kClass.java)
} catch (e: Exception) {
null
}
}
override fun writeValueAsString(obj: Any): String {
return mapper.writeValueAsString(obj)
}
}).apply {
defaultHeaders = mapOf("user-agent" to USER_AGENT)
}
class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
BiometricAuthenticator.BiometricAuthCallback {
companion object {
const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false
var lastError: String? = null
@ -676,7 +651,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
}
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val response = CommonActivity.dispatchKeyEvent(this, event)
if (response != null)
return response
@ -1402,7 +1377,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
}
}
observe(viewModel.watchStatus,::setWatchStatus)
observe(viewModel.watchStatus, ::setWatchStatus)
observe(syncViewModel.userData, ::setUserData)
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
@ -1756,6 +1731,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
runAutoUpdate()
}
FcastManager().init(this, false)
APIRepository.dubStatusActive = getApiDubstatusSettings()
try {

View file

@ -9,10 +9,16 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
import java.net.URL
class Geodailymotion : Dailymotion() {
override val name = "GeoDailymotion"
override val mainUrl = "https://geo.dailymotion.com"
}
open class Dailymotion : ExtractorApi() {
override val mainUrl = "https://www.dailymotion.com"
override val name = "Dailymotion"
override val requiresReferer = false
private val baseUrl = "https://www.dailymotion.com"
@Suppress("RegExpSimplifiable")
private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex()
@ -34,7 +40,7 @@ open class Dailymotion : ExtractorApi() {
val dmV1st = config.dmInternalData.v1st
val dmTs = config.dmInternalData.ts
val embedder = config.context.embedder
val metaDataUrl = "$mainUrl/player/metadata/video/$id?embedder=$embedder&locale=en-US&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
val metaDataUrl = "$baseUrl/player/metadata/video/$id?embedder=$embedder&locale=en-US&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = req.cookies)
.parsedSafe<MetaData>() ?: return
metaData.qualities.forEach { (_, video) ->
@ -45,16 +51,19 @@ open class Dailymotion : ExtractorApi() {
}
private fun getEmbedUrl(url: String): String? {
if (url.contains("/embed/")) {
if (url.contains("/embed/") || url.contains("/video/")) {
return url
}
val vid = getVideoId(url) ?: return null
return "$mainUrl/embed/video/$vid"
if (url.contains("geo.dailymotion.com")) {
val videoId = url.substringAfter("video=")
return "$baseUrl/embed/video/$videoId"
}
return null
}
private fun getVideoId(url: String): String? {
val path = URL(url).path
val id = path.substringAfter("video/")
val id = path.substringAfter("/video/")
if (id.matches(videoIdRegex)) {
return id
}

View file

@ -7,6 +7,18 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName
import kotlinx.coroutines.delay
class D0000d : DoodLaExtractor() {
override var mainUrl = "https://d0000d.com"
}
class D000dCom : DoodLaExtractor() {
override var mainUrl = "https://d000d.com"
}
class DoodstreamCom : DoodLaExtractor() {
override var mainUrl = "https://doodstream.com"
}
class Dooood : DoodLaExtractor() {
override var mainUrl = "https://dooood.com"
}
@ -56,9 +68,10 @@ open class DoodLaExtractor : ExtractorApi() {
}
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response0 = app.get(url).text // html of DoodStream page to look for /pass_md5/...
val md5 =mainUrl+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null) // get https://dood.ws/pass_md5/...
val trueUrl = app.get(md5, referer = url).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random)
val newUrl= url.replace(mainUrl, "https://d0000d.com")
val response0 = app.get(newUrl).text // html of DoodStream page to look for /pass_md5/...
val md5 ="https://d0000d.com"+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null) // get https://dood.ws/pass_md5/...
val trueUrl = app.get(md5, referer = newUrl).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random)
val quality = Regex("\\d{3,4}p").find(response0.substringAfter("<title>").substringBefore("</title>"))?.groupValues?.get(0)
return listOf(
ExtractorLink(

View file

@ -0,0 +1,37 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
class GoodstreamExtractor : ExtractorApi() {
override var name = "Goodstream"
override val mainUrl = "https://goodstream.uno"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
app.get(url).document.select("script").map { script ->
if (script.data().contains(Regex("file|player"))) {
val urlRegex = Regex("file: \"(https:\\/\\/[a-z0-9.\\/-_?=&]+)\",")
urlRegex.find(script.data())?.groupValues?.get(1).let { link ->
callback.invoke(
ExtractorLink(
name,
name,
link!!,
mainUrl,
Qualities.Unknown.value,
)
)
}
}
}
}
}

View file

@ -21,3 +21,8 @@ class FourPlayRu : ContentX() {
override var name = "FourPlayRu"
override var mainUrl = "https://four.playru.net"
}
class FourPichive : ContentX() {
override var name = "FourPichive"
override var mainUrl = "https://four.pichive.online"
}

View file

@ -1,34 +1,56 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.network.WebViewResolver
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName
class WishembedPro : StreamWishExtractor() {
override val mainUrl = "https://wishembed.pro"
}
class CdnwishCom : StreamWishExtractor() {
override val mainUrl = "https://cdnwish.com"
}
class FlaswishCom : StreamWishExtractor() {
override val mainUrl = "https://flaswish.com"
}
class SfastwishCom : StreamWishExtractor() {
override val mainUrl = "https://sfastwish.com"
}
open class StreamWishExtractor : ExtractorApi() {
override var name = "StreamWish"
override var mainUrl = "https://streamwish.to"
override val mainUrl = "https://streamwish.to"
override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response = app.get(
url, referer = referer ?: "$mainUrl/", interceptor = WebViewResolver(
Regex("""master\.m3u8""")
)
)
val sources = mutableListOf<ExtractorLink>()
if (response.url.contains("m3u8"))
sources.add(
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val doc = app.get(
url,
referer = referer,
allowRedirects = false
).document
var script = doc.select("script").find {
it.html().contains("jwplayer(\"vplayer\").setup(")
}
var scriptContent = script?.html()
val extractedurl = Regex("""sources: \[\{file:"(.*?)"""").find(scriptContent ?: "")?.groupValues?.get(1)
if (!extractedurl.isNullOrBlank()) {
callback(
ExtractorLink(
source = name,
name = name,
url = response.url,
referer = referer ?: "$mainUrl/",
quality = Qualities.Unknown.value,
isM3u8 = true
this.name,
this.name,
extractedurl,
referer ?: "$mainUrl/",
getQualityFromName(""),
extractedurl.contains("m3u8")
)
)
return sources
}
}
}

View file

@ -0,0 +1,70 @@
package com.lagradost.cloudstream3.extractors
import android.util.Base64
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import java.net.URLDecoder
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
class VidSrcTo : ExtractorApi() {
override val name = "VidSrcTo"
override val mainUrl = "https://vidsrc.to"
override val requiresReferer = true
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return
if (res.status != 200) return
res.result?.amap { source ->
try {
val embedRes = app.get("$mainUrl/ajax/embed/source/${source.id}").parsedSafe<VidsrctoEmbedSource>() ?: return@amap
val finalUrl = DecryptUrl(embedRes.result.encUrl)
if(finalUrl.equals(embedRes.result.encUrl)) return@amap
when (source.title) {
"Vidplay" -> AnyVidplay(finalUrl.substringBefore("/e/")).getUrl(finalUrl, referer, subtitleCallback, callback)
"Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback)
}
} catch (e: Exception) {
logError(e)
}
}
}
private fun DecryptUrl(encUrl: String): String {
var data = encUrl.toByteArray()
data = Base64.decode(data, Base64.URL_SAFE)
val rc4Key = SecretKeySpec("WXrUARXb1aDLaZjI".toByteArray(), "RC4")
val cipher = Cipher.getInstance("RC4")
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
data = cipher.doFinal(data)
return URLDecoder.decode(data.toString(Charsets.UTF_8), "utf-8")
}
data class VidsrctoEpisodeSources(
@JsonProperty("status") val status: Int,
@JsonProperty("result") val result: List<VidsrctoResult>?
)
data class VidsrctoResult(
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String
)
data class VidsrctoEmbedSource(
@JsonProperty("status") val status: Int,
@JsonProperty("result") val result: VidsrctoUrl
)
data class VidsrctoUrl(@JsonProperty("url") val encUrl: String)
}

View file

@ -13,6 +13,10 @@ import javax.crypto.spec.SecretKeySpec
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys
// special credits to @KillerDogeEmpire for providing key
class AnyVidplay(hostUrl: String) : Vidplay() {
override val mainUrl = hostUrl
}
class MyCloud : Vidplay() {
override val name = "MyCloud"
override val mainUrl = "https://mcloud.bz"

View file

@ -105,6 +105,7 @@ open class TmdbProvider : MainAPI() {
this.id,
episode.episode_number,
episode.season_number,
this.name ?: this.original_name,
).toJson(),
episode.name,
episode.season_number,
@ -122,6 +123,7 @@ open class TmdbProvider : MainAPI() {
this.id,
episodeNum,
season.season_number,
this.name ?: this.original_name,
).toJson(),
season = season.season_number
)

View file

@ -1,17 +1,39 @@
package com.lagradost.cloudstream3.metaproviders
import android.net.Uri
import com.lagradost.cloudstream3.*
import com.fasterxml.jackson.annotation.JsonAlias
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.Actor
import com.lagradost.cloudstream3.ActorData
import com.lagradost.cloudstream3.Episode
import com.lagradost.cloudstream3.HomePageResponse
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.MainPageRequest
import com.lagradost.cloudstream3.NextAiring
import com.lagradost.cloudstream3.ProviderType
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.ShowStatus
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.addDate
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode
import com.lagradost.cloudstream3.mainPageOf
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.newHomePageResponse
import com.lagradost.cloudstream3.newMovieLoadResponse
import com.lagradost.cloudstream3.newMovieSearchResponse
import com.lagradost.cloudstream3.newTvSeriesLoadResponse
import com.lagradost.cloudstream3.newTvSeriesSearchResponse
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import java.util.Locale
import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.math.roundToInt
open class TraktProvider : MainAPI() {
@ -24,7 +46,8 @@ open class TraktProvider : MainAPI() {
TvType.Anime,
)
private val traktClientId = base64Decode("N2YzODYwYWQzNGI4ZTZmOTdmN2I5MTA0ZWQzMzEwOGI0MmQ3MTdlMTM0MmM2NGMxMTg5NGE1MjUyYTQ3NjE3Zg==")
private val traktClientId =
base64Decode("N2YzODYwYWQzNGI4ZTZmOTdmN2I5MTA0ZWQzMzEwOGI0MmQ3MTdlMTM0MmM2NGMxMTg5NGE1MjUyYTQ3NjE3Zg==")
private val traktApiUrl = base64Decode("aHR0cHM6Ly9hcGl6LnRyYWt0LnR2")
override val mainPage = mainPageOf(
@ -76,7 +99,8 @@ open class TraktProvider : MainAPI() {
}
override suspend fun search(query: String): List<SearchResponse>? {
val apiResponse = getApi("$traktApiUrl/search/movie,show?extended=cloud9,full&limit=20&page=1&query=$query")
val apiResponse =
getApi("$traktApiUrl/search/movie,show?extended=cloud9,full&limit=20&page=1&query=$query")
val results = parseJson<List<MediaDetails>>(apiResponse).map { element ->
element.toSearchResponse()
@ -84,6 +108,7 @@ open class TraktProvider : MainAPI() {
return results
}
override suspend fun load(url: String): LoadResponse {
val data = parseJson<Data>(url)
@ -93,7 +118,8 @@ open class TraktProvider : MainAPI() {
val posterUrl = mediaDetails?.images?.poster?.firstOrNull()
val backDropUrl = mediaDetails?.images?.fanart?.firstOrNull()
val resActor = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=cloud9,full")
val resActor =
getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=cloud9,full")
val actors = parseJson<People>(resActor).cast?.map {
ActorData(
@ -105,12 +131,15 @@ open class TraktProvider : MainAPI() {
)
}
val resRelated = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=cloud9,full&limit=20")
val resRelated =
getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=cloud9,full&limit=20")
val relatedMedia = parseJson<List<MediaDetails>>(resRelated).map { it.toSearchResponse() }
val isCartoon = mediaDetails?.genres?.contains("animation") == true || mediaDetails?.genres?.contains("anime") == true
val isAnime = isCartoon && (mediaDetails?.language == "zh" || mediaDetails?.language == "ja")
val isCartoon =
mediaDetails?.genres?.contains("animation") == true || mediaDetails?.genres?.contains("anime") == true
val isAnime =
isCartoon && (mediaDetails?.language == "zh" || mediaDetails?.language == "ja")
val isAsian = !isAnime && (mediaDetails?.language == "zh" || mediaDetails?.language == "ko")
val isBollywood = mediaDetails?.country == "in"
@ -118,8 +147,12 @@ open class TraktProvider : MainAPI() {
val linkData = LinkData(
id = mediaDetails?.ids?.tmdb,
traktId = mediaDetails?.ids?.trakt,
traktSlug = mediaDetails?.ids?.slug,
tmdbId = mediaDetails?.ids?.tmdb,
imdbId = mediaDetails?.ids?.imdb.toString(),
tvdbId = mediaDetails?.ids?.tvdb,
tvrageId = mediaDetails?.ids?.tvrage,
type = data.type.toString(),
title = mediaDetails?.title,
year = mediaDetails?.year,
@ -139,7 +172,6 @@ open class TraktProvider : MainAPI() {
type = if (isAnime) TvType.AnimeMovie else TvType.Movie,
) {
this.name = mediaDetails.title
this.apiName = "Trakt"
this.type = if (isAnime) TvType.AnimeMovie else TvType.Movie
this.posterUrl = getOriginalWidthImageUrl(posterUrl)
this.year = mediaDetails.year
@ -159,26 +191,24 @@ open class TraktProvider : MainAPI() {
}
} else {
val resSeasons = getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=cloud9,full,episodes")
val resSeasons =
getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=cloud9,full,episodes")
val episodes = mutableListOf<Episode>()
val seasons = parseJson<List<Seasons>>(resSeasons)
val seasonsNames = mutableListOf<SeasonData>()
var nextAir: NextAiring? = null
seasons.forEach { season ->
seasonsNames.add(
SeasonData(
season.number!!,
season.title
)
)
season.episodes?.map { episode ->
val linkData = LinkData(
id = mediaDetails?.ids?.tmdb,
traktId = mediaDetails?.ids?.trakt,
traktSlug = mediaDetails?.ids?.slug,
tmdbId = mediaDetails?.ids?.tmdb,
imdbId = mediaDetails?.ids?.imdb.toString(),
tvdbId = mediaDetails?.ids?.tvdb,
tvrageId = mediaDetails?.ids?.tvrage,
type = data.type.toString(),
season = episode.season,
episode = episode.number,
@ -207,7 +237,14 @@ open class TraktProvider : MainAPI() {
rating = episode.rating?.times(10)?.roundToInt(),
description = episode.overview,
).apply {
this.addDate(episode.firstAired)
this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
if (nextAir == null && this.date != null && this.date!! > unixTimeMS) {
nextAir = NextAiring(
episode = this.episode!!,
unixTime = this.date!!.div(1000L),
season = if (this.season == 1) null else this.season,
)
}
}
)
}
@ -220,7 +257,6 @@ open class TraktProvider : MainAPI() {
episodes = episodes
) {
this.name = mediaDetails.title
this.apiName = "Trakt"
this.type = if (isAnime) TvType.Anime else TvType.TvSeries
this.episodes = episodes
this.posterUrl = getOriginalWidthImageUrl(posterUrl)
@ -234,7 +270,7 @@ open class TraktProvider : MainAPI() {
this.actors = actors
this.comingSoon = isUpcoming(mediaDetails.released)
//posterHeaders
this.seasonNames = seasonsNames
this.nextAiring = nextAir
this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl)
this.contentRating = mediaDetails.certification
addTrailer(mediaDetails.trailer)
@ -244,7 +280,7 @@ open class TraktProvider : MainAPI() {
}
}
private suspend fun getApi(url: String) : String {
private suspend fun getApi(url: String): String {
return app.get(
url = url,
headers = mapOf(
@ -279,14 +315,14 @@ open class TraktProvider : MainAPI() {
return "https://$url"
}
private fun getWidthImageUrl(path: String?, width: String) : String? {
private fun getWidthImageUrl(path: String?, width: String): String? {
if (path == null) return null
if (!path.contains("image.tmdb.org")) return fixPath(path)
val fileName = Uri.parse(path).lastPathSegment ?: return null
return "https://image.tmdb.org/t/p/${width}/${fileName}"
}
private fun getOriginalWidthImageUrl(path: String?) : String? {
private fun getOriginalWidthImageUrl(path: String?): String? {
if (path == null) return null
if (!path.contains("image.tmdb.org")) return fixPath(path)
return getWidthImageUrl(path, "original")
@ -406,8 +442,12 @@ open class TraktProvider : MainAPI() {
data class LinkData(
val id: Int? = null,
val traktId: Int? = null,
val traktSlug: String? = null,
val tmdbId: Int? = null,
val imdbId: String? = null,
val tvdbId: Int? = null,
val tvrageId: String? = null,
val type: String? = null,
val season: Int? = null,
val episode: Int? = null,

View file

@ -67,6 +67,7 @@ abstract class Plugin {
* This will contain your resources if you specified requiresResources in gradle
*/
var resources: Resources? = null
/** Full file path to the plugin. */
var __filename: String? = null
/**

View file

@ -18,7 +18,6 @@ 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
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
@ -518,7 +517,7 @@ object PluginManager {
return true
}
pluginInstance.__filename = fileName
pluginInstance.__filename = file.absolutePath
if (manifest.requiresResources) {
Log.d(TAG, "Loading resources for ${data.internalName}")
// based on https://stackoverflow.com/questions/7483568/dynamic-resource-loading-from-other-apk

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.subtitles
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.TvType
class AbstractSubtitleEntities {
@ -19,8 +20,11 @@ class AbstractSubtitleEntities {
data class SubtitleSearch(
var query: String = "",
var imdb: Long? = null,
var lang: String? = null,
var imdbId: String? = null,
var tmdbId: Int? = null,
var malId: Int? = null,
var aniListId: Int? = null,
var epNumber: Int? = null,
var seasonNumber: Int? = null,
var year: Int? = null

View file

@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.syncproviders
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.syncproviders.providers.SubScene
import com.lagradost.cloudstream3.syncproviders.providers.*
import java.util.concurrent.TimeUnit
@ -13,9 +12,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
val aniListApi = AniListApi(0)
val openSubtitlesApi = OpenSubtitlesApi(0)
val simklApi = SimklApi(0)
val indexSubtitlesApi = IndexSubtitleApi()
val addic7ed = Addic7ed()
val subScene = SubScene()
val subDlApi = SubDlApi(0)
val localListApi = LocalList()
// used to login via app intent
@ -27,7 +25,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
// this needs init with context and can be accessed in settings
val accountManagers
get() = listOf(
malApi, aniListApi, openSubtitlesApi, simklApi //nginxApi
malApi, aniListApi, openSubtitlesApi, subDlApi, simklApi //nginxApi
)
// used for active syncing
@ -37,14 +35,16 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
)
val inAppAuths
get() = listOf(openSubtitlesApi)//, nginxApi)
get() = listOf<InAppAuthAPIManager>(
openSubtitlesApi,
subDlApi
)//, nginxApi)
val subtitleProviders
get() = listOf(
openSubtitlesApi,
indexSubtitlesApi, // they got anti scraping measures in place :(
addic7ed,
subScene
subDlApi
)
const val appString = "cloudstreamapp"

View file

@ -5,7 +5,23 @@ import androidx.fragment.app.FragmentActivity
interface OAuth2API : AuthAPI {
val key: String
val redirectUrl: String
val supportDeviceAuth: Boolean
suspend fun handleRedirect(url: String) : Boolean
fun authenticate(activity: FragmentActivity?)
suspend fun getDevicePin() : PinAuthData? {
return null
}
suspend fun handleDeviceAuth(pinAuthData: PinAuthData) : Boolean {
return false
}
data class PinAuthData(
val deviceCode: String,
val userCode: String,
val verificationUrl: String,
val expiresIn: Int,
val interval: Int,
)
}

View file

@ -32,6 +32,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
override val redirectUrl = "anilistlogin"
override val idPrefix = "anilist"
override var requireLibraryRefresh = true
override val supportDeviceAuth = false
override var mainUrl = "https://anilist.co"
override val icon = R.drawable.ic_anilist_icon
override val requiresLogin = false

View file

@ -11,6 +11,7 @@ class Dropbox : OAuth2API {
override val key = "zlqsamadlwydvb2"
override val redirectUrl = "dropboxlogin"
override val requiresLogin = true
override val supportDeviceAuth = false
override val createAccountUrl: String? = null
override val icon: Int

View file

@ -1,265 +0,0 @@
package com.lagradost.cloudstream3.syncproviders.providers
import android.util.Log
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.imdbUrlToIdNullable
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.utils.SubtitleHelper
class IndexSubtitleApi : AbstractSubApi {
override val name = "IndexSubtitle"
override val idPrefix = "indexsubtitle"
override val requiresLogin = false
override val icon: Nothing? = null
override val createAccountUrl: Nothing? = null
override fun loginInfo(): Nothing? = null
override fun logOut() {}
companion object {
const val host = "https://indexsubtitle.com"
const val TAG = "INDEXSUBS"
fun getOrdinal(num: Int?): String? {
return when (num) {
1 -> "First"
2 -> "Second"
3 -> "Third"
4 -> "Fourth"
5 -> "Fifth"
6 -> "Sixth"
7 -> "Seventh"
8 -> "Eighth"
9 -> "Ninth"
10 -> "Tenth"
11 -> "Eleventh"
12 -> "Twelfth"
13 -> "Thirteenth"
14 -> "Fourteenth"
15 -> "Fifteenth"
16 -> "Sixteenth"
17 -> "Seventeenth"
18 -> "Eighteenth"
19 -> "Nineteenth"
20 -> "Twentieth"
21 -> "Twenty-First"
22 -> "Twenty-Second"
23 -> "Twenty-Third"
24 -> "Twenty-Fourth"
25 -> "Twenty-Fifth"
26 -> "Twenty-Sixth"
27 -> "Twenty-Seventh"
28 -> "Twenty-Eighth"
29 -> "Twenty-Ninth"
30 -> "Thirtieth"
31 -> "Thirty-First"
32 -> "Thirty-Second"
33 -> "Thirty-Third"
34 -> "Thirty-Fourth"
35 -> "Thirty-Fifth"
else -> null
}
}
}
private fun fixUrl(url: String): String {
if (url.startsWith("http")) {
return url
}
if (url.isEmpty()) {
return ""
}
val startsWithNoHttp = url.startsWith("//")
if (startsWithNoHttp) {
return "https:$url"
} else {
if (url.startsWith('/')) {
return host + url
}
return "$host/$url"
}
}
private fun isRightEps(text: String, seasonNum: Int?, epNum: Int?): Boolean {
val FILTER_EPS_REGEX =
Regex("(?i)((Chapter\\s?0?${epNum})|((Season)?\\s?0?${seasonNum}?\\s?(Episode)\\s?0?${epNum}[^0-9]))|(?i)((S?0?${seasonNum}?E0?${epNum}[^0-9])|(0?${seasonNum}[a-z]0?${epNum}[^0-9]))")
return text.contains(FILTER_EPS_REGEX)
}
private fun haveEps(text: String): Boolean {
val HAVE_EPS_REGEX =
Regex("(?i)((Chapter\\s?0?\\d)|((Season)?\\s?0?\\d?\\s?(Episode)\\s?0?\\d))|(?i)((S?0?\\d?E0?\\d)|(0?\\d[a-z]0?\\d))")
return text.contains(HAVE_EPS_REGEX)
}
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity> {
val imdbId = query.imdb ?: 0
val lang = query.lang
val queryLang = SubtitleHelper.fromTwoLettersToLanguage(lang.toString())
val queryText = query.query
val epNum = query.epNumber ?: 0
val seasonNum = query.seasonNumber ?: 0
val yearNum = query.year ?: 0
val urlItems = ArrayList<String>()
fun cleanResources(
results: MutableList<AbstractSubtitleEntities.SubtitleEntity>,
name: String,
link: String
) {
results.add(
AbstractSubtitleEntities.SubtitleEntity(
idPrefix = idPrefix,
name = name,
lang = queryLang.toString(),
data = link,
source = this.name,
type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie,
epNumber = epNum,
seasonNumber = seasonNum,
year = yearNum,
)
)
}
val document = app.get("$host/?search=$queryText").document
document.select("div.my-3.p-3 div.media").map { block ->
if (seasonNum > 0) {
val name = block.select("strong.text-primary, strong.text-info").text().trim()
val season = getOrdinal(seasonNum)
if ((block.selectFirst("a")?.attr("href")
?.contains(
"$season",
ignoreCase = true
)!! || name.contains(
"$season",
ignoreCase = true
)) && name.contains(queryText, ignoreCase = true)
) {
block.select("div.media").mapNotNull {
urlItems.add(
fixUrl(
it.selectFirst("a")!!.attr("href")
)
)
}
}
} else {
if (block.selectFirst("strong")!!.text().trim()
.matches(Regex("(?i)^$queryText\$"))
) {
if (block.select("span[title=Release]").isNullOrEmpty()) {
block.select("div.media").mapNotNull {
val urlItem = fixUrl(
it.selectFirst("a")!!.attr("href")
)
val itemDoc = app.get(urlItem).document
val id = imdbUrlToIdNullable(
itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent()
?.attr("href")
)?.toLongOrNull()
val year = itemDoc.selectFirst("div.d-flex span.badge.badge-success")
?.ownText()
?.trim().toString()
Log.i(TAG, "id => $id \nyear => $year||$yearNum")
if (imdbId > 0) {
if (id == imdbId) {
urlItems.add(urlItem)
}
} else {
if (year.contains("$yearNum")) {
urlItems.add(urlItem)
}
}
}
} else {
if (block.select("span[title=Release]").text().trim()
.contains("$yearNum")
) {
block.select("div.media").mapNotNull {
urlItems.add(
fixUrl(
it.selectFirst("a")!!.attr("href")
)
)
}
}
}
}
}
}
Log.i(TAG, "urlItems => $urlItems")
val results = mutableListOf<AbstractSubtitleEntities.SubtitleEntity>()
urlItems.forEach { url ->
val request = app.get(url)
if (request.isSuccessful) {
request.document.select("div.my-3.p-3 div.media").map { block ->
if (block.select("span.d-block span[data-original-title=Language]").text()
.trim()
.contains("$queryLang")
) {
var name = block.select("strong.text-primary, strong.text-info").text().trim()
val link = fixUrl(block.selectFirst("a")!!.attr("href"))
if (seasonNum > 0) {
when {
isRightEps(name, seasonNum, epNum) -> {
cleanResources(results, name, link)
}
!(haveEps(name)) -> {
name = "$name (S${seasonNum}:E${epNum})"
cleanResources(results, name, link)
}
}
} else {
cleanResources(results, name, link)
}
}
}
}
}
return results
}
override suspend fun load(data: AbstractSubtitleEntities.SubtitleEntity): String? {
val seasonNum = data.seasonNumber
val epNum = data.epNumber
val req = app.get(data.data)
if (req.isSuccessful) {
val document = req.document
val link = if (document.select("div.my-3.p-3 div.media").size == 1) {
fixUrl(
document.selectFirst("div.my-3.p-3 div.media a")!!.attr("href")
)
} else {
document.select("div.my-3.p-3 div.media").firstNotNullOf { block ->
val name =
block.selectFirst("strong.d-block")?.text()?.trim().toString()
if (seasonNum!! > 0) {
if (isRightEps(name, seasonNum, epNum)) {
fixUrl(block.selectFirst("a")!!.attr("href"))
} else {
null
}
} else {
fixUrl(block.selectFirst("a")!!.attr("href"))
}
}
}
return link
}
return null
}
}

View file

@ -21,6 +21,7 @@ class LocalList : SyncAPI {
override val name = "Local"
override val icon: Int = R.drawable.ic_baseline_storage_24
override val requiresLogin = false
override val supportDeviceAuth = false
override val createAccountUrl: Nothing? = null
override val idPrefix = "local"
override var requireLibraryRefresh = true

View file

@ -40,6 +40,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
private val apiUrl = "https://api.myanimelist.net"
override val icon = R.drawable.mal_logo
override val requiresLogin = false
override val supportDeviceAuth = false
override val syncIdName = SyncIdName.MyAnimeList
override var requireLibraryRefresh = true
override val createAccountUrl = "$mainUrl/register.php"

View file

@ -185,7 +185,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
throwIfCantDoRequest()
val fixedLang = fixLanguage(query.lang)
val imdbId = query.imdb ?: 0
val imdbId = query.imdbId?.replace("tt", "")?.toInt() ?: 0
val queryText = query.query
val epNum = query.epNumber ?: 0
val seasonNum = query.seasonNumber ?: 0

View file

@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.OAuth2API
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.SyncIdName
import com.lagradost.cloudstream3.ui.SyncWatchType
@ -45,6 +46,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
override var name = "Simkl"
override val key = "simkl-key"
override val redirectUrl = "simkl"
override val supportDeviceAuth = true
override val idPrefix = "simkl"
override var requireLibraryRefresh = true
override var mainUrl = "https://api.simkl.com"
@ -267,6 +269,21 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
)
}
data class PinAuthResponse(
@JsonProperty("result") val result: String,
@JsonProperty("device_code") val deviceCode: String,
@JsonProperty("user_code") val userCode: String,
@JsonProperty("verification_url") val verificationUrl: String,
@JsonProperty("expires_in") val expiresIn: Int,
@JsonProperty("interval") val interval: Int,
)
data class PinExchangeResponse(
@JsonProperty("result") val result: String,
@JsonProperty("message") val message: String? = null,
@JsonProperty("access_token") val accessToken: String? = null,
)
// -------------------
data class ActivitiesResponse(
val all: String?,
@ -1045,6 +1062,44 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
return simklUrlRegex.find(url)?.groupValues?.get(1) ?: ""
}
override suspend fun getDevicePin(): OAuth2API.PinAuthData? {
val pinAuthResp = app.get(
"$mainUrl/oauth/pin?client_id=$clientId&redirect_uri=$appString://${redirectUrl}"
).parsedSafe<PinAuthResponse>() ?: return null
return OAuth2API.PinAuthData(
deviceCode = pinAuthResp.deviceCode,
userCode = pinAuthResp.userCode,
verificationUrl = pinAuthResp.verificationUrl,
expiresIn = pinAuthResp.expiresIn,
interval = pinAuthResp.interval
)
}
override suspend fun handleDeviceAuth(pinAuthData: OAuth2API.PinAuthData): Boolean {
val pinAuthResp = app.get(
"$mainUrl/oauth/pin/${pinAuthData.userCode}?client_id=$clientId"
).parsedSafe<PinExchangeResponse>() ?: return false
if (pinAuthResp.accessToken != null) {
switchToNewAccount()
setKey(accountId, SIMKL_TOKEN_KEY, pinAuthResp.accessToken)
val user = getUser()
if (user == null) {
removeKey(accountId, SIMKL_TOKEN_KEY)
switchToOldAccount()
return false
}
setKey(accountId, SIMKL_USER_KEY, user)
registerAccount()
requireLibraryRefresh = true
return true
}
return false
}
override suspend fun handleRedirect(url: String): Boolean {
val uri = url.toUri()
val state = uri.getQueryParameter("state")

View file

@ -1,118 +0,0 @@
package com.lagradost.cloudstream3.syncproviders.providers
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.debugPrint
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.subtitles.SubtitleResource
import com.lagradost.cloudstream3.syncproviders.providers.IndexSubtitleApi.Companion.getOrdinal
import com.lagradost.cloudstream3.utils.SubtitleHelper
class SubScene : AbstractSubProvider {
val mainUrl = "https://subscene.com"
val name = "Subscene"
override val idPrefix = "subscene"
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
val seasonName =
query.seasonNumber?.let { number ->
// Need to translate "7" to "Seventh Season"
getOrdinal(number)?.let { words -> " - $words Season" }
} ?: ""
val fullQuery = query.query + seasonName
val doc = app.post(
"$mainUrl/subtitles/searchbytitle",
data = mapOf("query" to fullQuery, "l" to "")
).document
return doc.select("div.title a").map { element ->
val href = "$mainUrl${element.attr("href")}"
val title = element.text()
AbstractSubtitleEntities.SubtitleEntity(
idPrefix = idPrefix,
name = title,
source = name,
data = href,
lang = query.lang ?: "en",
epNumber = query.epNumber
)
}.distinctBy { it.data }
}
override suspend fun SubtitleResource.getResources(data: AbstractSubtitleEntities.SubtitleEntity) {
val resultDoc = app.get(data.data).document
val queryLanguage = SubtitleHelper.fromTwoLettersToLanguage(data.lang) ?: "English"
val results = resultDoc.select("table tbody tr").mapNotNull { element ->
val anchor = element.select("a")
val href = anchor.attr("href") ?: return@mapNotNull null
val fixedHref = "$mainUrl${href}"
val spans = anchor.select("span")
val language = spans.firstOrNull()?.text()
val title = spans.getOrNull(1)?.text()
val isPositive = anchor.select("span.positive-icon").isNotEmpty()
TableElement(title, language, fixedHref, isPositive)
}.sortedBy {
it.getScore(queryLanguage, data.epNumber)
}
debugPrint { "$name found subtitles: ${results.takeLast(3)}" }
// Last = highest score
val selectedResult = results.lastOrNull() ?: return
val subtitleDocument = app.get(selectedResult.href).document
val subtitleDownloadUrl =
"$mainUrl${subtitleDocument.select("div.download a").attr("href")}"
this.addZipUrl(subtitleDownloadUrl) { name, _ ->
name
}
}
/**
* Class to manage the various different subtitle results and rank them.
*/
data class TableElement(
val title: String?,
val language: String?,
val href: String,
val isPositive: Boolean
) {
private fun matchesLanguage(other: String): Boolean {
return language != null && (language.contains(other, ignoreCase = true) ||
other.contains(language, ignoreCase = true))
}
/**
* Scores in this order:
* Preferred Language > Episode number > Positive rating > English Language
*/
fun getScore(queryLanguage: String, episodeNum: Int?): Int {
var score = 0
if (this.matchesLanguage(queryLanguage)) {
score += 8
}
// Matches Episode 7 using "E07" with any number of leading zeroes
if (episodeNum != null && title != null && title.contains(
Regex(
"""E0*${episodeNum}""",
RegexOption.IGNORE_CASE
)
)
) {
score += 4
}
if (isPositive) {
score += 2
}
if (this.matchesLanguage("English")) {
score += 1
}
return score
}
}
}

View file

@ -0,0 +1,247 @@
package com.lagradost.cloudstream3.syncproviders.providers
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.subtitles.SubtitleResource
import com.lagradost.cloudstream3.syncproviders.AuthAPI.LoginInfo
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
class SubDlApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi {
override val idPrefix = "subdl"
override val name = "SubDL"
override val icon = R.drawable.subdl_logo_big
override val requiresPassword = true
override val requiresEmail = true
override val createAccountUrl = "https://subdl.com/login"
companion object {
const val APIURL = "https://api.subdl.com"
const val APIENDPOINT = "$APIURL/api/v1/subtitles"
const val DOWNLOADENDPOINT = "https://dl.subdl.com"
const val SUBDL_SUBTITLES_USER_KEY: String = "subdl_user"
var currentSession: SubtitleOAuthEntity? = null
}
override suspend fun initialize() {
currentSession = getAuthKey()
}
override fun logOut() {
setAuthKey(null)
removeAccountKeys()
currentSession = getAuthKey()
}
override suspend fun login(data: InAppAuthAPI.LoginData): Boolean {
val email = data.email ?: throw ErrorLoadingException("Requires Email")
val password = data.password ?: throw ErrorLoadingException("Requires Password")
switchToNewAccount()
try {
if (initLogin(email, password)) {
registerAccount()
return true
}
} catch (e: Exception) {
logError(e)
switchToOldAccount()
}
switchToOldAccount()
return false
}
override fun getLatestLoginData(): InAppAuthAPI.LoginData? {
val current = getAuthKey() ?: return null
return InAppAuthAPI.LoginData(
email = current.userEmail,
password = current.pass
)
}
override fun loginInfo(): LoginInfo? {
getAuthKey()?.let { user ->
return LoginInfo(
profilePicture = null,
name = user.name ?: user.userEmail,
accountIndex = accountIndex
)
}
return null
}
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
val queryText = query.query
val epNum = query.epNumber ?: 0
val seasonNum = query.seasonNumber ?: 0
val yearNum = query.year ?: 0
val idQuery = when {
query.imdbId != null -> "&imdb_id=${query.imdbId}"
query.tmdbId != null -> "&tmdb_id=${query.tmdbId}"
else -> null
}
val epQuery = if (epNum > 0) "&episode_number=$epNum" else ""
val seasonQuery = if (seasonNum > 0) "&season_number=$seasonNum" else ""
val yearQuery = if (yearNum > 0) "&year=$yearNum" else ""
val searchQueryUrl = when (idQuery) {
//Use imdb/tmdb id to search if its valid
null -> "$APIENDPOINT?api_key=${currentSession?.apiKey}&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
else -> "$APIENDPOINT?api_key=${currentSession?.apiKey}$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
}
val req = app.get(
url = searchQueryUrl,
headers = mapOf(
"Accept" to "application/json"
)
)
return req.parsedSafe<ApiResponse>()?.subtitles?.map { subtitle ->
val lang = subtitle.lang.replaceFirstChar { it.uppercase() }
val resEpNum = subtitle.episode ?: query.epNumber
val resSeasonNum = subtitle.season ?: query.seasonNumber
val type = if ((resSeasonNum ?: 0) > 0) TvType.TvSeries else TvType.Movie
AbstractSubtitleEntities.SubtitleEntity(
idPrefix = this.idPrefix,
name = subtitle.releaseName,
lang = lang,
data = "${DOWNLOADENDPOINT}${subtitle.url}",
type = type,
source = this.name,
epNumber = resEpNum,
seasonNumber = resSeasonNum,
isHearingImpaired = subtitle.hearingImpaired ?: false,
)
}
}
override suspend fun SubtitleResource.getResources(data: AbstractSubtitleEntities.SubtitleEntity) {
this.addZipUrl(data.data) { name, _ ->
name
}
}
private suspend fun initLogin(useremail: String, password: String): Boolean {
val tokenResponse = app.post(
url = "$APIURL/login",
data = mapOf(
"email" to useremail,
"password" to password
)
).parsedSafe<OAuthTokenResponse>()
if (tokenResponse?.token == null) return false
val apiResponse = app.get(
url = "$APIURL/user/userApi",
headers = mapOf(
"Authorization" to "Bearer ${tokenResponse.token}"
)
).parsedSafe<ApiKeyResponse>()
if (apiResponse?.ok == false) return false
setAuthKey(
SubtitleOAuthEntity(
userEmail = useremail,
pass = password,
name = tokenResponse.userData?.username ?: tokenResponse.userData?.name,
accessToken = tokenResponse.token,
apiKey = apiResponse?.apiKey
)
)
return true
}
private fun getAuthKey(): SubtitleOAuthEntity? {
return getKey(accountId, SUBDL_SUBTITLES_USER_KEY)
}
private fun setAuthKey(data: SubtitleOAuthEntity?) {
if (data == null) removeKey(
accountId,
SUBDL_SUBTITLES_USER_KEY
)
currentSession = data
setKey(accountId, SUBDL_SUBTITLES_USER_KEY, data)
}
data class SubtitleOAuthEntity(
@JsonProperty("userEmail") var userEmail: String,
@JsonProperty("pass") var pass: String,
@JsonProperty("name") var name: String? = null,
@JsonProperty("accessToken") var accessToken: String? = null,
@JsonProperty("apiKey") var apiKey: String? = null,
)
data class OAuthTokenResponse(
@JsonProperty("token") val token: String? = null,
@JsonProperty("userData") val userData: UserData? = null,
@JsonProperty("status") val status: Boolean? = null,
@JsonProperty("message") val message: String? = null,
)
data class UserData(
@JsonProperty("email") val email: String,
@JsonProperty("name") val name: String,
@JsonProperty("country") val country: String,
@JsonProperty("scStepCode") val scStepCode: String,
@JsonProperty("scVerified") val scVerified: Boolean,
@JsonProperty("username") val username: String? = null,
@JsonProperty("scUsername") val scUsername: String,
)
data class ApiKeyResponse(
@JsonProperty("ok") val ok: Boolean? = false,
@JsonProperty("api_key") val apiKey: String? = null,
@JsonProperty("usage") val usage: Usage? = null,
)
data class Usage(
@JsonProperty("total") val total: Long? = 0,
@JsonProperty("today") val today: Long? = 0,
)
data class ApiResponse(
@JsonProperty("status") val status: Boolean? = null,
@JsonProperty("results") val results: List<Result>? = null,
@JsonProperty("subtitles") val subtitles: List<Subtitle>? = null,
)
data class Result(
@JsonProperty("sd_id") val sdId: Int? = null,
@JsonProperty("type") val type: String? = null,
@JsonProperty("name") val name: String? = null,
@JsonProperty("imdb_id") val imdbId: String? = null,
@JsonProperty("tmdb_id") val tmdbId: Long? = null,
@JsonProperty("first_air_date") val firstAirDate: String? = null,
@JsonProperty("year") val year: Int? = null,
)
data class Subtitle(
@JsonProperty("release_name") val releaseName: String,
@JsonProperty("name") val name: String,
@JsonProperty("lang") val lang: String,
@JsonProperty("author") val author: String? = null,
@JsonProperty("url") val url: String? = null,
@JsonProperty("subtitlePage") val subtitlePage: String? = null,
@JsonProperty("season") val season: Int? = null,
@JsonProperty("episode") val episode: Int? = null,
@JsonProperty("language") val language: String? = null,
@JsonProperty("hi") val hearingImpaired: Boolean? = null,
)
}

View file

@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
@ -33,7 +34,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.BiometricAuthCallback {
class AccountSelectActivity : AppCompatActivity(), BiometricCallback {
lateinit var viewModel: AccountViewModel

View file

@ -0,0 +1,223 @@
package com.lagradost.cloudstream3.ui.download
import android.annotation.SuppressLint
import android.text.format.Formatter.formatShortFileSize
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
const val DOWNLOAD_ACTION_PLAY_FILE = 0
const val DOWNLOAD_ACTION_DELETE_FILE = 1
const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
const val DOWNLOAD_ACTION_DOWNLOAD = 4
const val DOWNLOAD_ACTION_LONG_CLICK = 5
abstract class VisualDownloadCached(
open val currentBytes: Long,
open val totalBytes: Long,
open val data: VideoDownloadHelper.DownloadCached
) {
// Just to be extra-safe with areContentsTheSame
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is VisualDownloadCached) return false
if (currentBytes != other.currentBytes) return false
if (totalBytes != other.totalBytes) return false
if (data != other.data) return false
return true
}
override fun hashCode(): Int {
var result = currentBytes.hashCode()
result = 31 * result + totalBytes.hashCode()
result = 31 * result + data.hashCode()
return result
}
}
data class VisualDownloadChildCached(
override val currentBytes: Long,
override val totalBytes: Long,
override val data: VideoDownloadHelper.DownloadEpisodeCached,
): VisualDownloadCached(currentBytes, totalBytes, data)
data class VisualDownloadHeaderCached(
override val currentBytes: Long,
override val totalBytes: Long,
override val data: VideoDownloadHelper.DownloadHeaderCached,
val child: VideoDownloadHelper.DownloadEpisodeCached?,
val currentOngoingDownloads: Int,
val totalDownloads: Int,
): VisualDownloadCached(currentBytes, totalBytes, data)
data class DownloadClickEvent(
val action: Int,
val data: VideoDownloadHelper.DownloadEpisodeCached
)
data class DownloadHeaderClickEvent(
val action: Int,
val data: VideoDownloadHelper.DownloadHeaderCached
)
class DownloadAdapter(
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
companion object {
private const val VIEW_TYPE_HEADER = 0
private const val VIEW_TYPE_CHILD = 1
}
inner class DownloadViewHolder(
private val binding: ViewBinding,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(card: VisualDownloadCached?) {
when (binding) {
is DownloadHeaderEpisodeBinding -> binding.apply {
if (card == null || card !is VisualDownloadHeaderCached) return@apply
val d = card.data
downloadHeaderPoster.apply {
setImage(d.poster)
setOnClickListener {
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
}
}
downloadHeaderTitle.text = d.name
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
if (card.child != null) {
downloadHeaderGotoChild.isVisible = false
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
downloadButton.isVisible = true
episodeHolder.setOnClickListener {
mediaClickCallback.invoke(
DownloadClickEvent(
DOWNLOAD_ACTION_PLAY_FILE,
card.child
)
)
}
} else {
downloadButton.isVisible = false
downloadHeaderGotoChild.isVisible = true
try {
downloadHeaderInfo.text =
downloadHeaderInfo.context.getString(R.string.extra_info_format)
.format(
card.totalDownloads,
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(
R.string.episode
) else downloadHeaderInfo.context.getString(
R.string.episodes
),
mbString
)
} catch (t: Throwable) {
// You probably formatted incorrectly
downloadHeaderInfo.text = "Error"
logError(t)
}
episodeHolder.setOnClickListener {
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
}
}
}
is DownloadChildEpisodeBinding -> binding.apply {
if (card == null || card !is VisualDownloadChildCached) return@apply
val d = card.data
val posDur = DataStoreHelper.getViewPos(d.id)
downloadChildEpisodeProgress.apply {
if (posDur != null) {
val visualPos = posDur.fixVisual()
max = (visualPos.duration / 1000).toInt()
progress = (visualPos.position / 1000).toInt()
isVisible = true
} else isVisible = false
}
downloadButton.setDefaultClickListener(card.data, downloadChildEpisodeTextExtra, mediaClickCallback)
downloadChildEpisodeText.apply {
text = context.getNameFull(d.name, d.episode, d.season)
isSelected = true // Needed for text repeating
}
downloadChildEpisodeHolder.setOnClickListener {
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
}
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
val binding = when (viewType) {
VIEW_TYPE_HEADER -> {
DownloadHeaderEpisodeBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
}
VIEW_TYPE_CHILD -> {
DownloadChildEpisodeBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
}
else -> throw IllegalArgumentException("Invalid view type")
}
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
}
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun getItemViewType(position: Int): Int {
val card = getItem(position)
return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER
}
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
return oldItem.data.id == newItem.data.id
}
override fun areContentsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
return oldItem == newItem
}
}
}

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.download
import android.app.Activity
import android.content.DialogInterface
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@ -22,7 +21,6 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager
object DownloadButtonSetup {
fun handleDownloadClick(click: DownloadClickEvent) {
val id = click.data.id
if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return
when (click.action) {
DOWNLOAD_ACTION_DELETE_FILE -> {
activity?.let { ctx ->

View file

@ -1,94 +0,0 @@
package com.lagradost.cloudstream3.ui.download
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
const val DOWNLOAD_ACTION_PLAY_FILE = 0
const val DOWNLOAD_ACTION_DELETE_FILE = 1
const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
const val DOWNLOAD_ACTION_DOWNLOAD = 4
const val DOWNLOAD_ACTION_LONG_CLICK = 5
data class VisualDownloadChildCached(
val currentBytes: Long,
val totalBytes: Long,
val data: VideoDownloadHelper.DownloadEpisodeCached,
)
data class DownloadClickEvent(val action: Int, val data: VideoDownloadHelper.DownloadEpisodeCached)
class DownloadChildAdapter(
var cardList: List<VisualDownloadChildCached>,
private val clickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return DownloadChildViewHolder(
DownloadChildEpisodeBinding.inflate(LayoutInflater.from(parent.context), parent, false),
clickCallback
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is DownloadChildViewHolder -> {
holder.bind(cardList[position])
}
}
}
override fun getItemCount(): Int {
return cardList.size
}
class DownloadChildViewHolder
constructor(
val binding: DownloadChildEpisodeBinding,
private val clickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
/*private val title: TextView = itemView.download_child_episode_text
private val extraInfo: TextView = itemView.download_child_episode_text_extra
private val holder: CardView = itemView.download_child_episode_holder
private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
private val downloadImage: ImageView = itemView.download_child_episode_download*/
fun bind(card: VisualDownloadChildCached) {
val d = card.data
val posDur = getViewPos(d.id)
binding.downloadChildEpisodeProgress.apply {
if (posDur != null) {
val visualPos = posDur.fixVisual()
max = (visualPos.duration / 1000).toInt()
progress = (visualPos.position / 1000).toInt()
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
binding.downloadButton.setDefaultClickListener(card.data, binding.downloadChildEpisodeTextExtra, clickCallback)
binding.downloadChildEpisodeText.apply {
text = context.getNameFull(d.name, d.episode, d.season)
isSelected = true // is needed for text repeating
}
binding.downloadChildEpisodeHolder.setOnClickListener {
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
}
}
}
}

View file

@ -5,12 +5,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKeys
@ -37,7 +39,8 @@ class DownloadChildFragment : Fragment() {
super.onDestroyView()
}
var binding: FragmentChildDownloadsBinding? = null
private var binding: FragmentChildDownloadsBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -45,7 +48,7 @@ class DownloadChildFragment : Fragment() {
): View {
val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root//inflater.inflate(R.layout.fragment_child_downloads, container, false)
return localBinding.root
}
private fun updateList(folder: String) = main {
@ -57,7 +60,11 @@ class DownloadChildFragment : Fragment() {
}.mapNotNull {
val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(ctx, it.id)
?: return@mapNotNull null
VisualDownloadChildCached(info.fileLength, info.totalBytes, it)
VisualDownloadChildCached(
currentBytes = info.fileLength,
totalBytes = info.totalBytes,
data = it,
)
}
}.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 }
if (eps.isEmpty()) {
@ -65,9 +72,7 @@ class DownloadChildFragment : Fragment() {
return@main
}
(binding?.downloadChildList?.adapter as DownloadChildAdapter? ?: return@main).cardList =
eps
binding?.downloadChildList?.adapter?.notifyDataSetChanged()
(binding?.downloadChildList?.adapter as? DownloadAdapter)?.submitList(eps)
}
}
@ -86,38 +91,48 @@ class DownloadChildFragment : Fragment() {
binding?.downloadChildToolbar?.apply {
title = name
if (isLayout(PHONE or EMULATOR)) {
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
setNavigationOnClickListener {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
setAppBarNoScrollFlagsOnTV()
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
DownloadChildAdapter(
ArrayList(),
) { click ->
handleDownloadClick(click)
val adapter = DownloadAdapter(
{},
{ downloadClickEvent ->
handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
setUpDownloadDeleteListener(folder)
}
}
)
binding?.downloadChildList?.apply {
setHasFixedSize(true)
setItemViewCacheSize(20)
this.adapter = adapter
setLinearListLayout(
isHorizontal = false,
nextRight = FOCUS_SELF,
nextDown = FOCUS_SELF,
)
}
updateList(folder)
}
private fun setUpDownloadDeleteListener(folder: String) {
downloadDeleteEventListener = { id: Int ->
val list = (binding?.downloadChildList?.adapter as DownloadChildAdapter?)?.cardList
val list = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.currentList
if (list != null) {
if (list.any { it.data.id == id }) {
updateList(folder)
}
}
}
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
binding?.downloadChildList?.adapter = adapter
binding?.downloadChildList?.setLinearListLayout(
isHorizontal = false,
nextDown = FOCUS_SELF,
nextRight = FOCUS_SELF
)//layoutManager = GridLayoutManager(context, 1)
updateList(folder)
}
}

View file

@ -10,14 +10,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
@ -42,11 +43,9 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import java.net.URI
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
class DownloadFragment : Fragment() {
@ -63,33 +62,30 @@ class DownloadFragment : Fragment() {
private fun setList(list: List<VisualDownloadHeaderCached>) {
main {
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list
binding?.downloadList?.adapter?.notifyDataSetChanged()
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list)
}
}
override fun onDestroyView() {
if (downloadDeleteEventListener != null) {
VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!!
downloadDeleteEventListener = null
downloadDeleteEventListener?.let {
VideoDownloadManager.downloadDeleteEvent -= it
}
downloadDeleteEventListener = null
binding = null
super.onDestroyView()
}
var binding: FragmentDownloadsBinding? = null
private var binding: FragmentDownloadsBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
downloadsViewModel =
ViewModelProvider(this)[DownloadViewModel::class.java]
): View {
downloadsViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root//inflater.inflate(R.layout.fragment_downloads, container, false)
return localBinding.root
}
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
@ -97,7 +93,6 @@ class DownloadFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
hideKeyboard()
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
observe(downloadsViewModel.noDownloadsText) {
@ -108,135 +103,109 @@ class DownloadFragment : Fragment() {
binding?.downloadLoading?.isVisible = false
}
observe(downloadsViewModel.availableBytes) {
binding?.downloadFreeTxt?.text =
getString(R.string.storage_size_format).format(
getString(R.string.free_storage),
formatShortFileSize(view.context, it)
)
binding?.downloadFree?.setLayoutWidth(it)
updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree)
}
observe(downloadsViewModel.usedBytes) {
binding?.apply {
downloadUsedTxt.text =
getString(R.string.storage_size_format).format(
getString(R.string.used_storage),
formatShortFileSize(view.context, it)
)
downloadUsed.setLayoutWidth(it)
downloadStorageAppbar.isVisible = it > 0
}
updateStorageInfo(view.context, it, R.string.used_storage, binding?.downloadUsedTxt, binding?.downloadUsed)
binding?.downloadStorageAppbar?.isVisible = it > 0
}
observe(downloadsViewModel.downloadBytes) {
binding?.apply {
downloadAppTxt.text =
getString(R.string.storage_size_format).format(
getString(R.string.app_storage),
formatShortFileSize(view.context, it)
)
downloadApp.setLayoutWidth(it)
}
updateStorageInfo(view.context, it, R.string.app_storage, binding?.downloadAppTxt, binding?.downloadApp)
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
DownloadHeaderAdapter(
ArrayList(),
val adapter = DownloadAdapter(
{ click ->
handleItemClick(click)
},
{ downloadClickEvent ->
handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
setUpDownloadDeleteListener()
}
}
)
binding?.downloadList?.apply {
setHasFixedSize(true)
setItemViewCacheSize(20)
this.adapter = adapter
setLinearListLayout(
isHorizontal = false,
nextRight = FOCUS_SELF,
nextUp = FOCUS_SELF,
nextDown = FOCUS_SELF,
)
}
binding?.downloadStreamButton?.apply {
isGone = isLayout(TV)
setOnClickListener { showStreamInputDialog(it.context) }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
handleScroll(scrollY - oldScrollY)
}
}
downloadsViewModel.updateList(requireContext())
fixPaddingStatusbar(binding?.downloadRoot)
}
private fun handleItemClick(click: DownloadHeaderClickEvent) {
when (click.action) {
0 -> {
if (click.data.type.isMovieType()) {
//wont be called
} else {
val folder = DataStore.getFolderName(
DOWNLOAD_EPISODE_CACHE,
click.data.id.toString()
)
if (!click.data.type.isMovieType()) {
val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString())
activity?.navigate(
R.id.action_navigation_downloads_to_navigation_download_child,
DownloadChildFragment.newInstance(click.data.name, folder)
)
}
}
1 -> {
(activity as AppCompatActivity?)?.loadResult(
click.data.url,
click.data.apiName
)
(activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName)
}
}
}
},
{ downloadClickEvent ->
if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter
handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
context?.let { ctx ->
downloadsViewModel.updateList(ctx)
}
}
}
)
private fun setUpDownloadDeleteListener() {
downloadDeleteEventListener = { id ->
val list = (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList
if (list != null) {
if (list.any { it.data.id == id }) {
context?.let { ctx ->
setList(ArrayList())
downloadsViewModel.updateList(ctx)
val list = (binding?.downloadList?.adapter as? DownloadAdapter)?.currentList
if (list?.any { it.data.id == id } == true) {
context?.let { downloadsViewModel.updateList(it) }
}
}
}
}
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
binding?.downloadList?.apply {
this.adapter = adapter
setLinearListLayout(
isHorizontal = false,
nextRight = FOCUS_SELF,
nextUp = FOCUS_SELF,
nextDown = FOCUS_SELF
)
//layoutManager = GridLayoutManager(context, 1)
}
// Should be visible in emulator layout
binding?.downloadStreamButton?.isGone = isLayout(TV)
binding?.downloadStreamButton?.setOnClickListener {
val dialog =
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
private fun updateStorageInfo(
context: Context,
bytes: Long,
@StringRes stringRes: Int,
textView: TextView?,
view: View?
) {
textView?.text = getString(R.string.storage_size_format).format(getString(stringRes), formatShortFileSize(context, bytes))
view?.setLayoutWidth(bytes)
}
private fun showStreamInputDialog(context: Context) {
val dialog = Dialog(context, R.style.AlertDialogCustom)
val binding = StreamInputBinding.inflate(dialog.layoutInflater)
dialog.setContentView(binding.root)
dialog.show()
// If user has clicked the switch do not interfere
var preventAutoSwitching = false
binding.hlsSwitch.setOnClickListener {
preventAutoSwitching = true
}
fun activateSwitchOnHls(text: String?) {
binding.hlsSwitch.isChecked = normalSafeApiCall {
URI(text).path?.substringAfterLast(".")?.contains("m3u")
} == true
}
binding.hlsSwitch.setOnClickListener { preventAutoSwitching = true }
binding.streamReferer.doOnTextChanged { text, _, _, _ ->
if (!preventAutoSwitching)
activateSwitchOnHls(text?.toString())
if (!preventAutoSwitching) activateSwitchOnHls(text?.toString(), binding)
}
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
0
)?.text?.toString()?.let { copy ->
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip?.getItemAt(0)?.text?.toString()?.let { copy ->
val fixedText = copy.trim()
binding.streamUrl.setText(fixedText)
activateSwitchOnHls(fixedText)
activateSwitchOnHls(fixedText, binding)
}
binding.applyBtt.setOnClickListener {
@ -245,7 +214,6 @@ class DownloadFragment : Fragment() {
showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT)
} else {
val referer = binding.streamReferer.text?.toString()
activity?.navigate(
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
@ -257,7 +225,6 @@ class DownloadFragment : Fragment() {
)
)
)
dialog.dismissSafe(activity)
}
}
@ -266,18 +233,18 @@ class DownloadFragment : Fragment() {
dialog.dismissSafe(activity)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
binding?.downloadStreamButton?.shrink() // hide
} else if (dy < -5) {
binding?.downloadStreamButton?.extend() // show
}
}
}
downloadsViewModel.updateList(requireContext())
fixPaddingStatusbar(binding?.downloadRoot)
private fun activateSwitchOnHls(text: String?, binding: StreamInputBinding) {
binding.hlsSwitch.isChecked = normalSafeApiCall {
URI(text).path?.substringAfterLast(".")?.contains("m3u")
} == true
}
private fun handleScroll(dy: Int) {
if (dy > 0) {
binding?.downloadStreamButton?.shrink()
} else if (dy < -5) {
binding?.downloadStreamButton?.extend()
}
}
}

View file

@ -1,149 +0,0 @@
package com.lagradost.cloudstream3.ui.download
import android.annotation.SuppressLint
import android.text.format.Formatter.formatShortFileSize
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import java.util.*
data class VisualDownloadHeaderCached(
val currentOngoingDownloads: Int,
val totalDownloads: Int,
val totalBytes: Long,
val currentBytes: Long,
val data: VideoDownloadHelper.DownloadHeaderCached,
val child: VideoDownloadHelper.DownloadEpisodeCached?,
)
data class DownloadHeaderClickEvent(
val action: Int,
val data: VideoDownloadHelper.DownloadHeaderCached
)
class DownloadHeaderAdapter(
var cardList: List<VisualDownloadHeaderCached>,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val movieClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return DownloadHeaderViewHolder(
DownloadHeaderEpisodeBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
clickCallback,
movieClickCallback
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is DownloadHeaderViewHolder -> {
holder.bind(cardList[position])
}
}
}
override fun getItemCount(): Int {
return cardList.size
}
class DownloadHeaderViewHolder
constructor(
val binding: DownloadHeaderEpisodeBinding,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val movieClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
/*private val poster: ImageView? = itemView.download_header_poster
private val title: TextView = itemView.download_header_title
private val extraInfo: TextView = itemView.download_header_info
private val holder: CardView = itemView.episode_holder
private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded
private val downloadImage: ImageView = itemView.download_header_episode_download
private val normalImage: ImageView = itemView.download_header_goto_child*/
@SuppressLint("SetTextI18n")
fun bind(card: VisualDownloadHeaderCached) {
val d = card.data
binding.downloadHeaderPoster.apply {
setImage(d.poster)
setOnClickListener {
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
}
}
binding.apply {
binding.downloadHeaderTitle.text = d.name
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
//val isMovie = d.type.isMovieType()
if (card.child != null) {
//downloadHeaderProgressDownloaded.visibility = View.VISIBLE
// downloadHeaderEpisodeDownload.visibility = View.VISIBLE
binding.downloadHeaderGotoChild.visibility = View.GONE
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, movieClickCallback)
downloadButton.isVisible = true
/*setUpButton(
card.currentBytes,
card.totalBytes,
downloadBar,
downloadImage,
extraInfo,
card.child,
movieClickCallback
)*/
episodeHolder.setOnClickListener {
movieClickCallback.invoke(
DownloadClickEvent(
DOWNLOAD_ACTION_PLAY_FILE,
card.child
)
)
}
} else {
downloadButton.isVisible = false
// downloadHeaderProgressDownloaded.visibility = View.GONE
// downloadHeaderEpisodeDownload.visibility = View.GONE
binding.downloadHeaderGotoChild.visibility = View.VISIBLE
try {
downloadHeaderInfo.text =
downloadHeaderInfo.context.getString(R.string.extra_info_format).format(
card.totalDownloads,
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(R.string.episode) else downloadHeaderInfo.context.getString(
R.string.episodes
),
mbString
)
} catch (t: Throwable) {
// you probably formatted incorrectly
downloadHeaderInfo.text = "Error"
logError(t)
}
episodeHolder.setOnClickListener {
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
}
}
}
}
}
}

View file

@ -39,6 +39,8 @@ class DownloadViewModel : ViewModel() {
val availableBytes: LiveData<Long> = _availableBytes
val downloadBytes: LiveData<Long> = _downloadBytes
private var previousVisual: List<VisualDownloadHeaderCached>? = null
fun updateList(context: Context) = viewModelScope.launchSafe {
val children = withContext(Dispatchers.IO) {
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
@ -53,7 +55,6 @@ class DownloadViewModel : ViewModel() {
// parentId : downloadsCount
val totalDownloads = HashMap<Int, Int>()
// Gets all children downloads
withContext(Dispatchers.IO) {
for (c in children) {
@ -69,7 +70,7 @@ class DownloadViewModel : ViewModel() {
}
}
val cached = withContext(Dispatchers.IO) { // wont fetch useless keys
val cached = withContext(Dispatchers.IO) { // Won't fetch useless keys
totalDownloads.entries.filter { it.value > 0 }.mapNotNull {
context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
DOWNLOAD_HEADER_CACHE,
@ -79,7 +80,7 @@ class DownloadViewModel : ViewModel() {
}
val visual = withContext(Dispatchers.IO) {
cached.mapNotNull { // TODO FIX
cached.mapNotNull {
val downloads = totalDownloads[it.id] ?: 0
val bytes = totalBytesUsedByChild[it.id] ?: 0
val currentBytes = currentBytesUsedByChild[it.id] ?: 0
@ -91,32 +92,37 @@ class DownloadViewModel : ViewModel() {
getFolderName(it.id.toString(), it.id.toString())
)
VisualDownloadHeaderCached(
0,
downloads,
bytes,
currentBytes,
it,
movieEpisode
currentBytes = currentBytes,
totalBytes = bytes,
data = it,
child = movieEpisode,
currentOngoingDownloads = 0,
totalDownloads = downloads,
)
}.sortedBy {
(it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0)
} // episode sorting by episode, lowest to highest
} // Episode sorting by episode, lowest to highest
}
// Only update list if different from the previous one to prevent duplicate initialization
if (visual != previousVisual) {
previousVisual = visual
try {
val stat = StatFs(Environment.getExternalStorageDirectory().path)
val localBytesAvailable = stat.availableBytes//stat.blockSizeLong * stat.blockCountLong
val localBytesAvailable = stat.availableBytes
val localTotalBytes = stat.blockSizeLong * stat.blockCountLong
val localDownloadedBytes = visual.sumOf { it.totalBytes }
_usedBytes.postValue(localTotalBytes - localBytesAvailable - localDownloadedBytes)
_availableBytes.postValue(localBytesAvailable)
_downloadBytes.postValue(localDownloadedBytes)
} catch (t : Throwable) {
} catch (t: Throwable) {
_downloadBytes.postValue(0)
logError(t)
}
_headerCards.postValue(visual)
}
}
}

View file

@ -17,7 +17,7 @@ import com.lagradost.safefile.SafeFile
const val DTAG = "PlayerActivity"
class DownloadedPlayerActivity : AppCompatActivity() {
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let {
return it
}

View file

@ -32,6 +32,7 @@ import com.lagradost.cloudstream3.CommonActivity.keyEventListener
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.CommonActivity.screenHeight
import com.lagradost.cloudstream3.CommonActivity.screenWidth
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding
@ -177,7 +178,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
open fun openOnlineSubPicker(
context: Context,
imdbId: Long?,
loadResponse: LoadResponse?,
dismissCallback: (() -> Unit)
) {
throw NotImplementedError()

View file

@ -25,6 +25,10 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.getImdbId
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
import com.lagradost.cloudstream3.LoadResponse.Companion.getTMDbId
import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
import com.lagradost.cloudstream3.databinding.PlayerSelectSourceAndSubsBinding
@ -39,7 +43,6 @@ import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSub
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
import com.lagradost.cloudstream3.ui.result.*
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
@ -258,6 +261,7 @@ class GeneratorPlayer : FullScreenPlayer() {
var episode: Int? = null,
var season: Int? = null,
var name: String? = null,
var imdbId: String? = null,
)
private fun getMetaData(): TempMetaData {
@ -284,7 +288,7 @@ class GeneratorPlayer : FullScreenPlayer() {
}
override fun openOnlineSubPicker(
context: Context, imdbId: Long?, dismissCallback: (() -> Unit)
context: Context, loadResponse: LoadResponse?, dismissCallback: (() -> Unit)
) {
val providers = subsProviders
val isSingleProvider = subsProviders.size == 1
@ -377,6 +381,7 @@ class GeneratorPlayer : FullScreenPlayer() {
}
val currentTempMeta = getMetaData()
// bruh idk why it is not correct
val color = ColorStateList.valueOf(context.colorFromAttribute(R.attr.colorAccent))
binding.searchLoadingBar.progressTintList = color
@ -424,7 +429,10 @@ class GeneratorPlayer : FullScreenPlayer() {
val search =
AbstractSubtitleEntities.SubtitleSearch(
query = query ?: return@ioSafe,
imdb = imdbId,
imdbId = loadResponse?.getImdbId(),
tmdbId = loadResponse?.getTMDbId()?.toInt(),
malId = loadResponse?.getMalId()?.toInt(),
aniListId = loadResponse?.getAniListId()?.toInt(),
epNumber = currentTempMeta.episode,
seasonNumber = currentTempMeta.season,
lang = currentLanguageTwoLetters.ifBlank { null },
@ -633,6 +641,8 @@ class GeneratorPlayer : FullScreenPlayer() {
}
if (subsProvidersIsActive) {
val currentLoadResponse = viewModel.getLoadResponse()
val loadFromOpenSubsFooter: TextView = layoutInflater.inflate(
R.layout.sort_bottom_footer_add_choice, null
) as TextView
@ -643,7 +653,7 @@ class GeneratorPlayer : FullScreenPlayer() {
loadFromOpenSubsFooter.setOnClickListener {
shouldDismiss = false
sourceDialog.dismissSafe(activity)
openOnlineSubPicker(it.context, null) {
openOnlineSubPicker(it.context, currentLoadResponse) {
dismiss()
}
}

View file

@ -10,7 +10,8 @@ enum class LoadType {
InAppDownload,
ExternalApp,
Browser,
Chromecast
Chromecast,
Fcast
}
fun LoadType.toSet() : Set<ExtractorLinkType> {
@ -29,12 +30,17 @@ fun LoadType.toSet() : Set<ExtractorLinkType> {
ExtractorLinkType.VIDEO,
ExtractorLinkType.M3U8
)
LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.values().toSet()
LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.entries.toSet()
LoadType.Chromecast -> setOf(
ExtractorLinkType.VIDEO,
ExtractorLinkType.DASH,
ExtractorLinkType.M3U8
)
LoadType.Fcast -> setOf(
ExtractorLinkType.VIDEO,
ExtractorLinkType.DASH,
ExtractorLinkType.M3U8
)
}
}

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError
@ -111,6 +112,9 @@ class PlayerGeneratorViewModel : ViewModel() {
}
}
}
fun getLoadResponse(): LoadResponse? {
return normalSafeApiCall { (generator as? RepoLinkGenerator?)?.page }
}
fun getMeta(): Any? {
return normalSafeApiCall { generator?.getCurrent() }

View file

@ -34,6 +34,9 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.search.SearchViewModel
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
@ -274,9 +277,14 @@ class QuickSearchFragment : Fragment() {
// UIHelper.showInputMethod(view.findFocus())
// }
//}
binding?.quickSearchBack?.setOnClickListener {
if (isLayout(PHONE or EMULATOR)) {
binding?.quickSearchBack?.apply {
isVisible = true
setOnClickListener {
activity?.popCurrentPage()
}
}
}
if (isLayout(TV)) {
binding?.quickSearch?.requestFocus()

View file

@ -55,6 +55,8 @@ const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
const val ACTION_PLAY_EPISODE_IN_MPV = 17
const val ACTION_MARK_AS_WATCHED = 18
const val ACTION_FCAST = 19
const val TV_EP_SIZE_LARGE = 400
const val TV_EP_SIZE_SMALL = 300
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
@ -190,15 +192,15 @@ class EpisodeAdapter(
downloadButton.isVisible = hasDownloadSupport
downloadButton.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
card.name,
card.poster,
card.episode,
card.season,
card.id,
card.parentId,
card.rating,
card.description,
System.currentTimeMillis(),
name = card.name,
poster = card.poster,
episode = card.episode,
season = card.season,
id = card.id,
parentId = card.parentId,
rating = card.rating,
description = card.description,
cacheTime = System.currentTimeMillis(),
), null
) {
when (it.action) {
@ -341,15 +343,15 @@ class EpisodeAdapter(
downloadButton.isVisible = hasDownloadSupport
downloadButton.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
card.name,
card.poster,
card.episode,
card.season,
card.id,
card.parentId,
card.rating,
card.description,
System.currentTimeMillis(),
name = card.name,
poster = card.poster,
episode = card.episode,
season = card.season,
id = card.id,
parentId = card.parentId,
rating = card.rating,
description = card.description,
cacheTime = System.currentTimeMillis(),
), null
) {
when (it.action) {

View file

@ -185,8 +185,6 @@ open class ResultFragmentPhone : FullScreenPlayer() {
}
binding?.resultFullscreenHolder?.isVisible = !isSuccess && isFullScreenPlayer
}
//player_view?.apply {
//alpha = 0.0f
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
@ -200,9 +198,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
// fillAfter = true
//}
//startAnimation(fadeIn)
// }
//}
}
private fun setTrailers(trailers: List<ExtractorLink>?) {
@ -630,15 +626,15 @@ open class ResultFragmentPhone : FullScreenPlayer() {
}
downloadButton.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
ep.poster,
0,
null,
ep.id,
ep.id,
null,
null,
System.currentTimeMillis(),
name = ep.name,
poster = ep.poster,
episode = 0,
season = null,
id = ep.id,
parentId = ep.id,
rating = null,
description = null,
cacheTime = System.currentTimeMillis(),
),
null
) { click ->

View file

@ -12,6 +12,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.lagradost.cloudstream3.CommonActivity.screenHeight
import com.lagradost.cloudstream3.CommonActivity.screenWidth
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.PlayerEventSource
@ -110,7 +111,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone() {
override fun openOnlineSubPicker(
context: Context,
imdbId: Long?,
loadResponse: LoadResponse?,
dismissCallback: () -> Unit
) {
}

View file

@ -27,8 +27,17 @@ import com.lagradost.cloudstream3.CommonActivity.getCastSession
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.getImdbId
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
import com.lagradost.cloudstream3.MainActivity.Companion.MPV
import com.lagradost.cloudstream3.MainActivity.Companion.MPV_COMPONENT
import com.lagradost.cloudstream3.MainActivity.Companion.MPV_PACKAGE
import com.lagradost.cloudstream3.MainActivity.Companion.VLC
import com.lagradost.cloudstream3.MainActivity.Companion.VLC_COMPONENT
import com.lagradost.cloudstream3.MainActivity.Companion.VLC_PACKAGE
import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO
import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO_CAST_PACKAGE
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.syncproviders.AccountManager
@ -83,6 +92,10 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.fcast.FcastManager
import com.lagradost.cloudstream3.utils.fcast.FcastSession
import com.lagradost.cloudstream3.utils.fcast.Opcode
import com.lagradost.cloudstream3.utils.fcast.PlayMessage
import kotlinx.coroutines.*
import java.io.File
import java.util.concurrent.TimeUnit
@ -197,7 +210,11 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
else -> null
}?.also {
nextAiringEpisode = txt(R.string.next_episode_format, airing.episode)
nextAiringEpisode = when (airing.season) {
null -> txt(R.string.next_episode_format, airing.episode)
else -> txt(R.string.next_season_episode_format, airing.season, airing.episode)
}
}
}
}
@ -688,13 +705,13 @@ class ResultViewModel2 : ViewModel() {
DOWNLOAD_HEADER_CACHE,
parentId.toString(),
VideoDownloadHelper.DownloadHeaderCached(
apiName,
url,
currentType,
currentHeaderName,
currentPoster,
parentId,
System.currentTimeMillis(),
apiName = apiName,
url = url,
type = currentType,
name = currentHeaderName,
poster = currentPoster,
id = parentId,
cacheTime = System.currentTimeMillis(),
)
)
@ -705,15 +722,15 @@ class ResultViewModel2 : ViewModel() {
), // 3 deep folder for faster acess
episode.id.toString(),
VideoDownloadHelper.DownloadEpisodeCached(
episode.name,
episode.poster,
episode.episode,
episode.season,
episode.id,
parentId,
episode.rating,
episode.description,
System.currentTimeMillis(),
name = episode.name,
poster = episode.poster,
episode = episode.episode,
season = episode.season,
id = episode.id,
parentId = parentId,
rating = episode.rating,
description = episode.description,
cacheTime = System.currentTimeMillis(),
)
)
@ -1346,7 +1363,7 @@ class ResultViewModel2 : ViewModel() {
private fun launchActivity(
activity: Activity?,
resumeApp: ResultResume,
resumeApp: MainActivity.Companion.ResultResume,
id: Int? = null,
work: suspend (Intent.(Activity) -> Unit)
): Job? {
@ -1515,6 +1532,13 @@ class ResultViewModel2 : ViewModel() {
)
)
}
if (FcastManager.currentDevices.isNotEmpty()) {
options.add(
txt(R.string.player_settings_play_in_fcast) to ACTION_FCAST
)
}
options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER)
for (app in apps) {
@ -1690,6 +1714,39 @@ class ResultViewModel2 : ViewModel() {
}
}
ACTION_FCAST -> {
val devices = FcastManager.currentDevices.toList()
postPopup(
txt(R.string.player_settings_select_cast_device),
devices.map { txt(it.name) }) { index ->
if (index == null) return@postPopup
val device = devices.getOrNull(index)
acquireSingleLink(
click.data,
LoadType.Fcast,
txt(R.string.episode_action_cast_mirror)
) { (result, index) ->
val host = device?.host ?: return@acquireSingleLink
val link = result.links.getOrNull(index) ?: return@acquireSingleLink
FcastSession(host).use { session ->
session.sendMessage(
Opcode.Play,
PlayMessage(
link.type.getMimeType(),
link.url,
headers = mapOf(
"referer" to link.referer,
"user-agent" to USER_AGENT
) + link.headers
)
)
}
}
}
}
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
click.data,
LoadType.Browser,
@ -2361,7 +2418,7 @@ class ResultViewModel2 : ViewModel() {
null,
loadResponse.type,
mainId,
null
null,
)
)
}
@ -2719,13 +2776,13 @@ class ResultViewModel2 : ViewModel() {
DOWNLOAD_HEADER_CACHE,
mainId.toString(),
VideoDownloadHelper.DownloadHeaderCached(
apiName,
validUrl,
loadResponse.type,
loadResponse.name,
loadResponse.posterUrl,
mainId,
System.currentTimeMillis(),
apiName = apiName,
url = validUrl,
type = loadResponse.type,
name = loadResponse.name,
poster = loadResponse.posterUrl,
id = mainId,
cacheTime = System.currentTimeMillis(),
)
)
if (loadTrailers)

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.result
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import android.widget.ImageView
import android.widget.TextView
@ -84,12 +85,14 @@ sealed class UiImage {
) : UiImage()
data class Drawable(@DrawableRes val resId: Int) : UiImage()
data class Bitmap(val bitmap: android.graphics.Bitmap) : UiImage()
}
fun ImageView?.setImage(value: UiImage?, fadeIn: Boolean = true) {
when (value) {
is UiImage.Image -> setImageImage(value, fadeIn)
is UiImage.Drawable -> setImageDrawable(value)
is UiImage.Bitmap -> setImageBitmap(value)
null -> {
this?.isVisible = false
}
@ -107,6 +110,12 @@ fun ImageView?.setImageDrawable(value: UiImage.Drawable) {
this.setImage(UiImage.Drawable(value.resId))
}
fun ImageView?.setImageBitmap(value: UiImage.Bitmap) {
if (this == null) return
this.isVisible = true
this.setImageBitmap(value.bitmap)
}
@JvmName("imgNull")
fun img(
url: String?,
@ -129,6 +138,10 @@ fun img(@DrawableRes drawable: Int): UiImage {
return UiImage.Drawable(drawable)
}
fun img(bitmap: Bitmap): UiImage {
return UiImage.Bitmap(bitmap)
}
fun txt(value: String): UiText {
return UiText.DynamicString(value)
}

View file

@ -25,7 +25,7 @@ object SearchHelper {
SEARCH_ACTION_PLAY_FILE -> {
if (card is DataStoreHelper.ResumeWatchingResult) {
val id = card.id
if(id == null) {
if (id == null) {
showToast(R.string.error_invalid_id, Toast.LENGTH_SHORT)
} else {
if (card.isFromDownload) {
@ -33,15 +33,15 @@ object SearchHelper {
DownloadClickEvent(
DOWNLOAD_ACTION_PLAY_FILE,
VideoDownloadHelper.DownloadEpisodeCached(
card.name,
card.posterUrl,
card.episode ?: 0,
card.season,
id,
card.parentId ?: return,
null,
null,
System.currentTimeMillis()
name = card.name,
poster = card.posterUrl,
episode = card.episode ?: 0,
season = card.season,
id = id,
parentId = card.parentId ?: return,
rating = null,
description = null,
cacheTime = System.currentTimeMillis(),
)
)
)

View file

@ -1,12 +1,16 @@
package com.lagradost.cloudstream3.ui.settings
import android.graphics.Bitmap
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import android.view.View.*
import android.view.View.FOCUS_DOWN
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.annotation.UiThread
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmapOrNull
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
@ -21,26 +25,33 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.AccountManagmentBinding
import com.lagradost.cloudstream3.databinding.AccountSwitchBinding
import com.lagradost.cloudstream3.databinding.AddAccountInputBinding
import com.lagradost.cloudstream3.databinding.DeviceAuthBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.OAuth2API
import com.lagradost.cloudstream3.ui.result.img
import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.BackupUtils
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.authCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
@ -49,11 +60,15 @@ import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import qrcode.QRCode
import java.io.ByteArrayOutputStream
class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.BiometricAuthCallback {
class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback {
companion object {
/** Used by nginx plugin too */
fun showLoginInfo(
@ -132,7 +147,109 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.Biome
try {
when (api) {
is OAuth2API -> {
if (isLayout(PHONE) || !api.supportDeviceAuth) {
api.authenticate(activity)
} else if (api.supportDeviceAuth && activity != null) {
val binding: DeviceAuthBinding =
DeviceAuthBinding.inflate(activity.layoutInflater, null, false)
val builder =
AlertDialog.Builder(activity)
.setView(binding.root)
builder.apply {
setNegativeButton(R.string.cancel) { _, _ -> }
setPositiveButton(R.string.auth_locally) { _, _ ->
api.authenticate(activity)
}
}
val dialog = builder.create()
ioSafe {
try {
val pinCodeData = api.getDevicePin()
if (pinCodeData == null) {
showToast(R.string.device_pin_error_message)
api.authenticate(activity)
return@ioSafe
}
/*val logoBytes = ContextCompat.getDrawable(
activity,
R.drawable.cloud_2_solid
)?.toBitmapOrNull()?.let { bitmap ->
val csLogo = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, csLogo)
csLogo.toByteArray()
}*/
val qrCodeImage = QRCode.ofRoundedSquares()
.withColor(activity.colorFromAttribute(R.attr.textColor))
.withBackgroundColor(activity.colorFromAttribute(R.attr.primaryBlackBackground))
//.withLogo(logoBytes, 200.toPx, 200.toPx) //For later if logo needed anytime
.build(pinCodeData.verificationUrl)
.render().nativeImage() as Bitmap
activity.runOnUiThread {
dialog.show()
binding.apply {
devicePinCode.setText(txt(pinCodeData.userCode))
deviceAuthMessage.setText(
txt(
R.string.device_pin_url_message,
pinCodeData.verificationUrl
)
)
deviceAuthQrcode.setImage(
img(qrCodeImage)
)
}
val expirationMillis =
pinCodeData.expiresIn.times(1000).toLong()
object : CountDownTimer(expirationMillis, 1000) {
override fun onTick(millisUntilFinished: Long) {
val secondsUntilFinished =
millisUntilFinished.div(1000).toInt()
binding.deviceAuthValidationCounter.setText(
txt(
R.string.device_pin_counter_text,
secondsUntilFinished.div(60),
secondsUntilFinished.rem(60)
)
)
ioSafe {
if (secondsUntilFinished.rem(pinCodeData.interval) == 0 && api.handleDeviceAuth(pinCodeData)) {
showToast(
txt(
R.string.authenticated_user,
api.name
)
)
dialog.dismissSafe(activity)
cancel()
}
}
}
override fun onFinish() {
showToast(R.string.device_pin_expired_message)
dialog.dismissSafe(activity)
}
}.start()
}
} catch (e: Exception) {
logError(e)
}
}
}
}
is InAppAuthAPI -> {
@ -225,23 +342,15 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.Biome
server = if (api.requiresServer) binding.loginServerInput.text?.toString() else null,
)
ioSafe {
val isSuccessful = try {
api.login(loginData)
} catch (e: Exception) {
logError(e)
false
}
activity.runOnUiThread {
try {
showToast(
activity.getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail)
.format(
txt(
if (api.login(loginData)) R.string.authenticated_user else R.string.authenticated_user_fail,
api.name
)
)
} catch (e: Exception) {
logError(e) // format might fail
}
logError(e)
}
}
dialog.dismissSafe(activity)
@ -297,10 +406,10 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.Biome
hideKeyboard()
setPreferencesFromResource(R.xml.settings_account, rootKey)
// hide preference on tvs and emulators
getPref(R.string.biometric_key)?.isEnabled = isLayout(PHONE)
//Hides the security category on TV as it's only Biometric for now
getPref(R.string.pref_category_security_key)?.hideOn(TV or EMULATOR)
getPref(R.string.biometric_key)?.setOnPreferenceClickListener {
getPref(R.string.biometric_key)?.hideOn(TV or EMULATOR)?.setOnPreferenceClickListener {
val ctx = context ?: return@setOnPreferenceClickListener false
if (deviceHasPasswordPinLock(ctx)) {
@ -324,12 +433,12 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.Biome
R.string.anilist_key to aniListApi,
R.string.simkl_key to simklApi,
R.string.opensubtitles_key to openSubtitlesApi,
R.string.subdl_key to subDlApi,
)
for ((key, api) in syncApis) {
getPref(key)?.apply {
title =
getString(R.string.login_format).format(api.name, getString(R.string.account))
title = api.name
setOnPreferenceClickListener {
val info = api.loginInfo()
if (info != null) {

View file

@ -23,8 +23,10 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.account
import com.lagradost.cloudstream3.ui.home.HomeFragment
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
@ -52,6 +54,30 @@ class SettingsFragment : Fragment() {
}
}
/**
* Hide many Preferences on selected layouts.
**/
fun PreferenceFragmentCompat?.hidePrefs(ids: List<Int>, layoutFlags: Int) {
if (this == null) return
try {
ids.forEach {
getPref(it)?.isVisible = !isLayout(layoutFlags)
}
} catch (e: Exception) {
logError(e)
}
}
/**
* Hide the Preference on selected layouts.
**/
fun Preference?.hideOn(layoutFlags: Int): Preference? {
if (this == null) return null
this.isVisible = !isLayout(layoutFlags)
return this
}
/**
* On TV you cannot properly scroll to the bottom of settings, this fixes that.
* */
@ -84,11 +110,13 @@ class SettingsFragment : Fragment() {
settingsToolbar.apply {
setTitle(title)
if (isLayout(PHONE or EMULATOR)) {
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
setNavigationOnClickListener {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
}
UIHelper.fixPaddingStatusbar(settingsToolbar)
}
@ -98,12 +126,14 @@ class SettingsFragment : Fragment() {
settingsToolbar.apply {
setTitle(title)
if (isLayout(PHONE or EMULATOR)) {
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag)
setNavigationOnClickListener {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
}
UIHelper.fixPaddingStatusbar(settingsToolbar)
}

View file

@ -27,10 +27,13 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.network.initClient
import com.lagradost.cloudstream3.ui.EasterEggMonke
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -71,6 +74,7 @@ val appLanguages = arrayListOf(
Triple("", "አማርኛ", "am"),
Triple("", "العربية", "ar"),
Triple("", "اللهجة النجدية", "ars"),
Triple("", "অসমীয়া", "as"),
Triple("", "български", "bg"),
Triple("", "বাংলা", "bn"),
Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"),
@ -208,9 +212,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
// disable preference on tvs and emulators
getPref(R.string.battery_optimisation_key)?.isEnabled = isLayout(PHONE)
getPref(R.string.battery_optimisation_key)?.setOnPreferenceClickListener {
getPref(R.string.battery_optimisation_key)?.hideOn(TV or EMULATOR)?.setOnPreferenceClickListener {
val ctx = context ?: return@setOnPreferenceClickListener false
if (isAppRestricted(ctx)) {

View file

@ -7,8 +7,14 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hidePrefs
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -31,6 +37,18 @@ class SettingsPlayer : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.settings_player, rootKey)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
//Hide specific prefs on TV/EMULATOR
hidePrefs(
listOf(
R.string.pref_category_gestures_key,
R.string.rotate_video_key,
R.string.auto_rotate_video_key
),
TV or EMULATOR
)
getPref(R.string.pref_category_android_tv_key)?.hideOn(PHONE)
getPref(R.string.video_buffer_length_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.video_buffer_length_names)
val prefValues = resources.getIntArray(R.array.video_buffer_length_values)
@ -227,6 +245,5 @@ class SettingsPlayer : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
}
}
}

View file

@ -33,7 +33,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog
import com.lagradost.cloudstream3.utils.AppUtils.addRepositoryDialog
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
@ -273,9 +273,9 @@ class ExtensionsFragment : Fragment() {
if (plugins.isNullOrEmpty()) {
showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG)
} else {
this@ExtensionsFragment.activity?.downloadAllPluginsDialog(
this@ExtensionsFragment.activity?.addRepositoryDialog(
fixedName,
url,
fixedName
)
}
}

View file

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding
@ -70,6 +71,8 @@ class PluginsFragment : Fragment() {
val name = arguments?.getString(PLUGINS_BUNDLE_NAME)
val url = arguments?.getString(PLUGINS_BUNDLE_URL)
val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true
// download all extensions button
val downloadAllButton = binding?.settingsToolbar?.menu?.findItem(R.id.download_all)
if (url == null || name == null) {
activity?.onBackPressedDispatcher?.onBackPressed()
@ -171,7 +174,7 @@ class PluginsFragment : Fragment() {
if (isLocal) {
// No download button and no categories on local
binding?.settingsToolbar?.menu?.findItem(R.id.download_all)?.isVisible = false
downloadAllButton?.isVisible = false
binding?.settingsToolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false
pluginViewModel.updatePluginListLocal()
@ -179,6 +182,10 @@ class PluginsFragment : Fragment() {
} else {
pluginViewModel.updatePluginList(context, url)
binding?.tvtypesChipsScroll?.root?.isVisible = true
// not needed for users but may be useful for devs
downloadAllButton?.isVisible = BuildConfig.DEBUG
bindChips(
binding?.tvtypesChipsScroll?.tvtypesChips,

View file

@ -9,6 +9,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.amap
@ -181,8 +182,11 @@ class PluginsViewModel : ViewModel() {
}
private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) {
val isAdult = settingsForProvider.enableAdult
val plugins = getPlugins(repositoryUrl)
val list = plugins.map { plugin ->
val list = plugins.filter {
return@filter !(it.second.tvTypes?.contains("NSFW") == true && !isAdult)
}.map { plugin ->
PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first))
}

View file

@ -2,26 +2,31 @@ 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 android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ProviderTestItemBinding
import com.lagradost.cloudstream3.mvvm.getAllMessages
import com.lagradost.cloudstream3.mvvm.getStackTracePretty
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.TestingUtils
import java.io.File
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(
ProviderTestItemBinding.inflate(LayoutInflater.from(parent.context), parent,false)
ProviderTestItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
//LayoutInflater.from(parent.context)
// .inflate(R.layout.provider_test_item, parent, false),
)
@ -36,7 +41,8 @@ class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUti
}
}
inner class ProviderTestViewHolder(binding: ProviderTestItemBinding) : RecyclerView.ViewHolder(binding.root) {
inner class ProviderTestViewHolder(binding: ProviderTestItemBinding) :
RecyclerView.ViewHolder(binding.root) {
private val languageText: TextView = binding.langIcon
private val providerTitle: TextView = binding.mainText
private val statusText: TextView = binding.passedFailedMarker
@ -52,7 +58,11 @@ class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUti
providerTitle.text = api.name
val (resultText, resultColor) = if (result.success) {
if (result.log.any { it.level == TestingUtils.Logger.LogLevel.Warning }) {
R.string.test_warning to R.color.colorTestWarning
} else {
R.string.test_passed to R.color.colorTestPass
}
} else {
R.string.test_failed to R.color.colorTestFail
}
@ -62,17 +72,43 @@ class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUti
val stackTrace = result.exception?.getStackTracePretty(false)?.ifBlank { null }
val messages = result.exception?.getAllMessages()?.ifBlank { null }
val resultLog = result.log.joinToString("\n")
val fullLog =
result.log + (messages?.let { "\n\n$it" } ?: "") + (stackTrace?.let { "\n\n$it" } ?: "")
resultLog +
(messages?.let { "\n\nError: $it" } ?: "") +
(stackTrace?.let { "\n\n$it" } ?: "")
failDescription.text = messages?.lastLine() ?: result.log.lastLine()
failDescription.text = messages?.lastLine() ?: resultLog.lastLine()
logButton.setOnClickListener {
val builder: AlertDialog.Builder =
AlertDialog.Builder(it.context, R.style.AlertDialogCustom)
builder.setMessage(fullLog)
.setTitle(R.string.test_log)
.show()
// Ok button just closes the dialog
.setPositiveButton(R.string.ok) { _, _ -> }
api.sourcePlugin?.let { path ->
val pluginFile = File(path)
// Cannot delete a deleted plugin
if (!pluginFile.exists()) return@let
builder.setNegativeButton(R.string.delete_plugin) { _, _ ->
ioSafe {
val success = PluginManager.deletePlugin(pluginFile)
runOnMainThread {
if (success) {
showToast(R.string.plugin_deleted, Toast.LENGTH_SHORT)
} else {
showToast(R.string.error, Toast.LENGTH_SHORT)
}
}
}
}
}
builder.show()
}
}
}

View file

@ -95,7 +95,7 @@ class TestViewModel : ViewModel() {
providers.clear()
updateProgress()
TestingUtils.getDeferredProviderTests(scope ?: return, apis, ::println) { api, result ->
TestingUtils.getDeferredProviderTests(scope ?: return, apis) { api, result ->
addProvider(api, result)
}
}

View file

@ -62,7 +62,8 @@ import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WebviewFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll
import com.lagradost.cloudstream3.ui.settings.extensions.ExtensionsFragment
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsFragment
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
@ -386,7 +387,7 @@ object AppUtils {
)
}
afterRepositoryLoadedEvent.invoke(true)
downloadAllPluginsDialog(url, repo.name)
addRepositoryDialog(repo.name, url)
}
}
@ -429,25 +430,36 @@ object AppUtils {
}
}
fun Activity.addRepositoryDialog(
repositoryName: String,
repositoryURL: String,
) {
val repos = RepositoryManager.getRepositories()
// navigate to newly added repository on pressing Open Repository
fun openAddedRepo() {
if (repos.isNotEmpty()) {
navigate(
R.id.global_to_navigation_settings_plugins,
PluginsFragment.newInstance(
repositoryName,
repositoryURL,
false,
)
)
}
}
fun Activity.downloadAllPluginsDialog(repositoryUrl: String, repositoryName: String) {
runOnUiThread {
val context = this
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle(
repositoryName
)
builder.setMessage(
R.string.download_all_plugins_from_repo
)
builder.apply {
setPositiveButton(R.string.download) { _, _ ->
downloadAll(context, repositoryUrl, null)
AlertDialog.Builder(this).apply {
setTitle(repositoryName)
setMessage(R.string.download_all_plugins_from_repo)
setPositiveButton(R.string.open_downloaded_repo) { _, _ ->
openAddedRepo()
}
setNegativeButton(R.string.no) { _, _ -> }
setNegativeButton(R.string.dismiss, null)
show().setDefaultFocus()
}
builder.show().setDefaultFocus()
}
}

View file

@ -26,6 +26,7 @@ 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.syncproviders.providers.SubDlApi.Companion.SUBDL_SUBTITLES_USER_KEY
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
@ -64,6 +65,7 @@ object BackupUtils {
PLUGINS_KEY_LOCAL,
OPEN_SUBTITLES_USER_KEY,
SUBDL_SUBTITLES_USER_KEY,
DOWNLOAD_EPISODE_CACHE,

View file

@ -26,7 +26,7 @@ object BiometricAuthenticator {
private var biometricManager: BiometricManager? = null
var biometricPrompt: BiometricPrompt? = null
var promptInfo: BiometricPrompt.PromptInfo? = null
var authCallback: BiometricAuthCallback? = null // listen to authentication success
var authCallback: BiometricCallback? = null // listen to authentication success
private fun initializeBiometrics(activity: Activity) {
val executor = ContextCompat.getMainExecutor(activity)
@ -141,14 +141,14 @@ object BiometricAuthenticator {
// function to start authentication in any fragment or activity
fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
initializeBiometrics(activity)
authCallback = activity as? BiometricAuthCallback
authCallback = activity as? BiometricCallback
if (isBiometricHardWareAvailable()) {
authCallback = activity as? BiometricAuthCallback
authCallback = activity as? BiometricCallback
authenticationDialog(activity, title, setDeviceCred)
promptInfo?.let { biometricPrompt?.authenticate(it) }
} else {
if (deviceHasPasswordPinLock(activity)) {
authCallback = activity as? BiometricAuthCallback
authCallback = activity as? BiometricCallback
authenticationDialog(activity, R.string.password_pin_authentication_title, true)
promptInfo?.let { biometricPrompt?.authenticate(it) }
@ -165,7 +165,7 @@ object BiometricAuthenticator {
}
}
interface BiometricAuthCallback {
interface BiometricCallback {
fun onAuthenticationSuccess()
fun onAuthenticationError()
}

View file

@ -17,6 +17,7 @@ import com.lagradost.cloudstream3.extractors.BullStream
import com.lagradost.cloudstream3.extractors.ByteShare
import com.lagradost.cloudstream3.extractors.Cda
import com.lagradost.cloudstream3.extractors.Cdnplayer
import com.lagradost.cloudstream3.extractors.CdnwishCom
import com.lagradost.cloudstream3.extractors.Chillx
import com.lagradost.cloudstream3.extractors.CineGrabber
import com.lagradost.cloudstream3.extractors.Cinestart
@ -53,6 +54,7 @@ import com.lagradost.cloudstream3.extractors.FileMoonIn
import com.lagradost.cloudstream3.extractors.FileMoonSx
import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.extractors.Fplayer
import com.lagradost.cloudstream3.extractors.Geodailymotion
import com.lagradost.cloudstream3.extractors.GMPlayer
import com.lagradost.cloudstream3.extractors.Gdriveplayer
import com.lagradost.cloudstream3.extractors.Gdriveplayerapi
@ -66,6 +68,7 @@ import com.lagradost.cloudstream3.extractors.Gdriveplayerorg
import com.lagradost.cloudstream3.extractors.Gdriveplayerus
import com.lagradost.cloudstream3.extractors.Gofile
import com.lagradost.cloudstream3.extractors.GuardareStream
import com.lagradost.cloudstream3.extractors.GoodstreamExtractor
import com.lagradost.cloudstream3.extractors.Guccihide
import com.lagradost.cloudstream3.extractors.Hxfile
import com.lagradost.cloudstream3.extractors.JWPlayer
@ -104,11 +107,15 @@ import com.lagradost.cloudstream3.extractors.Odnoklassniki
import com.lagradost.cloudstream3.extractors.TauVideo
import com.lagradost.cloudstream3.extractors.SibNet
import com.lagradost.cloudstream3.extractors.ContentX
import com.lagradost.cloudstream3.extractors.D0000d
import com.lagradost.cloudstream3.extractors.D000dCom
import com.lagradost.cloudstream3.extractors.DoodstreamCom
import com.lagradost.cloudstream3.extractors.EmturbovidExtractor
import com.lagradost.cloudstream3.extractors.Hotlinger
import com.lagradost.cloudstream3.extractors.FourCX
import com.lagradost.cloudstream3.extractors.PlayRu
import com.lagradost.cloudstream3.extractors.FourPlayRu
import com.lagradost.cloudstream3.extractors.FourPichive
import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem
import com.lagradost.cloudstream3.extractors.VideoSeyred
@ -185,6 +192,7 @@ import com.lagradost.cloudstream3.extractors.Vanfem
import com.lagradost.cloudstream3.extractors.Vicloud
import com.lagradost.cloudstream3.extractors.VidSrcExtractor
import com.lagradost.cloudstream3.extractors.VidSrcExtractor2
import com.lagradost.cloudstream3.extractors.VidSrcTo
import com.lagradost.cloudstream3.extractors.VideoVard
import com.lagradost.cloudstream3.extractors.VideovardSX
import com.lagradost.cloudstream3.extractors.Vidgomunime
@ -223,7 +231,10 @@ import com.lagradost.cloudstream3.extractors.Zplayer
import com.lagradost.cloudstream3.extractors.ZplayerV2
import com.lagradost.cloudstream3.extractors.Ztreamhub
import com.lagradost.cloudstream3.extractors.EPlayExtractor
import com.lagradost.cloudstream3.extractors.FlaswishCom
import com.lagradost.cloudstream3.extractors.SfastwishCom
import com.lagradost.cloudstream3.extractors.Vtbe
import com.lagradost.cloudstream3.extractors.WishembedPro
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import kotlinx.coroutines.delay
@ -306,7 +317,18 @@ enum class ExtractorLinkType {
/** No support at the moment */
TORRENT,
/** No support at the moment */
MAGNET,
MAGNET;
// See https://www.iana.org/assignments/media-types/media-types.xhtml
fun getMimeType(): String {
return when (this) {
VIDEO -> "video/mp4"
M3U8 -> "application/x-mpegURL"
DASH -> "application/dash+xml"
TORRENT -> "application/x-bittorrent"
MAGNET -> "application/x-bittorrent"
}
}
}
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
@ -735,6 +757,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
FourCX(),
PlayRu(),
FourPlayRu(),
FourPichive(),
HDMomPlayer(),
HDPlayerSystem(),
VideoSeyred(),
@ -761,6 +784,9 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
DoodSoExtractor(),
DoodLaExtractor(),
Dooood(),
D0000d(),
D000dCom(),
DoodstreamCom(),
DoodWsExtractor(),
DoodShExtractor(),
DoodWatchExtractor(),
@ -838,6 +864,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Guccihide(),
FileMoon(),
FileMoonSx(),
Vido(),
Linkbox(),
Acefile(),
@ -864,6 +891,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Gdriveplayerorg(),
Gdriveplayerus(),
Gdriveplayerco(),
GoodstreamExtractor(),
Gdriveplayer(),
DatabaseGdrive(),
DatabaseGdrive2(),
@ -876,6 +904,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Streamlare(),
VidSrcExtractor(),
VidSrcExtractor2(),
VidSrcTo(),
PlayLtXyz(),
AStreamHub(),
Vidplay(),
@ -891,6 +920,10 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Megacloud(),
VidhideExtractor(),
StreamWishExtractor(),
WishembedPro(),
CdnwishCom(),
FlaswishCom(),
SfastwishCom(),
EmturbovidExtractor(),
Vtbe(),
EPlayExtractor(),
@ -898,7 +931,9 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Simpulumlamerop(),
Urochsunloath(),
Yipsu(),
MetaGnathTuggers()
MetaGnathTuggers(),
Geodailymotion(),
)
@ -980,7 +1015,7 @@ abstract class ExtractorApi {
abstract val mainUrl: String
abstract val requiresReferer: Boolean
/** Determines which plugin a given extractor is from */
/** Determines which plugin a given provider is from. This is the full path to the plugin. */
var sourcePlugin: String? = null
//suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? {

View file

@ -13,16 +13,55 @@ object TestingUtils {
}
}
class TestResultSearch(val results: List<SearchResponse>) : TestResult(true)
class TestResultLoad(val extractorData: String) : TestResult(true)
class Logger {
enum class LogLevel {
Normal,
Warning,
Error;
}
class TestResultProvider(success: Boolean, val log: String, val exception: Throwable?) :
data class Message(val level: LogLevel, val message: String) {
override fun toString(): String {
val level = when (this.level) {
LogLevel.Normal -> ""
LogLevel.Warning -> "Warning: "
LogLevel.Error -> "Error: "
}
return "$level$message"
}
}
private val messageLog = mutableListOf<Message>()
fun getRawLog(): List<Message> = messageLog
fun log(message: String) {
messageLog.add(Message(LogLevel.Normal, message))
}
fun warn(message: String) {
messageLog.add(Message(LogLevel.Warning, message))
}
fun error(message: String) {
messageLog.add(Message(LogLevel.Error, message))
}
}
class TestResultList(val results: List<SearchResponse>) : TestResult(true)
class TestResultLoad(val extractorData: String, val shouldLoadLinks: Boolean) : TestResult(true)
class TestResultProvider(
success: Boolean,
val log: List<Logger.Message>,
val exception: Throwable?
) :
TestResult(success)
@Throws(AssertionError::class, CancellationException::class)
suspend fun testHomepage(
api: MainAPI,
logger: (String) -> Unit
logger: Logger
): TestResult {
if (api.hasMainPage) {
try {
@ -31,22 +70,33 @@ object TestingUtils {
api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages))
when {
homepage == null -> {
logger.invoke("Homepage provider ${api.name} did not correctly load homepage!")
logger.error("Provider ${api.name} did not correctly load homepage!")
}
homepage.items.isEmpty() -> {
logger.invoke("Homepage provider ${api.name} does not contain any items!")
logger.warn("Provider ${api.name} does not contain any homepage rows!")
}
homepage.items.any { it.list.isEmpty() } -> {
logger.invoke("Homepage provider ${api.name} does not have any items on result!")
logger.warn("Provider ${api.name} does not have any items in a homepage row!")
}
}
val homePageList = homepage?.items?.flatMap { it.list } ?: emptyList()
return TestResultList(homePageList)
} catch (e: Throwable) {
if (e is NotImplementedError) {
when (e) {
is NotImplementedError -> {
Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented")
} else if (e is CancellationException) {
}
is CancellationException -> {
throw e
}
logError(e)
else -> {
e.message?.let { logger.warn("Exception thrown when loading homepage: \"$it\"") }
}
}
}
}
return TestResult.Pass
@ -54,11 +104,13 @@ object TestingUtils {
@Throws(AssertionError::class, CancellationException::class)
private suspend fun testSearch(
api: MainAPI
api: MainAPI,
testQueries: List<String>,
logger: Logger,
): TestResult {
val searchQueries = listOf("over", "iron", "guy")
val searchResults = searchQueries.firstNotNullOfOrNull { query ->
val searchResults = testQueries.firstNotNullOfOrNull { query ->
try {
logger.log("Searching for: $query")
api.search(query).takeIf { !it.isNullOrEmpty() }
} catch (e: Throwable) {
if (e is NotImplementedError) {
@ -72,12 +124,11 @@ object TestingUtils {
}
return if (searchResults.isNullOrEmpty()) {
Assert.fail("Api ${api.name} did not return any valid search responses")
Assert.fail("Api ${api.name} did not return any search responses")
TestResult.Fail // Should not be reached
} else {
TestResultSearch(searchResults)
TestResultList(searchResults)
}
}
@ -85,31 +136,27 @@ object TestingUtils {
private suspend fun testLoad(
api: MainAPI,
result: SearchResponse,
logger: (String) -> Unit
logger: Logger
): TestResult {
try {
Assert.assertEquals(
"Invalid apiName on SearchResponse on ${api.name}",
result.apiName,
api.name
)
if (result.apiName != api.name) {
logger.warn("Wrong apiName on SearchResponse: ${api.name} != ${result.apiName}")
}
val loadResponse = api.load(result.url)
if (loadResponse == null) {
logger.invoke("Returned null loadResponse on ${result.url} on ${api.name}")
logger.error("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)
)
if (loadResponse.apiName != api.name) {
logger.warn("Wrong apiName on LoadResponse: ${api.name} != ${loadResponse.apiName}")
}
if (!api.supportedTypes.contains(loadResponse.type)) {
logger.warn("Api ${api.name} on load does not contain any of the supportedTypes: ${loadResponse.type}")
}
val url = when (loadResponse) {
is AnimeLoadResponse -> {
@ -117,39 +164,43 @@ object TestingUtils {
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}")
logger.error("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}")
logger.error("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}")
logger.error("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}")
logger.error("Unknown load response: ${loadResponse.javaClass.name}")
return TestResult.Fail
}
} ?: return TestResult.Fail
return TestResultLoad(url)
return TestResultLoad(url, loadResponse.type != TvType.CustomMedia)
// val loadTest = testLoadResponse(api, load, logger)
// if (loadTest is TestResultLoad) {
@ -174,7 +225,7 @@ object TestingUtils {
private suspend fun testLinkLoading(
api: MainAPI,
url: String?,
logger: (String) -> Unit
logger: Logger
): TestResult {
Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
if (url == null) return TestResult.Fail // Should never trigger
@ -182,7 +233,7 @@ object TestingUtils {
var linksLoaded = 0
try {
val success = api.loadLinks(url, false, {}) { link ->
logger.invoke("Video loaded: ${link.name}")
logger.log("Video loaded: ${link.name}")
Assert.assertTrue(
"Api ${api.name} returns link with invalid url ${link.url}",
link.url.length > 4
@ -190,7 +241,7 @@ object TestingUtils {
linksLoaded++
}
if (success) {
logger.invoke("Links loaded: $linksLoaded")
logger.log("Links loaded: $linksLoaded")
return TestResult(linksLoaded > 0)
} else {
Assert.fail("Api ${api.name} returns false on loadLinks() with $linksLoaded links loaded")
@ -200,8 +251,9 @@ object TestingUtils {
is NotImplementedError -> {
Assert.fail("Provider has not implemented loadLinks()")
}
else -> {
logger.invoke("Failed link loading on ${api.name} using data: $url")
logger.error("Failed link loading on ${api.name} using data: $url")
throw e
}
}
@ -212,53 +264,57 @@ object TestingUtils {
fun getDeferredProviderTests(
scope: CoroutineScope,
providers: Array<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 logger = Logger()
val result = try {
addToLog("Trying ${api.name}")
logger.log("Trying ${api.name}")
// Test Homepage
val homepage = testHomepage(api, logger).success
Assert.assertTrue("Homepage failed to load", homepage)
val homepage = testHomepage(api, logger)
Assert.assertTrue("Homepage failed to load", homepage.success)
val homePageList = (homepage as? TestResultList)?.results ?: emptyList()
// Test Search Results
val searchResults = testSearch(api)
val searchQueries =
// Use the first 3 home page results as queries since they are guaranteed to exist
(homePageList.take(3).map { it.name } +
// If home page is sparse then use generic search queries
listOf("over", "iron", "guy")).take(3)
val searchResults = testSearch(api, searchQueries, logger)
Assert.assertTrue("Failed to get search results", searchResults.success)
searchResults as TestResultSearch
searchResults as TestResultList
// 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)
logger.log("Testing search result: ${searchResponse.url}")
val loadResponse = testLoad(api, searchResponse, logger)
if (loadResponse !is TestResultLoad) {
false
} else {
testLinkLoading(api, loadResponse.extractorData, ::addToLog).success
if (loadResponse.shouldLoadLinks) {
testLinkLoading(api, loadResponse.extractorData, logger).success
} else {
logger.log("Skipping link loading test")
true
}
}
}
if (success) {
logger.invoke("Success ${api.name}")
TestResultProvider(true, getLog(), null)
logger.log("Success ${api.name}")
TestResultProvider(true, logger.getRawLog(), null)
} else {
logger.invoke("Error ${api.name}")
TestResultProvider(false, getLog(), null)
logger.error("Link loading failed")
TestResultProvider(false, logger.getRawLog(), null)
}
} catch (e: Throwable) {
TestResultProvider(false, getLog(), e)
TestResultProvider(false, logger.getRawLog(), e)
}
callback.invoke(api, result)
}

View file

@ -3,17 +3,21 @@ package com.lagradost.cloudstream3.utils
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.TvType
object VideoDownloadHelper {
abstract class DownloadCached(
@JsonProperty("id") open val id: Int,
)
data class DownloadEpisodeCached(
@JsonProperty("name") val name: String?,
@JsonProperty("poster") val poster: String?,
@JsonProperty("episode") val episode: Int,
@JsonProperty("season") val season: Int?,
@JsonProperty("id") val id: Int,
@JsonProperty("parentId") val parentId: Int,
@JsonProperty("rating") val rating: Int?,
@JsonProperty("description") val description: String?,
@JsonProperty("cacheTime") val cacheTime: Long,
)
override val id: Int,
): DownloadCached(id)
data class DownloadHeaderCached(
@JsonProperty("apiName") val apiName: String,
@ -21,9 +25,9 @@ object VideoDownloadHelper {
@JsonProperty("type") val type: TvType,
@JsonProperty("name") val name: String,
@JsonProperty("poster") val poster: String?,
@JsonProperty("id") val id: Int,
@JsonProperty("cacheTime") val cacheTime: Long,
)
override val id: Int,
): DownloadCached(id)
data class ResumeWatching(
@JsonProperty("parentId") val parentId: Int,

View file

@ -0,0 +1,135 @@
package com.lagradost.cloudstream3.utils.fcast
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.util.Log
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
class FcastManager {
private var nsdManager: NsdManager? = null
// Used for receiver
private val registrationListenerTcp = DefaultRegistrationListener()
private fun getDeviceName(): String {
return "${Build.MANUFACTURER}-${Build.MODEL}"
}
/**
* Start the fcast service
* @param registerReceiver If true will register the app as a compatible fcast receiver for discovery in other app
*/
fun init(context: Context, registerReceiver: Boolean) = ioSafe {
nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
val serviceType = "_fcast._tcp"
if (registerReceiver) {
val serviceName = "$APP_PREFIX-${getDeviceName()}"
val serviceInfo = NsdServiceInfo().apply {
this.serviceName = serviceName
this.serviceType = serviceType
this.port = TCP_PORT
}
nsdManager?.registerService(
serviceInfo,
NsdManager.PROTOCOL_DNS_SD,
registrationListenerTcp
)
}
nsdManager?.discoverServices(
serviceType,
NsdManager.PROTOCOL_DNS_SD,
DefaultDiscoveryListener()
)
}
fun stop() {
nsdManager?.unregisterService(registrationListenerTcp)
}
inner class DefaultDiscoveryListener : NsdManager.DiscoveryListener {
val tag = "DiscoveryListener"
override fun onStartDiscoveryFailed(serviceType: String?, errorCode: Int) {
Log.d(tag, "Discovery failed: $serviceType, error code: $errorCode")
}
override fun onStopDiscoveryFailed(serviceType: String?, errorCode: Int) {
Log.d(tag, "Stop discovery failed: $serviceType, error code: $errorCode")
}
override fun onDiscoveryStarted(serviceType: String?) {
Log.d(tag, "Discovery started: $serviceType")
}
override fun onDiscoveryStopped(serviceType: String?) {
Log.d(tag, "Discovery stopped: $serviceType")
}
override fun onServiceFound(serviceInfo: NsdServiceInfo?) {
if (serviceInfo == null) return
nsdManager?.resolveService(serviceInfo, object : ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo?, errorCode: Int) {
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo?) {
if (serviceInfo == null) return
currentDevices.add(PublicDeviceInfo(serviceInfo))
Log.d(
tag,
"Service found: ${serviceInfo.serviceName}, Net: ${serviceInfo.host.hostAddress}"
)
}
})
}
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {
if (serviceInfo == null) return
// May remove duplicates, but net and port is null here, preventing device specific identification
currentDevices.removeAll {
it.rawName == serviceInfo.serviceName
}
Log.d(tag, "Service lost: ${serviceInfo.serviceName}")
}
}
companion object {
const val APP_PREFIX = "CloudStream"
val currentDevices: MutableList<PublicDeviceInfo> = mutableListOf()
class DefaultRegistrationListener : NsdManager.RegistrationListener {
val tag = "DiscoveryService"
override fun onServiceRegistered(serviceInfo: NsdServiceInfo) {
Log.d(tag, "Service registered: ${serviceInfo.serviceName}")
}
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.e(tag, "Service registration failed: errorCode=$errorCode")
}
override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) {
Log.d(tag, "Service unregistered: ${serviceInfo.serviceName}")
}
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.e(tag, "Service unregistration failed: errorCode=$errorCode")
}
}
const val TCP_PORT = 46899
}
}
class PublicDeviceInfo(serviceInfo: NsdServiceInfo) {
val rawName: String = serviceInfo.serviceName
val host: String? = serviceInfo.host.hostAddress
val name = rawName.replace("-", " ") + host?.let { " $it" }
}

View file

@ -0,0 +1,60 @@
package com.lagradost.cloudstream3.utils.fcast
import android.util.Log
import androidx.annotation.WorkerThread
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.safefile.closeQuietly
import java.io.DataOutputStream
import java.net.Socket
import kotlin.jvm.Throws
class FcastSession(private val hostAddress: String): AutoCloseable {
val tag = "FcastSession"
private var socket: Socket? = null
@Throws
@WorkerThread
fun open(): Socket {
val socket = Socket(hostAddress, FcastManager.TCP_PORT)
this.socket = socket
return socket
}
override fun close() {
socket?.closeQuietly()
socket = null
}
@Throws
private fun acquireSocket(): Socket {
return socket ?: open()
}
fun ping() {
sendMessage(Opcode.Ping, null)
}
fun <T> sendMessage(opcode: Opcode, message: T) {
ioSafe {
val socket = acquireSocket()
val outputStream = DataOutputStream(socket.getOutputStream())
val json = message?.toJson()
val content = json?.toByteArray() ?: ByteArray(0)
// Little endian starting from 1
// https://gitlab.com/futo-org/fcast/-/wikis/Protocol-version-1
val size = content.size + 1
val sizeArray = ByteArray(4) { num ->
(size shr 8 * num and 0xff).toByte()
}
Log.d(tag, "Sending message with size: $size, opcode: $opcode")
outputStream.write(sizeArray)
outputStream.write(ByteArray(1) { opcode.value })
outputStream.write(content)
}
}
}

View file

@ -0,0 +1,62 @@
package com.lagradost.cloudstream3.utils.fcast
// See https://gitlab.com/futo-org/fcast/-/wikis/Protocol-version-1
enum class Opcode(val value: Byte) {
None(0),
Play(1),
Pause(2),
Resume(3),
Stop(4),
Seek(5),
PlaybackUpdate(6),
VolumeUpdate(7),
SetVolume(8),
PlaybackError(9),
SetSpeed(10),
Version(11),
Ping(12),
Pong(13);
}
data class PlayMessage(
val container: String,
val url: String? = null,
val content: String? = null,
val time: Double? = null,
val speed: Double? = null,
val headers: Map<String, String>? = null
)
data class SeekMessage(
val time: Double
)
data class PlaybackUpdateMessage(
val generationTime: Long,
val time: Double,
val duration: Double,
val state: Int,
val speed: Double
)
data class VolumeUpdateMessage(
val generationTime: Long,
val volume: Double
)
data class PlaybackErrorMessage(
val message: String
)
data class SetSpeedMessage(
val speed: Double
)
data class SetVolumeMessage(
val volume: Double
)
data class VersionMessage(
val version: Long
)

View file

@ -0,0 +1,8 @@
<vector xmlns:tools="http://schemas.android.com/tools" android:name="vector"
android:width="200dp" android:height="200dp" android:viewportWidth="283"
android:viewportHeight="283" xmlns:android="http://schemas.android.com/apk/res/android">
<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="?attr/textColor" android:strokeWidth="1"
tools:ignore="VectorPath"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="20dp"
android:viewportHeight="320"
android:viewportWidth="320"
android:width="20dp">
<path android:fillColor="@color/white"
android:pathData="m107.87,39.3l-8.44,8.59l0,35.68l0,35.83l18.95,22.5c10.36,12.44 30.35,36.27 44.41,53l25.46,30.5l0,12.14l0,12.29l-24.43,-0l-24.43,-0l0,-11.84l0,-11.84l-19.99,-0l-19.99,-0l0,23.24l0,23.24l8.44,8.59l8.44,8.59l48.26,-0l48.26,-0l7.7,-7.85l7.7,-7.85l0,-36.86l-0.15,-37.01l-23.98,-28.28c-13.18,-15.54 -33.16,-39.23 -44.26,-52.55l-20.43,-24.13l0,-12.29l0,-12.29l24.43,-0l24.43,-0l0,12.58l0,12.58l19.99,-0l19.99,-0l0,-24.87l0,-24.87l-7.85,-7.7l-7.85,-7.7l-48.11,-0l-48.11,-0l-8.44,8.59z"/>
</vector>

View file

@ -64,12 +64,14 @@
<TextView
android:id="@+id/account_switch_account"
android:text="@string/switch_account"
style="@style/SettingsItem" />
style="@style/SettingsItem"
android:focusable="true"/>
<TextView
android:id="@+id/account_logout"
android:text="@string/logout"
style="@style/SettingsItem">
style="@style/SettingsItem"
android:focusable="true">
<requestFocus />
</TextView>

View file

@ -4,7 +4,8 @@
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent">
android:layout_width="match_parent"
android:focusable="true">
<androidx.cardview.widget.CardView
android:id="@+id/account_profile_picture_holder"

View file

@ -13,12 +13,14 @@
tools:listitem="@layout/account_single"
android:layout_width="match_parent"
android:layout_rowWeight="1"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:focusable="true"/>
<TextView
android:id="@+id/account_add"
android:text="@string/add_account"
style="@style/SettingsItem">
style="@style/SettingsItem"
android:focusable="true">
<requestFocus />
</TextView>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<TextView
android:id="@+id/device_pin_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/colorPrimary"
android:textSize="30sp"
android:textStyle="bold"
tools:text="YJTSKL" />
<TextView
android:id="@+id/device_auth_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/textColor"
android:textSize="17sp"
tools:text="Visit simkl.com/pin on your smartphone or computer and enter the above code" />
<ImageView
android:id="@+id/device_auth_qrcode"
android:layout_marginTop="20dp"
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:layout_gravity="center_horizontal"
android:visibility="visible"
android:background="?attr/primaryGrayBackground"
android:contentDescription="@string/qr_image"
tools:visibility="visible"
tools:src="@drawable/example_qr" />
<TextView
android:id="@+id/device_auth_validation_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/textColor"
android:textSize="17sp"
tools:text="Code expires in 13m 2s" />
</LinearLayout>

View file

@ -40,7 +40,7 @@
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="30dp">
android:layout_marginEnd="40dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/subtitles_search"
@ -91,7 +91,8 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="20dp"
android:nextFocusLeft="@id/subtitles_search"
android:nextFocusRight="@id/search_filter"
android:text="@string/none"
/>
</LinearLayout>
@ -106,7 +107,8 @@
android:layout_margin="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:focusable="true"
android:nextFocusLeft="@id/year_btt"
android:nextFocusRight="@id/main_search"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"

View file

@ -59,12 +59,12 @@
<ImageView
android:id="@+id/download_header_goto_child"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_width="@dimen/download_size"
android:layout_height="@dimen/download_size"
android:layout_gravity="center_vertical|end"
android:layout_marginStart="-50dp"
android:contentDescription="@string/download"
android:padding="50dp"
android:padding="10dp"
android:src="@drawable/ic_baseline_keyboard_arrow_right_24" />
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton

View file

@ -178,42 +178,40 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:textStyle="bold"
tools:text="The Perfect Run The Perfect Run" />
<LinearLayout
android:id="@+id/result_next_airing_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:orientation="vertical">
<TextView
android:id="@+id/result_episodes_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:textColor="?attr/textColor"
android:textSize="17sp"
android:textStyle="normal"
android:visibility="gone"
tools:visibility="visible"
tools:text="8 Episodes" />
<LinearLayout
android:id="@+id/result_next_airing_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:orientation="horizontal">
<TextView
android:id="@+id/result_next_airing"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:gravity="start"
android:textColor="?attr/grayTextColor"
android:textSize="17sp"
android:textStyle="normal"
tools:text="Episode 1022 will be released in" />
android:layout_marginEnd="5dp"
tools:text="Season 2 Episode 1022 will be released in" />
<TextView
android:id="@+id/result_next_airing_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="start"
android:textColor="?attr/textColor"
android:textSize="17sp"

View file

@ -8,18 +8,18 @@
<ScrollView
android:layout_marginBottom="60dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:nextFocusRight="@id/save_btt">
<TextView
android:id="@+id/text1"
android:padding="15dp"
android:textSize="15sp"
android:textColor="?attr/textColor"
android:layout_width="match_parent"
android:layout_rowWeight="1"
tools:text="Test"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"/>
</ScrollView>
<HorizontalScrollView

View file

@ -23,11 +23,10 @@
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
android:focusable="true"
android:visibility="gone"
android:layout_width="25dp"
android:layout_height="wrap_content">
<requestFocus />
android:layout_height="wrap_content"
tools:visibility="visible">
</ImageView>
<FrameLayout

View file

@ -21,5 +21,6 @@
android:id="@+id/download_all"
android:icon="@drawable/netflix_download"
android:title="@string/batch_download"
app:showAsAction="collapseActionView|ifRoom" />
app:showAsAction="collapseActionView|ifRoom"
android:visible="false"/>
</menu>

View file

@ -111,6 +111,27 @@
app:argType="boolean" />
</action>
<action
android:id="@+id/global_to_navigation_settings_plugins"
app:destination="@id/navigation_settings_plugins"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim">
<argument
android:name="name"
android:defaultValue="@null"
app:argType="string" />
<argument
android:name="url"
android:defaultValue="@null"
app:argType="string" />
<argument
android:name="isLocal"
android:defaultValue="false"
app:argType="boolean" />
</action>
<fragment
android:id="@+id/navigation_settings_player"
android:name="com.lagradost.cloudstream3.ui.settings.SettingsPlayer"

View file

@ -76,7 +76,7 @@
<string name="subtitles_settings">سَتِنگز الترجمة</string>
<string name="subs_window_color">لون العلبة</string>
<string name="play_torrent_button">عمول ستريم للتورنت</string>
<string name="automatic_plugin_download_summary">تلقائيًا نَزِل كل الإضافات من الريپويات يلي نزادِت.</string>
<string name="automatic_plugin_download_summary">تلقائيًا نَزِل كل الإضافات من الريپويات يللي نزادِت.</string>
<string name="delete">محي</string>
<string name="start">بلش</string>
<string name="apk_installer_settings_des">في تِلِفونات ما فيا تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إذا ما عم تنزل التجديدات.</string>
@ -90,7 +90,7 @@
<string name="browser">متصفح الوَب</string>
<string name="double_tap_to_seek_settings_des">كبوس مرتين على اليمين أو الشمال حتى تقرب أو ترَجِع الڤيديو</string>
<string name="normal_no_plot">ما نلاقى وصف الأحداث</string>
<string name="next_episode">الحلقة يلي بَعدا</string>
<string name="next_episode">الحلقة يللي بَعدها</string>
<string name="updates_settings">فرجي تجديدات الآپ</string>
<string name="library">رفّ</string>
<string name="lightnovel">آپ من نفس المطورين للروايات الخفيفة، بدل من الڤيديوات</string>
@ -218,7 +218,7 @@
<string name="live_singular">بث مباشر</string>
<string name="android_tv_interface_on_seek_settings">المشغل مبين - مدة التقديم</string>
<string name="unexpected_error">مشكلة مش متوقع بمشغل الڤيديو (Unexpected player error)</string>
<string name="video_disk_description">بسبب أعطال إذا نحط على مستوى عالي كتير على الأجهزة يلي ما بتساع كتير، متل تلفزيون \"أندرويد\".</string>
<string name="video_disk_description">بسبِب أعطال إذا نحط على مستوى عالي كتير على الأجهزة يللي ما بتساع كتير، متل تلفزيون \"أندرويد\".</string>
<string name="others">شي غير</string>
<string name="skip_update">أفي هيدا التجديد</string>
<string name="episode_action_copy_link">نسوخ الرابط</string>
@ -244,7 +244,7 @@
<string name="video_buffer_length_settings">طول التخزين المتوقت</string>
<string name="episode_action_chromecast_episode">حلقة \"كروم كاست\"</string>
<string name="asian_drama_singular">دراما آسيوية</string>
<string name="video_ram_description">بسبب أعطال إذا نحط على مستوى عالي كتير على الأجهزة يلي ذِكرتا زغيرة، متل تلفزيون \"أندرويد\".</string>
<string name="video_ram_description">بسبِب أعطال إذا نحط على مستوى عالي كتير على الأجهزة يللي ذِكرتها زغيرة، متل تلفزيون \"أندرويد\".</string>
<string name="source_error">مشكلة بالمصدر</string>
<string name="video_buffer_disk_settings">التخزين الموقت للڤيديو على الديسك</string>
<string name="documentaries_singular">فلم وثائقي</string>
@ -283,7 +283,7 @@
<string name="error_bookmarks_text">إشارات المرجعية</string>
<string name="download_started">بَلَش التنزيل</string>
<string name="logged_account" formatted="true">فتّو على الأكونت \"%s\"</string>
<string name="pref_disable_acra">وقِف الإعلان الأتوماتيكي عن المشاكل يلي بالآپ</string>
<string name="pref_disable_acra">وقِف الإعلان الأتوماتيكي عن المشاكل يللي بالآپ</string>
<string name="bottom_title_settings">محل عنوان الپوستر</string>
<string name="category_ui">الشكل</string>
<string name="next_episode_time_hour_format" formatted="true">%1$d ساعة %2$d ديقة</string>
@ -314,7 +314,7 @@
<string name="app_dubbed_text">مدبلج</string>
<string name="automatic">أوتوماتيك</string>
<string name="edit_account">عدلو الأكونت</string>
<string name="pin_error_incorrect">الأرقام السرية يلي نحطت مش صحيحة. جرب مرة أخرى.</string>
<string name="pin_error_incorrect">الأرقام السرية يللي نحطت مش صحيحة. جرب مرة أخرى.</string>
<string name="tv_layout">الشكل المعمول للتلفزيون</string>
<string name="download_canceled">نلغى التنزيل</string>
<string name="account">أكونت</string>
@ -366,9 +366,9 @@
<string name="added_sync_format" formatted="true">زِدت %s</string>
<string name="favorites_list_name">المفضلين</string>
<string name="favorite_added">\"%s\" نزاد ع المفضل</string>
<string name="home_next_random_img_des">العشوائي يلي بعدو</string>
<string name="home_next_random_img_des">العشوائي يللي بعده</string>
<string name="subtitles_shadow">خيال</string>
<string name="subscription_in_progress_notification">عم نجدِد المثلثلات يلي مشتركينلا</string>
<string name="subscription_in_progress_notification">عم نجدِد المثلثلات يللي مشتركينلها</string>
<string name="duplicate_message_multiple" formatted="true">مبين إنو في عنصر متل هيدا موجود بالمكتبة عندكن:
\n
\n%s
@ -481,7 +481,7 @@
<string name="batch_download">نزلو المصادر بالجملة</string>
<string name="open_with">فتاح بـ</string>
<string name="sync_score">راينيگ</string>
<string name="delete_repository_plugins">محي الريپو كمان بمحي الإضافات يلي في</string>
<string name="delete_repository_plugins">محي الريپو كمان بمحي الإضافات يللي فيه</string>
<string name="extension_language">اللغة</string>
<string name="action_subscribe">شترك</string>
<string name="plugin_deleted">نمحت الإضافة</string>
@ -490,7 +490,7 @@
<string name="extensions">الإضافات</string>
<string name="subtitles_remove_bloat">شيل الإعلانات من الترجمة</string>
<string name="empty_library_no_accounts_message">رفّكن فاضي ☹
\nفوتو على أكونت فيا رفّ الڤيديوات يلي حضرينا أو زيدو ڤيديوات بالرفّ المحلي.</string>
\nفوتو على أكونت فيا رفّ الڤيديوات يللي حضرينها أو زيدو ڤيديوات بال رفّ المحلي.</string>
<string name="repository_name_hint">إسم الريپوزيتاري</string>
<string name="qualities">الجودات</string>
<string name="error_invalid_data">بيانات مش صالحة</string>
@ -530,13 +530,13 @@
<string name="quality_blueray">Blu-ray</string>
<string name="sync_score_format" formatted="true">%d/10</string>
<string name="clipboard_too_large">النص كبير كتير. ما فينا ننسخو.</string>
<string name="plugins_updated" formatted="true">عدد الإضافيات يلي تجددت: %d</string>
<string name="plugins_updated" formatted="true">عدد الإضافيات يللي تجددت: %d</string>
<string name="delayed_update_notice">رح يتجدد الآپ وقتا تطلعو مِنو</string>
<string name="hls_playlist">پلاي ليست \"ايش أل أس\"</string>
<string name="add_repository">زيد ريپوزيتوري</string>
<string name="action_mark_as_watched">علم إنو حضرتو</string>
<string name="preferred_media_subtext">شو بَدَك تشوف</string>
<string name="subtitles_remove_captions">شيل المعلومات يلي محطوطة بالترجمة ليلي عندن فقد سمعي</string>
<string name="subtitles_remove_captions">شيل المعلومات يللي محطوطة بال ترجمة ل يللي عندهن فقد سمعي</string>
<string name="no_repository_found_error">ما لقينا ريپو. تأكدو إنو الرابط صح وجربو تعوزو \"ڤي پي أن\" (VPN)</string>
<string name="authenticated_user_fail" formatted="true">فشل الفوت ع الأكونت \"%s\"</string>
<string name="max">الحد الأعلى</string>
@ -544,7 +544,7 @@
<string name="plugin">إضافات</string>
<string name="plugin_load_fail" formatted="true">ما قدرنا نفتح %s</string>
<string name="extension_rating" formatted="true">رايتينگ: %s</string>
<string name="download_all_plugins_from_repo">بدكن تنزلو كل الإضافات من هيدا الريپو؟</string>
<string name="download_all_plugins_from_repo">تحزير: \"كلاود ستريم 3\" مش مسؤولة عن الإضافات المش رسمية، وما بتدعمن أبدًا!</string>
<string name="extension_status">الحالة</string>
<string name="delete_repository">محي الريپو</string>
<string name="category_player">مشغل الڤيديو</string>
@ -567,8 +567,8 @@
<string name="resolution">الجودة</string>
<string name="set_default">عين الافتراضي</string>
<string name="referer">المرجع (إختياري)</string>
<string name="player_settings_play_in_app">المشغل يلي بـ\"كلود ستريم\"</string>
<string name="setup_extensions_subtext">نزل لايحة المواقع يلي بدك تعوزن</string>
<string name="player_settings_play_in_app">المشغل يللي ب \"كلود ستريم\"</string>
<string name="setup_extensions_subtext">نزل لايحة المواقع يللي بدك تعوزهن</string>
<string name="enter_pin">حطو الأرقام السرية</string>
<string name="subtitles_filter_lang">فَلتِر حسب اللغة المفضلة</string>
<string name="confirm_exit_dialog">أكيد بدكون تطلعو؟</string>
@ -576,13 +576,13 @@
<string name="action_open_play">@string/home_play</string>
<string name="action_remove_from_watched">شيلو من لايحة المحتوى الحاضرينو</string>
<string name="skip_type_creddits">الإعتمادات</string>
<string name="quality_profile_help">فيكُن هون تغيرو طريقة ترتيب المصادر. المصدر يلي عندو أولوية أكتر بينحط أعلى بلايحت تنقايت المصدر. إنتو بتنقو الأولوية يإستعمال الأرقام. حطو الرقم الأعلى للمصادر والجودات يلي بتفضلوّا.
<string name="quality_profile_help">فيكُن هون تغيرو طريقة ترتيب المصادر. المصدر يللي عنده أولوية أكتر بينحط أعلى بلايحت تنقايت المصدر. إنتو بتنقو الأولوية يإستعمال الأرقام. حطو الرقم الأعلى للمصادر والجودات يللي بتفضلوها.
\n
\nمثلًا:
\nإذا المصدر \"أ\" بتفضلوّ، بتعطوّ كتير نقات (مثلًا 8).
\nإذا الجودة 480 ما بتحبوّا، بتعطوّا نقات قليلة (مثلًا 1).
\nمتلًا:
\nإزا المصدر \"أ\" بتفضلوه، بتعطوه كتير نقات (متلًا 8).
\nإزا الجودة 480 ما بتحبوها، بتعطوها نقات قليلة (متلًا 1).
\n
\nعلامت المصدر والجودة تبعو بينجمعو مع بعض (8 + 1 = 9). يلي علامتو 10 أو أعلى، بينحط تلقائيًا، من دون ما ينعمل لود لكل المصادر!</string>
\nعلامت المصدر وال جودة تبعه بينجمعو مع بعض (8 + 1 = 9). يللي علامته 10 أو أعلى، بينحط تلقائيًا، من دون ما ينعمل لود لكل المصادر!</string>
<string name="enter_current_pin">حطو الأرقام السرية الحالية</string>
<string name="audio_tracks">صوت</string>
<string name="rotate_video_desc">حط كبسة لبرم إتجاه الشاشة</string>
@ -603,7 +603,7 @@
<string name="biometric_setting">قفل بواسطة المقاييس الحيوية</string>
<string name="password_pin_authentication_title">رمز/كلمة مرور للمصادقة</string>
<string name="biometric_setting_summary">فتاح التطبيق باستعمال البصمة، آي دي الوج، پِن، النمط، إو الپاسورد.</string>
<string name="biometric_prompt_description">تسَكرت هيدي الواجهة من ورا محاولات فاشلة عديدة. پليز، سكر الآپ ورجاع فتحه.</string>
<string name="biometric_prompt_description">بعد كذا محاولة فاشلة، هيدا الشباك رح يسكر. بكل بساطة، سكر الآپ ورجاع فتحه حتى تجرب بعد مرة.</string>
<string name="resume_remaining" formatted="true">%s
\nباقي</string>
<string name="biometric_unsupported">المصادقة البيومترية مش مدعومة ع هالجهاز</string>
@ -621,5 +621,23 @@
<string name="music_singlar">موسيقى</string>
<string name="audio_book_singular">أوديو بوك</string>
<string name="custom_media_singluar">الميديا</string>
<string name="battery_dialog_message">لتضمن عدم انقطاع التنزيلات والنوتيفيكايشنات للبرامج التلفزيونية يلي مشتركلها، الآپ \"كلود ستريم\" بده إذن ليمشي بـ الباكگروند. ازا كبست أوكي، رح تتوجه ع صفحة معلومات التطبيق. هونيك، نزال حتى توصل ل «استخدام بطارية التطبيق» \"App battery usage\" وحط استخدام البطارية ع «غير مقيد» \"Unrestricted\". ملاحظة إنو هيدا الإذن ما بيعني إنو \"كلود ستريم 3\" رح تستنزف البطارية. ومش رح يشتغل الآن بـ الباكگروند إلّا عند الضرورة، متل لمّا تتلقا نوتيفيكايشن أو تنزل ڤيديو من الريپو الاصلي. فيك ترجع ترد هيدا الستنگ بـ«الإعدادات العامة» \"General settings\"، إزا غيرت رأيك.</string>
<string name="battery_dialog_message">ت تضمن عدم انقطاع التنزيلات والنوتيفيكايشنات للبرامج التلفزيونية يللي مشتركلها، الآپ \"كلود ستريم\" بده إذن ليمشي ب الباكگراوند. ازا كبست أوكي، رح تتوجه ع صفحة معلومات التطبيق. هونيك، نزال حتى توصل ل «استخدام بطارية التطبيق» \"App battery usage\" وحط استخدام البطارية ع «غير مقيد» \"Unrestricted\".
\nملاحظة إنو هيدا الإذن ما بيعني إنو \"كلود ستريم 3\" رح تستنزف البطارية. ومش رح يشتغل الآپ بال باكگراوند إلّا عند الضرورة، متل لمّا تتلقا نوتيفيكايشن أو تنزل ڤيديو من الريپو الاصلي. فيك ترجع ترد هيدا الستنگ ب «الإعدادات العامة» \"General settings\"، إزا غيرت رأيك.</string>
<string name="reset_btn">ريسات</string>
<string name="episode_upcoming_format" formatted="true">رح ينزل ب %s</string>
<string name="next_season_episode_format" formatted="true">الحلقة ال %2$d من الجزء ال%1$d رح تنزل ب</string>
<string name="episode_action_cast_mirror">كاست مراية</string>
<string name="player_settings_play_in_fcast">إف كاست</string>
<string name="player_settings_select_cast_device">نقي جهاز الكاست</string>
<string name="cs3wiki">ويكي \"كلود ستريم\"</string>
<string name="pref_category_accounts">أكونتات</string>
<string name="pref_category_security">سكوريتي</string>
<string name="qr_image">صورة الـ\"كيو آر\" كود</string>
<string name="device_pin_error_message">فشلنا ب فتح پِن الجهاز، جرب تفوت ع أكونت محليًا</string>
<string name="device_pin_expired_message">خلصت مدة الپِن!</string>
<string name="device_pin_counter_text">بتخلص مدة الپِن ب %1$d دقايق و%2$d ثانية</string>
<string name="auth_locally">فوت عال أكونت محليًا</string>
<string name="dismiss">تجاهل</string>
<string name="open_downloaded_repo">متاح الريپوزيتوري</string>
<string name="device_pin_url_message">فتاح <b>%s</b> ع تلفونك أو كمپيوترك، وحط الكود اللي فوق</string>
</resources>

View file

@ -220,7 +220,7 @@
<string name="ova_singular">أوفا</string>
<string name="torrent_singular">تورنت</string>
<string name="documentaries_singular">وثائقي</string>
<string name="asian_drama_singular">دراما آسيوية</string>
<string name="asian_drama_singular">الدراما الآسيوية</string>
<string name="live_singular">بث حي</string>
<string name="nsfw_singular">+18</string>
<string name="other_singular">فيديو</string>
@ -435,7 +435,7 @@
<string name="view_public_repositories_button">عرض مستودعات المجتمع</string>
<string name="view_public_repositories_button_short">قائمة عامة</string>
<string name="uppercase_all_subtitles">جميع الترجمات حروف كبيرة</string>
<string name="download_all_plugins_from_repo">تحميل جميع الإضافات من هذا المستودع\?</string>
<string name="download_all_plugins_from_repo">تحذير: لا يتحمل CloudStream 3 أي مسؤولية عن استخدام ملحقات الطرف الثالث ولا يقدم أي دعم لها!</string>
<string name="single_plugin_disabled" formatted="true">%s (معطل)</string>
<string name="tracks">المسارات</string>
<string name="audio_tracks">مسار الصوت</string>
@ -631,7 +631,7 @@
<string name="biometric_setting_summary">افتح التطبيق باستخدام بصمة الإصبع ومعرف الوجه ورقم التعريف الشخصي والنمط وكلمة المرور.</string>
<string name="biometric_authentication_title">فتح سحابة البث</string>
<string name="password_pin_authentication_title">مصادقة كلمة المرور/رقم التعريف الشخصي</string>
<string name="biometric_prompt_description">تم إغلاق هذه الشاشة بسبب عدة محاولات فاشلة. الرجاء إعادة تشغيل التطبيق.</string>
<string name="biometric_prompt_description">بعد عدة محاولات فاشلة، سيتم إغلاق المطالبة. ما عليك سوى إعادة تشغيل التطبيق للمحاولة مرة أخرى.</string>
<string name="biometric_warning">لقد تم الآن نسخ بيانات CloudStream احتياطيًا. على الرغم من أن احتمال حدوث ذلك منخفض جدًا، إلا أن جميع الأجهزة يمكن أن تتصرف بشكل مختلف. في الحالات النادرة، التي يتم فيها منعك من الوصول إلى التطبيق، قم بمسح بيانات التطبيق بالكامل واستعادتها من نسخة احتياطية. نحن نأسف جدًا لأي إزعاج ناتج عن هذا.</string>
<string name="resume_remaining" formatted="true">%s
\nمتبقي</string>
@ -646,8 +646,24 @@
<string name="app_info_intent_error">غير قادر على فتح معلومات تطبيق CloudStream.</string>
<string name="audio_book_singular">كتاب صوتي</string>
<string name="ok">حسناً</string>
<string name="battery_dialog_message">لضمان عدم انقطاع التنزيلات والإشعارات للبرامج التلفزيونية المشتركة، يحتاج CloudStream إلى إذن للتشغيل في الخلفية. بالضغط على موافق، سيتم توجيهك إلى معلومات التطبيق. هناك، انتقل إلى استخدام بطارية التطبيق
\nواضبط استخدام البطارية على غير مقيد. يرجى ملاحظة أن هذا الإذن لا يعني أن CS3 سوف يستنزف البطارية. ولن يعمل إلا في الخلفية عند الضرورة، كما هو الحال عند تلقي الإشعارات أو تنزيل مقاطع الفيديو من الملحقات الرسمية. إذا اخترت الإلغاء، فيمكنك ضبط هذا الإعداد لاحقًا في الإعدادات العامة.</string>
<string name="battery_dialog_message">لضمان عدم انقطاع التنزيلات والإشعارات للبرامج التلفزيونية المشتركة، يحتاج CloudStream إلى إذن للتشغيل في الخلفية. بالضغط على موافق، سيتم توجيهك إلى معلومات التطبيق. هناك، انتقل إلى 𝘼𝙥𝙥 𝙗𝙖𝙩𝙩𝙚𝙧𝙮 𝙪𝙨𝙖𝙜𝙚 واضبط استخدام البطارية على 𝙐𝙣𝙧𝙚𝙨𝙩𝙧𝙞𝙘𝙩𝙚𝙙. يرجى ملاحظة أن هذا الإذن لا يعني أن CS3 سوف يستنزف البطارية. ولن يعمل إلا في الخلفية عند الضرورة، كما هو الحال عند تلقي الإشعارات أو تنزيل مقاطع الفيديو من الامتدادات الرسمية. إذا اخترت الإلغاء، فيمكنك ضبط هذا الإعداد لاحقًا في 𝙂𝙚𝙣𝙚𝙧𝙖𝙡 𝙎𝙚𝙩𝙩𝙞𝙣𝙜𝙨.</string>
<string name="music_singlar">موسيقى</string>
<string name="custom_media_singluar">الوسائط</string>
<string name="reset_btn">اعادة تعيين</string>
<string name="episode_upcoming_format" formatted="true">قادم خلال %s</string>
<string name="next_season_episode_format" formatted="true">سيتم إصدار الحلقة %1$d من الموسم %2$d في</string>
<string name="episode_action_cast_mirror">مرآة البث</string>
<string name="player_settings_play_in_fcast">بث ف</string>
<string name="player_settings_select_cast_device">حدد جهاز البث</string>
<string name="cs3wiki">CloudStream ويكي</string>
<string name="pref_category_security">إعدادات الأمان</string>
<string name="pref_category_accounts">الحسابات</string>
<string name="qr_image">صورة رمز الاستجابة السريعة</string>
<string name="dismiss">تجاهَل</string>
<string name="open_downloaded_repo">فتح المستودع</string>
<string name="device_pin_expired_message">لقد انتهت صلاحية الرمز السري الآن!</string>
<string name="auth_locally">تحقق محليا</string>
<string name="device_pin_url_message">قم بزيارة <b>%s</b> على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه</string>
<string name="device_pin_error_message">لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية</string>
<string name="device_pin_counter_text">تنتهي صلاحية الرمز خلال %1$dm %2$ds</string>
</resources>

View file

@ -0,0 +1,624 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s এপি %2$d</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="go_back_img_des">পিছলৈ যাওক</string>
<string name="rated_format" formatted="true">ৰেটিং: %.1f</string>
<string name="title_settings">ছেটিংছ</string>
<string name="search_hint">অনুসন্ধান কৰক…</string>
<string name="search_hint_site" formatted="true">%s ত অনুসন্ধান কৰক…</string>
<string name="downloading">ডাউনল\'ড কৰি আছে</string>
<string name="recommendations_tooltip">পৰামৰ্শ চাওক</string>
<string name="player_speed">প্লেয়াৰৰ গতি</string>
<string name="subs_text_color">সাবটেক্সটৰ ৰং</string>
<string name="subs_edge_type">এজেৰ ধৰণ</string>
<string name="action_remove_watching">চালনালৈ অবন্ধ কৰক</string>
<string name="torrent_plot">প্লট</string>
<string name="show_log_cat">লগ ক্যাট চাওক</string>
<string name="test_log">লগ</string>
<string name="backup_failed_error_format">বেকআপ ত্ৰুটি %s</string>
<string name="search">অনুসন্ধান</string>
<string name="library">গ্ৰন্থাগাৰ</string>
<string name="category_account">হিসাব আৰু সুৰক্ষা</string>
<string name="category_updates">উন্নয়ন আৰু বেকআপ</string>
<string name="automatic_plugin_download">স্বয়ংক্ৰিয়ভাৱে প্লাগইন ডাউনল\'ড কৰক</string>
<string name="automatic_plugin_download_mode_title">প্লাগইন ডাউনল\'ড ম’ড নিৰ্বাচন কৰক</string>
<string name="automatic_plugin_download_summary">নিষ্ক্ৰিয়ত আৰম্ভকৰ্তাৰ আদৰ্শ সংগ্ৰহসমূহতৰ প্ৰয়াস হৈছে, আইন্সটল স্বয়ংক্ৰিয়ভাৱে যোগ কৰা হয়।</string>
<string name="cartoons">কাৰ্টুনসমূহ</string>
<string name="anime">এনিমে</string>
<string name="torrent">টৰেণ্টসমূহ</string>
<string name="ova">OVA</string>
<string name="asian_drama_singular">এছিয়ান ড্ৰামা</string>
<string name="live_singular">লাইভস্ট্ৰিম</string>
<string name="nsfw_singular">NSFW</string>
<string name="other_singular">অন্যান্য</string>
<string name="episode_action_play_in_app">এপত বাজাওক</string>
<string name="episode_action_reload_links">লিংকসমূহ পুনঃ ল\'ড কৰক</string>
<string name="episode_action_download_subtitle">সাবটাইটেল ডাউনল\'ড কৰক</string>
<string name="show_hd">HD চিহ্ন</string>
<string name="show_title">শিৰোনাম</string>
<string name="no_update_found">কোনো উন্নীতকৰণ পোৱা নাই</string>
<string name="check_for_update">উন্নীতকৰণ পৰীক্ষা কৰক</string>
<string name="dont_show_again">আৰম্ভ কৰক না</string>
<string name="limit_title">ভিডিঅ\' প্লেয়াৰৰ শিৰোনামৰ সীমা</string>
<string name="add_site_summary">বিদ্যমান চাবৰ এটা ক্লোন যোগ কৰক, একটু ভিন্ন urlৰ সৈতে</string>
<string name="pref_category_links">লিংকসমূহ</string>
<string name="category_ui">আউটলৈন</string>
<string name="automatic">স্বয়ংক্ৰিয়</string>
<string name="example_password">password123</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="logout">লগ আউট</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="sync_total_episodes_some" formatted="true">/%d</string>
<string name="authenticated_user" formatted="true">%s প্ৰমাণিত</string>
<string name="authenticated_user_fail" formatted="true">%s ত লগ ইন কৰিব পৰা নহয়</string>
<string name="error_invalid_data">অবৈধ ডেটা</string>
<string name="error_invalid_url">অবৈধ url</string>
<string name="error">ত্ৰুটি</string>
<string name="subtitles_remove_captions">সাবটাইটেলৰ পৰা ক্যাপশনসমূহ দূৰ কৰক</string>
<string name="referer">উল্লেখক (ঐচ্ছিক)</string>
<string name="next">পৰৱৰ্তী</string>
<string name="provider_languages_tip">প্ৰদাতাৰ ভাষা পছন্দ কৰক</string>
<string name="batch_download">গভেয়ান ডাউনলোড</string>
<string name="setup_extensions_subtext">সেটআপ এক্সটেনশনসমূহ</string>
<string name="plugins_downloaded" formatted="true">ডাউনলোড হৈছিল: %d</string>
<string name="clipboard_permission_error">ক্লিপবোৰ্ড অনুমতি দিবলৈ ত্ৰুটি, অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।</string>
<string name="sort_alphabetical_z">বৰ্ণানুক্রমিক (জ থকা অ পৰা অ)</string>
<string name="subscription_episode_released">%d প্ৰকাৰ মুকলো!</string>
<string name="action_subscribe">সাবস্ক্ৰাইব কৰক</string>
<string name="action_unsubscribe">সাবস্ক্ৰাইব পৰা মুকা</string>
<string name="profile_number">প্ৰ’ফাইল %d</string>
<string name="enter_pin">পিন এন্টাৰ কৰক</string>
<string name="edit_account">একাউন্ট সম্পাদনা কৰক</string>
<string name="custom_media_singluar">মিডিয়া</string>
<string name="reset_btn">পুনৰায় ছেট কৰক</string>
<string name="cast_format" formatted="true">অভিনেতা: %s</string>
<string name="type_watching">দেখা হৈ আছে</string>
<string name="continue_watching">অবিৰাম দেখিব</string>
<string name="player_subtitles_settings">সাবটাইটেল</string>
<string name="use_system_brightness_settings">চিত্ৰমূল্যৰ সিষ্টেম ব্যৱহাৰ কৰক</string>
<string name="backup_failed">বেকআপ অনুমতি অনুপলব্ধ। অনুগ্ৰহ কৰি পুনৰায় চেষ্টা কৰক।</string>
<string name="min">অল্প</string>
<string name="quality_sd">এচডি</string>
<string name="setup_done">সমাপ্ত</string>
<string name="view_public_repositories_button_short">সাৰদাৰী ভাণ্ডা</string>
<string name="stop">অব্যাহত্‌</string>
<string name="player_settings_play_in_vlc">VLCত বাজাওক</string>
<string name="battery_dialog_title">বেটাৰী অপটিমাইজেশন অসমৰ্থ</string>
<string name="subscription_list_name">সাবস্ক্ৰাইব কৰা বস্তুসমূহ</string>
<string name="qualities">মান</string>
<string name="duplicate_add">যোগ কৰক</string>
<string name="duplicate_replace">পুনৰ্স্থাপন কৰক</string>
<string name="biometric_authentication_title">ক্লাউডষ্ট্ৰীম অনলক কৰক</string>
<string name="biometric_setting">বায়োমেট্ৰিক ছেটিংসমূহ</string>
<string name="password_pin_authentication_title">পাছৱাৰ্ড/PIN পুনৰ্প্ৰমাণৰ</string>
<string name="music_singlar">সংগীত</string>
<string name="audio_book_singular">অডিঅ’ বুক</string>
<string name="next_episode_format" formatted="true">পৰৱৰ্তী পৰ্ব %d</string>
<string name="next_season_episode_format" formatted="true">পৰৱৰ্তী মৌচৰা %1$d পৰ্ব %2$d</string>
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dm</string>
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dm</string>
<string name="next_episode_time_min_format" formatted="true">%dm</string>
<string name="result_poster_img_des">পোস্টাৰ</string>
<string name="home_change_provider_img_des">প্ৰদাতা সলনি কৰক</string>
<string name="preview_background_img_des">পূৰ্বালোচনাৰ পৃষ্ঠপুতী</string>
<string name="home_next_random_img_des">পৰৱৰ্তী যিনিমূলক</string>
<string name="player_speed_text_format" formatted="true">গতি (%.2fx)</string>
<string name="next_episode">পৰৱৰ্তী পৰ্ব</string>
<string name="duration_format" formatted="true">%d মিনিট</string>
<string name="app_name">ক্লাউডষ্ট্ৰীম</string>
<string name="title_home">মূল</string>
<string name="no_data">কোনো ডাটা নাই</string>
<string name="episode_more_options_des">অধিক বিকল্পসমূহ</string>
<string name="result_share">শেয়াৰ কৰক</string>
<string name="result_open_in_browser">ব্ৰাউজাৰত খোলক</string>
<string name="browser">ব্ৰাউজাৰ</string>
<string name="type_plan_to_watch">দেখিব প্লেন কৰা হৈছে</string>
<string name="play_torrent_button">টৰেণ্ট ষ্ট্ৰীম কৰক</string>
<string name="reload_error">সংযোগ পুনঃচেষ্টা কৰক…</string>
<string name="go_back">পিছলৈ যাওক</string>
<string name="pick_source">উৎস বাছনি কৰক</string>
<string name="downloaded">ডাউনলোড হোৱা</string>
<string name="download_paused">ডাউনলোড থমা কৰা হৈছে</string>
<string name="download_started">ডাউনলোড আৰম্ভ হোৱা</string>
<string name="download_failed">ডাউনলোড বিফল হৈছে</string>
<string name="download_canceled">ডাউনলোড বাতিল হৈছে</string>
<string name="download_done">ডাউনলোড সম্পন্ন হৈছে</string>
<string name="update_started">আপডেট আৰম্ভ হোৱা</string>
<string name="stream">নেটৱৰ্ক ষ্ট্ৰীম</string>
<string name="error_loading_links_toast">লিংক লোড কৰা বিফল</string>
<string name="links_reloaded_toast">লিংক পুনৰাবৃত্তি হোৱা</string>
<string name="download_storage_text">অভ্যন্তৰীণ ষ্ট’ৰেজ</string>
<string name="app_dubbed_text">ডাব</string>
<string name="app_subbed_text">ছাবটাইটেল</string>
<string name="popup_delete_file">ফাইল মুছি দিব</string>
<string name="popup_play_file">ফাইল প্লে কৰক</string>
<string name="pref_disable_acra">স্বয়ংক্ৰিয় বাগৰ প্ৰতিবেদন নিষ্ক্ৰিয় কৰক</string>
<string name="popup_resume_download">ডাউনলোড পুনৰাৰম্ভ কৰক</string>
<string name="popup_pause_download">ডাউনলোড থমা কৰক</string>
<string name="home_more_info">অধিক তথ্য</string>
<string name="home_expanded_hide">লুকুৱা কৰক</string>
<string name="home_play">খেলা</string>
<string name="home_info">তথ্য</string>
<string name="action_remove_from_bookmarks">অপশনটো সংগ্ৰহ বাদ কৰক</string>
<string name="action_add_to_bookmarks">সংগ্ৰহৰ তলত যোগ কৰক</string>
<string name="filter_bookmarks">সংগ্ৰহৰ ফিল্টাৰ কৰক</string>
<string name="error_bookmarks_text">সংগ্ৰহ ত্ৰুটি</string>
<string name="sort_copy">কপি কৰক</string>
<string name="sort_close">বন্ধ কৰক</string>
<string name="sort_clear">পৰিষ্কাৰ কৰক</string>
<string name="sort_save">সংৰক্ষণ কৰক</string>
<string name="repo_copy_label">ভৰসৰ্বক্ষণৰ নাম আৰু URL</string>
<string name="toast_copied">প্ৰতিলিপি কৰা হৈছে!</string>
<string name="subscribe_tooltip">নতুন সদস্যতা</string>
<string name="result_search_tooltip">অন্যান্য এক্সটেনছনসমূহত অনুসন্ধান কৰক</string>
<string name="subtitles_settings">সাবটাইটেল ছেটিংছ</string>
<string name="subs_outline_color">আউটলাইনৰ ৰং</string>
<string name="subs_background_color">পৃষ্ঠপুতীৰ ৰং</string>
<string name="subs_window_color">উইণ্ডোৰ ৰং</string>
<string name="subs_subtitle_elevation">সাবটাইটেলৰ উঁচুতি</string>
<string name="subs_font">ফন্ট</string>
<string name="subs_font_size">ফন্ট আকাৰ</string>
<string name="search_provider_text_types">প্ৰকাৰসমূহে অনুসন্ধান কৰক</string>
<string name="search_provider_text_providers">সৰব সাধনসমূহে অনুসন্ধান কৰক</string>
<string name="benene_count_text">%d ডেভল\'পাৰ পৰা বেনেন দিয়া হৈছে</string>
<string name="benene_count_text_none">কোনো বেনেন দিয়া নাই</string>
<string name="subs_auto_select_language">স্বয়ংক্ৰিয় ভাষা বাছনি কৰক</string>
<string name="subs_download_languages">ভাষা ডাউনলোড কৰক</string>
<string name="subs_subtitle_languages">ছাবটাইটেলৰ ভাষা</string>
<string name="subs_hold_to_reset_to_default">ডিফল্টৰ পুনৰাৰম্ভ কৰিবলৈ ধাৰণ কৰক</string>
<string name="vpn_might_be_needed">এই প্ৰদাতাৰ সঠিকভাবে কাম কৰিবলৈ এটা VPN প্ৰয়োজন হবে</string>
<string name="vpn_torrent">এই প্ৰদাতা এটা ট\'ৰেণ্ট হৈছে, এটা VPN পৰামৰ্শ দিয়া হল</string>
<string name="subs_import_text" formatted="true">ফন্ট ইম্প\'ৰ্ট কৰিবলৈ %sত ত থকা প্ৰতিষ্ঠান কৰক</string>
<string name="action_open_watching">অধিক তথ্য</string>
<string name="action_open_play">@string/home_play</string>
<string name="provider_info_meta">মেটাডাটা ঠিকানাৰ সৈতে প্ৰদান কৰা নাই, যদি এটা ঠিকানাৰ মেটাডাটা নাই তেন্তি ভিডিঅ\' ল\'ড হোৱা নাই।</string>
<string name="normal_no_plot">কোনো বিবৰণ পোৱা নাই</string>
<string name="torrent_no_plot">কোনো বিৱৰণ পোৱা নাই</string>
<string name="player_size_settings">প্লেয়াৰৰ আকাৰ বটাম</string>
<string name="player_size_settings_des">কৃষ্টি বোৰ্ডাৰ প্ৰস্তুতি কৰক</string>
<string name="picture_in_picture">ছবি-মাধ্যমে ছবি</string>
<string name="picture_in_picture_des">অন্য এপ্‌সমূহৰ ওপৰত এটা সন্নিহিত প্লেয়াৰত অবিৰত প্লে কৰা হৈছে</string>
<string name="player_subtitles_settings_des">প্লেয়াৰৰ ছাবটাইটেল ছেটিংছ</string>
<string name="chromecast_subtitles_settings">ক্ৰ\'মকাস্ট ছাবটাইটেল</string>
<string name="chromecast_subtitles_settings_des">ক্ৰ\'মকাস্ট ছাবটাইটেল ছেটিংছ</string>
<string name="eigengraumode_settings">প্লেবেক গতি</string>
<string name="speed_setting_summary">প্লেয়াৰত গতিৰ বিকল্প যোগ কৰে</string>
<string name="swipe_to_seek_settings">সোধক হওৱাৰ বাবে স্বাইপ কৰক</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="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_settings_des">অগ্ৰগামী বা পিছত সন্ধান কৰিবলৈ সোহদৰ বা বাঁয়া দিকত দুইবাৰ টেপ কৰক</string>
<string name="double_tap_to_pause_settings_des">থমবা নিছবা পৰিমাণ দুইবাৰ টেপ কৰক</string>
<string name="use_system_brightness_settings_des">অ্যাপ প্লেয়াৰত সিষ্টেমৰ আলোক অভিলাই ব্যৱহাৰ কৰিবলৈ বিষ্টা উলঙ্ঘনস্থিতি ব্যৱহাৰ কৰক</string>
<string name="episode_sync_settings">এপিস\'ড সংমিলিত কৰা</string>
<string name="episode_sync_settings_des">আপোনাৰ বৰ্তমান পৰ্বৰ প্ৰগতি স্বয়ংক্ৰিয়ভাবে সমতলীয়া কৰা</string>
<string name="restore_settings">বেকআপৰ তথ্য পুনৰায় স্থাপন কৰক</string>
<string name="backup_settings">তথ্য বেকআপ কৰক</string>
<string name="backup_frequency">বেকআপ সংখ্যা</string>
<string name="restore_failed_format" formatted="true">ফাইল %sৰ পৰা তথ্য পুনৰায় স্থাপন কৰা নহয়</string>
<string name="backup_success">তথ্য সংৰক্ষিত হৈছে</string>
<string name="settings_info">তথ্য</string>
<string name="restore_success">বেকআপ ফাইল ল\'ড হৈছে</string>
<string name="advanced_search">সুধাৰি অনুসন্ধান</string>
<string name="advanced_search_des">প্ৰদানকাৰীৰ পৰা সন্ধান ফলাফল সোমোৱা</string>
<string name="bug_report_settings_off">কেৱল দুৰ্ঘটনাত ডাটা পঠাৱ</string>
<string name="bug_report_settings_on">কোনো ডাটা পঠাৱ নহয়</string>
<string name="show_fillers_settings">অনুপ্রেৰণাৰ অধ্যাপক দেখাওঁক</string>
<string name="show_trailers_settings">ট্ৰেলাৰ দেখাওঁক</string>
<string name="kitsu_settings">Kitsuৰ পোষ্টাৰ দেখাওঁক</string>
<string name="pref_filter_search_quality">অনুসন্ধান ফলাফলত নিৰ্বাচিত ভিডিঅ’ গুণত্ব লুকাওক</string>
<string name="automatic_plugin_updates">স্বয়ংক্ৰিয় প্লাগইন উন্নয়ন</string>
<string name="updates_settings">এপ্লিকেশন উন্নয়ন দেখাওঁক</string>
<string name="redo_setup_process">সেটআপ প্ৰক্ৰিয়া পুনৰাবৃত্তি কৰক</string>
<string name="uprereleases_settings">প্ৰি-ৰিলিজ আপডেট কৰক</string>
<string name="apk_installer_settings">APK Installer</string>
<string name="apk_installer_settings_des">কিছু ফ’নসমূহ নতুন পেকেজ ইনষ্টলাৰ সমৰ্থন কৰে নাই। আপডেট ইনষ্টল নহয় পৰা পৰীক্ষা কৰক লেগেচি বিকল্প প্ৰয়োগ কৰক।</string>
<string name="github">গিটহাব</string>
<string name="lightnovel">একেই ডেভল\'পাৰকৰ লাইট নভেল এপ্‌</string>
<string name="discord">ডিসকৰ্ডত যোগদান কৰক</string>
<string name="no_links_found_toast">কোনো লিংক পোৱা নাই</string>
<string name="benene">ডেভল\'পাৰকলৈ এখন বিনম্রতা দাওঁক</string>
<string name="benene_des">দিয়া বিনম্রতা</string>
<string name="app_language">এপ্‌ ভাষা</string>
<string name="no_chromecast_support_toast">এই প্ৰদাতাৰ কোনো চ্ৰোমকাছ্ট সমৰ্থন নাই</string>
<string name="copy_link_toast">লিংক ক্লিপবৰ্ডত প্ৰতিলিপি কৰা হৈছে</string>
<string name="play_episode_toast">এপিস\'ড বাজাওঁক</string>
<string name="acra_report_toast">দুঃখিত, অ্যাপ্লিকেশন সংঘটিত হৈছে। অজ্ঞাত বাগ ৰিপ’ৰ্ট ডেভেলপাৰসমূহলৈ পঠাওক</string>
<string name="season">ঋতু</string>
<string name="subs_default_reset_toast">ডিফ’ল্ট মান পুনৰাবৃত্তি কৰক</string>
<string name="season_format">%1$s %2$d%3$s</string>
<string name="no_season">কোনো ঋতু নাই</string>
<string name="episode">এপিস\'ড</string>
<string name="episodes">এপিস\'ডসমূহ</string>
<string name="episodes_range">%1$d-%2$d</string>
<string name="episode_format" formatted="true">%1$d %2$s</string>
<string name="episode_upcoming_format" formatted="true">%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="cancel">বাতিল কৰক</string>
<string name="pause">অধিপ্ত কৰক</string>
<string name="start">আৰম্ভ কৰক</string>
<string name="test_failed">অসফল</string>
<string name="test_passed">সফল</string>
<string name="resume">আবাৰ আৰম্ভ কৰক</string>
<string name="go_back_30">-৩০</string>
<string name="go_forward_30">+৩০</string>
<string name="delete_message" formatted="true">এইটো স্থায়ীভাৱে %s মচি দিব
\nআপুনি নিশ্চিত?</string>
<string name="resume_time_left" formatted="true">%dm
\nবাকি আছে</string>
<string name="resume_remaining" formatted="true">%s
\nবাকি আছে</string>
<string name="status_ongoing">চলিত আছে</string>
<string name="status_completed">সম্পন্ন হৈছে</string>
<string name="status">অবস্থা</string>
<string name="year">বছৰ</string>
<string name="rating">ৰেটিং</string>
<string name="duration">সময়</string>
<string name="site">চাইট</string>
<string name="synopsis">সাৰাংশ</string>
<string name="queued">অপেক্ষাৰত</string>
<string name="no_subtitles">সাবটাইটেল নাই</string>
<string name="action_default">ডিফ’ল্ট</string>
<string name="free_storage">মুক্ত</string>
<string name="used_storage">ব্যৱহাৰ কৰা</string>
<string name="app_storage">এপ্‌ স্টোৰেজ</string>
<string name="movies">চলচ্চিত্ৰসমূহ</string>
<string name="tv_series">টিভি চলচ্চিত্ৰসমূহ</string>
<string name="documentaries">ডকুমেণ্টাৰিসমূহ</string>
<string name="asian_drama">এছিয়ান ড্ৰামা</string>
<string name="livestreams">লাইভস্ট্ৰিমসমূহ</string>
<string name="nsfw">এনএসএফডবলিউ</string>
<string name="others">অন্যান্য</string>
<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">OVA</string>
<string name="torrent_singular">টৰেণ্ট</string>
<string name="documentaries_singular">ডকুমেণ্টাৰী</string>
<string name="source_error">উৎস ত্ৰুটি</string>
<string name="remote_error">দূৰবৰ্তী ত্ৰুটি</string>
<string name="render_error">ৰেন্ডাৰাৰ ত্ৰুটি</string>
<string name="storage_error">ডাউনল\'ড ত্ৰুটি, সংৰক্ষণ অনুমতিসমূহ পৰীক্ষা কৰক</string>
<string name="unexpected_error">অপ্ৰত্যাশিত প্লেয়াৰ ত্ৰুটি</string>
<string name="episode_action_chromecast_episode">চ্ৰোমকাছ্ট এপিস\'ড</string>
<string name="episode_action_chromecast_mirror">চ্ৰোমকাছ্ট আইনমিৰৰ</string>
<string name="episode_action_cast_mirror">আইনমিৰ আদৰণি</string>
<string name="show_dub">ডাব চিহ্ন</string>
<string name="episode_action_play_in_format">%sত বাজাওক</string>
<string name="episode_action_play_in_browser">ব্ৰাউজাৰত বাজাওক</string>
<string name="episode_action_copy_link">লিংক প্ৰতিলিপি কৰক</string>
<string name="episode_action_auto_download">স্বয়ংক্ৰিয় ডাউনল\'ড</string>
<string name="episode_action_download_mirror">আদৰণি ডাউনল\'ড কৰক</string>
<string name="show_sub">উপশিৰোনিৰ চিহ্ন</string>
<string name="poster_ui_settings">প\'ষ্টাৰৰ UI উপাদানসমূহ ট\'গল কৰক</string>
<string name="video_lock">ল\'ক</string>
<string name="video_aspect_ratio_resize">ৰিসাইজ</string>
<string name="video_source">উৎস</string>
<string name="update">আপডেট</string>
<string name="watch_quality_pref">পছন্দসই দেখাৰ গুণত্ব (WiFi)</string>
<string name="video_skip_op">অপ স্কিপ কৰক</string>
<string name="skip_update">এই আপডেটটো প্ৰস্থান কৰক</string>
<string name="watch_quality_pref_data">পছন্দসই দেখাৰ গুণত্ব (মোবাইল ডেটা)</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="android_tv_interface_on_seek_settings_summary">প্লেয়াৰ দৃশ্যমান হ\'লে ব্যৱহাৰ কৰা সন্ধান পৰিমাণ</string>
<string name="android_tv_interface_off_seek_settings">প্লেয়াৰ লুকুৱাওলৈ - সন্ধান পৰিমাণ</string>
<string name="android_tv_interface_on_seek_settings">প্লেয়াৰ দেখা হোৱালৈ - সন্ধান পৰিমাণ</string>
<string name="android_tv_interface_off_seek_settings_summary">প্লেয়াৰ লুকুৱা হ\'লে ব্যৱহাৰ কৰা সন্ধান পৰিমাণ</string>
<string name="video_disk_description">যদি স্তন্যপান উচ্চত নিৰ্ধাৰিত হ\'লে, অন্যত্ৰ সঞ্চয় স্থানত সন্দেহ ঘটিব। উদাহৰণস্বৰূপ, এছিয়ান টিভি।</string>
<string name="dns_pref">DNS ওভাৰ HTTPS</string>
<string name="video_ram_description">যদি স্তন্যপান উচ্চত নিৰ্ধাৰিত হ\'লে, অন্যত্ৰ মেম\'ৰী সন্ধানৰ যন্ত্ৰসমূহত সন্দেহ ঘটিব। উদাহৰণস্বৰূপ, এছিয়ান টিভি।</string>
<string name="dns_pref_summary">আইএছপি ব্লক পাৰ কৰিবলৈ কাৰ্যকাৰী</string>
<string name="add_site_pref">চাব চাওঁক</string>
<string name="jsdelivr_proxy">GitHub প্ৰক্সি</string>
<string name="jsdelivr_enabled">GitHub প্ৰাপ্য নাই। jsDelivr প্ৰক্সি অন কৰা হচ্ছে…</string>
<string name="jsdelivr_proxy_summary">jsDelivr ব্যৱহাৰ কৰি সোধ গুগল url ব্লক অনলাইন কৰক। কিছুদিনৰ মাজত আপডেট ল\'ওক দিব পাৰে।</string>
<string name="remove_site_pref">চাব আঁতৰাওক</string>
<string name="download_path_pref">ডাউনল\'ড পাথ</string>
<string name="nginx_url_pref">NGINX চাৰ্ভাৰ url</string>
<string name="display_subbed_dubbed_settings">ডাব কৰা/সাব কৰা অ্যানিমে প্ৰদৰ্শন কৰক</string>
<string name="resize_fit">স্ক্ৰীনলৈ সজাব কৰক</string>
<string name="resize_fill">পৰবৰ্তী কৰক</string>
<string name="legal_notice">অকৃত্রিম নোটিশ</string>
<string name="pref_category_bypass">আইএছপি পাৰ কৰা</string>
<string name="resize_zoom">জুম কৰক</string>
<string name="pref_category_app_updates">এপ্ আপডেটসমূহ</string>
<string name="pref_category_backup">বেকাপ</string>
<string name="pref_category_extensions">এক্সটেনচনসমূহ</string>
<string name="pref_category_actions">ক্ৰিয়াসমূহ</string>
<string name="pref_category_cache">কেচ</string>
<string name="pref_category_android_tv">এণ্ড্ৰোইড টিভি</string>
<string name="pref_category_gestures">হাস্য</string>
<string name="pref_category_player_features">প্লেয়াৰৰ বৈশিষ্ট্য</string>
<string name="pref_category_subtitles">উপশিৰোতা</string>
<string name="pref_category_player_layout">লে\'আ\'উট</string>
<string name="pref_category_defaults">ডিফ’ল্টসমূহ</string>
<string name="pref_category_looks">দেখুৱাই</string>
<string name="pref_category_ui_features">বৈশিষ্ট্যসমূহ</string>
<string name="category_general">সাধাৰণ</string>
<string name="random_button_settings">যিতা বুটাম</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>
<string name="enable_nsfw_on_providers">সুবিধা দিয়া সময় NSFW সক্ষম কৰক</string>
<string name="subtitles_encoding">উপশিৰোতা ক\'ডিং</string>
<string name="category_providers">প্ৰদাতা</string>
<string name="category_provider_test">প্ৰদাতা পৰীক্ষা</string>
<string name="test_extensions">সকলো এক্সটেনচনসমূহ পৰীক্ষা কৰক</string>
<string name="test_extensions_summary">এই পৰীক্ষা কেৱল উন্নীতকাৰীসমূহলৈ পৰমিট আৰু বাৰ্তা কৰা হৈছে আৰু কেইকোনো এক্সটেনচনৰ কাৰ্যক্ষম বা অকাৰ্যক্ষমতা প্ৰমাণিত নহয়।</string>
<string name="tv_layout">টিভি লে\'আ\'উট</string>
<string name="emulator_layout">এমুলেটৰ লে\'আ\'উট</string>
<string name="primary_color_settings">প্ৰাথমিক ৰং</string>
<string name="app_theme_settings">এপ্ থীম</string>
<string name="bottom_title_settings">প\'ষ্টাৰ শিৰোনাম অৱস্থান</string>
<string name="phone_layout">ফোন লে\'আ\'উট</string>
<string name="bottom_title_settings_des">প\'ষ্টাৰ তলত শিৰোনাম দিয়ক</string>
<string name="example_site_name">নতুন সাইটৰ নাম</string>
<string name="example_site_url">https://example.com</string>
<string name="example_lang_name">ভাষা ক\'ড (en)</string>
<string name="login_format" formatted="true">%1$s %2$s</string>
<string name="account">একাউন্ট</string>
<string name="login">লগ ইন</string>
<string name="switch_account">একাউন্ট পৰিবৰ্তন কৰক</string>
<string name="add_account">একাউন্ট যোগ কৰক</string>
<string name="create_account">একাউন্ট সৃষ্টি কৰক</string>
<string name="add_sync">ট্ৰেকিং যোগ কৰক</string>
<string name="none">কোনোবিলাক</string>
<string name="normal">সাধাৰিত</string>
<string name="added_sync_format" formatted="true">%s যোগ কৰা হৈছে</string>
<string name="upload_sync">সিঙ্ক</string>
<string name="disable">অক্ষম কৰক</string>
<string name="all">সকল</string>
<string name="max">অধিকতম</string>
<string name="subtitles_outline">আউটলাইন</string>
<string name="subtitles_depressed">ডিপ্ৰেছড</string>
<string name="subtitles_shadow">ছাড়া</string>
<string name="subtitle_offset_hint">1000 মিলিছেকেন্ড</string>
<string name="subtitles_raised">উঁচুহোৱা</string>
<string name="subtitle_offset">উপশিৰোতা সিংক</string>
<string name="subtitle_offset_title">উপশিৰোতা দেলাই</string>
<string name="subtitle_offset_extra_hint_later_format">যদি উপশিৰোতা %d মিলিছেকেন্ড পূৰ্ববৰ্তী দেখা যায় তেন্ত এইটো ব্যৱহাৰ কৰক</string>
<string name="subtitle_offset_extra_hint_none_format">কোনো উপশিৰোতা বিলম্ব নাই</string>
<string name="subtitle_offset_extra_hint_before_format">যদি উপশিৰোতা %d মিলিছেকেন্ড পূৰ্ববৰ্তী দেখা যায় না তেন্ত এইটো ব্যৱহাৰ কৰক</string>
<string name="subtitles_example_text">দ্রুত মাৰেল ভুঁটি সোমালী কুকুৰা ধীৰে</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>
<string name="actor_background">পছৱা</string>
<string name="downloaded_file">ডাউনলোড কৰা ফাইল</string>
<string name="home_source">উৎস</string>
<string name="actor_main">প্ৰধান</string>
<string name="home_random">যাত্রা</string>
<string name="coming_soon">শীঘ্ৰই আসিব…</string>
<string name="actor_supporting">সহায়ক</string>
<string name="quality_cam">ক্যাম</string>
<string name="quality_cam_rip">ক্যাম</string>
<string name="quality_cam_hd">ক্যাম</string>
<string name="quality_hq">এইচকিউ</string>
<string name="quality_hd">এইচডি</string>
<string name="quality_ts">টিএছ</string>
<string name="quality_blueray">ব্লু-ৰে</string>
<string name="quality_workprint">ডব</string>
<string name="quality_tc">টিসি</string>
<string name="quality_dvd">ডিভিডি</string>
<string name="quality_4k">৪কে</string>
<string name="quality_uhd">ইউএচডি</string>
<string name="quality_hdr">এইচডিৰ</string>
<string name="quality_sdr">এসডিৰ</string>
<string name="quality_webrip">ওয়েব</string>
<string name="poster_image">পোষ্টাৰ ছবি</string>
<string name="category_player">প্লেয়াৰ</string>
<string name="resolution_and_title">দৰ্শনীয়তা আৰু শীৰ্ষক</string>
<string name="title">শীৰ্ষক</string>
<string name="resolution">দৰ্শনীয়তা</string>
<string name="error_invalid_id">অবৈধ আইডি</string>
<string name="subtitles_filter_lang">পছন্দৰ মিডিয়া ভাষাৰ দ্বাৰা ফিল্টাৰ কৰক</string>
<string name="subtitles_remove_bloat">সাবটাইটেলত ব্লোট দূৰ কৰক</string>
<string name="extras">অতিৰিক্ত</string>
<string name="trailer">ট্ৰেলাৰ</string>
<string name="crash_reporting_title">ক্ৰেশ ৰিপ\'টিং</string>
<string name="network_adress_example">https://example.com/example.mp4</string>
<string name="previous">পূৰ্বৱৰ্তী</string>
<string name="skip_setup">ছেটআপ প্ৰস্থান কৰক</string>
<string name="app_layout_subtext">আপোনাৰ ডিভাইচ অনুযায়ী এপ্পৰ প্ৰস্তুতি সলনি কৰক</string>
<string name="preferred_media_subtext">আপুনি কি চাব</string>
<string name="extensions">এক্সটেনছনসমূহ</string>
<string name="add_repository">প্ৰায়ণশাল যোগ কৰক</string>
<string name="repository_name_hint">প্ৰায়ণশালৰ নাম</string>
<string name="repository_url_hint">প্ৰায়ণশাল URL</string>
<string name="plugin_loaded">প্লাগইন লোড হোৱা হৈছে</string>
<string name="plugin_downloaded">প্লাগইন ডাউনলোড হোৱা হৈছে</string>
<string name="plugin_deleted">প্লাগইন মুছিলো</string>
<string name="plugin_load_fail" formatted="true">%s লোড কৰিব পৰা নহয়</string>
<string name="is_adult">১৮+</string>
<string name="batch_download_start_format" formatted="true">%1$d %2$s ডাউনলোড আৰম্ভ কৰা হ’ল…</string>
<string name="batch_download_finish_format" formatted="true">%1$d %2$s ডাউনলোড কৰা হ’ল</string>
<string name="batch_download_nothing_to_download_format" formatted="true">সকলো %s ইতিমধ্যে ডাউনলোড কৰা হৈছে</string>
<string name="no_plugins_found_error">ৰিপ’চ্যুটৰীত কোনো প্লাগইন পোৱা নাই</string>
<string name="plugin_singular">প্লাগইন</string>
<string name="plugin">প্লাগইনসমূহ</string>
<string name="no_repository_found_error">ৰিপ’চ্যুটৰি পোৱা নাই, URL চেক কৰক আৰু VPN চেক কৰক</string>
<string name="delete_repository_plugins">এইটো আৰুও ৰিপ’চ্যুটৰীত সকলো প্লাগইন মুছিব</string>
<string name="delete_repository">ৰিপ’চ্যুটৰি মুছিব</string>
<string name="plugins_updated" 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আমাৰ ডিসক’ৰ্ডত যোগদান কৰক অথবা অনলাইনত সন্ধান কৰক।</string>
<string name="view_public_repositories_button">সম্প্রদায়ৰ প্ৰায়ণশালসমূহ চাওক</string>
<string name="uppercase_all_subtitles">সকলো সাবটাইটেলৰ মাজতে আপাৰ আকাৰত লিখক</string>
<string name="download_all_plugins_from_repo">এই প্ৰায়ণশালৰ সকলো প্লাগিন ডাউনলোড কৰিব?</string>
<string name="single_plugin_disabled" formatted="true">%s (অক্ষম কৰা আছে)</string>
<string name="tracks">পথসমূহ</string>
<string name="audio_tracks">অডিঅ’ পথসমূহ</string>
<string name="video_tracks">ভিডিঅ’ পথসমূহ</string>
<string name="apply_on_restart">প্ৰযোগ সলনি কৰিবলৈ এপ্‌লিকেছন পুনৰ আৰম্ভ কৰক।</string>
<string name="restart">পুনৰ আৰম্ভ কৰক</string>
<string name="safe_mode_title">নিৰাপদ প্ৰণামী চালু</string>
<string name="safe_mode_description">এপ্প সমস্থ একটি দ্বিঘাতৰ ফলে সমস্থ এক্সটেনছন নিষ্ক্ৰিয় কৰা হৈছিল যিতে আপোনাক কিবা সমস্যা আছে তা চেনা নিব পাৰে।</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_authors">লেখক</string>
<string name="extension_types">সমৰ্থিত</string>
<string name="extension_language">ভাষা</string>
<string name="extension_install_first">শীৰ্ষকত প্লাগিনটো ইনষ্টল কৰক</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="hls_playlist">HLS প্লেলিষ্ট</string>
<string name="player_pref">পছন্দৰ ভিডিঅ’ প্লেয়াৰ</string>
<string name="player_settings_play_in_app">আইণ্টাৰনেল প্লেয়াৰ</string>
<string name="player_settings_play_in_mpv">MPV</string>
<string name="player_settings_play_in_web">Web ভিডিঅ’ কাষ্ট</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_play_in_browser">Web ব্ৰাউজাৰ</string>
<string name="player_settings_select_cast_device">কাষ্ট ডিভাইচ নিৰ্বাচন কৰক</string>
<string name="skip_type_mixed_ed">মিছিণ শেষ</string>
<string name="skip_type_mixed_op">মিছিণ আৰম্ভ</string>
<string name="clear_history">ইতিহাস মুকা</string>
<string name="skip_type_creddits">শ্ৰেয়া</string>
<string name="skip_type_intro">প্ৰস্তাবনা</string>
<string name="history">ইতিহাস</string>
<string name="enable_skip_op_from_database_des">ডাটাবেছত প্ৰবেশ/শেষৰ বাবে পপাপ দেখাওক</string>
<string name="clipboard_too_large">বেছি পাঠ্য। ক্লিপব’ৰ্ডত সংৰক্ষণ কৰিব নোৱা যায়।</string>
<string name="clipboard_unknown_error">কপি কৰিবলৈ ত্ৰুটি, অনুগ্ৰহ কৰি লগকেট কপি কৰি অ্যাপ সমৰ্থনকৰ্তাৰ সৈতে যোগাযোগ কৰক।</string>
<string name="action_mark_as_watched">চাওক হিচাপে চিহ্নিত কৰক</string>
<string name="action_remove_from_watched">চাওক হিচাপৰ পৰা মুকা</string>
<string name="yes">হয়</string>
<string name="no">নহয়</string>
<string name="ok">ঠিক আছে</string>
<string name="confirm_exit_dialog">আপুনি নিশ্চিত হৈছে যে আপুনি প্ৰস্থান কৰিব বিচাৰে?</string>
<string name="app_unrestricted_toast">অ্যাপ্‌ৰ বেটেৰি ব্যৱহাৰ ইতিমধ্যে অসীমিত হ’লে</string>
<string name="battery_dialog_message">সাবস্ক্ৰাইব কৰা TV সেৰাৰ বাবে অবিচ্ছিন্ন ডাউনলোড আৰু বিজ্ঞাপনৰ বাবে নোটিফিকেশনৰ বাবে, ক্লাউডষ্ট্ৰিমৰ অনুমতি প্ৰয়াপ্ত কৰিবলৈ পৃষ্ঠভূমিত চলকৰ বাবে অনুমতি প্ৰয়াপ্ত কৰিবলৈ লাগে। ঠিক আছে টিপিবলৈ, আপুনি এপ তথ্যলৈ দীঘল হৈ যাব। তত্ত্বাবধানে, ইয়াৰ ব্যৱহাৰ ক্লাউডষ্ট্ৰিমক আপোনাৰ বেটাৰী সেক্ষাৰ কৰিব নাই অৰু। ইয়া শুধু প্ৰয়োজনীয় হোৱা সময়ত বেক্গ্‌গ্‌ত অতিৰিক্ত কাৰ্য কৰিবলৈ অপাৰে, যেনে নোটিফিকেশন সোধা আৰু আধিকাৰিক প্ৰস্তাবনাৰ ভিডিঅ’স ডাউনলোড কৰিবলৈ। যদি আপুনি বাতিল কৰিব বাচন কৰিছে, আপুনি পৰেন্তু সেটিং ইয়াৰ পিছতে ক্রোয়জবলৈ চাব পাৰে জেনে গওঁক সেটিংছ।</string>
<string name="app_info_intent_error">ক্লাউডষ্ট্ৰীমৰ অ্যাপ্‌ তথ্য খোলাত অসমৰ্থ</string>
<string name="update_notification_failed">অ্যাপ্‌ৰ নতুন সংস্কৰণ ইনষ্টল কৰিব পৰা নাই</string>
<string name="update_notification_downloading">অ্যাপ্‌ আপডেট ডাউনলোড হৈছে…</string>
<string name="update_notification_installing">অ্যাপ্‌ আপডেট ইনষ্টল হৈছে…</string>
<string name="apk_installer_legacy">পুৰণৰূপ</string>
<string name="apk_installer_package_installer">পেকেজ ইনষ্টলাৰ</string>
<string name="delayed_update_notice">অ্যাপ্‌ প্ৰস্থানত আপডেট হৈ যাব</string>
<string name="sort_by">ক্ৰমৰ অনুযায়ী</string>
<string name="sort">সাজোৱা</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="select_library">গোটি বাছক কৰক</string>
<string name="open_with">সৈতে খোলক</string>
<string name="empty_library_no_accounts_message">আপোনাৰ গোটিটো খালি আছে :(
\nগোটিত প্ৰৱেশ কৰিবলৈ এখনকা একাউণ্টত লগিন কৰক নাইবা আপোনাৰ স্থানীয় গোটিত চইক যোগ কৰক।</string>
<string name="empty_library_logged_in_message">এই তালিকাটো খালি আছে। আৰু এটা অন্য এটা তালিকাত স্থানান্তৰ কৰিব চেষ্টা কৰক।</string>
<string name="safe_mode_file">নিৰাপত ম’ড ফাইল পোৱা গৈছে!
\nফাইল মুকা হোৱা পৰা প্ৰাৰম্ভত কোনো এক্সটেনচন লোড কৰা হ’ব নহয়।</string>
<string name="revert">পুনৰ প্ৰয়াণ কৰক</string>
<string name="subscription_in_progress_notification">সাবস্ক্ৰাইব কৰা প্ৰদর্শন আপডেট হৈ আছে</string>
<string name="subscription_new">%s সাবস্ক্ৰাইব কৰা হ\'ল</string>
<string name="subscription_deleted">%s পৰা সাবস্ক্ৰাইব কৰা হ\'ল</string>
<string name="wifi">ওয়াইফাই</string>
<string name="mobile_data">মোবাইল ডাটা</string>
<string name="set_default">ডিফ’ল্ট ঠিকনা কৰক</string>
<string name="use">ব্যৱহাৰ কৰক</string>
<string name="edit">সম্পাদনা কৰক</string>
<string name="profiles">প্ৰ’ফাইলসমূহ</string>
<string name="help">সাহায্য</string>
<string name="quality_profile_help">ইয়াত আপুনি সৃষ্টিগত উৎসসমূহ কেমানে সাজোৱা পৰিবৰ্তন কৰিব পাৰে। যদি এটা ভিডিঅ’ এটা উচ্চ অগ্ৰাধিকাৰ থাকে তেনেহলে ই উচ্চ মানৰ উৎস বাছনি তথ্যত প্ৰদৰ্শিত হ\'ব। উৎসৰ প্ৰাথমিকতা আৰু মানৰ প্ৰাথমিকতাৰ যোগফল ভিডিঅ’ৰ প্ৰাথমিকতা হ’ব।
\n
\nউৎস A: 3
\nগুণগত মান B: 7
\n10 এৰি মানৰ এটা সংযুক্ত ভিডিঅ’ প্ৰাথমিকতা থাকিব।
\n
\nনোট: যদি যোগফল 10 বা তাতো অধিক হ\'ব তেনেহলে প্লেয়াৰ আটোমেটিকলি সেই লিংক লোড হোৱা সময়ত লোড কৰিব নোৱা হ\'ব!</string>
<string name="profile_background_des">প্ৰ’ফাইল পৃষ্ঠভূমি</string>
<string name="unable_to_inflate">ইউআই সঠিকভাৱে সৃষ্টি কৰা নাই, এটা মুখ্য বুগ আৰু অধীৰ অনুমতি দিয়া হোৱা উচিত এইটো পৰ্যন্ত প্ৰতিবেদন কৰক %s</string>
<string name="already_voted">আপুনি ইতিমধ্যে ভোট দিয়া আছে</string>
<string name="favorites_list_name">প্ৰিয়মূল্য বিষয়বস্তুসমূহ</string>
<string name="favorite_added">%s প্ৰিয়মূল্য বিষয়বস্তুসমূহত যোগ কৰা হ\'ল</string>
<string name="favorite_removed">%s প্ৰিয়মূল্য বিষয়বস্তুসমূহৰ পৰা আঁতৰাই কৰা হ\'ল</string>
<string name="action_add_to_favorites">প্ৰিয়মূল্য বিষয়বস্তুসমূহত যোগ কৰক</string>
<string name="action_remove_from_favorites">প্ৰিয়মূল্য বিষয়বস্তুসমূহৰ পৰা আঁতৰাই কৰক</string>
<string name="duplicate_title">সম্ভাৱ্য ডুপ্লিকেট পোৱা গৈছে</string>
<string name="duplicate_replace_all">সকলোত প্ৰতিস্থাপন কৰক</string>
<string name="duplicate_message_single" formatted="true">আপোনাৰ গোটিত এটা সম্ভাব্য ডুপ্লিকেট আইটেম অলপ অস্তিত্বত আছে: \'%s\'
\n
\nআপুনি ই আইটেমটো সংযোগ কৰিব বিচাৰে নে, বৰং ইতিমধ্যে থকা আইটেমটো সংস্কৰণ কৰিব নে, নাইবা অ্যাকছনটো বাতিল কৰিব?</string>
<string name="duplicate_message_multiple" formatted="true">আপোনাৰ গোটিত এক সম্ভাব্য ডুপ্লিকেট আইটেম অনেক অস্তিত্বত আছে:
\n
\n%s
\n
\nআপুনি ই আইটেমটো সংযোগ কৰিব বিচাৰে নে, বৰং ইতিমধ্যে থকা আইটেমটোসমূহ সংস্কৰণ কৰিব নে, নাইবা অ্যাকছনটো বাতিল কৰিব?</string>
<string name="enter_pin_with_name" formatted="true">%sৰ বাবে PIN এন্টাৰ কৰক</string>
<string name="enter_current_pin">বৰ্তমান পিন এন্টাৰ কৰক</string>
<string name="lock_profile">প্ৰ’ফাইল লক কৰক</string>
<string name="pin">পিন</string>
<string name="pin_error_incorrect">অশুদ্ধ পিন। অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।</string>
<string name="pin_error_length">পিনটো 4 বৰ্ণ থাকিব লাগিব</string>
<string name="select_an_account">এটা একাউন্ট নিৰ্বাচন কৰক</string>
<string name="manage_accounts">একাউন্টসমূহ পৰিচালনা কৰক</string>
<string name="skip_startup_account_select_pref">প্ৰস্থানত একাউন্ট নিৰ্বাচন পাছ দিব</string>
<string name="use_default_account">ডিফ’ল্ট একাউন্ট ব্যৱহাৰ কৰক</string>
<string name="rotate_video">ঘূৰাওক</string>
<string name="logged_account" formatted="true">%sৰ হিচাপে লগ ইন কৰা হৈছে</string>
<string name="rotate_video_desc">স্ক্ৰীনৰ অৰিএণ্টেশ্বনৰ বাবে ট’গল বুটাম প্ৰদৰ্শন কৰক</string>
<string name="auto_rotate_video_desc">ভিডিঅ’ৰ অৰিএণ্টেশ্বনৰ ভিত্তিত স্বয়ংক্ৰিয় স্ক্ৰীনৰ অৰিএণ্টেশ্বন প্ৰবণ কৰক</string>
<string name="auto_rotate_video">স্বয়ংক্ৰিয় ঘূৰাওক</string>
<string name="unfavorite">প্ৰিয়মূল্যহীন</string>
<string name="biometric_unsupported">এই ডিভাইচত বায়োমেট্ৰিক পুনৰ্প্ৰমাণৰ সমৰ্থন কৰা নাই</string>
<string name="biometric_setting_summary">আঙুলি ছাঁচ ব্যৱহাৰ কৰি, মুখৰ চিত্ৰ, PIN, প্ৰণালী আৰু পাছৱাৰ্ডৰ সৈতে অ্যাপ্‌ আনলক কৰক।</string>
<string name="favorite">প্ৰিয়মূল্য</string>
<string name="biometric_prompt_description">এই স্ক্ৰীন একাধিক অসফল চেষ্টাৰ কাৰণে বন্ধ হৈছিল। অনুগ্ৰহ কৰি অ্যাপ্লিকেশ্বন পুনৰ্‌ আৰম্ভ কৰক।</string>
<string name="biometric_warning">আপোনাৰ CloudStream ডাটা এতিয়া বেকআপ কৰা হৈছে। হৈচঁদিক এই সম্ভাবনা বেছি নাই, সকলো ডিভাইচত বিভিন্নভাৱে আচৰণ কৰিব পাৰে। যদিচয় আপুনি এপ্পটো প্ৰৱেশ কৰাৰ বন্ধ হৈ যাওৱাৰ অভাবতে, অ্যাপ্‌ৰ তথ্য পূৰ্ণভাৱে মচলা কৰক আৰু এক বেকআপৰ পৰা প্ৰতিস্থাপন কৰক। এই বিষয়ত উদ্ভাবিত যোৱা অসুবিধাৰ বাবে আমি অত্যন্ত দুঃখী।</string>
<string name="download">ডাউনলোড কৰক</string>
<string name="updates_settings_des">এপ্‌টোক আৰম্ভ কৰাৰ পাছত নতুন উন্নয়নসমূহ স্বয়ংক্ৰিয়ভাৱে খোঁজি পাওক।</string>
<string name="uprereleases_settings_des">পূৰ্ণ মুক্তি প্ৰাপ্ত কৰিবলৈ প্ৰি-ৰিলিজ উন্নয়ন খোঁজি পাওক।</string>
<string name="anim">একেই ডেভল\'পাৰকৰ অনিমে এপ্‌</string>
<string name="new_update_format" formatted="true">নতুন আপডেট পাইছো!
\n%1$s -&gt; %2$s</string>
<string name="filler" formatted="true">ফিলাৰ</string>
<string name="play_with_app_name">CloudStream ৰ জৰিয়তে খেলোৱা</string>
<string name="title_search">সন্ধান</string>
<string name="title_downloads">ডাউনলোডসমূহ</string>
<string name="result_tags">টেগসমূহ</string>
<string name="skip_loading">লোডিং এৰি যাওক</string>
<string name="loading">লোড হৈ আছে…</string>
<string name="type_on_hold">ধৰি ৰখা হৈ আছে</string>
<string name="type_completed">সম্পন্ন হৈ গ\'ল</string>
<string name="type_dropped">এৰি দিয়া হৈছে</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="pick_subtitle">ছাবটাইটেল বাছনি কৰক</string>
<string name="play_episode">পৰ্ব খেলাওক</string>
<string name="sort_apply">প্ৰয়োগ কৰক</string>
</resources>

View file

@ -422,7 +422,7 @@
<string name="view_public_repositories_button">Вижте хранилищата на общността</string>
<string name="view_public_repositories_button_short">Публичен списък</string>
<string name="uppercase_all_subtitles">Всички субтитри с главни букви</string>
<string name="download_all_plugins_from_repo">Изтегляне на всички добавки от това хранилище\?</string>
<string name="download_all_plugins_from_repo">Изтегляне на всички добавки от това хранилище?</string>
<string name="single_plugin_disabled" formatted="true">%s (Деактивиран)</string>
<string name="tracks">Потоци</string>
<string name="audio_tracks">Аудио потоци</string>

View file

@ -56,7 +56,7 @@
<string name="download_started">ডাউনলোড শুরু</string>
<string name="download_canceled">ডাউনলোড বাদ</string>
<string name="download_done">ডাউনলোড শেষ</string>
<string name="stream">স্ট্রিম</string>
<string name="stream">নেটওয়ার্ক স্ট্রিম</string>
<string name="error_loading_links_toast">লিংক লোডিং ব্যর্থ</string>
<string name="app_dubbed_text">ডাব</string>
<string name="app_subbed_text">সাব</string>
@ -119,7 +119,7 @@
<string name="chromecast_subtitles_settings_des">ক্রোমক্যাস্ট এ সাবটাইটেল সমূহের সেটিংস</string>
<string name="player_size_settings_des">কালো প্রান্ত অপসারণ করুন</string>
<string name="search">অনুসন্ধান করুন</string>
<string name="category_account">অ্যাকাউন্টসমূহ</string>
<string name="category_account">অ্যাকাউন্টসমূহ এবং নিরাপত্তা</string>
<string name="bug_report_settings_on">কোনো উপাত্ত পাঠাবে না</string>
<string name="double_tap_to_pause_settings_des">বিরতি দিতে মাঝে দুইবার চাপুন</string>
<string name="use_system_brightness_settings">সিস্টেম এর উজ্জ্বলতা ব্যবহার করুন</string>
@ -143,7 +143,7 @@
<string name="search_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="eigengraumode_settings">প্লেব্যাক এর গতি</string>
<string name="update_started">আপডেট শুরু হয়েছে</string>
<string name="browser">ব্রাউজার</string>
<string name="test_log">লগ</string>
@ -229,4 +229,134 @@
<string name="episode_sync_settings_des">আপনার বর্তমান পর্বের অগ্রগতি স্বয়ংক্রিয়ভাবে সিঙ্ক করুন</string>
<string name="automatic_plugin_download_mode_title">প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন</string>
<string name="links_reloaded_toast">লিঙ্ক পুনরায় লোড হয়েছে</string>
<string name="switch_account">সুইচ অ্যাকাউন্ট</string>
<string name="episode_action_play_in_browser">ব্রাউজারে প্লে করুন</string>
<string name="legal_notice">দাবিত্যাগ</string>
<string name="asian_drama_singular">এশিয়ান ড্রামা</string>
<string name="video_source">সোর্স</string>
<string name="pref_category_extensions">এক্সটেনশন</string>
<string name="pref_category_links">লিংকস</string>
<string name="nsfw_singular">এনএসএফডব্লিউ</string>
<string name="episode_action_download_mirror">ডাউনলোড মিরর</string>
<string name="unexpected_error">অপ্রত্যাশিত প্লেয়ার এর সমস্যা</string>
<string name="episode_action_download_subtitle">সাবটাইটেল ডাউনলোড করুন</string>
<string name="repo_copy_label">রিপোজিটরির নাম এবং ইউ আর এল</string>
<string name="toast_copied">কপি করা হয়েছে!</string>
<string name="video_disk_description">অ্যান্ড্রয়েড টিভির মতো, কম মেমরির ডিভাইসে খুব বেশি সেট করা হলে সমস্যা করবে।</string>
<string name="add_site_pref">ক্লোন সাইট</string>
<string name="pref_category_player_features">প্লেয়ারের ফিচার</string>
<string name="login_format" formatted="true"><string name="mal_account_settings" translatable="false">MAL</string> <string name="anilist_account_settings" translatable="false">AniList</string> <string name="tmdb_account_settings" translatable="false">TMDB</string> <string name="imdb_account_settings" translatable="false">IMDB</string> <string name="kitsu_account_settings" translatable="false">Kitsu</string> <string name="trakt_account_settings" translatable="false">Trakt</string> %1$s%2$s</string>
<string name="app_theme_settings">অ্যাপ থিম</string>
<string name="recommendations_tooltip">রিকমেন্ডেশনগুলো দেখাও</string>
<string name="speed_setting_summary">প্লেয়ারে গতির বিকল্প যোগ কর</string>
<string name="season_format">%1$s %2$d%3$s</string>
<string name="cartoons_singular">কার্টুন</string>
<string name="anime_singular">এনিমে</string>
<string name="poster_ui_settings">পোস্টারে ইউ আই উপাদান টগল করুন</string>
<string name="check_for_update">আপডেট চেক করুন</string>
<string name="video_aspect_ratio_resize">রিসাইজ</string>
<string name="video_skip_op">ওপেনিং স্কিপ করুন</string>
<string name="video_buffer_disk_settings">ডিক্সের ভিডিও ক্যাশ</string>
<string name="video_buffer_clear_settings">ভিডিও এবং ইমেজ ক্যাশ পরিস্কার করুন</string>
<string name="video_ram_description">অ্যান্ড্রয়েড টিভির মতো, কম মেমরির ডিভাইসে খুব বেশি সেট করা হলে ক্র্যাশ করবে</string>
<string name="dns_pref">ডিএনএস ওভার এইচটিটিপিএস</string>
<string name="add_site_summary">একটি ভিন্ন URL সহ একটি বিদ্যমান সাইটের, একটি ক্লোন যোগ করুন</string>
<string name="resize_fit">স্ক্রিনে ফিট করুন</string>
<string name="pref_category_cache">ক্যাশ</string>
<string name="pref_category_player_layout">লেআউট</string>
<string name="tv_layout">টিভি লেআউট</string>
<string name="example_username">ব্যবহারকারীর নাম</string>
<string name="episodes_range">%1$d-%2$d</string>
<string name="example_site_name">NewSiteName</string>
<string name="action_default">ডিফল্ট</string>
<string name="ova">OVA</string>
<string name="ova_singular">ওভিয়ে</string>
<string name="torrent_singular">টরেন্ট</string>
<string name="episode_action_chromecast_episode">এপিসোড ক্রোমকাস্ট করুন</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="show_title">টাইটেল</string>
<string name="android_tv_interface_off_seek_settings">প্লেয়ার দেখা যাচ্ছে - সিকের পরিমাণ</string>
<string name="remove_site_pref">রিমুভ সাইট</string>
<string name="nginx_url_pref">NGINX সার্ভারের ইউআরএল</string>
<string name="pref_category_bypass">আইএসপি বাইপাস</string>
<string name="pref_category_android_tv">অ্যান্ড্রয়েড টিভি</string>
<string name="enable_nsfw_on_providers">সমর্থিত এক্সটেনশনগুলিতে NSFW সক্ষম করুন</string>
<string name="subtitles_encoding">সাবটাইটেল এনকোডিং</string>
<string name="pref_category_looks">দেখার ধরন</string>
<string name="pref_category_ui_features">ফিচার সমূহ</string>
<string name="random_button_settings_desc">হোমপেজ এবং লাইব্রেরিতে এলোমেলো বোতাম দেখান</string>
<string name="category_providers">প্রদানকারী</string>
<string name="category_provider_test">প্রদানকারী পরীক্ষা</string>
<string name="automatic">স্বয়ংক্রিয়</string>
<string name="phone_layout">ফোন লেআউট</string>
<string name="bottom_title_settings">পোস্টার শিরোনামের অবস্থান</string>
<string name="bottom_title_settings_des">পোস্টারের নীচে শিরোনাম রাখুন</string>
<string name="login">প্রবেশ করুন</string>
<string name="create_account">অ্যাকাউন্ট তৈরি করা</string>
<string name="add_account">একাউন্ট যোগ করা</string>
<string name="android_tv_interface_off_seek_settings_summary">যখন প্লেয়ার হিডেন থাকবে তখন সিকের পরিমান</string>
<string name="episode_action_chromecast_mirror">ক্রোমকাস্ট মিরর</string>
<string name="provider_lang_settings">এক্সটেনশন ভাষা</string>
<string name="category_ui">লেআউট</string>
<string name="pref_category_subtitles">সাবটাইটেল</string>
<string name="pref_category_actions">অ্যাকশন</string>
<string name="limit_title">ভিডিও প্লেয়ারের টাইটেল এ সর্বোচ্চ ক্যারেক্টার</string>
<string name="documentaries_singular">ডকুমেন্টারি</string>
<string name="app_layout">অ্যাপ লেআউট</string>
<string name="other_singular">ভিডিও</string>
<string name="pref_category_backup">বেকাপ</string>
<string name="episode_short">E</string>
<string name="season_short">S</string>
<string name="example_email">hello@world.com</string>
<string name="example_site_url">https://example.com</string>
<string name="subscribe_tooltip">নতুন এপিসোডের নোটিফিকেশন</string>
<string name="result_search_tooltip">অন্য এক্সটেনশনের মধ্যে খুঁজুন</string>
<string name="no_update_found">কোন আপডেট পাওয়া যায়নি</string>
<string name="example_password">password123</string>
<string name="episode_upcoming_format" formatted="true">আসছে %s সময়ের মধ্যে</string>
<string name="cancel">বাতিল করুন</string>
<string name="resume_remaining" formatted="true">%s
\nঅবশিষ্ট</string>
<string name="live_singular">লাইভ স্ট্রিম</string>
<string name="source_error">সোর্স সমস্যা</string>
<string name="remote_error">রিমোট সমস্যা</string>
<string name="render_error">রেন্ডারের সমস্যা</string>
<string name="storage_error">ডাউনলোডের সমস্যা, স্ট্রোরেজদের পারমিশন চেক করুন</string>
<string name="episode_action_play_in_app">অ্যাপ এ প্লে করুন</string>
<string name="episode_action_reload_links">লিংক রিলোড করুন</string>
<string name="show_hd">কোয়ালিটি লেবেল</string>
<string name="show_sub">সাব লেবেল</string>
<string name="show_dub">ডাব লেবেল</string>
<string name="video_lock">লক</string>
<string name="dont_show_again">আর দেখাবেন না</string>
<string name="skip_update">এই আপডেট স্কিপ করুন</string>
<string name="update">আপডেট</string>
<string name="watch_quality_pref">ওয়াইফাই তে যে কোয়ালিটিতে দেখতে চান</string>
<string name="watch_quality_pref_data">মোবাইল ডাটায় যে কোয়ালিটিতে দেখতে চান</string>
<string name="limit_title_rez">ভিডিও প্লেয়ারে রেজুলেশন</string>
<string name="video_buffer_size_settings">ভিডিও বাফার সাইজ</string>
<string name="video_buffer_length_settings">ভিডিও বাফার লেনথ</string>
<string name="dns_pref_summary">যখন আইএসপি ব্লক করবে তখন কার্যকরী</string>
<string name="jsdelivr_proxy">গিডহাব প্রক্সি</string>
<string name="download_path_pref">ডাউনলোডের পথ</string>
<string name="display_subbed_dubbed_settings">ডাব/সাব এনিমে দেখান</string>
<string name="resize_fill">স্ট্রেচ</string>
<string name="resize_zoom">বড় করুন</string>
<string name="pref_category_app_updates">অ্যাপ আপডেট</string>
<string name="pref_category_gestures">গ্যাসচার</string>
<string name="pref_category_defaults">ডিফল্ট</string>
<string name="category_general">সাধারণ</string>
<string name="random_button_settings">এলোমেলো বোতাম</string>
<string name="preferred_media_settings">পছন্দের মিডিয়া</string>
<string name="test_extensions">সমস্ত এক্সটেনশন পরীক্ষা করুন</string>
<string name="test_extensions_summary">এই পরীক্ষাটি শুধুমাত্র ডেভেলপারদের জন্য এবং কোন এক্সটেনশনের কাজ যাচাই বা অস্বীকার করে না।</string>
<string name="emulator_layout">এমুলেটর লেআউট</string>
<string name="primary_color_settings">প্রাইমারি রং</string>
<string name="example_ip">127.0.0.1</string>
<string name="example_lang_name">Language code (en)</string>
<string name="account">অ্যাকাউন্ট</string>
<string name="logout">প্রস্থান</string>
<string name="episode_format" formatted="true">%1$d%2$s</string>
</resources>

View file

@ -422,7 +422,7 @@
<string name="view_public_repositories_button">Ver repositórios da comunidade</string>
<string name="view_public_repositories_button_short">Lista pública</string>
<string name="uppercase_all_subtitles">Todas as legendas em maiúsculas</string>
<string name="download_all_plugins_from_repo">Transferir todos os plugins deste repositório\?</string>
<string name="download_all_plugins_from_repo">Atenção: CloudStream 3 não assume nenhuma responsabilidade pelo uso de extensões de terceiros e não fornece nenhum suporte para elas!</string>
<string name="single_plugin_disabled" formatted="true">%s (Desativado)</string>
<string name="autoplay_next_settings">Reproduzir automaticamente próximo episódio</string>
<string name="autoplay_next_settings_des">Começa o próximo episódio quando o atual termina</string>
@ -622,7 +622,7 @@
<string name="password_pin_authentication_title">Autenticação de Senha/PIN</string>
<string name="biometric_unsupported">A autenticação biométrica não é compatível com este dispositivo</string>
<string name="biometric_setting_summary">Desbloquear o aplicativo com impressão digital, ID facial, PIN, padrão e senha.</string>
<string name="biometric_prompt_description">Esta tela foi fechada devido a diversas tentativas malsucedidas. Por favor reinicie o aplicativo.</string>
<string name="biometric_prompt_description">Após algumas tentativas fracassadas, o prompt será fechado. Basta reiniciar o aplicativo para tentar novamente.</string>
<string name="resume_remaining" formatted="true">%s
\nrestante(s)</string>
<string name="favorite">Favorito</string>
@ -639,4 +639,21 @@
<string name="music_singlar">Música</string>
<string name="audio_book_singular">Áudio-livro</string>
<string name="custom_media_singluar">Mídia</string>
<string name="reset_btn">Redefinir</string>
<string name="episode_upcoming_format" formatted="true">Próximos em %s</string>
<string name="next_season_episode_format" formatted="true">Temporada %1$d Episódio %2$d será lançado em</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Selecione o dispositivo de transmissão</string>
<string name="episode_action_cast_mirror">Espelhar transmissão</string>
<string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_security">Segurança</string>
<string name="pref_category_accounts">Contas</string>
<string name="auth_locally">Autenticação local</string>
<string name="qr_image">Imagem do código QR</string>
<string name="dismiss">Descartar</string>
<string name="open_downloaded_repo">Abrir repositório</string>
<string name="device_pin_url_message">Acesse <b>%s</b> em seu smartphone ou computador e digite o código acima</string>
<string name="device_pin_error_message">Não é possível obter o código PIN do dispositivo, tente a autenticação local</string>
<string name="device_pin_expired_message">O código PIN expirou!</string>
<string name="device_pin_counter_text">O código expira em %1$dm %2$ds</string>
</resources>

View file

@ -489,7 +489,7 @@
<string name="add_site_summary">Přidat klon existujícího webu s jinou adresou URL</string>
<string name="example_site_url">https://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="download_all_plugins_from_repo">Varování: CloudStream 3 nenese žádnou zodpovědnost za používání rozšíření třetích stran a neposkytuje pro ně žádnou podporu!</string>
<string name="single_plugin_disabled" formatted="true">%s (zakázáno)</string>
<string name="tracks">Stopy</string>
<string name="nsfw">NSFW</string>
@ -623,7 +623,7 @@
<string name="password_pin_authentication_title">Ověření heslem/PINem</string>
<string name="biometric_unsupported">Biometrické ověření není na tomto zařízení podporováno</string>
<string name="biometric_setting_summary">Odemkněte aplikaci otiskem prstu, obličejem, PINem, gestem nebo heslem.</string>
<string name="biometric_prompt_description">Tato obrazovka byla po několika nezdařilých pokusech uzavřena. Restartujte prosím aplikaci.</string>
<string name="biometric_prompt_description">Po několika nezdařilých pokusech se okno zavře. Pro opětovný pokus restartujte aplikaci.</string>
<string name="biometric_warning">Vaše data z aplikace CloudStream byla nyní zálohována. Ačkoli je tato možnost velmi malá, různá zařízení se mohou chovat různě. Ve výjimečném případě, že se vám přístup k aplikaci zablokuje, data aplikace zcela vymažte a obnovte je ze zálohy. Velmi se omlouváme za případné nepříjemnosti z toho plynoucí.</string>
<string name="unfavorite">Odebrat z oblíbených</string>
<string name="resume_remaining" formatted="true">%s
@ -641,4 +641,21 @@
<string name="battery_dialog_title">Zakažte optimalizace baterie</string>
<string name="battery_dialog_message">Aby bylo zajištěno nepřetržité stahování a upozornění na odebírané seriály, potřebuje aplikace CloudStream povolení ke spuštění na pozadí. Stisknutím tlačítka OK budete přesměrováni na informace o aplikaci. Tam přejděte na položku Využití baterie aplikací a nastavte možnost Využití baterie na hodnotu Neomezené. Upozorňujeme, že toto povolení neznamená, že CS3 bude vybíjet baterii. Na pozadí bude pracovat pouze v případě potřeby, například při přijímání oznámení nebo stahování videí z oficiálních rozšíření. Pokud se rozhodnete toto nastavení zrušit, můžete jej později upravit v Obecných nastaveních.</string>
<string name="audio_book_singular">Audiokniha</string>
<string name="reset_btn">Resetovat</string>
<string name="episode_upcoming_format" formatted="true">Vychází %s</string>
<string name="next_season_episode_format" formatted="true">Epizoda %2$d ze série %1$d bude vydána za</string>
<string name="episode_action_cast_mirror">Vysílat zrcadlení</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Vyberte zařízení k vysílání</string>
<string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_security">Zabezpečení</string>
<string name="qr_image">Obrázek QR kódu</string>
<string name="dismiss">Zavřít</string>
<string name="open_downloaded_repo">Otevřít repozitář</string>
<string name="device_pin_url_message">Navštivte <b>%s</b> na vašem zařízení nebo počítači a zadejte kód výše</string>
<string name="device_pin_error_message">Nepodařilo se získat PIN kód, zkuste místní ověření</string>
<string name="device_pin_counter_text">Kód vyprší za %1$dm %2$ds</string>
<string name="pref_category_accounts">Účty</string>
<string name="auth_locally">Lokální ověření</string>
<string name="device_pin_expired_message">PIN kód vypršel!</string>
</resources>

View file

@ -151,7 +151,7 @@
<string name="backup_failed">Speicherberechtigungen fehlen. Bitte erneut versuchen.</string>
<string name="search">Suche</string>
<string name="category_account">Konten und Sicherheit</string>
<string name="category_updates">Updates und Datensicherung</string>
<string name="category_updates">Aktualisierungen und Datensicherung</string>
<string name="settings_info">Info</string>
<string name="advanced_search">Erweiterte Suche</string>
<string name="advanced_search_des">Liefert die Suchergebnisse getrennt nach Anbietern</string>
@ -416,12 +416,12 @@
<string name="view_public_repositories_button">Community-Repositories anzeigen</string>
<string name="view_public_repositories_button_short">Öffentliche Liste</string>
<string name="uppercase_all_subtitles">Alle Untertitel in Großbuchstaben</string>
<string name="download_all_plugins_from_repo">Alle Plugins aus diesem Repository herunterladen\?</string>
<string name="download_all_plugins_from_repo">Alle Plugins aus diesem Repository herunterladen?</string>
<string name="single_plugin_disabled" formatted="true">%s (Deaktiviert)</string>
<string name="tracks">Spuren</string>
<string name="audio_tracks">Audiospuren</string>
<string name="video_tracks">Videospuren</string>
<string name="apply_on_restart">Bei Neustart anwenden</string>
<string name="apply_on_restart">Starte die App neu, um die Änderungen zu sehen.</string>
<string name="safe_mode_title">Abgesicherter Modus aktiviert</string>
<string name="safe_mode_description">Alle Erweiterungen wurden aufgrund eines Absturzes deaktiviert, damit Sie diejenige finden können, welche Probleme verursacht.</string>
<string name="safe_mode_crash_info">Absturzinfo ansehen</string>
@ -607,4 +607,12 @@
<string name="clipboard_unknown_error">Beim kopieren ist ein Fehler aufgetreten, bitte kopieren sie logical und wenden sich an den Support.</string>
<string name="clipboard_permission_error">Fehler beim zugriff auf die Zwischenablage, bitte erneut versuchen.</string>
<string name="repo_copy_label">Repository Name und URL</string>
<string name="ok">OK</string>
<string name="battery_dialog_title">Akku-Optimierung deaktivieren</string>
<string name="music_singlar">Musik</string>
<string name="audio_book_singular">Hörbuch</string>
<string name="custom_media_singluar">Medien</string>
<string name="reset_btn">Zurücksetzen</string>
<string name="app_unrestricted_toast">Akkuverbrauch der App ist bereits auf unbeschränkt eingestellt</string>
<string name="app_info_intent_error">CloudStreams App-Info kann nicht geöffnet werden.</string>
</resources>

View file

@ -91,7 +91,7 @@
<string name="player_subtitles_settings_des">Ρυθμίσεις υποτίτλων του προγράμματος αναπαραγωγής</string>
<string name="chromecast_subtitles_settings">Υπότιτλοι για Chromecast</string>
<string name="chromecast_subtitles_settings_des">Ρυθμίσεις υποτίτλων για Chromecast</string>
<string name="eigengraumode_settings">Eigengravy Mode</string>
<string name="eigengraumode_settings">Ταχύτητα Προβολής</string>
<string name="swipe_to_seek_settings">Σύρετε για αναζήτηση</string>
<string name="swipe_to_seek_settings_des">Σύρετε από πλευρά σε πλευρά για να ελέγξετε το σημείο του βίντεο στο οποίο βρίσκεστε</string>
<string name="swipe_to_change_settings">Σύρετε για να αλλάξετε ρυθμίσεις</string>
@ -130,7 +130,7 @@
<string name="backup_success">Τα δεδομένα αποθηκεύτηκαν</string>
<string name="backup_failed">Δεν έχει δοθεί άδεια για πρόσβαση στον αποθηκευτικό χώρο. Παρακαλώ προσπαθήστε ξανά.</string>
<string name="backup_failed_error_format">Σφάλμα δημιουργίας αντιγράφων ασφαλείας %s</string>
<string name="category_account">Λογαριασμοί</string>
<string name="category_account">Λογαριασμοί και Ασφάλεια</string>
<string name="category_updates">Ενημερώσεις και αντίγραφα ασφαλείας</string>
<string name="show_fillers_settings">Εμφάνιση filler επεισοδίου για άνιμε</string>
<string name="show_trailers_settings">Εμφάνιση trailer</string>
@ -201,7 +201,7 @@
<string name="episode_action_reload_links">Επαναφόρτωση συνδέσμων</string>
<string name="episode_action_download_subtitle">Λήψη υποτίτλων</string>
<string name="show_hd">Ποιότητα</string>
<string name="show_dub">Dub</string>
<string name="show_dub">Ετικέτα Dub</string>
<string name="show_sub">Sub</string>
<string name="show_title">Τίτλος</string>
<string name="poster_ui_settings">Εναλλαγή γραφικών στοιχείων στην αφίσα</string>
@ -233,11 +233,11 @@
<string name="legal_notice">Αποποίηση ευθυνών</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>
<string name="enable_nsfw_on_providers">Ενεργοποίηση NSFW σε υποστηριζόμενους παρόχους</string>
<string name="enable_nsfw_on_providers">Ενεργοποίηση ακατάλληλου περιεχομένου σε υποστηριζόμενους παρόχους</string>
<string name="subtitles_encoding">Κωδικοποίηση υποτίτλων</string>
<string name="category_providers">Πάροχοι</string>
<string name="category_ui">Διάταξη</string>
@ -302,8 +302,8 @@
<string name="subtitles_filter_lang">Φιλτράρισμα ανά την προτεινόμενη γλώσσα του μέσου</string>
<string name="extras">Έξτρα</string>
<string name="trailer">Τρέιλερ</string>
<string name="network_adress_example">Σύνδεσμος για stream</string>
<string name="referer">Παραπομπή</string>
<string name="network_adress_example">Https://Παράδειγμα.com/Παράδειγμα.mp4</string>
<string name="referer">Παραπομπή (προαιρετική)</string>
<string name="next">Επόμενο</string>
<string name="provider_languages_tip">Παρακολούθηση βίντεο σε αυτή την γλώσσα</string>
<string name="previous">Προηγούμενο</string>
@ -334,8 +334,6 @@
<string name="plugins_updated" formatted="true">Ενημερώθηκαν %d πρόσθετα</string>
<string name="blank_repo_message">Το CloudStream δεν έχει προεγκατεστημένους ιστότοπους. Πρέπει να εγκαταστήσετε ιστότοπους μέσω ορισμένων αποθετηρίων.
\n
\nΛόγω ενός χαζού DMCA takedown από μέρους των Sky UK Limited 🤮 δεν μπορούμε να προσθέσουμε απευθείας σύνδεσμο προς τα προαναφερόμενα αποθετήρια εντός της εφαρμογής.
\n
\nΒρείτε μας στο Discord ή ψάξτε στο διαδίκτυο.</string>
<string name="view_public_repositories_button">Προβολή αποθετηρίων κοινότητας</string>
<string name="view_public_repositories_button_short">Δημόσια λίστα</string>
@ -345,7 +343,7 @@
<string name="tracks">Κομμάτια</string>
<string name="audio_tracks">Ηχητικά κομμάτια</string>
<string name="video_tracks">Κομμάτια βίντεο</string>
<string name="apply_on_restart">Εφαρμογή στην επανεκκίνηση</string>
<string name="apply_on_restart">Κάντε επανεκκίνηση της εφαρμογής για να δείτε τις αλλαγές.</string>
<string name="safe_mode_title">Η ασφαλής λειτουργία ενεργοποιήθηκε</string>
<string name="safe_mode_description">Όλα τα extensions απενεργοποιήθηκαν , ώστε να μπορέσετε να διαπιστώσετε ποιο από αυτά προκάλεσε τη κατάρρευση.</string>
<string name="safe_mode_crash_info">Προβολή πληροφορίας κατάρρευσης</string>
@ -392,7 +390,7 @@
<string name="automatic_plugin_updates">Αυτόματη ενημέρωση plugin</string>
<string name="automatic_plugin_download">Αυτόματη λήψη plugin</string>
<string name="dns_pref">DNS μέσω HTTPS</string>
<string name="example_site_url">παράδειγμα.com</string>
<string name="example_site_url">https://παράδειγμα.com</string>
<string name="quality_hq">HQ</string>
<string name="quality_ts">TS</string>
<string name="quality_tc">TC</string>
@ -412,7 +410,7 @@
<string name="nsfw_singular">NSFW</string>
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
<string name="nginx_url_pref">Σύνδεσμος NGINX σέρβερ</string>
<string name="example_site_name">ΟΚουλΙστότοποςΜου</string>
<string name="example_site_name">ΝεοΟνομαΙστοτοπου</string>
<string name="sync_total_episodes_none">/\?\?</string>
<string name="sync_total_episodes_some" formatted="true">/%d</string>
<string name="sync_score_format" formatted="true">%d / 10</string>
@ -434,7 +432,7 @@
<string name="no_update_found">Δεν βρέθηκε ενημέρωση</string>
<string name="check_for_update">Έλεγχος για ενημέρωση</string>
<string name="example_password">κωδικός123</string>
<string name="example_username">ΤοΚουλΨευδώνυμοΜου</string>
<string name="example_username">ΤοΚουλΟνομαΜου</string>
<string name="example_email">γειασου@κόσμε.com</string>
<string name="subtitles_example_text">Η γρήγορη, καφέ αλεπού πηδάει πάνω από τον τεμπέλη σκύλο / The quick brown fox jumps over the lazy dog</string>
<string name="quality_cam">Cam</string>
@ -521,7 +519,7 @@
\nΣΗΜΕΙΩΣΗ: Εάν το άθροισμα είναι 10 ή περισσότερο, η συσκευή αναπαραγωγής θα παραλείψει αυτόματα τη φόρτωση όταν φορτωθεί αυτός ο σύνδεσμος!</string>
<string name="category_provider_test">Δοκιμή παρόχου</string>
<string name="watch_quality_pref_data">Προτιμώμενη ποιότητας παρακολούθησης (Δεδομένα τηλεφώνου)</string>
<string name="jsdelivr_proxy">Διακομιστής μεσολάβησης raw.githubusercontent.com</string>
<string name="jsdelivr_proxy">Διακομιστής μεσολάβησης GitHub</string>
<string name="pref_category_android_tv">Android TV</string>
<string name="subscription_in_progress_notification">Ενημέρωση εγγεγραμμένων εκπομπών</string>
<string name="subscription_new">Έγινε εγγραφή σε %s</string>
@ -546,4 +544,85 @@
<string name="automatic_plugin_download_mode_title">Επιλέξτε κατάσταση για φιλτράρισμα επεκτάσεων για λήψη</string>
<string name="disable">Απενεργοποιημένο</string>
<string name="stop">Τέλος</string>
<string name="backup_frequency">Συχνότητα δημιουργίας αντιγράφων ασφαλείας</string>
<string name="links_reloaded_toast">Οι σύνδεσμοι επαναφορτώθηκαν</string>
<string name="toast_copied">αντιγράφηκε!</string>
<string name="result_search_tooltip">Αναζήτηση σε άλλες επεκτάσεις</string>
<string name="subscribe_tooltip">Ειδοποίηση για νέο επεισόδιο</string>
<string name="enter_pin">Εισαγωγή Κωδικού</string>
<string name="repo_copy_label">Όνομα \"αποθήκης\" και λινκ</string>
<string name="recommendations_tooltip">Εμφάνιση προτάσεων</string>
<string name="action_add_to_favorites">Προσθήκη στα Αγαπημένα</string>
<string name="action_subscribe">Εγγραφή</string>
<string name="duplicate_replace_all">Αντικατάσταση Όλων</string>
<string name="use_default_account">Χρησιμοποίηση Βασικού λογαριασμού</string>
<string name="action_remove_from_favorites">Αφαίρεση από τα Αγαπημένα</string>
<string name="android_tv_interface_off_seek_settings">Κρυμμένο Πλέιερ - Δευτερόλεπτα Σκιπ</string>
<string name="android_tv_interface_off_seek_settings_summary">Δευτερόλεπτα Σκιπ όταν ο αναπαραγωγέας είναι κρυφός</string>
<string name="ok">Εντάξει</string>
<string name="battery_dialog_title">Απενεργοποιήση της εξοικονόμησης της μπαταρίας</string>
<string name="already_voted">Έχετε ήδη ψηφίσει</string>
<string name="duplicate_message_single" formatted="true">Φαίνεται πως ένα πιθανό αντίγραφο βρίσκεται στη βιβλιοθήκη σας: \'%s.\'
\n
\nΘα επιθυμούσατε να το προσθέσετε, να το αντικαταστήσετε, ή να ακυρώσετε την ενέργεια;</string>
<string name="enter_current_pin">Εισαγωγή Τρέχον Κωδικού</string>
<string name="lock_profile">Κλείδωμα Προφίλ</string>
<string name="biometric_authentication_title">Ξεκλείδωμα Cloudstream</string>
<string name="password_pin_authentication_title">ΚωδικόςPIN Αυθεντικότητας</string>
<string name="speed_setting_summary">Προσθέτει επιλογή ταχύτητας στον αναπαραγωγέα</string>
<string name="logged_account" formatted="true">Σύνδεση ως %s</string>
<string name="rotate_video">Περιστροφή</string>
<string name="action_unsubscribe">Απεγγραφή</string>
<string name="manage_accounts">Διαχείριση λογαριασμών</string>
<string name="auto_rotate_video_desc">Ενεργοποίηση αυτόματης περιστροφής οθόνης αναλόγως του βίντεο</string>
<string name="auto_rotate_video">Αυτόματη περιστροφή</string>
<string name="next_season_episode_format" formatted="true">Σεζόν %1$dΕπεισόδιο%2$d θα κυκλοφορήσει</string>
<string name="android_tv_interface_on_seek_settings_summary">Δευτερόλεπτα Σκιπ όταν φαίνεται ο αναπαραγωγέας (πλειερ)</string>
<string name="test_extensions">Δοκιμή όλων των παροχών</string>
<string name="test_extensions_summary">Αυτό το τεστ προορίζεται μόνο για τους προγραμματιστές και δε επαληθείει ούτε απορρίπτει την λειτουργία οποιουδήποτε παρόχου.</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Επιλογή συσκευής για αναμετάδοση</string>
<string name="clipboard_permission_error">Πρόβλημα στην πρόσβαση στο Clipboard, Παρακαλώ προσπαθήστε ξανά.</string>
<string name="clipboard_unknown_error">Πρόβλημα στην αντιγραφή , Παρακαλούμε αντιγράψτε το logcat και επικοινωνήστε με την υποστήριξη.</string>
<string name="app_unrestricted_toast">Η χρήση μπαταρίας έχει ήδη τεθεί χωρίς περιορισμό</string>
<string name="app_info_intent_error">Αδύνατο άνοιγμα των στοιχείων της εφαρμογής Cloudstream.</string>
<string name="favorites_list_name">Αγαπημένα</string>
<string name="favorite_added">%s προστέθηκε στα αγαπημένα</string>
<string name="favorite_removed">%s αφαιρέθηκε από τα αγαπημένα</string>
<string name="duplicate_title">Πιθανό αντίγραφο βρέθηκε</string>
<string name="duplicate_add">Προσθήκη</string>
<string name="duplicate_replace">Αντικατάσταση</string>
<string name="duplicate_message_multiple" formatted="true">Πιθανά διπλά αρχεία βρέθηκαν στην βιβλιοθήκη:
\n
\n%s
\n
\nΘα επιθυμούσατε να προσθέσετε αυτό το αρχείο ούτως η άλλως, να αντικαταστήσετε τα ήδη υπάρχοντα, ή να ακυρώσετε την ενέργεια?</string>
<string name="enter_pin_with_name" formatted="true">Εισαγωγή Κωδικού για %s</string>
<string name="pin">Κωδικός</string>
<string name="pin_error_incorrect">Εσφαλμένος Κωδικός. Προσπαθήστε ξανά.</string>
<string name="pin_error_length">Ο κωδικός να περιέχει 4 χαρακτήρες</string>
<string name="select_an_account">Επιλογή λογαριασμού</string>
<string name="unfavorite">Αφαίρεση από αγαπημένα</string>
<string name="biometric_setting">Κλείδωμα με βιομετρικά</string>
<string name="episode_upcoming_format" formatted="true">Έρχεται σε %s</string>
<string name="android_tv_interface_on_seek_settings">Εμφάνιση Πλέιερ - Δευτερόλεπτα Σκιπ</string>
<string name="jsdelivr_proxy_summary">Παράκαμψη απαγόρευσης από raw github URLs χρησιμοποιώντας jsDelivr. Μπορεί να καθυστερήσει τις ενημερώσεις για μερικές μέρες.</string>
<string name="rotate_video_desc">Εμφάνιση κουμπιού για περιστροφή οθόνης</string>
<string name="favorite">Αγαπημένο</string>
<string name="resume_remaining" formatted="true">%s
\nαπομένουν</string>
<string name="biometric_unsupported">Βιομετρική αυθεντικοποίηση δεν υποστηρίζεται από τη συσκευή</string>
<string name="episode_action_cast_mirror">Καστ ταινίας</string>
<string name="battery_dialog_message">Για να σιγουρέψουμε πως οι λήψεις ταινιών και οι ειδοποιήσεις για σειρές στο Cloudstream δεν έχουν πρόβλημα, το Cloudstream χρειάζεται άδεια να τρέχει στο παρασκήνιο. Πατώντας ΟΚ θα μεταφερθείτε στις λεπτομέρειες εφαρμογής, από κει πηγαίνετε στην Χρήση μπαταρίας από εφαρμογές και θέσετε την χρήση σε μη περιορισμένη. Να έχετε στο νου σας πως το Cloudstream δε θα καταναλώσει την μπαταρία σας. Απλά θα λειτουργήσει μόνο όταν χρειάζεται, όπως για την ειδοποίηση για ανερχόμενες σειρές ή της λήψεις σας μέσω των παροχών. Άμα θέλετε να ακυρώσετε, μπορείτε να αλλάξετε αυτή τη ρύθμιση μέσω των γενικών ρυθμίσεων.</string>
<string name="biometric_setting_summary">Ξεκλείδωμα εφαρμογής με δακτυλικό αποτύπωμα, Face ID, PIN, Μοτίβο και Κωδικό.</string>
<string name="biometric_prompt_description">Η οθόνη έκλεισε λόγω πολλαπλών ανεπιτυχών ενεργειών. Κάντε επανεκκίνηση της εφαρμογής.</string>
<string name="edit_account">Επεξεργασία λογαριασμού</string>
<string name="skip_startup_account_select_pref">Παράλειψη επιλογής λογαριασμού στην εκκίνηση της εφαρμογής</string>
<string name="music_singlar">Μουσική</string>
<string name="audio_book_singular">Ακουστικό Βιβλίο</string>
<string name="custom_media_singluar">Μέσα</string>
<string name="reset_btn">Επαναφορά</string>
<string name="biometric_warning">Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία.</string>
<string name="pref_category_accounts">Λογαριασμοί</string>
<string name="pref_category_security">Ασφάλεια</string>
</resources>

View file

@ -176,7 +176,7 @@
<string name="subs_edge_type">Tipo de Borde</string>
<string name="subs_subtitle_elevation">Elevación de Subtítulo</string>
<string name="search_provider_text_providers">Buscar usando proveedores</string>
<string name="picture_in_picture_des">Continúa la reproducción en un reproductor miniatura encima de otras aplicaciones</string>
<string name="picture_in_picture_des">Continúa la reproducción en una imagen pequeña encima de otras aplicaciones</string>
<string name="player_size_settings">Botón de cambio de tamaño del reproductor</string>
<string name="player_size_settings_des">Eliminar bordes negros</string>
<string name="subs_auto_select_language">Seleccionar idioma automáticamente</string>
@ -232,7 +232,7 @@
<string name="advanced_search_des">Mostrar los resultados de la búsqueda por proveedor</string>
<string name="bug_report_settings_off">Solo envíar los datos si la App se cierra / falla inesperadamente</string>
<string name="bug_report_settings_on">No enviar datos</string>
<string name="show_trailers_settings">Mostrar los trailers</string>
<string name="show_trailers_settings">Mostrar avances</string>
<string name="kitsu_settings">Mostrar pósters de Kitsu</string>
<string name="uprereleases_settings">Actualizar a las versiones preliminares</string>
<string name="uprereleases_settings_des">Buscar actualizaciones preliminares (beta) en lugar de solo versiones completas (stable releases)</string>
@ -289,7 +289,7 @@
<string name="blank_repo_message">CloudStream no tiene sitios instalados por defecto. Necesitas instalar los sitios desde los repositorios.
\n
\nÚnase a nuestro Discord o busque en línea.</string>
<string name="download_all_plugins_from_repo">¿Descargar todos los plugins de este repositorio?</string>
<string name="download_all_plugins_from_repo">Advertencia: ¡CloudStream 3 no asume ninguna responsabilidad por el uso de extensiones de terceros y no brinda ningún soporte para ellas!</string>
<string name="updates_settings">Mostrar actualizaciones de la aplicación</string>
<string name="apk_installer_settings">Instalador de APK</string>
<string name="apk_installer_settings_des">Algunos dispositivos no soportan el nuevo instalador de paquetes. Pruebe la opción antigua (legacy) si las actualizaciones no se instalan.</string>
@ -339,7 +339,7 @@
<string name="poster_ui_settings">Alternar elementos de la interfaz de usuario en el póster</string>
<string name="no_update_found">No se encontró ninguna actualización</string>
<string name="category_general">General</string>
<string name="primary_color_settings">Color primario</string>
<string name="primary_color_settings">Color principal</string>
<string name="app_theme_settings">Tema de la aplicación</string>
<string name="example_email">hola@mundo.com</string>
<string name="login_format" formatted="true">%1$s %2$s</string>
@ -449,13 +449,13 @@
<string name="batch_download">Descarga por lotes</string>
<string name="plugin_singular">plugin</string>
<string name="plugin">plugins</string>
<string name="plugins_updated" formatted="true">Actualizados %d plugins</string>
<string name="plugins_updated" formatted="true">%d plugins actualizados</string>
<string name="view_public_repositories_button">Ver repositorios de la comunidad</string>
<string name="view_public_repositories_button_short">Lista pública</string>
<string name="tracks">Pistas</string>
<string name="audio_tracks">Pistas de audio</string>
<string name="video_tracks">Pistas de video</string>
<string name="safe_mode_title">Modo seguro ON</string>
<string name="safe_mode_title">Modo seguro activado</string>
<string name="safe_mode_crash_info">Ver información de fallos</string>
<string name="extension_rating" formatted="true">Puntaje:%s</string>
<string name="extension_version">Versión</string>
@ -483,7 +483,7 @@
<string name="yes">Si</string>
<string name="delayed_update_notice">La aplicación se actualizará al salir</string>
<string name="update_started">Actualización iniciada</string>
<string name="plugin_downloaded">Complemento descargado</string>
<string name="plugin_downloaded">Plugin descargado</string>
<string name="action_remove_from_watched">Quitar de visto</string>
<string name="sort_by">Ordenar por</string>
<string name="sort">Ordenar</string>
@ -512,7 +512,7 @@
<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="category_provider_test">Verificar al proveedor</string>
<string name="restart">Reiniciar</string>
<string name="subscription_list_name">Suscrito</string>
<string name="subscription_new">Suscrito a %s</string>
@ -545,7 +545,7 @@
<string name="unable_to_inflate">La interfaz de usuario no se ha podido crear correctamente, se trata de un GRAN BUG y debe ser reportado inmediatamente %s</string>
<string name="automatic_plugin_download_mode_title">Seleccionar modo para filtrar los plugins descargados</string>
<string name="disable">Deshabilitar</string>
<string name="no_plugins_found_error">No se encontraron complementos en el repositorio</string>
<string name="no_plugins_found_error">No se encontraron plugins en el repositorio</string>
<string name="no_repository_found_error">Repositorio no encontrado, comprueba la URL y prueba la VPN</string>
<string name="already_voted">Ya has votado</string>
<string name="backup_frequency">Frecuencia de la copia de seguridad</string>
@ -599,7 +599,7 @@
<string name="biometric_setting_summary">Desbloquea la aplicación con huella dactilar, Face ID, PIN, patrón y contraseña.</string>
<string name="biometric_authentication_title">Desbloquear CloudStream</string>
<string name="biometric_unsupported">La autenticación biométrica no es compatible con este dispositivo</string>
<string name="biometric_prompt_description">Esta pantalla se cerró después de algunos intentos fallidos. Reinicie la aplicación.</string>
<string name="biometric_prompt_description">Después de algunos intentos fallidos, el mensaje se cerrará. Simplemente reinicie la aplicación para volver a intentarlo.</string>
<string name="biometric_warning">Ahora se ha realizado una copia de seguridad de sus datos de CloudStream. Aunque la posibilidad de que esto ocurra es muy baja, todos los dispositivos pueden comportarse de forma diferente. En el raro caso de que no puedas acceder a la aplicación, borra completamente los datos de la aplicación y restaura desde una copia de seguridad. Sentimos mucho las molestias que esto pueda ocasionarte.</string>
<string name="favorite">Favorito</string>
<string name="unfavorite">No favorito</string>
@ -616,5 +616,22 @@
<string name="app_info_intent_error">No se puede abrir la información de la aplicación CloudStream.</string>
<string name="custom_media_singluar">Media</string>
<string name="audio_book_singular">Audiolibro</string>
<string name="battery_dialog_message">Para garantizar descargas y notificaciones ininterrumpidas para programas de televisión suscritos, CloudStream necesita permiso para ejecutarse en segundo plano. Al presionar OK, se le dirigirá a información de la aplicación. Allí, desplácese hasta Uso de la batería de la aplicación y establezca el uso de la batería en Sin restricciones. Tenga en cuenta que este permiso no significa que CS3 agotará su batería. Solo funcionará en segundo plano cuando sea necesario, como cuando reciba notificaciones o descargue videos de extensiones oficiales. Si decide cancelar, puede ajustar esta configuración más adelante en los ajustes generales.</string>
<string name="battery_dialog_message">Para garantizar descargas y notificaciones ininterrumpidas para programas de televisión suscritos, CloudStream necesita permiso para ejecutarse en segundo plano. Al presionar OK, se le dirigirá a información de la aplicación. Allí, desplácese hasta 𝙐𝙨𝙤 𝙙𝙚 𝙡𝙖 𝙗𝙖𝙩𝙚𝙧í𝙖 𝙙𝙚 𝙡𝙖 𝙖𝙥𝙡𝙞𝙘𝙖𝙘𝙞ó𝙣 y establezca el uso de la batería en 𝙎𝙞𝙣 𝙧𝙚𝙨𝙩𝙧𝙞𝙘𝙘𝙞𝙤𝙣𝙚𝙨. Tenga en cuenta que este permiso no significa que CS3 agotará su batería. Solo funcionará en segundo plano cuando sea necesario, como cuando reciba notificaciones o descargue videos de extensiones oficiales. Si decide cancelar, puede ajustar esta configuración más adelante en 𝙡𝙤𝙨 𝙖𝙟𝙪𝙨𝙩𝙚𝙨 𝙜𝙚𝙣𝙚𝙧𝙖𝙡𝙚𝙨.</string>
<string name="reset_btn">Reset</string>
<string name="episode_upcoming_format" formatted="true">Próximamente en %s</string>
<string name="next_season_episode_format" formatted="true">La temporada %1$d y el episodio %2$d se estrenarán en</string>
<string name="player_settings_select_cast_device">Seleccionar el dispositivo para transmitir</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="episode_action_cast_mirror">Espejo de transmisión</string>
<string name="cs3wiki">Wiki de CloudStream</string>
<string name="pref_category_security">Seguridad</string>
<string name="pref_category_accounts">Cuentas</string>
<string name="auth_locally">Autenticación local</string>
<string name="qr_image">Imagen del código QR</string>
<string name="dismiss">Descartar</string>
<string name="open_downloaded_repo">Repositorio abierto</string>
<string name="device_pin_url_message">Visita <b> %s </b> en tu smartphone o ordenador e introduce el código anterior</string>
<string name="device_pin_expired_message">¡El código PIN ya ha caducado!</string>
<string name="device_pin_counter_text">El código caduca en %1$d mín y %2$d s</string>
<string name="device_pin_error_message">No puedo obtener el código PIN del dispositivo; intente con la autenticación local</string>
</resources>

View file

@ -280,7 +280,7 @@
<string name="backup_failed_error_format">Erreur de sauvegarde %s</string>
<string name="search">Recherche</string>
<string name="category_account">Comptes et Sécurité</string>
<string name="category_updates">Mises à jour et sauvegarde</string>
<string name="category_updates">Mises à jour et Sauvegarde</string>
<string name="settings_info">Info</string>
<string name="advanced_search">Recherche avancée</string>
<string name="advanced_search_des">Vous donne les résultats de la recherche séparés par fournisseur</string>
@ -419,7 +419,7 @@
<string name="setup_extensions_subtext">Télécharger la liste de sites que vous voulez utiliser</string>
<string name="plugins_downloaded" formatted="true">Téléchargé : %d</string>
<string name="video_tracks">Pistes vidéo</string>
<string name="apply_on_restart">Appliqué au redémarrage</string>
<string name="apply_on_restart">Redémarrez l\'application pour voir les changements.</string>
<string name="safe_mode_description">Toutes les extensions ont été désactivé à cause d\'un crash pour vous aider à trouver l\'extension causant le problème.</string>
<string name="safe_mode_title">Mode sans échec activé</string>
<string name="extension_size">Taille</string>
@ -446,7 +446,7 @@
<string name="plugins_disabled" formatted="true">Désactivé : %d</string>
<string name="plugins_not_downloaded" formatted="true">Non téléchargé : %d</string>
<string name="plugins_updated" formatted="true">%d plugins mis-à-jour</string>
<string name="download_all_plugins_from_repo">Télécharger tous les plugins de ce repository \?</string>
<string name="download_all_plugins_from_repo">Télécharger tous les plugins de ce repository ?</string>
<string name="single_plugin_disabled" formatted="true">%s (Désactivé)</string>
<string name="tracks">Pistes</string>
<string name="audio_tracks">Pistes audio</string>
@ -595,4 +595,29 @@
<string name="test_extensions_summary">Ce test est destiné uniquement aux développeurs et ne vérifie ni n\'empêche le fonctionnement d\'aucune extension.</string>
<string name="toast_copied">Copié!</string>
<string name="repo_copy_label">Nom du dépôt et adresse internet</string>
<string name="favorite">Favori</string>
<string name="biometric_warning">Vos données CloudStream viennent d\'être sauvegardées. Bien que cette éventualité soit très faible, tous les appareils peuvent se comporter différemment. Dans le rare cas où l\'accès à l\'application est bloqué, effacez complètement les données de l\'application et restaurez à partir d\'une sauvegarde. Nous sommes sincèrement désolés pour les désagréments occasionnés par cette situation.</string>
<string name="battery_dialog_title">Désactiver l\'optimisation de la batterie</string>
<string name="app_info_intent_error">Impossible d\'ouvrir les informations de l\'application CloudStream.</string>
<string name="biometric_authentication_title">Déverrouiller CloudStream</string>
<string name="music_singlar">Musique</string>
<string name="resume_remaining" formatted="true">%s
\nrestants</string>
<string name="clipboard_permission_error">Erreur d\'accès au presse-papiers, veuillez réessayer.</string>
<string name="ok">OK</string>
<string name="biometric_unsupported">L\'authentification biométrique n\'est pas prise en charge sur cet appareil</string>
<string name="audio_book_singular">Livre Audio</string>
<string name="password_pin_authentication_title">Mot de passe/Code PIN</string>
<string name="clipboard_unknown_error">Erreur de copie, Veuillez copier le logcat et contacter le support de l\'application.</string>
<string name="biometric_setting_summary">Déverrouiller l\'appli avec l\'empreinte digitale, l\'identification faciale, le code PIN, le motif et le mot de passe.</string>
<string name="biometric_prompt_description">Cet écran a été fermé en raison de plusieurs tentatives infructueuses. Veuillez relancer l\'application.</string>
<string name="battery_dialog_message">Pour garantir des téléchargements ininterrompus et des notifications pour les émissions de télévision auxquelles vous êtes abonné, CloudStream a besoin d\'une autorisation pour fonctionner en arrière-plan. En appuyant sur OK, vous serez dirigé vers App info. Faites défiler jusqu\'à 𝘼𝙥𝙥 𝙗𝙖𝙩𝙩𝙚𝙧𝙮 𝙪𝙨𝙖𝙜𝙚 et réglez l\'utilisation de la batterie sur 𝙐𝙣𝙧𝙚𝙨𝙩𝙧𝙞𝙘𝙩𝙚𝙙. Veuillez noter que cette autorisation ne signifie pas que CS3 épuisera votre batterie. Il ne fonctionnera en arrière-plan que lorsque cela sera nécessaire, par exemple lors de la réception de notifications ou du téléchargement de vidéos à partir d\'extensions officielles. Si vous choisissez d\'annuler, vous pouvez ajuster ce paramètre ultérieurement dans 𝙂𝙚𝙣𝙚𝙧𝙖𝙡 𝙎𝙚𝙩𝙩𝙞𝙣𝙜𝙨.</string>
<string name="app_unrestricted_toast">L\'utilisation de la batterie de l\'application est déjà réglée sur illimitée</string>
<string name="unfavorite">Supprimer des favoris</string>
<string name="custom_media_singluar">Média</string>
<string name="reset_btn">Réinitialiser</string>
<string name="episode_upcoming_format" formatted="true">À venir dans %s</string>
<string name="biometric_setting">Verrouillage biométrique</string>
<string name="player_settings_select_cast_device">Sélectionnez un appareil de diffusion</string>
<string name="next_season_episode_format" formatted="true">Saison %1$d Episode %2$d sera publié dans</string>
</resources>

View file

@ -192,4 +192,21 @@
<string name="links_reloaded_toast">लिंक पुन्ह खुली</string>
<string name="enter_current_pin">वर्तमान पिन दर्ज करें</string>
<string name="stream">नेटवर्क स्ट्रीम</string>
<string name="sort_clear">साफ़ करें</string>
<string name="subtitles_settings">उपशीर्षक सेटिंग्स</string>
<string name="subs_font_size">अक्षर का माप</string>
<string name="sort_close">बंद करें</string>
<string name="repo_copy_label">रिपॉजिटरी का नाम और यूआरएल</string>
<string name="toast_copied">कॉपी!</string>
<string name="sort_save">सहेजें</string>
<string name="subscribe_tooltip">नये एपिसोड की अधिसूचना</string>
<string name="result_search_tooltip">अन्य एक्सटेंशन में खोजें</string>
<string name="recommendations_tooltip">सुझाव दिखाएं</string>
<string name="subs_background_color">पृष्ठभूमि का रंग</string>
<string name="subs_edge_type">रूपरेखा प्रकार</string>
<string name="subs_text_color">अक्षर का रंग</string>
<string name="subs_window_color">बॉक्स का रंग</string>
<string name="subs_outline_color">रूपरेखा रंग</string>
<string name="subs_subtitle_elevation">उपशीर्षक ऊंचाई</string>
<string name="subs_font">अक्षर शैली</string>
</resources>

View file

@ -11,41 +11,41 @@
<string name="rew_text_regular_format" formatted="true" translatable="false">%d</string>
<string name="rating_format" formatted="true" translatable="false">%.1f/10.0</string>
<string name="year_format" formatted="true" translatable="false">%d</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s Ep %2$d</string>
<string name="cast_format" formatted="true">Cast: %s</string>
<string name="next_episode_format" formatted="true">Epizoda %d će izaći</string>
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dm</string>
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dm</string>
<string name="next_episode_time_min_format" formatted="true">%dm</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s epizoda %2$d</string>
<string name="cast_format" formatted="true">Glumačka postava: %s</string>
<string name="next_episode_format" formatted="true">Epizoda %d će izaći za</string>
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dmin</string>
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dmin</string>
<string name="next_episode_time_min_format" formatted="true">%dmin</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="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>
<string name="go_back_img_des">Go back</string>
<string name="home_change_provider_img_des">Change Provider</string>
<string name="preview_background_img_des">Preview Background</string>
<string name="episode_poster_img_des">Poster epizode</string>
<string name="home_main_poster_img_des">Glavni poster</string>
<string name="home_next_random_img_des">Sljedeće slučajno odabrano</string>
<string name="go_back_img_des">Idi natrag</string>
<string name="home_change_provider_img_des">Promijeni pružatelja usluge</string>
<string name="preview_background_img_des">Pregled slike pozadine</string>
<!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
<string name="player_speed_text_format" formatted="true">Brzina (%.2fx)</string>
<string name="player_speed_text_format" formatted="true">Brzina (%.2f×)</string>
<string name="rated_format" formatted="true">Ocjena: %.1f</string>
<string name="new_update_format" formatted="true">Pronađeno novo ažuriranje!
<string name="new_update_format" formatted="true">Pronađeno je novo ažuriranje!
\n%1$s -&gt; %2$s</string>
<string name="filler" formatted="true">Umetak</string>
<string name="duration_format" formatted="true">%d min</string>
<string name="app_name">CloudStream</string>
<string name="play_with_app_name">Reproduciraj s CloudStream-om</string>
<string name="play_with_app_name">Reproduciraj s CloudStreamom</string>
<string name="title_home">Početna stranica</string>
<string name="title_search">Pretraži</string>
<string name="title_search">Traži</string>
<string name="title_downloads">Preuzimanja</string>
<string name="title_settings">Postavke</string>
<string name="search_hint">Pretraži</string>
<string name="search_hint_site" formatted="true">Pretraži %s</string>
<string name="search_hint">Traži </string>
<string name="search_hint_site" formatted="true">Traži %s </string>
<string name="no_data">Nema podataka</string>
<string name="episode_more_options_des">Više postavki</string>
<string name="episode_more_options_des">Više opcija</string>
<string name="next_episode">Sljedeća epizoda</string>
<string name="result_tags">Žanrovi</string>
<string name="result_share">Podijeli</string>
<string name="result_share">Dijeli</string>
<string name="result_open_in_browser">Otvori u pregledniku</string>
<string name="skip_loading">Preskoči učitavanje</string>
<string name="loading">Učitavanje …</string>
@ -53,35 +53,35 @@
<string name="type_on_hold">Na čekanju</string>
<string name="type_completed">Dovršeno</string>
<string name="type_dropped">Ispušteno</string>
<string name="type_plan_to_watch">Planiram pogledati</string>
<string name="type_re_watching">Ponovno gledam</string>
<string name="play_movie_button">Pokreni Film</string>
<string name="type_plan_to_watch">Planiram gledati</string>
<string name="type_re_watching">Ponovo gledam</string>
<string name="play_movie_button">Pokreni film</string>
<string name="play_livestream_button">Pokreni LiveStream</string>
<string name="play_torrent_button">Pokreni Torrent</string>
<string name="pick_source">Izvori</string>
<string name="pick_subtitle">Titlovi</string>
<string name="reload_error">Ponovno pokušaj povezivanje</string>
<string name="reload_error">Ponovni pokušaj povezivanja </string>
<string name="go_back">Idi natrag</string>
<string name="play_episode">Pokreni Epizodu</string>
<string name="play_episode">Pokreni epizodu</string>
<!--<string name="need_storage">Allow to download episodes</string>-->
<string name="download">Preuzmi</string>
<string name="downloaded">Preuzeto</string>
<string name="downloading">Trenutno preuzimam</string>
<string name="downloading">Preuzimanje u tijeku</string>
<string name="download_paused">Preuzimanje pauzirano</string>
<string name="download_started">Preuzimanje započeto</string>
<string name="download_failed">Preuzimanje nije uspjelo</string>
<string name="download_failed">Preuzimanje neuspjelo</string>
<string name="download_canceled">Preuzimanje otkazano</string>
<string name="download_done">Preuzimanje dovršeno</string>
<string name="stream">Mrežni stream</string>
<string name="error_loading_links_toast">Pogreška pri učitavanju veza</string>
<string name="download_storage_text">Unutarnja pohrana</string>
<string name="app_dubbed_text">Dub</string>
<string name="app_subbed_text">Sub</string>
<string name="error_loading_links_toast">Pogreška pri učitavanju poveznica</string>
<string name="download_storage_text">Interna pohrana</string>
<string name="app_dubbed_text">Sinkronizacija</string>
<string name="app_subbed_text">Titlovi</string>
<string name="popup_delete_file">Izbriši datoteku</string>
<string name="popup_play_file">Otvori datoteku</string>
<string name="popup_resume_download">Nastavi preuzimanje</string>
<string name="popup_pause_download">Pauziraj preuzimanje</string>
<string name="pref_disable_acra">Onemogući automatsko izvješćivanje o bugovima</string>
<string name="pref_disable_acra">Onemogući automatsko izvješćivanje o greškama</string>
<string name="home_more_info">Više informacija</string>
<string name="home_expanded_hide">Sakrij</string>
<string name="home_play">Pokreni</string>
@ -93,99 +93,99 @@
<string name="sort_apply">Primijeni</string>
<string name="sort_copy">Kopiraj</string>
<string name="sort_close">Zatvori</string>
<string name="sort_clear">Očisti</string>
<string name="sort_clear">Izbriši</string>
<string name="sort_save">Spremi</string>
<string name="player_speed">Brzina playera</string>
<string name="subtitles_settings">Postavke titlova</string>
<string name="subs_text_color">Boja teksta</string>
<string name="subs_outline_color">Boja obruba</string>
<string name="subs_background_color">Pozadinska boja</string>
<string name="subs_outline_color">Boja konture</string>
<string name="subs_background_color">Boja pozadine</string>
<string name="subs_window_color">Boja prozora</string>
<string name="subs_edge_type">Tip ruba</string>
<string name="subs_edge_type">Vrsta ruba</string>
<string name="subs_subtitle_elevation">Visina titlova</string>
<string name="subs_font">Font</string>
<string name="subs_font_size">Veličina fonta</string>
<string name="search_provider_text_providers">Pretraži s uslugama</string>
<string name="search_provider_text_types">Pretraži s tipovima</string>
<string name="benene_count_text">%d banana dano developerima</string>
<string name="search_provider_text_providers">Traži koristeći pružatelje usluga</string>
<string name="search_provider_text_types">Traži koristeći vrste</string>
<string name="benene_count_text">%d banana dano programerima</string>
<string name="benene_count_text_none">Nisi dao ni jednu bananu</string>
<string name="subs_auto_select_language">Automatski odabir jezika</string>
<string name="subs_download_languages">Preuzmi jezike</string>
<string name="subs_subtitle_languages">Jezik titlova</string>
<string name="subs_hold_to_reset_to_default">Držite za vraćanje na zadane postavke</string>
<string name="subs_import_text" formatted="true">Uvezi fontove tako da ih postavite u %s</string>
<string name="continue_watching">Nastavite s gledanjem</string>
<string name="subs_hold_to_reset_to_default">Pritisni za vraćanje na zadane postavke</string>
<string name="subs_import_text" formatted="true">Uvezi fontove postavljanjem u %s</string>
<string name="continue_watching">Nastavi gledati</string>
<string name="action_remove_watching">Ukloni</string>
<string name="action_open_watching">Više informacija</string>
<string name="action_open_play">@string/home_play</string>
<string name="vpn_might_be_needed">Za ispravan rad ovog pružatelja usluga može biti potreban VPN</string>
<string name="vpn_might_be_needed">Za ispravan rad ovog pružatelja usluga je možda 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>
<string name="provider_info_meta">Stranica ne sadrži metapodatke. Učitavanje videa neće uspjeti ako ne postoje na stranici.</string>
<string name="torrent_plot">Opis</string>
<string name="normal_no_plot">Plot nije pronađen</string>
<string name="normal_no_plot">Radnja nije pronađena</string>
<string name="torrent_no_plot">Opis nije pronađen</string>
<string name="show_log_cat">Prikaži LogMačku 🐈</string>
<string name="picture_in_picture">Picture-in-picture</string>
<string name="picture_in_picture_des">Nastavlja reprodukciju u minijaturnom playeru povrh drugih aplikacija</string>
<string name="player_size_settings">Gumb za promjenu veličine playera</string>
<string name="player_size_settings_des">Uklaja crne rubove</string>
<string name="show_log_cat">Prikaži Logcat 🐈</string>
<string name="picture_in_picture">Slika u slici</string>
<string name="picture_in_picture_des">Nastavlja reprodukciju u minijaturnom playeru ispred drugih aplikacija</string>
<string name="player_size_settings">Gumb za mijenjenje veličine playera</string>
<string name="player_size_settings_des">Ukloni crne rubove</string>
<string name="player_subtitles_settings">Titlovi</string>
<string name="player_subtitles_settings_des">Postavke titlova playera</string>
<string name="chromecast_subtitles_settings">Chromecast Titlovi</string>
<string name="chromecast_subtitles_settings">Chromecast titlovi</string>
<string name="chromecast_subtitles_settings_des">Postavke Chromecast titlova</string>
<string name="eigengraumode_settings">Brzina reprodukcije</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_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="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="swipe_to_seek_settings">Klizni prstom za pomicanje</string>
<string name="swipe_to_seek_settings_des">Klizni prstom ulijevo ili udesno za postavljanje pozicije videa</string>
<string name="swipe_to_change_settings">Klizni prstom za mijenjanje postavki</string>
<string name="swipe_to_change_settings_des">Klizni prstom prema gore ili dolje na lijevoj ili desnoj strani za mijenjanje svjetline ili glasnoće</string>
<string name="autoplay_next_settings">Automatski pokreni sljedeću epizodu</string>
<string name="autoplay_next_settings_des">Pokreni sljedeću epizodu kada trenutačna epizoda završi</string>
<string name="double_tap_to_seek_settings">Dodirni dvaput za pomicanje</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_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="use_system_brightness_settings">Koristi svijetlinu u sustavu</string>
<string name="double_tap_to_seek_amount_settings">Količina pomicanja u playeru (sekunde)</string>
<string name="double_tap_to_seek_settings_des">Dodirni dvaput desnu ili lijevu stranu za pomicanje prema naprijed ili natrag</string>
<string name="double_tap_to_pause_settings_des">Dodirni dvaput u sredinu za pauziranje</string>
<string name="use_system_brightness_settings">Koristi svijetlinu sustava</string>
<string name="use_system_brightness_settings_des">Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog preklopa</string>
<string name="episode_sync_settings">Ažuriraj napredak gledanja</string>
<string name="episode_sync_settings_des">Automatski sinkronizira vaš trenutni napredak u filmu ili epizodi</string>
<string name="restore_settings">Vraćanje podataka iz sigurnosne kopije</string>
<string name="restore_settings">Obnovi podatke iz sigurnosne kopije</string>
<string name="backup_settings">Sigurnosno kopiranje podataka</string>
<string name="restore_success">Učitana datoteka sigurnosne kopije</string>
<string name="restore_failed_format" formatted="true">Vraćanje podataka iz datoteke nije uspjelo %s</string>
<string name="restore_success">Datoteka sigurnosne kopije je učitana</string>
<string name="restore_failed_format" formatted="true">Obnavljanje podataka iz datoteke %s nije uspjelo</string>
<string name="backup_success">Podaci pohranjeni</string>
<string name="backup_failed">Nedostaju dozvole za pohranu, pokušaj ponovo.</string>
<string name="backup_failed_error_format">Pogreška pri sigurnosnom kopiranju %s</string>
<string name="search">Pretraži</string>
<string name="search">Traži</string>
<string name="category_account">Računi i sigurnost</string>
<string name="category_updates">Ažuriranja i sigurnosne kopije</string>
<string name="category_updates">Ažuriranja i sigurnosna kopija</string>
<string name="settings_info">Informacije</string>
<string name="advanced_search">Napredno pretraživanje</string>
<string name="advanced_search_des">Daje rezultate pretraživanja odvojene prema pružatelju usluga</string>
<string name="advanced_search">Napredna pretraga</string>
<string name="advanced_search_des">Daje rezultate pretrage odvojene prema pružatelju usluga</string>
<string name="bug_report_settings_off">Šalje samo podatke o padovima aplikacije</string>
<string name="bug_report_settings_on">Ne šalje podatke</string>
<string name="show_fillers_settings">Prikaži dodatnu epizodu za anime</string>
<string name="show_trailers_settings">Prikaži trailere</string>
<string name="kitsu_settings">Prikaži postere iz Kitsua</string>
<string name="pref_filter_search_quality">Sakrij odabranu kvalitetu videozapisa u rezultatima pretraživanja</string>
<string name="pref_filter_search_quality">Sakrij odabranu kvalitetu videa u rezultatima pretrage</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="uprereleases_settings">Ažuriranje na predizdanja</string>
<string name="uprereleases_settings_des">Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja</string>
<string name="uprereleases_settings_des">Tražite ažuriranja predizdanja umjesto samo potpunih izdanja</string>
<string name="github">Github</string>
<string name="lightnovel">Aplikacija za romane od istih developera</string>
<string name="anim">Anime aplikacija od istih developera</string>
<string name="discord">Uđi u naš Discord</string>
<string name="benene">Daj bananu developerima</string>
<string name="benene_des">Dana banana</string>
<string name="lightnovel">Aplikacija za romane od istih programera</string>
<string name="anim">Anime aplikacija od istih programera</string>
<string name="discord">Pridruži se Discordu</string>
<string name="benene">Daj bananu programerima</string>
<string name="benene_des">Dane banane</string>
<string name="app_language">Jezik aplikacije</string>
<string name="no_chromecast_support_toast">Ovaj pružatelj usluga nema podršku za Chromecast</string>
<string name="no_links_found_toast">Nisu pronađene veze</string>
<string name="copy_link_toast">Veza je kopirana u međuspremnik</string>
<string name="no_links_found_toast">Nisu pronađene poveznice</string>
<string name="copy_link_toast">Poveznica je kopirana u međuspremnik</string>
<string name="play_episode_toast">Pokreni epizodu</string>
<string name="subs_default_reset_toast">Vrati na zadanu vrijednost</string>
<string name="acra_report_toast">Nažalost, aplikacija se srušila. Anonimno izvješće o bugu bit će poslano developerima</string>
<string name="acra_report_toast">Nažalost se aplikacija srušila. Anonimno izvješće o grešci će se poslati programerima</string>
<string name="season">Sezona</string>
<string name="no_season">Nema sezone</string>
<string name="episode">Epizoda</string>
@ -197,14 +197,14 @@
<string name="no_episodes_found">Nisu pronađene epizode</string>
<string name="delete_file">Izbriši datoteku</string>
<string name="delete">Izbriši</string>
<string name="cancel">Poništi</string>
<string name="cancel">Odustani</string>
<string name="pause">Pauziraj</string>
<string name="resume">Nastavi</string>
<string name="go_back_30">-30</string>
<string name="go_back_30">30</string>
<string name="go_forward_30">+30</string>
<string name="delete_message" formatted="true">Ovo će trajno izbrisati %s
\nJeste li sigurni\?</string>
<string name="resume_time_left" formatted="true">%dm
<string name="resume_time_left" formatted="true">%dmin
\npreostalo</string>
<string name="status_ongoing">U tijeku</string>
<string name="status_completed">Završeno</string>
@ -244,11 +244,11 @@
<string name="live_singular">Livestream</string>
<string name="nsfw_singular">NSFW</string>
<string name="other_singular">Video</string>
<string name="source_error">Greška u izvoru</string>
<string name="remote_error">Pogreška remote-a</string>
<string name="render_error">Pogreška renderera</string>
<string name="source_error">Pogreška u izvoru</string>
<string name="remote_error">Pogreška eksternog računala</string>
<string name="render_error">Pogreška u prikazu</string>
<string name="unexpected_error">Neočekivana pogreška playera</string>
<string name="storage_error">Pogreška preuzimanja, provjeri dozvole za pohranu</string>
<string name="storage_error">Pogreška tijekom preuzimanja, provjeri dozvole za pohranu</string>
<string name="episode_action_chromecast_episode">Chromecast epizoda</string>
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
<string name="episode_action_play_in_app">Pokreni u aplikaciji</string>
@ -257,11 +257,11 @@
<string name="episode_action_copy_link">Kopiraj poveznicu</string>
<string name="episode_action_auto_download">Automatsko preuzimanje</string>
<string name="episode_action_download_mirror">Preuzmi zrcalo</string>
<string name="episode_action_reload_links">Ponovno učitaj poveznice</string>
<string name="episode_action_reload_links">Ponovo učitaj poveznice</string>
<string name="episode_action_download_subtitle">Preuzmi titlove</string>
<string name="show_hd">Oznaka kvalitete</string>
<string name="show_dub">Oznaka sinkronizacije</string>
<string name="show_sub">Oznaka titlova</string>
<string name="show_hd">Oznaka za kvalitetu</string>
<string name="show_dub">Oznaka za sinkronizaciju</string>
<string name="show_sub">Oznaka za titlove</string>
<string name="show_title">Naslov</string>
<string name="poster_ui_settings">Uključi/isključi elemente korisničkog sučelja na posteru</string>
<string name="no_update_found">Nije pronađeno ažuriranje</string>
@ -270,38 +270,38 @@
<string name="video_aspect_ratio_resize">Promijeni veličinu</string>
<string name="video_source">Izvor</string>
<string name="video_skip_op">Preskoči OP</string>
<string name="dont_show_again">Ne prikazuj više</string>
<string name="dont_show_again">Nemoj više prikazivati</string>
<string name="skip_update">Preskoči ovo ažuriranje</string>
<string name="update">Ažuriraj</string>
<string name="watch_quality_pref">Preferirana kvaliteta streama</string>
<string name="watch_quality_pref">Preferirana kvaliteta gledanja (WiFi)</string>
<string name="limit_title">Maksimalni broj znakova u naslovu video playera</string>
<string name="limit_title_rez">Rezolucija video playera</string>
<string name="video_buffer_size_settings">Veličina video međuspremnika</string>
<string name="video_buffer_length_settings">Duljina video međuspremnika</string>
<string name="video_buffer_disk_settings">Video predmemorija na disku</string>
<string name="video_buffer_clear_settings">Očisti predmemoriju videa i slika</string>
<string name="video_ram_description">Izazvat će nasumična rušenja ako se postavi previsoko. Nemojte mijenjati ako imate malu količinu RAM-a kao što je Android TV ili stari telefon.</string>
<string name="video_disk_description">Može uzrokovati probleme na sustavima s malo prostora za pohranu kao što su Android TV uređaji ako postavite previsoko.</string>
<string name="video_buffer_size_settings">Veličina međuspremnika videa</string>
<string name="video_buffer_length_settings">Duljina međuspremnika videa</string>
<string name="video_buffer_disk_settings">Predmemorija videa na disku</string>
<string name="video_buffer_clear_settings">Izbriši predmemoriju videa i slika</string>
<string name="video_ram_description">Uzrokuje rušenje aplikacije ako se postavi previsoko na uređajima s malom količinom RAM-a kao što je Android TV.</string>
<string name="video_disk_description">Uzrokuje probleme ako se postavi previsoko na uređajima s malom količinom memorije kao što je Android TV.</string>
<string name="dns_pref">DNS preko HTTPS-a</string>
<string name="dns_pref_summary">Korisno za zaobilaženje blokada ISP-a</string>
<string name="add_site_pref">Kloniraj web stranicu</string>
<string name="remove_site_pref">Ukloni web stranicu</string>
<string name="add_site_summary">Dodajte klon postojeće web-lokacije s drugim url-om</string>
<string name="download_path_pref">Put preuzimanja</string>
<string name="download_path_pref">Putanja preuzimanja</string>
<string name="nginx_url_pref">NGINX server URL</string>
<string name="display_subbed_dubbed_settings">Prikaži sinkronizirani anime ili s titlovima</string>
<string name="resize_fit">Prilagodi zaslonu</string>
<string name="resize_fit">Prilagodi veličini ekrana</string>
<string name="resize_fill">Rastegni</string>
<string name="resize_zoom">Zoom</string>
<string name="legal_notice">Obavijest</string>
<string name="legal_notice">Pravna obavijest</string>
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
<string name="category_general">Općenito</string>
<string name="random_button_settings">Random gumb</string>
<string name="random_button_settings_desc">Prikaži gumb za slučajni odabir reprodukcija na početnoj stranici i biblioteci</string>
<string name="random_button_settings">Gumb za slučajni odabir</string>
<string name="random_button_settings_desc">Prikaži gumb za slučajni odabir na početnoj stranici i biblioteci</string>
<string name="provider_lang_settings">Jezici proširenja</string>
<string name="app_layout">Izgled aplikacije</string>
<string name="preferred_media_settings">Preferirani mediji</string>
<string name="enable_nsfw_on_providers">Omogućava NSFW na podržanim proširenjima</string>
<string name="enable_nsfw_on_providers">Omogući NSFW na podržanim proširenjima</string>
<string name="subtitles_encoding">Kodiranje titlova</string>
<string name="category_providers">Pružatelji usluga</string>
<string name="category_ui">Raspored</string>
@ -320,60 +320,60 @@
<string name="example_ip">127.0.0.1</string>
<string name="example_site_name">NovoImeStranice</string>
<string name="example_site_url">https://primjer.com</string>
<string name="example_lang_name">Šifra jezika (en)</string>
<string name="example_lang_name">Šifra jezika (hr)</string>
<string name="login_format" formatted="true">%1$s %2$s</string>
<string name="account">račun</string>
<string name="logout">Odjava</string>
<string name="login">Prijava</string>
<string name="switch_account">Promijeni račun</string>
<string name="add_account">Dodaj račun</string>
<string name="create_account">Napravi račun</string>
<string name="add_sync">Dodaj tracking</string>
<string name="create_account">Stvori račun</string>
<string name="add_sync">Dodaj praćenje</string>
<string name="added_sync_format" formatted="true">Dodano %s</string>
<string name="upload_sync">Sinkroniziraj</string>
<string name="sync_score">Ocijenjeno</string>
<string name="sync_score_format" formatted="true">%d / 10</string>
<string name="sync_total_episodes_none">/\?\?</string>
<string name="sync_total_episodes_some" formatted="true">/%d</string>
<string name="authenticated_user" formatted="true">Ovjereno%s</string>
<string name="authenticated_user" formatted="true">%s ovjeren</string>
<string name="authenticated_user_fail" formatted="true">Nije moguće prijaviti se na %s</string>
<!-- ============ -->
<string name="none">Nijedan</string>
<string name="normal">Normal</string>
<string name="normal">Normalno</string>
<string name="all">Sve</string>
<string name="max">Maksimalno</string>
<string name="min">Minimalno</string>
<string name="subtitles_outline">Obrub</string>
<string name="subtitles_depressed">Depresivno</string>
<string name="max">Maks.</string>
<string name="min">Min.</string>
<string name="subtitles_outline">Kontura</string>
<string name="subtitles_depressed">Udubljeno</string>
<string name="subtitles_shadow">Sjena</string>
<string name="subtitles_raised">Podignuto</string>
<string name="subtitles_raised">Izdignuto</string>
<string name="subtitle_offset">Sinkroniziraj titlove</string>
<string name="subtitle_offset_hint">1000 ms</string>
<string name="subtitle_offset_title">Kašnjenje titlova</string>
<string name="subtitle_offset_extra_hint_later_format">Koristi ovo ako su titlovi prikazani %d ms prerano</string>
<string name="subtitle_offset_extra_hint_later_format">Koristi ovo ako se titlovi prikazuju %d ms prerano</string>
<string name="subtitle_offset_extra_hint_before_format">Koristite ovo ako se titlovi prikazuju %d ms prekasno</string>
<string name="subtitle_offset_extra_hint_none_format">Nema kašnjenja titlova</string>
<string name="subtitle_offset_extra_hint_none_format">Bez kašnjenja titlova</string>
<!--
Example text (pangram) can optionally be translated; if you do, include all the letters in the alphabet,
see:
https://en.wikipedia.org/w/index.php?title=Pangram&oldid=225849300
https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog
-->
<string name="subtitles_example_text">The quick brown fox jumps over the lazy dog</string>
<string name="subtitles_example_text">Gojazni đačić s biciklom drži hmelj i finu vatu u džepu nošnje</string>
<string name="recommended">Preporučeno</string>
<string name="player_loaded_subtitles" formatted="true">Učitano %s</string>
<string name="player_load_subtitles">Učitaj datoteku titlova</string>
<string name="player_load_subtitles_online">Učitaj sa interneta</string>
<string name="player_load_subtitles">Učitaj iz datoteke</string>
<string name="player_load_subtitles_online">Učitaj s interneta</string>
<string name="downloaded_file">Preuzeta datoteka</string>
<string name="actor_main">Glavno</string>
<string name="actor_supporting">Podupiranje</string>
<string name="actor_background">Pozadina</string>
<string name="actor_main">Glavni</string>
<string name="actor_supporting">Sporedni</string>
<string name="actor_background">Statist</string>
<string name="home_source">Izvor</string>
<string name="home_random">Random</string>
<string name="coming_soon">Dolazi uskoro…</string>
<string name="quality_cam">Cam</string>
<string name="quality_cam_rip">Cam</string>
<string name="quality_cam_hd">Cam</string>
<string name="home_random">Slučajno</string>
<string name="coming_soon">Dolazi uskoro </string>
<string name="quality_cam">Kamera</string>
<string name="quality_cam_rip">Kamera</string>
<string name="quality_cam_hd">Kamera</string>
<string name="quality_hq">HQ</string>
<string name="quality_hd">HD</string>
<string name="quality_ts">TS</string>
@ -392,59 +392,59 @@
<string name="resolution_and_title">Rezolucija i naslov</string>
<string name="title">Naslov</string>
<string name="resolution">Rezolucija</string>
<string name="error_invalid_id">ID je nevažeći</string>
<string name="error_invalid_id">Nevažeći ID</string>
<string name="error_invalid_data">Nevažeći podaci</string>
<string name="error_invalid_url">URL je nevažeći</string>
<string name="error">Greška</string>
<string name="subtitles_remove_captions">Ukloni CC iz titlova</string>
<string name="subtitles_remove_bloat">Ukloni reklame iz titlova</string>
<string name="subtitles_filter_lang">Filtriraj po željenom jeziku medija</string>
<string name="extras">Extras</string>
<string name="error_invalid_url">Nevažeći URL</string>
<string name="error">Pogreška</string>
<string name="subtitles_remove_captions">Ukloni titlove za gluhe osobe iz titlova</string>
<string name="subtitles_remove_bloat">Ukloni nepotrebne elemente iz titlova (npr. oglase)</string>
<string name="subtitles_filter_lang">Filtriraj po preferiranom jeziku medija</string>
<string name="extras">Dodatni sadržaji</string>
<string name="trailer">Trailer</string>
<string name="network_adress_example">https://primjer.com/primjer.mp4</string>
<string name="referer">Referent (nije obavezno)</string>
<string name="referer">Referent (opcionalno)</string>
<string name="next">Sljedeće</string>
<string name="provider_languages_tip">Gledaj videozapise na ovim jezicima</string>
<string name="provider_languages_tip">Gledaj videa na ovim jezicima</string>
<string name="previous">Prethodno</string>
<string name="skip_setup">Preskoči postavljanje</string>
<string name="app_layout_subtext">Promijeni izgled aplikacije kako bi odgovarao vašem uređaju</string>
<string name="app_layout_subtext">Promijeni izgled aplikacije kako bi odgovarao tvom uređaju</string>
<string name="crash_reporting_title">Izvještavanje o rušenju</string>
<string name="preferred_media_subtext">Što želite vidjeti</string>
<string name="preferred_media_subtext">Što želiš vidjeti</string>
<string name="setup_done">Gotovo</string>
<string name="extensions">Ekstenzije</string>
<string name="add_repository">Dodaj repository</string>
<string name="repository_name_hint">Ime repositorya</string>
<string name="repository_url_hint">URL spremišta (repositorija)</string>
<string name="extensions">Proširenja</string>
<string name="add_repository">Dodaj repozitorij</string>
<string name="repository_name_hint">Ime repozitorija</string>
<string name="repository_url_hint">URL repozitorija</string>
<string name="plugin_loaded">Dodatak učitan</string>
<string name="plugin_deleted">Dodatak izbrisan</string>
<string name="plugin_load_fail" formatted="true">Nije moguće učitati %s</string>
<string name="is_adult">18+</string>
<string name="batch_download_start_format" formatted="true">Započeto preuzimanje %1$d %2$s…</string>
<string name="batch_download_start_format" formatted="true">Započeto preuzimanje %1$d %2$s </string>
<string name="batch_download_finish_format" formatted="true">Preuzeto %1$d %2$s</string>
<string name="batch_download_nothing_to_download_format" formatted="true">Sve %s je već preuzeto</string>
<string name="batch_download_nothing_to_download_format" formatted="true">Sve %s već preuzeto</string>
<string name="batch_download">Skupno preuzimanje</string>
<string name="plugin_singular">dodatak</string>
<string name="plugin">dodaci</string>
<string name="delete_repository_plugins">Ovo će također izbrisati sve dodatke repozitorija</string>
<string name="delete_repository">Izbriši repository</string>
<string name="setup_extensions_subtext">Preuzmi popis stranica koje želite koristiti</string>
<string name="delete_repository">Izbriši repozitorij</string>
<string name="setup_extensions_subtext">Preuzmi popis stranica koje želiš koristiti</string>
<string name="plugins_downloaded" formatted="true">Preuzeto: %d</string>
<string name="plugins_disabled" formatted="true">Onemogućeno: %d</string>
<string name="plugins_not_downloaded" formatted="true">Nepreuzeto: %d</string>
<string name="blank_repo_message">CloudStream nema instalirane web stranice prema zadanim postavkama. Morate instalirati stranice iz repozitorija.
<string name="blank_repo_message">CloudStream standardno nema instalirane web stranice. Stranice morate instalirati iz repozitorija.
\n
\nPridružite se našem Discordu ili tražite online.</string>
<string name="view_public_repositories_button">Pregledajte repozitorije zajednice</string>
<string name="view_public_repositories_button">Prikaži repozitorije zajednice</string>
<string name="view_public_repositories_button_short">Javni popis</string>
<string name="uppercase_all_subtitles">Svi titlovi pisani velikim slovima</string>
<string name="download_all_plugins_from_repo">Preuzeti sve dodatke iz ovog repozitorija\?</string>
<string name="single_plugin_disabled" formatted="true">%s (Onemogućeno)</string>
<string name="tracks">Zapis</string>
<string name="uppercase_all_subtitles">Koristi velika slova za sve titlove</string>
<string name="download_all_plugins_from_repo">Preuzeti sve dodatke iz ovog repozitorija?</string>
<string name="single_plugin_disabled" formatted="true">%s (onemogućeno)</string>
<string name="tracks">Zapisi</string>
<string name="audio_tracks">Audio zapis</string>
<string name="video_tracks">Video zapis</string>
<string name="apply_on_restart">Primjenjuje se na ponovnom pokretanju</string>
<string name="safe_mode_title">Sigurnosni način rada omogućen</string>
<string name="safe_mode_description">Sve su ekstenzije isključene zbog rušenja aplikacije kako biste lakše pronašli ono koje uzrokuje probleme.</string>
<string name="apply_on_restart">Za prikaz promjena ponovo pokreni aplikaciju.</string>
<string name="safe_mode_title">Sigurnosni način rada uključen</string>
<string name="safe_mode_description">Sva proširenja su isključena zbog rušenja aplikacije kako bi se pronašlo proširenje koje uzrokuje probleme.</string>
<string name="safe_mode_crash_info">Pogledajte podatke o padu</string>
<string name="extension_rating" formatted="true">Ocjena: %s</string>
<string name="extension_description">Opis</string>
@ -454,38 +454,38 @@
<string name="extension_authors">Autori</string>
<string name="extension_types">Podržano</string>
<string name="extension_language">Jezik</string>
<string name="hls_playlist">HLS Playlista</string>
<string name="hls_playlist">HLS playlista</string>
<string name="automatic_plugin_download">Automatski instaliraj dodatke</string>
<string name="skip_type_creddits">Zasluge</string>
<string name="automatic_plugin_download_summary">Automatski instaliraj sve neinstalirane dodatke iz dodanih repozitorija.</string>
<string name="player_pref">Preferirani video player</string>
<string name="player_settings_play_in_app">Interni player</string>
<string name="extension_install_first">Prvo instalirajte ekstenziju</string>
<string name="extension_install_first">Najprije instaliraj proširenje</string>
<string name="player_settings_play_in_vlc">VLC</string>
<string name="player_settings_play_in_mpv">MPV</string>
<string name="player_settings_play_in_web">Web Video Cast</string>
<string name="player_settings_play_in_web">Emitiranje na webu</string>
<string name="app_not_found_error">Aplikacija nije pronađena</string>
<string name="all_languages_preference">Svi jezici</string>
<string name="clipboard_too_large">Previše teksta. Nije moguće spremiti u međuspremnik.</string>
<string name="action_mark_as_watched">Označi kao gledano</string>
<string name="enable_skip_op_from_database_des">Prikazuje skočni prozor za preskakanje početka ili završetka medija</string>
<string name="enable_skip_op_from_database_des">Prikaži skočni prozor za uvod/kraj</string>
<string name="yes">Da</string>
<string name="update_notification_downloading">Preuzimanje ažuriranja aplikacije…</string>
<string name="update_notification_downloading">Preuzimanje ažuriranja aplikacije </string>
<string name="confirm_exit_dialog">Jeste li sigurni da želite izaći\?</string>
<string name="no">Ne</string>
<string name="update_notification_installing">Instaliranje ažuriranja aplikacije…</string>
<string name="update_notification_installing">Instaliranje ažuriranja aplikacije </string>
<string name="update_notification_failed">Nije moguće instalirati novu verziju aplikacije</string>
<string name="plugins_updated" formatted="true">Ažurirano %d dodataka</string>
<string name="skip_type_mixed_op">Mješoviti početak</string>
<string name="plugins_updated" formatted="true">Ažurirani dodaci: %d</string>
<string name="skip_type_mixed_op">Mješoviti uvod</string>
<string name="skip_type_intro">Uvod</string>
<string name="pref_category_links">Linkovi</string>
<string name="play_trailer_button">Pokreni Trailer</string>
<string name="pref_category_links">Poveznice</string>
<string name="play_trailer_button">Pokreni trailer</string>
<string name="redo_setup_process">Ponovi postupak postavljanja</string>
<string name="apk_installer_settings_des">Neki telefoni ne podržavaju novi program za instaliranje paketa. Isprobaj naslijeđenu opciju ako se ažuriranja ne instaliraju.</string>
<string name="apk_installer_settings_des">Neki telefoni ne podržavaju novi program za instaliranje paketa. Pokušaj sa starijom opcijom ako se ažuriranja ne instaliraju.</string>
<string name="apk_installer_settings">Instalator APK-a</string>
<string name="pref_category_app_updates">Ažuriranja aplikacije</string>
<string name="pref_category_backup">Sigurnosna kopija</string>
<string name="pref_category_extensions">Ekstenzije</string>
<string name="pref_category_extensions">Proširenja</string>
<string name="pref_category_actions">Radnje</string>
<string name="pref_category_cache">Predmemorija</string>
<string name="pref_category_gestures">Geste</string>
@ -493,21 +493,21 @@
<string name="pref_category_subtitles">Titlovi</string>
<string name="pref_category_player_layout">Raspored</string>
<string name="pref_category_defaults">Zadane postavke</string>
<string name="pref_category_looks">Izgled</string>
<string name="pref_category_looks">Izgledi</string>
<string name="pref_category_ui_features">Značajke</string>
<string name="player_settings_play_in_browser">Web preglednik</string>
<string name="skip_type_format" formatted="true">Preskoči %s</string>
<string name="skip_type_ed">Završetak</string>
<string name="skip_type_recap">Zaključak</string>
<string name="skip_type_mixed_ed">Mješoviti završetak</string>
<string name="clear_history">Obriši povijest</string>
<string name="skip_type_ed">Kraj</string>
<string name="skip_type_recap">Sažetak</string>
<string name="skip_type_mixed_ed">Mješoviti kraj</string>
<string name="clear_history">Izbriši povijest</string>
<string name="history">Povijest</string>
<string name="apk_installer_legacy">Legacy</string>
<string name="skip_type_op">Otvaranje</string>
<string name="skip_type_op">Uvod</string>
<string name="apk_installer_package_installer">PackageInstaller</string>
<string name="season_format">%1$s %2$d%3$s</string>
<string name="update_started">Aktualiziranje započeto</string>
<string name="delayed_update_notice">Program če se aktualizirati tijekom zatvaranja programa</string>
<string name="delayed_update_notice">Aplikacija će se aktualizirati tijekom zatvaranja</string>
<string name="plugin_downloaded">Dodatak preuzet</string>
<string name="action_remove_from_watched">Ukloni iz pogledanog</string>
<string name="browser">Preglednik</string>
@ -525,19 +525,19 @@
<string name="empty_library_no_accounts_message">Vaša je biblioteka prazna :(
\nPrijavite se na račun biblioteke ili dodajte filmove / serije u svoju lokalnu biblioteku.</string>
<string name="empty_library_logged_in_message">Ova je lista prazna. Pokušajte se prebaciti na jednu drugu listu.</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="safe_mode_file">Pronađena je datoteka sigurnog načina rada!
\nProširenja se ne učitavaju tijekom pokretanja dok se datoteka ne ukloni.</string>
<string name="android_tv_interface_on_seek_settings">Prikazan player Količina pomicanja</string>
<string name="android_tv_interface_on_seek_settings_summary">Količina pomicanja koja se koristi kada je player vidljiv</string>
<string name="android_tv_interface_off_seek_settings">Player skriven Količina pomicanja</string>
<string name="android_tv_interface_off_seek_settings_summary">Količina pomicanja 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_passed">Uspjelo</string>
<string name="restart">Pokreni ponovo</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="start">Pokreni</string>
<string name="test_failed">Neuspjelo</string>
<string name="stop">Prekini</string>
<string name="category_provider_test">Test pružatelja usluga</string>
<string name="subscription_in_progress_notification">Ažuriranje pretplaćenih emisija</string>
<string name="subscription_episode_released">Epizoda %d izbačena!</string>
@ -549,7 +549,7 @@
<string name="jsdelivr_proxy">GitHub Proxy</string>
<string name="jsdelivr_enabled">Neuspješno dohvaćanje GitHuba. Uključuje se jsdelivr proxy …</string>
<string name="jsdelivr_proxy_summary">Zaobilazi blokiranje neobrađenih GitHub URL-ova koristeći jsDelivr. Može uzrokovati kašnjenje ažuriranja nekoliko dana.</string>
<string name="watch_quality_pref_data">Preferirana kvaliteta gledanja (podatkovna mobilna mreža)</string>
<string name="watch_quality_pref_data">Preferirana kvaliteta gledanja (mobilni podaci)</string>
<string name="profile_number">Profil %d</string>
<string name="wifi">Wi-Fi</string>
<string name="mobile_data">Mobilni podaci</string>
@ -560,20 +560,20 @@
<string name="help">Pomoć</string>
<string name="qualities">Kvalitete</string>
<string name="profile_background_des">Pozadina profila</string>
<string name="unable_to_inflate">Nije bilo moguće ispravno izraditi korisničko sučelje. Ovo je ZNAČAJNA GREŠKA i treba se odmah prijaviti %s</string>
<string name="unable_to_inflate">Nije bilo moguće ispravno izraditi korisničko sučelje. Ovo je ZNAČAJNA POGREŠKA i treba se odmah prijaviti %s</string>
<string name="automatic_plugin_download_mode_title">Odaberi modus za filtriranje preuzimanja dodataka</string>
<string name="disable">Onemogući</string>
<string name="no_plugins_found_error">U repozitoriju nisu pronađeni dodaci</string>
<string name="no_repository_found_error">Repozitorij nije pronađen, provjerite URL i pokušajte koristiti VPN</string>
<string name="quality_profile_help">Ovdje možete promijeniti način na koji su izvori poredani. Ako video ima viši prioritet, pojavit će se više u odabiru izvora. Zbroj prioriteta izvora i prioriteta kvalitete je video prioritet.
<string name="no_repository_found_error">Repozitorij nije pronađen. Provjeri URL i pokušaj VPN</string>
<string name="quality_profile_help">Ovdje možete promijeniti način na koji su izvori poredani. Ako video ima viši prioritet, pojavit će se više u odabiru izvora. Zbroj prioriteta izvora i prioriteta kvalitete je prioritet videa.
\n
\nIzvor A: 3
\nKvaliteta B: 7
\nImat će kombinirani prioritet videozapisa od 10.
\nImat će kombinirani prioritet videa od 10.
\n
\nNAPOMENA: Ako je zbroj 10 ili više, video player će automatski preskočiti učitavanje kada se ta poveznica učita!</string>
<string name="already_voted">Već si glasao/la</string>
<string name="backup_frequency">Učestalost rezervne kopije</string>
<string name="backup_frequency">Učestalost spremanja sigurnosne kopije</string>
<string name="favorite_removed">%s uklonjeno iz favorita</string>
<string name="favorites_list_name">Favoriti</string>
<string name="favorite_added">%s dodano u favorite</string>
@ -606,7 +606,7 @@
<string name="skip_startup_account_select_pref">Preskoči odabir računa pri pokretanju</string>
<string name="manage_accounts">Upravljanje računima</string>
<string name="edit_account">Uredi račun</string>
<string name="links_reloaded_toast">Linkovi ponovno učitani</string>
<string name="links_reloaded_toast">Poveznice su ponovo učitane</string>
<string name="rotate_video">Rotiraj</string>
<string name="rotate_video_desc">Prikaži gumb za prebacivanje orijentacije zaslona</string>
<string name="auto_rotate_video_desc">Omogućuje automatsko mijenjanje orijentacije zaslona na temelju orijentacije videa</string>
@ -614,8 +614,8 @@
<string name="rotate_video_key">rotiraj_video_tipka</string>
<string name="auto_rotate_video_key">automatski_rotiraj_video_tipka</string>
<string name="subscribe_tooltip">Obavijest za novu epizodu</string>
<string name="result_search_tooltip">Pretraži u ostalim proširenjima</string>
<string name="speed_setting_summary">Dodaje opciju brzine u playeru</string>
<string name="result_search_tooltip">Traži u drugim proširenjima</string>
<string name="speed_setting_summary">Dodaje opciju za brzinu u playeru</string>
<string name="test_extensions">Testiraj sva proširenja</string>
<string name="test_extensions_summary">Ovaj je test namijenjen samo programerima i ne provjerava niti negira rad bilo kojeg proširenja.</string>
<string name="recommendations_tooltip">Prikaži preporuke</string>
@ -624,9 +624,31 @@
<string name="biometric_setting">Zaključaj s biometrijskim podatcima</string>
<string name="resume_remaining" formatted="true">%s
\npreostalo</string>
<string name="clipboard_permission_error">Greška u pristupanju međuspremnika. Pokušaj ponovo.</string>
<string name="clipboard_permission_error">Pogreška pri pristupanju međuspremnika. Pokušaj ponovo.</string>
<string name="biometric_authentication_title">Otključaj CloudStream</string>
<string name="password_pin_authentication_title">Lozinka/PIN autentifikacija</string>
<string name="biometric_unsupported">Ovaj uređaj ne podržava biometrijsku autentifikaciju</string>
<string name="biometric_prompt_description">Ovaj je ekran zatvoren zbog višestrukih neuspjelih pokušaja. Pokrenite aplikaciju ponovo.</string>
<string name="ok">U redu</string>
<string name="battery_dialog_title">Deaktiviraj optimizaciju baterije</string>
<string name="audio_book_singular">Audio knjiga</string>
<string name="custom_media_singluar">Medij</string>
<string name="app_unrestricted_toast">Korištenje baterije aplikacije već je postavljeno na neograničeno</string>
<string name="app_info_intent_error">Neuspjelo otvaranje podataka CloudStream aplikacije.</string>
<string name="favorite">Favorit</string>
<string name="unfavorite">Ukloni iz favorita</string>
<string name="music_singlar">Glazba</string>
<string name="reset_btn">Obnovi</string>
<string name="biometric_setting_summary">Otključaj aplikaciju pomoću otiska prsta, ID-a lica, PIN-a, uzorka i lozinke.</string>
<string name="episode_upcoming_format" formatted="true">Sljedeća u %s</string>
<string name="clipboard_unknown_error">Pogreška pri kopiranju. Kopirajte zapisnik i kontaktirajte podršku aplikacije.</string>
<string name="battery_dialog_message">Kako bi se osigurala neometana preuzimanja i obavijesti za pretplaćene TV emisije, CloudStream treba dopuštenje za rad u pozadini. Pritiskom gumba „U redu” bit ćete preusmjereni na informacije o aplikaciji. Tamo odaberite „Korištenje baterije aplikacije” i postavite potrošnju baterije na „Neograničeno”. Imajte na umu da ovo dopuštenje ne znači da će CS3 isprazniti vašu bateriju. Radit će u pozadini samo kada je potrebno, kao što je primanje obavijesti ili preuzimanje videa sa službenih proširenja. Ako odlučite otkazati, ovu postavku možete prilagoditi kasnije u „Opće postavke”.</string>
<string name="biometric_warning">Vaši CloudStream podaci su sada spremljeni u sigurnosnu kopiju. Iako je vjerojatnost mala, neki se uređaji mogu ponašati drugačije. Ako izgubite pristup aplikaciji, potpuno izbrišite podatke aplikacije i obnovite ih pomoću sigurnosne kopije. Ispričavamo se zbog mogućih neugodnosti.</string>
<string name="next_season_episode_format" formatted="true">Sezona %1$d epizoda %2$d izlazi</string>
<string name="episode_action_cast_mirror">Cast mirror</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Odaberite uređaj za emitiranje</string>
<string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_accounts">Računi</string>
<string name="pref_category_security">Sigurnost</string>
</resources>

View file

@ -365,7 +365,7 @@
<string name="network_adress_example">https://példa.hu/példa.mp4</string>
<string name="plugin_load_fail" formatted="true">Nem sikerült betölteni: %s</string>
<string name="batch_download_start_format" formatted="true">Elkezdődött a(z) %1$d %2$s letöltése…</string>
<string name="download_all_plugins_from_repo">Töltse le az összes bővítményt ebből a tárolóból\?</string>
<string name="download_all_plugins_from_repo">Töltse le az összes bővítményt ebből a tárolóból?</string>
<string name="safe_mode_title">Biztonságos mód bekapcsolva</string>
<string name="extension_size">Méret</string>
<string name="player_settings_play_in_mpv">MPV</string>

View file

@ -428,7 +428,7 @@
<string name="uppercase_all_subtitles">Ganti subtitle jadi huruf besar semua</string>
<string name="plugins_downloaded" formatted="true">Terunduh: %d</string>
<string name="plugins_not_downloaded" formatted="true">Tidak terunduh: %d</string>
<string name="download_all_plugins_from_repo">Unduh semua plugin dari repositori ini\?</string>
<string name="download_all_plugins_from_repo">Unduh semua plugin dari repositori ini?</string>
<string name="safe_mode_title">Semua Umur</string>
<string name="single_plugin_disabled" formatted="true">%s (Tidak aktif)</string>
<string name="tracks">Trek</string>
@ -638,4 +638,13 @@
<string name="audio_book_singular">Buku Audio</string>
<string name="custom_media_singluar">Media</string>
<string name="battery_dialog_message">Untuk memastikan unduhan dan pemberitahuan tanpa gangguan untuk acara TV berlangganan, CloudStream memerlukan izin untuk berjalan di latar belakang. Dengan menekan OK, Anda akan diarahkan ke Info aplikasi. Di sana, gulir ke Penggunaan baterai aplikasi dan atur penggunaan baterai ke Tidak Terbatas. Harap dicatat, izin ini tidak berarti CS3 akan menguras baterai Anda. Ini hanya akan beroperasi di latar belakang ketika diperlukan, seperti ketika menerima pemberitahuan atau mengunduh video dari ekstensi resmi. Jika Anda memilih untuk membatalkannya, Anda dapat menyesuaikan pengaturan ini nanti di Pengaturan Umum.</string>
<string name="reset_btn">Mengatur ulang</string>
<string name="next_season_episode_format" formatted="true">Musim %1$d Episode %2$d akan dirilis pada</string>
<string name="episode_upcoming_format" formatted="true">Akan datang di %s</string>
<string name="episode_action_cast_mirror">Cermin Cast</string>
<string name="player_settings_select_cast_device">Pilih perangkat cast</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_security">Keamanan</string>
<string name="pref_category_accounts">Akun</string>
</resources>

View file

@ -239,7 +239,7 @@
<string name="render_error">Errore del renderer</string>
<string name="unexpected_error">Errore inaspettato nel player video</string>
<string name="storage_error">Errore download, controlla i permessi di archiviazione</string>
<string name="episode_action_chromecast_episode">Chromecast</string>
<string name="episode_action_chromecast_episode">Episodio Chromecast</string>
<string name="episode_action_chromecast_mirror">Mirror Chromecast</string>
<string name="episode_action_play_in_app">Riproduci in app</string>
<string name="episode_action_play_in_format">Riproduci in %s</string>
@ -427,7 +427,7 @@
<string name="view_public_repositories_button">Vedi le repository della community</string>
<string name="view_public_repositories_button_short">Lista pubblica</string>
<string name="uppercase_all_subtitles">Tutti i sottotitoli in maiuscolo</string>
<string name="download_all_plugins_from_repo">Scaricare tutti i plugin da questa repository\?</string>
<string name="download_all_plugins_from_repo">Attenzione: CloudStream 3 non si assume alcuna responsabilità per l\'utilizzo di estensioni di terze parti e non fornisce alcun supporto per esse!</string>
<string name="single_plugin_disabled" formatted="true">%s (Disabilitato)</string>
<string name="tracks">Tracce</string>
<string name="audio_tracks">Traccia audio</string>
@ -619,7 +619,7 @@
<string name="password_pin_authentication_title">Autenticazione con password/PIN</string>
<string name="biometric_unsupported">L\'autenticazione biometrica non è supportata su questo dispositivo</string>
<string name="biometric_setting_summary">Sblocca app con impronta digitale, Face ID, PIN, sequenza e password.</string>
<string name="biometric_prompt_description">Questa schermata è stata chiusa a causa di più tentativi falliti. Riavvia l\'app.</string>
<string name="biometric_prompt_description">Dopo alcuni tentativi falliti, il prompt si chiuderà. Riavvia semplicemente l\'app per riprovare.</string>
<string name="biometric_warning">È stato eseguito il backup dei tuoi dati CloudStream. Sebbene questa possibilità sia molto bassa, tutti i dispositivi possono comportarsi in modo diverso. Nel raro caso in cui ti venga bloccato l\'accesso all\'app, cancella completamente i dati dell\'app e ripristina da un backup. Siamo molto spiacenti per qualsiasi inconveniente derivanti da questo.</string>
<string name="unfavorite">Non preferito</string>
<string name="resume_remaining" formatted="true">%s
@ -637,4 +637,21 @@
<string name="app_unrestricted_toast">L\'utilizzo della batteria dell\'app è già impostato su \"Senza restrizioni\"</string>
<string name="music_singlar">Musica</string>
<string name="audio_book_singular">Audiolibro</string>
<string name="reset_btn">Reimposta</string>
<string name="episode_upcoming_format" formatted="true">Prossimamente tra %s</string>
<string name="next_season_episode_format" formatted="true">L\'episodio %2$d della stagione %1$d uscirà tra</string>
<string name="episode_action_cast_mirror">Mirror cast</string>
<string name="player_settings_select_cast_device">Seleziona dispositivo per cast</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="cs3wiki">Wiki di CloudStream</string>
<string name="pref_category_accounts">Conti</string>
<string name="pref_category_security">Sicurezza</string>
<string name="auth_locally">Autenticazione locale</string>
<string name="qr_image">Immagine codice QR</string>
<string name="dismiss">Respingi</string>
<string name="open_downloaded_repo">Apri repository</string>
<string name="device_pin_url_message">Visita <b>%s</b> sul tuo smartphone o computer e inserisci il codice sopra</string>
<string name="device_pin_error_message">Impossibile ottenere il codice PIN del dispositivo, prova l\'autenticazione locale</string>
<string name="device_pin_expired_message">Il codice PIN è scaduto!</string>
<string name="device_pin_counter_text">Il codice scadrà tra %1$dm %2$ds</string>
</resources>

View file

@ -442,7 +442,7 @@
<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="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>

View file

@ -133,7 +133,7 @@
<string name="backup_failed_error_format">백업 중 오류 %s</string>
<string name="search">검색</string>
<string name="library">라이브러리</string>
<string name="category_account">계정</string>
<string name="category_account">계정 및 보안</string>
<string name="advanced_search_des">소스별로 구분된 검색 결과를 제공합니다</string>
<string name="show_trailers_settings">예고편 보기</string>
<string name="kitsu_settings">Kitsu에서 포스터 보기</string>
@ -311,7 +311,7 @@
<string name="view_public_repositories_button">커뮤니티 저장소 보기</string>
<string name="view_public_repositories_button_short">공개 목록</string>
<string name="uppercase_all_subtitles">모든 자막 대문자화</string>
<string name="download_all_plugins_from_repo">이 저장소에서 모든 플러그인을 다운로드하시겠습니까\?</string>
<string name="download_all_plugins_from_repo">이 저장소에서 모든 플러그인을 다운로드하시겠습니까?</string>
<string name="single_plugin_disabled" formatted="true">%s (사용불가)</string>
<string name="add_repository">저장소 추가</string>
<string name="repository_name_hint">저장소 이름</string>
@ -338,7 +338,7 @@
<string name="restore_success">로드된 백업 파일</string>
<string name="settings_info">정보</string>
<string name="advanced_search">고급 검색</string>
<string name="bug_report_settings_on">데이터를 보내지 않</string>
<string name="bug_report_settings_on">데이터를 보내지 않습니다</string>
<string name="redo_setup_process">설정 프로세스 다시 실행</string>
<string name="apk_installer_settings">APK 인스톨러</string>
<string name="github">Github</string>
@ -527,4 +527,111 @@
<string name="subscription_list_name">구독중</string>
<string name="subscription_new">구독 %s</string>
<string name="subscription_deleted">구독 취소 %s</string>
<string name="pref_category_security">보안</string>
<string name="pref_category_accounts">장부</string>
<string name="no_plugins_found_error">리포지토리에서 플러그인을 찾을 수 없습니다</string>
<string name="toast_copied">복사됨!</string>
<string name="repo_copy_label">레포지토리 이름 및 URL</string>
<string name="test_extensions_summary">본 테스트는 개발자만을 대상으로 하며, 확장자의 작업을 확인하거나 거부하지 않습니다.</string>
<string name="cs3wiki">클라우스스트림 위키</string>
<string name="links_reloaded_toast">다시 기록된 링크</string>
<string name="backup_frequency">백업 빈도</string>
<string name="favorites_list_name">즐겨찾기</string>
<string name="qr_image">QR 이미지</string>
<string name="test_extensions">모든 확장프로그램 테스트</string>
<string name="auth_locally">로컬 인증</string>
<string name="clipboard_permission_error">클립보드에 액세스하는 중 오류가 발생했습니다. 다시 시도하십시오.</string>
<string name="dismiss">취소</string>
<string name="open_downloaded_repo">저장소 열기</string>
<string name="enter_current_pin">현재 PIN 입력</string>
<string name="auto_rotate_video_desc">비디오 방향에 따라 화면 방향을 자동으로 전환합니다</string>
<string name="device_pin_error_message">장치 PIN 코드를 가져올 수 없습니다, 로컬 인증을 시도하세요</string>
<string name="device_pin_expired_message">PIN 코드가 만료되었습니다!</string>
<string name="device_pin_counter_text">코드 만료까지 남은 시간: %1$dm %2$ds</string>
<string name="no_repository_found_error">리포지토리를 찾을 수 없습니다. URL을 확인하고 VPN을 시도하십시오</string>
<string name="already_voted">이미 투표했습니다</string>
<string name="unable_to_inflate">UI를 올바르게 만들 수 없습니다. 이것은 주요 버그이며 %s 즉시 보고해야 합니다</string>
<string name="action_add_to_favorites">즐겨찾기에 추가</string>
<string name="wifi">와이파이</string>
<string name="help">도움</string>
<string name="qualities">품질</string>
<string name="edit">편집</string>
<string name="profiles">프로필</string>
<string name="ok">확인</string>
<string name="battery_dialog_title">배터리 최적화 사용 안 함</string>
<string name="app_unrestricted_toast">앱 배터리 사용량이 이미 무제한으로 설정되었습니다</string>
<string name="app_info_intent_error">CloudStream의 App 정보를 열 수 없습니다.</string>
<string name="favorite_added">즐겨찾기에 %s 추가</string>
<string name="profile_number">프로필 %d</string>
<string name="profile_background_des">프로필 배경</string>
<string name="duplicate_replace">대체</string>
<string name="enter_pin">PIN 입력</string>
<string name="pin">PIN</string>
<string name="pin_error_length">PIN은 4자여야 합니다</string>
<string name="logged_account" formatted="true">%s으로 로그인 됨</string>
<string name="skip_startup_account_select_pref">시작 시 계정 선택 건너뛰기</string>
<string name="favorite">즐겨찾기</string>
<string name="unfavorite">즐겨찾기 해제</string>
<string name="biometric_authentication_title">잠금 해제</string>
<string name="biometric_setting">생체 인식으로 잠금</string>
<string name="music_singlar">음악</string>
<string name="audio_book_singular">오디오책</string>
<string name="auto_rotate_video">자동 회전</string>
<string name="mobile_data">모바일 데이터</string>
<string name="disable">사용 불가능</string>
<string name="player_settings_play_in_fcast">fcast</string>
<string name="player_settings_select_cast_device">캐스트 장치 선택</string>
<string name="clipboard_unknown_error">복사하는 중 오류가 발생했습니다. 로그캣을 복사하고 문의하십시오.</string>
<string name="action_unsubscribe">구독 취소</string>
<string name="set_default">기본값 설정</string>
<string name="action_subscribe">구독</string>
<string name="use">사용</string>
<string name="duplicate_message_single" formatted="true">당신의 라이브러리에 이미 잠재적으로 중복된 항목이 존재합니다: \'%s\'.
\n
\n이 항목을 그래도 추가하시겠습니까, 기존 항목을 교체하시겠습니까, 아니면 작업을 취소하시겠습니까?</string>
<string name="duplicate_replace_all">전부 대체</string>
<string name="duplicate_add">추가</string>
<string name="favorite_removed">즐겨찾기에서 %s 제거</string>
<string name="duplicate_message_multiple" formatted="true">당신의 라이브러리에 잠재적으로 중복된 항목이 발견되었습니다:
\n
\n%s
\n
\n이 항목을 그래도 추가하시겠습니까, 기존 항목을 교체하시겠습니까, 아니면 작업을 취소하시겠습니까?</string>
<string name="select_an_account">계정 선택</string>
<string name="use_default_account">기본 계정 사용</string>
<string name="rotate_video">회전</string>
<string name="rotate_video_desc">화면 방향을 전환할 토글 버튼 표시</string>
<string name="manage_accounts">계정 관리</string>
<string name="lock_profile">프로필 잠금</string>
<string name="pin_error_incorrect">잘못된 PIN입니다. 다시 시도하세요.</string>
<string name="edit_account">계정 편집</string>
<string name="custom_media_singluar">미디어</string>
<string name="password_pin_authentication_title">비밀번호/PIN 인증</string>
<string name="biometric_unsupported">이 장치에서는 생체 인식이 지원되지 않습니다</string>
<string name="biometric_setting_summary">지문, 얼굴 ID, PIN, 패턴 또는 비밀번호로 앱을 잠급니다.</string>
<string name="biometric_prompt_description">여러 번 실패하면 프롬프트가 닫힙니다. 다시 시도하려면 앱을 다시 시작하세요.</string>
<string name="reset_btn">재설정</string>
<string name="automatic_plugin_download_mode_title">플러그인 다운로드를 필터링할 모드 선택</string>
<string name="biometric_warning">데이터가 백업되었습니다. 장치에 따라 동작이 다를 수 있으며 앱 접근이 차단될 경우 앱 데이터를 완전히 지우고 백업에서 복원하세요. 이로 인해 발생하는 불편을 사과드립니다.</string>
<string name="device_pin_url_message">스마트폰이나 컴퓨터에서 <b>%s</b>를 방문하여 위의 코드를 입력하세요</string>
<string name="battery_dialog_message">구독 TV 프로그램에 대한 중단 없는 다운로드 및 알림을 보장하기 위해 CloudStream은 백그라운드에서 실행할 수 있는 권한이 필요합니다. 확인을 누르면 App info로 이동합니다. 거기서 𝘼𝙥𝙥 𝙗𝙖𝙩𝙩𝙚𝙧𝙮 𝙪𝙨𝙖𝙜𝙚로 스크롤하여 배터리 사용량을 𝙐𝙣𝙧𝙚𝙨𝙩𝙧𝙞𝙘𝙩𝙚𝙙로 설정합니다. 이 권한은 CS3가 배터리를 소모한다는 의미가 아닙니다. 알림을 받거나 공식 확장에서 동영상을 다운로드하는 등 필요할 때만 백그라운드에서 작동합니다. 취소를 선택한 경우 나중에 𝙂𝙚𝙣𝙚𝙧𝙖𝙡 𝙎𝙚𝙩𝙩𝙞𝙣𝙜𝙨에서 이 설정을 조정할 수 있습니다.</string>
<string name="quality_profile_help">여기서 소스의 순서를 변경할 수 있습니다. 비디오의 우선 순위가 높은 경우에는 소스 선택에 더 높게 나타납니다. 소스 우선 순위와 품질 우선 순위의 합이 비디오 우선 순위입니다.
\n
\n참고 A: 3
\n품질 B: 7
\n총 비디오 우선 순위는 10입니다.
\n
\n참고: 합이 10 이상이면 해당 링크가 로드되면 플레이어는 자동으로 로드를 건너뜁니다!</string>
<string name="next_season_episode_format" formatted="true">시즌 %1$d 에피소드 %2$d이(가) 출시됩니다</string>
<string name="result_search_tooltip">다른 확장자에서 검색</string>
<string name="subscribe_tooltip">새로운 에피소드 알림</string>
<string name="recommendations_tooltip">권장 사항 표시</string>
<string name="speed_setting_summary">플레이어에 속도 옵션을 추가합니다</string>
<string name="episode_upcoming_format" formatted="true">%s로 출시 예정</string>
<string name="resume_remaining" formatted="true">%s
\n남음</string>
<string name="duplicate_title">잠재적 중복 발견</string>
<string name="enter_pin_with_name" formatted="true">%s의 PIN 입력</string>
<string name="action_remove_from_favorites">즐겨찾기에서 제거</string>
<string name="episode_action_cast_mirror">캐스트미러</string>
</resources>

View file

@ -415,7 +415,7 @@
<string name="view_public_repositories_button">Skatīt kopienas krātuves</string>
<string name="view_public_repositories_button_short">Publisks saraksts</string>
<string name="uppercase_all_subtitles">Visi subtitri ar lielajiem burtiem</string>
<string name="download_all_plugins_from_repo">Vai lejupielādēt visus spraudņus no šīs krātuves\?</string>
<string name="download_all_plugins_from_repo">Vai lejupielādēt visus spraudņus no šīs krātuves?</string>
<string name="single_plugin_disabled" formatted="true">%s (atspējots)</string>
<string name="tracks">Tracks</string>
<string name="audio_tracks">Audio dziesmas</string>

View file

@ -89,7 +89,7 @@
<string name="player_size_settings_des">Отстранете ги црните граници</string>
<string name="player_subtitles_settings">Преводи</string>
<string name="player_subtitles_settings_des">Поставки на плеерот за преводи</string>
<string name="eigengraumode_settings">Режим на Eigengravy</string>
<string name="eigengraumode_settings">Брзина на репродукција</string>
<string name="swipe_to_seek_settings">Повлечете за да барате</string>
<string name="swipe_to_seek_settings_des">Повлечете од страна на страна за да ја контролирате вашата позиција во видеото</string>
<string name="swipe_to_change_settings">Повлечете за да ги промените поставките</string>
@ -186,7 +186,7 @@
<string name="resize_zoom">Зумирај</string>
<string name="legal_notice">Disclaimer</string>
<string name="category_general">Општи поставки</string>
<string name="provider_lang_settings">Јазици на провајдерите</string>
<string name="provider_lang_settings">Јазици на екстензиите</string>
<string name="app_layout">Распоред на апликацијата</string>
<string name="preferred_media_settings">Претпочитани медиуми</string>
<string name="automatic">Автоматски</string>
@ -239,7 +239,7 @@
<string name="other_singular">Видео</string>
<string name="sort_clear">Исчисти</string>
<string name="test_passed">Положен</string>
<string name="example_site_name">MyCoolSite</string>
<string name="example_site_name">Име на сајт</string>
<string name="error_invalid_data">Неважечки податоци</string>
<string name="actor_supporting">Поддршка</string>
<string name="pref_category_player_features">Функции на плеерот</string>
@ -250,11 +250,11 @@
<string name="extension_description">Опис</string>
<string name="delayed_update_notice">Апликацијата ќе се ажурира по излегувањето</string>
<string name="subscription_deleted">Отпишана е од %s</string>
<string name="jsdelivr_proxy">прокси raw.githubusercontent.com</string>
<string name="jsdelivr_proxy">GitHub прокси</string>
<string name="quality_tc">TC</string>
<string name="subscription_new">Претплатен на %s</string>
<string name="pref_category_subtitles">Преводи</string>
<string name="download_all_plugins_from_repo">Да се преземат сите приклучоци од ова складиште\?</string>
<string name="download_all_plugins_from_repo">Да се преземат сите приклучоци од ова складиште?</string>
<string name="backup_failed">Недостасуваат дозволи за складирање. Обидете се повторно.</string>
<string name="sort_save">Зачувај</string>
<string name="player_load_subtitles">Вчитај од датотека</string>
@ -299,7 +299,7 @@
<string name="player_settings_play_in_mpv">MPV</string>
<string name="apk_installer_package_installer">Инсталатор на пакети</string>
<string name="ova_singular">ОВА</string>
<string name="category_updates">Ажурирања и резервни копии</string>
<string name="category_updates">Ажурирање и резервна копија</string>
<string name="empty_library_no_accounts_message">Вашата библиотека е празна :(
\nНајавете се на корисничка сметка или додадете серии.</string>
<string name="no_episodes_found">Не се пронајдени епизоди</string>
@ -337,7 +337,7 @@
<string name="extras">Додатоци</string>
<string name="random_button_settings_desc">Прикажи случајно копче на почетната страница и библиотеката</string>
<string name="extension_types">Поддржано</string>
<string name="category_account">Сметки</string>
<string name="category_account">Сметки и безбедност</string>
<string name="skip_type_intro">Вовед</string>
<string name="create_account">Креирај сметка</string>
<string name="action_remove_from_watched">Отстрани од гледаното</string>
@ -349,7 +349,7 @@
<string name="plugins_updated" formatted="true">Ажурирани %d приклучоци</string>
<string name="skip_type_mixed_op">Мешано отворање</string>
<string name="pref_category_extensions">Екстензии</string>
<string name="enable_nsfw_on_providers">Овозможете NSFW на поддржани провајдери</string>
<string name="enable_nsfw_on_providers">Овозможете NSFW на поддржани екстензии</string>
<string name="jsdelivr_enabled">Не успеа да стигне до GitHub. Вклучувам jsDelivr прокси…</string>
<string name="subtitles_filter_lang">Филтрирајте по претпочитан медиумски јазик</string>
<string name="action_open_play">@string/home_play</string>
@ -381,7 +381,7 @@
<string name="kitsu_settings">Прикажи постери од Kitsu</string>
<string name="confirm_exit_dialog">Дали сте сигурни дека сакате да излезете\?</string>
<string name="video_disk_description">Предизвикува проблеми ако е превисоко поставено на уреди со мал простор за складирање, како што е Android TV.</string>
<string name="jsdelivr_proxy_summary">Користејќи jsDelivr, блокирањето на GitHub може да се заобиколи. Може да ги одложи ажурирањата за неколку дена.</string>
<string name="jsdelivr_proxy_summary">Заобиколете го блокирањето на необработени URL-адреси на github користејќи jsDelivr. Може да предизвика ажурирањата да се одложат за неколку дена.</string>
<string name="yes">Да</string>
<string name="sort_alphabetical_z">Азбучно (Ш до А)</string>
<string name="quality_workprint">WP</string>
@ -398,7 +398,7 @@
<string name="apk_installer_settings">Инсталатор на APK</string>
<string name="extensions">Екстензии</string>
<string name="quality_uhd">UHD</string>
<string name="referer">Референт</string>
<string name="referer">Референт (опционално)</string>
<string name="skip_type_op">Се отвора</string>
<string name="example_ip">127.0.0.1</string>
<string name="delete_repository_plugins">Ова исто така ќе се избрише сите приклучоци за складиште</string>
@ -409,7 +409,7 @@
<string name="restore_failed_format" formatted="true">Не успеа да ги врати податоците од датотеката %s</string>
<string name="test_failed">Не успеа</string>
<string name="documentaries_singular">Документарец</string>
<string name="stream">Стрим</string>
<string name="stream">Мрежен проток</string>
<string name="duration_format" formatted="true">%d мин</string>
<string name="play_with_app_name">Играј со CloudStream</string>
<string name="play_trailer_button">Пушти трејлер</string>
@ -433,7 +433,7 @@
<string name="resume_time_left" formatted="true">%dm
\nпреостанува</string>
<string name="video_buffer_disk_settings">Видео кеш на дискот</string>
<string name="network_adress_example">Поврзување до пренос</string>
<string name="network_adress_example">https://example.com/example.mp4</string>
<string name="setup_done">Готово</string>
<string name="add_repository">Додај складиште</string>
<string name="is_adult">18+</string>
@ -449,7 +449,7 @@
<string name="quality_sdr">SDR</string>
<string name="player_settings_play_in_browser">Веб-прелистувач</string>
<string name="app_not_found_error">Апликацијата не е пронајдена</string>
<string name="example_username">MyCoolUsername</string>
<string name="example_username">Корисничко име</string>
<string name="open_with">Отвори со</string>
<string name="season_format">%1$s %2$d%3$s</string>
<string name="redo_setup_process">Повторете го процесот на поставување</string>
@ -480,9 +480,7 @@
<string name="plugin_downloaded">Приклучокот е преземен</string>
<string name="plugin_load_fail" formatted="true">Не може да се вчита %s</string>
<string name="setup_extensions_subtext">Преземете ја листата на сајтови што сакате да ги користите</string>
<string name="blank_repo_message">CloudStream нема стандардно инсталирани локации. Треба да ги инсталирате сајтовите од складиштата.
\n
\nПоради отстранување на DMCA без мозок од страна на Sky UK Limited 🤮 не можеме да ја поврземе локацијата на складиштето во апликацијата.
<string name="blank_repo_message">CloudStream нема стандардно инсталирани екстензии. Треба сами да инсталирате екстензии.
\n
\nПридружете се на нашиот Discord или барајте онлајн.</string>
<string name="tracks">Песни</string>
@ -507,9 +505,9 @@
<string name="skip_type_ed">Завршува</string>
<string name="skip_type_mixed_ed">Измешан крај</string>
<string name="quality_hdr">HDR</string>
<string name="example_site_url">example.com</string>
<string name="example_site_url">https://example.com</string>
<string name="subtitle_offset">Синхронизирај преводи</string>
<string name="apply_on_restart">Примени при рестартирање</string>
<string name="apply_on_restart">Рестартирајте ја апликацијата за да ги видите промените.</string>
<string name="limit_title">Наслов на видео плеер максимални знаци</string>
<string name="subs_import_text" formatted="true">Увезете фонтови ставајќи ги во %s</string>
<string name="restore_settings">Врати ги податоците од резервна копија</string>
@ -591,4 +589,39 @@
<string name="backup_frequency">Зачестеност на зачувување на бекап</string>
<string name="auto_rotate_video_desc">Овозможете автоматско префрлување на ориентацијата на екранот врз основа на видео ориентација</string>
<string name="auto_rotate_video">Автоматска ротација</string>
<string name="repo_copy_label">Име и URL на складиштето</string>
<string name="toast_copied">копирано!</string>
<string name="test_extensions">Тестирај ги сите екстензии</string>
<string name="ok">ОК</string>
<string name="app_unrestricted_toast">Користењето на батеријата на апликацијата е веќе поставено на неограничено</string>
<string name="unfavorite">Неомилен</string>
<string name="favorite">Омилен</string>
<string name="biometric_setting">Заклучување со биометрика</string>
<string name="music_singlar">Музика</string>
<string name="subscribe_tooltip">Известување за нова епизода</string>
<string name="result_search_tooltip">Пребарајте во други екстензии</string>
<string name="recommendations_tooltip">Прикажи препораки</string>
<string name="speed_setting_summary">Додава опција за брзина во плеерот</string>
<string name="episode_action_cast_mirror">Cast mirror</string>
<string name="test_extensions_summary">Овој тест е наменет само за програмери и не ја потврдува или негира работата на која било екстензија.</string>
<string name="app_info_intent_error">Не може да се отворат информациите за апликацијата CloudStream.</string>
<string name="password_pin_authentication_title">Лозинка/ПИН автентикација</string>
<string name="biometric_setting_summary">Отклучете ја апликацијата со отпечаток од прст, ID на лице, PIN, шема и лозинка.</string>
<string name="biometric_warning">Сега е направена резервна копија на вашите податоци на CloudStream. Иако можноста за ова е многу мала, сите уреди можат да се однесуваат поинаку. Во ретки случаи, кога ќе се заклучите од пристап до апликацијата, целосно исчистете ги податоците на апликацијата и вратете ги од резервна копија. Многу ни е жал за какви било непријатности што произлегуваат од ова.</string>
<string name="reset_btn">Ресетирај</string>
<string name="next_season_episode_format" formatted="true">Сезона %1$d Епизода %2$d ќе биде објавена за</string>
<string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Одбери уред да кастираш</string>
<string name="battery_dialog_title">Оневозможи оптимизација на батерија</string>
<string name="biometric_authentication_title">Отклучи CloudStream</string>
<string name="biometric_unsupported">Биометриската автентикација не е поддржана на овој уред</string>
<string name="biometric_prompt_description">Овој екран беше затворен поради повеќе неуспешни обиди. Ве молиме рестартирајте ја апликацијата.</string>
<string name="custom_media_singluar">Медиуми</string>
<string name="episode_upcoming_format" formatted="true">Претстои во %s</string>
<string name="resume_remaining" formatted="true">%s
\nпреостанати</string>
<string name="battery_dialog_message">За да обезбеди непрекинато преземања и известувања за претплатени ТВ-серии, на CloudStream му треба дозвола да работи во заднина. Со притискање на ОК, ќе бидете упатени до информации за апликацијата. Таму, дојдете до 𝘼𝙥𝙥 𝙗𝙖𝙩𝙩𝙚𝙧𝙮 𝙪𝙨𝙖𝙜𝙚 и поставете ја употребата на батеријата на Неограничено. Ве молиме имајте предвид, оваа дозвола не значи дека CS3 ќе ви ја испразни батеријата. Ќе работи само во заднина кога е потребно, како на пример при примање известувања или преземање видеа од официјални екстензии. Ако изберете да откажете, може да ја прилагодите оваа поставка подоцна во 𝙂𝙚𝙣𝙚𝙧𝙖𝙡 𝙎𝙚𝙩𝙩𝙞𝙣𝙜𝙨.</string>
<string name="clipboard_permission_error">Грешка при пристапот до таблата со исечоци, обидете се повторно.</string>
<string name="clipboard_unknown_error">Грешка при копирање, копирајте го logcat и контактирајте со поддршката за апликацијата.</string>
<string name="audio_book_singular">Аудио книга</string>
</resources>

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