diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 99eec423..f72eb321 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -47,8 +47,8 @@ android { minSdk = 21 targetSdk = 30 - versionCode = 52 - versionName = "3.1.6" + versionCode = 55 + versionName = "3.2.3" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") @@ -190,7 +190,7 @@ dependencies { // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") // 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 🙏 implementation("com.github.tachiyomiorg:unifile:17bec43") diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 32df314f..342dc6f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -61,6 +61,7 @@ object CommonActivity { } } + @MainThread fun showToast(act: Activity?, @StringRes message: Int, duration: Int) { if (act == null) return showToast(act, act.getString(message), duration) @@ -69,6 +70,7 @@ object CommonActivity { const val TAG = "COMPACT" /** duration is Toast.LENGTH_SHORT if null*/ + @MainThread fun showToast(act: Activity?, message: String?, duration: Int? = null) { if (act == null || message == null) { Log.w(TAG, "invalid showToast act = $act message = $message") @@ -337,6 +339,9 @@ object CommonActivity { KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> { 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 PlayerEventType.PlayPauseToggle } diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 190c0cf5..4b271192 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -165,7 +165,8 @@ object APIHolder { val hashSet = HashSet() val activeLangs = getApiProviderLangSettings() 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( this.getString(R.string.search_providers_list_key), @@ -241,7 +242,13 @@ object APIHolder { } fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List { - val default = enumValues().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 + // enumValues() might be the cause, hence I am trying TvType.values() + val default = TvType.values() + .sorted() + .filter { it != TvType.NSFW } + .map { it.ordinal } val defaultSet = default.map { it.toString() }.toSet() val currentPrefMedia = try { PreferenceManager.getDefaultSharedPreferences(this) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index cfd650bb..777b7338 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -92,6 +92,9 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient +import okhttp3.internal.applyConnectionSpec import java.io.File import java.net.URI import java.nio.charset.Charset diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt index 18602664..b0051ba7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt @@ -2,10 +2,11 @@ package com.lagradost.cloudstream3.extractors import android.util.Log import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.base64Decode -import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.ExtractorApi +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 mainUrl = "https://astreamhub.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt index fe46791b..18198f44 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt @@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.utils.* -class Acefile : ExtractorApi() { +open class Acefile : ExtractorApi() { override val name = "Acefile" override val mainUrl = "https://acefile.co" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt index cf16f200..7a62fb52 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.getQualityFromName import java.net.URI -class AsianLoad : ExtractorApi() { +open class AsianLoad : ExtractorApi() { override var name = "AsianLoad" override var mainUrl = "https://asianembed.io" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt index cae77322..44e700b1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -class Blogger : ExtractorApi() { +open class Blogger : ExtractorApi() { override val name = "Blogger" override val mainUrl = "https://www.blogger.com" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt index d4f87f4c..71fa7066 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -class BullStream : ExtractorApi() { +open class BullStream : ExtractorApi() { override val name = "BullStream" override val mainUrl = "https://bullstream.xyz" override val requiresReferer = false @@ -18,7 +18,7 @@ class BullStream : ExtractorApi() { ?: return null val m3u8 = "$mainUrl/m3u8/${data[1]}/${data[2]}/master.txt?s=1&cache=${data[4]}" - println("shiv : $m3u8") + //println("shiv : $m3u8") return M3u8Helper.generateM3u8( name, m3u8, diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt index 35569663..45a06dcc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.httpsify -class Embedgram : ExtractorApi() { +open class Embedgram : ExtractorApi() { override val name = "Embedgram" override val mainUrl = "https://embedgram.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt index 0d5a5c78..f813d7ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 -class Fastream: ExtractorApi() { +open class Fastream: ExtractorApi() { override var mainUrl = "https://fastream.to" override var name = "Fastream" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt index 5c8af1c5..8e3dc730 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -class Filesim : ExtractorApi() { +open class Filesim : ExtractorApi() { override val name = "Filesim" override val mainUrl = "https://files.im" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt index af02ee8a..52c45096 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt @@ -3,10 +3,9 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.Qualities -class GMPlayer : ExtractorApi() { +open class GMPlayer : ExtractorApi() { override val name = "GM Player" override val mainUrl = "https://gmplayer.xyz" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt index 52fc5532..73734e2a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName -class Linkbox : ExtractorApi() { +open class Linkbox : ExtractorApi() { override val name = "Linkbox" override val mainUrl = "https://www.linkbox.to" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt deleted file mode 100644 index 29d98557..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt index 68a4a103..93a280ed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getAndUnpack -class Mp4Upload : ExtractorApi() { +open class Mp4Upload : ExtractorApi() { override var name = "Mp4Upload" override var mainUrl = "https://www.mp4upload.com" private val srcRegex = Regex("""player\.src\("(.*?)"""") diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt index 0c0b5c68..44657196 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getQualityFromName import java.net.URI -class MultiQuality : ExtractorApi() { +open class MultiQuality : ExtractorApi() { override var name = "MultiQuality" override var mainUrl = "https://gogo-play.net" private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""") diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt index c00df942..9e5f5e74 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -class Mvidoo : ExtractorApi() { +open class Mvidoo : ExtractorApi() { override val name = "Mvidoo" override val mainUrl = "https://mvidoo.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt index de469b22..45ec4c2f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -14,7 +14,7 @@ import org.jsoup.Jsoup * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc * 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" private fun getExtractorUrl(id: String): String { diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt index 9a031556..2b286abb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -class PlayLtXyz: ExtractorApi() { +open class PlayLtXyz: ExtractorApi() { override val name: String = "PlayLt" override val mainUrl: String = "https://play.playlt.xyz" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt index 849f5fc8..cc34781c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName -class Solidfiles : ExtractorApi() { +open class Solidfiles : ExtractorApi() { override val name = "Solidfiles" override val mainUrl = "https://www.solidfiles.com" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt index af436ff3..ece8dc4b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt @@ -5,7 +5,15 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink 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 mainUrl = "https://streamtape.com" override val requiresReferer = false @@ -16,7 +24,8 @@ class StreamTape : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { 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( ExtractorLink( name, diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt index 2765ae17..c7689c58 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.JsUnpacker import com.lagradost.cloudstream3.utils.Qualities import java.net.URI -class Streamhub : ExtractorApi() { +open class Streamhub : ExtractorApi() { override var mainUrl = "https://streamhub.to" override var name = "Streamhub" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt index 3f5e5bd6..e6bbfeba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import java.net.URI -class Streamplay : ExtractorApi() { +open class Streamplay : ExtractorApi() { override val name = "Streamplay" override val mainUrl = "https://streamplay.to" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt index 955345aa..dd49d994 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt @@ -11,7 +11,7 @@ data class Files( @JsonProperty("label") val label: String? = null, ) - open class Supervideo : ExtractorApi() { +open class Supervideo : ExtractorApi() { override var name = "Supervideo" override var mainUrl = "https://supervideo.tv" override val requiresReferer = false @@ -20,10 +20,13 @@ data class Files( val response = app.get(url).text val jstounpack = Regex("eval((.|\\n)*?)").find(response)?.groups?.get(1)?.value 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>(extractedUrl) 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( name, data.id, @@ -34,8 +37,6 @@ data class Files( } } } - - return extractedLinksList } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt index 1eb384c4..09e47d03 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -class UpstreamExtractor : ExtractorApi() { +open class UpstreamExtractor : ExtractorApi() { override val name: String = "Upstream" override val mainUrl: String = "https://upstream.to" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt index 41e77967..30a1d8fe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt @@ -11,7 +11,7 @@ class VideovardSX : WcoStream() { 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 mainUrl = "https://videovard.to" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt index d99485ea..6cc486cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -53,6 +53,12 @@ class VizcloudSite : WcoStream() { 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() { override var name = "VidStream" // Cause works for animekisa and wco override var mainUrl = "https://vidstream.pro" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt index 3c564f67..c7aa989d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName -class YourUpload: ExtractorApi() { +open class YourUpload: ExtractorApi() { override val name = "Yourupload" override val mainUrl = "https://www.yourupload.com" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt index 572d93a3..23704e90 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YoutubeExtractor.kt @@ -46,7 +46,6 @@ open class YoutubeExtractor : ExtractorApi() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - println("TRYING TO ExTRACT: $url") if (ytVideos[url].isNullOrEmpty()) { val link = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl( diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt index 84785b6c..43c4eefb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -class Zorofile : ExtractorApi() { +open class Zorofile : ExtractorApi() { override val name = "Zorofile" override val mainUrl = "https://zorofile.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt index 13299002..8bf1f91b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -12,7 +12,6 @@ import okhttp3.Headers.Companion.toHeaders import okhttp3.OkHttpClient import java.io.File - fun Requests.initClient(context: Context): OkHttpClient { val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 877e918c..7373f5a5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -74,8 +74,10 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectSt import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe 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.getSpanCount +import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur @@ -555,7 +557,13 @@ class HomeFragment : Fragment() { observe(homeViewModel.preview) { preview -> // 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) { is Resource.Success -> { home_preview?.isVisible = true @@ -576,7 +584,7 @@ class HomeFragment : Fragment() { false ) home_preview?.isVisible = false - context?.fixPaddingStatusbar(home_watch_holder) + context?.fixPaddingStatusbarView(home_fix_padding) } } } @@ -592,6 +600,11 @@ class HomeFragment : Fragment() { setPageTransformer(HomeScrollTransformer()) val callback: OnPageChangeCallback = object : OnPageChangeCallback() { override fun onPageSelected(position: Int) { + + // home_search?.isIconified = true + //home_search?.isVisible = true + //home_search?.clearFocus() + (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply { if (position >= itemCount - 1 && hasMoreItems) { hasMoreItems = false // dont make two requests diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 9a0debcf..21047db3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.utils.AppUtils 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.hideSystemUI import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage @@ -103,6 +104,14 @@ abstract class AbstractPlayerFragment( throw NotImplementedError() } + open fun onTimestamp(timestamp: EpisodeSkip.SkipStamp?) { + + } + + open fun onTimestampSkipped(timestamp: EpisodeSkip.SkipStamp) { + + } + open fun exitedPipMode() { throw NotImplementedError() } @@ -373,7 +382,9 @@ abstract class AbstractPlayerFragment( ), subtitlesUpdates = ::subtitlesChanged, embeddedSubtitlesFetched = ::embeddedSubtitlesFetched, - onTracksInfoChanged = ::onTracksInfoChanged + onTracksInfoChanged = ::onTracksInfoChanged, + onTimestampInvoked = ::onTimestamp, + onTimestampSkipped = ::onTimestampSkipped ) if (player is CS3IPlayer) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index f60d8c78..8eda6e30 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -18,7 +18,10 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.trackselection.TrackSelectionOverride import com.google.android.exoplayer2.trackselection.TrackSelector 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.LeastRecentlyUsedCacheEvictor 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.normalSafeApiCall import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle +import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorUri @@ -113,6 +117,8 @@ class CS3IPlayer : IPlayer { private var playerUpdated: ((Any?) -> Unit)? = null private var embeddedSubtitlesFetched: ((List) -> Unit)? = null private var onTracksInfoChanged: (() -> Unit)? = null + private var onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)? = null + private var onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null override fun releaseCallbacks() { playerUpdated = null @@ -126,7 +132,9 @@ class CS3IPlayer : IPlayer { prevEpisode = null subtitlesUpdates = null onTracksInfoChanged = null + onTimestampInvoked = null requestSubtitleUpdate = null + onTimestampSkipped = null } override fun initCallbacks( @@ -142,6 +150,8 @@ class CS3IPlayer : IPlayer { subtitlesUpdates: (() -> Unit)?, embeddedSubtitlesFetched: ((List) -> Unit)?, onTracksInfoChanged: (() -> Unit)?, + onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)?, + onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)?, ) { this.playerUpdated = playerUpdated this.updateIsPlaying = updateIsPlaying @@ -155,6 +165,8 @@ class CS3IPlayer : IPlayer { this.subtitlesUpdates = subtitlesUpdates this.embeddedSubtitlesFetched = embeddedSubtitlesFetched this.onTracksInfoChanged = onTracksInfoChanged + this.onTimestampInvoked = onTimestampInvoked + this.onTimestampSkipped = onTimestampSkipped } // I know, this is not a perfect solution, however it works for fixing subs @@ -719,7 +731,7 @@ class CS3IPlayer : IPlayer { source } - println("PLAYBACK POS $playbackPosition") + //println("PLAYBACK POS $playbackPosition") return exoPlayerBuilder.build().apply { setPlayWhenReady(playWhenReady) seekTo(currentWindow, playbackPosition) @@ -735,8 +747,22 @@ class CS3IPlayer : IPlayer { } } - fun updatedTime() { - val position = exoPlayer?.currentPosition + private fun getCurrentTimestamp(writePosition : Long? = null): EpisodeSkip.SkipStamp? { + 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 if (duration != null && position != null) { playerPositionChanged?.invoke(Pair(position, duration)) @@ -748,12 +774,12 @@ class CS3IPlayer : IPlayer { } override fun seekTo(time: Long) { - updatedTime() + updatedTime(time) exoPlayer?.seekTo(time) } private fun ExoPlayer.seekTime(time: Long) { - updatedTime() + updatedTime(currentPosition + time) seekTo(currentPosition + time) } @@ -789,6 +815,17 @@ class CS3IPlayer : IPlayer { CSPlayerEvent.SeekBack -> seekTime(-seekActionTime) CSPlayerEvent.NextEpisode -> nextEpisode?.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) { @@ -1007,6 +1044,24 @@ class CS3IPlayer : IPlayer { } } + private var lastTimeStamps: List = emptyList() + override fun addTimeStamps(timeStamps: List) { + 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() { if (!hasUsedFirstRender) { // this insures that we only call this once per player load Log.i(TAG, "Rendered first frame") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 1ddd752f..0f9a6548 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -587,7 +587,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { updateLockUI() } - private fun updateUIVisibility() { + fun updateUIVisibility() { val isGone = isLocked || !isShowing var togglePlayerTitleGone = isGone context?.let { @@ -1141,6 +1141,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() { PlayerEventType.Play -> { player.handleEvent(CSPlayerEvent.Play) } + PlayerEventType.SkipCurrentChapter -> { + player.handleEvent(CSPlayerEvent.SkipCurrentChapter) + } PlayerEventType.Resize -> { nextResize() } @@ -1254,6 +1257,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() { player.handleEvent(CSPlayerEvent.PlayPauseToggle) } + skip_chapter_button?.setOnClickListener { + player.handleEvent(CSPlayerEvent.SkipCurrentChapter) + } + // init clicks player_resize_btt?.setOnClickListener { autoHide() @@ -1306,12 +1313,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { 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 player_holder?.setOnTouchListener { callView, event -> return@setOnTouchListener handleMotionEvent(callView, event) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index f466dd7e..95a9393f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.player +import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.Dialog import android.content.Context @@ -13,6 +14,7 @@ import android.view.ViewGroup import android.widget.* import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog +import androidx.core.animation.addListener import androidx.core.content.ContextCompat import androidx.core.view.isGone 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.ResultFragment 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.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1 import com.lagradost.cloudstream3.utils.* 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.hideSystemUI 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.apply_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_tracks.* import kotlinx.coroutines.Job -import kotlinx.coroutines.delay class GeneratorPlayer : FullScreenPlayer() { companion object { @@ -67,8 +69,7 @@ class GeneratorPlayer : FullScreenPlayer() { Log.i(TAG, "newInstance = $syncData") lastUsedGenerator = generator return Bundle().apply { - if (syncData != null) - putSerializable("syncData", syncData) + if (syncData != null) putSerializable("syncData", syncData) } } @@ -165,6 +166,8 @@ class GeneratorPlayer : FullScreenPlayer() { isActive = true setPlayerDimen(null) setTitle() + if (!sameEpisode) + hasRequestedStamps = false loadExtractorJob(link.first) // load player @@ -180,12 +183,13 @@ class GeneratorPlayer : FullScreenPlayer() { }, currentSubs, (if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle( - currentSubs, - settings = true, - downloads = true + currentSubs, settings = true, downloads = true ), ) } + + if (!sameEpisode) + player.addTimeStamps(listOf()) // clear stamps } private fun sortLinks(useQualitySettings: Boolean = true): List> { @@ -231,9 +235,7 @@ class GeneratorPlayer : FullScreenPlayer() { } override fun openOnlineSubPicker( - context: Context, - imdbId: Long?, - dismissCallback: (() -> Unit) + context: Context, imdbId: Long?, dismissCallback: (() -> Unit) ) { val providers = subsProviders val isSingleProvider = subsProviders.size == 1 @@ -256,8 +258,7 @@ class GeneratorPlayer : FullScreenPlayer() { val arrayAdapter = object : ArrayAdapter(dialog.context, layout) { fun setHearingImpairedIcon( - imageViewEnd: ImageView?, - position: Int + imageViewEnd: ImageView?, position: Int ) { if (imageViewEnd == null) return val isHearingImpaired = @@ -265,13 +266,11 @@ class GeneratorPlayer : FullScreenPlayer() { val drawableEnd = if (isHearingImpaired) { ContextCompat.getDrawable( - context, - R.drawable.ic_baseline_hearing_24 + context, R.drawable.ic_baseline_hearing_24 )?.apply { setTint( ContextCompat.getColor( - context, - R.color.textColor + context, R.color.textColor ) ) } @@ -281,8 +280,7 @@ class GeneratorPlayer : FullScreenPlayer() { } override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: LayoutInflater.from(context) - .inflate(layout, null) + val view = convertView ?: LayoutInflater.from(context).inflate(layout, null) val item = getItem(position) @@ -337,13 +335,12 @@ class GeneratorPlayer : FullScreenPlayer() { override fun onQueryTextSubmit(query: String?): Boolean { dialog.search_loading_bar?.show() ioSafe { - val search = AbstractSubtitleEntities.SubtitleSearch( - query = query ?: return@ioSafe, - imdb = imdbId, - epNumber = currentTempMeta.episode, - seasonNumber = currentTempMeta.season, - lang = currentLanguageTwoLetters.ifBlank { null } - ) + val search = + AbstractSubtitleEntities.SubtitleSearch(query = query ?: return@ioSafe, + imdb = imdbId, + epNumber = currentTempMeta.episode, + seasonNumber = currentTempMeta.season, + lang = currentLanguageTwoLetters.ifBlank { null }) val results = providers.amap { try { it.search(search) @@ -379,14 +376,12 @@ class GeneratorPlayer : FullScreenPlayer() { dialog.search_filter.setOnClickListener { view -> val lang639_1 = languages.map { it.ISO_639_1 } - activity?.showDialog( - languages.map { it.languageName }, + activity?.showDialog(languages.map { it.languageName }, lang639_1.indexOf(currentLanguageTwoLetters), view?.context?.getString(R.string.subs_subtitle_languages) ?: return@setOnClickListener, true, - { } - ) { index -> + { }) { index -> currentLanguageTwoLetters = lang639_1[index] dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true) } @@ -472,8 +467,8 @@ class GeneratorPlayer : FullScreenPlayer() { if (uri == null) return@normalSafeApiCall val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall // RW perms for the path - val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION + val flags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ctx.contentResolver.takePersistableUriPermission(uri, flags) @@ -536,11 +531,9 @@ class GeneratorPlayer : FullScreenPlayer() { } if (subsProvidersIsActive) { - val loadFromOpenSubsFooter: TextView = - layoutInflater.inflate( - R.layout.sort_bottom_footer_add_choice, - null - ) as TextView + val loadFromOpenSubsFooter: TextView = layoutInflater.inflate( + R.layout.sort_bottom_footer_add_choice, null + ) as TextView loadFromOpenSubsFooter.text = ctx.getString(R.string.player_load_subtitles_online) @@ -592,8 +585,7 @@ class GeneratorPlayer : FullScreenPlayer() { val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1 var subtitleIndex = subtitleIndexStart - val subsArrayAdapter = - ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) + val subsArrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) subsArrayAdapter.add(ctx.getString(R.string.no_subtitles)) subsArrayAdapter.addAll(currentSubtitles.map { it.name }) @@ -631,8 +623,7 @@ class GeneratorPlayer : FullScreenPlayer() { val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values) val value = settingsManager.getString( - ctx.getString(R.string.subtitles_encoding_key), - null + ctx.getString(R.string.subtitles_encoding_key), null ) val index = prefValues.indexOf(value) 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 prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values) - val currentPrefMedia = - settingsManager.getString( - ctx.getString(R.string.subtitles_encoding_key), - null - ) + val currentPrefMedia = settingsManager.getString( + ctx.getString(R.string.subtitles_encoding_key), null + ) shouldDismiss = false sourceDialog.dismissSafe(activity) val index = prefValues.indexOf(currentPrefMedia) - activity?.showDialog( - prefNames.toList(), + activity?.showDialog(prefNames.toList(), if (index == -1) 0 else index, ctx.getString(R.string.subtitles_encoding), true, {}) { - settingsManager.edit() - .putString( - ctx.getString(R.string.subtitles_encoding_key), - prefValues[it] - ) - .apply() + settingsManager.edit().putString( + ctx.getString(R.string.subtitles_encoding_key), prefValues[it] + ).apply() updateForcedEncoding(ctx) dismiss() @@ -878,7 +863,7 @@ class GeneratorPlayer : FullScreenPlayer() { } var maxEpisodeSet: Int? = null - + var hasRequestedStamps: Boolean = false override fun playerPositionChanged(posDur: Pair) { // Don't save livestream data if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return @@ -887,11 +872,24 @@ class GeneratorPlayer : FullScreenPlayer() { if ((currentMeta as? ResultEpisode)?.tvType == TvType.NSFW) return 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 { DataStoreHelper.setViewPos(it, position, duration) } + val percentage = position * 100L / duration val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE @@ -939,17 +937,14 @@ class GeneratorPlayer : FullScreenPlayer() { context?.let { ctx -> val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) if (settingsManager.getBoolean( - ctx.getString(R.string.episode_sync_enabled_key), - true + ctx.getString(R.string.episode_sync_enabled_key), true ) - ) - maxEpisodeSet = meta.episode + ) maxEpisodeSet = meta.episode sync.modifyMaxEpisode(meta.episode) } } - if (meta.tvType.isAnimeOp()) - isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE + if (meta.tvType.isAnimeOp()) isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE } } player_skip_op?.isVisible = isOpVisible @@ -961,9 +956,7 @@ class GeneratorPlayer : FullScreenPlayer() { } private fun getAutoSelectSubtitle( - subtitles: Set, - settings: Boolean, - downloads: Boolean + subtitles: Set, settings: Boolean, downloads: Boolean ): SubtitleData? { val langCode = preferredAutoSelectSubtitles ?: return null val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null @@ -1009,23 +1002,20 @@ class GeneratorPlayer : FullScreenPlayer() { player.handleEvent(CSPlayerEvent.Play) return true } - } else - if (!langCode.isNullOrEmpty()) { - getAutoSelectSubtitle( - currentSubs, - settings = true, - downloads = false - )?.let { sub -> - - if (setSubtitles(sub)) { - player.saveData() - player.reloadPlayer(ctx) - player.handleEvent(CSPlayerEvent.Play) - return true - } + } else if (!langCode.isNullOrEmpty()) { + getAutoSelectSubtitle( + currentSubs, settings = true, downloads = false + )?.let { sub -> + if (setSubtitles(sub)) { + player.saveData() + player.reloadPlayer(ctx) + player.handleEvent(CSPlayerEvent.Play) + return true } + } + } } return false } @@ -1081,17 +1071,17 @@ class GeneratorPlayer : FullScreenPlayer() { context?.let { ctx -> //Generate video title val playerVideoTitle = if (headerName != null) { - (headerName + - if (tvType.isEpisodeBased() && episode != null) - if (season == null) - " - ${ctx.getString(R.string.episode)} $episode" - else - " \"${ctx.getString(R.string.season_short)}${season}:${ - ctx.getString( - R.string.episode_short - ) - }${episode}\"" - else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName" + (headerName + if (tvType.isEpisodeBased() && episode != null) if (season == null) " - ${ + ctx.getString( + R.string.episode + ) + } $episode" + else " \"${ctx.getString(R.string.season_short)}${season}:${ + ctx.getString( + R.string.episode_short + ) + }${episode}\"" + else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName" } else { "" } @@ -1131,8 +1121,7 @@ class GeneratorPlayer : FullScreenPlayer() { "" } - val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name - ?: "NULL" + val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL" player_video_title_rez?.text = when (titleRez) { 0 -> "" @@ -1155,14 +1144,11 @@ class GeneratorPlayer : FullScreenPlayer() { } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // 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() - layout = - if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player + layout = if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] sync = ViewModelProvider(this)[SyncViewModel::class.java] @@ -1174,6 +1160,68 @@ class GeneratorPlayer : FullScreenPlayer() { 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?) { super.onViewCreated(view, savedInstanceState) var langFilterList = listOf() @@ -1189,8 +1237,7 @@ class GeneratorPlayer : FullScreenPlayer() { settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false) if (filterSubByLang) { val langFromPrefMedia = settingsManager.getStringSet( - this.getString(R.string.provider_lang_key), - mutableSetOf("en") + this.getString(R.string.provider_lang_key), mutableSetOf("en") ) langFilterList = langFromPrefMedia?.mapNotNull { fromTwoLettersToLanguage(it)?.lowercase() ?: return@mapNotNull null @@ -1203,7 +1250,7 @@ class GeneratorPlayer : FullScreenPlayer() { sync.updateUserData() - preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1() + preferredAutoSelectSubtitles = getAutoSelectLanguageISO639_1() if (currentSelectedLink == null) { viewModel.loadLinks() @@ -1218,6 +1265,10 @@ class GeneratorPlayer : FullScreenPlayer() { activity?.popCurrentPage() } + observe(viewModel.currentStamps) { stamps -> + player.addTimeStamps(stamps) + } + observe(viewModel.loadingLinks) { when (it) { is Resource.Loading -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt index 473b3e65..ba5a4a85 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player import android.content.Context import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle +import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorUri @@ -12,9 +13,9 @@ enum class PlayerEventType(val value: Int) { SeekForward(2), SeekBack(3), - //SkipCurrentChapter(4), + SkipCurrentChapter(4), NextEpisode(5), - PrevEpisode(5), + PrevEpisode(6), PlayPauseToggle(7), ToggleMute(8), Lock(9), @@ -32,7 +33,7 @@ enum class CSPlayerEvent(val value: Int) { SeekForward(2), SeekBack(3), - //SkipCurrentChapter(4), + SkipCurrentChapter(4), NextEpisode(5), PrevEpisode(6), PlayPauseToggle(7), @@ -54,7 +55,8 @@ interface Track { **/ val id: String? val label: String? -// val isCurrentlyPlaying: Boolean + + // val isCurrentlyPlaying: Boolean val language: String? } @@ -124,6 +126,8 @@ interface IPlayer { subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way embeddedSubtitlesFetched: ((List) -> Unit)? = null, // callback from player to give all embedded subtitles 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() @@ -131,6 +135,8 @@ interface IPlayer { fun updateSubtitleStyle(style: SaveCaptionStyle) fun saveData() + fun addTimeStamps(timeStamps: List) + fun loadPlayer( context: Context, sameEpisode: Boolean, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 0ed26b71..4f16e9f6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -9,10 +9,12 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.normalSafeApiCall 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.ExtractorUri import kotlinx.coroutines.Job -import kotlinx.coroutines.launch class PlayerGeneratorViewModel : ViewModel() { companion object { @@ -30,6 +32,9 @@ class PlayerGeneratorViewModel : ViewModel() { private val _loadingLinks = MutableLiveData>() val loadingLinks: LiveData> = _loadingLinks + private val _currentStamps = MutableLiveData>(emptyList()) + val currentStamps: LiveData> = _currentStamps + fun getId(): Int? { return generator?.getCurrentId() } @@ -113,10 +118,31 @@ class PlayerGeneratorViewModel : ViewModel() { } 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) { Log.i(TAG, "loadLinks") currentJob?.cancel() + currentJob = viewModelScope.launchSafe { val currentLinks = mutableSetOf>() val currentSubs = mutableSetOf() @@ -142,5 +168,6 @@ class PlayerGeneratorViewModel : ViewModel() { _currentLinks.postValue(currentLinks) _currentSubs.postValue(currentSubs) } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt index d0ab245d..2ce53ea5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player import android.util.Log import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.ExtractorLink @@ -11,7 +12,8 @@ import kotlin.math.min class RepoLinkGenerator( private val episodes: List, - private var currentIndex: Int = 0 + private var currentIndex: Int = 0, + val page: LoadResponse? = null, ) : IGenerator { companion object { const val TAG = "RepoLink" diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt index 8a6e5b37..bf47209a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.result +import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration import android.graphics.Rect @@ -7,9 +8,11 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.core.view.isGone import androidx.core.view.isVisible import com.discord.panels.PanelsChildGestureRegionObserver import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.utils.IOnBackPressed 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.trailer_custom_layout.* + open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(), PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed { @@ -60,14 +64,43 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen result_smallscreen_holder?.isVisible = !isFullScreenPlayer result_fullscreen_holder?.isVisible = isFullScreenPlayer + val to = sw * h / w + player_background?.apply { isVisible = true layoutParams = FrameLayout.LayoutParams( 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 showTracksDialogue() {} - override fun openOnlineSubPicker(context: Context, imdbId: Long?, dismissCallback: () -> Unit) {} + override fun openOnlineSubPicker( + context: Context, + imdbId: Long?, + dismissCallback: () -> Unit + ) { + } override fun subtitlesChanged() {} @@ -124,6 +162,13 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen } updateFullscreen(isFullScreenPlayer) uiReset() + + player_intro_play?.setOnClickListener { + player_intro_play?.isGone = true + player.handleEvent(CSPlayerEvent.Play) + updateUIVisibility() + fixPlayerSize() + } } override fun onBackPressed(): Boolean { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 6b04ebf9..0c26f69c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -416,7 +416,7 @@ class ResultViewModel2 : ViewModel() { return this?.firstOrNull { it.season == season } } - fun updateWatchStatus(currentResponse : LoadResponse, status: WatchType) { + fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) { val currentId = currentResponse.getId() val resultPage = currentResponse @@ -793,7 +793,7 @@ class ResultViewModel2 : ViewModel() { fun updateWatchStatus(status: WatchType) { - updateWatchStatus(currentResponse ?: return,status) + updateWatchStatus(currentResponse ?: return, status) _watchStatus.postValue(status) } @@ -1681,10 +1681,10 @@ class ResultViewModel2 : ViewModel() { preferDubStatus = indexer.dubStatus generator = if (isMovie) { - getMovie()?.let { RepoLinkGenerator(listOf(it)) } + getMovie()?.let { RepoLinkGenerator(listOf(it), page = currentResponse) } } else { episodes?.let { list -> - RepoLinkGenerator(list) + RepoLinkGenerator(list, page = currentResponse) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index bcb36f7e..4da88af7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.search +import android.content.DialogInterface import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater @@ -10,6 +11,7 @@ import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.ListView +import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible 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.AcraApplication.Companion.getKey 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.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.Resource @@ -128,9 +131,9 @@ class SearchFragment : Fragment() { context?.let { ctx -> val default = enumValues().sorted().filter { it != TvType.NSFW } .map { it.ordinal.toString() }.toSet() - val preferredTypes = PreferenceManager.getDefaultSharedPreferences(ctx) - .getStringSet(this.getString(R.string.prefer_media_type_key), default) - ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } ?: default + val preferredTypes = (PreferenceManager.getDefaultSharedPreferences(ctx) + .getStringSet(this.getString(R.string.prefer_media_type_key), default)?.ifEmpty { default } ?: default) + .mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } val settings = ctx.getApiSettings() @@ -343,7 +346,7 @@ class SearchFragment : Fragment() { searchViewModel.updateHistory() } - search_history_recycler?.isVisible = showHistory + search_history_holder?.isVisible = showHistory search_master_recycler?.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 -> + search_clear_call_history?.isVisible = list.isNotEmpty() (search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 49f40879..fbf10499 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -200,7 +200,9 @@ class ExtensionsFragment : Fragment() { val url = dialog.repo_url_input?.text?.toString() ?.let { it1 -> RepositoryManager.parseRepoUrl(it1) } 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 { val fixedName = if (!name.isNullOrBlank()) name else RepositoryManager.parseRepository(url)?.name ?: "No name" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt new file mode 100644 index 00000000..e9b69c5b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt @@ -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>() + + 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 { + cachedStamps[episode.id]?.let { list -> + return list + } + + val out = mutableListOf() + 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>? { + 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() + 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?, + @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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 1a1afb68..24708e99 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -205,6 +205,8 @@ val extractorApis: MutableList = arrayListOf( VideovardSX(), Mp4Upload(), StreamTape(), + StreamTapeNet(), + ShaveTape(), //mixdrop extractors MixDropBz(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 20d6d03e..553860ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -328,7 +328,9 @@ object UIHelper { ) } - fun Context.fixPaddingStatusbarView(v: View) { + fun Context.fixPaddingStatusbarView(v: View?) { + if (v == null) return + val params = v.layoutParams params.height = getStatusBarHeight() v.layoutParams = params diff --git a/app/src/main/res/drawable/delete_all.xml b/app/src/main/res/drawable/delete_all.xml new file mode 100644 index 00000000..a5e8775c --- /dev/null +++ b/app/src/main/res/drawable/delete_all.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 4eb7b9ee..e6270a77 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -405,7 +405,11 @@ - + + + android:id="@+id/player_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + app:auto_show="true" + app:backgroundTint="@android:color/black" + app:controller_layout_id="@layout/player_custom_layout" + app:hide_on_touch="false" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:show_timeout="0" /> + android:id="@+id/player_loading_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + android:backgroundTint="@android:color/black" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:text="@string/skip_loading" + android:textAllCaps="false" + android:textColor="?attr/textColor" + android:visibility="gone" + app:cornerRadius="4dp" + app:icon="@drawable/ic_baseline_skip_next_24" + app:iconTint="?attr/textColor" + app:rippleColor="?attr/colorPrimary" + tools:visibility="visible" /> + android:id="@+id/main_load" + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_gravity="center" /> + android:id="@+id/video_go_back_holder_holder" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="5dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:layout_width="30dp" + android:layout_height="30dp" + android:layout_gravity="center" + android:contentDescription="@string/go_back_img_des" + android:src="@drawable/ic_baseline_arrow_back_24" + app:tint="@android:color/white" /> + android:id="@+id/player_loading_go_back" + android:layout_width="70dp" + android:layout_height="70dp" + android:layout_gravity="center" + android:background="@drawable/video_tap_button_always_white" + android:clickable="true" + android:contentDescription="@string/go_back_img_des" + android:focusable="true" /> + + + 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" /> - - + android:id="@+id/video_torrent_seeders" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="0dp" + android:gravity="start" + android:textColor="@color/white" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/player_video_title" + tools:text="17 seeders" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index ed2ec6bd..f88fc173 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -267,10 +267,10 @@ --> - + - - + + + @@ -330,6 +332,7 @@ android:contentDescription="@string/result_poster_img_des" android:foreground="@drawable/outline_drawable" android:scaleType="centerCrop" + android:layout_gravity="bottom" tools:src="@drawable/example_poster" /> diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 083cd64e..6df5837f 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -112,6 +112,11 @@ android:nextFocusLeft="@id/nav_rail_view" android:visibility="gone" tools:listitem="@layout/homepage_parent" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_trailer.xml b/app/src/main/res/layout/fragment_trailer.xml index fd348760..ff477118 100644 --- a/app/src/main/res/layout/fragment_trailer.xml +++ b/app/src/main/res/layout/fragment_trailer.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="0dp" + android:layout_height="match_parent" android:visibility="visible" android:orientation="horizontal" android:id="@+id/player_background" diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 9bbded4e..2cdcbf22 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -318,6 +318,25 @@ + + 投屏 浏览器 未找到应用 + 所有语言 + + 跳过 %s + 片头 + 片尾 + 前情回顾 + 混合片尾 + 混合片头 + 致谢名单 + 介绍 + + 清除历史记录 + 历史记录 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8b3d0fbf..c3e51ab5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -41,6 +41,7 @@ #66000000 #C0121212 + #4D121212 #121212 #66B5B5B5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb1e954a..70b21b28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,7 +28,7 @@ pip_enabled_key double_tap_enabled_key double_tap_pause_enabled_key - double_tap_seek_time_key + double_tap_seek_time_key2 swipe_vertical_enabled_key autoplay_next_key display_sub_key @@ -58,6 +58,7 @@ pref_filter_search_quality_key enable_nsfw_on_providers_key automatic_cloud_backups + enable_skip_op_from_database %d %s | %s @@ -644,4 +645,18 @@ Browser App not found All Languages + + Skip %s + Opening + Ending + Recap + Mixed ending + Mixed opening + Credits + Intro + + Clear history + History + Show skip popups for opening/ending + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 13089cc2..9c2f62fc 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -83,6 +83,7 @@ @color/chip_color_text @color/chip_color_text @font/google_sans + @color/chip_color_text @font/google_sans diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml index 6946a5c9..0be6313f 100644 --- a/app/src/main/res/xml/settings_player.xml +++ b/app/src/main/res/xml/settings_player.xml @@ -1,126 +1,132 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:icon="@drawable/ic_outline_subtitles_24" + android:key="@string/subtitle_settings_key" + android:title="@string/player_subtitles_settings" + app:summary="@string/player_subtitles_settings_des" /> + android:icon="@drawable/ic_outline_subtitles_24" + android:key="@string/subtitle_settings_chromecast_key" + android:title="@string/chromecast_subtitles_settings" + app:summary="@string/chromecast_subtitles_settings_des" /> + android:icon="@drawable/ic_baseline_hd_24" + android:key="@string/quality_pref_key" + android:title="@string/watch_quality_pref" /> + android:icon="@drawable/netflix_play" + android:key="@string/player_pref_key" + android:title="@string/player_pref" /> + android:icon="@drawable/ic_baseline_text_format_24" + android:key="@string/prefer_limit_title_key" + android:title="@string/limit_title" /> + android:icon="@drawable/ic_baseline_text_format_24" + android:key="@string/prefer_limit_title_rez_key" + android:title="@string/limit_title_rez" /> + android:icon="@drawable/ic_baseline_picture_in_picture_alt_24" + android:summary="@string/picture_in_picture_des" + android:title="@string/picture_in_picture" + app:defaultValue="true" + app:key="@string/pip_enabled_key" /> + android:icon="@drawable/ic_baseline_aspect_ratio_24" + android:summary="@string/player_size_settings_des" + android:title="@string/player_size_settings" + app:defaultValue="true" + app:key="@string/player_resize_enabled_key" /> + android:icon="@drawable/ic_baseline_speed_24" + android:summary="@string/eigengraumode_settings_des" + android:title="@string/eigengraumode_settings" + app:defaultValue="false" + app:key="@string/playback_speed_enabled_key" /> + android:icon="@drawable/ic_baseline_ondemand_video_24" + android:summary="@string/swipe_to_seek_settings_des" + android:title="@string/swipe_to_seek_settings" + app:defaultValue="true" + app:key="@string/swipe_enabled_key" /> - + android:icon="@drawable/ic_baseline_ondemand_video_24" + android:summary="@string/swipe_to_change_settings_des" + android:title="@string/swipe_to_change_settings" + app:defaultValue="true" + app:key="@string/swipe_vertical_enabled_key" /> + android:icon="@drawable/ic_baseline_skip_next_24" + android:summary="@string/autoplay_next_settings_des" + android:title="@string/autoplay_next_settings" + app:defaultValue="true" + app:key="@string/autoplay_next_key" /> + android:icon="@drawable/ic_baseline_skip_next_24" + android:title="@string/video_skip_op" + app:defaultValue="true" + android:summary="@string/enable_skip_op_from_database_des" + app:key="@string/enable_skip_op_from_database" /> + + + android:defaultValue="10" + android:max="60" + android:title="@string/double_tap_to_seek_amount_settings" + app:adjustable="true" + app:defaultValue="10" + app:icon="@drawable/go_forward_30" + app:key="@string/double_tap_seek_time_key" + app:min="5" + app:seekBarIncrement="5" + app:showSeekBarValue="true" /> + android:icon="@drawable/baseline_sync_24" + android:summary="@string/episode_sync_settings_des" + android:title="@string/episode_sync_settings" + app:defaultValue="true" + app:key="@string/episode_sync_enabled_key" /> + android:icon="@drawable/ic_baseline_storage_24" + android:key="@string/video_buffer_disk_key" + android:summary="@string/video_disk_description" + android:title="@string/video_buffer_disk_settings" /> + android:icon="@drawable/ic_baseline_storage_24" + android:key="@string/video_buffer_size_key" + android:summary="@string/video_ram_description" + android:title="@string/video_buffer_size_settings" /> + android:icon="@drawable/ic_baseline_storage_24" + android:key="@string/video_buffer_length_key" + android:summary="@string/video_ram_description" + android:title="@string/video_buffer_length_settings" /> + android:icon="@drawable/ic_baseline_delete_outline_24" + android:key="@string/video_buffer_clear_key" + android:title="@string/video_buffer_clear_settings" /> \ No newline at end of file