mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge remote-tracking branch 'origin/githubAccount' into githubAccount
This commit is contained in:
commit
337f33d0c8
61 changed files with 918 additions and 397 deletions
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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\("(.*?)"""")
|
||||||
|
|
|
@ -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*['"](.*?)['"]""")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
140
app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt
Normal file
140
app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt
Normal 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
|
||||||
|
)
|
||||||
|
}
|
|
@ -205,6 +205,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
||||||
VideovardSX(),
|
VideovardSX(),
|
||||||
Mp4Upload(),
|
Mp4Upload(),
|
||||||
StreamTape(),
|
StreamTape(),
|
||||||
|
StreamTapeNet(),
|
||||||
|
ShaveTape(),
|
||||||
|
|
||||||
//mixdrop extractors
|
//mixdrop extractors
|
||||||
MixDropBz(),
|
MixDropBz(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
9
app/src/main/res/drawable/delete_all.xml
Normal file
9
app/src/main/res/drawable/delete_all.xml
Normal 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>
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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-->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue