Merge remote-tracking branch 'origin/githubAccount' into githubAccount

This commit is contained in:
antonydp 2022-11-07 14:07:21 +01:00
commit 337f33d0c8
61 changed files with 918 additions and 397 deletions

View file

@ -47,8 +47,8 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 30 targetSdk = 30
versionCode = 52 versionCode = 55
versionName = "3.1.6" versionName = "3.2.3"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
@ -190,7 +190,7 @@ dependencies {
// Networking // Networking
// implementation("com.squareup.okhttp3:okhttp:4.9.2") // implementation("com.squareup.okhttp3:okhttp:4.9.2")
// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1") // implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
implementation("com.github.Blatzar:NiceHttp:0.3.3") implementation("com.github.Blatzar:NiceHttp:0.3.5")
// Util to skip the URI file fuckery 🙏 // Util to skip the URI file fuckery 🙏
implementation("com.github.tachiyomiorg:unifile:17bec43") implementation("com.github.tachiyomiorg:unifile:17bec43")

View file

@ -61,7 +61,9 @@ object CommonActivity {
} }
} }
fun showToast(act: Activity?, @StringRes message: Int, duration: Int) { /** duration is Toast.LENGTH_SHORT if null*/
@MainThread
fun showToast(act: Activity?, @StringRes message: Int, duration: Int? = null) {
if (act == null) return if (act == null) return
showToast(act, act.getString(message), duration) showToast(act, act.getString(message), duration)
} }
@ -69,6 +71,7 @@ object CommonActivity {
const val TAG = "COMPACT" const val TAG = "COMPACT"
/** duration is Toast.LENGTH_SHORT if null*/ /** duration is Toast.LENGTH_SHORT if null*/
@MainThread
fun showToast(act: Activity?, message: String?, duration: Int? = null) { fun showToast(act: Activity?, message: String?, duration: Int? = null) {
if (act == null || message == null) { if (act == null || message == null) {
Log.w(TAG, "invalid showToast act = $act message = $message") Log.w(TAG, "invalid showToast act = $act message = $message")
@ -337,6 +340,9 @@ object CommonActivity {
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> { KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> {
PlayerEventType.SkipOp PlayerEventType.SkipOp
} }
KeyEvent.KEYCODE_V, KeyEvent.KEYCODE_NUMPAD_5, KeyEvent.KEYCODE_5 -> {
PlayerEventType.SkipCurrentChapter
}
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
PlayerEventType.PlayPauseToggle PlayerEventType.PlayPauseToggle
} }

View file

@ -81,7 +81,8 @@ object APIHolder {
synchronized(allProviders) { synchronized(allProviders) {
initMap() initMap()
return apiMap?.get(apiName)?.let { apis.getOrNull(it) } return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
?: allProviders.firstOrNull { it.name == apiName } // Leave the ?. null check, it can crash regardless
?: allProviders.firstOrNull { it?.name == apiName }
} }
} }
@ -165,7 +166,8 @@ object APIHolder {
val hashSet = HashSet<String>() val hashSet = HashSet<String>()
val activeLangs = getApiProviderLangSettings() val activeLangs = getApiProviderLangSettings()
val hasUniversal = activeLangs.contains(AllLanguagesName) val hasUniversal = activeLangs.contains(AllLanguagesName)
hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }.map { it.name }) hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }
.map { it.name })
/*val set = settingsManager.getStringSet( /*val set = settingsManager.getStringSet(
this.getString(R.string.search_providers_list_key), this.getString(R.string.search_providers_list_key),
@ -241,7 +243,19 @@ object APIHolder {
} }
fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> { fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> {
val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }.map { it.ordinal } // We are getting the weirdest crash ever done:
// java.lang.ClassCastException: com.lagradost.cloudstream3.TvType cannot be cast to com.lagradost.cloudstream3.TvType
// Trying fixing using classloader fuckery
val oldLoader = Thread.currentThread().contextClassLoader
Thread.currentThread().contextClassLoader = TvType::class.java.classLoader
val default = TvType.values()
.sorted()
.filter { it != TvType.NSFW }
.map { it.ordinal }
Thread.currentThread().contextClassLoader = oldLoader
val defaultSet = default.map { it.toString() }.toSet() val defaultSet = default.map { it.toString() }.toSet()
val currentPrefMedia = try { val currentPrefMedia = try {
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)

View file

@ -92,6 +92,9 @@ import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.internal.applyConnectionSpec
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.nio.charset.Charset import java.nio.charset.Charset

View file

@ -2,10 +2,11 @@ package com.lagradost.cloudstream3.extractors
import android.util.Log import android.util.Log
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
class AStreamHub : ExtractorApi() { open class AStreamHub : ExtractorApi() {
override val name = "AStreamHub" override val name = "AStreamHub"
override val mainUrl = "https://astreamhub.com" override val mainUrl = "https://astreamhub.com"
override val requiresReferer = true override val requiresReferer = true

View file

@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.base64Decode
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
class Acefile : ExtractorApi() { open class Acefile : ExtractorApi() {
override val name = "Acefile" override val name = "Acefile"
override val mainUrl = "https://acefile.co" override val mainUrl = "https://acefile.co"
override val requiresReferer = false override val requiresReferer = false

View file

@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URI import java.net.URI
class AsianLoad : ExtractorApi() { open class AsianLoad : ExtractorApi() {
override var name = "AsianLoad" override var name = "AsianLoad"
override var mainUrl = "https://asianembed.io" override var mainUrl = "https://asianembed.io"
override val requiresReferer = true override val requiresReferer = true

View file

@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
class Blogger : ExtractorApi() { open class Blogger : ExtractorApi() {
override val name = "Blogger" override val name = "Blogger"
override val mainUrl = "https://www.blogger.com" override val mainUrl = "https://www.blogger.com"
override val requiresReferer = false override val requiresReferer = false

View file

@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
class BullStream : ExtractorApi() { open class BullStream : ExtractorApi() {
override val name = "BullStream" override val name = "BullStream"
override val mainUrl = "https://bullstream.xyz" override val mainUrl = "https://bullstream.xyz"
override val requiresReferer = false override val requiresReferer = false
@ -18,7 +18,7 @@ class BullStream : ExtractorApi() {
?: return null ?: return null
val m3u8 = "$mainUrl/m3u8/${data[1]}/${data[2]}/master.txt?s=1&cache=${data[4]}" val m3u8 = "$mainUrl/m3u8/${data[1]}/${data[2]}/master.txt?s=1&cache=${data[4]}"
println("shiv : $m3u8") //println("shiv : $m3u8")
return M3u8Helper.generateM3u8( return M3u8Helper.generateM3u8(
name, name,
m3u8, m3u8,

View file

@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import com.lagradost.cloudstream3.utils.httpsify import com.lagradost.cloudstream3.utils.httpsify
class Embedgram : ExtractorApi() { open class Embedgram : ExtractorApi() {
override val name = "Embedgram" override val name = "Embedgram"
override val mainUrl = "https://embedgram.com" override val mainUrl = "https://embedgram.com"
override val requiresReferer = true override val requiresReferer = true

View file

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
class Fastream: ExtractorApi() { open class Fastream: ExtractorApi() {
override var mainUrl = "https://fastream.to" override var mainUrl = "https://fastream.to"
override var name = "Fastream" override var name = "Fastream"
override val requiresReferer = false override val requiresReferer = false

View file

@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
class Filesim : ExtractorApi() { open class Filesim : ExtractorApi() {
override val name = "Filesim" override val name = "Filesim"
override val mainUrl = "https://files.im" override val mainUrl = "https://files.im"
override val requiresReferer = false override val requiresReferer = false

View file

@ -3,10 +3,9 @@ package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
class GMPlayer : ExtractorApi() { open class GMPlayer : ExtractorApi() {
override val name = "GM Player" override val name = "GM Player"
override val mainUrl = "https://gmplayer.xyz" override val mainUrl = "https://gmplayer.xyz"
override val requiresReferer = true override val requiresReferer = true

View file

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
class Linkbox : ExtractorApi() { open class Linkbox : ExtractorApi() {
override val name = "Linkbox" override val name = "Linkbox"
override val mainUrl = "https://www.linkbox.to" override val mainUrl = "https://www.linkbox.to"
override val requiresReferer = true override val requiresReferer = true

View file

@ -1,7 +0,0 @@
package com.lagradost.cloudstream3.extractors
open class Mcloud : WcoStream() {
override var name = "Mcloud"
override var mainUrl = "https://mcloud.to"
override val requiresReferer = true
}

View file

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getAndUnpack import com.lagradost.cloudstream3.utils.getAndUnpack
class Mp4Upload : ExtractorApi() { open class Mp4Upload : ExtractorApi() {
override var name = "Mp4Upload" override var name = "Mp4Upload"
override var mainUrl = "https://www.mp4upload.com" override var mainUrl = "https://www.mp4upload.com"
private val srcRegex = Regex("""player\.src\("(.*?)"""") private val srcRegex = Regex("""player\.src\("(.*?)"""")

View file

@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URI import java.net.URI
class MultiQuality : ExtractorApi() { open class MultiQuality : ExtractorApi() {
override var name = "MultiQuality" override var name = "MultiQuality"
override var mainUrl = "https://gogo-play.net" override var mainUrl = "https://gogo-play.net"
private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""") private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""")

View file

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
class Mvidoo : ExtractorApi() { open class Mvidoo : ExtractorApi() {
override val name = "Mvidoo" override val name = "Mvidoo"
override val mainUrl = "https://mvidoo.com" override val mainUrl = "https://mvidoo.com"
override val requiresReferer = true override val requiresReferer = true

View file

@ -14,7 +14,7 @@ import org.jsoup.Jsoup
* overrideMainUrl is necessary for for other vidstream clones like vidembed.cc * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc
* If they diverge it'd be better to make them separate. * If they diverge it'd be better to make them separate.
* */ * */
class Pelisplus(val mainUrl: String) { open class Pelisplus(val mainUrl: String) {
val name: String = "Vidstream" val name: String = "Vidstream"
private fun getExtractorUrl(id: String): String { private fun getExtractorUrl(id: String): String {

View file

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
class PlayLtXyz: ExtractorApi() { open class PlayLtXyz: ExtractorApi() {
override val name: String = "PlayLt" override val name: String = "PlayLt"
override val mainUrl: String = "https://play.playlt.xyz" override val mainUrl: String = "https://play.playlt.xyz"
override val requiresReferer = true override val requiresReferer = true

View file

@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
class Solidfiles : ExtractorApi() { open class Solidfiles : ExtractorApi() {
override val name = "Solidfiles" override val name = "Solidfiles"
override val mainUrl = "https://www.solidfiles.com" override val mainUrl = "https://www.solidfiles.com"
override val requiresReferer = false override val requiresReferer = false

View file

@ -5,7 +5,15 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
class StreamTape : ExtractorApi() { class StreamTapeNet : StreamTape() {
override var mainUrl = "https://streamtape.net"
}
class ShaveTape : StreamTape(){
override var mainUrl = "https://shavetape.cash"
}
open class StreamTape : ExtractorApi() {
override var name = "StreamTape" override var name = "StreamTape"
override var mainUrl = "https://streamtape.com" override var mainUrl = "https://streamtape.com"
override val requiresReferer = false override val requiresReferer = false
@ -16,7 +24,8 @@ class StreamTape : ExtractorApi() {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
with(app.get(url)) { with(app.get(url)) {
linkRegex.find(this.text)?.let { linkRegex.find(this.text)?.let {
val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" val extractedUrl =
"https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3)}"
return listOf( return listOf(
ExtractorLink( ExtractorLink(
name, name,

View file

@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.JsUnpacker
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import java.net.URI import java.net.URI
class Streamhub : ExtractorApi() { open class Streamhub : ExtractorApi() {
override var mainUrl = "https://streamhub.to" override var mainUrl = "https://streamhub.to"
override var name = "Streamhub" override var name = "Streamhub"
override val requiresReferer = false override val requiresReferer = false

View file

@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import java.net.URI import java.net.URI
class Streamplay : ExtractorApi() { open class Streamplay : ExtractorApi() {
override val name = "Streamplay" override val name = "Streamplay"
override val mainUrl = "https://streamplay.to" override val mainUrl = "https://streamplay.to"
override val requiresReferer = true override val requiresReferer = true

View file

@ -11,7 +11,7 @@ data class Files(
@JsonProperty("label") val label: String? = null, @JsonProperty("label") val label: String? = null,
) )
open class Supervideo : ExtractorApi() { open class Supervideo : ExtractorApi() {
override var name = "Supervideo" override var name = "Supervideo"
override var mainUrl = "https://supervideo.tv" override var mainUrl = "https://supervideo.tv"
override val requiresReferer = false override val requiresReferer = false
@ -20,10 +20,13 @@ data class Files(
val response = app.get(url).text val response = app.get(url).text
val jstounpack = Regex("eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value val jstounpack = Regex("eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value
val unpacjed = JsUnpacker(jstounpack).unpack() val unpacjed = JsUnpacker(jstounpack).unpack()
val extractedUrl = unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString().replace("file",""""file"""").replace("label",""""label"""").substringBeforeLast(",") val extractedUrl =
unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString()
.replace("file", """"file"""").replace("label", """"label"""")
.substringBeforeLast(",")
val parsedlinks = parseJson<List<Files>>(extractedUrl) val parsedlinks = parseJson<List<Files>>(extractedUrl)
parsedlinks.forEach { data -> parsedlinks.forEach { data ->
if (data.label.isNullOrBlank()){ // mp4 links (with labels) are slow. Use only m3u8 link. if (data.label.isNullOrBlank()) { // mp4 links (with labels) are slow. Use only m3u8 link.
M3u8Helper.generateM3u8( M3u8Helper.generateM3u8(
name, name,
data.id, data.id,
@ -34,8 +37,6 @@ data class Files(
} }
} }
} }
return extractedLinksList return extractedLinksList
} }
} }

View file

@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
class UpstreamExtractor : ExtractorApi() { open class UpstreamExtractor : ExtractorApi() {
override val name: String = "Upstream" override val name: String = "Upstream"
override val mainUrl: String = "https://upstream.to" override val mainUrl: String = "https://upstream.to"
override val requiresReferer = true override val requiresReferer = true

View file

@ -11,7 +11,7 @@ class VideovardSX : WcoStream() {
override var mainUrl = "https://videovard.sx" override var mainUrl = "https://videovard.sx"
} }
class VideoVard : ExtractorApi() { open class VideoVard : ExtractorApi() {
override var name = "Videovard" // Cause works for animekisa and wco override var name = "Videovard" // Cause works for animekisa and wco
override var mainUrl = "https://videovard.to" override var mainUrl = "https://videovard.to"
override val requiresReferer = false override val requiresReferer = false

View file

@ -53,6 +53,12 @@ class VizcloudSite : WcoStream() {
override var mainUrl = "https://vizcloud.site" override var mainUrl = "https://vizcloud.site"
} }
class Mcloud : WcoStream() {
override var name = "Mcloud"
override var mainUrl = "https://mcloud.to"
override val requiresReferer = true
}
open class WcoStream : ExtractorApi() { open class WcoStream : ExtractorApi() {
override var name = "VidStream" // Cause works for animekisa and wco override var name = "VidStream" // Cause works for animekisa and wco
override var mainUrl = "https://vidstream.pro" override var mainUrl = "https://vidstream.pro"

View file

@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
class YourUpload: ExtractorApi() { open class YourUpload: ExtractorApi() {
override val name = "Yourupload" override val name = "Yourupload"
override val mainUrl = "https://www.yourupload.com" override val mainUrl = "https://www.yourupload.com"
override val requiresReferer = false override val requiresReferer = false

View file

@ -46,7 +46,6 @@ open class YoutubeExtractor : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
println("TRYING TO ExTRACT: $url")
if (ytVideos[url].isNullOrEmpty()) { if (ytVideos[url].isNullOrEmpty()) {
val link = val link =
YoutubeStreamLinkHandlerFactory.getInstance().fromUrl( YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(

View file

@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
class Zorofile : ExtractorApi() { open class Zorofile : ExtractorApi() {
override val name = "Zorofile" override val name = "Zorofile"
override val mainUrl = "https://zorofile.com" override val mainUrl = "https://zorofile.com"
override val requiresReferer = true override val requiresReferer = true

View file

@ -12,7 +12,6 @@ import okhttp3.Headers.Companion.toHeaders
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.File import java.io.File
fun Requests.initClient(context: Context): OkHttpClient { fun Requests.initClient(context: Context): OkHttpClient {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0) val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0)

View file

@ -74,8 +74,10 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectSt
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
@ -555,7 +557,13 @@ class HomeFragment : Fragment() {
observe(homeViewModel.preview) { preview -> observe(homeViewModel.preview) { preview ->
// Always reset the padding, otherwise the will move lower and lower // Always reset the padding, otherwise the will move lower and lower
home_watch_holder?.setPadding(0, 0, 0, 0) // home_fix_padding?.setPadding(0, 0, 0, 0)
home_fix_padding?.let { v ->
val params = v.layoutParams
params.height = 0
v.layoutParams = params
}
when (preview) { when (preview) {
is Resource.Success -> { is Resource.Success -> {
home_preview?.isVisible = true home_preview?.isVisible = true
@ -576,7 +584,7 @@ class HomeFragment : Fragment() {
false false
) )
home_preview?.isVisible = false home_preview?.isVisible = false
context?.fixPaddingStatusbar(home_watch_holder) context?.fixPaddingStatusbarView(home_fix_padding)
} }
} }
} }
@ -592,6 +600,11 @@ class HomeFragment : Fragment() {
setPageTransformer(HomeScrollTransformer()) setPageTransformer(HomeScrollTransformer())
val callback: OnPageChangeCallback = object : OnPageChangeCallback() { val callback: OnPageChangeCallback = object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
// home_search?.isIconified = true
//home_search?.isVisible = true
//home_search?.clearFocus()
(home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply { (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply {
if (position >= itemCount - 1 && hasMoreItems) { if (position >= itemCount - 1 && hasMoreItems) {
hasMoreItems = false // dont make two requests hasMoreItems = false // dont make two requests

View file

@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
@ -103,6 +104,14 @@ abstract class AbstractPlayerFragment(
throw NotImplementedError() throw NotImplementedError()
} }
open fun onTimestamp(timestamp: EpisodeSkip.SkipStamp?) {
}
open fun onTimestampSkipped(timestamp: EpisodeSkip.SkipStamp) {
}
open fun exitedPipMode() { open fun exitedPipMode() {
throw NotImplementedError() throw NotImplementedError()
} }
@ -373,7 +382,9 @@ abstract class AbstractPlayerFragment(
), ),
subtitlesUpdates = ::subtitlesChanged, subtitlesUpdates = ::subtitlesChanged,
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched, embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
onTracksInfoChanged = ::onTracksInfoChanged onTracksInfoChanged = ::onTracksInfoChanged,
onTimestampInvoked = ::onTimestamp,
onTimestampSkipped = ::onTimestampSkipped
) )
if (player is CS3IPlayer) { if (player is CS3IPlayer) {

View file

@ -18,7 +18,10 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride import com.google.android.exoplayer2.trackselection.TrackSelectionOverride
import com.google.android.exoplayer2.trackselection.TrackSelector import com.google.android.exoplayer2.trackselection.TrackSelector
import com.google.android.exoplayer2.ui.SubtitleView import com.google.android.exoplayer2.ui.SubtitleView
import com.google.android.exoplayer2.upstream.* import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.HttpDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSource import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache import com.google.android.exoplayer2.upstream.cache.SimpleCache
@ -32,6 +35,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.ExtractorUri
@ -113,6 +117,8 @@ class CS3IPlayer : IPlayer {
private var playerUpdated: ((Any?) -> Unit)? = null private var playerUpdated: ((Any?) -> Unit)? = null
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
private var onTracksInfoChanged: (() -> Unit)? = null private var onTracksInfoChanged: (() -> Unit)? = null
private var onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)? = null
private var onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null
override fun releaseCallbacks() { override fun releaseCallbacks() {
playerUpdated = null playerUpdated = null
@ -126,7 +132,9 @@ class CS3IPlayer : IPlayer {
prevEpisode = null prevEpisode = null
subtitlesUpdates = null subtitlesUpdates = null
onTracksInfoChanged = null onTracksInfoChanged = null
onTimestampInvoked = null
requestSubtitleUpdate = null requestSubtitleUpdate = null
onTimestampSkipped = null
} }
override fun initCallbacks( override fun initCallbacks(
@ -142,6 +150,8 @@ class CS3IPlayer : IPlayer {
subtitlesUpdates: (() -> Unit)?, subtitlesUpdates: (() -> Unit)?,
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?, embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
onTracksInfoChanged: (() -> Unit)?, onTracksInfoChanged: (() -> Unit)?,
onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)?,
onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)?,
) { ) {
this.playerUpdated = playerUpdated this.playerUpdated = playerUpdated
this.updateIsPlaying = updateIsPlaying this.updateIsPlaying = updateIsPlaying
@ -155,6 +165,8 @@ class CS3IPlayer : IPlayer {
this.subtitlesUpdates = subtitlesUpdates this.subtitlesUpdates = subtitlesUpdates
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
this.onTracksInfoChanged = onTracksInfoChanged this.onTracksInfoChanged = onTracksInfoChanged
this.onTimestampInvoked = onTimestampInvoked
this.onTimestampSkipped = onTimestampSkipped
} }
// I know, this is not a perfect solution, however it works for fixing subs // I know, this is not a perfect solution, however it works for fixing subs
@ -719,7 +731,7 @@ class CS3IPlayer : IPlayer {
source source
} }
println("PLAYBACK POS $playbackPosition") //println("PLAYBACK POS $playbackPosition")
return exoPlayerBuilder.build().apply { return exoPlayerBuilder.build().apply {
setPlayWhenReady(playWhenReady) setPlayWhenReady(playWhenReady)
seekTo(currentWindow, playbackPosition) seekTo(currentWindow, playbackPosition)
@ -735,8 +747,22 @@ class CS3IPlayer : IPlayer {
} }
} }
fun updatedTime() { private fun getCurrentTimestamp(writePosition : Long? = null): EpisodeSkip.SkipStamp? {
val position = exoPlayer?.currentPosition val position = writePosition ?: this@CS3IPlayer.getPosition() ?: return null
for (lastTimeStamp in lastTimeStamps) {
if (lastTimeStamp.startMs <= position && position < lastTimeStamp.endMs) {
return lastTimeStamp
}
}
return null
}
fun updatedTime(writePosition : Long? = null) {
getCurrentTimestamp(writePosition)?.let { timestamp ->
onTimestampInvoked?.invoke(timestamp)
}
val position = writePosition ?: exoPlayer?.currentPosition
val duration = exoPlayer?.contentDuration val duration = exoPlayer?.contentDuration
if (duration != null && position != null) { if (duration != null && position != null) {
playerPositionChanged?.invoke(Pair(position, duration)) playerPositionChanged?.invoke(Pair(position, duration))
@ -748,12 +774,12 @@ class CS3IPlayer : IPlayer {
} }
override fun seekTo(time: Long) { override fun seekTo(time: Long) {
updatedTime() updatedTime(time)
exoPlayer?.seekTo(time) exoPlayer?.seekTo(time)
} }
private fun ExoPlayer.seekTime(time: Long) { private fun ExoPlayer.seekTime(time: Long) {
updatedTime() updatedTime(currentPosition + time)
seekTo(currentPosition + time) seekTo(currentPosition + time)
} }
@ -789,6 +815,17 @@ class CS3IPlayer : IPlayer {
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime) CSPlayerEvent.SeekBack -> seekTime(-seekActionTime)
CSPlayerEvent.NextEpisode -> nextEpisode?.invoke() CSPlayerEvent.NextEpisode -> nextEpisode?.invoke()
CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke() CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke()
CSPlayerEvent.SkipCurrentChapter -> {
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
getCurrentTimestamp()?.let { lastTimeStamp ->
if (lastTimeStamp.skipToNextEpisode) {
handleEvent(CSPlayerEvent.NextEpisode)
} else {
seekTo(lastTimeStamp.endMs + 1L)
}
onTimestampSkipped?.invoke(lastTimeStamp)
}
}
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -1007,6 +1044,24 @@ class CS3IPlayer : IPlayer {
} }
} }
private var lastTimeStamps: List<EpisodeSkip.SkipStamp> = emptyList()
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
lastTimeStamps = timeStamps
timeStamps.forEach { timestamp ->
exoPlayer?.createMessage { _, _ ->
updatedTime()
//if (payload is EpisodeSkip.SkipStamp) // this should always be true
// onTimestampInvoked?.invoke(payload)
}
?.setLooper(Looper.getMainLooper())
?.setPosition(timestamp.startMs)
//?.setPayload(timestamp)
?.setDeleteAfterDelivery(false)
?.send()
}
updatedTime()
}
fun onRenderFirst() { fun onRenderFirst() {
if (!hasUsedFirstRender) { // this insures that we only call this once per player load if (!hasUsedFirstRender) { // this insures that we only call this once per player load
Log.i(TAG, "Rendered first frame") Log.i(TAG, "Rendered first frame")

View file

@ -587,7 +587,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
updateLockUI() updateLockUI()
} }
private fun updateUIVisibility() { fun updateUIVisibility() {
val isGone = isLocked || !isShowing val isGone = isLocked || !isShowing
var togglePlayerTitleGone = isGone var togglePlayerTitleGone = isGone
context?.let { context?.let {
@ -1141,6 +1141,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
PlayerEventType.Play -> { PlayerEventType.Play -> {
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play)
} }
PlayerEventType.SkipCurrentChapter -> {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
}
PlayerEventType.Resize -> { PlayerEventType.Resize -> {
nextResize() nextResize()
} }
@ -1254,6 +1257,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
player.handleEvent(CSPlayerEvent.PlayPauseToggle) player.handleEvent(CSPlayerEvent.PlayPauseToggle)
} }
skip_chapter_button?.setOnClickListener {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
}
// init clicks // init clicks
player_resize_btt?.setOnClickListener { player_resize_btt?.setOnClickListener {
autoHide() autoHide()
@ -1306,12 +1313,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
showTracksDialogue() showTracksDialogue()
} }
player_intro_play?.setOnClickListener {
player_intro_play?.isGone = true
player.handleEvent(CSPlayerEvent.Play)
updateUIVisibility()
}
// it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar // it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar
player_holder?.setOnTouchListener { callView, event -> player_holder?.setOnTouchListener { callView, event ->
return@setOnTouchListener handleMotionEvent(callView, event) return@setOnTouchListener handleMotionEvent(callView, event)

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import android.animation.ValueAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
@ -13,6 +14,7 @@ import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.animation.addListener
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -36,8 +38,8 @@ import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSub
import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.SyncViewModel import com.lagradost.cloudstream3.ui.result.SyncViewModel
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1 import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -49,6 +51,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.dialog_online_subtitles.* import kotlinx.android.synthetic.main.dialog_online_subtitles.*
import kotlinx.android.synthetic.main.dialog_online_subtitles.apply_btt import kotlinx.android.synthetic.main.dialog_online_subtitles.apply_btt
import kotlinx.android.synthetic.main.dialog_online_subtitles.cancel_btt import kotlinx.android.synthetic.main.dialog_online_subtitles.cancel_btt
@ -58,7 +61,6 @@ 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_source_and_subs.subtitles_click_settings
import kotlinx.android.synthetic.main.player_select_tracks.* import kotlinx.android.synthetic.main.player_select_tracks.*
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
class GeneratorPlayer : FullScreenPlayer() { class GeneratorPlayer : FullScreenPlayer() {
companion object { companion object {
@ -67,8 +69,7 @@ class GeneratorPlayer : FullScreenPlayer() {
Log.i(TAG, "newInstance = $syncData") Log.i(TAG, "newInstance = $syncData")
lastUsedGenerator = generator lastUsedGenerator = generator
return Bundle().apply { return Bundle().apply {
if (syncData != null) if (syncData != null) putSerializable("syncData", syncData)
putSerializable("syncData", syncData)
} }
} }
@ -165,6 +166,8 @@ class GeneratorPlayer : FullScreenPlayer() {
isActive = true isActive = true
setPlayerDimen(null) setPlayerDimen(null)
setTitle() setTitle()
if (!sameEpisode)
hasRequestedStamps = false
loadExtractorJob(link.first) loadExtractorJob(link.first)
// load player // load player
@ -180,12 +183,13 @@ class GeneratorPlayer : FullScreenPlayer() {
}, },
currentSubs, currentSubs,
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle( (if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
currentSubs, currentSubs, settings = true, downloads = true
settings = true,
downloads = true
), ),
) )
} }
if (!sameEpisode)
player.addTimeStamps(listOf()) // clear stamps
} }
private fun sortLinks(useQualitySettings: Boolean = true): List<Pair<ExtractorLink?, ExtractorUri?>> { private fun sortLinks(useQualitySettings: Boolean = true): List<Pair<ExtractorLink?, ExtractorUri?>> {
@ -231,9 +235,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
override fun openOnlineSubPicker( override fun openOnlineSubPicker(
context: Context, context: Context, imdbId: Long?, dismissCallback: (() -> Unit)
imdbId: Long?,
dismissCallback: (() -> Unit)
) { ) {
val providers = subsProviders val providers = subsProviders
val isSingleProvider = subsProviders.size == 1 val isSingleProvider = subsProviders.size == 1
@ -256,8 +258,7 @@ class GeneratorPlayer : FullScreenPlayer() {
val arrayAdapter = val arrayAdapter =
object : ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>(dialog.context, layout) { object : ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>(dialog.context, layout) {
fun setHearingImpairedIcon( fun setHearingImpairedIcon(
imageViewEnd: ImageView?, imageViewEnd: ImageView?, position: Int
position: Int
) { ) {
if (imageViewEnd == null) return if (imageViewEnd == null) return
val isHearingImpaired = val isHearingImpaired =
@ -265,13 +266,11 @@ class GeneratorPlayer : FullScreenPlayer() {
val drawableEnd = if (isHearingImpaired) { val drawableEnd = if (isHearingImpaired) {
ContextCompat.getDrawable( ContextCompat.getDrawable(
context, context, R.drawable.ic_baseline_hearing_24
R.drawable.ic_baseline_hearing_24
)?.apply { )?.apply {
setTint( setTint(
ContextCompat.getColor( ContextCompat.getColor(
context, context, R.color.textColor
R.color.textColor
) )
) )
} }
@ -281,8 +280,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(context) val view = convertView ?: LayoutInflater.from(context).inflate(layout, null)
.inflate(layout, null)
val item = getItem(position) val item = getItem(position)
@ -337,13 +335,12 @@ class GeneratorPlayer : FullScreenPlayer() {
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
dialog.search_loading_bar?.show() dialog.search_loading_bar?.show()
ioSafe { ioSafe {
val search = AbstractSubtitleEntities.SubtitleSearch( val search =
query = query ?: return@ioSafe, AbstractSubtitleEntities.SubtitleSearch(query = query ?: return@ioSafe,
imdb = imdbId, imdb = imdbId,
epNumber = currentTempMeta.episode, epNumber = currentTempMeta.episode,
seasonNumber = currentTempMeta.season, seasonNumber = currentTempMeta.season,
lang = currentLanguageTwoLetters.ifBlank { null } lang = currentLanguageTwoLetters.ifBlank { null })
)
val results = providers.amap { val results = providers.amap {
try { try {
it.search(search) it.search(search)
@ -379,14 +376,12 @@ class GeneratorPlayer : FullScreenPlayer() {
dialog.search_filter.setOnClickListener { view -> dialog.search_filter.setOnClickListener { view ->
val lang639_1 = languages.map { it.ISO_639_1 } val lang639_1 = languages.map { it.ISO_639_1 }
activity?.showDialog( activity?.showDialog(languages.map { it.languageName },
languages.map { it.languageName },
lang639_1.indexOf(currentLanguageTwoLetters), lang639_1.indexOf(currentLanguageTwoLetters),
view?.context?.getString(R.string.subs_subtitle_languages) view?.context?.getString(R.string.subs_subtitle_languages)
?: return@setOnClickListener, ?: return@setOnClickListener,
true, true,
{ } { }) { index ->
) { index ->
currentLanguageTwoLetters = lang639_1[index] currentLanguageTwoLetters = lang639_1[index]
dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true) dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true)
} }
@ -472,8 +467,8 @@ class GeneratorPlayer : FullScreenPlayer() {
if (uri == null) return@normalSafeApiCall if (uri == null) return@normalSafeApiCall
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
// RW perms for the path // RW perms for the path
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or val flags =
Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
ctx.contentResolver.takePersistableUriPermission(uri, flags) ctx.contentResolver.takePersistableUriPermission(uri, flags)
@ -536,11 +531,9 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
if (subsProvidersIsActive) { if (subsProvidersIsActive) {
val loadFromOpenSubsFooter: TextView = val loadFromOpenSubsFooter: TextView = layoutInflater.inflate(
layoutInflater.inflate( R.layout.sort_bottom_footer_add_choice, null
R.layout.sort_bottom_footer_add_choice, ) as TextView
null
) as TextView
loadFromOpenSubsFooter.text = loadFromOpenSubsFooter.text =
ctx.getString(R.string.player_load_subtitles_online) ctx.getString(R.string.player_load_subtitles_online)
@ -592,8 +585,7 @@ class GeneratorPlayer : FullScreenPlayer() {
val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1 val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1
var subtitleIndex = subtitleIndexStart var subtitleIndex = subtitleIndexStart
val subsArrayAdapter = val subsArrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
subsArrayAdapter.add(ctx.getString(R.string.no_subtitles)) subsArrayAdapter.add(ctx.getString(R.string.no_subtitles))
subsArrayAdapter.addAll(currentSubtitles.map { it.name }) subsArrayAdapter.addAll(currentSubtitles.map { it.name })
@ -631,8 +623,7 @@ class GeneratorPlayer : FullScreenPlayer() {
val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values) val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values)
val value = settingsManager.getString( val value = settingsManager.getString(
ctx.getString(R.string.subtitles_encoding_key), ctx.getString(R.string.subtitles_encoding_key), null
null
) )
val index = prefValues.indexOf(value) val index = prefValues.indexOf(value)
text = prefNames[if (index == -1) 0 else index] text = prefNames[if (index == -1) 0 else index]
@ -644,28 +635,22 @@ class GeneratorPlayer : FullScreenPlayer() {
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list) val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values) val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values)
val currentPrefMedia = val currentPrefMedia = settingsManager.getString(
settingsManager.getString( ctx.getString(R.string.subtitles_encoding_key), null
ctx.getString(R.string.subtitles_encoding_key), )
null
)
shouldDismiss = false shouldDismiss = false
sourceDialog.dismissSafe(activity) sourceDialog.dismissSafe(activity)
val index = prefValues.indexOf(currentPrefMedia) val index = prefValues.indexOf(currentPrefMedia)
activity?.showDialog( activity?.showDialog(prefNames.toList(),
prefNames.toList(),
if (index == -1) 0 else index, if (index == -1) 0 else index,
ctx.getString(R.string.subtitles_encoding), ctx.getString(R.string.subtitles_encoding),
true, true,
{}) { {}) {
settingsManager.edit() settingsManager.edit().putString(
.putString( ctx.getString(R.string.subtitles_encoding_key), prefValues[it]
ctx.getString(R.string.subtitles_encoding_key), ).apply()
prefValues[it]
)
.apply()
updateForcedEncoding(ctx) updateForcedEncoding(ctx)
dismiss() dismiss()
@ -878,7 +863,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
var maxEpisodeSet: Int? = null var maxEpisodeSet: Int? = null
var hasRequestedStamps: Boolean = false
override fun playerPositionChanged(posDur: Pair<Long, Long>) { override fun playerPositionChanged(posDur: Pair<Long, Long>) {
// Don't save livestream data // Don't save livestream data
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
@ -887,11 +872,24 @@ class GeneratorPlayer : FullScreenPlayer() {
if ((currentMeta as? ResultEpisode)?.tvType == TvType.NSFW) return if ((currentMeta as? ResultEpisode)?.tvType == TvType.NSFW) return
val (position, duration) = posDur val (position, duration) = posDur
if (duration == 0L) return // idk how you achieved this, but div by zero crash if (duration <= 0L) return // idk how you achieved this, but div by zero crash
if (!hasRequestedStamps) {
hasRequestedStamps = true
val fetchStamps = context?.let { ctx ->
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
settingsManager.getBoolean(
ctx.getString(R.string.enable_skip_op_from_database),
true
)
} ?: true
if (fetchStamps)
viewModel.loadStamps(duration)
}
viewModel.getId()?.let { viewModel.getId()?.let {
DataStoreHelper.setViewPos(it, position, duration) DataStoreHelper.setViewPos(it, position, duration)
} }
val percentage = position * 100L / duration val percentage = position * 100L / duration
val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE
@ -939,17 +937,14 @@ class GeneratorPlayer : FullScreenPlayer() {
context?.let { ctx -> context?.let { ctx ->
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
if (settingsManager.getBoolean( if (settingsManager.getBoolean(
ctx.getString(R.string.episode_sync_enabled_key), ctx.getString(R.string.episode_sync_enabled_key), true
true
) )
) ) maxEpisodeSet = meta.episode
maxEpisodeSet = meta.episode
sync.modifyMaxEpisode(meta.episode) sync.modifyMaxEpisode(meta.episode)
} }
} }
if (meta.tvType.isAnimeOp()) if (meta.tvType.isAnimeOp()) isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
} }
} }
player_skip_op?.isVisible = isOpVisible player_skip_op?.isVisible = isOpVisible
@ -961,9 +956,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
private fun getAutoSelectSubtitle( private fun getAutoSelectSubtitle(
subtitles: Set<SubtitleData>, subtitles: Set<SubtitleData>, settings: Boolean, downloads: Boolean
settings: Boolean,
downloads: Boolean
): SubtitleData? { ): SubtitleData? {
val langCode = preferredAutoSelectSubtitles ?: return null val langCode = preferredAutoSelectSubtitles ?: return null
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
@ -1009,23 +1002,20 @@ class GeneratorPlayer : FullScreenPlayer() {
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play)
return true return true
} }
} else } else if (!langCode.isNullOrEmpty()) {
if (!langCode.isNullOrEmpty()) { getAutoSelectSubtitle(
getAutoSelectSubtitle( currentSubs, settings = true, downloads = false
currentSubs, )?.let { sub ->
settings = true,
downloads = false
)?.let { sub ->
if (setSubtitles(sub)) {
player.saveData()
player.reloadPlayer(ctx)
player.handleEvent(CSPlayerEvent.Play)
return true
}
if (setSubtitles(sub)) {
player.saveData()
player.reloadPlayer(ctx)
player.handleEvent(CSPlayerEvent.Play)
return true
} }
} }
}
} }
return false return false
} }
@ -1081,17 +1071,17 @@ class GeneratorPlayer : FullScreenPlayer() {
context?.let { ctx -> context?.let { ctx ->
//Generate video title //Generate video title
val playerVideoTitle = if (headerName != null) { val playerVideoTitle = if (headerName != null) {
(headerName + (headerName + if (tvType.isEpisodeBased() && episode != null) if (season == null) " - ${
if (tvType.isEpisodeBased() && episode != null) ctx.getString(
if (season == null) R.string.episode
" - ${ctx.getString(R.string.episode)} $episode" )
else } $episode"
" \"${ctx.getString(R.string.season_short)}${season}:${ else " \"${ctx.getString(R.string.season_short)}${season}:${
ctx.getString( ctx.getString(
R.string.episode_short R.string.episode_short
) )
}${episode}\"" }${episode}\""
else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName" else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName"
} else { } else {
"" ""
} }
@ -1131,8 +1121,7 @@ class GeneratorPlayer : FullScreenPlayer() {
"" ""
} }
val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL"
?: "NULL"
player_video_title_rez?.text = when (titleRez) { player_video_title_rez?.text = when (titleRez) {
0 -> "" 0 -> ""
@ -1155,14 +1144,11 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
container: ViewGroup?,
savedInstanceState: Bundle?
): View? { ): View? {
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason // this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
isTv = isTvSettings() isTv = isTvSettings()
layout = layout = if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
sync = ViewModelProvider(this)[SyncViewModel::class.java] sync = ViewModelProvider(this)[SyncViewModel::class.java]
@ -1174,6 +1160,68 @@ class GeneratorPlayer : FullScreenPlayer() {
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }
var timestampShowState = false
var skipAnimator: ValueAnimator? = null
var skipIndex = 0
private fun displayTimeStamp(show: Boolean) {
if (timestampShowState == show) return
skipIndex++
println("displayTimeStamp = $show")
timestampShowState = show
skip_chapter_button?.apply {
val showWidth = 170.toPx
val noShowWidth = 10.toPx
//if((show && width == showWidth) || (!show && width == noShowWidth)) {
// return
//}
val to = if (show) showWidth else noShowWidth
val from = if (!show) showWidth else noShowWidth
skipAnimator?.cancel()
isVisible = true
// just in case
val lay = layoutParams
lay.width = from
layoutParams = lay
skipAnimator = ValueAnimator.ofInt(
from, to
).apply {
addListener(onEnd = {
if (!show) skip_chapter_button?.isVisible = false
})
addUpdateListener { valueAnimator ->
val value = valueAnimator.animatedValue as Int
val layoutParams: ViewGroup.LayoutParams = layoutParams
layoutParams.width = value
setLayoutParams(layoutParams)
}
duration = 500
start()
}
}
}
override fun onTimestampSkipped(timestamp: EpisodeSkip.SkipStamp) {
displayTimeStamp(false)
}
override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp?) {
if (timestamp != null) {
skip_chapter_button.setText(timestamp.uiText)
displayTimeStamp(true)
val currentIndex = skipIndex
skip_chapter_button?.handler?.postDelayed({
if (skipIndex == currentIndex)
displayTimeStamp(false)
}, 6000)
} else {
displayTimeStamp(false)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
var langFilterList = listOf<String>() var langFilterList = listOf<String>()
@ -1189,8 +1237,7 @@ class GeneratorPlayer : FullScreenPlayer() {
settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false) settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false)
if (filterSubByLang) { if (filterSubByLang) {
val langFromPrefMedia = settingsManager.getStringSet( val langFromPrefMedia = settingsManager.getStringSet(
this.getString(R.string.provider_lang_key), this.getString(R.string.provider_lang_key), mutableSetOf("en")
mutableSetOf("en")
) )
langFilterList = langFromPrefMedia?.mapNotNull { langFilterList = langFromPrefMedia?.mapNotNull {
fromTwoLettersToLanguage(it)?.lowercase() ?: return@mapNotNull null fromTwoLettersToLanguage(it)?.lowercase() ?: return@mapNotNull null
@ -1203,7 +1250,7 @@ class GeneratorPlayer : FullScreenPlayer() {
sync.updateUserData() sync.updateUserData()
preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1() preferredAutoSelectSubtitles = getAutoSelectLanguageISO639_1()
if (currentSelectedLink == null) { if (currentSelectedLink == null) {
viewModel.loadLinks() viewModel.loadLinks()
@ -1218,6 +1265,10 @@ class GeneratorPlayer : FullScreenPlayer() {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
observe(viewModel.currentStamps) { stamps ->
player.addTimeStamps(stamps)
}
observe(viewModel.loadingLinks) { observe(viewModel.loadingLinks) {
when (it) { when (it) {
is Resource.Loading -> { is Resource.Loading -> {

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
import android.content.Context import android.content.Context
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.ExtractorUri
@ -12,9 +13,9 @@ enum class PlayerEventType(val value: Int) {
SeekForward(2), SeekForward(2),
SeekBack(3), SeekBack(3),
//SkipCurrentChapter(4), SkipCurrentChapter(4),
NextEpisode(5), NextEpisode(5),
PrevEpisode(5), PrevEpisode(6),
PlayPauseToggle(7), PlayPauseToggle(7),
ToggleMute(8), ToggleMute(8),
Lock(9), Lock(9),
@ -32,7 +33,7 @@ enum class CSPlayerEvent(val value: Int) {
SeekForward(2), SeekForward(2),
SeekBack(3), SeekBack(3),
//SkipCurrentChapter(4), SkipCurrentChapter(4),
NextEpisode(5), NextEpisode(5),
PrevEpisode(6), PrevEpisode(6),
PlayPauseToggle(7), PlayPauseToggle(7),
@ -54,7 +55,8 @@ interface Track {
**/ **/
val id: String? val id: String?
val label: String? val label: String?
// val isCurrentlyPlaying: Boolean
// val isCurrentlyPlaying: Boolean
val language: String? val language: String?
} }
@ -124,6 +126,8 @@ interface IPlayer {
subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles
onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes
onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)? = null, // Callback when timestamps appear, null when it should disappear
onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null, // callback for when a chapter is skipped, aka when event is handled (or for future use when skip automatically ads/sponsor)
) )
fun releaseCallbacks() fun releaseCallbacks()
@ -131,6 +135,8 @@ interface IPlayer {
fun updateSubtitleStyle(style: SaveCaptionStyle) fun updateSubtitleStyle(style: SaveCaptionStyle)
fun saveData() fun saveData()
fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>)
fun loadPlayer( fun loadPlayer(
context: Context, context: Context,
sameEpisode: Boolean, sameEpisode: Boolean,

View file

@ -9,10 +9,12 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.ExtractorUri
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class PlayerGeneratorViewModel : ViewModel() { class PlayerGeneratorViewModel : ViewModel() {
companion object { companion object {
@ -30,6 +32,9 @@ class PlayerGeneratorViewModel : ViewModel() {
private val _loadingLinks = MutableLiveData<Resource<Boolean?>>() private val _loadingLinks = MutableLiveData<Resource<Boolean?>>()
val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks
private val _currentStamps = MutableLiveData<List<EpisodeSkip.SkipStamp>>(emptyList())
val currentStamps: LiveData<List<EpisodeSkip.SkipStamp>> = _currentStamps
fun getId(): Int? { fun getId(): Int? {
return generator?.getCurrentId() return generator?.getCurrentId()
} }
@ -113,10 +118,31 @@ class PlayerGeneratorViewModel : ViewModel() {
} }
private var currentJob: Job? = null private var currentJob: Job? = null
private var currentStampJob: Job? = null
fun loadStamps(duration: Long) {
//currentStampJob?.cancel()
currentStampJob = ioSafe {
val meta = generator?.getCurrent()
val page = (generator as? RepoLinkGenerator?)?.page
if (page != null && meta is ResultEpisode) {
_currentStamps.postValue(listOf())
_currentStamps.postValue(
EpisodeSkip.getStamps(
page,
meta,
duration,
hasNextEpisode() ?: false
)
)
}
}
}
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) { fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) {
Log.i(TAG, "loadLinks") Log.i(TAG, "loadLinks")
currentJob?.cancel() currentJob?.cancel()
currentJob = viewModelScope.launchSafe { currentJob = viewModelScope.launchSafe {
val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>() val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>()
val currentSubs = mutableSetOf<SubtitleData>() val currentSubs = mutableSetOf<SubtitleData>()
@ -142,5 +168,6 @@ class PlayerGeneratorViewModel : ViewModel() {
_currentLinks.postValue(currentLinks) _currentLinks.postValue(currentLinks)
_currentSubs.postValue(currentSubs) _currentSubs.postValue(currentSubs)
} }
} }
} }

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
import android.util.Log import android.util.Log
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -11,7 +12,8 @@ import kotlin.math.min
class RepoLinkGenerator( class RepoLinkGenerator(
private val episodes: List<ResultEpisode>, private val episodes: List<ResultEpisode>,
private var currentIndex: Int = 0 private var currentIndex: Int = 0,
val page: LoadResponse? = null,
) : IGenerator { ) : IGenerator {
companion object { companion object {
const val TAG = "RepoLink" const val TAG = "RepoLink"

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Rect import android.graphics.Rect
@ -7,9 +8,11 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver import com.discord.panels.PanelsChildGestureRegionObserver
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed import com.lagradost.cloudstream3.utils.IOnBackPressed
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
@ -19,6 +22,7 @@ import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.* import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.trailer_custom_layout.* import kotlinx.android.synthetic.main.trailer_custom_layout.*
open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(), open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(),
PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed { PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed {
@ -60,14 +64,43 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
result_smallscreen_holder?.isVisible = !isFullScreenPlayer result_smallscreen_holder?.isVisible = !isFullScreenPlayer
result_fullscreen_holder?.isVisible = isFullScreenPlayer result_fullscreen_holder?.isVisible = isFullScreenPlayer
val to = sw * h / w
player_background?.apply { player_background?.apply {
isVisible = true isVisible = true
layoutParams = layoutParams =
FrameLayout.LayoutParams( FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
if (isFullScreenPlayer) FrameLayout.LayoutParams.MATCH_PARENT else sw * h / w if (isFullScreenPlayer) FrameLayout.LayoutParams.MATCH_PARENT else to
) )
} }
player_intro_play?.apply {
layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
result_top_holder?.measuredHeight ?: FrameLayout.LayoutParams.MATCH_PARENT
)
}
if (player_intro_play?.isGone == true) {
result_top_holder?.apply {
val anim = ValueAnimator.ofInt(
measuredHeight,
if (isFullScreenPlayer) ViewGroup.LayoutParams.MATCH_PARENT else to
)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams: ViewGroup.LayoutParams =
layoutParams
layoutParams.height = `val`
setLayoutParams(layoutParams)
}
anim.duration = 200
anim.start()
}
}
} }
} }
@ -79,7 +112,12 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
override fun showMirrorsDialogue() {} override fun showMirrorsDialogue() {}
override fun showTracksDialogue() {} override fun showTracksDialogue() {}
override fun openOnlineSubPicker(context: Context, imdbId: Long?, dismissCallback: () -> Unit) {} override fun openOnlineSubPicker(
context: Context,
imdbId: Long?,
dismissCallback: () -> Unit
) {
}
override fun subtitlesChanged() {} override fun subtitlesChanged() {}
@ -124,6 +162,13 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
} }
updateFullscreen(isFullScreenPlayer) updateFullscreen(isFullScreenPlayer)
uiReset() uiReset()
player_intro_play?.setOnClickListener {
player_intro_play?.isGone = true
player.handleEvent(CSPlayerEvent.Play)
updateUIVisibility()
fixPlayerSize()
}
} }
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {

View file

@ -416,7 +416,7 @@ class ResultViewModel2 : ViewModel() {
return this?.firstOrNull { it.season == season } return this?.firstOrNull { it.season == season }
} }
fun updateWatchStatus(currentResponse : LoadResponse, status: WatchType) { fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) {
val currentId = currentResponse.getId() val currentId = currentResponse.getId()
val resultPage = currentResponse val resultPage = currentResponse
@ -793,7 +793,7 @@ class ResultViewModel2 : ViewModel() {
fun updateWatchStatus(status: WatchType) { fun updateWatchStatus(status: WatchType) {
updateWatchStatus(currentResponse ?: return,status) updateWatchStatus(currentResponse ?: return, status)
_watchStatus.postValue(status) _watchStatus.postValue(status)
} }
@ -1681,10 +1681,10 @@ class ResultViewModel2 : ViewModel() {
preferDubStatus = indexer.dubStatus preferDubStatus = indexer.dubStatus
generator = if (isMovie) { generator = if (isMovie) {
getMovie()?.let { RepoLinkGenerator(listOf(it)) } getMovie()?.let { RepoLinkGenerator(listOf(it), page = currentResponse) }
} else { } else {
episodes?.let { list -> episodes?.let { list ->
RepoLinkGenerator(list) RepoLinkGenerator(list, page = currentResponse)
} }
} }

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.ui.search package com.lagradost.cloudstream3.ui.search
import android.content.DialogInterface
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -10,6 +11,7 @@ import android.widget.AbsListView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.ListView import android.widget.ListView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -28,6 +30,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.APIHolder.getApiSettings import com.lagradost.cloudstream3.APIHolder.getApiSettings
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
@ -128,9 +131,9 @@ class SearchFragment : Fragment() {
context?.let { ctx -> context?.let { ctx ->
val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW } val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }
.map { it.ordinal.toString() }.toSet() .map { it.ordinal.toString() }.toSet()
val preferredTypes = PreferenceManager.getDefaultSharedPreferences(ctx) val preferredTypes = (PreferenceManager.getDefaultSharedPreferences(ctx)
.getStringSet(this.getString(R.string.prefer_media_type_key), default) .getStringSet(this.getString(R.string.prefer_media_type_key), default)?.ifEmpty { default } ?: default)
?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } ?: default .mapNotNull { it.toIntOrNull() ?: return@mapNotNull null }
val settings = ctx.getApiSettings() val settings = ctx.getApiSettings()
@ -343,7 +346,7 @@ class SearchFragment : Fragment() {
searchViewModel.updateHistory() searchViewModel.updateHistory()
} }
search_history_recycler?.isVisible = showHistory search_history_holder?.isVisible = showHistory
search_master_recycler?.isVisible = !showHistory && isAdvancedSearch search_master_recycler?.isVisible = !showHistory && isAdvancedSearch
search_autofit_results?.isVisible = !showHistory && !isAdvancedSearch search_autofit_results?.isVisible = !showHistory && !isAdvancedSearch
@ -352,7 +355,41 @@ class SearchFragment : Fragment() {
} }
}) })
search_clear_call_history?.setOnClickListener {
activity?.let { ctx ->
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
val dialogClickListener =
DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
removeKeys(SEARCH_HISTORY_KEY)
searchViewModel.updateHistory()
}
DialogInterface.BUTTON_NEGATIVE -> {
}
}
}
try {
builder.setTitle(R.string.clear_history).setMessage(
ctx.getString(R.string.delete_message).format(
ctx.getString(R.string.history)
)
)
.setPositiveButton(R.string.sort_clear, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
.show()
} catch (e: Exception) {
logError(e)
// ye you somehow fucked up formatting did you?
}
}
}
observe(searchViewModel.currentHistory) { list -> observe(searchViewModel.currentHistory) { list ->
search_clear_call_history?.isVisible = list.isNotEmpty()
(search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list) (search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list)
} }

View file

@ -4,12 +4,14 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.TransactionTooLargeException
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
@ -83,12 +85,17 @@ class SettingsUpdates : PreferenceFragmentCompat() {
dialog.text1?.text = text dialog.text1?.text = text
dialog.copy_btt?.setOnClickListener { dialog.copy_btt?.setOnClickListener {
val serviceClipboard = // Can crash on too much text
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?) try {
?: return@setOnClickListener val serviceClipboard =
val clip = ClipData.newPlainText("logcat", text) (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?)
serviceClipboard.setPrimaryClip(clip) ?: return@setOnClickListener
dialog.dismissSafe(activity) val clip = ClipData.newPlainText("logcat", text)
serviceClipboard.setPrimaryClip(clip)
dialog.dismissSafe(activity)
} catch (e: TransactionTooLargeException) {
showToast(activity, R.string.clipboard_too_large)
}
} }
dialog.clear_btt?.setOnClickListener { dialog.clear_btt?.setOnClickListener {
Runtime.getRuntime().exec("logcat -c") Runtime.getRuntime().exec("logcat -c")

View file

@ -200,7 +200,9 @@ class ExtensionsFragment : Fragment() {
val url = dialog.repo_url_input?.text?.toString() val url = dialog.repo_url_input?.text?.toString()
?.let { it1 -> RepositoryManager.parseRepoUrl(it1) } ?.let { it1 -> RepositoryManager.parseRepoUrl(it1) }
if (url.isNullOrBlank()) { if (url.isNullOrBlank()) {
showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT) main {
showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT)
}
} else { } else {
val fixedName = if (!name.isNullOrBlank()) name val fixedName = if (!name.isNullOrBlank()) name
else RepositoryManager.parseRepository(url)?.name ?: "No name" else RepositoryManager.parseRepository(url)?.name ?: "No name"

View file

@ -0,0 +1,140 @@
package com.lagradost.cloudstream3.utils
import android.util.Log
import androidx.annotation.StringRes
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.txt
import java.lang.Long.min
object EpisodeSkip {
private const val TAG = "EpisodeSkip"
enum class SkipType(@StringRes name: Int) {
Opening(R.string.skip_type_op),
Ending(R.string.skip_type_ed),
Recap(R.string.skip_type_recap),
MixedOpening(R.string.skip_type_mixed_op),
MixedEnding(R.string.skip_type_mixed_ed),
Credits(R.string.skip_type_creddits),
Intro(R.string.skip_type_creddits),
}
data class SkipStamp(
val type: SkipType,
val skipToNextEpisode: Boolean,
val startMs: Long,
val endMs: Long,
) {
val uiText = if (skipToNextEpisode) txt(R.string.next_episode) else txt(
R.string.skip_type_format,
txt(type.name)
)
}
private val cachedStamps = HashMap<Int, List<SkipStamp>>()
private fun shouldSkipToNextEpisode(endMs: Long, episodeDurationMs: Long): Boolean {
return episodeDurationMs - endMs < 20_000L // some might have outro that we don't care about tbh
}
suspend fun getStamps(
data: LoadResponse,
episode: ResultEpisode,
episodeDurationMs: Long,
hasNextEpisode: Boolean,
): List<SkipStamp> {
cachedStamps[episode.id]?.let { list ->
return list
}
val out = mutableListOf<SkipStamp>()
Log.i(TAG, "Requesting SkipStamp from ${data.syncData}")
if (data is AnimeLoadResponse && (data.type == TvType.Anime || data.type == TvType.OVA)) {
data.getMalId()?.toIntOrNull()?.let { malId ->
val (resultLength, stamps) = AniSkip.getResult(
malId,
episode.episode,
episodeDurationMs
) ?: return@let null
// because it also returns an expected episode length we use that just in case it is mismatched with like 2s next episode will still work
val dur = min(episodeDurationMs, resultLength)
stamps.mapNotNull { stamp ->
val skipType = when (stamp.skipType) {
"op" -> SkipType.Opening
"ed" -> SkipType.Ending
"recap" -> SkipType.Recap
"mixed-ed" -> SkipType.MixedEnding
"mixed-op" -> SkipType.MixedOpening
else -> null
} ?: return@mapNotNull null
val end = (stamp.interval.endTime * 1000.0).toLong()
val start = (stamp.interval.startTime * 1000.0).toLong()
SkipStamp(
type = skipType,
skipToNextEpisode = hasNextEpisode && shouldSkipToNextEpisode(
end,
dur
),
startMs = start,
endMs = end
)
}?.let { list ->
out.addAll(list)
}
}
}
if (out.isNotEmpty())
cachedStamps[episode.id] = out
return out
}
}
// taken from https://github.com/saikou-app/saikou/blob/3803f8a7a59b826ca193664d46af3a22bbc989f7/app/src/main/java/ani/saikou/others/AniSkip.kt
// the following is GPLv3 code https://github.com/saikou-app/saikou/blob/main/LICENSE.md
object AniSkip {
private const val TAG = "AniSkip"
suspend fun getResult(
malId: Int,
episodeNumber: Int,
episodeLength: Long
): Pair<Long, List<Stamp>>? {
return try {
val url =
"https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=${episodeLength / 1000L}"
Log.i(TAG, "Requesting $url")
val a = app.get(url)
val res = a.parsed<AniSkipResponse>()
Log.i(TAG, "Found ${res.found} with ${res.results?.size} results")
if (res.found && !res.results.isNullOrEmpty()) (res.results[0].episodeLength * 1000).toLong() to res.results else null
} catch (t: Throwable) {
Log.i(TAG, "error = ${t.message}")
logError(t)
null
}
}
data class AniSkipResponse(
@JsonSerialize val found: Boolean,
@JsonSerialize val results: List<Stamp>?,
@JsonSerialize val message: String?,
@JsonSerialize val statusCode: Int
)
data class Stamp(
@JsonSerialize val interval: AniSkipInterval,
@JsonSerialize val skipType: String,
@JsonSerialize val skipId: String,
@JsonSerialize val episodeLength: Double
)
data class AniSkipInterval(
@JsonSerialize val startTime: Double,
@JsonSerialize val endTime: Double
)
}

View file

@ -205,6 +205,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
VideovardSX(), VideovardSX(),
Mp4Upload(), Mp4Upload(),
StreamTape(), StreamTape(),
StreamTapeNet(),
ShaveTape(),
//mixdrop extractors //mixdrop extractors
MixDropBz(), MixDropBz(),

View file

@ -328,7 +328,9 @@ object UIHelper {
) )
} }
fun Context.fixPaddingStatusbarView(v: View) { fun Context.fixPaddingStatusbarView(v: View?) {
if (v == null) return
val params = v.layoutParams val params = v.layoutParams
params.height = getStatusBarHeight() params.height = getStatusBarHeight()
v.layoutParams = params v.layoutParams = params

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<vector android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48"
android:tint="?attr/white"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M30,36.5V33.5H37.25V36.5ZM30,18.1V15.1H44V18.1ZM30,27.3V24.3H41.75V27.3ZM6.25,14.25H4V11.25H12.5V9H19.25V11.25H27.75V14.25H25.5V35Q25.5,36.2 24.6,37.1Q23.7,38 22.5,38H9.25Q8.05,38 7.15,37.1Q6.25,36.2 6.25,35ZM9.25,14.25V35Q9.25,35 9.25,35Q9.25,35 9.25,35H22.5Q22.5,35 22.5,35Q22.5,35 22.5,35V14.25ZM9.25,14.25V35Q9.25,35 9.25,35Q9.25,35 9.25,35Q9.25,35 9.25,35Q9.25,35 9.25,35Z"/>
</vector>

View file

@ -405,7 +405,11 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<View
android:id="@+id/home_fix_padding"
android:layout_width="match_parent"
android:layout_height="0dp">
</View>
<!-- <!--
All padding in home_watch_holder is determined in runtime All padding in home_watch_holder is determined in runtime
This is because the home poster can be invisible which forces This is because the home poster can be invisible which forces

View file

@ -1,130 +1,130 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:id="@+id/player_background"
android:layout_height="match_parent" android:layout_width="match_parent"
android:orientation="horizontal" android:layout_height="match_parent"
android:id="@+id/player_background" android:background="@android:color/black"
app:backgroundTint="@android:color/black" android:orientation="horizontal"
android:background="@android:color/black" android:screenOrientation="sensorLandscape"
android:screenOrientation="sensorLandscape" app:backgroundTint="@android:color/black"
app:surface_type="texture_view"> app:surface_type="texture_view">
<!-- <!--
app:fastforward_increment="10000" app:fastforward_increment="10000"
app:rewind_increment="10000"--> app:rewind_increment="10000"-->
<com.google.android.exoplayer2.ui.PlayerView <com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view" android:id="@+id/player_view"
app:show_timeout="0" android:layout_width="match_parent"
app:hide_on_touch="false" android:layout_height="match_parent"
app:auto_show="true" android:background="@android:color/black"
android:layout_width="match_parent" app:auto_show="true"
android:layout_height="match_parent" app:backgroundTint="@android:color/black"
app:backgroundTint="@android:color/black" app:controller_layout_id="@layout/player_custom_layout"
android:background="@android:color/black" app:hide_on_touch="false"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:controller_layout_id="@layout/player_custom_layout" /> app:show_timeout="0" />
<FrameLayout <FrameLayout
app:layout_constraintBottom_toBottomOf="parent" android:id="@+id/player_loading_overlay"
app:layout_constraintEnd_toEndOf="parent" android:layout_width="match_parent"
app:layout_constraintStart_toStartOf="parent" android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent" android:background="@android:color/black"
android:layout_width="match_parent" android:backgroundTint="@android:color/black"
android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/player_loading_overlay" app:layout_constraintEnd_toEndOf="parent"
android:background="@android:color/black" app:layout_constraintStart_toStartOf="parent"
android:backgroundTint="@android:color/black"> app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
tools:visibility="visible" android:id="@+id/overlay_loading_skip_button"
android:visibility="gone" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_marginTop="70dp" android:layout_width="wrap_content"
android:layout_gravity="center" android:layout_height="45dp"
app:cornerRadius="4dp" android:layout_gravity="center"
android:id="@+id/overlay_loading_skip_button" android:layout_marginTop="70dp"
android:text="@string/skip_loading" android:backgroundTint="@color/transparent"
app:rippleColor="?attr/colorPrimary" android:text="@string/skip_loading"
android:textColor="?attr/textColor" android:textAllCaps="false"
app:iconTint="?attr/textColor" android:textColor="?attr/textColor"
android:textAllCaps="false" android:visibility="gone"
app:icon="@drawable/ic_baseline_skip_next_24" app:cornerRadius="4dp"
android:backgroundTint="@color/transparent" app:icon="@drawable/ic_baseline_skip_next_24"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" app:iconTint="?attr/textColor"
android:layout_width="wrap_content" app:rippleColor="?attr/colorPrimary"
android:layout_height="45dp" /> tools:visibility="visible" />
<ProgressBar <ProgressBar
android:layout_width="50dp" android:id="@+id/main_load"
android:layout_height="50dp" android:layout_width="50dp"
android:layout_gravity="center" android:layout_height="50dp"
android:id="@+id/main_load" /> android:layout_gravity="center" />
<FrameLayout <FrameLayout
android:id="@+id/video_go_back_holder_holder" android:id="@+id/video_go_back_holder_holder"
android:layout_margin="5dp" android:layout_width="wrap_content"
app:layout_constraintStart_toStartOf="parent" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" android:layout_margin="5dp"
android:layout_width="wrap_content" app:layout_constraintStart_toStartOf="parent"
android:layout_height="wrap_content"> app:layout_constraintTop_toTopOf="parent">
<ImageView <ImageView
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@drawable/ic_baseline_arrow_back_24" android:contentDescription="@string/go_back_img_des"
app:tint="@android:color/white" android:src="@drawable/ic_baseline_arrow_back_24"
android:contentDescription="@string/go_back_img_des" /> app:tint="@android:color/white" />
<ImageView <ImageView
android:id="@+id/player_loading_go_back" android:id="@+id/player_loading_go_back"
android:layout_width="70dp" android:layout_width="70dp"
android:layout_height="70dp" android:layout_height="70dp"
android:layout_gravity="center" android:layout_gravity="center"
android:focusable="true" android:background="@drawable/video_tap_button_always_white"
android:clickable="true" android:clickable="true"
android:background="@drawable/video_tap_button_always_white" android:contentDescription="@string/go_back_img_des"
android:contentDescription="@string/go_back_img_des" /> android:focusable="true" />
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:visibility="gone" android:id="@+id/player_torrent_info"
android:paddingStart="20dp" android:layout_width="match_parent"
android:paddingEnd="20dp" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:paddingStart="20dp"
app:layout_constraintEnd_toEndOf="parent" android:paddingEnd="20dp"
app:layout_constraintStart_toStartOf="parent" android:visibility="gone"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/player_torrent_info" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/video_torrent_progress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="start"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="78% at 18kb/s" />
<TextView <TextView
android:layout_width="match_parent" android:id="@+id/video_torrent_seeders"
android:layout_height="wrap_content" android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent" android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent" android:layout_marginTop="0dp"
android:gravity="start" android:gravity="start"
android:layout_marginTop="15dp" android:textColor="@color/white"
android:textStyle="bold" app:layout_constraintLeft_toLeftOf="parent"
android:textColor="@color/white" app:layout_constraintTop_toBottomOf="@+id/player_video_title"
android:id="@+id/video_torrent_progress" tools:text="17 seeders" />
tools:text="78% at 18kb/s" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
android:gravity="start"
android:layout_marginTop="0dp"
android:textColor="@color/white"
android:id="@+id/video_torrent_seeders"
tools:text="17 seeders"
app:layout_constraintTop_toBottomOf="@+id/player_video_title" />
</FrameLayout> </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -267,10 +267,10 @@
</LinearLayout> </LinearLayout>
</FrameLayout>--> </FrameLayout>-->
<FrameLayout <FrameLayout
android:id="@+id/result_top_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp"> android:layout_height="200dp">
<FrameLayout <FrameLayout
android:id="@+id/result_smallscreen_holder" android:id="@+id/result_smallscreen_holder"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -279,21 +279,23 @@
<include layout="@layout/fragment_trailer" /> <include layout="@layout/fragment_trailer" />
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/result_poster_background_holder" android:id="@+id/result_poster_background_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView
android:id="@+id/result_poster_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<View <ImageView
android:layout_width="match_parent" android:id="@+id/result_poster_background"
android:layout_height="60dp" android:layout_width="match_parent"
android:layout_gravity="bottom" android:layout_height="match_parent"
android:background="@drawable/background_shadow" /> android:scaleType="centerCrop" />
<View
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="@drawable/background_shadow" />
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
@ -330,6 +332,7 @@
android:contentDescription="@string/result_poster_img_des" android:contentDescription="@string/result_poster_img_des"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:layout_gravity="bottom"
tools:src="@drawable/example_poster" /> tools:src="@drawable/example_poster" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View file

@ -112,6 +112,11 @@
android:nextFocusLeft="@id/nav_rail_view" android:nextFocusLeft="@id/nav_rail_view"
android:visibility="gone" android:visibility="gone"
tools:listitem="@layout/homepage_parent" /> tools:listitem="@layout/homepage_parent" />
<FrameLayout
android:id="@+id/search_history_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_history_recycler" android:id="@+id/search_history_recycler"
@ -122,5 +127,19 @@
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/nav_rail_view" android:nextFocusLeft="@id/nav_rail_view"
android:visibility="visible" android:visibility="visible"
android:paddingBottom="50dp"
tools:listitem="@layout/search_history_item" /> tools:listitem="@layout/search_history_item" />
<com.google.android.material.button.MaterialButton
android:id="@+id/search_clear_call_history"
style="@style/BlackButton"
android:layout_gravity="bottom"
android:padding="0dp"
app:cornerRadius="0dp"
android:layout_margin="0dp"
android:text="@string/clear_history"
app:icon="@drawable/delete_all"
android:layout_width="match_parent"
android:layout_height="50dp" />
</FrameLayout>
</LinearLayout> </LinearLayout>

View file

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:visibility="visible" android:visibility="visible"
android:orientation="horizontal" android:orientation="horizontal"
android:id="@+id/player_background" android:id="@+id/player_background"

View file

@ -318,6 +318,25 @@
</FrameLayout> </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton
tools:visibility="visible"
android:id="@+id/skip_chapter_button"
style="@style/NiceButton"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginEnd="100dp"
android:visibility="gone"
android:maxLines="1"
android:backgroundTint="@color/skipOpTransparent"
android:padding="10dp"
android:textColor="@color/white"
app:cornerRadius="@dimen/rounded_button_radius"
app:layout_constraintBottom_toTopOf="@+id/bottom_player_bar"
app:layout_constraintEnd_toEndOf="parent"
app:strokeColor="@color/white"
app:strokeWidth="1dp"
tools:text="Skip Opening" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -62,7 +62,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start|bottom" android:layout_gravity="start|bottom"
android:padding="10dp" android:padding="15dp"
android:text="@string/trailer" android:text="@string/trailer"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="20sp" android:textSize="20sp"

View file

@ -577,4 +577,17 @@
<string name="player_settings_play_in_web">投屏</string> <string name="player_settings_play_in_web">投屏</string>
<string name="player_settings_play_in_browser">浏览器</string> <string name="player_settings_play_in_browser">浏览器</string>
<string name="app_not_found_error">未找到应用</string> <string name="app_not_found_error">未找到应用</string>
<string name="all_languages_preference">所有语言</string>
<string name="skip_type_format" formatted="true">跳过 %s</string>
<string name="skip_type_op">片头</string>
<string name="skip_type_ed">片尾</string>
<string name="skip_type_recap">前情回顾</string>
<string name="skip_type_mixed_ed">混合片尾</string>
<string name="skip_type_mixed_op">混合片头</string>
<string name="skip_type_creddits">致谢名单</string>
<string name="skip_type_intro">介绍</string>
<string name="clear_history">清除历史记录</string>
<string name="history">历史记录</string>
</resources> </resources>

View file

@ -41,6 +41,7 @@
<color name="black_overlay">#66000000</color> <color name="black_overlay">#66000000</color>
<color name="darkBarTransparent">#C0121212</color> <color name="darkBarTransparent">#C0121212</color>
<color name="skipOpTransparent">#4D121212</color>
<color name="darkBar">#121212</color> <color name="darkBar">#121212</color>
<color name="videoProgress">#66B5B5B5</color> <!--66B5B5B5--> <color name="videoProgress">#66B5B5B5</color> <!--66B5B5B5-->
<!--<color name="videoCache">#663D50FA</color>--> <!--66B5B5B5--> <!--<color name="videoCache">#663D50FA</color>--> <!--66B5B5B5-->

View file

@ -28,7 +28,7 @@
<string name="pip_enabled_key" translatable="false">pip_enabled_key</string> <string name="pip_enabled_key" translatable="false">pip_enabled_key</string>
<string name="double_tap_enabled_key" translatable="false">double_tap_enabled_key</string> <string name="double_tap_enabled_key" translatable="false">double_tap_enabled_key</string>
<string name="double_tap_pause_enabled_key" translatable="false">double_tap_pause_enabled_key</string> <string name="double_tap_pause_enabled_key" translatable="false">double_tap_pause_enabled_key</string>
<string name="double_tap_seek_time_key" translatable="false">double_tap_seek_time_key</string> <string name="double_tap_seek_time_key" translatable="false">double_tap_seek_time_key2</string>
<string name="swipe_vertical_enabled_key" translatable="false">swipe_vertical_enabled_key</string> <string name="swipe_vertical_enabled_key" translatable="false">swipe_vertical_enabled_key</string>
<string name="autoplay_next_key" translatable="false">autoplay_next_key</string> <string name="autoplay_next_key" translatable="false">autoplay_next_key</string>
<string name="display_sub_key" translatable="false">display_sub_key</string> <string name="display_sub_key" translatable="false">display_sub_key</string>
@ -58,6 +58,7 @@
<string name="pref_filter_search_quality_key" translatable="false">pref_filter_search_quality_key</string> <string name="pref_filter_search_quality_key" translatable="false">pref_filter_search_quality_key</string>
<string name="enable_nsfw_on_providers_key" translatable="false">enable_nsfw_on_providers_key</string> <string name="enable_nsfw_on_providers_key" translatable="false">enable_nsfw_on_providers_key</string>
<string name="automatic_cloud_backups" translatable="false">automatic_cloud_backups</string> <string name="automatic_cloud_backups" translatable="false">automatic_cloud_backups</string>
<string name="enable_skip_op_from_database" translatable="false">enable_skip_op_from_database</string>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string> <string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
@ -644,4 +645,18 @@
<string name="player_settings_play_in_browser">Browser</string> <string name="player_settings_play_in_browser">Browser</string>
<string name="app_not_found_error">App not found</string> <string name="app_not_found_error">App not found</string>
<string name="all_languages_preference">All Languages</string> <string name="all_languages_preference">All Languages</string>
<string name="skip_type_format" formatted="true">Skip %s</string>
<string name="skip_type_op">Opening</string>
<string name="skip_type_ed">Ending</string>
<string name="skip_type_recap">Recap</string>
<string name="skip_type_mixed_ed">Mixed ending</string>
<string name="skip_type_mixed_op">Mixed opening</string>
<string name="skip_type_creddits">Credits</string>
<string name="skip_type_intro">Intro</string>
<string name="clear_history">Clear history</string>
<string name="history">History</string>
<string name="enable_skip_op_from_database_des">Show skip popups for opening/ending</string>
<string name="clipboard_too_large">Too much text. Unable to save to clipboard.</string>
</resources> </resources>

View file

@ -83,6 +83,7 @@
<item name="android:textColor">@color/chip_color_text</item> <item name="android:textColor">@color/chip_color_text</item>
<item name="checkedIconTint">@color/chip_color_text</item> <item name="checkedIconTint">@color/chip_color_text</item>
<item name="fontFamily">@font/google_sans</item> <item name="fontFamily">@font/google_sans</item>
<item name="chipIconTint">@color/chip_color_text</item>
<item name="android:fontFamily">@font/google_sans</item> <item name="android:fontFamily">@font/google_sans</item>
</style> </style>

View file

@ -1,126 +1,132 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference <Preference
android:key="@string/subtitle_settings_key" android:icon="@drawable/ic_outline_subtitles_24"
android:title="@string/player_subtitles_settings" android:key="@string/subtitle_settings_key"
android:icon="@drawable/ic_outline_subtitles_24" android:title="@string/player_subtitles_settings"
app:summary="@string/player_subtitles_settings_des" /> app:summary="@string/player_subtitles_settings_des" />
<Preference <Preference
android:key="@string/subtitle_settings_chromecast_key" android:icon="@drawable/ic_outline_subtitles_24"
android:title="@string/chromecast_subtitles_settings" android:key="@string/subtitle_settings_chromecast_key"
android:icon="@drawable/ic_outline_subtitles_24" android:title="@string/chromecast_subtitles_settings"
app:summary="@string/chromecast_subtitles_settings_des" /> app:summary="@string/chromecast_subtitles_settings_des" />
<Preference <Preference
android:key="@string/quality_pref_key" android:icon="@drawable/ic_baseline_hd_24"
android:title="@string/watch_quality_pref" android:key="@string/quality_pref_key"
android:icon="@drawable/ic_baseline_hd_24" /> android:title="@string/watch_quality_pref" />
<Preference <Preference
android:key="@string/player_pref_key" android:icon="@drawable/netflix_play"
android:title="@string/player_pref" android:key="@string/player_pref_key"
android:icon="@drawable/netflix_play" /> android:title="@string/player_pref" />
<Preference <Preference
android:key="@string/prefer_limit_title_key" android:icon="@drawable/ic_baseline_text_format_24"
android:title="@string/limit_title" android:key="@string/prefer_limit_title_key"
android:icon="@drawable/ic_baseline_text_format_24" /> android:title="@string/limit_title" />
<Preference <Preference
android:key="@string/prefer_limit_title_rez_key" android:icon="@drawable/ic_baseline_text_format_24"
android:title="@string/limit_title_rez" android:key="@string/prefer_limit_title_rez_key"
android:icon="@drawable/ic_baseline_text_format_24" /> android:title="@string/limit_title_rez" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_picture_in_picture_alt_24" android:icon="@drawable/ic_baseline_picture_in_picture_alt_24"
app:key="@string/pip_enabled_key" android:summary="@string/picture_in_picture_des"
android:title="@string/picture_in_picture" android:title="@string/picture_in_picture"
android:summary="@string/picture_in_picture_des" app:defaultValue="true"
app:defaultValue="true" /> app:key="@string/pip_enabled_key" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_aspect_ratio_24" android:icon="@drawable/ic_baseline_aspect_ratio_24"
app:key="@string/player_resize_enabled_key" android:summary="@string/player_size_settings_des"
android:title="@string/player_size_settings" android:title="@string/player_size_settings"
android:summary="@string/player_size_settings_des" app:defaultValue="true"
app:defaultValue="true" /> app:key="@string/player_resize_enabled_key" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_speed_24" android:icon="@drawable/ic_baseline_speed_24"
app:key="@string/playback_speed_enabled_key" android:summary="@string/eigengraumode_settings_des"
android:title="@string/eigengraumode_settings" android:title="@string/eigengraumode_settings"
android:summary="@string/eigengraumode_settings_des" app:defaultValue="false"
app:defaultValue="false" /> app:key="@string/playback_speed_enabled_key" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_ondemand_video_24" android:icon="@drawable/ic_baseline_ondemand_video_24"
app:key="@string/swipe_enabled_key" android:summary="@string/swipe_to_seek_settings_des"
android:title="@string/swipe_to_seek_settings" android:title="@string/swipe_to_seek_settings"
android:summary="@string/swipe_to_seek_settings_des" app:defaultValue="true"
app:defaultValue="true" /> app:key="@string/swipe_enabled_key" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_ondemand_video_24" android:icon="@drawable/ic_baseline_ondemand_video_24"
app:key="@string/swipe_vertical_enabled_key" android:summary="@string/swipe_to_change_settings_des"
android:title="@string/swipe_to_change_settings" android:title="@string/swipe_to_change_settings"
android:summary="@string/swipe_to_change_settings_des" app:defaultValue="true"
app:defaultValue="true" /> app:key="@string/swipe_vertical_enabled_key" />
<SwitchPreference
android:icon="@drawable/ic_baseline_skip_next_24"
app:key="@string/autoplay_next_key"
android:title="@string/autoplay_next_settings"
android:summary="@string/autoplay_next_settings_des"
app:defaultValue="true" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_touch_app_24" android:icon="@drawable/ic_baseline_skip_next_24"
app:key="@string/double_tap_enabled_key" android:summary="@string/autoplay_next_settings_des"
android:title="@string/double_tap_to_seek_settings" android:title="@string/autoplay_next_settings"
android:summary="@string/double_tap_to_seek_settings_des" app:defaultValue="true"
app:defaultValue="false" /> app:key="@string/autoplay_next_key" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/netflix_pause" android:icon="@drawable/ic_baseline_skip_next_24"
app:key="@string/double_tap_pause_enabled_key" android:title="@string/video_skip_op"
android:title="@string/double_tap_to_pause_settings" app:defaultValue="true"
android:summary="@string/double_tap_to_pause_settings_des" android:summary="@string/enable_skip_op_from_database_des"
app:defaultValue="false" /> app:key="@string/enable_skip_op_from_database" />
<SwitchPreference
android:icon="@drawable/ic_baseline_touch_app_24"
android:summary="@string/double_tap_to_seek_settings_des"
android:title="@string/double_tap_to_seek_settings"
app:defaultValue="false"
app:key="@string/double_tap_enabled_key" />
<SwitchPreference
android:icon="@drawable/netflix_pause"
android:summary="@string/double_tap_to_pause_settings_des"
android:title="@string/double_tap_to_pause_settings"
app:defaultValue="false"
app:key="@string/double_tap_pause_enabled_key" />
<SeekBarPreference <SeekBarPreference
app:icon="@drawable/go_forward_30" android:defaultValue="10"
app:adjustable="true" android:max="60"
android:defaultValue="10" android:title="@string/double_tap_to_seek_amount_settings"
app:min="5" app:adjustable="true"
app:seekBarIncrement="5" app:defaultValue="10"
app:showSeekBarValue="true" app:icon="@drawable/go_forward_30"
android:max="60" app:key="@string/double_tap_seek_time_key"
app:key="@string/double_tap_seek_time_key" app:min="5"
android:title="@string/double_tap_to_seek_amount_settings" app:seekBarIncrement="5"
app:defaultValue="false" /> app:showSeekBarValue="true" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/baseline_sync_24" android:icon="@drawable/baseline_sync_24"
app:key="@string/episode_sync_enabled_key" android:summary="@string/episode_sync_settings_des"
android:title="@string/episode_sync_settings" android:title="@string/episode_sync_settings"
android:summary="@string/episode_sync_settings_des" app:defaultValue="true"
app:defaultValue="true" /> app:key="@string/episode_sync_enabled_key" />
<Preference <Preference
android:key="@string/video_buffer_disk_key" android:icon="@drawable/ic_baseline_storage_24"
android:title="@string/video_buffer_disk_settings" android:key="@string/video_buffer_disk_key"
android:summary="@string/video_disk_description" android:summary="@string/video_disk_description"
android:icon="@drawable/ic_baseline_storage_24" /> android:title="@string/video_buffer_disk_settings" />
<Preference <Preference
android:key="@string/video_buffer_size_key" android:icon="@drawable/ic_baseline_storage_24"
android:title="@string/video_buffer_size_settings" android:key="@string/video_buffer_size_key"
android:summary="@string/video_ram_description" android:summary="@string/video_ram_description"
android:icon="@drawable/ic_baseline_storage_24" /> android:title="@string/video_buffer_size_settings" />
<Preference <Preference
android:key="@string/video_buffer_length_key" android:icon="@drawable/ic_baseline_storage_24"
android:title="@string/video_buffer_length_settings" android:key="@string/video_buffer_length_key"
android:summary="@string/video_ram_description" android:summary="@string/video_ram_description"
android:icon="@drawable/ic_baseline_storage_24" /> android:title="@string/video_buffer_length_settings" />
<Preference <Preference
android:key="@string/video_buffer_clear_key" android:icon="@drawable/ic_baseline_delete_outline_24"
android:title="@string/video_buffer_clear_settings" android:key="@string/video_buffer_clear_key"
android:icon="@drawable/ic_baseline_delete_outline_24" /> android:title="@string/video_buffer_clear_settings" />
</PreferenceScreen> </PreferenceScreen>