Compare commits
53 Commits
pre-releas
...
master
Author | SHA1 | Date |
---|---|---|
Sofie | 93d81ea038 | |
Sofie | e007714701 | |
KingLucius | 805f80b2ac | |
KingLucius | b5fb0997c4 | |
KingLucius | ca918b1581 | |
IndusAryan | 09779b4ee0 | |
Sofie | 012d38398e | |
Ömer Faruk Sancak | d1db4c3370 | |
Sarlay | 8d318ca84a | |
KingLucius | eea6e13346 | |
CranberrySoup | 2b7d102716 | |
Osten | 9ea7674a0f | |
IndusAryan | 3dcf7076d0 | |
Sofie | 8b14fcb881 | |
IndusAryan | 01f21e0fe8 | |
coxju | bdef6524e7 | |
Cloudburst | f40a8d9418 | |
IndusAryan | 03fcb106ac | |
coxju | 636e157c63 | |
CranberrySoup | 5af1b80cb7 | |
coxju | 5dfc08aabb | |
coxju | 1676094488 | |
Sir Aguacata | 19145c6cc4 | |
coxju | ebb72d6a0c | |
Sir Aguacata | 399b28c75b | |
IndusAryan | 601483e103 | |
recloudstream[bot] | 9733d0b316 | |
Weblate (bot) | 0cf199248a | |
Sofie | 2624947b5b | |
coxju | 31c783d0b4 | |
firelight | 9f1b172f34 | |
IndusAryan | 93dce8682e | |
IndusAryan | 723c653b07 | |
coxju | 0c73f5e59a | |
coxju | 0eb152c5db | |
IndusAryan | 8c5ab86714 | |
Ömer Faruk Sancak | 85a769a898 | |
Sir Aguacata | 96aa56209b | |
coxju | d71d3890b5 | |
IndusAryan | 19b1a40cf8 | |
Yutatsu | e5f483b0b2 | |
coxju | 6f1e0bef80 | |
LagradOst | 5e6272be3f | |
coxju | 97ec98b9e2 | |
coxju | 42fd0b5c76 | |
Ömer Faruk Sancak | 42774f6183 | |
Sofie | f687508521 | |
recloudstream[bot] | dbba6d7f27 | |
Hosted Weblate | f4da170a57 | |
Cloudburst | 2a1876f54c | |
wrongwrong | f1d0a8e955 | |
IndusAryan | 1c6be2d5cb | |
Horis | fc802cdcdd |
|
@ -19,21 +19,21 @@ jobs:
|
|||
steps:
|
||||
- name: Generate access token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
repository: "recloudstream/secrets"
|
||||
- name: Generate access token (archive)
|
||||
id: generate_archive_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
repository: "recloudstream/cloudstream-archive"
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
SIGNING_STORE_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
|
||||
SIMKL_CLIENT_ID: ${{ secrets.SIMKL_CLIENT_ID }}
|
||||
SIMKL_CLIENT_SECRET: ${{ secrets.SIMKL_CLIENT_SECRET }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "recloudstream/cloudstream-archive"
|
||||
token: ${{ steps.generate_archive_token.outputs.token }}
|
||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
steps:
|
||||
- name: Generate access token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
|
@ -43,12 +43,13 @@ jobs:
|
|||
rm -rf "./-cloudstream"
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Generate Dokka
|
||||
run: |
|
||||
|
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
steps:
|
||||
- name: Generate access token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
|
@ -27,7 +27,7 @@ jobs:
|
|||
comment-body: '${index}. ${similarity} #${number}'
|
||||
- name: Label if possible duplicate
|
||||
if: steps.similarity.outputs.similar-issues-found =='true'
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
repo: context.repo.repo,
|
||||
labels: ["possible duplicate"]
|
||||
})
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Automatically close issues that dont follow the issue template
|
||||
uses: lucasbento/auto-close-issues@v1.0.2
|
||||
with:
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
|||
Found provider name: `${{ steps.provider_check.outputs.name }}`
|
||||
- name: Label if mentions provider
|
||||
if: steps.provider_check.outputs.name != 'none'
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
|
|
|
@ -18,14 +18,14 @@ jobs:
|
|||
steps:
|
||||
- name: Generate access token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
repository: "recloudstream/secrets"
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
|
|
|
@ -6,9 +6,9 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
|
@ -17,7 +17,7 @@ jobs:
|
|||
- name: Run Gradle
|
||||
run: ./gradlew assemblePrereleaseDebug
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pull-request-build
|
||||
path: "app/build/outputs/apk/prerelease/debug/*.apk"
|
||||
|
|
|
@ -18,12 +18,12 @@ jobs:
|
|||
steps:
|
||||
- name: Generate access token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_KEY }}
|
||||
repository: "recloudstream/cloudstream"
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Install dependencies
|
||||
|
|
|
@ -139,6 +139,10 @@ android {
|
|||
abortOnError = false
|
||||
checkReleaseBuilds = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
namespace = "com.lagradost.cloudstream3"
|
||||
}
|
||||
|
@ -159,10 +163,10 @@ dependencies {
|
|||
// Android Core & Lifecycle
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
||||
|
||||
// Design & UI
|
||||
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
||||
|
@ -195,13 +199,13 @@ 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:eac850") /* For Trailers
|
||||
implementation("com.github.teamnewpipe:NewPipeExtractor:6dc25f7") /* 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
|
||||
|
||||
// Crash Reports (AcraApplication.kt)
|
||||
implementation("ch.acra:acra-core:5.11.2")
|
||||
implementation("ch.acra:acra-toast:5.11.2")
|
||||
implementation("ch.acra:acra-core:5.11.3")
|
||||
implementation("ch.acra:acra-toast:5.11.3")
|
||||
|
||||
// UI Stuff
|
||||
implementation("com.facebook.shimmer:shimmer:0.5.0") // Shimmering Effect (Loading Skeleton)
|
||||
|
@ -224,9 +228,9 @@ dependencies {
|
|||
Level 25 or Less. */
|
||||
|
||||
// Downloading & Networking
|
||||
implementation("androidx.work:work-runtime:2.8.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
||||
implementation("com.github.Blatzar:NiceHttp:0.4.4") // HTTP Lib
|
||||
implementation("androidx.work:work-runtime:2.9.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
implementation("com.github.Blatzar:NiceHttp:0.4.5") // HTTP Lib
|
||||
}
|
||||
|
||||
tasks.register("androidSourcesJar", Jar::class) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import androidx.preference.PreferenceManager
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.kotlinModule
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||
|
@ -34,7 +34,7 @@ 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())
|
||||
val mapper = JsonMapper.builder().addModule(kotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||
|
||||
/**
|
||||
|
@ -1587,8 +1587,15 @@ data class AnimeLoadResponse(
|
|||
}
|
||||
|
||||
override fun getTotalEpisodeIndex(episode: Int, season: Int): Int {
|
||||
val displayMap = this.seasonNames?.associate { it.season to it.displaySeason } ?: emptyMap()
|
||||
|
||||
return this.episodes.maxOf { (_, episodes) ->
|
||||
episodes.count { ((it.season ?: Int.MIN_VALUE) < season) && it.season != 0 }
|
||||
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
|
||||
// Count all episodes from season 1 to below the current season.
|
||||
episodeSeason in 1..<season
|
||||
}
|
||||
} + episode
|
||||
}
|
||||
|
||||
|
@ -1895,8 +1902,13 @@ data class TvSeriesLoadResponse(
|
|||
}
|
||||
|
||||
override fun getTotalEpisodeIndex(episode: Int, season: Int): Int {
|
||||
return episodes.count {
|
||||
(it.season ?: Int.MIN_VALUE) < season && it.season != 0
|
||||
val displayMap = this.seasonNames?.associate { it.season to it.displaySeason } ?: emptyMap()
|
||||
|
||||
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
|
||||
// Count all episodes from season 1 to below the current season.
|
||||
episodeSeason in 1..<season
|
||||
} + episode
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ import com.lagradost.cloudstream3.APIHolder.apis
|
|||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.initAll
|
||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||
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.loadThemes
|
||||
|
@ -287,9 +288,27 @@ var app = Requests(responseParser = object : ResponseParser {
|
|||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||
companion object {
|
||||
const val TAG = "MAINACT"
|
||||
const val ANIMATED_OUTLINE : Boolean = false
|
||||
const val ANIMATED_OUTLINE: Boolean = false
|
||||
var lastError: String? = null
|
||||
|
||||
private const val FILE_DELETE_KEY = "FILES_TO_DELETE_KEY"
|
||||
|
||||
/**
|
||||
* Transient files to delete on application exit.
|
||||
* Deletes files on onDestroy().
|
||||
*/
|
||||
private var filesToDelete: Set<String>
|
||||
// This needs to be persistent because the application may exit without calling onDestroy.
|
||||
get() = getKey<Set<String>>(FILE_DELETE_KEY) ?: setOf()
|
||||
private set(value) = setKey(FILE_DELETE_KEY, value)
|
||||
|
||||
/**
|
||||
* Add file to delete on Exit.
|
||||
*/
|
||||
fun deleteFileOnExit(file: File) {
|
||||
filesToDelete = filesToDelete + file.path
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting this will automatically enter the query in the search
|
||||
* next time the search fragment is opened.
|
||||
|
@ -676,6 +695,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
filesToDelete.forEach { path ->
|
||||
val result = File(path).deleteRecursively()
|
||||
if (result) {
|
||||
Log.d(TAG, "Deleted temporary file: $path")
|
||||
} else {
|
||||
Log.d(TAG, "Failed to delete temporary file: $path")
|
||||
}
|
||||
}
|
||||
filesToDelete = setOf()
|
||||
val broadcastIntent = Intent()
|
||||
broadcastIntent.action = "restart_service"
|
||||
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
|
||||
|
@ -1374,6 +1402,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
}
|
||||
|
||||
observeNullable(viewModel.favoriteStatus) observeFavoriteStatus@{ isFavorite ->
|
||||
resultviewPreviewFavorite.isVisible = isFavorite != null
|
||||
if (isFavorite == null) return@observeFavoriteStatus
|
||||
|
||||
val drawable = if (isFavorite) {
|
||||
R.drawable.ic_baseline_favorite_24
|
||||
} else {
|
||||
R.drawable.ic_baseline_favorite_border_24
|
||||
}
|
||||
|
||||
resultviewPreviewFavorite.setImageResource(drawable)
|
||||
}
|
||||
|
||||
resultviewPreviewFavorite.setOnClickListener{
|
||||
viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? ->
|
||||
if (newStatus == null) return@toggleFavoriteStatus
|
||||
|
||||
val message = if (newStatus) {
|
||||
R.string.favorite_added
|
||||
} else {
|
||||
R.string.favorite_removed
|
||||
}
|
||||
|
||||
val name = (viewModel.page.value as? Resource.Success)?.value?.title
|
||||
?: txt(R.string.no_data).asStringNull(this@MainActivity) ?: ""
|
||||
showToast(txt(message, name), Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isTvSettings()) // dont want this clickable on tv layout
|
||||
resultviewPreviewDescription.setOnClickListener { view ->
|
||||
view.context?.let { ctx ->
|
||||
|
@ -1625,7 +1682,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
// this ensures that no unnecessary space is taken
|
||||
loadCache()
|
||||
File(filesDir, "exoplayer").deleteRecursively() // old cache
|
||||
File(cacheDir, "exoplayer").deleteOnExit() // current cache
|
||||
deleteFileOnExit(File(cacheDir, "exoplayer")) // current cache
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64Decode
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
open class Acefile : ExtractorApi() {
|
||||
|
@ -9,31 +9,35 @@ open class Acefile : ExtractorApi() {
|
|||
override val mainUrl = "https://acefile.co"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
app.get(url).document.select("script").map { script ->
|
||||
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
|
||||
val data = getAndUnpack(script.data())
|
||||
val id = data.substringAfter("{\"id\":\"").substringBefore("\",")
|
||||
val key = data.substringAfter("var nfck=\"").substringBefore("\";")
|
||||
app.get("https://acefile.co/local/$id?key=$key").text.let {
|
||||
base64Decode(
|
||||
it.substringAfter("JSON.parse(atob(\"").substringBefore("\"))")
|
||||
).let { res ->
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
res.substringAfter("\"file\":\"").substringBefore("\","),
|
||||
"$mainUrl/",
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val id = "/(?:d|download|player|f|file)/(\\w+)".toRegex().find(url)?.groupValues?.get(1)
|
||||
val script = getAndUnpack(app.get("$mainUrl/player/${id ?: return}").text)
|
||||
val service = """service\s*=\s*['"]([^'"]+)""".toRegex().find(script)?.groupValues?.get(1)
|
||||
val serverUrl = """['"](\S+check&id\S+?)['"]""".toRegex().find(script)?.groupValues?.get(1)
|
||||
?.replace("\"+service+\"", service ?: return)
|
||||
|
||||
val video = app.get(serverUrl ?: return, referer = "$mainUrl/").parsedSafe<Source>()?.data
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
video ?: return,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
data class Source(
|
||||
val data: String? = null,
|
||||
)
|
||||
|
||||
}
|
|
@ -49,8 +49,23 @@ open class Chillx : ExtractorApi() {
|
|||
val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
|
||||
|
||||
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
|
||||
val tracks = Regex("""tracks:\s*\[(.+)]""").find(decrypt)?.groupValues?.get(1)
|
||||
|
||||
val subtitles = Regex("""subtitle"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
|
||||
val subtitlePattern = """\[(.*?)\](https?://[^\s,]+)""".toRegex()
|
||||
val matches = subtitlePattern.findAll(subtitles ?: "")
|
||||
val languageUrlPairs = matches.map { matchResult ->
|
||||
val (language, url) = matchResult.destructured
|
||||
decodeUnicodeEscape(language) to url
|
||||
}.toList()
|
||||
|
||||
languageUrlPairs.forEach{ (name, file) ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
name,
|
||||
file
|
||||
)
|
||||
)
|
||||
}
|
||||
// required
|
||||
val headers = mapOf(
|
||||
"Accept" to "*/*",
|
||||
|
@ -67,18 +82,15 @@ open class Chillx : ExtractorApi() {
|
|||
"$mainUrl/",
|
||||
headers = headers
|
||||
).forEach(callback)
|
||||
|
||||
AppUtils.tryParseJson<List<Tracks>>("[$tracks]")
|
||||
?.filter { it.kind == "captions" }?.map { track ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
track.label ?: "",
|
||||
track.file ?: return@map null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun decodeUnicodeEscape(input: String): String {
|
||||
val regex = Regex("u([0-9a-fA-F]{4})")
|
||||
return regex.replace(input) {
|
||||
it.groupValues[1].toInt(16).toChar().toString()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getKey() = key ?: fetchKey().also { key = it }
|
||||
|
||||
private suspend fun fetchKey(): String {
|
||||
|
|
|
@ -18,7 +18,22 @@ open class ContentX : ExtractorApi() {
|
|||
val i_source = app.get(url, referer=ext_ref).text
|
||||
val i_extract = Regex("""window\.openPlayer\('([^']+)'""").find(i_source)!!.groups[1]?.value ?: throw ErrorLoadingException("i_extract is null")
|
||||
|
||||
val vid_source = app.get("https://contentx.me/source2.php?v=${i_extract}", referer=ext_ref).text
|
||||
val sub_urls = mutableSetOf<String>()
|
||||
Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(i_source).forEach {
|
||||
val (sub_url, sub_lang) = it.destructured
|
||||
|
||||
if (sub_url in sub_urls) { return@forEach }
|
||||
sub_urls.add(sub_url)
|
||||
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
lang = sub_lang.replace("\\u0131", "ı").replace("\\u0130", "İ").replace("\\u00fc", "ü").replace("\\u00e7", "ç"),
|
||||
url = fixUrl(sub_url.replace("\\", ""))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val vid_source = app.get("${mainUrl}/source2.php?v=${i_extract}", referer=ext_ref).text
|
||||
val vid_extract = Regex("""file\":\"([^\"]+)""").find(vid_source)!!.groups[1]?.value ?: throw ErrorLoadingException("vid_extract is null")
|
||||
val m3u_link = vid_extract.replace("\\", "")
|
||||
|
||||
|
@ -35,7 +50,7 @@ open class ContentX : ExtractorApi() {
|
|||
|
||||
val i_dublaj = Regex(""",\"([^']+)\",\"Türkçe""").find(i_source)!!.groups[1]?.value
|
||||
if (i_dublaj != null) {
|
||||
val dublaj_source = app.get("https://contentx.me/source2.php?v=${i_dublaj}", referer=ext_ref).text
|
||||
val dublaj_source = app.get("${mainUrl}/source2.php?v=${i_dublaj}", referer=ext_ref).text
|
||||
val dublaj_extract = Regex("""file\":\"([^\"]+)""").find(dublaj_source)!!.groups[1]?.value ?: throw ErrorLoadingException("dublaj_extract is null")
|
||||
val dublaj_link = dublaj_extract.replace("\\", "")
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
||||
open class EmturbovidExtractor : ExtractorApi() {
|
||||
override var name = "Emturbovid"
|
||||
override var mainUrl = "https://emturbovid.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val response = app.get(
|
||||
url, referer = referer ?: "$mainUrl/"
|
||||
)
|
||||
val playerScript =
|
||||
response.document.selectXpath("//script[contains(text(),'var urlPlay')]")
|
||||
.html()
|
||||
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
if (playerScript.isNotBlank()) {
|
||||
val m3u8Url =
|
||||
playerScript.substringAfter("var urlPlay = '").substringBefore("'")
|
||||
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
source = name,
|
||||
name = name,
|
||||
url = m3u8Url,
|
||||
referer = "$mainUrl/",
|
||||
quality = Qualities.Unknown.value,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -22,9 +22,9 @@ open class Gofile : ExtractorApi() {
|
|||
val id = Regex("/(?:\\?c=|d/)([\\da-zA-Z-]+)").find(url)?.groupValues?.get(1)
|
||||
val token = app.get("$mainApi/createAccount").parsedSafe<Account>()?.data?.get("token")
|
||||
val websiteToken = app.get("$mainUrl/dist/js/alljs.js").text.let {
|
||||
Regex("websiteToken\\s*=\\s*\"([^\"]+)").find(it)?.groupValues?.get(1)
|
||||
Regex("fetchData.wt\\s*=\\s*\"([^\"]+)").find(it)?.groupValues?.get(1)
|
||||
}
|
||||
app.get("$mainApi/getContent?contentId=$id&token=$token&websiteToken=$websiteToken")
|
||||
app.get("$mainApi/getContent?contentId=$id&token=$token&wt=$websiteToken")
|
||||
.parsedSafe<Source>()?.data?.contents?.forEach {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
|
|
|
@ -5,4 +5,19 @@ package com.lagradost.cloudstream3.extractors
|
|||
class Hotlinger : ContentX() {
|
||||
override var name = "Hotlinger"
|
||||
override var mainUrl = "https://hotlinger.com"
|
||||
}
|
||||
|
||||
class FourCX : ContentX() {
|
||||
override var name = "FourCX"
|
||||
override var mainUrl = "https://four.contentx.me"
|
||||
}
|
||||
|
||||
class PlayRu : ContentX() {
|
||||
override var name = "PlayRu"
|
||||
override var mainUrl = "https://playru.net"
|
||||
}
|
||||
|
||||
class FourPlayRu : ContentX() {
|
||||
override var name = "FourPlayRu"
|
||||
override var mainUrl = "https://four.playru.net"
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
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.INFER_TYPE
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
||||
open class Mediafire : ExtractorApi() {
|
||||
override val name = "Mediafire"
|
||||
override val mainUrl = "https://www.mediafire.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val res = app.get(url, referer = referer).document
|
||||
val title = res.select("div.dl-btn-label").text()
|
||||
val video = res.selectFirst("a#downloadButton")?.attr("href")
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
video ?: return,
|
||||
"",
|
||||
getQuality(title),
|
||||
INFER_TYPE
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private fun getQuality(str: String?): Int {
|
||||
return Regex("(\\d{3,4})[pP]").find(str ?: "")?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
?: Qualities.Unknown.value
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import android.util.Log
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
|
@ -12,19 +11,14 @@ open class PixelDrain : ExtractorApi() {
|
|||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
|
||||
val ext_ref = referer ?: ""
|
||||
val pixel_id = Regex("""([^\/]+)(?=\?download)""").find(url)?.groupValues?.get(1)
|
||||
val downloadLink = "${mainUrl}/api/file/${pixel_id}?download"
|
||||
Log.d("Kekik_${this.name}", "downloadLink » ${downloadLink}")
|
||||
|
||||
val mId = Regex("/([ul]/[\\da-zA-Z\\-]+)(?:\\?download)?").find(url)?.groupValues?.get(1)?.split("/")
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = "pixeldrain - ${pixel_id}",
|
||||
name = "pixeldrain - ${pixel_id}",
|
||||
url = downloadLink,
|
||||
referer = "${mainUrl}/u/${pixel_id}?download",
|
||||
quality = Qualities.Unknown.value,
|
||||
type = INFER_TYPE
|
||||
this.name,
|
||||
this.name,
|
||||
"$mainUrl/api/file/${mId?.last() ?: return}?download",
|
||||
url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException
|
|||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64DecodeArray
|
||||
import com.lagradost.cloudstream3.base64Encode
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
|
@ -16,13 +17,52 @@ import javax.crypto.Cipher
|
|||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
// Code found in https://github.com/Claudemirovsky/keys
|
||||
// special credits to @Claudemirovsky for providing key
|
||||
class Megacloud : Rabbitstream() {
|
||||
override val name = "Megacloud"
|
||||
override val mainUrl = "https://megacloud.tv"
|
||||
override val embed = "embed-2/ajax/e-1"
|
||||
override val key = "https://raw.githubusercontent.com/Claudemirovsky/keys/e1/key"
|
||||
private val scriptUrl = "$mainUrl/js/player/a/prod/e1-player.min.js"
|
||||
|
||||
override suspend fun extractRealKey(sources: String): Pair<String, String> {
|
||||
val rawKeys = getKeys()
|
||||
val sourcesArray = sources.toCharArray()
|
||||
|
||||
var extractedKey = ""
|
||||
var currentIndex = 0
|
||||
for (index in rawKeys) {
|
||||
val start = index[0] + currentIndex
|
||||
val end = start + index[1]
|
||||
for (i in start until end) {
|
||||
extractedKey += sourcesArray[i].toString()
|
||||
sourcesArray[i] = ' '
|
||||
}
|
||||
currentIndex += index[1]
|
||||
}
|
||||
|
||||
return extractedKey to sourcesArray.joinToString("").replace(" ", "")
|
||||
}
|
||||
|
||||
private suspend fun getKeys(): List<List<Int>> {
|
||||
val script = app.get(scriptUrl).text
|
||||
fun matchingKey(value: String): String {
|
||||
return Regex(",$value=((?:0x)?([0-9a-fA-F]+))").find(script)?.groupValues?.get(1)
|
||||
?.removePrefix("0x") ?: throw ErrorLoadingException("Failed to match the key")
|
||||
}
|
||||
|
||||
val regex = Regex("case\\s*0x[0-9a-f]+:(?![^;]*=partKey)\\s*\\w+\\s*=\\s*(\\w+)\\s*,\\s*\\w+\\s*=\\s*(\\w+);")
|
||||
val indexPairs = regex.findAll(script).toList().map { match ->
|
||||
val matchKey1 = matchingKey(match.groupValues[1])
|
||||
val matchKey2 = matchingKey(match.groupValues[2])
|
||||
try {
|
||||
listOf(matchKey1.toInt(16), matchKey2.toInt(16))
|
||||
} catch (e: NumberFormatException) {
|
||||
emptyList()
|
||||
}
|
||||
}.filter { it.isNotEmpty() }
|
||||
|
||||
return indexPairs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Dokicloud : Rabbitstream() {
|
||||
|
@ -30,12 +70,14 @@ class Dokicloud : Rabbitstream() {
|
|||
override val mainUrl = "https://dokicloud.one"
|
||||
}
|
||||
|
||||
// Code found in https://github.com/eatmynerds/key
|
||||
// special credits to @eatmynerds for providing key
|
||||
open class Rabbitstream : ExtractorApi() {
|
||||
override val name = "Rabbitstream"
|
||||
override val mainUrl = "https://rabbitstream.net"
|
||||
override val requiresReferer = false
|
||||
open val embed = "ajax/embed-4"
|
||||
open val key = "https://raw.githubusercontent.com/Claudemirovsky/keys/e4/key"
|
||||
open val key = "https://raw.githubusercontent.com/eatmynerds/key/e4/key.txt"
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
|
@ -56,7 +98,7 @@ open class Rabbitstream : ExtractorApi() {
|
|||
val decryptedSources = if (sources == null || encryptedMap.encrypted == false) {
|
||||
response.parsedSafe()
|
||||
} else {
|
||||
val (key, encData) = extractRealKey(sources, getRawKey())
|
||||
val (key, encData) = extractRealKey(sources)
|
||||
val decrypted = decryptMapped<List<Sources>>(encData, key)
|
||||
SourcesResponses(
|
||||
sources = decrypted,
|
||||
|
@ -75,8 +117,8 @@ open class Rabbitstream : ExtractorApi() {
|
|||
decryptedSources?.tracks?.map { track ->
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(
|
||||
track?.label ?: "",
|
||||
track?.file ?: return@map
|
||||
track?.label ?: return@map,
|
||||
track.file ?: return@map
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -84,25 +126,10 @@ open class Rabbitstream : ExtractorApi() {
|
|||
|
||||
}
|
||||
|
||||
private suspend fun getRawKey(): String = app.get(key).text
|
||||
|
||||
private fun extractRealKey(sources: String, stops: String): Pair<String, String> {
|
||||
val decryptKey = parseJson<List<List<Int>>>(stops)
|
||||
val sourcesArray = sources.toCharArray()
|
||||
|
||||
var extractedKey = ""
|
||||
var currentIndex = 0
|
||||
for (index in decryptKey) {
|
||||
val start = index[0] + currentIndex
|
||||
val end = start + index[1]
|
||||
for (i in start until end) {
|
||||
extractedKey += sourcesArray[i].toString()
|
||||
sourcesArray[i] = ' '
|
||||
}
|
||||
currentIndex += index[1]
|
||||
}
|
||||
|
||||
return extractedKey to sourcesArray.joinToString("")
|
||||
open suspend fun extractRealKey(sources: String): Pair<String, String> {
|
||||
val rawKeys = parseJson<List<Int>>(app.get(key).text)
|
||||
val extractedKey = base64Encode(rawKeys.map { it.toByte() }.toByteArray())
|
||||
return extractedKey to sources
|
||||
}
|
||||
|
||||
private inline fun <reified T> decryptMapped(input: String, key: String): T? {
|
||||
|
|
|
@ -9,6 +9,10 @@ class StreamTapeNet : StreamTape() {
|
|||
override var mainUrl = "https://streamtape.net"
|
||||
}
|
||||
|
||||
class StreamTapeXyz : StreamTape() {
|
||||
override var mainUrl = "https://streamtape.xyz"
|
||||
}
|
||||
|
||||
class ShaveTape : StreamTape(){
|
||||
override var mainUrl = "https://shavetape.cash"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
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
|
||||
|
||||
open class StreamWishExtractor : ExtractorApi() {
|
||||
override var name = "StreamWish"
|
||||
override var 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(
|
||||
ExtractorLink(
|
||||
source = name,
|
||||
name = name,
|
||||
url = response.url,
|
||||
referer = referer ?: "$mainUrl/",
|
||||
quality = Qualities.Unknown.value,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
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
|
||||
|
||||
open class VidhideExtractor : ExtractorApi() {
|
||||
override var name = "VidHide"
|
||||
override var mainUrl = "https://vidhide.com"
|
||||
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(
|
||||
ExtractorLink(
|
||||
source = name,
|
||||
name = name,
|
||||
url = response.url,
|
||||
referer = referer ?: "$mainUrl/",
|
||||
quality = Qualities.Unknown.value,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -10,13 +10,24 @@ import com.lagradost.cloudstream3.utils.M3u8Helper
|
|||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
// Code found in https://github.com/Claudemirovsky/worstsource-keys
|
||||
// special credits to @Claudemirovsky for providing key
|
||||
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys
|
||||
// special credits to @KillerDogeEmpire for providing key
|
||||
|
||||
class MyCloud : Vidplay() {
|
||||
override val name = "MyCloud"
|
||||
override val mainUrl = "https://mcloud.bz"
|
||||
}
|
||||
|
||||
class VidplayOnline : Vidplay() {
|
||||
override val mainUrl = "https://vidplay.online"
|
||||
}
|
||||
|
||||
open class Vidplay : ExtractorApi() {
|
||||
override val name = "Vidplay"
|
||||
override val mainUrl = "https://vidplay.site"
|
||||
override val requiresReferer = true
|
||||
open val key = "https://raw.githubusercontent.com/Claudemirovsky/worstsource-keys/keys/keys.json"
|
||||
open val key =
|
||||
"https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json"
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
|
@ -32,9 +43,9 @@ open class Vidplay : ExtractorApi() {
|
|||
"Accept" to "application/json, text/javascript, */*; q=0.01",
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
), referer = url
|
||||
).parsedSafe<Response>()?.result?.sources
|
||||
).parsedSafe<Response>()?.result
|
||||
|
||||
res?.map {
|
||||
res?.sources?.map {
|
||||
M3u8Helper.generateM3u8(
|
||||
this.name,
|
||||
it.file ?: return@map,
|
||||
|
@ -42,6 +53,12 @@ open class Vidplay : ExtractorApi() {
|
|||
).forEach(callback)
|
||||
}
|
||||
|
||||
res?.tracks?.filter { it.kind == "captions" }?.map {
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(it.label ?: return@map, it.file ?: return@map)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun getKeys(): List<String> {
|
||||
|
@ -77,12 +94,19 @@ open class Vidplay : ExtractorApi() {
|
|||
return base64Encode(input).replace("/", "_")
|
||||
}
|
||||
|
||||
data class Tracks(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
@JsonProperty("kind") val kind: String? = null,
|
||||
)
|
||||
|
||||
data class Sources(
|
||||
@JsonProperty("file") val file: String? = null,
|
||||
)
|
||||
|
||||
data class Result(
|
||||
@JsonProperty("sources") val sources: ArrayList<Sources>? = arrayListOf(),
|
||||
@JsonProperty("tracks") val tracks: ArrayList<Tracks>? = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Response(
|
||||
|
|
|
@ -70,19 +70,18 @@ open class YoutubeExtractor : ExtractorApi() {
|
|||
}
|
||||
}
|
||||
ytVideos[url]?.mapNotNull {
|
||||
if (it.isVideoOnly || it.height <= 0) return@mapNotNull null
|
||||
if (it.isVideoOnly() || it.height <= 0) return@mapNotNull null
|
||||
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
it.url ?: return@mapNotNull null,
|
||||
it.content ?: return@mapNotNull null,
|
||||
"",
|
||||
it.height
|
||||
)
|
||||
}?.forEach(callback)
|
||||
ytVideosSubtitles[url]?.mapNotNull {
|
||||
SubtitleFile(it.languageTag ?: return@mapNotNull null, it.url ?: return@mapNotNull null)
|
||||
SubtitleFile(it.languageTag ?: return@mapNotNull null, it.content ?: return@mapNotNull null)
|
||||
}?.forEach(subtitleCallback)
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.network
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.http.SslError
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.webkit.*
|
||||
import com.lagradost.cloudstream3.AcraApplication
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||
|
@ -27,16 +29,39 @@ import java.net.URI
|
|||
* @param additionalUrls this will make resolveUsingWebView also return all other requests matching the list of Regex.
|
||||
* @param userAgent if null then will use the default user agent
|
||||
* @param useOkhttp will try to use the okhttp client as much as possible, but this might cause some requests to fail. Disable for cloudflare.
|
||||
* @param script pass custom js to execute
|
||||
* @param scriptCallback will be called with the result from custom js
|
||||
* @param timeout close webview after timeout
|
||||
* */
|
||||
class WebViewResolver(
|
||||
val interceptUrl: Regex,
|
||||
val additionalUrls: List<Regex> = emptyList(),
|
||||
val userAgent: String? = USER_AGENT,
|
||||
val useOkhttp: Boolean = true
|
||||
val useOkhttp: Boolean = true,
|
||||
val script: String? = null,
|
||||
val scriptCallback: ((String) -> Unit)? = null,
|
||||
val timeout: Long = DEFAULT_TIMEOUT
|
||||
) :
|
||||
Interceptor {
|
||||
|
||||
constructor(
|
||||
interceptUrl: Regex,
|
||||
additionalUrls: List<Regex> = emptyList(),
|
||||
userAgent: String? = USER_AGENT,
|
||||
useOkhttp: Boolean = true,
|
||||
script: String? = null,
|
||||
scriptCallback: ((String) -> Unit)? = null,
|
||||
) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, script, scriptCallback, DEFAULT_TIMEOUT)
|
||||
|
||||
constructor(
|
||||
interceptUrl: Regex,
|
||||
additionalUrls: List<Regex> = emptyList(),
|
||||
userAgent: String? = USER_AGENT,
|
||||
useOkhttp: Boolean = true
|
||||
) : this(interceptUrl, additionalUrls, userAgent, useOkhttp, null, null, DEFAULT_TIMEOUT)
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_TIMEOUT = 60_000L
|
||||
var webViewUserAgent: String? = null
|
||||
|
||||
@JvmName("getWebViewUserAgent1")
|
||||
|
@ -136,6 +161,14 @@ class WebViewResolver(
|
|||
val webViewUrl = request.url.toString()
|
||||
println("Loading WebView URL: $webViewUrl")
|
||||
|
||||
if (script != null) {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
handler.post {
|
||||
view.evaluateJavascript("$script")
|
||||
{ scriptCallback?.invoke(it) }
|
||||
}
|
||||
}
|
||||
|
||||
if (interceptUrl.containsMatchIn(webViewUrl)) {
|
||||
fixedRequest = request.toRequest()?.also {
|
||||
requestCallBack(it)
|
||||
|
@ -241,7 +274,7 @@ class WebViewResolver(
|
|||
|
||||
var loop = 0
|
||||
// Timeouts after this amount, 60s
|
||||
val totalTime = 60000L
|
||||
val totalTime = timeout
|
||||
|
||||
val delayTime = 100L
|
||||
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
package com.lagradost.cloudstream3.subtitles
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.net.toUri
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleEntity
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities.SubtitleSearch
|
||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleOrigin
|
||||
import okio.BufferedSource
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import java.io.File
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
interface AbstractSubProvider {
|
||||
val idPrefix: String
|
||||
|
||||
@WorkerThread
|
||||
suspend fun search(query: SubtitleSearch): List<SubtitleEntity>? {
|
||||
throw NotImplementedError()
|
||||
|
@ -15,6 +27,98 @@ interface AbstractSubProvider {
|
|||
suspend fun load(data: SubtitleEntity): String? {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
suspend fun SubtitleResource.getResources(data: SubtitleEntity) {
|
||||
this.addUrl(load(data))
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
suspend fun getResource(data: SubtitleEntity): SubtitleResource {
|
||||
return SubtitleResource().apply {
|
||||
this.getResources(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for subtitle files.
|
||||
* @see addUrl
|
||||
* @see addFile
|
||||
*/
|
||||
class SubtitleResource {
|
||||
fun downloadFile(source: BufferedSource): File {
|
||||
val file = File.createTempFile("temp-subtitle", ".tmp").apply {
|
||||
deleteFileOnExit(this)
|
||||
}
|
||||
val sink = file.sink().buffer()
|
||||
sink.writeAll(source)
|
||||
sink.close()
|
||||
source.close()
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
fun unzip(file: File): List<Pair<String, File>> {
|
||||
val entries = mutableListOf<Pair<String, File>>()
|
||||
|
||||
ZipInputStream(file.inputStream()).use { zipInputStream ->
|
||||
var zipEntry = zipInputStream.nextEntry
|
||||
|
||||
while (zipEntry != null) {
|
||||
val tempFile = File.createTempFile("unzipped-subtitle", ".tmp").apply {
|
||||
deleteFileOnExit(this)
|
||||
}
|
||||
entries.add(zipEntry.name to tempFile)
|
||||
|
||||
tempFile.sink().buffer().use { buffer ->
|
||||
buffer.writeAll(zipInputStream.source())
|
||||
}
|
||||
|
||||
zipEntry = zipInputStream.nextEntry
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
data class SingleSubtitleResource(
|
||||
val name: String?,
|
||||
val url: String,
|
||||
val origin: SubtitleOrigin
|
||||
)
|
||||
|
||||
private var resources: MutableList<SingleSubtitleResource> = mutableListOf()
|
||||
|
||||
fun getSubtitles(): List<SingleSubtitleResource> {
|
||||
return resources.toList()
|
||||
}
|
||||
|
||||
fun addUrl(url: String?, name: String? = null) {
|
||||
if (url == null) return
|
||||
this.resources.add(
|
||||
SingleSubtitleResource(name, url, SubtitleOrigin.URL)
|
||||
)
|
||||
}
|
||||
|
||||
fun addFile(file: File, name: String? = null) {
|
||||
this.resources.add(
|
||||
SingleSubtitleResource(name, file.toUri().toString(), SubtitleOrigin.DOWNLOADED_FILE)
|
||||
)
|
||||
deleteFileOnExit(file)
|
||||
}
|
||||
|
||||
suspend fun addZipUrl(
|
||||
url: String,
|
||||
nameGenerator: (String, File) -> String? = { _, _ -> null }
|
||||
) {
|
||||
val source = app.get(url).okhttpResponse.body.source()
|
||||
val zip = downloadFile(source)
|
||||
val realFiles = unzip(zip)
|
||||
zip.deleteRecursively()
|
||||
realFiles.forEach { (name, subtitleFile) ->
|
||||
addFile(subtitleFile, nameGenerator(name, subtitleFile))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface AbstractSubApi : AbstractSubProvider, AuthAPI
|
|
@ -3,6 +3,7 @@ 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
|
||||
|
||||
|
@ -14,6 +15,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
val simklApi = SimklApi(0)
|
||||
val indexSubtitlesApi = IndexSubtitleApi()
|
||||
val addic7ed = Addic7ed()
|
||||
val subScene = SubScene()
|
||||
val localListApi = LocalList()
|
||||
|
||||
// used to login via app intent
|
||||
|
@ -41,7 +43,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
get() = listOf(
|
||||
openSubtitlesApi,
|
||||
indexSubtitlesApi, // they got anti scraping measures in place :(
|
||||
addic7ed
|
||||
addic7ed,
|
||||
subScene
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
|
|
@ -23,6 +23,47 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
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 {
|
||||
|
@ -44,47 +85,6 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
}
|
||||
}
|
||||
|
||||
private 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 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]))")
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import android.widget.*
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.kotlinModule
|
||||
import com.google.android.gms.cast.MediaQueueItem
|
||||
import com.google.android.gms.cast.MediaSeekOptions
|
||||
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
|
||||
|
@ -98,7 +98,7 @@ data class MetadataHolder(
|
|||
|
||||
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) :
|
||||
UIController() {
|
||||
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
private val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||
|
||||
init {
|
||||
|
|
|
@ -216,6 +216,9 @@ class HomeParentItemAdapterPreview(
|
|||
viewModel.click(callback)
|
||||
return@HomeChildItemAdapter
|
||||
}
|
||||
|
||||
(callback.view.context?.getActivity() as? MainActivity)?.loadPopup(callback.card, load = false)
|
||||
/*
|
||||
callback.view.context?.getActivity()?.showOptionSelectStringRes(
|
||||
callback.view,
|
||||
callback.card.posterUrl,
|
||||
|
@ -261,6 +264,7 @@ class HomeParentItemAdapterPreview(
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -313,7 +317,7 @@ class HomeParentItemAdapterPreview(
|
|||
homePreviewText.text = item.name
|
||||
populateChips(
|
||||
homePreviewTags,
|
||||
item.tags ?: emptyList(),
|
||||
item.tags?.take(6) ?: emptyList(),
|
||||
R.style.ChipFilledSemiTransparent
|
||||
)
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
binding.homeScrollPreviewTags.apply {
|
||||
text = card.tags?.joinToString(" • ") ?: ""
|
||||
isGone = card.tags.isNullOrEmpty()
|
||||
maxLines = 2
|
||||
}
|
||||
binding.homeScrollPreviewTitle.text = card.name
|
||||
}
|
||||
|
|
|
@ -20,11 +20,12 @@ import android.widget.Toast
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.allViews
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
|
@ -35,6 +36,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
|||
import com.lagradost.cloudstream3.CommonActivity
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||
|
@ -80,6 +82,8 @@ data class ProviderLibraryData(
|
|||
|
||||
class LibraryFragment : Fragment() {
|
||||
companion object {
|
||||
|
||||
val listLibraryItems = mutableListOf<SyncAPI.LibraryItem>()
|
||||
fun newInstance() = LibraryFragment()
|
||||
|
||||
/**
|
||||
|
@ -91,6 +95,7 @@ class LibraryFragment : Fragment() {
|
|||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
var binding: FragmentLibraryBinding? = null
|
||||
private var toggleRandomButton = false
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
|
@ -196,6 +201,25 @@ class LibraryFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
//Load value for toggling Random button. Hide at startup
|
||||
context?.let {
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
|
||||
toggleRandomButton =
|
||||
settingsManager.getBoolean(
|
||||
getString(R.string.random_button_key),
|
||||
false
|
||||
) && !SettingsFragment.isTvSettings()
|
||||
binding?.libraryRandom?.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding?.libraryRandom?.setOnClickListener {
|
||||
if (listLibraryItems.isNotEmpty()) {
|
||||
val listLibraryItem = listLibraryItems.random()
|
||||
libraryViewModel.currentSyncApi?.syncIdName?.let {
|
||||
loadLibraryItem(it, listLibraryItem.syncId,listLibraryItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a plugin selection dialogue and saves the response
|
||||
|
@ -277,8 +301,10 @@ class LibraryFragment : Fragment() {
|
|||
{ isScrollingDown: Boolean ->
|
||||
if (isScrollingDown) {
|
||||
binding?.sortFab?.shrink()
|
||||
binding?.libraryRandom?.shrink()
|
||||
} else {
|
||||
binding?.sortFab?.extend()
|
||||
binding?.libraryRandom?.extend()
|
||||
}
|
||||
}) callback@{ searchClickCallback ->
|
||||
// To prevent future accidents
|
||||
|
@ -303,52 +329,7 @@ class LibraryFragment : Fragment() {
|
|||
}
|
||||
|
||||
SEARCH_ACTION_LOAD -> {
|
||||
// This basically first selects the individual opener and if that is default then
|
||||
// selects the whole list opener
|
||||
val savedListSelection =
|
||||
getKey<LibraryOpener>("$currentAccount/$LIBRARY_FOLDER", syncName.name)
|
||||
val savedSelection = getKey<LibraryOpener>(
|
||||
"$currentAccount/$LIBRARY_FOLDER",
|
||||
syncId
|
||||
).takeIf {
|
||||
it?.openType != LibraryOpenerType.Default
|
||||
} ?: savedListSelection
|
||||
|
||||
when (savedSelection?.openType) {
|
||||
null, LibraryOpenerType.Default -> {
|
||||
// Prevents opening MAL/AniList as a provider
|
||||
if (APIHolder.getApiFromNameNull(searchClickCallback.card.apiName) != null) {
|
||||
activity?.loadSearchResult(
|
||||
searchClickCallback.card
|
||||
)
|
||||
} else {
|
||||
// Search when no provider can open
|
||||
QuickSearchFragment.pushSearch(
|
||||
activity,
|
||||
searchClickCallback.card.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LibraryOpenerType.None -> {}
|
||||
LibraryOpenerType.Provider ->
|
||||
savedSelection.providerData?.apiName?.let { apiName ->
|
||||
activity?.loadResult(
|
||||
searchClickCallback.card.url,
|
||||
apiName,
|
||||
)
|
||||
}
|
||||
|
||||
LibraryOpenerType.Browser ->
|
||||
openBrowser(searchClickCallback.card.url)
|
||||
|
||||
LibraryOpenerType.Search -> {
|
||||
QuickSearchFragment.pushSearch(
|
||||
activity,
|
||||
searchClickCallback.card.name
|
||||
)
|
||||
}
|
||||
}
|
||||
loadLibraryItem(syncName, syncId, searchClickCallback.card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -414,6 +395,16 @@ class LibraryFragment : Fragment() {
|
|||
binding?.viewpager?.setCurrentItem(page, false)
|
||||
}
|
||||
|
||||
observe(libraryViewModel.currentPage){
|
||||
if (toggleRandomButton) {
|
||||
listLibraryItems.clear()
|
||||
listLibraryItems.addAll(pages[it].items)
|
||||
libraryRandom.isVisible = listLibraryItems.isNotEmpty()
|
||||
} else {
|
||||
libraryRandom.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
|
||||
// Without this there would be a flashing effect:
|
||||
// loading -> show old viewpager -> black screen -> show new viewpager
|
||||
|
@ -512,6 +503,62 @@ class LibraryFragment : Fragment() {
|
|||
}
|
||||
})*/
|
||||
}
|
||||
|
||||
private fun loadLibraryItem(
|
||||
syncName: SyncIdName,
|
||||
syncId: String,
|
||||
card: SearchResponse
|
||||
) {
|
||||
// This basically first selects the individual opener and if that is default then
|
||||
// selects the whole list opener
|
||||
val savedListSelection =
|
||||
getKey<LibraryOpener>("$currentAccount/$LIBRARY_FOLDER", syncName.name)
|
||||
|
||||
val savedSelection = getKey<LibraryOpener>(
|
||||
"$currentAccount/$LIBRARY_FOLDER",
|
||||
syncId
|
||||
).takeIf {
|
||||
it?.openType != LibraryOpenerType.Default
|
||||
} ?: savedListSelection
|
||||
|
||||
when (savedSelection?.openType) {
|
||||
null, LibraryOpenerType.Default -> {
|
||||
// Prevents opening MAL/AniList as a provider
|
||||
if (APIHolder.getApiFromNameNull(card.apiName) != null) {
|
||||
activity?.loadSearchResult(
|
||||
card
|
||||
)
|
||||
} else {
|
||||
// Search when no provider can open
|
||||
QuickSearchFragment.pushSearch(
|
||||
activity,
|
||||
card.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LibraryOpenerType.None -> {}
|
||||
LibraryOpenerType.Provider ->
|
||||
savedSelection.providerData?.apiName?.let { apiName ->
|
||||
activity?.loadResult(
|
||||
card.url,
|
||||
apiName,
|
||||
)
|
||||
}
|
||||
|
||||
LibraryOpenerType.Browser ->
|
||||
openBrowser(card.url)
|
||||
|
||||
LibraryOpenerType.Search -> {
|
||||
QuickSearchFragment.pushSearch(
|
||||
activity,
|
||||
card.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
|
|
@ -102,6 +102,8 @@ abstract class AbstractPlayerFragment(
|
|||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
open fun playerStatusChanged(){}
|
||||
|
||||
open fun playerDimensionsLoaded(width: Int, height: Int) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
@ -431,6 +433,7 @@ abstract class AbstractPlayerFragment(
|
|||
|
||||
is StatusEvent -> {
|
||||
updateIsPlaying(wasPlaying = event.wasPlaying, isPlaying = event.isPlaying)
|
||||
playerStatusChanged()
|
||||
}
|
||||
|
||||
is PositionEvent -> {
|
||||
|
|
|
@ -50,6 +50,7 @@ import androidx.preference.PreferenceManager
|
|||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
|
||||
import com.lagradost.cloudstream3.USER_AGENT
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||
|
@ -657,7 +658,7 @@ class CS3IPlayer : IPlayer {
|
|||
SimpleCache(
|
||||
File(
|
||||
context.cacheDir, "exoplayer"
|
||||
).also { it.deleteOnExit() }, // Ensures always fresh file
|
||||
).also { deleteFileOnExit(it) }, // Ensures always fresh file
|
||||
LeastRecentlyUsedCacheEvictor(cacheSize),
|
||||
databaseProvider
|
||||
)
|
||||
|
|
|
@ -7,14 +7,13 @@ import android.content.Context
|
|||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.Editable
|
||||
import android.util.DisplayMetrics
|
||||
import android.text.format.DateUtils
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
|
@ -31,11 +30,10 @@ import androidx.core.graphics.blue
|
|||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
|
||||
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
|
||||
import com.lagradost.cloudstream3.CommonActivity.screenHeight
|
||||
|
@ -48,6 +46,7 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvid
|
|||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||
import com.lagradost.cloudstream3.ui.result.setText
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||
|
@ -59,10 +58,10 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||
import com.lagradost.cloudstream3.utils.UserPreferenceDelegate
|
||||
import com.lagradost.cloudstream3.utils.Vector2
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
|
||||
const val MINIMUM_VERTICAL_SWIPE = 2.0f // in percentage
|
||||
const val MINIMUM_HORIZONTAL_SWIPE = 2.0f // in percentage
|
||||
|
@ -79,10 +78,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
protected open var lockRotation = true
|
||||
protected open var isFullScreenPlayer = true
|
||||
protected open var isTv = false
|
||||
|
||||
protected var playerBinding: PlayerCustomLayoutBinding? = null
|
||||
|
||||
|
||||
private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false)
|
||||
// state of player UI
|
||||
protected var isShowing = false
|
||||
protected var isLocked = false
|
||||
|
@ -379,7 +377,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
}
|
||||
|
||||
protected fun exitFullscreen() {
|
||||
activity?.showSystemUI()
|
||||
//if (lockRotation)
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
|
||||
|
||||
|
@ -391,6 +388,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|
||||
}
|
||||
activity?.window?.attributes = lp
|
||||
activity?.showSystemUI()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -1332,15 +1330,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
} else false
|
||||
}
|
||||
|
||||
//player_episodes_button?.setOnClickListener {
|
||||
// player_episodes_button?.isGone = true
|
||||
// player_episode_list?.isVisible = true
|
||||
//}
|
||||
//
|
||||
//player_episode_list?.adapter = PlayerEpisodeAdapter { click ->
|
||||
//
|
||||
//}
|
||||
|
||||
try {
|
||||
context?.let { ctx ->
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
|
@ -1425,12 +1414,21 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
||||
playerBinding?.apply {
|
||||
playerPausePlay.setOnClickListener {
|
||||
autoHide()
|
||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
||||
}
|
||||
|
||||
exoDuration.setOnClickListener {
|
||||
setRemainingTimeCounter(true)
|
||||
}
|
||||
|
||||
timeLeft.setOnClickListener {
|
||||
setRemainingTimeCounter(false)
|
||||
}
|
||||
|
||||
skipChapterButton.setOnClickListener {
|
||||
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
|
||||
}
|
||||
|
@ -1515,32 +1513,14 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
return@setOnTouchListener false
|
||||
}
|
||||
}
|
||||
// cs3 is peak media center
|
||||
setRemainingTimeCounter(durationMode || SettingsFragment.isTrueTvSettings())
|
||||
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
|
||||
updateRemainingTime()
|
||||
}
|
||||
// init UI
|
||||
try {
|
||||
uiReset()
|
||||
|
||||
// init chromecast UI
|
||||
// removed due to having no use and bugging
|
||||
//activity?.let {
|
||||
// if (it.isCastApiAvailable()) {
|
||||
// try {
|
||||
// CastButtonFactory.setUpMediaRouteButton(it, player_media_route_button)
|
||||
// val castContext = CastContext.getSharedInstance(it.applicationContext)
|
||||
//
|
||||
// player_media_route_button?.isGone =
|
||||
// castContext.castState == CastState.NO_DEVICES_AVAILABLE
|
||||
// castContext.addCastStateListener { state ->
|
||||
// player_media_route_button?.isGone =
|
||||
// state == CastState.NO_DEVICES_AVAILABLE
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// logError(e)
|
||||
// }
|
||||
// } else {
|
||||
// // if cast is not possible hide UI
|
||||
// player_media_route_button?.isGone = true
|
||||
// }
|
||||
//}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
@ -1558,6 +1538,24 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
updateOrientation()
|
||||
}
|
||||
|
||||
private fun updateRemainingTime() {
|
||||
val duration = player.getDuration()
|
||||
val position = player.getPosition()
|
||||
|
||||
if (duration != null && duration > 1 && position != null) {
|
||||
val remainingTimeSeconds = (duration - position + 500) / 1000
|
||||
val formattedTime = "-${DateUtils.formatElapsedTime(remainingTimeSeconds)}"
|
||||
|
||||
playerBinding?.timeLeft?.text = formattedTime
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRemainingTimeCounter(showRemaining: Boolean) {
|
||||
durationMode = showRemaining
|
||||
playerBinding?.exoDuration?.isInvisible= showRemaining
|
||||
playerBinding?.timeLeft?.isVisible = showRemaining
|
||||
}
|
||||
|
||||
private fun dynamicOrientation(): Int {
|
||||
return if (autoPlayerRotateEnabled) {
|
||||
if (isVerticalOrientation) {
|
||||
|
@ -1569,4 +1567,4 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE // default orientation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
|
|||
import com.lagradost.cloudstream3.databinding.PlayerSelectSourceAndSubsBinding
|
||||
import com.lagradost.cloudstream3.databinding.PlayerSelectTracksBinding
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
|
||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders
|
||||
import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage
|
||||
|
@ -69,7 +70,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
val subsProviders
|
||||
get() = subtitleProviders.filter { !it.requiresLogin || it.loginInfo() != null }
|
||||
get() = subtitleProviders.filter { provider ->
|
||||
(provider as? AbstractSubApi)?.let { !it.requiresLogin || it.loginInfo() != null }
|
||||
?: true
|
||||
}
|
||||
val subsProvidersIsActive
|
||||
get() = subsProviders.isNotEmpty()
|
||||
}
|
||||
|
@ -146,6 +150,12 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun playerStatusChanged() {
|
||||
if (player.getIsPlaying()) {
|
||||
viewModel.forceClearCache = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun noSubtitles(): Boolean {
|
||||
return setSubtitles(null)
|
||||
}
|
||||
|
@ -467,17 +477,21 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
currentSubtitle?.let { currentSubtitle ->
|
||||
providers.firstOrNull { it.idPrefix == currentSubtitle.idPrefix }?.let { api ->
|
||||
ioSafe {
|
||||
val url = api.load(currentSubtitle) ?: return@ioSafe
|
||||
val subtitle = SubtitleData(
|
||||
name = getName(currentSubtitle, true),
|
||||
url = url,
|
||||
origin = SubtitleOrigin.URL,
|
||||
mimeType = url.toSubtitleMimeType(),
|
||||
headers = currentSubtitle.headers,
|
||||
currentSubtitle.lang
|
||||
)
|
||||
runOnMainThread {
|
||||
addAndSelectSubtitles(subtitle)
|
||||
val subtitles =
|
||||
api.getResource(currentSubtitle).getSubtitles().map { resource ->
|
||||
SubtitleData(
|
||||
name = resource.name ?: getName(currentSubtitle, true),
|
||||
url = resource.url,
|
||||
origin = resource.origin,
|
||||
mimeType = resource.url.toSubtitleMimeType(),
|
||||
headers = currentSubtitle.headers,
|
||||
currentSubtitle.lang
|
||||
)
|
||||
}
|
||||
if (subtitles.isNotEmpty()) {
|
||||
runOnMainThread {
|
||||
addAndSelectSubtitles(*subtitles.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -515,7 +529,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun addAndSelectSubtitles(subtitleData: SubtitleData) {
|
||||
private fun addAndSelectSubtitles(
|
||||
vararg subtitleData: SubtitleData
|
||||
) {
|
||||
if (subtitleData.isEmpty()) return
|
||||
val selectedSubtitle = subtitleData.first()
|
||||
val ctx = context ?: return
|
||||
|
||||
val subs = currentSubs + subtitleData
|
||||
|
@ -527,13 +545,13 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
player.saveData()
|
||||
player.reloadPlayer(ctx)
|
||||
|
||||
setSubtitles(subtitleData)
|
||||
viewModel.addSubtitles(setOf(subtitleData))
|
||||
setSubtitles(selectedSubtitle)
|
||||
viewModel.addSubtitles(subtitleData.toSet())
|
||||
|
||||
selectSourceDialog?.dismissSafe()
|
||||
|
||||
showToast(
|
||||
String.format(ctx.getString(R.string.player_loaded_subtitles), subtitleData.name),
|
||||
String.format(ctx.getString(R.string.player_loaded_subtitles), selectedSubtitle.name),
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
|
@ -913,10 +931,15 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
override fun playerError(exception: Throwable) {
|
||||
Log.i(TAG, "playerError = $currentSelectedLink")
|
||||
if (!hasNextMirror()) {
|
||||
viewModel.forceClearCache = true
|
||||
}
|
||||
super.playerError(exception)
|
||||
}
|
||||
|
||||
private fun noLinksFound() {
|
||||
viewModel.forceClearCache = true
|
||||
|
||||
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
|
@ -1383,6 +1406,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
binding?.playerLoadingGoBack?.setOnClickListener {
|
||||
exitFullscreen()
|
||||
player.release()
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
*/
|
||||
private var currentLoadingEpisodeId: Int? = null
|
||||
|
||||
var forceClearCache = false
|
||||
|
||||
fun setSubtitleYear(year: Int?) {
|
||||
_currentSubtitleYear.postValue(year)
|
||||
}
|
||||
|
@ -168,7 +170,7 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun loadLinks(clearCache: Boolean = false, type: LoadType = LoadType.InApp) {
|
||||
fun loadLinks(type: LoadType = LoadType.InApp) {
|
||||
Log.i(TAG, "loadLinks")
|
||||
currentJob?.cancel()
|
||||
|
||||
|
@ -183,7 +185,7 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
// load more data
|
||||
_loadingLinks.postValue(Resource.Loading())
|
||||
val loadingState = safeApiCall {
|
||||
generator?.generateLinks(type = type, clearCache = clearCache, callback = {
|
||||
generator?.generateLinks(type = type, clearCache = forceClearCache, callback = {
|
||||
currentLinks.add(it)
|
||||
// Clone to prevent ConcurrentModificationException
|
||||
normalSafeApiCall {
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
|
|||
|
||||
import android.util.Log
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||
|
@ -10,6 +11,12 @@ import com.lagradost.cloudstream3.utils.ExtractorUri
|
|||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
data class Cache(
|
||||
val linkCache: MutableSet<ExtractorLink>,
|
||||
val subtitleCache: MutableSet<SubtitleData>,
|
||||
var lastCachedTimestamp: Long = unixTime
|
||||
)
|
||||
|
||||
class RepoLinkGenerator(
|
||||
private val episodes: List<ResultEpisode>,
|
||||
private var currentIndex: Int = 0,
|
||||
|
@ -17,7 +24,7 @@ class RepoLinkGenerator(
|
|||
) : IGenerator {
|
||||
companion object {
|
||||
const val TAG = "RepoLink"
|
||||
val cache: HashMap<Pair<String, Int>, Pair<MutableSet<ExtractorLink>, MutableSet<SubtitleData>>> =
|
||||
val cache: HashMap<Pair<String, Int>, Cache> =
|
||||
hashMapOf()
|
||||
}
|
||||
|
||||
|
@ -76,10 +83,10 @@ class RepoLinkGenerator(
|
|||
val index = currentIndex
|
||||
val current = episodes.getOrNull(index + offset) ?: return false
|
||||
|
||||
val (currentLinkCache, currentSubsCache) = if (clearCache) {
|
||||
Pair(mutableSetOf(), mutableSetOf())
|
||||
val (currentLinkCache, currentSubsCache, lastCachedTimestamp) = if (clearCache) {
|
||||
Cache(mutableSetOf(), mutableSetOf(), unixTime)
|
||||
} else {
|
||||
cache[current.apiName to current.id] ?: Pair(mutableSetOf(), mutableSetOf())
|
||||
cache[current.apiName to current.id] ?: Cache(mutableSetOf(), mutableSetOf(), unixTime)
|
||||
}
|
||||
|
||||
//val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet()
|
||||
|
@ -89,6 +96,12 @@ class RepoLinkGenerator(
|
|||
val currentSubsUrls = mutableSetOf<String>() // makes all subs urls unique
|
||||
val currentSubsNames = mutableSetOf<String>() // makes all subs names unique
|
||||
|
||||
val invalidateCache = unixTime - lastCachedTimestamp > 60 * 20 // 20 minutes
|
||||
if(invalidateCache){
|
||||
currentLinkCache.clear()
|
||||
currentSubsCache.clear()
|
||||
}
|
||||
|
||||
currentLinkCache.filter { allowedTypes.contains(it.type) }.forEach { link ->
|
||||
currentLinks.add(link.url)
|
||||
callback(link to null)
|
||||
|
@ -147,7 +160,7 @@ class RepoLinkGenerator(
|
|||
}
|
||||
}
|
||||
)
|
||||
cache[Pair(current.apiName, current.id)] = Pair(currentLinkCache, currentSubsCache)
|
||||
cache[Pair(current.apiName, current.id)] = Cache(currentLinkCache, currentSubsCache, unixTime)
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ package com.lagradost.cloudstream3.ui.result
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Rect
|
||||
|
@ -31,6 +34,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
|||
import com.lagradost.cloudstream3.APIHolder
|
||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||
import com.lagradost.cloudstream3.CommonActivity
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||
|
@ -77,7 +81,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
|
||||
|
||||
open class ResultFragmentPhone : FullScreenPlayer() {
|
||||
private val gestureRegionsListener = object : PanelsChildGestureRegionObserver.GestureRegionsListener {
|
||||
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
|
||||
|
@ -247,6 +250,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
var selectSeason: String? = null
|
||||
var selectEpisodeRange: String? = null
|
||||
|
||||
private fun setUrl(url: String?) {
|
||||
if (url == null) {
|
||||
|
@ -751,6 +755,17 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
resultLoadingError.isVisible = data is Resource.Failure
|
||||
resultErrorText.isVisible = data is Resource.Failure
|
||||
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
|
||||
|
||||
resultTitle.setOnLongClickListener {
|
||||
val titleToCopy = resultTitle.text
|
||||
val clipboardManager =
|
||||
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
|
||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText("Title", titleToCopy))
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||
showToast(R.string.copyTitle, Toast.LENGTH_SHORT)
|
||||
}
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1027,6 +1042,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
observeNullable(viewModel.selectedRange) { range ->
|
||||
resultBinding?.apply {
|
||||
resultEpisodeSelect.setText(range)
|
||||
|
||||
selectEpisodeRange = range?.asStringNull(resultEpisodeSelect.context)
|
||||
// If Season button is invisible then the bookmark button next focus is episode select
|
||||
if (resultEpisodeSelect.isVisible && !resultSeasonButton.isVisible && resultResumeParent.isVisible) {
|
||||
setFocusUpAndDown(resultResumeSeriesButton, resultEpisodeSelect)
|
||||
|
@ -1060,9 +1077,12 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
|
||||
}
|
||||
|
||||
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
|
||||
index to name
|
||||
}) {
|
||||
activity?.showDialog(
|
||||
names.map { it.second },
|
||||
names.indexOfFirst { it.second == selectEpisodeRange },
|
||||
"",
|
||||
false,
|
||||
{}) { itemId ->
|
||||
viewModel.changeRange(names[itemId].first)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1006,6 +1006,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
removeFavoritesData(currentId)
|
||||
statusChangedCallback?.invoke(false)
|
||||
_favoriteStatus.postValue(false)
|
||||
MainActivity.reloadLibraryEvent(true)
|
||||
} else {
|
||||
checkAndWarnDuplicates(
|
||||
context,
|
||||
|
@ -1050,8 +1051,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
|
||||
_favoriteStatus.postValue(true)
|
||||
|
||||
statusChangedCallback?.invoke(true)
|
||||
MainActivity.reloadLibraryEvent(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2604,6 +2605,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
|
||||
this.tags = searchResponse.tags
|
||||
}
|
||||
if (searchResponse is DataStoreHelper.BookmarkedData) {
|
||||
this.plot = searchResponse.plot
|
||||
this.rating = searchResponse.rating
|
||||
this.tags = searchResponse.tags
|
||||
}
|
||||
}
|
||||
val mainId = searchResponse.id ?: response.getId()
|
||||
|
||||
|
|
|
@ -264,6 +264,9 @@ class SearchFragment : Fragment() {
|
|||
builder.setContentView(selectMainpageBinding.root)
|
||||
builder.show()
|
||||
builder.let { dialog ->
|
||||
val previousSelectedApis = selectedApis.toSet()
|
||||
val previousSelectedSearchTypes = selectedSearchTypes.toSet()
|
||||
|
||||
val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
|
||||
set.size > 1 || set.contains(AllLanguagesName)
|
||||
}
|
||||
|
@ -352,7 +355,9 @@ class SearchFragment : Fragment() {
|
|||
selectedApis = currentSelectedApis
|
||||
|
||||
// run search when dialog is close
|
||||
search(binding?.mainSearch?.query?.toString())
|
||||
if(previousSelectedApis != selectedApis.toSet() || previousSelectedSearchTypes != selectedSearchTypes.toSet()) {
|
||||
search(binding?.mainSearch?.query?.toString())
|
||||
}
|
||||
}
|
||||
updateList(selectedSearchTypes.toList())
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ class PluginsViewModel : ViewModel() {
|
|||
PluginManager.downloadPlugin(
|
||||
activity,
|
||||
metadata.url,
|
||||
metadata.name,
|
||||
metadata.internalName,
|
||||
repo,
|
||||
isEnabled
|
||||
) to message
|
||||
|
@ -257,4 +257,4 @@ class PluginsViewModel : ViewModel() {
|
|||
false to downloadedPlugins.filterTvTypes().filterLang().sortByQuery(currentQuery)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.extensions
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
||||
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
|
||||
|
@ -112,6 +119,17 @@ class RepoAdapter(
|
|||
repositoryItemRoot.setOnClickListener {
|
||||
clickCallback(repositoryData)
|
||||
}
|
||||
|
||||
repositoryItemRoot.setOnLongClickListener {
|
||||
val clipboardManager =
|
||||
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
|
||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText("RepoUrl", repositoryData.url))
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||
showToast(R.string.copyRepoUrl, Toast.LENGTH_SHORT)
|
||||
}
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
mainText.text = repositoryData.name
|
||||
subText.text = repositoryData.url
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.content.SharedPreferences
|
|||
import androidx.preference.PreferenceManager
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.kotlinModule
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeyClass
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKeyClass
|
||||
|
@ -51,7 +51,7 @@ class PreferenceDelegate<T : Any>(
|
|||
}
|
||||
|
||||
object DataStore {
|
||||
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||
|
||||
private fun getPreferences(context: Context): SharedPreferences {
|
||||
|
|
|
@ -3,18 +3,24 @@ package com.lagradost.cloudstream3.utils
|
|||
class Event<T> {
|
||||
private val observers = mutableSetOf<(T) -> Unit>()
|
||||
|
||||
val size : Int get() = observers.size
|
||||
val size: Int get() = observers.size
|
||||
|
||||
operator fun plusAssign(observer: (T) -> Unit) {
|
||||
observers.add(observer)
|
||||
synchronized(observers) {
|
||||
observers.add(observer)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun minusAssign(observer: (T) -> Unit) {
|
||||
observers.remove(observer)
|
||||
synchronized(observers) {
|
||||
observers.remove(observer)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun invoke(value: T) {
|
||||
for (observer in observers)
|
||||
observer(value)
|
||||
synchronized(observers) {
|
||||
for (observer in observers)
|
||||
observer(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,13 +96,18 @@ import com.lagradost.cloudstream3.extractors.Moviesm4u
|
|||
import com.lagradost.cloudstream3.extractors.Mp4Upload
|
||||
import com.lagradost.cloudstream3.extractors.Mvidoo
|
||||
import com.lagradost.cloudstream3.extractors.MwvnVizcloudInfo
|
||||
import com.lagradost.cloudstream3.extractors.MyCloud
|
||||
import com.lagradost.cloudstream3.extractors.Neonime7n
|
||||
import com.lagradost.cloudstream3.extractors.Neonime8n
|
||||
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.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.HDMomPlayer
|
||||
import com.lagradost.cloudstream3.extractors.HDPlayerSystem
|
||||
import com.lagradost.cloudstream3.extractors.VideoSeyred
|
||||
|
@ -113,6 +118,7 @@ import com.lagradost.cloudstream3.extractors.TRsTX
|
|||
import com.lagradost.cloudstream3.extractors.VidMoxy
|
||||
import com.lagradost.cloudstream3.extractors.PixelDrain
|
||||
import com.lagradost.cloudstream3.extractors.MailRu
|
||||
import com.lagradost.cloudstream3.extractors.Mediafire
|
||||
import com.lagradost.cloudstream3.extractors.OkRuSSL
|
||||
import com.lagradost.cloudstream3.extractors.OkRuHTTP
|
||||
import com.lagradost.cloudstream3.extractors.Okrulink
|
||||
|
@ -150,6 +156,8 @@ import com.lagradost.cloudstream3.extractors.StreamSB8
|
|||
import com.lagradost.cloudstream3.extractors.StreamSB9
|
||||
import com.lagradost.cloudstream3.extractors.StreamTape
|
||||
import com.lagradost.cloudstream3.extractors.StreamTapeNet
|
||||
import com.lagradost.cloudstream3.extractors.StreamTapeXyz
|
||||
import com.lagradost.cloudstream3.extractors.StreamWishExtractor
|
||||
import com.lagradost.cloudstream3.extractors.StreamhideCom
|
||||
import com.lagradost.cloudstream3.extractors.StreamhideTo
|
||||
import com.lagradost.cloudstream3.extractors.Streamhub2
|
||||
|
@ -178,10 +186,12 @@ import com.lagradost.cloudstream3.extractors.VideoVard
|
|||
import com.lagradost.cloudstream3.extractors.VideovardSX
|
||||
import com.lagradost.cloudstream3.extractors.Vidgomunime
|
||||
import com.lagradost.cloudstream3.extractors.Vidgomunimesb
|
||||
import com.lagradost.cloudstream3.extractors.VidhideExtractor
|
||||
import com.lagradost.cloudstream3.extractors.Vidmoly
|
||||
import com.lagradost.cloudstream3.extractors.Vidmolyme
|
||||
import com.lagradost.cloudstream3.extractors.Vido
|
||||
import com.lagradost.cloudstream3.extractors.Vidplay
|
||||
import com.lagradost.cloudstream3.extractors.VidplayOnline
|
||||
import com.lagradost.cloudstream3.extractors.Vidstreamz
|
||||
import com.lagradost.cloudstream3.extractors.Vizcloud
|
||||
import com.lagradost.cloudstream3.extractors.Vizcloud2
|
||||
|
@ -210,6 +220,7 @@ import com.lagradost.cloudstream3.extractors.Ztreamhub
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import kotlinx.coroutines.delay
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import org.jsoup.Jsoup
|
||||
import java.net.URL
|
||||
import java.util.UUID
|
||||
|
@ -594,6 +605,18 @@ suspend fun loadExtractor(
|
|||
}
|
||||
}
|
||||
|
||||
// this is to match mirror domains - like example.com, example.net
|
||||
for (extractor in extractorApis) {
|
||||
if (FuzzySearch.partialRatio(
|
||||
extractor.mainUrl,
|
||||
currentUrl
|
||||
) > 80
|
||||
) {
|
||||
extractor.getSafeUrl(currentUrl, referer, subtitleCallback, callback)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -617,6 +640,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
StreamTape(),
|
||||
StreamTapeNet(),
|
||||
ShaveTape(),
|
||||
StreamTapeXyz(),
|
||||
|
||||
//mixdrop extractors
|
||||
MixDropBz(),
|
||||
|
@ -681,6 +705,9 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
SibNet(),
|
||||
ContentX(),
|
||||
Hotlinger(),
|
||||
FourCX(),
|
||||
PlayRu(),
|
||||
FourPlayRu(),
|
||||
HDMomPlayer(),
|
||||
HDPlayerSystem(),
|
||||
VideoSeyred(),
|
||||
|
@ -813,6 +840,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
Gdriveplayer(),
|
||||
DatabaseGdrive(),
|
||||
DatabaseGdrive2(),
|
||||
Mediafire(),
|
||||
|
||||
YoutubeExtractor(),
|
||||
YoutubeShortLinkExtractor(),
|
||||
|
@ -824,6 +852,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
PlayLtXyz(),
|
||||
AStreamHub(),
|
||||
Vidplay(),
|
||||
VidplayOnline(),
|
||||
MyCloud(),
|
||||
|
||||
Cda(),
|
||||
Dailymotion(),
|
||||
|
@ -832,6 +862,9 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
Rabbitstream(),
|
||||
Dokicloud(),
|
||||
Megacloud(),
|
||||
VidhideExtractor(),
|
||||
StreamWishExtractor(),
|
||||
EmturbovidExtractor()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import okio.buffer
|
|||
import okio.sink
|
||||
import java.io.File
|
||||
import android.text.TextUtils
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
|
@ -191,7 +192,7 @@ class InAppUpdater {
|
|||
Update(
|
||||
shouldUpdate,
|
||||
foundAsset.browser_download_url,
|
||||
tagResponse.github_object.sha,
|
||||
tagResponse.github_object.sha.take(10),
|
||||
found.body,
|
||||
found.node_id
|
||||
)
|
||||
|
@ -213,7 +214,7 @@ class InAppUpdater {
|
|||
this.cacheDir.listFiles()?.filter {
|
||||
it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix
|
||||
}?.forEach {
|
||||
it.deleteOnExit()
|
||||
deleteFileOnExit(it)
|
||||
}
|
||||
|
||||
val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix")
|
||||
|
@ -293,7 +294,13 @@ class InAppUpdater {
|
|||
update.updateVersion
|
||||
)
|
||||
)
|
||||
builder.setMessage("${update.changelog}")
|
||||
|
||||
val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)")
|
||||
val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult ->
|
||||
matchResult.groupValues[1]
|
||||
} // Sanitized because it looks cluttered
|
||||
|
||||
builder.setMessage(sanitizedChangelog)
|
||||
|
||||
val context = this
|
||||
builder.apply {
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.os.IBinder
|
|||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
|
||||
|
@ -75,7 +76,7 @@ class PackageInstallerService : Service() {
|
|||
this@PackageInstallerService.cacheDir.listFiles()?.filter {
|
||||
it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix
|
||||
}?.forEach {
|
||||
it.deleteOnExit()
|
||||
deleteFileOnExit(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ import androidx.core.graphics.blue
|
|||
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.core.view.marginLeft
|
||||
import androidx.core.view.marginRight
|
||||
|
@ -56,6 +59,7 @@ import com.google.android.material.chip.Chip
|
|||
import com.google.android.material.chip.ChipDrawable
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.result.UiImage
|
||||
|
@ -86,8 +90,9 @@ object UIHelper {
|
|||
if (view == null) return
|
||||
view.removeAllViews()
|
||||
val context = view.context ?: return
|
||||
val maxTags = tags.take(10) // Limited because they are too much
|
||||
|
||||
tags.forEach { tag ->
|
||||
maxTags.forEach { tag ->
|
||||
val chip = Chip(context)
|
||||
val chipDrawable = ChipDrawable.createFromAttributes(
|
||||
context,
|
||||
|
@ -179,9 +184,7 @@ object UIHelper {
|
|||
try {
|
||||
if (this is FragmentActivity) {
|
||||
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?
|
||||
navHostFragment?.navController?.let {
|
||||
it.navigate(navigation, arguments)
|
||||
}
|
||||
navHostFragment?.navController?.navigate(navigation, arguments)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
|
@ -401,81 +404,34 @@ object UIHelper {
|
|||
// Enables regular immersive mode.
|
||||
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
||||
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
// Set the content to appear under the system bars so that the
|
||||
// content doesn't resize when the system bars hide and show.
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
// Hide the nav bar and status bar
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
// or View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||
)
|
||||
// window.addFlags(View.KEEP_SCREEN_ON)
|
||||
/** BUGGED AF **/
|
||||
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
WindowInsetsControllerCompat(window, View(this)).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
}*/
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
// Set the content to appear under the system bars so that the
|
||||
// content doesn't resize when the system bars hide and show.
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
// Hide the nav bar and status bar
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
)
|
||||
//}
|
||||
}
|
||||
|
||||
fun FragmentActivity.popCurrentPage() {
|
||||
this.onBackPressedDispatcher.onBackPressed()
|
||||
/*val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
||||
it.isVisible
|
||||
} ?: return
|
||||
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.enter_anim,
|
||||
R.anim.exit_anim,
|
||||
R.anim.pop_enter,
|
||||
R.anim.pop_exit
|
||||
)
|
||||
.remove(currentFragment)
|
||||
.commitAllowingStateLoss()*/
|
||||
}
|
||||
/*
|
||||
fun FragmentActivity.popCurrentPage(isInPlayer: Boolean, isInExpandedView: Boolean, isInResults: Boolean) {
|
||||
val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
||||
it.isVisible
|
||||
}
|
||||
?: //this.onBackPressedDispatcher.onBackPressed()
|
||||
return
|
||||
|
||||
/*
|
||||
if (tvActivity == null) {
|
||||
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
}*/
|
||||
|
||||
// No fucked animations leaving the player :)
|
||||
when {
|
||||
isInPlayer -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
//.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit)
|
||||
.remove(currentFragment)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
isInExpandedView && !isInResults -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.enter_anim,//R.anim.enter_from_right,
|
||||
R.anim.exit_anim,//R.anim.exit_to_right,
|
||||
R.anim.pop_enter,
|
||||
R.anim.pop_exit
|
||||
)
|
||||
.remove(currentFragment)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
else -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
|
||||
.remove(currentFragment)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
fun Context.getStatusBarHeight(): Int {
|
||||
if (isTvSettings()) {
|
||||
|
@ -542,13 +498,26 @@ object UIHelper {
|
|||
|
||||
fun Activity.changeStatusBarState(hide: Boolean): Int {
|
||||
return if (hide) {
|
||||
window?.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.insetsController?.hide(WindowInsets.Type.statusBars())
|
||||
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
)
|
||||
}
|
||||
0
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.insetsController?.show(WindowInsets.Type.statusBars())
|
||||
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
}
|
||||
|
||||
this.getStatusBarHeight()
|
||||
}
|
||||
}
|
||||
|
@ -556,13 +525,19 @@ object UIHelper {
|
|||
// Shows the system bars by removing all the flags
|
||||
// except for the ones that make the content appear under the system bars.
|
||||
fun Activity.showSystemUI() {
|
||||
window.decorView.systemUiVisibility =
|
||||
|
||||
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
|
||||
Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility =
|
||||
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
//}
|
||||
|
||||
changeStatusBarState(isEmulatorSettings())
|
||||
|
||||
// window.clearFlags(View.KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
|
||||
|
|
|
@ -41,17 +41,36 @@
|
|||
android:layout_marginStart="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/resultview_preview_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="16sp"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
android:textStyle="bold"
|
||||
tools:text="The Perfect Run">
|
||||
<TextView
|
||||
android:id="@+id/resultview_preview_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="16sp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginEnd="25dp"
|
||||
tools:text="The Perfect Run">
|
||||
|
||||
</TextView>
|
||||
</TextView>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/resultview_preview_favorite"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
||||
android:layout_margin="5dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:elevation="10dp"
|
||||
android:nextFocusDown="@id/resultview_preview_bookmark"
|
||||
android:src="@drawable/ic_baseline_favorite_border_24"
|
||||
app:tint="?attr/textColor" />
|
||||
</FrameLayout>
|
||||
|
||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -123,6 +142,7 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/resultview_preview_bookmark"
|
||||
android:layout_weight="1"
|
||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||
android:nextFocusRight="@id/resultview_preview_more_info"
|
||||
|
||||
tools:visibility="visible"
|
||||
|
@ -136,6 +156,7 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/resultview_preview_more_info"
|
||||
android:layout_weight="1"
|
||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||
android:nextFocusLeft="@id/resultview_preview_bookmark"
|
||||
|
||||
tools:visibility="visible"
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="5"
|
||||
android:maxLines="3"
|
||||
android:paddingBottom="5dp"
|
||||
android:textSize="15sp"
|
||||
tools:text="very nice tv series" />
|
||||
|
|
|
@ -159,6 +159,16 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="40dp">
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/library_random"
|
||||
style="@style/ExtendedFloatingActionButton"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:text="@string/home_random"
|
||||
android:textColor="?attr/textColor"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/sort_fab"
|
||||
|
|
|
@ -190,6 +190,16 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="40dp">
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/library_random"
|
||||
style="@style/ExtendedFloatingActionButton"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:text="@string/home_random"
|
||||
android:textColor="?attr/textColor"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/sort_fab"
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:tooltipText="@string/subscribe_tooltip"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/baseline_notifications_none_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
@ -100,7 +100,7 @@
|
|||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:tooltipText="@string/action_add_to_favorites"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_baseline_favorite_border_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
@ -117,7 +117,7 @@
|
|||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:tooltipText="@string/result_share"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_outline_share_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
@ -135,7 +135,7 @@
|
|||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:tooltipText="@string/result_open_in_browser"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_baseline_public_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
@ -153,7 +153,7 @@
|
|||
android:layout_height="30dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:tooltipText="@string/result_search_tooltip"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/search_icon"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
@ -171,7 +171,7 @@
|
|||
android:layout_height="25dp"
|
||||
android:layout_margin="5dp"
|
||||
android:elevation="10dp"
|
||||
|
||||
android:tooltipText="@string/recommendations_tooltip"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/baseline_list_alt_24"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
|
|
@ -387,6 +387,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
app:itemSpacing="10dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
@ -399,6 +400,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:id="@+id/result_meta_content_rating"
|
||||
android:layout_gravity="center_vertical"
|
||||
style="@style/SmallWhiteButton"
|
||||
android:focusable="false"
|
||||
tools:text="PG-13" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -518,6 +518,26 @@
|
|||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="23:20" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time_left"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
|
||||
android:layout_marginEnd="20dp"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="-23:20"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
|
|
|
@ -607,6 +607,25 @@
|
|||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="23:20" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time_left"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
|
||||
android:layout_marginEnd="20dp"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="-23:20"
|
||||
android:visibility="gone"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
|
|
@ -506,6 +506,25 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/player_fullscreen"
|
||||
tools:text="23:20" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time_left"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
|
||||
android:layout_marginEnd="30dp"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="@id/player_fullscreen"
|
||||
tools:text="-23:20"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_fullscreen"
|
||||
android:layout_width="30dp"
|
||||
|
|
|
@ -210,7 +210,7 @@
|
|||
<string name="episode_action_play_in_format">مشي بـ\"%s\"</string>
|
||||
<string name="episode_action_download_subtitle">نزل الترجمة</string>
|
||||
<string name="dont_show_again">ما تفرجيا بعد مرة</string>
|
||||
<string name="video_buffer_clear_settings">فضّي التخزين المؤقت للصور والڤيديوات</string>
|
||||
<string name="video_buffer_clear_settings">فضّي التخزين الموقت للصور والڤيديوات</string>
|
||||
<string name="video_skip_op">أفي المقدمة</string>
|
||||
<string name="watch_quality_pref">الجودة المفضلة (Wi-Fi)</string>
|
||||
<string name="cartoons_singular">رسوم مُتَحَرِكة</string>
|
||||
|
@ -260,7 +260,7 @@
|
|||
<string name="add_site_pref">نسوخ موقع (clone site)</string>
|
||||
<string name="random_button_settings">زر العشوائي</string>
|
||||
<string name="resize_fill">مدو</string>
|
||||
<string name="random_button_settings_desc">فرجي زر \"عشوائي\" على الصفحة الرئيسية</string>
|
||||
<string name="random_button_settings_desc">فرجي زر \"عشوائي\" بالصفحة الرئيسية وبصفحة الرفّ</string>
|
||||
<string name="pref_category_ui_features">الميزات</string>
|
||||
<string name="display_subbed_dubbed_settings">فرجي أنمي المدبلج-المترجم</string>
|
||||
<string name="pref_category_backup">النسخ الإحتياطي</string>
|
||||
|
@ -595,4 +595,5 @@
|
|||
<string name="rotate_video">برومو</string>
|
||||
<string name="auto_rotate_video_desc">غير إتجاه الشاشة أوتوماتيكيًا حسب شكل الڤيديو</string>
|
||||
<string name="links_reloaded_toast">رجع نعمل لاود لاللينك</string>
|
||||
<string name="copyTitle">نعمل كَپي للعنوان!</string>
|
||||
</resources>
|
||||
|
|
|
@ -283,7 +283,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">عام</string>
|
||||
<string name="random_button_settings">زر العشوائي</string>
|
||||
<string name="random_button_settings_desc">إظهار زر العشوائي على الصفحة الرئيسية</string>
|
||||
<string name="random_button_settings_desc">إظهار زر عشوائي على الصفحة الرئيسية والمكتبة</string>
|
||||
<string name="provider_lang_settings">لغات المزود</string>
|
||||
<string name="app_layout">واجهة التطبيق</string>
|
||||
<string name="preferred_media_settings">المحتوى المفضل</string>
|
||||
|
@ -618,9 +618,10 @@
|
|||
<string name="edit_account">تعديل الحساب</string>
|
||||
<string name="links_reloaded_toast">تم إعادة تحميل الروابط</string>
|
||||
<string name="rotate_video_desc">عرض زر تبديل لاتجاه الشاشة</string>
|
||||
<string name="rotate_video_key">مفتاح تدوير الفيديو</string>
|
||||
<string name="rotate_video_key">تدوير الفيديو</string>
|
||||
<string name="auto_rotate_video_key">مفتاح تدوير الفيديو التلقائي</string>
|
||||
<string name="auto_rotate_video">الدوران التلقائي</string>
|
||||
<string name="rotate_video">تدوير</string>
|
||||
<string name="auto_rotate_video_desc">تمكين التبديل التلقائي لاتجاه الشاشة بناءً على اتجاه الفيديو</string>
|
||||
<string name="copyTitle">تم نسخ العنوان!</string>
|
||||
</resources>
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
<string name="search">অনুসন্ধান করুন</string>
|
||||
<string name="category_account">অ্যাকাউন্টসমূহ</string>
|
||||
<string name="bug_report_settings_on">কোনো উপাত্ত পাঠাবে না</string>
|
||||
<string name="double_tap_to_pause_settings_des">বিরতি দিতে মাঝে চাপুন</string>
|
||||
<string name="double_tap_to_pause_settings_des">বিরতি দিতে মাঝে দুইবার চাপুন</string>
|
||||
<string name="use_system_brightness_settings">সিস্টেম এর উজ্জ্বলতা ব্যবহার করুন</string>
|
||||
<string name="play_trailer_button">ট্রেইলার চালু করুন</string>
|
||||
<string name="swipe_to_seek_settings_des">ভিডিওর সময় নিয়ন্ত্রণ করতে, ডানে অথবা বামে সোয়াইপ করুন</string>
|
||||
|
@ -148,4 +148,86 @@
|
|||
<string name="update_started">আপডেট শুরু হয়েছে</string>
|
||||
<string name="browser">ব্রাউজার</string>
|
||||
<string name="test_log">লগ</string>
|
||||
<string name="double_tap_to_seek_amount_settings">প্লেয়ারে এগিয়ে যাওয়ার পরিমাণ (সেকেন্ডে)</string>
|
||||
<string name="double_tap_to_seek_settings_des">সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন</string>
|
||||
<string name="delete_file">ফাইল ডিলিট</string>
|
||||
<string name="acra_report_toast">দুঃখিত, অ্যাপ্লিকেশন ক্র্যাশ হয়েছে। ডেভেলপারদের কাছে একটি বেনামী বাগ রিপোর্ট পাঠানো হবে</string>
|
||||
<string name="bug_report_settings_off">শুধুমাত্র ক্র্যাশ এর তথ্য পাঠায়</string>
|
||||
<string name="subs_default_reset_toast">মান ডিফল্ট এ রিসেট করুন</string>
|
||||
<string name="uprereleases_settings_des">ফুল রিলিজের পরিবর্তে শুধুমাত্র প্রি-রিলিজ আপডেটের জন্য অনুসন্ধান করুন</string>
|
||||
<string name="updates_settings_des">স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন</string>
|
||||
<string name="movies_singular">সিনেমা</string>
|
||||
<string name="show_fillers_settings">এনিমে এর ফিলার পর্ব দেখায়</string>
|
||||
<string name="tv_series">টিভি সিরিজ</string>
|
||||
<string name="no_links_found_toast">লিংক পাওয়া যায়নি</string>
|
||||
<string name="benene_des">চা খাওয়ানো হয়েছে</string>
|
||||
<string name="go_forward_30">+৩০</string>
|
||||
<string name="movies">সিনেমা</string>
|
||||
<string name="discord">ডিসকর্ডে যোগ দিন</string>
|
||||
<string name="torrent">টরেন্টস</string>
|
||||
<string name="delete_message" formatted="true">এটি স্থায়ীভাবে মুছে ফেলা হবে %s
|
||||
\nআপনি কি নিশ্চিত?</string>
|
||||
<string name="pause">থামুন</string>
|
||||
<string name="go_back_30">-৩০</string>
|
||||
<string name="github">গিটহাব</string>
|
||||
<string name="free_storage">ফ্রি</string>
|
||||
<string name="backup_settings">ডেটা ব্যাকআপ করুন</string>
|
||||
<string name="restore_success">ব্যাকআপ ফাইল লোড হয়েছে</string>
|
||||
<string name="year">বছর</string>
|
||||
<string name="no_season">কোন সিজন নেই</string>
|
||||
<string name="play_episode_toast">এপিসোড পরলে করুন</string>
|
||||
<string name="resume">শুরু করুন</string>
|
||||
<string name="documentaries">তথ্যচিত্র</string>
|
||||
<string name="automatic_plugin_download_summary">যোগ করা রিপোজিটরি থেকে এখনও ইনস্টল করা হয়নি এমন সব প্লাগইন স্বয়ংক্রিয়ভাবে ইনস্টল করুন।</string>
|
||||
<string name="delete">ডিলিট</string>
|
||||
<string name="start">শুরু</string>
|
||||
<string name="cartoons">কার্টুন</string>
|
||||
<string name="apk_installer_settings_des">কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন।</string>
|
||||
<string name="no_subtitles">সাবটাইটেল নেই</string>
|
||||
<string name="no_chromecast_support_toast">এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না</string>
|
||||
<string name="advanced_search">উন্নত অনুসন্ধান</string>
|
||||
<string name="synopsis">সারমর্ম</string>
|
||||
<string name="used_storage">ব্যবহৃত</string>
|
||||
<string name="library">লাইব্রেরী</string>
|
||||
<string name="lightnovel">আমাদের তৈরি ছোট উপন্যাস পড়ার অ্যাপ্লিকেশন</string>
|
||||
<string name="resume_time_left" formatted="true">%d মি
|
||||
\nবাকি</string>
|
||||
<string name="others">অন্যান্য</string>
|
||||
<string name="status_ongoing">চলমান</string>
|
||||
<string name="asian_drama">এশিয়ান নাটক</string>
|
||||
<string name="queued">অপেক্ষা করছে</string>
|
||||
<string name="episode">পর্ব</string>
|
||||
<string name="status">অবস্থা</string>
|
||||
<string name="use_system_brightness_settings_des">অ্যাপের প্লেয়ারে অন্ধকার ওভারলে এর পরিবর্তে সিস্টেম ব্রাইটনেস ব্যবহার করুন</string>
|
||||
<string name="restore_failed_format" formatted="true">%s ফাইল থেকে ডেটা পুনরুদ্ধার করা ব্যর্থ হয়েছে</string>
|
||||
<string name="no_episodes_found">কোনো এপিসোড পাওয়া যায়নি</string>
|
||||
<string name="test_failed">ব্যর্থ হয়েছে</string>
|
||||
<string name="advanced_search_des">প্রোভাইডার অনুযায়ী আপনাকে পৃথক অনুসন্ধান ফলাফল দেয়</string>
|
||||
<string name="tv_series_singular">সিরিজ</string>
|
||||
<string name="rating">রেটিং</string>
|
||||
<string name="uprereleases_settings">প্রি-রিলিজে আপডেট করুন</string>
|
||||
<string name="redo_setup_process">সেটআপ প্রক্রিয়া পুনরায় করুন</string>
|
||||
<string name="benene">ভাই এক কাপ চা খাওয়াও ☕</string>
|
||||
<string name="restore_settings">ব্যাকআপ থেকে ডেটা পুনরুদ্ধার করুন</string>
|
||||
<string name="copy_link_toast">ক্লিপবোর্ডে লিঙ্ক কপি করা হয়েছে</string>
|
||||
<string name="status_completed">দেখা শেষ</string>
|
||||
<string name="anim">আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন</string>
|
||||
<string name="nsfw">18+</string>
|
||||
<string name="anime">এনিমে</string>
|
||||
<string name="pref_filter_search_quality">অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান</string>
|
||||
<string name="app_storage">অ্যাপ</string>
|
||||
<string name="livestreams">লাইভস্ট্রিম</string>
|
||||
<string name="apk_installer_settings">APK ইনস্টলার</string>
|
||||
<string name="duration">সময়কাল</string>
|
||||
<string name="app_language">অ্যাপের ভাষা</string>
|
||||
<string name="test_passed">পাস করেছে</string>
|
||||
<string name="episodes">পর্ব</string>
|
||||
<string name="backup_failed_error_format">%s ব্যাক আপ করতে সমস্যা হয়েছে</string>
|
||||
<string name="site">সাইট</string>
|
||||
<string name="season">সিজন</string>
|
||||
<string name="backup_frequency">ব্যাকআপ ফ্রিকোয়েন্সি</string>
|
||||
<string name="episode_sync_settings">দেখার অগ্রগতি আপডেট করুন</string>
|
||||
<string name="episode_sync_settings_des">আপনার বর্তমান পর্বের অগ্রগতি স্বয়ংক্রিয়ভাবে সিঙ্ক করুন</string>
|
||||
<string name="automatic_plugin_download_mode_title">প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন</string>
|
||||
<string name="links_reloaded_toast">লিঙ্ক পুনরায় লোড হয়েছে</string>
|
||||
</resources>
|
||||
|
|
|
@ -265,7 +265,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí.</string>
|
||||
<string name="category_general">Obecné</string>
|
||||
<string name="random_button_settings">Náhodné tlačítko</string>
|
||||
<string name="random_button_settings_desc">Zobrazit na domovské stránce náhodné tlačítko</string>
|
||||
<string name="random_button_settings_desc">Zobrazit na domovské stránce a v knihovně náhodné tlačítko</string>
|
||||
<string name="provider_lang_settings">Jazyk poskytovatelů</string>
|
||||
<string name="app_layout">Rozložení aplikace</string>
|
||||
<string name="preferred_media_settings">Preferovaná média</string>
|
||||
|
@ -615,4 +615,5 @@
|
|||
<string name="auto_rotate_video">Automatické otáčení</string>
|
||||
<string name="rotate_video">Otočení</string>
|
||||
<string name="auto_rotate_video_desc">Zapnout automatické otáčení obrazovky v závislosti na orientaci videa</string>
|
||||
<string name="copyTitle">Název zkopírován!</string>
|
||||
</resources>
|
||||
|
|
|
@ -585,4 +585,8 @@
|
|||
\n
|
||||
\nWollen sie dieses Element trotzdem hinzufügen, das existierende ersetzen oder diese Aktion abbrechen?</string>
|
||||
<string name="links_reloaded_toast">Links wurden neu geladen</string>
|
||||
<string name="rotate_video">Drehen</string>
|
||||
<string name="rotate_video_desc">Zeige einen Umschalter für Bildschirmorientierung an</string>
|
||||
<string name="auto_rotate_video">Automatisch drehen</string>
|
||||
<string name="copyTitle">Titel kopiert!</string>
|
||||
</resources>
|
||||
|
|
|
@ -305,7 +305,7 @@
|
|||
<string name="pref_category_looks">Aspecto</string>
|
||||
<string name="pref_category_ui_features">Características</string>
|
||||
<string name="random_button_settings">Botón aleatorio</string>
|
||||
<string name="random_button_settings_desc">Mostrar el botón aleatorio en la página de inicio</string>
|
||||
<string name="random_button_settings_desc">Mostrar el botón aleatorio en la página de inicio y en la biblioteca</string>
|
||||
<string name="account">Cuenta</string>
|
||||
<string name="logout">Cerrar sesión</string>
|
||||
<string name="switch_account">Cambiar cuenta</string>
|
||||
|
@ -591,4 +591,5 @@
|
|||
<string name="auto_rotate_video">Giro automático</string>
|
||||
<string name="rotate_video">Girar</string>
|
||||
<string name="auto_rotate_video_desc">Activar el cambio automático de la orientación de la pantalla en función de la orientación del vídeo</string>
|
||||
<string name="copyTitle">¡Título copiado!</string>
|
||||
</resources>
|
||||
|
|
|
@ -53,4 +53,38 @@
|
|||
<string name="torrent_plot">شرح</string>
|
||||
<string name="subs_subtitle_languages">زبان زیرنویس</string>
|
||||
<string name="player_subtitles_settings">زیرنویس</string>
|
||||
<string name="action_remove_from_bookmarks">حذف</string>
|
||||
<string name="download_started">بارگیری آغاز شد</string>
|
||||
<string name="pref_disable_acra">غیرفعال کردن گذارش باگ خودکار</string>
|
||||
<string name="sort_clear">پاک کردن</string>
|
||||
<string name="update_started">بهروزرسانی آغاز شد</string>
|
||||
<string name="sort_copy">کپی</string>
|
||||
<string name="home_more_info">اطلاعات بیشتر</string>
|
||||
<string name="subs_auto_select_language">انتخاب زبان خودکار</string>
|
||||
<string name="continue_watching">ادامهٔ تماشا</string>
|
||||
<string name="subs_download_languages">بارگیری زبانها</string>
|
||||
<string name="subs_background_color">زنگ پسزمینه</string>
|
||||
<string name="popup_pause_download">توقف بارگیری</string>
|
||||
<string name="app_dubbed_text">دوبله</string>
|
||||
<string name="torrent_no_plot">شرحی یافت نشد</string>
|
||||
<string name="subtitles_settings">تنظیمات زیرنویس</string>
|
||||
<string name="subs_window_color">رنگ پنجره</string>
|
||||
<string name="eigengraumode_settings_des">افزودن گرینهٔ سرعت در پخشکننده</string>
|
||||
<string name="download_canceled">بارگیری لغو شد</string>
|
||||
<string name="home_expanded_hide">پنهان کردن</string>
|
||||
<string name="sort_apply">اعمال کردن</string>
|
||||
<string name="normal_no_plot">پیرنگی یافت نشد</string>
|
||||
<string name="download_paused">بارگیری متوقف شد</string>
|
||||
<string name="popup_delete_file">حذف فایل</string>
|
||||
<string name="downloaded">بارگرفتن</string>
|
||||
<string name="popup_resume_download">ازسرگیری بارگیری</string>
|
||||
<string name="subs_text_color">رنگ متن</string>
|
||||
<string name="popup_play_file">نمایش فایل</string>
|
||||
<string name="go_back">بازگشت</string>
|
||||
<string name="sort_save">ذخیره</string>
|
||||
<string name="home_play">نمایش</string>
|
||||
<string name="autoplay_next_settings">پخش خودکار قسمت بعد</string>
|
||||
<string name="picture_in_picture">تصویر در تصویر</string>
|
||||
<string name="sort_close">بستن</string>
|
||||
<string name="home_next_random_img_des">اتاق بعدی</string>
|
||||
</resources>
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<string name="sort_copy">Kopiraj</string>
|
||||
<string name="sort_close">Zatvori</string>
|
||||
<string name="sort_clear">Očisti</string>
|
||||
<string name="sort_save">Sačuvaj</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>
|
||||
|
@ -298,7 +298,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">Općenito</string>
|
||||
<string name="random_button_settings">Random gumb</string>
|
||||
<string name="random_button_settings_desc">Prikaži gumb za slučajni odabir reprodukcija na početnoj stranici</string>
|
||||
<string name="random_button_settings_desc">Prikaži gumb za slučajni odabir reprodukcija na početnoj stranici i biblioteci</string>
|
||||
<string name="provider_lang_settings">Jezici pružatelja usluga</string>
|
||||
<string name="app_layout">Izgled aplikacije</string>
|
||||
<string name="preferred_media_settings">Preferirani mediji</string>
|
||||
|
@ -609,4 +609,12 @@
|
|||
<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="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>
|
||||
<string name="auto_rotate_video">Automatsko rotiranje</string>
|
||||
<string name="copyTitle">Naslov je kopiran!</string>
|
||||
<string name="rotate_video_key">rotiraj_video_tipka</string>
|
||||
<string name="auto_rotate_video_key">automatski_rotiraj_video_tipka</string>
|
||||
</resources>
|
||||
|
|
|
@ -219,4 +219,27 @@
|
|||
<string name="subs_auto_select_language">言語の自動選択</string>
|
||||
<string name="error_loading_links_toast">リンクの読み込みエラー</string>
|
||||
<string name="enter_pin">137905</string>
|
||||
<string name="links_reloaded_toast">リンクがリロードされました</string>
|
||||
<string name="pref_disable_acra">自動バグ報告を無効にする</string>
|
||||
<string name="player_speed">プレーヤーの速度</string>
|
||||
<string name="subtitles_settings">字幕設定</string>
|
||||
<string name="app_dubbed_text">ダブ</string>
|
||||
<string name="app_subbed_text">サブ</string>
|
||||
<string name="torrent_no_plot">説明が見つかりません</string>
|
||||
<string name="swipe_to_change_settings">スワイプして設定を変更します</string>
|
||||
<string name="subs_outline_color">輪郭の色</string>
|
||||
<string name="subs_subtitle_elevation">字幕の標高</string>
|
||||
<string name="benene_count_text">%d ベネネスが開発者に与えられました</string>
|
||||
<string name="vpn_might_be_needed">このプロバイダーが正しく動作するには VPN が必要になる可能性があります</string>
|
||||
<string name="vpn_torrent">このプロバイダーは torrent です、VPN をお勧めします</string>
|
||||
<string name="player_size_settings">プレーヤーのサイズ変更ボタン</string>
|
||||
<string name="player_size_settings_des">黒い境界線を削除します</string>
|
||||
<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_des">プレーヤーに速度オプションを追加します</string>
|
||||
<string name="swipe_to_seek_settings">スワイプして探す</string>
|
||||
<string name="autoplay_next_settings">次のエピソードを自動再生する</string>
|
||||
<string name="autoplay_next_settings_des">現在のエピソードが終了したら次のエピソードを開始する</string>
|
||||
<string name="subs_hold_to_reset_to_default">長押しするとデフォルトにリセットされます</string>
|
||||
</resources>
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
<string name="action_mark_as_watched">കണ്ടതാണെന്ന് അടയാളപ്പെടുത്തുക</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$d%2$d</string>
|
||||
<string name="next_episode_format" formatted="true">yg5t4r%dujyhtg</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">qeWERT</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%d മണിക്കൂർ %d മിനിറ്റ്</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$sghj%2$d</string>
|
||||
<string name="cast_format" formatted="true">rtf:%</string>
|
||||
<string name="create_account">അക്കൗണ്ട് ഉണ്ടാക്കുക</string>
|
||||
|
@ -199,4 +199,52 @@
|
|||
<string name="browser">ബ്രൗസർ</string>
|
||||
<string name="type_re_watching">വീണ്ടും കാണുക</string>
|
||||
<string name="stream">സ്ട്രീം</string>
|
||||
<string name="subs_import_text" formatted="true">%s ൽ ഫോൻ്റ്സ് വെച്ചു കൊണ്ട് ഇംപോർട്ട് ചെയ്യുക</string>
|
||||
<string name="safe_mode_description">പ്രശ്നമുണ്ടാക്കുന്ന ഒന്ന് കണ്ടെത്താൻ നിങ്ങളെ സഹായിക്കുന്നതിന് ഒരു ക്രാഷ് കാരണം എല്ലാ വിപുലീകരണങ്ങളും ഓഫാക്കി.</string>
|
||||
<string name="view_public_repositories_button_short">പൊതു പട്ടിക</string>
|
||||
<string name="blank_repo_message">CloudStream-ന് സ്ഥിരസ്ഥിതിയായി സൈറ്റുകളൊന്നും ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. നിങ്ങൾ റിപ്പോസിറ്ററികളിൽ നിന്ന് സൈറ്റുകൾ ഇൻസ്റ്റാൾ ചെയ്യേണ്ടതുണ്ട്.
|
||||
\n
|
||||
\nസ്കൈ യുകെ ലിമിറ്റഡിലെ ഡോഗ്ഷിറ്റ് ആളുകളിൽ നിന്ന് DMCA നീക്കം ചെയ്തതിനാൽ 🤮 ഞങ്ങൾക്ക് ആപ്പിൽ റിപ്പോസിറ്ററി സൈറ്റ് ലിങ്ക് ചെയ്യാൻ കഴിയില്ല.
|
||||
\n
|
||||
\nഞങ്ങളുടെ ഡിസ്കോർഡിൽ ചേരുക അല്ലെങ്കിൽ ഓൺലൈനിൽ തിരയുക.</string>
|
||||
<string name="sort_copy">പകർത്തുക</string>
|
||||
<string name="uppercase_all_subtitles">എല്ലാ സബ്ടൈറ്റിലുകളും വലിയക്ഷരമാക്കുക</string>
|
||||
<string name="render_error">റെൻഡറർ പിശക്</string>
|
||||
<string name="lock_profile">പ്രൊഫൈൽ ലോക്ക് ചെയ്യുക</string>
|
||||
<string name="view_public_repositories_button">കമ്മ്യൂണിറ്റി റിപ്പോസിറ്ററികൾ കാണുക</string>
|
||||
<string name="safe_mode_title">സുരക്ഷിത മോഡ് ഓണാണ്</string>
|
||||
<string name="manage_accounts">അക്കൗണ്ടുകൾ കൈകാര്യം ചെയ്യുക</string>
|
||||
<string name="coming_soon">ഉടൻ വരുന്നു…</string>
|
||||
<string name="apply_on_restart">പുനരാരംഭിക്കുമ്പോൾ പ്രയോഗിക്കുക</string>
|
||||
<string name="edit_account">അക്കൗണ്ട് എഡിറ്റ് ചെയ്യുക</string>
|
||||
<string name="pin_error_incorrect">തെറ്റായ പിൻ. ദയവായി വീണ്ടും ശ്രമിക്കുക.</string>
|
||||
<string name="stop">നിർത്തുക</string>
|
||||
<string name="tracks">ട്രാക്കുകൾ</string>
|
||||
<string name="pin_error_length">പിൻ 4 പ്രതീകങ്ങൾ ആയിരിക്കണം</string>
|
||||
<string name="unexpected_error">അപ്രതീക്ഷിത പ്ലെയർ പിശക്</string>
|
||||
<string name="previous">മുമ്പത്തെ</string>
|
||||
<string name="delete_repository_plugins">ഇത് എല്ലാ റിപ്പോസിറ്ററി പ്ലഗിന്നുകളും ഇല്ലാതാക്കും</string>
|
||||
<string name="restart">പുനരാരംഭിക്കുക</string>
|
||||
<string name="safe_mode_crash_info">ക്രാഷ് വിവരം കാണുക</string>
|
||||
<string name="provider_languages_tip">ഈ ഭാഷകളിലെ വീഡിയോകൾ കാണുക</string>
|
||||
<string name="select_an_account">ഒരു അക്കൗണ്ട് തിരഞ്ഞെടുക്കുക</string>
|
||||
<string name="home_source">ഉറവിടം</string>
|
||||
<string name="video_tracks">വീഡിയോ ട്രാക്കുകൾ</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast മിറർ</string>
|
||||
<string name="download_all_plugins_from_repo">ഈ ശേഖരത്തിൽ നിന്ന് എല്ലാ പ്ലഗിന്നുകളും ഡൗൺലോഡ് ചെയ്യണോ?</string>
|
||||
<string name="delete_repository">ശേഖരം ഇല്ലാതാക്കുക</string>
|
||||
<string name="home_random">ക്രമരഹിതം</string>
|
||||
<string name="quality_cam">ക്യാമറ</string>
|
||||
<string name="storage_error">ഡൗൺലോഡ് പിശക്, സംഭരണ അനുമതികൾ പരിശോധിക്കുക</string>
|
||||
<string name="remote_error">റിമോട്ട് പിശക്</string>
|
||||
<string name="episode_action_chromecast_episode">Chromecast എപ്പിസോഡ്</string>
|
||||
<string name="setup_extensions_subtext">നിങ്ങൾ ഉപയോഗിക്കാൻ ആഗ്രഹിക്കുന്ന സൈറ്റുകളുടെ ലിസ്റ്റ് ഡൗൺലോഡ് ചെയ്യുക</string>
|
||||
<string name="enter_pin">പിൻ നൽകുക</string>
|
||||
<string name="sort_close">അടയ്ക്കുക</string>
|
||||
<string name="action_add_to_bookmarks">വാച്ച് സ്റ്റാറ്റസ് സജ്ജീകരിക്കുക</string>
|
||||
<string name="pin">പിൻ</string>
|
||||
<string name="links_reloaded_toast">ലിങ്കുകൾ വീണ്ടും ലോഡുചെയ്തു</string>
|
||||
<string name="source_error">ഉറവിട പിശക്</string>
|
||||
<string name="enter_current_pin">നിലവിലെ പിൻ നൽകുക</string>
|
||||
<string name="audio_tracks">ഓഡിയോ ട്രാക്കുകൾ</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,2 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources/>
|
||||
<resources>
|
||||
<string name="player_speed_text_format" formatted="true">गति (%.2fx)</string>
|
||||
<string name="home_next_random_img_des">अर्को अनियमित</string>
|
||||
<string name="preview_background_img_des">पृष्ठभूमि देखाउनुहोस्</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dm</string>
|
||||
<string name="search_poster_img_des">पोस्टर</string>
|
||||
<string name="go_back_img_des">पछाडी जाउ</string>
|
||||
<string name="episode_poster_img_des">एपिसोडको पोस्टर</string>
|
||||
<string name="home_change_provider_img_des">प्रोवाइडर परिवर्तन गर्नुहोस्</string>
|
||||
<string name="rated_format" formatted="true">रेटिंग: %.1f</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dm</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<string name="result_poster_img_des">विज्ञापन</string>
|
||||
<string name="home_main_poster_img_des">मुख्य पोस्टर</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s Ep %2$d</string>
|
||||
<string name="cast_format" formatted="true">अभिनेता:%s</string>
|
||||
<string name="new_update_format" formatted="true">नयाँ अपडेट भेटियो!
|
||||
\n%1$s -> %2$s</string>
|
||||
<string name="filler" formatted="true">फिलर</string>
|
||||
<string name="duration_format" formatted="true">%d मिनेट</string>
|
||||
<string name="app_name">क्लाउडस्ट्रीम</string>
|
||||
<string name="play_with_app_name">क्लाउडस्ट्रीममा प्ले गर्नुहोस्</string>
|
||||
<string name="title_home">होम्</string>
|
||||
<string name="title_search">खोजी</string>
|
||||
<string name="title_downloads">डाउनलोडस</string>
|
||||
<string name="title_settings">सेटिङह</string>
|
||||
<string name="search_hint">खोज्नुहोस्…</string>
|
||||
<string name="search_hint_site" formatted="true">%s खोज्नुहोस्…</string>
|
||||
<string name="episode_more_options_des">थप विकल्पहरू</string>
|
||||
<string name="result_tags">विधाहरू</string>
|
||||
<string name="result_share">सेयर गर्नुहोस्</string>
|
||||
<string name="result_open_in_browser">ब्राउजरमा खाेल्नुहाेस्</string>
|
||||
<string name="browser">ब्राउजर</string>
|
||||
<string name="skip_loading">लोड गर्न छोड्नुहोस्</string>
|
||||
<string name="loading">लोड हुँदै…</string>
|
||||
<string name="type_on_hold">होल्डमा भएको</string>
|
||||
<string name="type_dropped">अधुरै छाडेको</string>
|
||||
<string name="type_plan_to_watch">हेर्ने योजना</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="reload_error">पुनः प्रयास गर्नुहोस…</string>
|
||||
<string name="go_back">पछाडि जानुहोस्</string>
|
||||
<string name="play_episode">एपिसोड प्ले गर्नुहोस्</string>
|
||||
<string name="download">डाउनलोड</string>
|
||||
<string name="downloaded">डाउनलोड भयो</string>
|
||||
<string name="downloading">डाउनलोड हुदैछ</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">Dub</string>
|
||||
<string name="app_subbed_text">Sub</string>
|
||||
<string name="popup_delete_file">फाइल मेट्नुहोस्</string>
|
||||
<string name="popup_play_file">फाइल प्ले गर्नुहोस्</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="filter_bookmarks">बुकमार्कहरू फिल्टर गर्नुहोस्</string>
|
||||
<string name="error_bookmarks_text">बुकमार्कहरू</string>
|
||||
<string name="action_remove_from_bookmarks">हटाउनुहोस्</string>
|
||||
<string name="action_add_to_bookmarks">हेरेको स्थिति राख्नुहोस्</string>
|
||||
<string name="sort_copy">कपी</string>
|
||||
<string name="sort_close">बन्द</string>
|
||||
<string name="sort_clear">खाली गर्नुहोस्</string>
|
||||
<string name="sort_save">सेव</string>
|
||||
<string name="next_episode_format" formatted="true">एपिसोड %d रिलीज हुने समय</string>
|
||||
<string name="no_data">डाटा छैन</string>
|
||||
<string name="next_episode">अर्को एपिसोड</string>
|
||||
<string name="type_watching">हेर्दै गरेको</string>
|
||||
<string name="type_completed">पुरा भएको</string>
|
||||
<string name="type_re_watching">पुन: हेर्दै</string>
|
||||
<string name="play_torrent_button">स्ट्रिम टोरेन्ट</string>
|
||||
<string name="pick_source">स्रोतहरू</string>
|
||||
<string name="pref_disable_acra">स्वचालित बग रिपोर्टिङ असक्षम गर्नुहोस्</string>
|
||||
<string name="sort_apply">लागू गर्नुहोस्</string>
|
||||
</resources>
|
||||
|
|
|
@ -532,4 +532,11 @@
|
|||
\nLaster ikke inn noen utvidelser ved oppstart til filen er fjernet.</string>
|
||||
<string name="empty_library_no_accounts_message">Biblioteket ditt er tomt :(
|
||||
\nLogg inn på en bibliotekkonto eller legg til programmer i ditt lokale bibliotek.</string>
|
||||
<string name="edit">Rediger</string>
|
||||
<string name="profiles">Profiler</string>
|
||||
<string name="favorites_list_name">Favoritter</string>
|
||||
<string name="lock_profile">Lås profil</string>
|
||||
<string name="use">Bruk</string>
|
||||
<string name="help">Hjelp</string>
|
||||
<string name="profile_background_des">Profilbakgrunn</string>
|
||||
</resources>
|
||||
|
|
|
@ -158,4 +158,5 @@
|
|||
<string name="next_episode">ପରବର୍ତ୍ତୀ ଅଧ୍ୟାୟ</string>
|
||||
<string name="no_data">କୌଣସି ତଥ୍ୟ ନାହିଁ</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s ଅ %2$d</string>
|
||||
<string name="video_skip_op">ଆଦ୍ୟ ବାଦ୍ ଦିଅ</string>
|
||||
</resources>
|
||||
|
|
|
@ -554,4 +554,42 @@
|
|||
<string name="no_plugins_found_error">Nie znaleziono żadnych wtyczek w repozytorium</string>
|
||||
<string name="already_voted">Już oddano głos</string>
|
||||
<string name="no_repository_found_error">Nie znaleziono tego repozytorium, sprawdź adres URL lub spróbuj połączyć się przez VPN</string>
|
||||
<string name="favorite_removed">Usunięto %s z ulubionych</string>
|
||||
<string name="favorites_list_name">Ulubione</string>
|
||||
<string name="favorite_added">Dodano %s do ulubionych</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">W swojej bibliotece znaleziono potencjalne duplikaty:
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\nCzy chcesz dodać ten element, zastąpić istniejące, czy anulować operację?</string>
|
||||
<string name="enter_pin_with_name" formatted="true">Wprowadź pin dla %s</string>
|
||||
<string name="backup_frequency">Częstotliwość tworzenia kopii zapasowych</string>
|
||||
<string name="duplicate_title">Znaleziono potencjalny duplikat</string>
|
||||
<string name="lock_profile">Zablokuj profil</string>
|
||||
<string name="action_add_to_favorites">Dodaj do ulubionych</string>
|
||||
<string name="manage_accounts">Zarządzaj kontami</string>
|
||||
<string name="duplicate_replace_all">Zamień wszystko</string>
|
||||
<string name="edit_account">Edytuj konto</string>
|
||||
<string name="pin_error_incorrect">Nieprawidłowy PIN. Spróbuj ponownie.</string>
|
||||
<string name="action_unsubscribe">Anuluj subskrypcję</string>
|
||||
<string name="pin_error_length">Kod PIN musi mieć 4 znaki</string>
|
||||
<string name="duplicate_replace">Zastąp</string>
|
||||
<string name="duplicate_add">Dodaj</string>
|
||||
<string name="action_subscribe">Zasubskrybuj</string>
|
||||
<string name="action_remove_from_favorites">Usuń z ulubionych</string>
|
||||
<string name="select_an_account">Wybierz konto</string>
|
||||
<string name="duplicate_message_single" formatted="true">Wygląda się, że potencjalny duplikat już znajduje się w bibliotece: \'%s\'. \'
|
||||
\n
|
||||
\nCzy chciałbyś dodać ten element, zastąpić istniejący, czy anulować akcję?</string>
|
||||
<string name="enter_pin">Wprowadź PIN</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="links_reloaded_toast">Linki załadowane ponownie</string>
|
||||
<string name="enter_current_pin">Wprowadź obecny PIN</string>
|
||||
<string name="logged_account" formatted="true">Zalogowano jako %s</string>
|
||||
<string name="rotate_video_desc">Wyświetl przycisk przełączający orientację ekranu</string>
|
||||
<string name="use_default_account">Używaj domyślnego konta</string>
|
||||
<string name="skip_startup_account_select_pref">Pomiń wybór konta podczas uruchamiania</string>
|
||||
<string name="auto_rotate_video">Automatyczny obrót</string>
|
||||
<string name="rotate_video">Obrót</string>
|
||||
<string name="auto_rotate_video_desc">Włącz automatyczne przełączanie orientacji ekranu na podstawie orientacji filmu</string>
|
||||
</resources>
|
||||
|
|
|
@ -421,12 +421,12 @@
|
|||
<string name="pref_category_looks">Oтoбpaжeниe</string>
|
||||
<string name="trailer">Трейлер</string>
|
||||
<string name="single_plugin_disabled" formatted="true">%s (отключено)</string>
|
||||
<string name="next">Следующий</string>
|
||||
<string name="next">Далее</string>
|
||||
<string name="blank_repo_message">В CloudStream по умолчанию не установлены сайты. Вам необходимо установить сайты из репозиториев.
|
||||
\n
|
||||
\nИз-за безмозглой DMCA-атаки со стороны Sky UK Limited 🤮 мы не можем привязать сайт репозитория в приложении.
|
||||
\nИз-за безмозглой жалобы DMCA от Sky UK Limited 🤮 мы не можем привязать сайт репозитория в приложении.
|
||||
\n
|
||||
\nПрисоединяйтесь к нашему Discord или ищите в интернете.</string>
|
||||
\nПрисоединяйтесь к нашему Discord-серверу или найдите в интернете.</string>
|
||||
<string name="error_invalid_data">Недопустимые данные</string>
|
||||
<string name="resolution_and_title">Разрешение и название</string>
|
||||
<string name="previous">Предыдущий</string>
|
||||
|
@ -551,4 +551,5 @@
|
|||
\nБудет иметь общий приоритет видео 10.
|
||||
\n
|
||||
\nПРИМЕЧАНИЕ. Если сумма равна 10 или более, плеер автоматически пропустит загрузку при загрузке этой ссылки!</string>
|
||||
<string name="links_reloaded_toast">Ссылки перезагружены</string>
|
||||
</resources>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<string name="search_poster_img_des">Affisch</string>
|
||||
<string name="no_data">Ingen Data</string>
|
||||
<string name="episode_more_options_des">Mer Instälningar</string>
|
||||
<string name="go_back_img_des">Gå tillbacka</string>
|
||||
<string name="go_back_img_des">Gå tillbaka</string>
|
||||
<string name="next_episode">Nästa avsnitt</string>
|
||||
<string name="result_poster_img_des">Affisch</string>
|
||||
<string name="result_tags">Genrer</string>
|
||||
|
@ -24,8 +24,8 @@
|
|||
<string name="type_watching">Tittar på</string>
|
||||
<string name="type_on_hold">Pausad</string>
|
||||
<string name="type_completed">Avslutad</string>
|
||||
<string name="type_dropped">Dropped</string>
|
||||
<string name="type_plan_to_watch">Plannerad</string>
|
||||
<string name="type_dropped">Släppt</string>
|
||||
<string name="type_plan_to_watch">Planerad</string>
|
||||
<string name="play_movie_button">Spela Upp</string>
|
||||
<string name="play_torrent_button">Strömma Torrent</string>
|
||||
<string name="pick_source">Källor</string>
|
||||
|
@ -39,10 +39,10 @@
|
|||
<string name="app_dubbed_text">Dub</string>
|
||||
<string name="app_subbed_text">Sub</string>
|
||||
<string name="popup_delete_file">Ta bort</string>
|
||||
<string name="popup_play_file">Spela upp</string>
|
||||
<string name="popup_play_file">Spela upp fil</string>
|
||||
<string name="pref_disable_acra">Inaktivera automatisk felrapportering</string>
|
||||
<string name="home_more_info">Mer information</string>
|
||||
<string name="home_expanded_hide">Hide</string>
|
||||
<string name="home_expanded_hide">Göm</string>
|
||||
<string name="home_main_poster_img_des">@string/result_poster_img_des</string>
|
||||
<string name="home_play">Spela upp</string>
|
||||
<string name="home_info">Info</string>
|
||||
|
@ -64,10 +64,10 @@
|
|||
<string name="subs_font">Font</string>
|
||||
<string name="search_provider_text_providers">Sök med följande leverantörer</string>
|
||||
<string name="search_provider_text_types">Sök med följande filmtyper</string>
|
||||
<string name="benene_count_text">%d Bananer donerade till utvecklarna</string>
|
||||
<string name="benene_count_text">%d bananer donerade till utvecklarna</string>
|
||||
<string name="benene_count_text_none">Inga bananer givna</string>
|
||||
<string name="subs_auto_select_language">Automatisk val av undertextspråk</string>
|
||||
<string name="subs_download_languages">Automatisk nerladdaning av språk</string>
|
||||
<string name="subs_download_languages">Automatisk nedladdning av språk</string>
|
||||
<string name="subs_hold_to_reset_to_default">Håll inne för att återställa till standard</string>
|
||||
<string name="continue_watching">Fortsätt titta</string>
|
||||
<string name="action_remove_watching">Ta bort</string>
|
||||
|
@ -297,7 +297,7 @@
|
|||
<string name="referer">Referer</string>
|
||||
<string name="next">Nästa</string>
|
||||
<string name="subtitle_offset_hint">1000 ms</string>
|
||||
<string name="add_site_summary">Lägga till en klon av en befintlig webbplats med en annan webbadress.</string>
|
||||
<string name="add_site_summary">Lägga till en klon av en befintlig webbplats med en annan webbadress</string>
|
||||
<string name="error_invalid_id">Ogiltigt ID</string>
|
||||
<string name="error_invalid_url">Ogiltig webbadress</string>
|
||||
<string name="video_ram_description">Orsakar krascher om inställningen är för hög på enheter med lågt minne, t.ex. Android TV.</string>
|
||||
|
@ -403,7 +403,7 @@
|
|||
<string name="restart">Starta om</string>
|
||||
<string name="category_provider_test">Testa leverantörer</string>
|
||||
<string name="watch_quality_pref_data">Föredragen videokvalitet (Mobildata)</string>
|
||||
<string name="random_button_settings_desc">Visa en slumpknapp på förstasidan</string>
|
||||
<string name="random_button_settings_desc">Visa slumpmässig knapp på förstasidan och biblioteket</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="audio_tracks">Ljudspår</string>
|
||||
<string name="livestreams">Direktsändningar</string>
|
||||
|
@ -432,4 +432,164 @@
|
|||
<string name="profiles">Profiler</string>
|
||||
<string name="help">Hjälp</string>
|
||||
<string name="qualities">Kvalitet</string>
|
||||
<string name="stream">Ström</string>
|
||||
<string name="repository_name_hint">Databasens namn</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">All %s har redan laddats ner</string>
|
||||
<string name="download_all_plugins_from_repo">Ladda ner alla tillägg från den här databasen?</string>
|
||||
<string name="safe_mode_title">Felsäkert läge på</string>
|
||||
<string name="apply_on_restart">Applicera vid omstart</string>
|
||||
<string name="player_settings_play_in_app">Intern spelare</string>
|
||||
<string name="quality_cam_hd">Kamera HD</string>
|
||||
<string name="plugin_downloaded">Tillägg nedladdad</string>
|
||||
<string name="repository_url_hint">Lager URL</string>
|
||||
<string name="batch_download_start_format" formatted="true">Börjat hämta %1$d %2$s…</string>
|
||||
<string name="bottom_title_settings">Affisch titel plats</string>
|
||||
<string name="player_loaded_subtitles" formatted="true">Laddat %s</string>
|
||||
<string name="actor_main">Huvudsaklig</string>
|
||||
<string name="actor_supporting">Stödjande</string>
|
||||
<string name="actor_background">Bakgrund</string>
|
||||
<string name="view_public_repositories_button_short">Offentlig lista</string>
|
||||
<string name="extension_language">Språk</string>
|
||||
<string name="extension_install_first">Installera tillägget först</string>
|
||||
<string name="player_pref">Önskad videospelare</string>
|
||||
<string name="android_tv_interface_on_seek_settings">Spelare visas - Sök förlopp</string>
|
||||
<string name="android_tv_interface_on_seek_settings_summary">Sök förlopp som används när spelaren är synlig</string>
|
||||
<string name="start">Starta</string>
|
||||
<string name="stop">Stopp</string>
|
||||
<string name="revert">Återställ</string>
|
||||
<string name="subscription_deleted">Avslutat prenumerationen på %s</string>
|
||||
<string name="subscription_episode_released">Avsnitt %d släppt!</string>
|
||||
<string name="empty_library_logged_in_message">Den här listan är tom. Försök byta till en annan.</string>
|
||||
<string name="plugin_deleted">Tillägg borttagen</string>
|
||||
<string name="plugin_loaded">Tillägg laddade</string>
|
||||
<string name="plugin_singular">Tillägg</string>
|
||||
<string name="backup_frequency">Säkerhetskopierings antal</string>
|
||||
<string name="episode_sync_settings">Uppdatera visnings förlopp</string>
|
||||
<string name="automatic_plugin_download_mode_title">Välj läge för att filtrera nedladdning av plugins</string>
|
||||
<string name="extension_version">Utgåva</string>
|
||||
<string name="extension_status">Status</string>
|
||||
<string name="enter_pin">Ange PIN-kod</string>
|
||||
<string name="enter_pin_with_name" formatted="true">Ange PIN-kod för %s</string>
|
||||
<string name="enter_current_pin">Ange aktuell PIN-kod</string>
|
||||
<string name="lock_profile">Lås profil</string>
|
||||
<string name="pin_error_incorrect">Felaktig PIN-kod. Försök igen.</string>
|
||||
<string name="pin_error_length">PIN-koden måste vara fyra tecken</string>
|
||||
<string name="select_an_account">Välj ett konto</string>
|
||||
<string name="manage_accounts">Hantera konton</string>
|
||||
<string name="edit_account">Redigera konto</string>
|
||||
<string name="logged_account" formatted="true">Loggat in som %s</string>
|
||||
<string name="skip_startup_account_select_pref">Hoppa över val av konto vid start</string>
|
||||
<string name="auto_rotate_video_key">auto_rotera_video_nyckel</string>
|
||||
<string name="jsdelivr_proxy_summary">Förbi passera blockering av GitHub genom att använda jsDelivr. Kan göra att uppdateringar försenas med några dagar.</string>
|
||||
<string name="pref_category_actions">Funktion</string>
|
||||
<string name="preferred_media_settings">Önskad media</string>
|
||||
<string name="bottom_title_settings_des">Lägg titeln under affischen</string>
|
||||
<string name="added_sync_format" formatted="true">Tillagt %s</string>
|
||||
<string name="add_sync">Lägg till spårning</string>
|
||||
<string name="sync_score">Betygsatt</string>
|
||||
<string name="disable">Inaktivera</string>
|
||||
<string name="quality_webrip">Web</string>
|
||||
<string name="poster_image">Affischbild</string>
|
||||
<string name="preferred_media_subtext">Vad vill du se</string>
|
||||
<string name="add_repository">Lägg till databas</string>
|
||||
<string name="plugins_updated" formatted="true">Uppdaterade %d tillägg</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Nedladdat %1$d %2$s</string>
|
||||
<string name="plugins_disabled" formatted="true">Inaktiverad: %d</string>
|
||||
<string name="plugins_not_downloaded" formatted="true">Ej hämtad: %d</string>
|
||||
<string name="view_public_repositories_button">Visa community databaser</string>
|
||||
<string name="skip_type_mixed_op">Blandad inledning</string>
|
||||
<string name="skip_type_format" formatted="true">Skippa %s</string>
|
||||
<string name="blank_repo_message">CloudStream har inga webbplatser installerade som standard. Du måste installera webbplatserna från databaser.
|
||||
\n
|
||||
\nPå grund av en hjärnlös DMCA-borttagning av Sky UK Limited kan vi inte länka databaser på denna applikation.
|
||||
\n
|
||||
\nGå med i vår Discord eller sök online.</string>
|
||||
<string name="select_library">Välj bibliotek</string>
|
||||
<string name="empty_library_no_accounts_message">Ditt bibliotek är tomt :(
|
||||
\nLogga in på ett bibliotekskonto eller lägg till program i ditt lokala bibliotek.</string>
|
||||
<string name="enable_skip_op_from_database_des">Visa hoppa över popups för introduktion/eftertexter</string>
|
||||
<string name="action_remove_from_watched">Ta bort från sett</string>
|
||||
<string name="safe_mode_file">Fil i säkertläge hittades!
|
||||
\nLaddar inte några tillägg vid start tills filen har tagits bort.</string>
|
||||
<string name="subscription_in_progress_notification">Uppdaterar prenumererade program</string>
|
||||
<string name="subscription_list_name">Prenumererad</string>
|
||||
<string name="action_subscribe">Prenumerera</string>
|
||||
<string name="unable_to_inflate">UI kunde inte skapas korrekt, detta är en MASSIV BUG och bör rapporteras omedelbart %s</string>
|
||||
<string name="already_voted">Du har redan röstat</string>
|
||||
<string name="action_unsubscribe">Avprenumerera</string>
|
||||
<string name="action_add_to_favorites">Lägg till i favoriter</string>
|
||||
<string name="duplicate_add">Lägg till</string>
|
||||
<string name="duplicate_title">Potentiell Dublett Hittad</string>
|
||||
<string name="action_remove_from_favorites">Ta bort från favoriter</string>
|
||||
<string name="duplicate_replace">Ersätt</string>
|
||||
<string name="duplicate_replace_all">Ersätt alla</string>
|
||||
<string name="filler" formatted="true">Filler</string>
|
||||
<string name="pref_category_bypass">Förbikoppla ISP</string>
|
||||
<string name="quality_cam_rip">Kamera</string>
|
||||
<string name="quality_cam">Kamera</string>
|
||||
<string name="safe_mode_description">Alla tillägg stängdes av på grund av en krasch för att hjälpa dig hitta den som orsakar problem.</string>
|
||||
<string name="extension_size">Storlek</string>
|
||||
<string name="extension_authors">Författarna</string>
|
||||
<string name="extension_types">Stödd</string>
|
||||
<string name="no_plugins_found_error">Inga tillägg hittades i databasen</string>
|
||||
<string name="no_repository_found_error">Databasen hittades inte, kontrollera URL:n och prova VPN</string>
|
||||
<string name="batch_download">Flerfils nedladdning</string>
|
||||
<string name="plugin">Tillägg</string>
|
||||
<string name="plugins_downloaded" formatted="true">Hämtat: %d</string>
|
||||
<string name="extension_rating" formatted="true">Betyg: %s</string>
|
||||
<string name="clipboard_too_large">För mycket text. Det gick inte att spara till urklipp.</string>
|
||||
<string name="quality_hq">Hög kvalite</string>
|
||||
<string name="upload_sync">Synka</string>
|
||||
<string name="skip_type_ed">Slutet</string>
|
||||
<string name="skip_type_mixed_ed">Blandad avslut</string>
|
||||
<string name="subscription_new">Prenumererar på %s</string>
|
||||
<string name="safe_mode_crash_info">Visa krasch info</string>
|
||||
<string name="hls_playlist">HLS spellista</string>
|
||||
<string name="plugin_load_fail" formatted="true">Kunde inte ladda %s</string>
|
||||
<string name="skip_type_op">Inledning</string>
|
||||
<string name="skip_type_recap">Sammanfattning</string>
|
||||
<string name="pref_category_gestures">Gester</string>
|
||||
<string name="authenticated_user" formatted="true">%s autentiserad</string>
|
||||
<string name="extras">Statister</string>
|
||||
<string name="network_adress_example">Länka till strömmen</string>
|
||||
<string name="delete_repository">Radera databasen</string>
|
||||
<string name="profile_background_des">Profil bakgrund</string>
|
||||
<string name="quality_workprint">WP</string>
|
||||
<string name="auto_rotate_video">Auto rotera</string>
|
||||
<string name="rotate_video">Rotera</string>
|
||||
<string name="rotate_video_desc">Visa en växlingsknapp för skärmorientering</string>
|
||||
<string name="auto_rotate_video_desc">Aktivera automatisk växling av skärmorientering baserat på videoorientering</string>
|
||||
<string name="links_reloaded_toast">Länkar omladdade</string>
|
||||
<string name="android_tv_interface_off_seek_settings">Spelare dold - Sökförlopp</string>
|
||||
<string name="quality_blueray">Blu-ray</string>
|
||||
<string name="delete_repository_plugins">Detta kommer också att ta bort alla tillägg för databasen</string>
|
||||
<string name="setup_extensions_subtext">Ladda ner listan över webbplatser du vill använda</string>
|
||||
<string name="single_plugin_disabled" formatted="true">%s (Inaktiverad)</string>
|
||||
<string name="extension_description">Beskrivning</string>
|
||||
<string name="skip_type_creddits">Eftertexter</string>
|
||||
<string name="skip_type_intro">Introduktion</string>
|
||||
<string name="favorites_list_name">Favoriter</string>
|
||||
<string name="set_default">Ange standard</string>
|
||||
<string name="favorite_removed">%s togs bort från favoriter</string>
|
||||
<string name="favorite_added">%s har lagts till i favoriter</string>
|
||||
<string name="use_default_account">Använd standard konto</string>
|
||||
<string name="rotate_video_key">rotera_video_nyckel</string>
|
||||
<string name="pin">PIN-kod</string>
|
||||
<string name="android_tv_interface_off_seek_settings_summary">Sök mängden som används när spelaren är dold</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek:
|
||||
\n
|
||||
\n\'%s.\'
|
||||
\n
|
||||
\nVill du lägga till det här objektet ändå, ersätta det befintliga eller avbryta åtgärden?</string>
|
||||
<string name="duplicate_message_single" formatted="true">Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek: \'%s.\'
|
||||
\n
|
||||
\nVill du lägga till det här objektet ändå, ersätta det befintliga eller avbryta åtgärden?</string>
|
||||
<string name="quality_profile_help">Här kan du ändra hur källorna ska sorteras, om en video har högre prioritet visas den högre upp i källvalet. Summan av källprioriteten och kvalitetsprioriteten är videoprioriteten.
|
||||
\n
|
||||
\nKälla A: 3
|
||||
\nKvalitet B: 7
|
||||
\nKommer att ha en kombinerad videoprioritet på 10.
|
||||
\n
|
||||
\nOBS: Om summan är 10 eller mer kommer spelaren automatiskt att hoppa över laddningen när den länken laddas!</string>
|
||||
<string name="copyTitle">Titel kopierad!</string>
|
||||
</resources>
|
||||
|
|
|
@ -262,4 +262,8 @@
|
|||
<string name="add_account">Magdagdag ng Account</string>
|
||||
<string name="history">Kasaysayan</string>
|
||||
<string name="action_mark_as_watched">I-tanda bilang napanood na</string>
|
||||
<string name="update_started">Nagsimula ang Update</string>
|
||||
<string name="chromecast_subtitles_settings">Mga Subtitle ng Chromecast</string>
|
||||
<string name="chromecast_subtitles_settings_des">Mga setting ng mga subtitle ng Chromecast</string>
|
||||
<string name="play_trailer_button">Maglaro ng Trailer</string>
|
||||
</resources>
|
||||
|
|
|
@ -308,7 +308,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">Genel</string>
|
||||
<string name="random_button_settings">Rastgele İçerik</string>
|
||||
<string name="random_button_settings_desc">Ana sayfada rastgele bir film veya dizi seçen bir tuş göster</string>
|
||||
<string name="random_button_settings_desc">Anasayfada ve Kütüphanede rastgele düğmesini göster</string>
|
||||
<string name="provider_lang_settings">Sağlayıcı dilleri</string>
|
||||
<string name="app_layout">Uygulama düzeni</string>
|
||||
<string name="preferred_media_settings">Tercih edilen medya</string>
|
||||
|
@ -638,4 +638,5 @@
|
|||
<string name="rotate_video">Döndür</string>
|
||||
<string name="auto_rotate_video_desc">Video yönüne göre ekran yönünün otomatik olarak değişmesini sağla</string>
|
||||
<string name="links_reloaded_toast">Bağlantılar Yeniden Yüklendi</string>
|
||||
<string name="copyTitle">Başlık kopyalandı!</string>
|
||||
</resources>
|
||||
|
|
|
@ -282,7 +282,7 @@
|
|||
<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="random_button_settings_desc">Показувати кнопку на Головній сторінці та в Бібліотеці, яка вибирає випадковий фільм чи серіал</string>
|
||||
<string name="provider_lang_settings">Мови постачальника</string>
|
||||
<string name="app_layout">Макет застосунку</string>
|
||||
<string name="preferred_media_settings">Бажані медіа</string>
|
||||
|
@ -591,4 +591,5 @@
|
|||
<string name="auto_rotate_video_key">auto_rotate_video_key</string>
|
||||
<string name="auto_rotate_video">Автоповорот</string>
|
||||
<string name="auto_rotate_video_desc">Ввімкнути автоматичний поворот екрана на основі орієнтації відео</string>
|
||||
<string name="copyTitle">Назву скопійовано!</string>
|
||||
</resources>
|
||||
|
|
|
@ -309,7 +309,7 @@
|
|||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk.</string>
|
||||
<string name="category_general">通用</string>
|
||||
<string name="random_button_settings">随机按钮</string>
|
||||
<string name="random_button_settings_desc">在主页中显示随机按钮</string>
|
||||
<string name="random_button_settings_desc">在主页和库中显示随机按钮</string>
|
||||
<string name="provider_lang_settings">片源语言</string>
|
||||
<string name="app_layout">应用布局</string>
|
||||
<string name="preferred_media_settings">首选类型</string>
|
||||
|
@ -592,4 +592,22 @@
|
|||
\n注意:如果总和为 10 或更多,则加载该链接时播放器将自动跳过加载!</string>
|
||||
<string name="qualities">质量</string>
|
||||
<string name="profile_background_des">个人资料背景</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="links_reloaded_toast">链接已重新加载</string>
|
||||
<string name="disable">禁用</string>
|
||||
<string name="pin_error_incorrect">PIN码不正确,请再试一次。</string>
|
||||
<string name="pin_error_length">PIN码必须为4个字符</string>
|
||||
<string name="select_an_account">选择一个账户</string>
|
||||
<string name="manage_accounts">账户管理</string>
|
||||
<string name="action_subscribe">订阅</string>
|
||||
<string name="action_unsubscribe">取消订阅</string>
|
||||
<string name="duplicate_add">添加</string>
|
||||
<string name="duplicate_replace">替换</string>
|
||||
<string name="duplicate_replace_all">替换全部</string>
|
||||
<string name="no_plugins_found_error">仓库中未找到插件</string>
|
||||
<string name="rotate_video">旋转</string>
|
||||
<string name="auto_rotate_video">自动旋转</string>
|
||||
<string name="logged_account" formatted="true">以%s的身份登录</string>
|
||||
<string name="use_default_account">使用默认账户</string>
|
||||
<string name="automatic_plugin_download_mode_title">选择过滤插件的下载模式</string>
|
||||
</resources>
|
||||
|
|
|
@ -67,6 +67,8 @@
|
|||
<string name="enable_nsfw_on_providers_key" translatable="false">enable_nsfw_on_providers_key</string>
|
||||
<string name="skip_startup_account_select_key" translatable="false">skip_startup_account_select_key</string>
|
||||
<string name="enable_skip_op_from_database" translatable="false">enable_skip_op_from_database</string>
|
||||
<string name="rotate_video_key" translatable="false">rotate_video_key</string>
|
||||
<string name="auto_rotate_video_key" translatable="false">auto_rotate_video_key</string>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
|
||||
<string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
|
||||
|
@ -146,7 +148,7 @@
|
|||
<string name="download_done">Download Done</string>
|
||||
<string name="download_format" translatable="false">%s - %s</string>
|
||||
<string name="update_started">Update Started</string>
|
||||
<string name="stream">Stream</string>
|
||||
<string name="stream">Network stream</string>
|
||||
<string name="error_loading_links_toast">Error Loading Links</string>
|
||||
<string name="links_reloaded_toast">Links Reloaded</string>
|
||||
<string name="download_storage_text">Internal Storage</string>
|
||||
|
@ -171,6 +173,11 @@
|
|||
<string name="sort_close">Close</string>
|
||||
<string name="sort_clear">Clear</string>
|
||||
<string name="sort_save">Save</string>
|
||||
<string name="copyTitle">Title copied!</string>
|
||||
<string name="copyRepoUrl">Repo URL copied!</string>
|
||||
<string name="subscribe_tooltip">New episode notification</string>
|
||||
<string name="result_search_tooltip">Search in other extensions</string>
|
||||
<string name="recommendations_tooltip">Show recommendations</string>
|
||||
<string name="player_speed">Player Speed</string>
|
||||
<string name="subtitles_settings">Subtitle Settings</string>
|
||||
<string name="subs_text_color">Text Color</string>
|
||||
|
@ -210,8 +217,8 @@
|
|||
<string name="player_subtitles_settings_des">Player subtitles settings</string>
|
||||
<string name="chromecast_subtitles_settings">Chromecast Subtitles</string>
|
||||
<string name="chromecast_subtitles_settings_des">Chromecast subtitles settings</string>
|
||||
<string name="eigengraumode_settings">Eigengravy Mode</string>
|
||||
<string name="eigengraumode_settings_des">Adds a speed option in the player</string>
|
||||
<string name="eigengraumode_settings">Playback speed</string>
|
||||
<string name="speed_setting_summary">Adds a speed option in the player</string>
|
||||
<string name="swipe_to_seek_settings">Swipe to seek</string>
|
||||
<string name="swipe_to_seek_settings_des">Swipe from side to side to control your position in a video</string>
|
||||
<string name="swipe_to_change_settings">Swipe to change settings</string>
|
||||
|
@ -388,9 +395,9 @@
|
|||
<string name="video_disk_description">Causes problems if set too high on devices with low storage space, such as Android TV.</string>
|
||||
<string name="dns_pref">DNS over HTTPS</string>
|
||||
<string name="dns_pref_summary">Useful for bypassing ISP blocks</string>
|
||||
<string name="jsdelivr_proxy">raw.githubusercontent.com Proxy</string>
|
||||
<string name="jsdelivr_proxy">GitHub Proxy</string>
|
||||
<string name="jsdelivr_enabled">Could not reach GitHub. Turning on jsDelivr proxy…</string>
|
||||
<string name="jsdelivr_proxy_summary">Bypasses blocking of GitHub using jsDelivr. May cause updates to be delayed by few days.</string>
|
||||
<string name="jsdelivr_proxy_summary">Bypass blocking of raw github URLs using jsDelivr. May cause updates to be delayed by few days.</string>
|
||||
<string name="add_site_pref">Clone site</string>
|
||||
<string name="remove_site_pref">Remove site</string>
|
||||
<string name="add_site_summary">Add a clone of an existing site, with a different URL</string>
|
||||
|
@ -435,14 +442,16 @@
|
|||
<string name="pref_category_ui_features">Features</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="random_button_settings">Random Button</string>
|
||||
<string name="random_button_settings_desc">Show random button on Homepage</string>
|
||||
<string name="provider_lang_settings">Provider languages</string>
|
||||
<string name="random_button_settings_desc">Show random button on Homepage and Library</string>
|
||||
<string name="provider_lang_settings">Extension languages</string>
|
||||
<string name="app_layout">App Layout</string>
|
||||
<string name="preferred_media_settings">Preferred media</string>
|
||||
<string name="enable_nsfw_on_providers">Enable NSFW on supported providers</string>
|
||||
<string name="enable_nsfw_on_providers">Enable NSFW on supported Extensions</string>
|
||||
<string name="subtitles_encoding">Subtitle encoding</string>
|
||||
<string name="category_providers">Providers</string>
|
||||
<string name="category_provider_test">Provider test</string>
|
||||
<string name="test_extensions">Test all Extensions</string>
|
||||
<string name="test_extensions_summary">This Test is meant for developers only and does not verifies or denies working of any extension.</string>
|
||||
<string name="category_ui">Layout</string>
|
||||
<string name="automatic">Auto</string>
|
||||
<string name="tv_layout">TV layout</string>
|
||||
|
@ -459,11 +468,11 @@
|
|||
<string name="opensubtitles_key" translatable="false">opensubtitles_key</string>
|
||||
<string name="nginx_key" translatable="false">nginx_key</string>
|
||||
<string name="example_password">password123</string>
|
||||
<string name="example_username">MyCoolUsername</string>
|
||||
<string name="example_username">Username</string>
|
||||
<string name="example_email">hello@world.com</string>
|
||||
<string name="example_ip">127.0.0.1</string>
|
||||
<string name="example_site_name">MyCoolSite</string>
|
||||
<string name="example_site_url">example.com</string>
|
||||
<string name="example_site_name">NewSiteName</string>
|
||||
<string name="example_site_url">https://example.com</string>
|
||||
<string name="example_lang_name">Language code (en)</string>
|
||||
<!--
|
||||
<string name="mal_account_settings" translatable="false">MAL</string>
|
||||
|
@ -555,8 +564,8 @@
|
|||
<string name="subtitles_filter_lang">Filter by preferred media language</string>
|
||||
<string name="extras">Extras</string>
|
||||
<string name="trailer">Trailer</string>
|
||||
<string name="network_adress_example">Link to stream</string>
|
||||
<string name="referer">Referer</string>
|
||||
<string name="network_adress_example">https://example.com/example.mp4</string>
|
||||
<string name="referer">Referer (optional)</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="provider_languages_tip">Watch videos in these languages</string>
|
||||
<string name="previous">Previous</string>
|
||||
|
@ -591,8 +600,6 @@
|
|||
<string name="plugins_updated" formatted="true">Updated %d plugins</string>
|
||||
<string name="blank_repo_message">CloudStream has no sites installed by default. You need to install the sites from repositories.
|
||||
\n
|
||||
\nBecause of a brainless DMCA takedown by Sky UK Limited 🤮 we cannot link the repository site in app.
|
||||
\n
|
||||
\nJoin our Discord or search online.</string>
|
||||
<string name="view_public_repositories_button">View community repositories</string>
|
||||
<string name="view_public_repositories_button_short">Public list</string>
|
||||
|
@ -735,9 +742,7 @@
|
|||
<string name="skip_startup_account_select_pref">Skip account selection at startup</string>
|
||||
<string name="use_default_account">Use Default Account</string>
|
||||
<string name="rotate_video">Rotate</string>
|
||||
<string name="rotate_video_key">rotate_video_key</string>
|
||||
<string name="rotate_video_desc">Display a toggle button for screen orientation</string>
|
||||
<string name="auto_rotate_video_key">auto_rotate_video_key</string>
|
||||
<string name="auto_rotate_video_desc">Enable automatic switching of screen orientation based on video orientation</string>
|
||||
<string name="auto_rotate_video">Auto rotate</string>
|
||||
</resources>
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
app:key="@string/player_resize_enabled_key" />
|
||||
<SwitchPreference
|
||||
android:icon="@drawable/ic_baseline_speed_24"
|
||||
android:summary="@string/eigengraumode_settings_des"
|
||||
android:summary="@string/speed_setting_summary"
|
||||
android:title="@string/eigengraumode_settings"
|
||||
app:defaultValue="false"
|
||||
app:key="@string/playback_speed_enabled_key" />
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<Preference
|
||||
android:icon="@drawable/baseline_network_ping_24"
|
||||
android:key="@string/test_providers_key"
|
||||
android:title="Test all providers" />
|
||||
android:title="@string/test_extensions"
|
||||
android:summary="@string/test_extensions_summary"/>
|
||||
|
||||
</PreferenceScreen>
|
|
@ -5,8 +5,8 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.1.4")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21")
|
||||
classpath("com.android.tools.build:gradle:8.2.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
@ -22,7 +22,7 @@ allprojects {
|
|||
}
|
||||
|
||||
plugins {
|
||||
id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false
|
||||
id("com.google.devtools.ksp") version "1.9.22-1.0.16" apply false
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
عملو ستريم للأفلاف والمسلسلات والأنمي.
|
|
@ -0,0 +1 @@
|
|||
- Dodan je dnevnik promjena!
|
|
@ -0,0 +1,10 @@
|
|||
CloudStream-3 omogućuje emitiranje i preuzimanje filmova, TV serija i animea.
|
||||
|
||||
Aplikacija ne koristi oglase i analitike te
|
||||
podržava stranice s trailerima, filmovima i više, npr.
|
||||
|
||||
Oznake
|
||||
|
||||
Preuzimanja titlova
|
||||
|
||||
Chromecast podrška
|
|
@ -0,0 +1 @@
|
|||
Emitirajte i preuzmite filmove, TV serije i anime.
|
|
@ -0,0 +1 @@
|
|||
CloudStream
|
|
@ -0,0 +1 @@
|
|||
- Ändringslogg tillagd!
|
|
@ -0,0 +1,10 @@
|
|||
CloudStream-3 låter dig streama och ladda ner filmer, TV-serier och anime.
|
||||
|
||||
Appen levereras utan annonser och analyser och
|
||||
stöder flera trailer- och film webbplatser och mer, t.ex.
|
||||
|
||||
Bokmärken
|
||||
|
||||
Nedladdning av undertexter
|
||||
|
||||
Stöd för Chromecast
|
|
@ -0,0 +1 @@
|
|||
Streama och ladda ner filmer, TV serier och anime.
|
|
@ -0,0 +1 @@
|
|||
CloudStream
|
|
@ -19,6 +19,5 @@ android.useAndroidX=true
|
|||
# android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
|
@ -1,6 +1,6 @@
|
|||
#Fri Apr 30 17:11:15 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Loading…
Reference in New Issue