This commit is contained in:
Blatzar 2022-04-02 19:50:16 +02:00
parent 3be125f12a
commit ed573f4f22
5 changed files with 293 additions and 203 deletions

View file

@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
import com.lagradost.cloudstream3.movieproviders.* import com.lagradost.cloudstream3.movieproviders.*
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import okhttp3.Interceptor
import java.util.* import java.util.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -394,6 +395,11 @@ abstract class MainAPI {
): Boolean { ): Boolean {
throw NotImplementedError() throw NotImplementedError()
} }
/** An okhttp interceptor for used in OkHttpDataSource */
open fun getVideoInterceptor(extractorLink: ExtractorLink) : Interceptor? {
return null
}
} }
/** Might need a different implementation for desktop*/ /** Might need a different implementation for desktop*/

View file

@ -1,22 +1,23 @@
package com.lagradost.cloudstream3.animeproviders package com.lagradost.cloudstream3.animeproviders
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.util.NameTransformer
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
import com.lagradost.cloudstream3.movieproviders.SflixProvider
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toExtractorLink import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.runSflixExtractorVerifierJob
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toSubtitleFile import com.lagradost.cloudstream3.network.Requests.Companion.await
import com.lagradost.cloudstream3.network.WebViewResolver import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor import com.lagradost.cloudstream3.utils.loadExtractor
import okhttp3.Interceptor
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.net.URI import java.net.URI
import java.util.* import java.util.*
private const val OPTIONS = "OPTIONS"
class ZoroProvider : MainAPI() { class ZoroProvider : MainAPI() {
override var mainUrl = "https://zoro.to" override var mainUrl = "https://zoro.to"
override var name = "Zoro" override var name = "Zoro"
@ -285,26 +286,48 @@ class ZoroProvider : MainAPI() {
} }
} }
private suspend fun getM3u8FromRapidCloud(url: String): String {
return /*Regex("""/(embed-\d+)/(.*?)\?z=""").find(url)?.groupValues?.let {
val jsonLink = "https://rapid-cloud.ru/ajax/${it[1]}/getSources?id=${it[2]}"
app.get(jsonLink).text
} ?:*/ app.get(
"$url&autoPlay=1&oa=0",
headers = mapOf(
"Referer" to "https://zoro.to/",
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0"
),
interceptor = WebViewResolver(
Regex("""/getSources""")
)
).text
}
private data class RapidCloudResponse( private data class RapidCloudResponse(
@JsonProperty("link") val link: String @JsonProperty("link") val link: String
) )
override suspend fun extractorVerifierJob(extractorData: String?) {
Log.d(this.name, "Starting ${this.name} job!")
runSflixExtractorVerifierJob(this, extractorData, "https://rapid-cloud.ru/")
}
/** Url hashcode to sid */
var sid: HashMap<Int, String?> = hashMapOf()
/**
* Makes an identical Options request before .ts request
* Adds an SID header to the .ts request.
* */
override fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor {
return Interceptor { chain ->
val request = chain.request()
if (request.url.toString().endsWith(".ts")
&& request.method != OPTIONS
// No option requests on VidCloud
&& !request.url.toString().contains("betterstream")
) {
val newRequest =
chain.request()
.newBuilder().apply {
sid[extractorLink.url.hashCode()]?.let { sid ->
addHeader("SID", sid)
}
}
.build()
val options = request.newBuilder().method(OPTIONS, request.body).build()
ioSafe { app.baseClient.newCall(options).await() }
return@Interceptor chain.proceed(newRequest)
} else {
return@Interceptor chain.proceed(chain.request())
}
}
}
override suspend fun loadLinks( override suspend fun loadLinks(
data: String, data: String,
isCasting: Boolean, isCasting: Boolean,
@ -322,6 +345,8 @@ class ZoroProvider : MainAPI() {
) )
} }
val extractorData =
"https://ws1.rapid-cloud.ru/socket.io/?EIO=4&transport=polling"
// Prevent duplicates // Prevent duplicates
servers.distinctBy { it.second }.apmap { servers.distinctBy { it.second }.apmap {
@ -330,11 +355,18 @@ class ZoroProvider : MainAPI() {
val extractorLink = app.get( val extractorLink = app.get(
link, link,
).mapped<RapidCloudResponse>().link ).mapped<RapidCloudResponse>().link
val hasLoadedExtractorLink = loadExtractor(extractorLink, mainUrl, callback) val hasLoadedExtractorLink =
loadExtractor(extractorLink, "https://rapid-cloud.ru/", callback)
if (!hasLoadedExtractorLink) { if (!hasLoadedExtractorLink) {
extractRabbitStream(extractorLink, subtitleCallback, callback) { sourceName -> extractRabbitStream(
sourceName + " - ${it.first}" extractorLink,
subtitleCallback,
// Blacklist VidCloud for now
{ videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) },
extractorData
) { sourceName ->
sourceName + " - ${it.first}"
} }
} }
} }

View file

@ -7,11 +7,13 @@ import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
import com.lagradost.cloudstream3.animeproviders.ZoroProvider
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.network.AppResponse import com.lagradost.cloudstream3.network.AppResponse
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
@ -357,182 +359,24 @@ open class SflixProvider : MainAPI() {
return !urls.isNullOrEmpty() return !urls.isNullOrEmpty()
} }
data class PollingData(
@JsonProperty("sid") val sid: String? = null,
@JsonProperty("upgrades") val upgrades: ArrayList<String> = arrayListOf(),
@JsonProperty("pingInterval") val pingInterval: Int? = null,
@JsonProperty("pingTimeout") val pingTimeout: Int? = null
)
/*
# python code to figure out the time offset based on code if necessary
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
code = "Nxa_-bM"
total = 0
for i, char in enumerate(code[::-1]):
index = chars.index(char)
value = index * 64**i
total += value
print(f"total {total}")
*/
private fun generateTimeStamp(): String {
val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
var code = ""
var time = unixTimeMS
while (time > 0) {
code += chars[(time % (chars.length)).toInt()]
time /= chars.length
}
return code.reversed()
}
/**
* Generates a session
* */
private suspend fun negotiateNewSid(baseUrl: String): PollingData? {
// Tries multiple times
for (i in 1..5) {
val jsonText =
app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore("{", "")
// println("Negotiated sid $jsonText")
parseJson<PollingData?>(jsonText)?.let { return it }
delay(1000L * i)
}
return null
}
/**
* Generates a new session if the request fails
* @return the data and if it is new.
* */
private suspend fun getUpdatedData(
response: AppResponse,
data: PollingData,
baseUrl: String
): Pair<PollingData, Boolean> {
if (!response.response.isSuccessful) {
return negotiateNewSid(baseUrl)?.let {
it to true
} ?: data to false
}
return data to false
}
override suspend fun extractorVerifierJob(extractorData: String?) { override suspend fun extractorVerifierJob(extractorData: String?) {
if (extractorData == null) return runSflixExtractorVerifierJob(this, extractorData, "https://rabbitstream.net/")
val headers = mapOf(
"Referer" to "https://rabbitstream.net/"
)
var data = negotiateNewSid(extractorData) ?: return
// 40 is hardcoded, dunno how it's generated, but it seems to work everywhere.
// This request is obligatory
app.post(
"$extractorData&t=${generateTimeStamp()}&sid=${data.sid}",
data = 40, headers = headers
)//.also { println("First post ${it.text}") }
// This makes the second get request work, and re-connect work.
val reconnectSid =
parseJson<PollingData>(
app.get(
"$extractorData&t=${generateTimeStamp()}&sid=${data.sid}",
headers = headers
)
// .also { println("First get ${it.text}") }
.text.replaceBefore("{", "")
).sid
// This response is used in the post requests. Same contents in all it seems.
val authInt =
app.get(
"$extractorData&t=${generateTimeStamp()}&sid=${data.sid}",
timeout = 60,
headers = headers
).text
//.also { println("Second get ${it}") }
// Dunno if it's actually generated like this, just guessing.
.toIntOrNull()?.plus(1) ?: 3
// Prevents them from fucking us over with doing a while(true){} loop
val interval = maxOf(data.pingInterval?.toLong()?.plus(2000) ?: return, 10000L)
var reconnect = false
var newAuth = false
while (true) {
val authData =
when {
newAuth -> "40"
reconnect -> """42["_reconnect", "$reconnectSid"]"""
else -> authInt
}
val url = "${extractorData}&t=${generateTimeStamp()}&sid=${data.sid}"
getUpdatedData(
app.post(url, data = authData, headers = headers),
data,
extractorData
).also {
newAuth = it.second
data = it.first
}
//.also { println("Sflix post job ${it.text}") }
Log.d(this.name, "Running ${this.name} job $url")
val time = measureTimeMillis {
// This acts as a timeout
val getResponse = app.get(
"${extractorData}&t=${generateTimeStamp()}&sid=${data.sid}",
timeout = 60,
headers = headers
)
// .also { println("Sflix get job ${it.text}") }
if (getResponse.text.contains("sid")) {
reconnect = true
// println("Reconnecting")
}
}
// Always waits even if the get response is instant, to prevent a while true loop.
if (time < interval - 4000)
delay(4000)
}
} }
private fun Element.toSearchResult(): SearchResponse { private fun Element.toSearchResult(): SearchResponse {
val inner = this.selectFirst("div.film-poster") val img = this.select("img")
val img = inner.select("img")
val title = img.attr("title") val title = img.attr("title")
val posterUrl = img.attr("data-src") ?: img.attr("src") val posterUrl = img.attr("data-src")
val href = fixUrl(inner.select("a").attr("href")) val href = fixUrl(this.select("a").attr("href"))
val isMovie = href.contains("/movie/") val isMovie = href.contains("/movie/")
val otherInfo = this.selectFirst("div.film-detail > div.fd-infor")?.select("span")?.toList() ?: listOf()
var rating: Int? = null
var year: Int? = null
var quality: SearchQuality? = null
when (otherInfo.size) {
1 -> {
year = otherInfo[0]?.text()?.trim()?.toIntOrNull()
}
2 -> {
year = otherInfo[0]?.text()?.trim()?.toIntOrNull()
}
3 -> {
rating = otherInfo[0]?.text()?.toRatingInt()
quality = getQualityFromString(otherInfo[1]?.text())
year = otherInfo[2]?.text()?.trim()?.toIntOrNull()
}
}
return if (isMovie) { return if (isMovie) {
MovieSearchResponse( MovieSearchResponse(
title, title,
href, href,
this@SflixProvider.name, this@SflixProvider.name,
TvType.Movie, TvType.Movie,
posterUrl = posterUrl, posterUrl,
year = year, null
quality = quality
) )
} else { } else {
TvSeriesSearchResponse( TvSeriesSearchResponse(
@ -541,14 +385,179 @@ open class SflixProvider : MainAPI() {
this@SflixProvider.name, this@SflixProvider.name,
TvType.Movie, TvType.Movie,
posterUrl, posterUrl,
year = year, null,
episodes = null, null
quality = quality
) )
} }
} }
companion object { companion object {
data class PollingData(
@JsonProperty("sid") val sid: String? = null,
@JsonProperty("upgrades") val upgrades: ArrayList<String> = arrayListOf(),
@JsonProperty("pingInterval") val pingInterval: Int? = null,
@JsonProperty("pingTimeout") val pingTimeout: Int? = null
)
/*
# python code to figure out the time offset based on code if necessary
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
code = "Nxa_-bM"
total = 0
for i, char in enumerate(code[::-1]):
index = chars.index(char)
value = index * 64**i
total += value
print(f"total {total}")
*/
private fun generateTimeStamp(): String {
val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
var code = ""
var time = unixTimeMS
while (time > 0) {
code += chars[(time % (chars.length)).toInt()]
time /= chars.length
}
return code.reversed()
}
/**
* Generates a session
* 1 Get request.
* */
private suspend fun negotiateNewSid(baseUrl: String): PollingData? {
// Tries multiple times
for (i in 1..5) {
val jsonText =
app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore("{", "")
// println("Negotiated sid $jsonText")
parseJson<PollingData?>(jsonText)?.let { return it }
delay(1000L * i)
}
return null
}
/**
* Generates a new session if the request fails
* @return the data and if it is new.
* */
private suspend fun getUpdatedData(
response: AppResponse,
data: PollingData,
baseUrl: String
): Pair<PollingData, Boolean> {
if (!response.response.isSuccessful) {
return negotiateNewSid(baseUrl)?.let {
it to true
} ?: data to false
}
return data to false
}
private suspend fun initPolling(
extractorData: String,
referer: String
): Pair<PollingData?, String?> {
val headers = mapOf(
"Referer" to referer // "https://rabbitstream.net/"
)
val data = negotiateNewSid(extractorData) ?: return null to null
app.post(
"$extractorData&t=${generateTimeStamp()}&sid=${data.sid}",
data = 40, headers = headers
)
// This makes the second get request work, and re-connect work.
val reconnectSid =
parseJson<PollingData>(
app.get(
"$extractorData&t=${generateTimeStamp()}&sid=${data.sid}",
headers = headers
)
// .also { println("First get ${it.text}") }
.text.replaceBefore("{", "")
).sid
// This response is used in the post requests. Same contents in all it seems.
val authInt =
app.get(
"$extractorData&t=${generateTimeStamp()}&sid=${data.sid}",
timeout = 60,
headers = headers
).text
//.also { println("Second get ${it}") }
// Dunno if it's actually generated like this, just guessing.
.toIntOrNull()?.plus(1) ?: 3
return data to reconnectSid
}
suspend fun runSflixExtractorVerifierJob(
api: MainAPI,
extractorData: String?,
referer: String
) {
if (extractorData == null) return
val headers = mapOf(
"Referer" to referer // "https://rabbitstream.net/"
)
lateinit var data: PollingData
var reconnectSid = ""
initPolling(extractorData, referer)
.also {
data = it.first ?: throw RuntimeException("Data Null")
reconnectSid = it.second ?: throw RuntimeException("ReconnectSid Null")
}
// Prevents them from fucking us over with doing a while(true){} loop
val interval = maxOf(data.pingInterval?.toLong()?.plus(2000) ?: return, 10000L)
var reconnect = false
var newAuth = false
while (true) {
val authData =
when {
newAuth -> "40"
reconnect -> """42["_reconnect", "$reconnectSid"]"""
else -> "3"
}
val url = "${extractorData}&t=${generateTimeStamp()}&sid=${data.sid}"
getUpdatedData(
app.post(url, data = authData, headers = headers),
data,
extractorData
).also {
newAuth = it.second
data = it.first
}
//.also { println("Sflix post job ${it.text}") }
Log.d(api.name, "Running ${api.name} job $url")
val time = measureTimeMillis {
// This acts as a timeout
val getResponse = app.get(
url,
timeout = interval / 1000,
headers = headers
)
// .also { println("Sflix get job ${it.text}") }
reconnect = getResponse.text.contains("sid")
}
// Always waits even if the get response is instant, to prevent a while true loop.
if (time < interval - 4000)
delay(4000)
}
}
fun String?.isValidServer(): Boolean { fun String?.isValidServer(): Boolean {
if (this.isNullOrEmpty()) return false if (this.isNullOrEmpty()) return false
if (this.equals("UpCloud", ignoreCase = true) || this.equals( if (this.equals("UpCloud", ignoreCase = true) || this.equals(
@ -563,7 +572,7 @@ open class SflixProvider : MainAPI() {
fun Sources.toExtractorLink( fun Sources.toExtractorLink(
caller: MainAPI, caller: MainAPI,
name: String, name: String,
extractorData: String? = null extractorData: String? = null,
): List<ExtractorLink>? { ): List<ExtractorLink>? {
return this.file?.let { file -> return this.file?.let { file ->
//println("FILE::: $file") //println("FILE::: $file")
@ -632,13 +641,30 @@ open class SflixProvider : MainAPI() {
val number = val number =
Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1) Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1)
var sid: String? = null
extractorData?.let { negotiateNewSid(it) }?.also {
app.post(
"$extractorData&t=${generateTimeStamp()}&sid=${it.sid}",
data = "40",
timeout = 60
)
val text = app.get(
"$extractorData&t=${generateTimeStamp()}&sid=${it.sid}",
timeout = 60
).text.replaceBefore("{", "")
sid = parseJson<PollingData>(text).sid
ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${it.sid}") }
}
val mapped = app.get( val mapped = app.get(
"${ "${
mainIframeUrl.replace( mainIframeUrl.replace(
"/embed", "/embed",
"/ajax/embed" "/ajax/embed"
) )
}/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number", }/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number${sid?.let { "&sid=$it" } ?: ""}",
referer = mainUrl, referer = mainUrl,
headers = mapOf( headers = mapOf(
"X-Requested-With" to "XMLHttpRequest", "X-Requested-With" to "XMLHttpRequest",
@ -667,11 +693,18 @@ open class SflixProvider : MainAPI() {
mapped.sources2 to "source 3", mapped.sources2 to "source 3",
mapped.sourcesBackup to "source backup" mapped.sourcesBackup to "source backup"
) )
list.forEach { subList -> list.forEach { subList ->
subList.first?.forEach { source -> subList.first?.forEach { source ->
source?.toExtractorLink(this, nameTransformer(subList.second), extractorData) source?.toExtractorLink(
?.forEach(callback) this,
nameTransformer(subList.second),
extractorData,
)
?.forEach {
// Sets Zoro SID used for video loading
(this as? ZoroProvider)?.sid?.set(it.url.hashCode(), sid)
callback(it)
}
} }
} }
} }

View file

@ -19,7 +19,7 @@ class APIRepository(val api: MainAPI) {
override val supportedTypes = emptySet<TvType>() override val supportedTypes = emptySet<TvType>()
} }
fun isInvalidData(data : String): Boolean { fun isInvalidData(data: String): Boolean {
return data.isEmpty() || data == "[]" || data == "about:blank" return data.isEmpty() || data == "[]" || data == "about:blank"
} }
} }
@ -30,7 +30,7 @@ class APIRepository(val api: MainAPI) {
val hasQuickSearch = api.hasQuickSearch val hasQuickSearch = api.hasQuickSearch
suspend fun load(url: String): Resource<LoadResponse> { suspend fun load(url: String): Resource<LoadResponse> {
if(isInvalidData(url)) throw ErrorLoadingException() if (isInvalidData(url)) throw ErrorLoadingException()
return safeApiCall { return safeApiCall {
api.load(api.fixUrl(url)) ?: throw ErrorLoadingException() api.load(api.fixUrl(url)) ?: throw ErrorLoadingException()
@ -64,6 +64,12 @@ class APIRepository(val api: MainAPI) {
} }
} }
suspend fun extractorVerifierJob(extractorData: String?) {
safeApiCall {
api.extractorVerifierJob(extractorData)
}
}
suspend fun loadLinks( suspend fun loadLinks(
data: String, data: String,
isCasting: Boolean, isCasting: Boolean,

View file

@ -8,6 +8,7 @@ import android.util.Log
import android.widget.FrameLayout import android.widget.FrameLayout
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.MergingMediaSource import com.google.android.exoplayer2.source.MergingMediaSource
import com.google.android.exoplayer2.source.SingleSampleMediaSource import com.google.android.exoplayer2.source.SingleSampleMediaSource
@ -17,12 +18,13 @@ 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.DataSource import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
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
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -307,8 +309,19 @@ class CS3IPlayer : IPlayer {
var requestSubtitleUpdate: (() -> Unit)? = null var requestSubtitleUpdate: (() -> Unit)? = null
private fun createOnlineSource(link: ExtractorLink): DataSource.Factory { private fun createOnlineSource(link: ExtractorLink): DataSource.Factory {
// Because Trailers.to seems to fail with http/1.1 the normal one uses. val provider = getApiFromName(link.source)
return DefaultHttpDataSource.Factory().apply { val interceptor = provider.getVideoInterceptor(link)
val client = app.baseClient
.let {
if (interceptor != null)
it.newBuilder()
.addInterceptor(interceptor)
.build()
else it
}
return OkHttpDataSource.Factory(client).apply {
setUserAgent(USER_AGENT) setUserAgent(USER_AGENT)
val headers = mapOf( val headers = mapOf(
"referer" to link.referer, "referer" to link.referer,
@ -322,7 +335,7 @@ class CS3IPlayer : IPlayer {
setDefaultRequestProperties(headers) setDefaultRequestProperties(headers)
//https://stackoverflow.com/questions/69040127/error-code-io-bad-http-status-exoplayer-android //https://stackoverflow.com/questions/69040127/error-code-io-bad-http-status-exoplayer-android
setAllowCrossProtocolRedirects(true) // setAllowCrossProtocolRedirects(true)
} }
} }