forked from recloudstream/cloudstream
switched yt implementation to newpipe
This commit is contained in:
parent
f64b2f7f1b
commit
3d00015a0c
12 changed files with 240 additions and 106 deletions
|
@ -74,6 +74,8 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
@ -143,7 +145,7 @@ dependencies {
|
||||||
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||||
|
|
||||||
//run JS
|
//run JS
|
||||||
implementation 'org.mozilla:rhino:1.7R4'
|
implementation 'org.mozilla:rhino:1.7.14'
|
||||||
|
|
||||||
// TorrentStream
|
// TorrentStream
|
||||||
//implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
//implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
|
||||||
|
@ -175,6 +177,11 @@ dependencies {
|
||||||
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
||||||
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
|
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
|
||||||
|
|
||||||
// play yt
|
// slow af yt
|
||||||
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
|
//implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
|
||||||
|
|
||||||
|
// newpipe yt
|
||||||
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:master-SNAPSHOT'
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Request
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Response
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Downloader() {
|
||||||
|
private val client: OkHttpClient
|
||||||
|
override fun execute(request: Request): Response {
|
||||||
|
val httpMethod: String = request.httpMethod()
|
||||||
|
val url: String = request.url()
|
||||||
|
val headers: Map<String, List<String>> = request.headers()
|
||||||
|
val dataToSend: ByteArray? = request.dataToSend()
|
||||||
|
var requestBody: RequestBody? = null
|
||||||
|
if (dataToSend != null) {
|
||||||
|
requestBody = RequestBody.create(null, dataToSend)
|
||||||
|
}
|
||||||
|
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
|
||||||
|
.method(httpMethod, requestBody).url(url)
|
||||||
|
.addHeader("User-Agent", USER_AGENT)
|
||||||
|
|
||||||
|
for ((headerName, headerValueList) in headers) {
|
||||||
|
if (headerValueList.size > 1) {
|
||||||
|
requestBuilder.removeHeader(headerName)
|
||||||
|
for (headerValue in headerValueList) {
|
||||||
|
requestBuilder.addHeader(headerName, headerValue)
|
||||||
|
}
|
||||||
|
} else if (headerValueList.size == 1) {
|
||||||
|
requestBuilder.header(headerName, headerValueList[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val response = client.newCall(requestBuilder.build()).execute()
|
||||||
|
if (response.code == 429) {
|
||||||
|
response.close()
|
||||||
|
throw ReCaptchaException("reCaptcha Challenge requested", url)
|
||||||
|
}
|
||||||
|
val body = response.body
|
||||||
|
var responseBodyToReturn: String? = null
|
||||||
|
if (body != null) {
|
||||||
|
responseBodyToReturn = body.string()
|
||||||
|
}
|
||||||
|
val latestUrl = response.request.url.toString()
|
||||||
|
return Response(
|
||||||
|
response.code, response.message, response.headers.toMultimap(),
|
||||||
|
responseBodyToReturn, latestUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val USER_AGENT =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"
|
||||||
|
private var instance: DownloaderTestImpl? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's recommended to call exactly once in the entire lifetime of the application.
|
||||||
|
*
|
||||||
|
* @param builder if null, default builder will be used
|
||||||
|
* @return a new instance of [DownloaderTestImpl]
|
||||||
|
*/
|
||||||
|
fun init(builder: OkHttpClient.Builder?): DownloaderTestImpl? {
|
||||||
|
instance = DownloaderTestImpl(
|
||||||
|
builder ?: OkHttpClient.Builder()
|
||||||
|
)
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(): DownloaderTestImpl? {
|
||||||
|
if (instance == null) {
|
||||||
|
init(null)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
client = builder.readTimeout(30, TimeUnit.SECONDS).build()
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -854,7 +855,7 @@ interface LoadResponse {
|
||||||
var rating: Int? // 0-10000
|
var rating: Int? // 0-10000
|
||||||
var tags: List<String>?
|
var tags: List<String>?
|
||||||
var duration: Int? // in minutes
|
var duration: Int? // in minutes
|
||||||
var trailers: List<String>?
|
var trailers: List<ExtractorLink>?
|
||||||
var recommendations: List<SearchResponse>?
|
var recommendations: List<SearchResponse>?
|
||||||
var actors: List<ActorData>?
|
var actors: List<ActorData>?
|
||||||
var comingSoon: Boolean
|
var comingSoon: Boolean
|
||||||
|
@ -897,27 +898,34 @@ interface LoadResponse {
|
||||||
addImdbId(imdbUrlToIdNullable(url))
|
addImdbId(imdbUrlToIdNullable(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**better to set trailers directly instead of calling this multiple times*/
|
/**better to call addTrailer with mutible trailers directly instead of calling this multiple times*/
|
||||||
fun LoadResponse.addTrailer(trailerUrl: String?) {
|
suspend fun LoadResponse.addTrailer(trailerUrl: String?, referer: String? = null) {
|
||||||
if (trailerUrl == null) return
|
if (trailerUrl == null) return
|
||||||
|
val newTrailers = loadExtractor(trailerUrl, referer)
|
||||||
|
addTrailer(newTrailers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LoadResponse.addTrailer(newTrailers: List<ExtractorLink>) {
|
||||||
if (this.trailers == null) {
|
if (this.trailers == null) {
|
||||||
this.trailers = listOf(trailerUrl)
|
this.trailers = newTrailers
|
||||||
} else {
|
} else {
|
||||||
val update = this.trailers?.toMutableList()
|
val update = this.trailers?.toMutableList() ?: mutableListOf()
|
||||||
update?.add(trailerUrl)
|
update.addAll(newTrailers)
|
||||||
this.trailers = update
|
this.trailers = update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addTrailer(trailerUrls: List<String>?) {
|
suspend fun LoadResponse.addTrailer(trailerUrls: List<String>?, referer: String? = null) {
|
||||||
if (trailerUrls == null) return
|
if (trailerUrls == null) return
|
||||||
if (this.trailers == null) {
|
val newTrailers = trailerUrls.apmap { trailerUrl ->
|
||||||
this.trailers = trailerUrls
|
try {
|
||||||
} else {
|
loadExtractor(trailerUrl, referer)
|
||||||
val update = this.trailers?.toMutableList()
|
} catch (e: Exception) {
|
||||||
update?.addAll(trailerUrls)
|
logError(e)
|
||||||
this.trailers = update
|
emptyList()
|
||||||
}
|
}
|
||||||
|
}.flatten().distinct()
|
||||||
|
addTrailer(newTrailers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addImdbId(id: String?) {
|
fun LoadResponse.addImdbId(id: String?) {
|
||||||
|
@ -997,7 +1005,7 @@ data class TorrentLoadResponse(
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var tags: List<String>? = null,
|
override var tags: List<String>? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1025,7 +1033,7 @@ data class AnimeLoadResponse(
|
||||||
|
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1038,12 +1046,12 @@ fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<Episode>?) {
|
||||||
this.episodes[status] = episodes
|
this.episodes[status] = episodes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MainAPI.newAnimeLoadResponse(
|
suspend fun MainAPI.newAnimeLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
comingSoonIfNone: Boolean = true,
|
comingSoonIfNone: Boolean = true,
|
||||||
initializer: AnimeLoadResponse.() -> Unit = { },
|
initializer: suspend AnimeLoadResponse.() -> Unit = { },
|
||||||
): AnimeLoadResponse {
|
): AnimeLoadResponse {
|
||||||
val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type)
|
val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type)
|
||||||
builder.initializer()
|
builder.initializer()
|
||||||
|
@ -1072,7 +1080,7 @@ data class MovieLoadResponse(
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var tags: List<String>? = null,
|
override var tags: List<String>? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1080,12 +1088,12 @@ data class MovieLoadResponse(
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
) : LoadResponse
|
) : LoadResponse
|
||||||
|
|
||||||
fun <T> MainAPI.newMovieLoadResponse(
|
suspend fun <T> MainAPI.newMovieLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
data: T?,
|
data: T?,
|
||||||
initializer: MovieLoadResponse.() -> Unit = { }
|
initializer: suspend MovieLoadResponse.() -> Unit = { }
|
||||||
): MovieLoadResponse {
|
): MovieLoadResponse {
|
||||||
// just in case
|
// just in case
|
||||||
if (data is String) return newMovieLoadResponse(
|
if (data is String) return newMovieLoadResponse(
|
||||||
|
@ -1108,12 +1116,12 @@ fun <T> MainAPI.newMovieLoadResponse(
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MainAPI.newMovieLoadResponse(
|
suspend fun MainAPI.newMovieLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
dataUrl: String,
|
dataUrl: String,
|
||||||
initializer: MovieLoadResponse.() -> Unit = { }
|
initializer: suspend MovieLoadResponse.() -> Unit = { }
|
||||||
): MovieLoadResponse {
|
): MovieLoadResponse {
|
||||||
val builder = MovieLoadResponse(
|
val builder = MovieLoadResponse(
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -1193,7 +1201,7 @@ data class TvSeriesLoadResponse(
|
||||||
override var rating: Int? = null,
|
override var rating: Int? = null,
|
||||||
override var tags: List<String>? = null,
|
override var tags: List<String>? = null,
|
||||||
override var duration: Int? = null,
|
override var duration: Int? = null,
|
||||||
override var trailers: List<String>? = null,
|
override var trailers: List<ExtractorLink>? = null,
|
||||||
override var recommendations: List<SearchResponse>? = null,
|
override var recommendations: List<SearchResponse>? = null,
|
||||||
override var actors: List<ActorData>? = null,
|
override var actors: List<ActorData>? = null,
|
||||||
override var comingSoon: Boolean = false,
|
override var comingSoon: Boolean = false,
|
||||||
|
@ -1201,12 +1209,12 @@ data class TvSeriesLoadResponse(
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
) : LoadResponse
|
) : LoadResponse
|
||||||
|
|
||||||
fun MainAPI.newTvSeriesLoadResponse(
|
suspend fun MainAPI.newTvSeriesLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
type: TvType,
|
type: TvType,
|
||||||
episodes: List<Episode>,
|
episodes: List<Episode>,
|
||||||
initializer: TvSeriesLoadResponse.() -> Unit = { }
|
initializer: suspend TvSeriesLoadResponse.() -> Unit = { }
|
||||||
): TvSeriesLoadResponse {
|
): TvSeriesLoadResponse {
|
||||||
val builder = TvSeriesLoadResponse(
|
val builder = TvSeriesLoadResponse(
|
||||||
name = name,
|
name = name,
|
||||||
|
|
|
@ -76,6 +76,7 @@ import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
@ -366,9 +367,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
//val youtubeLink = "https://www.youtube.com/watch?v=TxB48MEAmZw"
|
/*thread {
|
||||||
|
val youtubeLink = "https://www.youtube.com/watch?v=Zxem9rqJ5S0"
|
||||||
|
|
||||||
|
val url = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(youtubeLink)
|
||||||
|
println("ID:::: ${url.id}")
|
||||||
|
NewPipe.init(DownloaderTestImpl.getInstance())
|
||||||
|
val service = ServiceList.YouTube
|
||||||
|
val s = object : YoutubeStreamExtractor(
|
||||||
|
service,
|
||||||
|
url
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
s.fetchPage()
|
||||||
|
val streams = s.videoStreams
|
||||||
|
println("STREAMS: ${streams.map { "url = "+ it.url + " extra= " + it.height + "|" + it.isVideoOnly + "\n" }}")
|
||||||
|
}*/
|
||||||
/*
|
/*
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|
||||||
|
@ -598,6 +613,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
loadCache()
|
loadCache()
|
||||||
test()
|
test()
|
||||||
|
NewPipe.init(DownloaderTestImpl.getInstance())
|
||||||
/*nav_view.setOnNavigationItemSelectedListener { item ->
|
/*nav_view.setOnNavigationItemSelectedListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.navigation_home -> {
|
R.id.navigation_home -> {
|
||||||
|
|
|
@ -3,10 +3,11 @@ package com.lagradost.cloudstream3.animeproviders
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
class KuronimeProvider : MainAPI() {
|
class KuronimeProvider : MainAPI() {
|
||||||
override var mainUrl = "https://185.231.223.254"
|
override var mainUrl = "https://185.231.223.254"
|
||||||
|
@ -139,7 +140,6 @@ class KuronimeProvider : MainAPI() {
|
||||||
plot = description
|
plot = description
|
||||||
addTrailer(trailer)
|
addTrailer(trailer)
|
||||||
this.tags = tags
|
this.tags = tags
|
||||||
trailers = listOf(trailer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory
|
||||||
|
import org.schabi.newpipe.extractor.stream.VideoStream
|
||||||
|
|
||||||
|
class YoutubeExtractor : ExtractorApi() {
|
||||||
|
override val mainUrl = "https://www.youtube.com"
|
||||||
|
override val requiresReferer = false
|
||||||
|
override val name = "YouTube"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var ytVideos: MutableMap<String, List<VideoStream>> = mutableMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExtractorUrl(id: String): String {
|
||||||
|
return "https://www.youtube.com/watch?v=$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||||
|
val streams = safeApiCall {
|
||||||
|
val streams = ytVideos[url] ?: let {
|
||||||
|
val link =
|
||||||
|
YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(url)
|
||||||
|
|
||||||
|
val s = object : YoutubeStreamExtractor(
|
||||||
|
ServiceList.YouTube,
|
||||||
|
link
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
s.fetchPage()
|
||||||
|
val streams = s.videoStreams ?: return@let emptyList()
|
||||||
|
ytVideos[url] = streams
|
||||||
|
streams
|
||||||
|
}
|
||||||
|
if (streams.isEmpty()) {
|
||||||
|
throw ErrorLoadingException("No Youtube streams")
|
||||||
|
}
|
||||||
|
|
||||||
|
streams
|
||||||
|
//streams.sortedBy { it.height }
|
||||||
|
// .firstOrNull { !it.isVideoOnly && it.height > 0 }
|
||||||
|
// ?: throw ErrorLoadingException("No valid Youtube stream")
|
||||||
|
}
|
||||||
|
if (streams is Resource.Success) {
|
||||||
|
return streams.value.mapNotNull {
|
||||||
|
if (it.isVideoOnly || it.height <= 0) return@mapNotNull null
|
||||||
|
|
||||||
|
ExtractorLink(
|
||||||
|
this.name,
|
||||||
|
this.name,
|
||||||
|
it.url ?: return@mapNotNull null,
|
||||||
|
"",
|
||||||
|
it.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -95,7 +95,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TvShow.toLoadResponse(): TvSeriesLoadResponse {
|
private suspend fun TvShow.toLoadResponse(): TvSeriesLoadResponse {
|
||||||
val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 }
|
val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 }
|
||||||
?.mapNotNull { season ->
|
?.mapNotNull { season ->
|
||||||
season.episodes?.map { episode ->
|
season.episodes?.map { episode ->
|
||||||
|
@ -167,7 +167,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Movie.toLoadResponse(): MovieLoadResponse {
|
private suspend fun Movie.toLoadResponse(): MovieLoadResponse {
|
||||||
return newMovieLoadResponse(
|
return newMovieLoadResponse(
|
||||||
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
||||||
this.imdb_id,
|
this.imdb_id,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.movieproviders
|
package com.lagradost.cloudstream3.movieproviders
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ class NginxProvider : MainAPI() {
|
||||||
this.plot = description
|
this.plot = description
|
||||||
this.rating = ratingAverage
|
this.rating = ratingAverage
|
||||||
this.tags = tagsList
|
this.tags = tagsList
|
||||||
this.trailers = trailer
|
addTrailer(trailer)
|
||||||
addPoster(poster, authHeader)
|
addPoster(poster, authHeader)
|
||||||
}
|
}
|
||||||
} else // a tv serie
|
} else // a tv serie
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.SparseArray
|
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.util.forEach
|
|
||||||
import at.huber.youtubeExtractor.VideoMeta
|
|
||||||
import at.huber.youtubeExtractor.YouTubeExtractor
|
|
||||||
import at.huber.youtubeExtractor.YtFile
|
|
||||||
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.ext.okhttp.OkHttpDataSource
|
||||||
|
@ -31,7 +25,6 @@ import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
||||||
import com.google.android.exoplayer2.util.MimeTypes
|
import com.google.android.exoplayer2.util.MimeTypes
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
import com.google.android.exoplayer2.video.VideoSize
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -39,7 +32,6 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -332,7 +324,6 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var ytVideos: MutableMap<String, YtFile> = mutableMapOf()
|
|
||||||
private var simpleCache: SimpleCache? = null
|
private var simpleCache: SimpleCache? = null
|
||||||
|
|
||||||
var requestSubtitleUpdate: (() -> Unit)? = null
|
var requestSubtitleUpdate: (() -> Unit)? = null
|
||||||
|
@ -883,57 +874,9 @@ class CS3IPlayer : IPlayer {
|
||||||
return Pair(subSources, activeSubtitles)
|
return Pair(subSources, activeSubtitles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadYtFile(context: Context, yt: YtFile) {
|
|
||||||
loadOnlinePlayer(
|
|
||||||
context,
|
|
||||||
ExtractorLink(
|
|
||||||
"YouTube",
|
|
||||||
"",
|
|
||||||
yt.url,
|
|
||||||
"",
|
|
||||||
yt.format?.height ?: Qualities.Unknown.value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
||||||
Log.i(TAG, "loadOnlinePlayer $link")
|
Log.i(TAG, "loadOnlinePlayer $link")
|
||||||
try {
|
try {
|
||||||
if (link.url.contains("youtube.com")) {
|
|
||||||
val ytLink = link.url.replace("/embed/", "/watch?v=")
|
|
||||||
ytVideos[ytLink]?.let {
|
|
||||||
loadYtFile(context, it)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val ytExtractor =
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
object : YouTubeExtractor(context) {
|
|
||||||
override fun onExtractionComplete(
|
|
||||||
ytFiles: SparseArray<YtFile>?,
|
|
||||||
videoMeta: VideoMeta?
|
|
||||||
) {
|
|
||||||
var yt: YtFile? = null
|
|
||||||
ytFiles?.forEach { _, value ->
|
|
||||||
if ((yt?.format?.height ?: 0) < (value.format?.height
|
|
||||||
?: -1) && (value.format?.audioBitrate ?: -1) > 0
|
|
||||||
) {
|
|
||||||
yt = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yt?.let { ytf ->
|
|
||||||
ytVideos[ytLink] = ytf
|
|
||||||
loadYtFile(context, ytf)
|
|
||||||
} ?: run {
|
|
||||||
playerError?.invoke(ErrorLoadingException("No Link"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i(TAG, "YouTube extraction on $ytLink")
|
|
||||||
ytExtractor.extract(ytLink)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLink = link
|
currentLink = link
|
||||||
|
|
||||||
if (ignoreSSL) {
|
if (ignoreSSL) {
|
||||||
|
|
|
@ -602,7 +602,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
|
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTrailers: List<String> = emptyList()
|
var currentTrailers: List<ExtractorLink> = emptyList()
|
||||||
var currentTrailerIndex = 0
|
var currentTrailerIndex = 0
|
||||||
|
|
||||||
override fun nextMirror() {
|
override fun nextMirror() {
|
||||||
|
@ -626,13 +626,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
player.loadPlayer(
|
player.loadPlayer(
|
||||||
ctx,
|
ctx,
|
||||||
false,
|
false,
|
||||||
ExtractorLink(
|
|
||||||
"",
|
|
||||||
"Trailer",
|
|
||||||
trailer,
|
trailer,
|
||||||
"",
|
|
||||||
Qualities.Unknown.value
|
|
||||||
),
|
|
||||||
null,
|
null,
|
||||||
startPosition = 0L,
|
startPosition = 0L,
|
||||||
subtitles = emptySet(),
|
subtitles = emptySet(),
|
||||||
|
@ -649,14 +643,14 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
result_trailer_loading?.isVisible = isSuccess
|
result_trailer_loading?.isVisible = isSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTrailers(trailers: List<String>?) {
|
private fun setTrailers(trailers: List<ExtractorLink>?) {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
if (ctx.isTvSettings()) return
|
if (ctx.isTvSettings()) return
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
val showTrailers =
|
val showTrailers =
|
||||||
settingsManager.getBoolean(ctx.getString(R.string.show_trailers_key), true)
|
settingsManager.getBoolean(ctx.getString(R.string.show_trailers_key), true)
|
||||||
if (!showTrailers) return
|
if (!showTrailers) return
|
||||||
currentTrailers = trailers ?: emptyList()
|
currentTrailers = trailers?.sortedBy { -it.quality } ?: emptyList()
|
||||||
loadTrailer()
|
loadTrailer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -769,7 +763,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
|
|
||||||
player_open_source?.setOnClickListener {
|
player_open_source?.setOnClickListener {
|
||||||
currentTrailers.getOrNull(currentTrailerIndex)?.let {
|
currentTrailers.getOrNull(currentTrailerIndex)?.let {
|
||||||
context?.openBrowser(it)
|
context?.openBrowser(it.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ class ResultViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastMeta: SyncAPI.SyncResult? = null
|
var lastMeta: SyncAPI.SyncResult? = null
|
||||||
private fun applyMeta(resp: LoadResponse, meta: SyncAPI.SyncResult?): LoadResponse {
|
private suspend fun applyMeta(resp: LoadResponse, meta: SyncAPI.SyncResult?): LoadResponse {
|
||||||
if (meta == null) return resp
|
if (meta == null) return resp
|
||||||
lastMeta = meta
|
lastMeta = meta
|
||||||
return resp.apply {
|
return resp.apply {
|
||||||
|
@ -145,7 +145,7 @@ class ResultViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMeta(meta: SyncAPI.SyncResult) {
|
fun setMeta(meta: SyncAPI.SyncResult) = viewModelScope.launch {
|
||||||
Log.i(TAG, "setMeta")
|
Log.i(TAG, "setMeta")
|
||||||
(result.value as? Resource.Success<LoadResponse>?)?.value?.let { resp ->
|
(result.value as? Resource.Success<LoadResponse>?)?.value?.let { resp ->
|
||||||
_resultResponse.postValue(Resource.Success(applyMeta(resp, meta)))
|
_resultResponse.postValue(Resource.Success(applyMeta(resp, meta)))
|
||||||
|
|
|
@ -106,6 +106,19 @@ suspend fun loadExtractor(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun loadExtractor(
|
||||||
|
url: String,
|
||||||
|
referer: String? = null,
|
||||||
|
): List<ExtractorLink> {
|
||||||
|
for (extractor in extractorApis) {
|
||||||
|
if (url.startsWith(extractor.mainUrl)) {
|
||||||
|
return extractor.getSafeUrl(url, referer) ?: emptyList()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
val extractorApis: Array<ExtractorApi> = arrayOf(
|
val extractorApis: Array<ExtractorApi> = arrayOf(
|
||||||
//AllProvider(),
|
//AllProvider(),
|
||||||
WcoStream(),
|
WcoStream(),
|
||||||
|
@ -205,6 +218,7 @@ val extractorApis: Array<ExtractorApi> = arrayOf(
|
||||||
KotakAnimeid(),
|
KotakAnimeid(),
|
||||||
Neonime8n(),
|
Neonime8n(),
|
||||||
Neonime7n(),
|
Neonime7n(),
|
||||||
|
YoutubeExtractor(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getExtractorApiFromName(name: String): ExtractorApi {
|
fun getExtractorApiFromName(name: String): ExtractorApi {
|
||||||
|
|
Loading…
Reference in a new issue