mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'recloudstream:master' into master
This commit is contained in:
commit
326080a9ef
53 changed files with 759 additions and 222 deletions
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,8 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Report provider bug
|
||||
- name: Request a new provider or report bug with an existing provider
|
||||
url: https://github.com/recloudstream
|
||||
about: Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
|
||||
about: Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
|
||||
- name: Discord
|
||||
url: https://discord.gg/5Hus6fM
|
||||
about: Join our discord for faster support on smaller issues.
|
||||
|
|
2
.github/workflows/issue_action.yml
vendored
2
.github/workflows/issue_action.yml
vendored
|
@ -2,7 +2,7 @@ name: Issue automatic actions
|
|||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
issue-moderator:
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
|
||||
<queries>
|
||||
<package android:name="org.videolan.vlc" />
|
||||
<package android:name="com.instantbits.cast.webvideo" />
|
||||
<package android:name="is.xyz.mpv" />
|
||||
</queries>
|
||||
|
||||
<!--TODO https://stackoverflow.com/questions/41799732/chromecast-button-not-visible-in-android-->
|
||||
<application
|
||||
android:name=".AcraApplication"
|
||||
|
|
|
@ -10,16 +10,22 @@ import android.util.Log
|
|||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||
import com.lagradost.cloudstream3.ui.result.UiText
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.Event
|
||||
import com.lagradost.cloudstream3.utils.UIHelper
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
|
||||
|
@ -34,6 +40,7 @@ object CommonActivity {
|
|||
return (this as MainActivity?)?.mSessionManager?.currentCastSession
|
||||
}
|
||||
|
||||
|
||||
var canEnterPipMode: Boolean = false
|
||||
var canShowPipMode: Boolean = false
|
||||
var isInPIPMode: Boolean = false
|
||||
|
@ -117,7 +124,7 @@ object CommonActivity {
|
|||
setLocale(this, localeCode)
|
||||
}
|
||||
|
||||
fun init(act: Activity?) {
|
||||
fun init(act: ComponentActivity?) {
|
||||
if (act == null) return
|
||||
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
|
||||
//https://developer.android.com/guide/topics/ui/picture-in-picture
|
||||
|
@ -129,6 +136,22 @@ object CommonActivity {
|
|||
act.updateLocale()
|
||||
act.updateTv()
|
||||
NewPipe.init(DownloaderTestImpl.getInstance())
|
||||
|
||||
for (resumeApp in resumeApps) {
|
||||
resumeApp.launcher =
|
||||
act.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
if (resultCode == AppCompatActivity.RESULT_OK && data != null && resumeApp.position != null && resumeApp.duration != null) {
|
||||
val pos = data.getLongExtra(resumeApp.position, -1L)
|
||||
val dur = data.getLongExtra(resumeApp.duration, -1L)
|
||||
if (dur > 0L && pos > 0L)
|
||||
DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur)
|
||||
removeKey(resumeApp.lastId)
|
||||
ResultFragment.updateUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Activity.enterPIPMode() {
|
||||
|
@ -167,7 +190,7 @@ object CommonActivity {
|
|||
"Amoled" -> R.style.AmoledMode
|
||||
"AmoledLight" -> R.style.AmoledModeLight
|
||||
"Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
R.style.MonetMode else R.style.AppTheme
|
||||
R.style.MonetMode else R.style.AppTheme
|
||||
else -> R.style.AppTheme
|
||||
}
|
||||
|
||||
|
|
|
@ -381,7 +381,16 @@ abstract class MainAPI {
|
|||
open var storedCredentials: String? = null
|
||||
open var canBeOverridden: Boolean = true
|
||||
|
||||
//open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
|
||||
/** if this is turned on then it will request the homepage one after the other,
|
||||
used to delay if they block many request at the same time*/
|
||||
open var sequentialMainPage : Boolean = false
|
||||
/** in milliseconds, this can be used to add more delay between homepage requests
|
||||
* on first load if sequentialMainPage is turned on */
|
||||
open var sequentialMainPageDelay : Long = 0L
|
||||
/** in milliseconds, this can be used to add more delay between homepage requests when scrolling */
|
||||
open var sequentialMainPageScrollDelay : Long = 0L
|
||||
/** used to keep track when last homepage request was in unixtime ms */
|
||||
var lastHomepageRequest : Long = 0L
|
||||
|
||||
open var lang = "en" // ISO_639_1 check SubtitleHelper
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import android.view.KeyEvent
|
|||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -35,6 +35,8 @@ 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.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.loadThemes
|
||||
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
||||
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||
|
@ -53,7 +55,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStri
|
|||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
|
@ -67,10 +68,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
|||
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
||||
import com.lagradost.cloudstream3.utils.Event
|
||||
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||
|
@ -95,17 +94,65 @@ import java.nio.charset.Charset
|
|||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
const val VLC_PACKAGE = "org.videolan.vlc"
|
||||
const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
|
||||
val VLC_COMPONENT: ComponentName =
|
||||
ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
|
||||
const val VLC_REQUEST_CODE = 42
|
||||
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
|
||||
//https://wiki.videolan.org/Android_Player_Intents/
|
||||
|
||||
const val VLC_FROM_START = -1
|
||||
const val VLC_FROM_PROGRESS = -2
|
||||
const val VLC_EXTRA_POSITION_OUT = "extra_position"
|
||||
const val VLC_EXTRA_DURATION_OUT = "extra_duration"
|
||||
const val VLC_LAST_ID_KEY = "vlc_last_open_id"
|
||||
//https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904
|
||||
//https://mpv-android.github.io/mpv-android/intent.html
|
||||
|
||||
// https://www.webvideocaster.com/integrations
|
||||
|
||||
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
|
||||
|
||||
const val VLC_PACKAGE = "org.videolan.vlc"
|
||||
const val MPV_PACKAGE = "is.xyz.mpv"
|
||||
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
|
||||
|
||||
val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
|
||||
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
|
||||
|
||||
//TODO REFACTOR AF
|
||||
data class ResultResume(
|
||||
val packageString: String,
|
||||
val action: String = Intent.ACTION_VIEW,
|
||||
val position: String? = null,
|
||||
val duration: String? = null,
|
||||
var launcher: ActivityResultLauncher<Intent>? = null,
|
||||
) {
|
||||
val lastId get() = "${packageString}_last_open_id"
|
||||
suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
|
||||
val intent = Intent(action)
|
||||
|
||||
if (id != null)
|
||||
setKey(lastId, id)
|
||||
else
|
||||
removeKey(lastId)
|
||||
|
||||
intent.setPackage(packageString)
|
||||
callback.invoke(intent)
|
||||
launcher?.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
val VLC = ResultResume(
|
||||
VLC_PACKAGE,
|
||||
"org.videolan.vlc.player.result",
|
||||
"extra_position",
|
||||
"extra_duration",
|
||||
)
|
||||
|
||||
val MPV = ResultResume(
|
||||
MPV_PACKAGE,
|
||||
//"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
|
||||
position = "position",
|
||||
duration = "duration",
|
||||
)
|
||||
|
||||
val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
|
||||
|
||||
val resumeApps = arrayOf(
|
||||
VLC, MPV, WEB_VIDEO
|
||||
)
|
||||
|
||||
// Short name for requests client to make it nicer to use
|
||||
|
||||
|
@ -372,31 +419,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == VLC_REQUEST_CODE) {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
val pos: Long =
|
||||
data.getLongExtra(
|
||||
VLC_EXTRA_POSITION_OUT,
|
||||
-1
|
||||
) //Last position in media when player exited
|
||||
val dur: Long =
|
||||
data.getLongExtra(
|
||||
VLC_EXTRA_DURATION_OUT,
|
||||
-1
|
||||
) //Last position in media when player exited
|
||||
val id = getKey<Int>(VLC_LAST_ID_KEY)
|
||||
println("SET KEY $id at $pos / $dur")
|
||||
if (dur > 0 && pos > 0) {
|
||||
setViewPos(id, pos, dur)
|
||||
}
|
||||
removeKey(VLC_LAST_ID_KEY)
|
||||
ResultFragment.updateUI()
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
val broadcastIntent = Intent()
|
||||
broadcastIntent.action = "restart_service"
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import android.util.Log
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64Decode
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
class AStreamHub : ExtractorApi() {
|
||||
override val name = "AStreamHub"
|
||||
override val mainUrl = "https://astreamhub.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
app.get(url).document.selectFirst("body > script").let { script ->
|
||||
val text = script?.html() ?: ""
|
||||
Log.i("Dev", "text => $text")
|
||||
if (text.isNotBlank()) {
|
||||
val m3link = "(?<=file:)(.*)(?=,)".toRegex().find(text)
|
||||
?.groupValues?.get(0)?.trim()?.trim('"') ?: ""
|
||||
Log.i("Dev", "m3link => $m3link")
|
||||
if (m3link.isNotBlank()) {
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name = name,
|
||||
source = name,
|
||||
url = m3link,
|
||||
isM3u8 = true,
|
||||
quality = Qualities.Unknown.value,
|
||||
referer = referer ?: url
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.APIHolder.removePluginMapping
|
|||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||
import com.lagradost.cloudstream3.mvvm.debugPrint
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -123,6 +124,10 @@ object PluginManager {
|
|||
val plugins = getPluginsOnline().filter {
|
||||
!it.filePath.contains(repositoryPath)
|
||||
}
|
||||
val file = File(repositoryPath)
|
||||
normalSafeApiCall {
|
||||
if (file.exists()) file.deleteRecursively()
|
||||
}
|
||||
setKey(PLUGINS_KEY, plugins)
|
||||
}
|
||||
}
|
||||
|
@ -174,8 +179,16 @@ object PluginManager {
|
|||
val onlineData: Pair<String, SitePlugin>,
|
||||
) {
|
||||
val isOutdated =
|
||||
onlineData.second.version != savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
|
||||
onlineData.second.version > savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
|
||||
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN
|
||||
|
||||
fun validOnlineData(context: Context): Boolean {
|
||||
return getPluginPath(
|
||||
context,
|
||||
savedData.internalName,
|
||||
onlineData.first
|
||||
).absolutePath == savedData.filePath
|
||||
}
|
||||
}
|
||||
|
||||
// var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet()
|
||||
|
@ -225,6 +238,8 @@ object PluginManager {
|
|||
.filter { onlineData -> savedData.internalName == onlineData.second.internalName }
|
||||
.map { onlineData ->
|
||||
OnlinePluginData(savedData, onlineData)
|
||||
}.filter {
|
||||
it.validOnlineData(activity)
|
||||
}
|
||||
}.flatten().distinctBy { it.onlineData.second.url }
|
||||
|
||||
|
@ -416,6 +431,18 @@ object PluginManager {
|
|||
) + "." + name.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* This should not be changed as it is used to also detect if a plugin is installed!
|
||||
**/
|
||||
fun getPluginPath(
|
||||
context: Context,
|
||||
internalName: String,
|
||||
repositoryUrl: String
|
||||
): File {
|
||||
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
|
||||
val fileName = getPluginSanitizedFileName(internalName)
|
||||
return File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for fresh installs
|
||||
|
@ -426,9 +453,7 @@ object PluginManager {
|
|||
internalName: String,
|
||||
repositoryUrl: String
|
||||
): Boolean {
|
||||
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
|
||||
val fileName = getPluginSanitizedFileName(internalName)
|
||||
val file = File("${activity.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
|
||||
val file = getPluginPath(activity, internalName, repositoryUrl)
|
||||
downloadAndLoadPlugin(activity, pluginUrl, internalName, file)
|
||||
return true
|
||||
}
|
||||
|
@ -454,7 +479,13 @@ object PluginManager {
|
|||
return loadPlugin(
|
||||
activity,
|
||||
newFile ?: return false,
|
||||
PluginData(internalName, pluginUrl, true, newFile.absolutePath, PLUGIN_VERSION_NOT_SET)
|
||||
PluginData(
|
||||
internalName,
|
||||
pluginUrl,
|
||||
true,
|
||||
newFile.absolutePath,
|
||||
PLUGIN_VERSION_NOT_SET
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
|
@ -462,18 +493,13 @@ object PluginManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isFilePath will treat the pluginUrl as as the filepath instead of url
|
||||
* */
|
||||
suspend fun deletePlugin(pluginIdentifier: String, isFilePath: Boolean): Boolean {
|
||||
val data =
|
||||
(if (isFilePath) (getPluginsLocal() + getPluginsOnline()).firstOrNull { it.filePath == pluginIdentifier }
|
||||
else getPluginsOnline().firstOrNull { it.url == pluginIdentifier }) ?: return false
|
||||
suspend fun deletePlugin(file: File): Boolean {
|
||||
val list = (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
|
||||
|
||||
return try {
|
||||
if (File(data.filePath).delete()) {
|
||||
unloadPlugin(data.filePath)
|
||||
deletePluginData(data)
|
||||
if (File(file.absolutePath).delete()) {
|
||||
unloadPlugin(file.absolutePath)
|
||||
list.forEach { deletePluginData(it) }
|
||||
return true
|
||||
}
|
||||
false
|
||||
|
|
|
@ -37,7 +37,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
val subtitleProviders
|
||||
get() = listOf(
|
||||
openSubtitlesApi,
|
||||
// indexSubtitlesApi // they got anti scraping measures in place :(
|
||||
indexSubtitlesApi // they got anti scraping measures in place :(
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
|
|
@ -20,10 +20,9 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
|
||||
override fun logOut() {}
|
||||
|
||||
private val interceptor = CloudflareKiller()
|
||||
|
||||
companion object {
|
||||
const val host = "https://indexsubtitle.com"
|
||||
const val host = "https://subscene.cyou"
|
||||
const val TAG = "INDEXSUBS"
|
||||
}
|
||||
|
||||
|
@ -126,16 +125,15 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
epNumber = epNum,
|
||||
seasonNumber = seasonNum,
|
||||
year = yearNum,
|
||||
headers = interceptor.getCookieHeaders(link).toMap()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val document = app.get("$host/?search=$queryText", interceptor = interceptor).document
|
||||
val document = app.get("$host/?search=$queryText").document
|
||||
|
||||
document.select("div.my-3.p-3 div.media").map { block ->
|
||||
if (seasonNum > 0) {
|
||||
val name = block.select("strong.text-primary").text().trim()
|
||||
val name = block.select("strong.text-primary, strong.text-info").text().trim()
|
||||
val season = getOrdinal(seasonNum)
|
||||
if ((block.selectFirst("a")?.attr("href")
|
||||
?.contains(
|
||||
|
@ -163,7 +161,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
val urlItem = fixUrl(
|
||||
it.selectFirst("a")!!.attr("href")
|
||||
)
|
||||
val itemDoc = app.get(urlItem, interceptor = interceptor).document
|
||||
val itemDoc = app.get(urlItem).document
|
||||
val id = imdbUrlToIdNullable(
|
||||
itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent()
|
||||
?.attr("href")
|
||||
|
@ -202,14 +200,14 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
val results = mutableListOf<AbstractSubtitleEntities.SubtitleEntity>()
|
||||
|
||||
urlItems.forEach { url ->
|
||||
val request = app.get(url, interceptor = interceptor)
|
||||
val request = app.get(url)
|
||||
if (request.isSuccessful) {
|
||||
request.document.select("div.my-3.p-3 div.media").map { block ->
|
||||
if (block.select("span.d-block span[data-original-title=Language]").text()
|
||||
.trim()
|
||||
.contains("$queryLang")
|
||||
) {
|
||||
var name = block.select("strong.text-primary").text().trim()
|
||||
var name = block.select("strong.text-primary, strong.text-info").text().trim()
|
||||
val link = fixUrl(block.selectFirst("a")!!.attr("href"))
|
||||
if (seasonNum > 0) {
|
||||
when {
|
||||
|
@ -235,7 +233,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
val seasonNum = data.seasonNumber
|
||||
val epNum = data.epNumber
|
||||
|
||||
val req = app.get(data.data, interceptor = interceptor)
|
||||
val req = app.get(data.data)
|
||||
|
||||
if (req.isSuccessful) {
|
||||
val document = req.document
|
||||
|
@ -246,7 +244,7 @@ class IndexSubtitleApi : AbstractSubApi {
|
|||
} else {
|
||||
document.select("div.my-3.p-3 div.media").mapNotNull { block ->
|
||||
val name =
|
||||
block.selectFirst("strong.d-block.text-primary")?.text()?.trim().toString()
|
||||
block.selectFirst("strong.d-block")?.text()?.trim().toString()
|
||||
if (seasonNum!! > 0) {
|
||||
if (isRightEps(name, seasonNum, epNum)) {
|
||||
fixUrl(block.selectFirst("a")!!.attr("href"))
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package com.lagradost.cloudstream3.ui
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class APIRepository(val api: MainAPI) {
|
||||
companion object {
|
||||
|
@ -62,12 +64,33 @@ class APIRepository(val api: MainAPI) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun waitForHomeDelay() {
|
||||
val delta = api.sequentialMainPageScrollDelay + api.lastHomepageRequest - unixTimeMS
|
||||
if(delta < 0) return
|
||||
delay(delta)
|
||||
}
|
||||
|
||||
suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource<List<HomePageResponse?>> {
|
||||
return safeApiCall {
|
||||
api.lastHomepageRequest = unixTimeMS
|
||||
|
||||
nameIndex?.let { api.mainPage.getOrNull(it) }?.let { data ->
|
||||
listOf(api.getMainPage(page, MainPageRequest(data.name, data.data)))
|
||||
} ?: api.mainPage.apmap { data ->
|
||||
api.getMainPage(page, MainPageRequest(data.name, data.data))
|
||||
} ?: run {
|
||||
if (api.sequentialMainPage) {
|
||||
var first = true
|
||||
api.mainPage.map { data ->
|
||||
if (!first) // dont want to sleep on first request
|
||||
delay(api.sequentialMainPageDelay)
|
||||
first = false
|
||||
|
||||
api.getMainPage(page, MainPageRequest(data.name, data.data))
|
||||
}
|
||||
} else {
|
||||
api.mainPage.apmap { data ->
|
||||
api.getMainPage(page, MainPageRequest(data.name, data.data))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -256,13 +256,14 @@ class HomeFragment : Fragment() {
|
|||
nsfw: MaterialButton?,
|
||||
others: MaterialButton?,
|
||||
): List<Pair<MaterialButton?, List<TvType>>> {
|
||||
// This list should be same order as home screen to aid navigation
|
||||
return listOf(
|
||||
Pair(anime, listOf(TvType.Anime, TvType.OVA, TvType.AnimeMovie)),
|
||||
Pair(cartoons, listOf(TvType.Cartoon)),
|
||||
Pair(tvs, listOf(TvType.TvSeries)),
|
||||
Pair(docs, listOf(TvType.Documentary)),
|
||||
Pair(movies, listOf(TvType.Movie, TvType.Torrent)),
|
||||
Pair(tvs, listOf(TvType.TvSeries)),
|
||||
Pair(anime, listOf(TvType.Anime, TvType.OVA, TvType.AnimeMovie)),
|
||||
Pair(asian, listOf(TvType.AsianDrama)),
|
||||
Pair(cartoons, listOf(TvType.Cartoon)),
|
||||
Pair(docs, listOf(TvType.Documentary)),
|
||||
Pair(livestream, listOf(TvType.Live)),
|
||||
Pair(nsfw, listOf(TvType.NSFW)),
|
||||
Pair(others, listOf(TvType.Others)),
|
||||
|
@ -352,11 +353,25 @@ class HomeFragment : Fragment() {
|
|||
arrayAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Since fire tv is fucked we need to manually define the focus layout.
|
||||
* Since visible buttons are only known in runtime this is required.
|
||||
**/
|
||||
var lastButton: MaterialButton? = null
|
||||
|
||||
for ((button, validTypes) in pairList) {
|
||||
val isValid =
|
||||
validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
|
||||
button?.isVisible = isValid
|
||||
if (isValid) {
|
||||
|
||||
// Set focus navigation
|
||||
button?.let { currentButton ->
|
||||
lastButton?.nextFocusRightId = currentButton.id
|
||||
lastButton?.id?.let { currentButton.nextFocusLeftId = it }
|
||||
lastButton = currentButton
|
||||
}
|
||||
|
||||
fun buttonContains(): Boolean {
|
||||
return preSelectedTypes.any { validTypes.contains(it) }
|
||||
}
|
||||
|
@ -506,15 +521,12 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
//Disable Random button, if its toggled off on settings
|
||||
//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)
|
||||
home_random?.isVisible = toggleRandomButton
|
||||
if (!toggleRandomButton) {
|
||||
home_random?.visibility = View.GONE
|
||||
}
|
||||
home_random?.visibility = View.GONE
|
||||
}
|
||||
|
||||
observe(homeViewModel.apiName) { apiName ->
|
||||
|
@ -611,6 +623,7 @@ class HomeFragment : Fragment() {
|
|||
home_loading_shimmer?.stopShimmer()
|
||||
|
||||
val d = data.value
|
||||
val mutableListOfResponse = mutableListOf<SearchResponse>()
|
||||
listHomepageItems.clear()
|
||||
|
||||
// println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}")
|
||||
|
@ -623,6 +636,11 @@ class HomeFragment : Fragment() {
|
|||
home_loading_error?.isVisible = false
|
||||
home_loaded?.isVisible = true
|
||||
if (toggleRandomButton) {
|
||||
//Flatten list
|
||||
d.values.forEach { dlist ->
|
||||
mutableListOfResponse.addAll(dlist.list.list)
|
||||
}
|
||||
listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url })
|
||||
home_random?.isVisible = listHomepageItems.isNotEmpty()
|
||||
} else {
|
||||
home_random?.isGone = true
|
||||
|
@ -1016,4 +1034,4 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,6 +162,8 @@ class HomeViewModel : ViewModel() {
|
|||
lock += name
|
||||
|
||||
repo?.apply {
|
||||
waitForHomeDelay()
|
||||
|
||||
expandable[name]?.let { current ->
|
||||
debugAssert({ !current.hasNext }) {
|
||||
"Expand called when not needed"
|
||||
|
@ -276,9 +278,6 @@ class HomeViewModel : ViewModel() {
|
|||
if (preferredApiName == noneApi.name) {
|
||||
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
|
||||
loadAndCancel(noneApi)
|
||||
// If the plugin isn't loaded yet. (Does not set the key)
|
||||
} else if (api == null) {
|
||||
loadAndCancel(noneApi)
|
||||
} else if (preferredApiName == randomApi.name) {
|
||||
val validAPIs = context?.filterProviderByPreferredMedia()
|
||||
if (validAPIs.isNullOrEmpty()) {
|
||||
|
@ -289,6 +288,9 @@ class HomeViewModel : ViewModel() {
|
|||
loadAndCancel(apiRandom)
|
||||
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
|
||||
}
|
||||
// If the plugin isn't loaded yet. (Does not set the key)
|
||||
} else if (api == null) {
|
||||
loadAndCancel(noneApi)
|
||||
} else {
|
||||
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
|
||||
loadAndCancel(api)
|
||||
|
|
|
@ -58,6 +58,7 @@ import kotlinx.android.synthetic.main.player_select_source_and_subs.*
|
|||
import kotlinx.android.synthetic.main.player_select_source_and_subs.subtitles_click_settings
|
||||
import kotlinx.android.synthetic.main.player_select_tracks.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class GeneratorPlayer : FullScreenPlayer() {
|
||||
companion object {
|
||||
|
@ -115,10 +116,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
override fun onTracksInfoChanged() {
|
||||
val tracks = player.getVideoTracks()
|
||||
player_tracks_btt?.isVisible = tracks.allVideoTracks.size > 1 || tracks.allAudioTracks.size > 1
|
||||
player_tracks_btt?.isVisible =
|
||||
tracks.allVideoTracks.size > 1 || tracks.allAudioTracks.size > 1
|
||||
// Only set the preferred language if it is available.
|
||||
// Otherwise it may give some users audio track init failed!
|
||||
if (tracks.allAudioTracks.any { it.language == preferredAudioTrackLanguage }){
|
||||
if (tracks.allAudioTracks.any { it.language == preferredAudioTrackLanguage }) {
|
||||
player.setPreferredAudioTrack(preferredAudioTrackLanguage)
|
||||
}
|
||||
}
|
||||
|
@ -602,8 +604,20 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
subtitleList.setItemChecked(subtitleIndex, true)
|
||||
|
||||
subtitleList.setOnItemClickListener { _, _, which, _ ->
|
||||
subtitleIndex = which
|
||||
subtitleList.setItemChecked(which, true)
|
||||
if (which > currentSubtitles.size) {
|
||||
// Since android TV is funky the setOnItemClickListener will be triggered
|
||||
// instead of setOnClickListener when selecting. To override this we programmatically
|
||||
// click the view when selecting an item outside the list.
|
||||
|
||||
// Cheeky way of getting the view at that position to click it
|
||||
// to avoid keeping track of the various footers.
|
||||
// getChildAt() gives null :(
|
||||
val child = subtitleList.adapter.getView(which, null, subtitleList)
|
||||
child?.performClick()
|
||||
} else {
|
||||
subtitleIndex = which
|
||||
subtitleList.setItemChecked(which, true)
|
||||
}
|
||||
}
|
||||
|
||||
sourceDialog.cancel_btt?.setOnClickListener {
|
||||
|
@ -762,7 +776,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||
// audioArrayAdapter.add(ctx.getString(R.string.no_subtitles))
|
||||
audioArrayAdapter.addAll(currentAudioTracks.mapIndexed { index, format ->
|
||||
format.label ?: format.language?.let { fromTwoLettersToLanguage(it) } ?: index.toString()
|
||||
format.label ?: format.language?.let { fromTwoLettersToLanguage(it) }
|
||||
?: index.toString()
|
||||
})
|
||||
|
||||
audioList.adapter = audioArrayAdapter
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -9,6 +10,7 @@ import android.widget.TextView
|
|||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
@ -53,6 +55,10 @@ const val ACTION_SHOW_DESCRIPTION = 15
|
|||
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13
|
||||
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
|
||||
|
||||
const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
|
||||
const val ACTION_PLAY_EPISODE_IN_MPV = 17
|
||||
|
||||
|
||||
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
||||
|
||||
class EpisodeAdapter(
|
||||
|
@ -60,6 +66,25 @@ class EpisodeAdapter(
|
|||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
companion object {
|
||||
/**
|
||||
* @return ACTION_PLAY_EPISODE_IN_PLAYER, ACTION_PLAY_EPISODE_IN_BROWSER or ACTION_PLAY_EPISODE_IN_VLC_PLAYER depending on player settings.
|
||||
* See array.xml/player_pref_values
|
||||
**/
|
||||
fun getPlayerAction(context: Context): Int {
|
||||
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return when (settingsManager.getInt(context.getString(R.string.player_pref_key), 1)) {
|
||||
1 -> ACTION_PLAY_EPISODE_IN_PLAYER
|
||||
2 -> ACTION_PLAY_EPISODE_IN_VLC_PLAYER
|
||||
3 -> ACTION_PLAY_EPISODE_IN_BROWSER
|
||||
4 -> ACTION_PLAY_EPISODE_IN_WEB_VIDEO
|
||||
5 -> ACTION_PLAY_EPISODE_IN_MPV
|
||||
else -> ACTION_PLAY_EPISODE_IN_PLAYER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cardList: MutableList<ResultEpisode> = mutableListOf()
|
||||
|
||||
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
||||
|
|
|
@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
|||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
@ -455,7 +456,8 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
val apiName: String,
|
||||
val showFillers: Boolean,
|
||||
val dubStatus: DubStatus,
|
||||
val start: AutoResume?
|
||||
val start: AutoResume?,
|
||||
val playerAction: Int
|
||||
)
|
||||
|
||||
private fun getStoredData(context: Context): StoredData? {
|
||||
|
@ -469,6 +471,8 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
) DubStatus.Dubbed else DubStatus.Subbed
|
||||
val startAction = arguments?.getInt(START_ACTION_BUNDLE)
|
||||
|
||||
val playerAction = getPlayerAction(context)
|
||||
|
||||
val start = startAction?.let { action ->
|
||||
val startValue = arguments?.getInt(START_VALUE_BUNDLE)
|
||||
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
|
||||
|
@ -483,7 +487,7 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
season = resumeSeason
|
||||
)
|
||||
}
|
||||
return StoredData(url, apiName, showFillers, dubStatus, start)
|
||||
return StoredData(url, apiName, showFillers, dubStatus, start, playerAction)
|
||||
}
|
||||
|
||||
private fun reloadViewModel(success: Boolean = false) {
|
||||
|
@ -774,7 +778,8 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
viewModel.handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(
|
||||
ACTION_PLAY_EPISODE_IN_PLAYER, value.result
|
||||
storedData?.playerAction ?: ACTION_PLAY_EPISODE_IN_PLAYER,
|
||||
value.result
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
@ -16,6 +15,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
|
@ -33,6 +33,7 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
|||
import com.lagradost.cloudstream3.ui.player.IGenerator
|
||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||
|
@ -43,7 +44,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
|||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
||||
|
@ -52,9 +52,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
|||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.lang.Math.abs
|
||||
|
@ -615,7 +613,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let
|
||||
|
||||
// SET VISUAL KEYS
|
||||
AcraApplication.setKey(
|
||||
setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
parentId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
|
@ -629,7 +627,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
)
|
||||
|
||||
AcraApplication.setKey(
|
||||
setKey(
|
||||
DataStore.getFolderName(
|
||||
DOWNLOAD_EPISODE_CACHE,
|
||||
parentId.toString()
|
||||
|
@ -956,70 +954,155 @@ class ResultViewModel2 : ViewModel() {
|
|||
return LinkLoadingResult(sortUrls(links), sortSubs(subs))
|
||||
}
|
||||
|
||||
private fun playWithVlc(act: Activity?, data: LinkLoadingResult, id: Int) = ioSafe {
|
||||
if (act == null) return@ioSafe
|
||||
if (data.links.isEmpty()) {
|
||||
showToast(act, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||
return@ioSafe
|
||||
private fun launchActivity(
|
||||
activity: Activity?,
|
||||
resumeApp: ResultResume,
|
||||
id: Int? = null,
|
||||
work: suspend (Intent.(Activity) -> Unit)
|
||||
): Job? {
|
||||
val act = activity ?: return null
|
||||
return CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
resumeApp.launch(id) {
|
||||
work(act)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
main {
|
||||
if (t is ActivityNotFoundException) {
|
||||
showToast(activity, txt(R.string.app_not_found_error), Toast.LENGTH_LONG)
|
||||
} else {
|
||||
showToast(activity, t.toString(), Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!act.checkWrite()) {
|
||||
act.requestRW()
|
||||
if (act.checkWrite()) return@ioSafe
|
||||
}
|
||||
}
|
||||
|
||||
val outputDir = act.cacheDir
|
||||
val outputFile = withContext(Dispatchers.IO) {
|
||||
File.createTempFile("mirrorlist", ".m3u8", outputDir)
|
||||
private fun playInWebVideo(
|
||||
activity: Activity?,
|
||||
link: ExtractorLink,
|
||||
title: String?,
|
||||
posterUrl: String?,
|
||||
subtitles: List<SubtitleData>
|
||||
) = launchActivity(activity, WEB_VIDEO) {
|
||||
setDataAndType(Uri.parse(link.url), "video/*")
|
||||
|
||||
putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray())
|
||||
title?.let { putExtra("title", title) }
|
||||
posterUrl?.let { putExtra("poster", posterUrl) }
|
||||
val headers = Bundle().apply {
|
||||
if (link.referer.isNotBlank())
|
||||
putString("Referer", link.referer)
|
||||
putString("User-Agent", USER_AGENT)
|
||||
for ((key, value) in link.headers) {
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
putExtra("android.media.intent.extra.HTTP_HEADERS", headers)
|
||||
putExtra("secure_uri", true)
|
||||
}
|
||||
|
||||
private fun playWithMpv(
|
||||
activity: Activity?,
|
||||
id: Int,
|
||||
link: ExtractorLink,
|
||||
subtitles: List<SubtitleData>,
|
||||
resume: Boolean = true,
|
||||
) = launchActivity(activity, MPV, id) {
|
||||
putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray())
|
||||
putExtra("subs.name", subtitles.map { it.name }.toTypedArray())
|
||||
putExtra("subs.filename", subtitles.map { it.name }.toTypedArray())
|
||||
setDataAndType(Uri.parse(link.url), "video/*")
|
||||
component = MPV_COMPONENT
|
||||
putExtra("secure_uri", true)
|
||||
putExtra("return_result", true)
|
||||
val position = getViewPos(id)?.position
|
||||
if (resume && position != null)
|
||||
putExtra("position", position.toInt())
|
||||
}
|
||||
|
||||
// https://wiki.videolan.org/Android_Player_Intents/
|
||||
private fun playWithVlc(
|
||||
activity: Activity?,
|
||||
data: LinkLoadingResult,
|
||||
id: Int,
|
||||
resume: Boolean = true,
|
||||
// if it is only a single link then resume works correctly
|
||||
singleFile: Boolean? = null
|
||||
) = launchActivity(activity, VLC, id) { act ->
|
||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
||||
val outputDir = act.cacheDir
|
||||
|
||||
if (singleFile ?: (data.links.size == 1)) {
|
||||
setDataAndType(data.links.first().url.toUri(), "video/*")
|
||||
} else {
|
||||
val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
|
||||
|
||||
var text = "#EXTM3U"
|
||||
for (sub in data.subs) {
|
||||
text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\""
|
||||
}
|
||||
|
||||
// With subtitles it doesn't work for no reason :(
|
||||
// for (sub in data.subs) {
|
||||
// text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\""
|
||||
// }
|
||||
for (link in data.links) {
|
||||
text += "\n#EXTINF:, ${link.name}\n${link.url}"
|
||||
}
|
||||
outputFile.writeText(text)
|
||||
|
||||
val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
|
||||
|
||||
vlcIntent.setPackage(VLC_PACKAGE)
|
||||
vlcIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
vlcIntent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
|
||||
vlcIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
vlcIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
||||
vlcIntent.setDataAndType(
|
||||
setDataAndType(
|
||||
FileProvider.getUriForFile(
|
||||
act,
|
||||
act.applicationContext.packageName + ".provider",
|
||||
outputFile
|
||||
), "video/*"
|
||||
)
|
||||
|
||||
val startId = VLC_FROM_PROGRESS
|
||||
|
||||
var position = startId
|
||||
if (startId == VLC_FROM_START) {
|
||||
position = 1
|
||||
} else if (startId == VLC_FROM_PROGRESS) {
|
||||
position = 0
|
||||
}
|
||||
|
||||
vlcIntent.putExtra("position", position)
|
||||
|
||||
vlcIntent.component = VLC_COMPONENT
|
||||
act.setKey(VLC_LAST_ID_KEY, id)
|
||||
act.startActivityForResult(vlcIntent, VLC_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
showToast(act, e.toString(), Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
val position = if (resume) {
|
||||
getViewPos(id)?.position ?: 0L
|
||||
} else {
|
||||
1L
|
||||
}
|
||||
|
||||
component = VLC_COMPONENT
|
||||
|
||||
putExtra("from_start", !resume)
|
||||
putExtra("position", position)
|
||||
}
|
||||
|
||||
fun handleAction(activity: Activity?, click: EpisodeClickEvent) = viewModelScope.launchSafe {
|
||||
handleEpisodeClickEvent(activity, click)
|
||||
}
|
||||
|
||||
fun handleAction(activity: Activity?, click: EpisodeClickEvent) =
|
||||
viewModelScope.launchSafe {
|
||||
handleEpisodeClickEvent(activity, click)
|
||||
}
|
||||
|
||||
data class ExternalApp(
|
||||
val packageString: String,
|
||||
val name: Int,
|
||||
val action: Int,
|
||||
)
|
||||
|
||||
private val apps = listOf(
|
||||
ExternalApp(
|
||||
VLC_PACKAGE,
|
||||
R.string.player_settings_play_in_vlc,
|
||||
ACTION_PLAY_EPISODE_IN_VLC_PLAYER
|
||||
), ExternalApp(
|
||||
WEB_VIDEO_CAST_PACKAGE,
|
||||
R.string.player_settings_play_in_web,
|
||||
ACTION_PLAY_EPISODE_IN_WEB_VIDEO
|
||||
),
|
||||
ExternalApp(
|
||||
MPV_PACKAGE,
|
||||
R.string.player_settings_play_in_mpv,
|
||||
ACTION_PLAY_EPISODE_IN_MPV
|
||||
)
|
||||
)
|
||||
|
||||
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
|
||||
when (click.action) {
|
||||
|
@ -1035,9 +1118,17 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER)
|
||||
|
||||
if (activity?.isAppInstalled(VLC_PACKAGE) == true) {
|
||||
options.add(txt(R.string.episode_action_play_in_vlc) to ACTION_PLAY_EPISODE_IN_VLC_PLAYER)
|
||||
for (app in apps) {
|
||||
if (activity?.isAppInstalled(app.packageString) == true) {
|
||||
options.add(
|
||||
txt(
|
||||
R.string.episode_action_play_in_format,
|
||||
txt(app.name)
|
||||
) to app.action
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
options.addAll(
|
||||
listOf(
|
||||
txt(R.string.episode_action_play_in_browser) to ACTION_PLAY_EPISODE_IN_BROWSER,
|
||||
|
@ -1073,9 +1164,10 @@ class ResultViewModel2 : ViewModel() {
|
|||
click.copy(action = ACTION_CHROME_CAST_EPISODE)
|
||||
)
|
||||
} else {
|
||||
val action = getPlayerAction(ctx)
|
||||
handleEpisodeClickEvent(
|
||||
activity,
|
||||
click.copy(action = ACTION_PLAY_EPISODE_IN_PLAYER)
|
||||
click.copy(action = action)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1212,6 +1304,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
|
||||
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
|
||||
if (links.links.isEmpty()) {
|
||||
showToast(activity, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||
return@loadLinks
|
||||
}
|
||||
|
||||
playWithVlc(
|
||||
activity,
|
||||
links,
|
||||
|
@ -1219,6 +1316,37 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
}
|
||||
}
|
||||
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
|
||||
click.data,
|
||||
isCasting = true,
|
||||
txt(
|
||||
R.string.episode_action_play_in_format,
|
||||
txt(R.string.player_settings_play_in_web)
|
||||
)
|
||||
) { (result, index) ->
|
||||
playInWebVideo(
|
||||
activity,
|
||||
result.links[index],
|
||||
click.data.name ?: click.data.headerName,
|
||||
click.data.poster,
|
||||
result.subs
|
||||
)
|
||||
}
|
||||
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
|
||||
click.data,
|
||||
isCasting = true,
|
||||
txt(
|
||||
R.string.episode_action_play_in_format,
|
||||
txt(R.string.player_settings_play_in_mpv)
|
||||
)
|
||||
) { (result, index) ->
|
||||
playWithMpv(
|
||||
activity,
|
||||
click.data.id,
|
||||
result.links[index],
|
||||
result.subs
|
||||
)
|
||||
}
|
||||
ACTION_PLAY_EPISODE_IN_PLAYER -> {
|
||||
val data = currentResponse?.syncData?.toList() ?: emptyList()
|
||||
val list =
|
||||
|
@ -1284,7 +1412,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
}, {
|
||||
if (this !is AnimeLoadResponse) return@argamap
|
||||
val map =
|
||||
Kitsu.getEpisodesDetails(getMalId(), getAniListId(), isResponseRequired = false)
|
||||
Kitsu.getEpisodesDetails(
|
||||
getMalId(),
|
||||
getAniListId(),
|
||||
isResponseRequired = false
|
||||
)
|
||||
if (map.isNullOrEmpty()) return@argamap
|
||||
updateEpisodes = DubStatus.values().map { dubStatus ->
|
||||
val current =
|
||||
|
@ -1304,8 +1436,10 @@ class ResultViewModel2 : ViewModel() {
|
|||
val currentBack = this
|
||||
this.description = this.description ?: node.description?.en
|
||||
this.name = this.name ?: node.titles?.canonical
|
||||
this.episode = this.episode ?: node.num ?: episodeNumbers[index]
|
||||
this.posterUrl = this.posterUrl ?: node.thumbnail?.original?.url
|
||||
this.episode =
|
||||
this.episode ?: node.num ?: episodeNumbers[index]
|
||||
this.posterUrl =
|
||||
this.posterUrl ?: node.thumbnail?.original?.url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1592,7 +1726,9 @@ class ResultViewModel2 : ViewModel() {
|
|||
val idIndex = ep.key.id
|
||||
for ((index, i) in ep.value.withIndex()) {
|
||||
val episode = i.episode ?: (index + 1)
|
||||
val id = mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000) ?: 0)
|
||||
val id =
|
||||
mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000)
|
||||
?: 0)
|
||||
if (!existingEpisodes.contains(id)) {
|
||||
existingEpisodes.add(id)
|
||||
val seasonData = loadResponse.seasonNames.getSeason(i.season)
|
||||
|
@ -1888,7 +2024,10 @@ class ResultViewModel2 : ViewModel() {
|
|||
if (ep.getWatchProgress() > 0.9) continue
|
||||
handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep)
|
||||
EpisodeClickEvent(
|
||||
getPlayerAction(activity),
|
||||
ep
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
|
@ -1905,7 +2044,10 @@ class ResultViewModel2 : ViewModel() {
|
|||
?: return@launchSafe
|
||||
handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, episode)
|
||||
EpisodeClickEvent(
|
||||
getPlayerAction(activity),
|
||||
episode
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1983,7 +2125,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
preferStartEpisode = getResultEpisode(mainId)
|
||||
preferStartSeason = getResultSeason(mainId)
|
||||
|
||||
AcraApplication.setKey(
|
||||
setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
mainId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
|
|
|
@ -113,6 +113,22 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.player_pref_key)?.setOnPreferenceClickListener {
|
||||
val prefNames = resources.getStringArray(R.array.player_pref_names)
|
||||
val prefValues = resources.getIntArray(R.array.player_pref_values)
|
||||
val current = settingsManager.getInt(getString(R.string.player_pref_key), 1)
|
||||
|
||||
activity?.showBottomDialog(
|
||||
prefNames.toList(),
|
||||
prefValues.indexOf(current),
|
||||
getString(R.string.player_pref),
|
||||
true,
|
||||
{}) {
|
||||
settingsManager.edit().putInt(getString(R.string.player_pref_key), prefValues[it]).apply()
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.subtitle_settings_key)?.setOnPreferenceClickListener {
|
||||
SubtitlesFragment.push(activity, false)
|
||||
return@setOnPreferenceClickListener true
|
||||
|
|
|
@ -147,7 +147,7 @@ class PluginsFragment : Fragment() {
|
|||
pluginViewModel.updatePluginListLocal()
|
||||
tv_types_scroll_view?.isVisible = false
|
||||
} else {
|
||||
pluginViewModel.updatePluginList(url)
|
||||
pluginViewModel.updatePluginList(context, url)
|
||||
tv_types_scroll_view?.isVisible = true
|
||||
|
||||
// 💀💀💀💀💀💀💀 Recyclerview when
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.LiveData
|
||||
|
@ -13,6 +14,7 @@ import com.lagradost.cloudstream3.apmap
|
|||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.plugins.PluginData
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
import com.lagradost.cloudstream3.plugins.SitePlugin
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
|
@ -45,8 +47,8 @@ class PluginsViewModel : ViewModel() {
|
|||
private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf()
|
||||
const val TAG = "PLG"
|
||||
|
||||
private fun isDownloaded(plugin: Plugin, data: Set<String>? = null): Boolean {
|
||||
return (data ?: getDownloads()).contains(plugin.second.internalName)
|
||||
private fun isDownloaded(context: Context, pluginName: String, repositoryUrl: String): Boolean {
|
||||
return getPluginPath(context, pluginName, repositoryUrl).exists()
|
||||
}
|
||||
|
||||
private suspend fun getPlugins(
|
||||
|
@ -63,24 +65,15 @@ class PluginsViewModel : ViewModel() {
|
|||
?.also { repositoryCache[repositoryUrl] = it } ?: emptyList()
|
||||
}
|
||||
|
||||
private fun getStoredPlugins(): Array<PluginData> {
|
||||
return PluginManager.getPluginsOnline()
|
||||
}
|
||||
|
||||
private fun getDownloads(): Set<String> {
|
||||
return getStoredPlugins().map { it.internalName }.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewModel optional, updates the plugins livedata for that viewModel if included
|
||||
* */
|
||||
fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) =
|
||||
ioSafe {
|
||||
if (activity == null) return@ioSafe
|
||||
val stored = getDownloads()
|
||||
val plugins = getPlugins(repositoryUrl)
|
||||
|
||||
plugins.filter { plugin -> !isDownloaded(plugin, stored) }.also { list ->
|
||||
plugins.filter { plugin -> !isDownloaded(activity, plugin.second.internalName, repositoryUrl) }.also { list ->
|
||||
main {
|
||||
showToast(
|
||||
activity,
|
||||
|
@ -103,7 +96,7 @@ class PluginsViewModel : ViewModel() {
|
|||
PluginManager.downloadAndLoadPlugin(
|
||||
activity,
|
||||
metadata.url,
|
||||
metadata.name,
|
||||
metadata.internalName,
|
||||
repo
|
||||
)
|
||||
}.main { list ->
|
||||
|
@ -117,7 +110,7 @@ class PluginsViewModel : ViewModel() {
|
|||
),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
viewModel?.updatePluginListPrivate(repositoryUrl)
|
||||
viewModel?.updatePluginListPrivate(activity, repositoryUrl)
|
||||
} else if (list.isNotEmpty()) {
|
||||
showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT)
|
||||
}
|
||||
|
@ -140,11 +133,10 @@ class PluginsViewModel : ViewModel() {
|
|||
if (activity == null) return@ioSafe
|
||||
val (repo, metadata) = plugin
|
||||
|
||||
val (success, message) = if (isDownloaded(plugin) || isLocal) {
|
||||
PluginManager.deletePlugin(
|
||||
metadata.url,
|
||||
isLocal
|
||||
) to R.string.plugin_deleted
|
||||
val file = getPluginPath(activity, plugin.second.internalName, plugin.first)
|
||||
|
||||
val (success, message) = if (file.exists() || isLocal) {
|
||||
PluginManager.deletePlugin(file) to R.string.plugin_deleted
|
||||
} else {
|
||||
PluginManager.downloadAndLoadPlugin(
|
||||
activity,
|
||||
|
@ -165,14 +157,13 @@ class PluginsViewModel : ViewModel() {
|
|||
if (isLocal)
|
||||
updatePluginListLocal()
|
||||
else
|
||||
updatePluginListPrivate(repositoryUrl)
|
||||
updatePluginListPrivate(activity, repositoryUrl)
|
||||
}
|
||||
|
||||
private suspend fun updatePluginListPrivate(repositoryUrl: String) {
|
||||
val stored = getDownloads()
|
||||
private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) {
|
||||
val plugins = getPlugins(repositoryUrl)
|
||||
val list = plugins.map { plugin ->
|
||||
PluginViewData(plugin, isDownloaded(plugin, stored))
|
||||
PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first))
|
||||
}
|
||||
|
||||
this.plugins = list
|
||||
|
@ -211,9 +202,10 @@ class PluginsViewModel : ViewModel() {
|
|||
_filteredPlugins.postValue(false to plugins.filterTvTypes().filterLang().sortByQuery(currentQuery))
|
||||
}
|
||||
|
||||
fun updatePluginList(repositoryUrl: String) = viewModelScope.launchSafe {
|
||||
fun updatePluginList(context: Context?, repositoryUrl: String) = viewModelScope.launchSafe {
|
||||
if (context == null) return@launchSafe
|
||||
Log.i(TAG, "updatePluginList = $repositoryUrl")
|
||||
updatePluginListPrivate(repositoryUrl)
|
||||
updatePluginListPrivate(context, repositoryUrl)
|
||||
}
|
||||
|
||||
fun search(query: String?) {
|
||||
|
|
|
@ -135,7 +135,8 @@ class SubtitlesFragment : Fragment() {
|
|||
it.mkdir()
|
||||
}
|
||||
return fontDir.list()?.mapNotNull {
|
||||
if (it.endsWith(".ttf")) {
|
||||
// No idea which formats are supported, but these should be.
|
||||
if (it.endsWith(".ttf") || it.endsWith(".otf")) {
|
||||
File(fontDir.absolutePath + "/" + it)
|
||||
} else null
|
||||
} ?: listOf()
|
||||
|
|
|
@ -344,6 +344,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
VidSrcExtractor(),
|
||||
VidSrcExtractor2(),
|
||||
PlayLtXyz(),
|
||||
AStreamHub(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -52,6 +52,11 @@
|
|||
|
||||
android:id="@+id/home_select_none"
|
||||
style="@style/RoundedSelectableButtonIcon"/>-->
|
||||
|
||||
<!--
|
||||
If you reorder this fix getPairList() too!
|
||||
That shit is responsible for focus selection
|
||||
-->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/home_select_movies"
|
||||
|
||||
|
|
|
@ -274,7 +274,7 @@
|
|||
<string name="episode_action_chromecast_episode">حلقة كروم كاست</string>
|
||||
<string name="episode_action_chromecast_mirror">مرآة كروم كاست</string>
|
||||
<string name="episode_action_play_in_app">تشغيل في التطبيق</string>
|
||||
<string name="episode_action_play_in_vlc">VLC تشغيل في</string>
|
||||
<string name="episode_action_play_in_format">%s تشغيل في</string>
|
||||
<string name="episode_action_play_in_browser">تشغيل في الويب </string>
|
||||
<string name="episode_action_copy_link">نسخ الرابط</string>
|
||||
<string name="episode_action_auto_download">التحميل التلقائي</string>
|
||||
|
|
|
@ -279,7 +279,7 @@
|
|||
<string name="episode_action_chromecast_episode">Episódio pelo Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Alternativa pelo Chromecast</string>
|
||||
<string name="episode_action_play_in_app">Assistir no App</string>
|
||||
<string name="episode_action_play_in_vlc">Assistir no VLC</string>
|
||||
<string name="episode_action_play_in_format">Assistir no %s</string>
|
||||
<string name="episode_action_play_in_browser">Assistir no navegador</string>
|
||||
<string name="episode_action_copy_link">Copiar link</string>
|
||||
<string name="episode_action_auto_download">Auto download</string>
|
||||
|
|
|
@ -268,7 +268,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecastovat epizodu</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast jako zrcadlo</string>
|
||||
<string name="episode_action_play_in_app">Přehrát v aplikace</string>
|
||||
<string name="episode_action_play_in_vlc">Přehrát ve VLC</string>
|
||||
<string name="episode_action_play_in_format">Přehrát ve %s</string>
|
||||
<string name="episode_action_play_in_browser">Přehrát v prohlížeči</string>
|
||||
<string name="episode_action_copy_link">Zkopírovat odkaz</string>
|
||||
<string name="episode_action_auto_download">Automaticky stáhnout</string>
|
||||
|
|
|
@ -281,7 +281,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast-Episode</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecastmirror</string>
|
||||
<string name="episode_action_play_in_app">In App wiedergeben</string>
|
||||
<string name="episode_action_play_in_vlc">In VLC wiedergeben</string>
|
||||
<string name="episode_action_play_in_format">In %s wiedergeben</string>
|
||||
<string name="episode_action_play_in_browser">In Browser wiedergeben</string>
|
||||
<string name="episode_action_copy_link">Link kopieren</string>
|
||||
<string name="episode_action_auto_download">Auto Download</string>
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
<item>@string/episode_action_chromecast_episode</item>
|
||||
<item>@string/episode_action_chromecast_mirror</item>
|
||||
<item>@string/episode_action_play_in_app</item>
|
||||
<item>@string/episode_action_play_in_vlc</item>
|
||||
<item>@string/episode_action_play_in_format</item>
|
||||
<item>@string/episode_action_play_in_browser</item>
|
||||
<item>@string/episode_action_copy_link</item>
|
||||
<item>@string/episode_action_auto_download</item>
|
||||
|
|
|
@ -269,7 +269,7 @@
|
|||
<string name="episode_action_chromecast_episode">Episodio Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Espejo Chromecast</string>
|
||||
<string name="episode_action_play_in_app">Reproducir en la app</string>
|
||||
<string name="episode_action_play_in_vlc">Reproducir en VLC</string>
|
||||
<string name="episode_action_play_in_format">Reproducir en %s</string>
|
||||
<string name="episode_action_play_in_browser">Reproducir en el navegador</string>
|
||||
<string name="episode_action_copy_link">Copiar enlace</string>
|
||||
<string name="episode_action_auto_download">Descarga automática</string>
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
<string name="episode_action_chromecast_episode">Episode Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Miroir Chromecast</string>
|
||||
<string name="episode_action_play_in_app">Lecture dans l\'application</string>
|
||||
<string name="episode_action_play_in_vlc">Lecture dans VLC</string>
|
||||
<string name="episode_action_play_in_format">Lecture dans %s</string>
|
||||
<string name="episode_action_play_in_browser">Lecture dans le navigateur</string>
|
||||
<string name="episode_action_copy_link">Copier le lien</string>
|
||||
<string name="episode_action_auto_download">Téléchargement Automatique</string>
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
<string name="episode_action_chromecast_episode">क्रोमकास्ट एपिसोड</string>
|
||||
<string name="episode_action_chromecast_mirror">कक्रोमकास्ट मिरर</string>
|
||||
<string name="episode_action_play_in_app">एप्प मैं चलाये</string>
|
||||
<string name="episode_action_play_in_vlc">VLC में चलाए</string>
|
||||
<string name="episode_action_play_in_format">%s में चलाए</string>
|
||||
<string name="episode_action_play_in_browser">Browser में चलाए</string>
|
||||
<string name="episode_action_copy_link">लिंक कॉपी करें</string>
|
||||
<string name="episode_action_auto_download">डाउनलोड करे</string>
|
||||
|
|
|
@ -299,7 +299,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast epizoda</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
|
||||
<string name="episode_action_play_in_app">Pokreni u aplikaciji</string>
|
||||
<string name="episode_action_play_in_vlc">Pokreni u VLC-u</string>
|
||||
<string name="episode_action_play_in_format">Pokreni u %s</string>
|
||||
<string name="episode_action_play_in_browser">Pokreni u pregledniku</string>
|
||||
<string name="episode_action_copy_link">Kopiraj poveznicu</string>
|
||||
<string name="episode_action_auto_download">Automatsko preuzimanje</string>
|
||||
|
|
|
@ -264,7 +264,7 @@
|
|||
<string name="episode_action_chromecast_episode">Episode Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Mirror Chromecast</string>
|
||||
<string name="episode_action_play_in_app">Putar di aplikasi</string>
|
||||
<string name="episode_action_play_in_vlc">Putar di VLC</string>
|
||||
<string name="episode_action_play_in_format">Putar di %s</string>
|
||||
<string name="episode_action_play_in_browser">Putar di browser</string>
|
||||
<string name="episode_action_copy_link">Salin tautan</string>
|
||||
<string name="episode_action_auto_download">Download otomatis</string>
|
||||
|
|
|
@ -271,7 +271,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
|
||||
<string name="episode_action_play_in_app">Riproduci in app</string>
|
||||
<string name="episode_action_play_in_vlc">Riproduci in VLC</string>
|
||||
<string name="episode_action_play_in_format">Riproduci in %s</string>
|
||||
<string name="episode_action_play_in_browser">Riproduci nel browser</string>
|
||||
<string name="episode_action_copy_link">Copia link</string>
|
||||
<string name="episode_action_auto_download">Download</string>
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
<string name="episode_action_chromecast_episode">Епизода на Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Огледало на Chromecastr</string>
|
||||
<string name="episode_action_play_in_app">Пушти во апликацијата</string>
|
||||
<string name="episode_action_play_in_vlc">Пушти на VLC</string>
|
||||
<string name="episode_action_play_in_format">Пушти на %s</string>
|
||||
<string name="episode_action_play_in_browser">Пушти на прелистувач</string>
|
||||
<string name="episode_action_copy_link">Копирај линк</string>
|
||||
<string name="episode_action_auto_download">Авто превземање</string>
|
||||
|
|
|
@ -175,7 +175,7 @@
|
|||
<!-- <string name="episode_action_chomecast_episode">Chromecast Episode</string>
|
||||
<string name="episode_action_chomecast_mirror">Chromecast Mirror</string> -->
|
||||
<string name="episode_action_play_in_app">ആപ്പിൽ പ്ലേയ് ചെയ്യുക</string>
|
||||
<string name="episode_action_play_in_vlc">VLCയിൽ പ്ലേയ് ചെയ്യുക</string>
|
||||
<string name="episode_action_play_in_format">%sയിൽ പ്ലേയ് ചെയ്യുക</string>
|
||||
<string name="episode_action_play_in_browser">ബ്രൗസറിൽ പ്ലേയ് ചെയ്യുക</string>
|
||||
<string name="episode_action_copy_link">ലിങ്ക് പകർത്തുക</string>
|
||||
<string name="episode_action_auto_download">ഡൌൺലോഡ് ചെയ്യൂ</string>
|
||||
|
|
|
@ -145,7 +145,7 @@
|
|||
<string name="episode_action_chromecast_episode">aauugghhooo-ahah ohaaauugghh</string>
|
||||
<string name="episode_action_chromecast_mirror">aoohaaahhu ahouuhhh</string>
|
||||
<string name="episode_action_play_in_app">ooo-ahahaauuh aaahhu</string>
|
||||
<string name="episode_action_play_in_vlc">ooo-ahah ohaauuh</string>
|
||||
<string name="episode_action_play_in_format">ooo-ahah ohaauuh</string>
|
||||
<string name="episode_action_play_in_browser">ahoha ooo-ahahohoohah oooohh</string>
|
||||
<string name="episode_action_copy_link">aauugghhahhaauugghh</string>
|
||||
<string name="episode_action_auto_download">aaaghhoooohh aaahhu ahooo</string>
|
||||
|
|
|
@ -274,7 +274,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast aflevering</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
|
||||
<string name="episode_action_play_in_app">Speel in app</string>
|
||||
<string name="episode_action_play_in_vlc">Speel in VLC</string>
|
||||
<string name="episode_action_play_in_format">Speel in %s</string>
|
||||
<string name="episode_action_play_in_browser">Speel in browser</string>
|
||||
<string name="episode_action_copy_link">Kopieer link</string>
|
||||
<string name="episode_action_auto_download">Automatisch downloaden</string>
|
||||
|
|
|
@ -196,7 +196,7 @@
|
|||
<string name="episode_action_chromecast_episode">Støpt Episode</string>
|
||||
<string name="episode_action_chromecast_mirror">Støpt Speil</string>
|
||||
<string name="episode_action_play_in_app">Spill i appen</string>
|
||||
<string name="episode_action_play_in_vlc">Spill i VLC</string>
|
||||
<string name="episode_action_play_in_format">Spill i %s</string>
|
||||
<string name="episode_action_play_in_browser">Spill i nettleseren</string>
|
||||
<string name="episode_action_copy_link">Kopier link</string>
|
||||
<string name="episode_action_auto_download">Automatisk nedlasting</string>
|
||||
|
|
|
@ -165,7 +165,7 @@
|
|||
<item>@string/episode_action_chromecast_episode</item>
|
||||
<item>@string/episode_action_chromecast_mirror</item>
|
||||
<item>@string/episode_action_play_in_app</item>
|
||||
<item>@string/episode_action_play_in_vlc</item>
|
||||
<item>@string/episode_action_play_in_format</item>
|
||||
<item>@string/episode_action_play_in_browser</item>
|
||||
<item>@string/episode_action_copy_link</item>
|
||||
<item>@string/episode_action_auto_download</item>
|
||||
|
|
|
@ -252,7 +252,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast odcinka</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast mirroru</string>
|
||||
<string name="episode_action_play_in_app">Odtwórz w aplikacji</string>
|
||||
<string name="episode_action_play_in_vlc">Odtwórz w VLC</string>
|
||||
<string name="episode_action_play_in_format">Odtwórz w %s</string>
|
||||
<string name="episode_action_play_in_browser">Odtwórz w przeglądarce</string>
|
||||
<string name="episode_action_copy_link">Kopiuj link</string>
|
||||
<string name="episode_action_auto_download">Automatyczne pobieranie</string>
|
||||
|
|
|
@ -268,7 +268,7 @@
|
|||
<string name="episode_action_chromecast_episode">Episódio pelo Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Alternativa pelo Chromecast</string>
|
||||
<string name="episode_action_play_in_app">Reproduzir na app</string>
|
||||
<string name="episode_action_play_in_vlc">Reproduzir no VLC</string>
|
||||
<string name="episode_action_play_in_format">Reproduzir no %s</string>
|
||||
<string name="episode_action_play_in_browser">Reproduzir no navegador</string>
|
||||
<string name="episode_action_copy_link">Copiar link</string>
|
||||
<string name="episode_action_auto_download">Transferência Automática</string>
|
||||
|
|
|
@ -267,7 +267,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast alternativ</string>
|
||||
<string name="episode_action_play_in_app">Redă în Aplicație</string>
|
||||
<string name="episode_action_play_in_vlc">Redă în VLC</string>
|
||||
<string name="episode_action_play_in_format">Redă în %s</string>
|
||||
<string name="episode_action_play_in_browser">Redă în Browser</string>
|
||||
<string name="episode_action_copy_link">Copiază link-ul</string>
|
||||
<string name="episode_action_auto_download">Auto-descărcare</string>
|
||||
|
|
|
@ -167,7 +167,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecasta ett Avsnitt</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecasta en Länk</string>
|
||||
<string name="episode_action_play_in_app">Spela upp i appen</string>
|
||||
<string name="episode_action_play_in_vlc">Spela upp i VLC</string>
|
||||
<string name="episode_action_play_in_format">Spela upp i %s</string>
|
||||
<string name="episode_action_play_in_browser">Spela upp i webbläsaren</string>
|
||||
<string name="episode_action_copy_link">Kopiera länk</string>
|
||||
<string name="episode_action_auto_download">Automatisk nerladdning</string>
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast Episode</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast Mirror</string>
|
||||
<string name="episode_action_play_in_app">I-play sa App</string>
|
||||
<string name="episode_action_play_in_vlc">I-play sa VLC</string>
|
||||
<string name="episode_action_play_in_format">I-play sa %s</string>
|
||||
<string name="episode_action_play_in_browser">I-play sa browser</string>
|
||||
<string name="episode_action_copy_link">Kopyahin ang Link</string>
|
||||
<string name="episode_action_auto_download">Awtomatiking i-download</string>
|
||||
|
|
|
@ -14,6 +14,41 @@
|
|||
<item>@id/cast_button_type_forward_30_seconds</item>
|
||||
</array>
|
||||
|
||||
<array name="dns_pref">
|
||||
<item>@string/none</item>
|
||||
<item>Google</item>
|
||||
<item>Cloudflare</item>
|
||||
<!-- <item>OpenDns</item>-->
|
||||
<item>AdGuard</item>
|
||||
<item>DNS.WATCH</item>
|
||||
<item>Quad9</item>
|
||||
</array>
|
||||
<array name="dns_pref_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<!-- <item>3</item>-->
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
</array>
|
||||
|
||||
<array name="player_pref_names">
|
||||
<item>@string/player_settings_play_in_app</item>
|
||||
<item>@string/player_settings_play_in_vlc</item>
|
||||
<item>@string/player_settings_play_in_mpv</item>
|
||||
<item>@string/player_settings_play_in_web</item>
|
||||
<item>@string/player_settings_play_in_browser</item>
|
||||
</array>
|
||||
|
||||
<array name="player_pref_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
<item>4</item>
|
||||
<item>3</item>
|
||||
</array>
|
||||
|
||||
<array name="limit_title_rez_pref_names">
|
||||
<item>@string/resolution_and_title</item>
|
||||
<item>@string/title</item>
|
||||
|
@ -156,7 +191,7 @@
|
|||
<item>@string/episode_action_chromecast_episode</item>
|
||||
<item>@string/episode_action_chromecast_mirror</item>
|
||||
<item>@string/episode_action_play_in_app</item>
|
||||
<item>@string/episode_action_play_in_vlc</item>
|
||||
<item>@string/episode_action_play_in_format</item>
|
||||
<item>@string/episode_action_play_in_browser</item>
|
||||
<item>@string/episode_action_copy_link</item>
|
||||
<item>@string/episode_action_auto_download</item>
|
||||
|
@ -210,7 +245,7 @@
|
|||
<item>Parti</item>
|
||||
<item>Pembe</item>
|
||||
<item>Material You</item>
|
||||
<item>Material You (Secondary)</item>
|
||||
<item>Material You (İkincil)</item>
|
||||
</string-array>
|
||||
<string-array name="themes_overlay_names_values">
|
||||
<item>Normal</item>
|
||||
|
@ -233,7 +268,6 @@
|
|||
<item>Monet2</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="themes_names">
|
||||
<item>Koyu</item>
|
||||
<item>Gri</item>
|
||||
|
@ -249,6 +283,14 @@
|
|||
<item>Monet</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="extension_statuses">
|
||||
<item>Çöktü</item>
|
||||
<!-- "Ok" is usually capitalized as "OK". Ok android studio 🤓-->
|
||||
<item>Ok</item>
|
||||
<item>Yavaş</item>
|
||||
<item>Beta</item>
|
||||
</string-array>
|
||||
|
||||
<!--https://github.com/videolan/vlc-android/blob/72ccfb93db027b49855760001d1a930fa657c5a8/application/resources/src/main/res/values/arrays.xml#L266-->
|
||||
<string-array name="subtitles_encoding_list" tools:ignore="TypographyDashes">
|
||||
<item>@string/automatic</item>
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
<!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
|
||||
<resources>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
|
||||
<string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
|
||||
<string name="download_size_format" formatted="true" translatable="false">%s / %s</string>
|
||||
<string name="episode_name_format" formatted="true" translatable="false">%s %s</string>
|
||||
<string name="ffw_text_format" formatted="true" translatable="false">+%d</string>
|
||||
<string name="rew_text_format" formatted="true" translatable="false">-%d</string>
|
||||
<string name="ffw_text_regular_format" formatted="true" translatable="false">%d</string>
|
||||
<string name="rew_text_regular_format" formatted="true" translatable="false">%d</string>
|
||||
<string name="rating_format" formatted="true" translatable="false">%.1f/10.0</string>
|
||||
<string name="year_format" formatted="true" translatable="false">%d</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Cast: %s</string>
|
||||
<string name="next_episode_format" formatted="true">Bölüm %d şu tarihte yayınlanacak: </string>
|
||||
|
@ -14,7 +24,9 @@
|
|||
<string name="episode_poster_img_des">Episode Poster</string>
|
||||
<string name="home_main_poster_img_des">Main Poster</string>
|
||||
<string name="home_next_random_img_des">Next Random</string>
|
||||
<string name="episode_play_img_des" translatable="false">@string/play_episode</string>
|
||||
<string name="go_back_img_des">Go back</string>
|
||||
<string name="change_providers_img_des" translatable="false">@string/home_change_provider_img_des</string>
|
||||
<string name="home_change_provider_img_des">Change Provider</string>
|
||||
<string name="preview_background_img_des">Preview Background</string>
|
||||
|
||||
|
@ -38,6 +50,7 @@
|
|||
<string name="no_data">Veri yok</string>
|
||||
<string name="episode_more_options_des">Daha fazla seçenek</string>
|
||||
<string name="next_episode">Sonraki bölüm</string>
|
||||
<string name="result_plot" translatable="false">@string/synopsis</string>
|
||||
<string name="result_tags">Türler</string>
|
||||
<string name="result_share">Paylaş</string>
|
||||
<string name="result_open_in_browser">Tarayıcıda aç</string>
|
||||
|
@ -70,6 +83,7 @@
|
|||
<string name="download_failed">İndirme başarısız oldu</string>
|
||||
<string name="download_canceled">İndirme iptal edildi</string>
|
||||
<string name="download_done">İndirme bitti</string>
|
||||
<string name="download_format" translatable="false">%s - %s</string>
|
||||
<string name="stream">Yayınla</string>
|
||||
|
||||
<string name="error_loading_links_toast">Bağlantılar yüklenirken hata oluştu</string>
|
||||
|
@ -149,11 +163,15 @@
|
|||
<string name="chromecast_subtitles_settings_des">Chromecast alt yazı ayarları</string>
|
||||
|
||||
<string name="eigengraumode_settings">Eigengravy modu</string>
|
||||
<string name="eigengraumode_settings_des">Oynatıcıya bir hız seçeneği ekler</string>
|
||||
<string name="eigengraumode_settings_des">Oynatıcıya bir hız seçeneği ekle</string>
|
||||
<string name="swipe_to_seek_settings">Gözlemek için kaydır</string>
|
||||
<string name="swipe_to_seek_settings_des">Zamanı ayarlamak için sağa veya sola kaydır</string>
|
||||
<string name="swipe_to_change_settings">Ayarları değiştirmek için kaydır</string>
|
||||
<string name="swipe_to_change_settings_des">Sol ve sağ taraftan kaydırarak parlaklık ve sesi ayarla</string>
|
||||
|
||||
<string name="autoplay_next_settings">Sonraki bölümü otomatik oynat</string>
|
||||
<string name="autoplay_next_settings_des">Mevcut bölüm bittiğinde sonraki bölüme başla</string>
|
||||
|
||||
<string name="double_tap_to_seek_settings">Gözlemek için çift tıkla</string>
|
||||
<string name="double_tap_to_pause_settings">Durdurmak için çift tıkla</string>
|
||||
<string name="double_tap_to_seek_amount_settings">Oynatıcı gözleme miktarı</string>
|
||||
|
@ -177,16 +195,18 @@
|
|||
<string name="search">Ara</string>
|
||||
<string name="category_account">Hesaplar</string>
|
||||
<string name="category_updates">Güncellemeler ve yedek</string>
|
||||
|
||||
|
||||
<string name="settings_info">Bilgi</string>
|
||||
<string name="advanced_search">Gelişmiş arama</string>
|
||||
<string name="advanced_search_des">Sağlayıcılara göre ayrılmış arama sonuçlarını verir</string>
|
||||
<string name="advanced_search_des">Sağlayıcılara göre ayrılmış arama sonuçlarını ver</string>
|
||||
<string name="bug_report_settings_off">Yalnızca çökmelerle ilgili verileri gönderir</string>
|
||||
<string name="bug_report_settings_on">Hiç veri göndermez</string>
|
||||
<string name="show_fillers_settings">Anime için filler bölümleri gösterir</string>
|
||||
<string name="show_trailers_settings">Fragmanları göster</string>
|
||||
<string name="kitsu_settings">Kitsu\'dan posterleri göster</string>
|
||||
<string name="pref_filter_search_quality">Arama sonuçlarında seçilen video kalitelerini gizle</string>
|
||||
|
||||
<string name="automatic_plugin_updates">Otomatik eklenti güncellemeleri</string>
|
||||
<string name="updates_settings">Uygulama güncellemelerini göster</string>
|
||||
<string name="updates_settings_des">Başlangıçta yeni güncellemeleri otomatik olarak ara</string>
|
||||
<string name="uprereleases_settings">Ön sürümlere güncelle</string>
|
||||
|
@ -208,15 +228,19 @@
|
|||
<string name="acra_report_toast">Üzgünüz, uygulama çöktü. Geliştiricilere isimsiz bir hata raporu gönderilecek</string>
|
||||
|
||||
<string name="season">Sezon</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="no_season">Sezon yok</string>
|
||||
<string name="episode">Bölüm</string>
|
||||
<string name="episodes">Bölümler</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="season_short">S</string>
|
||||
<string name="episode_short">B</string>
|
||||
<string name="no_episodes_found">Bölüm bulunamadı</string>
|
||||
|
||||
<string name="delete_file">Dosyayı sil</string>
|
||||
<string name="delete">Sil</string>
|
||||
<string name="cancel" translatable="false">@string/sort_cancel</string>
|
||||
<string name="pause">Durdur</string>
|
||||
<string name="resume">Sürdür</string>
|
||||
<string name="go_back_30">-30</string>
|
||||
|
@ -251,6 +275,8 @@
|
|||
<string name="ova">OVA</string>
|
||||
<string name="asian_drama">Asya dramaları</string>
|
||||
<string name="livestreams">Canlı yayınlar</string>
|
||||
<string name="nsfw">NSFW</string>
|
||||
<string name="others">Diğerleri</string>
|
||||
|
||||
<!--singular-->
|
||||
<string name="movies_singular">Film</string>
|
||||
|
@ -262,6 +288,8 @@
|
|||
<string name="documentaries_singular">Belgesel</string>
|
||||
<string name="asian_drama_singular">Asya draması</string>
|
||||
<string name="live_singular">Canlı yayın</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="other_singular">Video</string>
|
||||
|
||||
<string name="source_error">Kaynak hatası</string>
|
||||
<string name="remote_error">Sunucu hatası</string>
|
||||
|
@ -272,7 +300,7 @@
|
|||
<string name="episode_action_chromecast_episode">Bölümü Chromecast ile yayınla</string>
|
||||
<string name="episode_action_chromecast_mirror">Bağlantıyı Chromecast ile yayınla</string>
|
||||
<string name="episode_action_play_in_app">Uygulamada oynat</string>
|
||||
<string name="episode_action_play_in_vlc">VLC\'de oynat</string>
|
||||
<string name="episode_action_play_in_format">%s\'de\/da oynat</string>
|
||||
<string name="episode_action_play_in_browser">Tarayıcıda oynat</string>
|
||||
<string name="episode_action_copy_link">Linki kopyala</string>
|
||||
<string name="episode_action_auto_download">Otomatik indir</string>
|
||||
|
@ -284,6 +312,10 @@
|
|||
<string name="show_dub">Dublaj etiketi</string>
|
||||
<string name="show_sub">Alt yazı etiketi</string>
|
||||
<string name="show_title">Başlık</string>
|
||||
<string name="show_hd_key" translatable="false">show_hd_key</string>
|
||||
<string name="show_dub_key" translatable="false">show_dub_key</string>
|
||||
<string name="show_sub_key" translatable="false">show_sub_key</string>
|
||||
<string name="show_title_key" translatable="false">show_title_key</string>
|
||||
<string name="poster_ui_settings">Poster üzerindeki öğeler</string>
|
||||
|
||||
<string name="no_update_found">Güncelleme bulunamadı</string>
|
||||
|
@ -327,6 +359,7 @@
|
|||
<string name="resize_zoom">Yakınlaştır</string>
|
||||
|
||||
<string name="legal_notice">Disclaimer</string>
|
||||
<string name="legal_notice_key" translatable="false">legal_notice_key</string>
|
||||
<string name="legal_notice_text" translatable="false">Any legal issues regarding the content on this application
|
||||
should be taken up with the actual file hosts and providers themselves as we are not affiliated with them.
|
||||
|
||||
|
@ -349,7 +382,9 @@
|
|||
<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>
|
||||
<string name="enable_nsfw_on_providers">Desteklenen sağlayıcılarda NSFW\'yi etkinleştir</string>
|
||||
<string name="subtitles_encoding">Alt yazı kodlaması</string>
|
||||
<string name="category_providers">Sağlayıcılar</string>
|
||||
<string name="category_ui">Düzen</string>
|
||||
|
||||
<string name="automatic">Otomatik</string>
|
||||
|
@ -364,6 +399,10 @@
|
|||
|
||||
|
||||
<!-- account stuff -->
|
||||
<string name="anilist_key" translatable="false">anilist_key</string>
|
||||
<string name="mal_key" translatable="false">mal_key</string>
|
||||
<string name="opensubtitles_key" translatable="false">opensubtitles_key</string>
|
||||
<string name="nginx_key" translatable="false">nginx_key</string>
|
||||
<string name="example_password">şifre123</string>
|
||||
<string name="example_username">HavalıKullanıcıAdı</string>
|
||||
<string name="example_email">hello@world.com</string>
|
||||
|
@ -403,6 +442,7 @@
|
|||
<string name="all">Hepsi</string>
|
||||
<string name="max">Maksimum</string>
|
||||
<string name="min">Minimum</string>
|
||||
<string name="subtitles_none" translatable="false">@string/none</string>
|
||||
<string name="subtitles_outline">Dış hat</string>
|
||||
<string name="subtitles_depressed">Çökmüş</string>
|
||||
<string name="subtitles_shadow">Gölge</string>
|
||||
|
@ -464,6 +504,7 @@
|
|||
<string name="error">Hata</string>
|
||||
<string name="subtitles_remove_captions">Alt yazılardan seçmeli alt yazıyı kaldır</string>
|
||||
<string name="subtitles_remove_bloat">Alt yazılardaki şişkinliği kaldır</string>
|
||||
<string name="subtitles_filter_lang">Tercih edilen medya diline göre filtrele</string>
|
||||
<string name="extras">Ekstralar</string>
|
||||
<string name="trailer">Fragman</string>
|
||||
<string name="network_adress_example">Yayına bağlan</string>
|
||||
|
@ -476,5 +517,61 @@
|
|||
<string name="crash_reporting_title">Çökme raporları</string>
|
||||
<string name="preferred_media_subtext">Ne izlemek istiyorsunuz?</string>
|
||||
<string name="setup_done">Bitti</string>
|
||||
<string name="extensions">Eklentiler</string>
|
||||
<string name="add_repository">Depo ekle</string>
|
||||
<string name="repository_name_hint">Depo ismi</string>
|
||||
<string name="repository_url_hint">Depo URL\'i</string>
|
||||
<string name="plugin_loaded">Eklenti yüklendi</string>
|
||||
<string name="plugin_deleted">Eklenti silindi</string>
|
||||
<string name="plugin_load_fail" formatted="true">%s yüklenemedi</string>
|
||||
<string name="is_adult">+18</string>
|
||||
<string name="batch_download_start_format" formatted="true">%d %s indirilmeye başlandı</string>
|
||||
<string name="batch_download_finish_format" formatted="true">%d %s başarıyla indirildi</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">%s\'nin tamamı zaten indirildi</string>
|
||||
<string name="batch_download">Toplu indir</string>
|
||||
<string name="plugin_singular">eklenti</string>
|
||||
<string name="plugin">eklentiler</string>
|
||||
<string name="delete_repository_plugins">Bu aynı zamanda tüm depo eklentilerini de siler</string>
|
||||
<string name="delete_repository">Depoyu sil</string>
|
||||
<string name="setup_extensions_subtext">Kullanmak istediğiniz sitelerin listesini indirin</string>
|
||||
<string name="plugins_downloaded" formatted="true">İndirilen: %d</string>
|
||||
<string name="plugins_disabled" formatted="true">Devre dışı: %d</string>
|
||||
<string name="plugins_not_downloaded" formatted="true">İndirilmeyen: %d</string>
|
||||
<string name="plugins_updated" formatted="true">%d eklenti(ler) güncellendi</string>
|
||||
<string name="blank_repo_message">Site eklentilerini yüklemek için bir depo ekleyin</string>
|
||||
<string name="view_public_repositories_button">Topluluk depolarını görüntüle</string>
|
||||
<string name="view_public_repositories_button_short">Herkese açık liste</string>
|
||||
<string name="uppercase_all_subtitles">Tüm alt yazılar büyük harf</string>
|
||||
|
||||
<string name="download_all_plugins_from_repo">Bu depodaki tüm eklentiler indirilsin mi?</string>
|
||||
<string name="single_plugin_disabled" formatted="true">%s devre dışı bırakıldı</string>
|
||||
<string name="tracks">Parçalar</string>
|
||||
<string name="audio_tracks">Ses parçaları</string>
|
||||
<string name="video_tracks">Video parçaları</string>
|
||||
<string name="apply_on_restart">Yeniden başlatmada uygula</string>
|
||||
|
||||
<string name="safe_mode_title">Güvenli mod etkin</string>
|
||||
<string name="safe_mode_description">Kurtarılamaz bir çökme meydana geldi ve soruna neden olan eklentiyi bulup kaldırabilmeniz için tüm eklentileri otomatik olarak devre dışı bıraktık.</string>
|
||||
<string name="safe_mode_crash_info">Çökme bilgisini göster</string>
|
||||
|
||||
<string name="extension_rating" formatted="true">Puan: %s</string>
|
||||
<string name="extension_description">Açıklama</string>
|
||||
<string name="extension_version">Versiyon</string>
|
||||
<string name="extension_status">Durum</string>
|
||||
<string name="extension_size">Boyut</string>
|
||||
<string name="extension_authors">Geliştiriciler</string>
|
||||
<string name="extension_types">Desteklenen</string>
|
||||
<string name="extension_language">Dil</string>
|
||||
<string name="extension_install_first">Önce eklentiyi yükleyin</string>
|
||||
|
||||
<string name="hls_playlist">HLS Oynatma Listesi</string>
|
||||
|
||||
<string name="player_pref">Tercih edilen video oynatıcısı</string>
|
||||
<string name="player_settings_play_in_app">Dahili oynatıcı</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
<string name="player_settings_play_in_web">Web Video Yayını</string>
|
||||
<string name="player_settings_play_in_browser">Tarayıcı</string>
|
||||
<string name="app_not_found_error">Uygulama bulunamadı</string>
|
||||
</resources>
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@
|
|||
<item>@string/episode_action_chromecast_episode</item>
|
||||
<item>@string/episode_action_chromecast_mirror</item>
|
||||
<item>@string/episode_action_play_in_app</item>
|
||||
<item>@string/episode_action_play_in_vlc</item>
|
||||
<item>@string/episode_action_play_in_format</item>
|
||||
<item>@string/episode_action_play_in_browser</item>
|
||||
<item>@string/episode_action_copy_link</item>
|
||||
<item>@string/episode_action_auto_download</item>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<string name="episode_action_chromecast_episode">Tập Chromecast</string>
|
||||
<string name="episode_action_chromecast_mirror">Chiếu Chromecast</string>
|
||||
<string name="episode_action_play_in_app">Xem với trình phát mặc định</string>
|
||||
<string name="episode_action_play_in_vlc">Xem với trình phát VLC</string>
|
||||
<string name="episode_action_play_in_format">Xem với trình phát %s</string>
|
||||
<string name="episode_action_play_in_browser">Xem tại trình duyệt</string>
|
||||
<string name="episode_action_copy_link">Sao chép liên kết</string>
|
||||
<string name="episode_action_auto_download">Tự động tải xuống</string>
|
||||
|
|
|
@ -303,7 +303,7 @@
|
|||
<string name="episode_action_chromecast_episode">投屏剧集</string>
|
||||
<string name="episode_action_chromecast_mirror">投屏镜像</string>
|
||||
<string name="episode_action_play_in_app">在应用中播放</string>
|
||||
<string name="episode_action_play_in_vlc">在 VLC 中播放</string>
|
||||
<string name="episode_action_play_in_format">在 %s 中播放</string>
|
||||
<string name="episode_action_play_in_browser">在浏览器中播放</string>
|
||||
<string name="episode_action_copy_link">复制链接</string>
|
||||
<string name="episode_action_auto_download">自动下载</string>
|
||||
|
|
|
@ -33,6 +33,22 @@
|
|||
<item>6</item>
|
||||
</array>
|
||||
|
||||
<array name="player_pref_names">
|
||||
<item>@string/player_settings_play_in_app</item>
|
||||
<item>@string/player_settings_play_in_vlc</item>
|
||||
<item>@string/player_settings_play_in_mpv</item>
|
||||
<item>@string/player_settings_play_in_web</item>
|
||||
<item>@string/player_settings_play_in_browser</item>
|
||||
</array>
|
||||
|
||||
<array name="player_pref_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
<item>4</item>
|
||||
<item>3</item>
|
||||
</array>
|
||||
|
||||
<array name="limit_title_rez_pref_names">
|
||||
<item>@string/resolution_and_title</item>
|
||||
<item>@string/title</item>
|
||||
|
@ -175,7 +191,7 @@
|
|||
<item>@string/episode_action_chromecast_episode</item>
|
||||
<item>@string/episode_action_chromecast_mirror</item>
|
||||
<item>@string/episode_action_play_in_app</item>
|
||||
<item>@string/episode_action_play_in_vlc</item>
|
||||
<item>@string/episode_action_play_in_format</item>
|
||||
<item>@string/episode_action_play_in_browser</item>
|
||||
<item>@string/episode_action_copy_link</item>
|
||||
<item>@string/episode_action_auto_download</item>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<string name="subtitle_settings_key" translatable="false">subtitle_settings_key</string>
|
||||
<string name="subtitle_settings_chromecast_key" translatable="false">subtitle_settings_chromecast_key</string>
|
||||
<string name="quality_pref_key" translatable="false">quality_pref_key</string>
|
||||
<string name="player_pref_key" translatable="false">player_pref_key</string>
|
||||
<string name="prefer_limit_title_key" translatable="false">prefer_limit_title_key</string>
|
||||
<string name="prefer_limit_title_rez_key" translatable="false">prefer_limit_title_rez_key</string>
|
||||
<string name="video_buffer_size_key" translatable="false">video_buffer_size_key</string>
|
||||
|
@ -255,7 +256,7 @@
|
|||
<string name="search">Search</string>
|
||||
<string name="category_account">Accounts</string>
|
||||
<string name="category_updates">Updates and backup</string>
|
||||
|
||||
|
||||
<string name="settings_info">Info</string>
|
||||
<string name="advanced_search">Advanced Search</string>
|
||||
<string name="advanced_search_des">Gives you the search results separated by provider</string>
|
||||
|
@ -363,7 +364,7 @@
|
|||
<string name="episode_action_chromecast_episode">Chromecast episode</string>
|
||||
<string name="episode_action_chromecast_mirror">Chromecast mirror</string>
|
||||
<string name="episode_action_play_in_app">Play in app</string>
|
||||
<string name="episode_action_play_in_vlc">Play in VLC</string>
|
||||
<string name="episode_action_play_in_format">Play in %s</string>
|
||||
<string name="episode_action_play_in_browser">Play in browser</string>
|
||||
<string name="episode_action_copy_link">Copy link</string>
|
||||
<string name="episode_action_auto_download">Auto download</string>
|
||||
|
@ -626,6 +627,14 @@
|
|||
<string name="extension_types">Supported</string>
|
||||
<string name="extension_language">Language</string>
|
||||
<string name="extension_install_first">Install the extension first</string>
|
||||
|
||||
|
||||
<string name="hls_playlist">HLS Playlist</string>
|
||||
|
||||
<string name="player_pref">Preferred video player</string>
|
||||
<string name="player_settings_play_in_app">Internal player</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
<string name="player_settings_play_in_web">Web Video Cast</string>
|
||||
<string name="player_settings_play_in_browser">Browser</string>
|
||||
<string name="app_not_found_error">App not found</string>
|
||||
</resources>
|
||||
|
|
|
@ -18,6 +18,11 @@
|
|||
android:title="@string/watch_quality_pref"
|
||||
android:icon="@drawable/ic_baseline_hd_24" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/player_pref_key"
|
||||
android:title="@string/player_pref"
|
||||
android:icon="@drawable/netflix_play" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/prefer_limit_title_key"
|
||||
android:title="@string/limit_title"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue