mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
6a303919e7
18 changed files with 618 additions and 123 deletions
|
@ -51,12 +51,12 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdk = 33
|
compileSdk = 33
|
||||||
buildToolsVersion = "30.0.3"
|
buildToolsVersion = "34.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.lagradost.cloudstream3"
|
applicationId = "com.lagradost.cloudstream3"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 29
|
targetSdk = 33
|
||||||
|
|
||||||
versionCode = 59
|
versionCode = 59
|
||||||
versionName = "4.1.8"
|
versionName = "4.1.8"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||||
import com.lagradost.cloudstream3.utils.extractorApis
|
import com.lagradost.cloudstream3.utils.extractorApis
|
||||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
|
@ -66,7 +67,7 @@ open class Pelisplus(val mainUrl: String) {
|
||||||
href,
|
href,
|
||||||
page.url,
|
page.url,
|
||||||
getQualityFromName(qual),
|
getQualityFromName(qual),
|
||||||
element.attr("href").contains(".m3u8")
|
type = INFER_TYPE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.argamap
|
import com.lagradost.cloudstream3.argamap
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||||
import com.lagradost.cloudstream3.utils.extractorApis
|
import com.lagradost.cloudstream3.utils.extractorApis
|
||||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||||
|
@ -70,7 +71,7 @@ class Vidstream(val mainUrl: String) {
|
||||||
href,
|
href,
|
||||||
page.url,
|
page.url,
|
||||||
getQualityFromName(qual),
|
getQualityFromName(qual),
|
||||||
element.attr("href").contains(".m3u8")
|
type = INFER_TYPE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.extractors.helper.NineAnimeHelper.encrypt
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
|
||||||
class Vidstreamz : WcoStream() {
|
class Vidstreamz : WcoStream() {
|
||||||
|
@ -126,8 +127,7 @@ open class WcoStream : ExtractorApi() {
|
||||||
|
|
||||||
if (!response.text.startsWith("{")) throw ErrorLoadingException("Seems like 9Anime kiddies changed stuff again, Go touch some grass for bout an hour Or use a different Server")
|
if (!response.text.startsWith("{")) throw ErrorLoadingException("Seems like 9Anime kiddies changed stuff again, Go touch some grass for bout an hour Or use a different Server")
|
||||||
return response.parsed<Response>().data.media.sources.map {
|
return response.parsed<Response>().data.media.sources.map {
|
||||||
ExtractorLink(name, it.file,it.file,host,Qualities.Unknown.value,it.file.contains(".m3u8"))
|
ExtractorLink(name, it.file, it.file, host, Qualities.Unknown.value, type = INFER_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import com.lagradost.cloudstream3.SubtitleFile
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
open class Wibufile : ExtractorApi() {
|
open class Wibufile : ExtractorApi() {
|
||||||
override val name: String = "Wibufile"
|
override val name: String = "Wibufile"
|
||||||
|
@ -28,10 +28,8 @@ open class Wibufile : ExtractorApi() {
|
||||||
video ?: return,
|
video ?: return,
|
||||||
"$mainUrl/",
|
"$mainUrl/",
|
||||||
Qualities.Unknown.value,
|
Qualities.Unknown.value,
|
||||||
URI(url).path.endsWith(".m3u8")
|
type = INFER_TYPE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,16 +1,24 @@
|
||||||
package com.lagradost.cloudstream3.ui
|
package com.lagradost.cloudstream3.ui
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||||
|
import com.lagradost.cloudstream3.DubStatus
|
||||||
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
|
import com.lagradost.cloudstream3.HomePageResponse
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
|
import com.lagradost.cloudstream3.MainAPI
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||||
|
import com.lagradost.cloudstream3.MainPageRequest
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.TvType
|
||||||
|
import com.lagradost.cloudstream3.fixUrl
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
|
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.GlobalScope.coroutineContext
|
import kotlinx.coroutines.GlobalScope.coroutineContext
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -174,7 +182,7 @@ class APIRepository(val api: MainAPI) {
|
||||||
data: String,
|
data: String,
|
||||||
isCasting: Boolean,
|
isCasting: Boolean,
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (isInvalidData(data)) return false // this makes providers cleaner
|
if (isInvalidData(data)) return false // this makes providers cleaner
|
||||||
return try {
|
return try {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.sortSubs
|
import com.lagradost.cloudstream3.sortSubs
|
||||||
import com.lagradost.cloudstream3.sortUrls
|
import com.lagradost.cloudstream3.sortUrls
|
||||||
|
import com.lagradost.cloudstream3.ui.player.LoadType
|
||||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
|
@ -294,7 +295,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
||||||
val generator = RepoLinkGenerator(listOf(epData))
|
val generator = RepoLinkGenerator(listOf(epData))
|
||||||
|
|
||||||
val isSuccessful = safeApiCall {
|
val isSuccessful = safeApiCall {
|
||||||
generator.generateLinks(clearCache = false, isCasting = true,
|
generator.generateLinks(
|
||||||
|
clearCache = false, type = LoadType.Chromecast,
|
||||||
callback = {
|
callback = {
|
||||||
it.first?.let { link ->
|
it.first?.let { link ->
|
||||||
currentLinks.add(link)
|
currentLinks.add(link)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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
|
||||||
|
@ -7,6 +8,7 @@ import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.C.*
|
import androidx.media3.common.C.*
|
||||||
import androidx.media3.common.Format
|
import androidx.media3.common.Format
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
|
@ -31,6 +33,10 @@ import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
|
import androidx.media3.exoplayer.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.SeekParameters
|
import androidx.media3.exoplayer.SeekParameters
|
||||||
|
import androidx.media3.exoplayer.dash.DashMediaSource
|
||||||
|
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager
|
||||||
|
import androidx.media3.exoplayer.drm.FrameworkMediaDrm
|
||||||
|
import androidx.media3.exoplayer.drm.LocalMediaDrmCallback
|
||||||
import androidx.media3.exoplayer.source.ClippingMediaSource
|
import androidx.media3.exoplayer.source.ClippingMediaSource
|
||||||
import androidx.media3.exoplayer.source.ConcatenatingMediaSource
|
import androidx.media3.exoplayer.source.ConcatenatingMediaSource
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
@ -50,12 +56,16 @@ import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
||||||
|
import com.lagradost.cloudstream3.utils.DrmExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import java.util.UUID
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.SSLSession
|
import javax.net.ssl.SSLSession
|
||||||
|
@ -102,7 +112,16 @@ class CS3IPlayer : IPlayer {
|
||||||
* */
|
* */
|
||||||
data class MediaItemSlice(
|
data class MediaItemSlice(
|
||||||
val mediaItem: MediaItem,
|
val mediaItem: MediaItem,
|
||||||
val durationUs: Long
|
val durationUs: Long,
|
||||||
|
val drm: DrmMetadata? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DrmMetadata(
|
||||||
|
val kid: String,
|
||||||
|
val key: String,
|
||||||
|
val uuid: UUID,
|
||||||
|
val kty: String,
|
||||||
|
val keyRequestParameters: HashMap<String, String>,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun getDuration(): Long? = exoPlayer?.duration
|
override fun getDuration(): Long? = exoPlayer?.duration
|
||||||
|
@ -338,6 +357,7 @@ class CS3IPlayer : IPlayer {
|
||||||
}.flatten()
|
}.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun Tracks.Group.getFormats(): List<Pair<Format, Int>> {
|
private fun Tracks.Group.getFormats(): List<Pair<Format, Int>> {
|
||||||
return (0 until this.mediaTrackGroup.length).mapNotNull { i ->
|
return (0 until this.mediaTrackGroup.length).mapNotNull { i ->
|
||||||
if (this.isSupported)
|
if (this.isSupported)
|
||||||
|
@ -366,6 +386,7 @@ class CS3IPlayer : IPlayer {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun getVideoTracks(): CurrentTracks {
|
override fun getVideoTracks(): CurrentTracks {
|
||||||
val allTracks = exoPlayer?.currentTracks?.groups ?: emptyList()
|
val allTracks = exoPlayer?.currentTracks?.groups ?: emptyList()
|
||||||
val videoTracks = allTracks.filter { it.type == TRACK_TYPE_VIDEO }
|
val videoTracks = allTracks.filter { it.type == TRACK_TYPE_VIDEO }
|
||||||
|
@ -385,6 +406,7 @@ class CS3IPlayer : IPlayer {
|
||||||
/**
|
/**
|
||||||
* @return True if the player should be reloaded
|
* @return True if the player should be reloaded
|
||||||
* */
|
* */
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
|
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
|
||||||
Log.i(TAG, "setPreferredSubtitles init $subtitle")
|
Log.i(TAG, "setPreferredSubtitles init $subtitle")
|
||||||
currentSubtitles = subtitle
|
currentSubtitles = subtitle
|
||||||
|
@ -463,6 +485,7 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun getAspectRatio(): Rational? {
|
override fun getAspectRatio(): Rational? {
|
||||||
return exoPlayer?.videoFormat?.let { format ->
|
return exoPlayer?.videoFormat?.let { format ->
|
||||||
Rational(format.width, format.height)
|
Rational(format.width, format.height)
|
||||||
|
@ -473,6 +496,7 @@ class CS3IPlayer : IPlayer {
|
||||||
subtitleHelper.setSubStyle(style)
|
subtitleHelper.setSubStyle(style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun saveData() {
|
override fun saveData() {
|
||||||
Log.i(TAG, "saveData")
|
Log.i(TAG, "saveData")
|
||||||
updatedTime()
|
updatedTime()
|
||||||
|
@ -546,6 +570,7 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
var requestSubtitleUpdate: (() -> Unit)? = null
|
var requestSubtitleUpdate: (() -> Unit)? = null
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun createOnlineSource(headers: Map<String, String>): HttpDataSource.Factory {
|
private fun createOnlineSource(headers: Map<String, String>): HttpDataSource.Factory {
|
||||||
val source = OkHttpDataSource.Factory(app.baseClient).setUserAgent(USER_AGENT)
|
val source = OkHttpDataSource.Factory(app.baseClient).setUserAgent(USER_AGENT)
|
||||||
return source.apply {
|
return source.apply {
|
||||||
|
@ -553,6 +578,7 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun createOnlineSource(link: ExtractorLink): HttpDataSource.Factory {
|
private fun createOnlineSource(link: ExtractorLink): HttpDataSource.Factory {
|
||||||
val provider = getApiFromNameNull(link.source)
|
val provider = getApiFromNameNull(link.source)
|
||||||
val interceptor = provider?.getVideoInterceptor(link)
|
val interceptor = provider?.getVideoInterceptor(link)
|
||||||
|
@ -630,6 +656,7 @@ class CS3IPlayer : IPlayer {
|
||||||
return Pair(subSources, activeSubtitles)
|
return Pair(subSources, activeSubtitles)
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun getCache(context: Context, cacheSize: Long): SimpleCache? {
|
private fun getCache(context: Context, cacheSize: Long): SimpleCache? {
|
||||||
return try {
|
return try {
|
||||||
val databaseProvider = StandaloneDatabaseProvider(context)
|
val databaseProvider = StandaloneDatabaseProvider(context)
|
||||||
|
@ -661,6 +688,7 @@ class CS3IPlayer : IPlayer {
|
||||||
return getMediaItemBuilder(mimeType).setUri(url).build()
|
return getMediaItemBuilder(mimeType).setUri(url).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun getTrackSelector(context: Context, maxVideoHeight: Int?): TrackSelector {
|
private fun getTrackSelector(context: Context, maxVideoHeight: Int?): TrackSelector {
|
||||||
val trackSelector = DefaultTrackSelector(context)
|
val trackSelector = DefaultTrackSelector(context)
|
||||||
trackSelector.parameters = trackSelector.buildUponParameters()
|
trackSelector.parameters = trackSelector.buildUponParameters()
|
||||||
|
@ -674,6 +702,7 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
var currentTextRenderer: CustomTextRenderer? = null
|
var currentTextRenderer: CustomTextRenderer? = null
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun buildExoPlayer(
|
private fun buildExoPlayer(
|
||||||
context: Context,
|
context: Context,
|
||||||
mediaItemSlices: List<MediaItemSlice>,
|
mediaItemSlices: List<MediaItemSlice>,
|
||||||
|
@ -758,15 +787,33 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
// If there is only one item then treat it as normal, if multiple: concatenate the items.
|
// If there is only one item then treat it as normal, if multiple: concatenate the items.
|
||||||
val videoMediaSource = if (mediaItemSlices.size == 1) {
|
val videoMediaSource = if (mediaItemSlices.size == 1) {
|
||||||
factory.createMediaSource(mediaItemSlices.first().mediaItem)
|
val item = mediaItemSlices.first()
|
||||||
|
|
||||||
|
item.drm?.let { drm ->
|
||||||
|
val drmCallback =
|
||||||
|
LocalMediaDrmCallback("{\"keys\":[{\"kty\":\"${drm.kty}\",\"k\":\"${drm.key}\",\"kid\":\"${drm.kid}\"}],\"type\":\"temporary\"}".toByteArray())
|
||||||
|
val manager = DefaultDrmSessionManager.Builder()
|
||||||
|
.setPlayClearSamplesWithoutKeys(true)
|
||||||
|
.setMultiSession(false)
|
||||||
|
.setKeyRequestParameters(drm.keyRequestParameters)
|
||||||
|
.setUuidAndExoMediaDrmProvider(drm.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
||||||
|
.build(drmCallback)
|
||||||
|
val manifestDataSourceFactory = DefaultHttpDataSource.Factory()
|
||||||
|
|
||||||
|
DashMediaSource.Factory(manifestDataSourceFactory)
|
||||||
|
.setDrmSessionManagerProvider { manager }
|
||||||
|
.createMediaSource(item.mediaItem)
|
||||||
|
} ?: run {
|
||||||
|
factory.createMediaSource(item.mediaItem)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val source = ConcatenatingMediaSource()
|
val source = ConcatenatingMediaSource()
|
||||||
mediaItemSlices.map {
|
mediaItemSlices.map { item ->
|
||||||
source.addMediaSource(
|
source.addMediaSource(
|
||||||
// The duration MUST be known for it to work properly, see https://github.com/google/ExoPlayer/issues/4727
|
// The duration MUST be known for it to work properly, see https://github.com/google/ExoPlayer/issues/4727
|
||||||
ClippingMediaSource(
|
ClippingMediaSource(
|
||||||
factory.createMediaSource(it.mediaItem),
|
factory.createMediaSource(item.mediaItem),
|
||||||
it.durationUs
|
item.durationUs
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1103,6 +1150,8 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var lastTimeStamps: List<EpisodeSkip.SkipStamp> = emptyList()
|
private var lastTimeStamps: List<EpisodeSkip.SkipStamp> = emptyList()
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
|
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
|
||||||
lastTimeStamps = timeStamps
|
lastTimeStamps = timeStamps
|
||||||
timeStamps.forEach { timestamp ->
|
timeStamps.forEach { timestamp ->
|
||||||
|
@ -1120,6 +1169,7 @@ class CS3IPlayer : IPlayer {
|
||||||
updatedTime()
|
updatedTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
fun onRenderFirst() {
|
fun onRenderFirst() {
|
||||||
if (hasUsedFirstRender) { // this insures that we only call this once per player load
|
if (hasUsedFirstRender) { // this insures that we only call this once per player load
|
||||||
return
|
return
|
||||||
|
@ -1186,6 +1236,7 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun getSubSources(
|
private fun getSubSources(
|
||||||
onlineSourceFactory: HttpDataSource.Factory?,
|
onlineSourceFactory: HttpDataSource.Factory?,
|
||||||
offlineSourceFactory: DataSource.Factory?,
|
offlineSourceFactory: DataSource.Factory?,
|
||||||
|
@ -1241,6 +1292,7 @@ class CS3IPlayer : IPlayer {
|
||||||
return exoPlayer != null
|
return exoPlayer != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
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 {
|
||||||
|
@ -1257,18 +1309,37 @@ class CS3IPlayer : IPlayer {
|
||||||
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
|
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mime = when {
|
val mime = when (link.type) {
|
||||||
link.isM3u8 -> MimeTypes.APPLICATION_M3U8
|
ExtractorLinkType.M3U8 -> MimeTypes.APPLICATION_M3U8
|
||||||
link.isDash -> MimeTypes.APPLICATION_MPD
|
ExtractorLinkType.DASH -> MimeTypes.APPLICATION_MPD
|
||||||
else -> MimeTypes.VIDEO_MP4
|
ExtractorLinkType.VIDEO -> MimeTypes.VIDEO_MP4
|
||||||
|
ExtractorLinkType.TORRENT -> throw IllegalArgumentException("No torrent support")
|
||||||
|
ExtractorLinkType.MAGNET -> throw IllegalArgumentException("No magnet support")
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaItems = if (link is ExtractorLinkPlayList) {
|
|
||||||
link.playlist.map {
|
val mediaItems = when (link) {
|
||||||
|
is ExtractorLinkPlayList -> link.playlist.map {
|
||||||
MediaItemSlice(getMediaItem(mime, it.url), it.durationUs)
|
MediaItemSlice(getMediaItem(mime, it.url), it.durationUs)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
is DrmExtractorLink -> {
|
||||||
listOf(
|
listOf(
|
||||||
|
// Single sliced list with unset length
|
||||||
|
MediaItemSlice(
|
||||||
|
getMediaItem(mime, link.url), Long.MIN_VALUE,
|
||||||
|
drm = DrmMetadata(
|
||||||
|
kid = link.kid,
|
||||||
|
key = link.key,
|
||||||
|
uuid = link.uuid,
|
||||||
|
kty = link.kty,
|
||||||
|
keyRequestParameters = link.keyRequestParameters
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> listOf(
|
||||||
// Single sliced list with unset length
|
// Single sliced list with unset length
|
||||||
MediaItemSlice(getMediaItem(mime, link.url), Long.MIN_VALUE)
|
MediaItemSlice(getMediaItem(mime, link.url), Long.MIN_VALUE)
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,10 +56,10 @@ class DownloadFileGenerator(
|
||||||
|
|
||||||
override suspend fun generateLinks(
|
override suspend fun generateLinks(
|
||||||
clearCache: Boolean,
|
clearCache: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||||
subtitleCallback: (SubtitleData) -> Unit,
|
subtitleCallback: (SubtitleData) -> Unit,
|
||||||
offset: Int,
|
offset: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val meta = episodes[currentIndex + offset]
|
val meta = episodes[currentIndex + offset]
|
||||||
callback(null to meta)
|
callback(null to meta)
|
||||||
|
|
|
@ -37,15 +37,18 @@ class ExtractorLinkGenerator(
|
||||||
|
|
||||||
override suspend fun generateLinks(
|
override suspend fun generateLinks(
|
||||||
clearCache: Boolean,
|
clearCache: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||||
subtitleCallback: (SubtitleData) -> Unit,
|
subtitleCallback: (SubtitleData) -> Unit,
|
||||||
offset: Int
|
offset: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
subtitles.forEach(subtitleCallback)
|
subtitles.forEach(subtitleCallback)
|
||||||
|
val allowedTypes = type.toSet()
|
||||||
links.forEach {
|
links.forEach {
|
||||||
|
if(allowedTypes.contains(it.type)) {
|
||||||
callback.invoke(it to null)
|
callback.invoke(it to null)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,43 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
|
|
||||||
|
enum class LoadType {
|
||||||
|
Unknown,
|
||||||
|
InApp,
|
||||||
|
InAppDownload,
|
||||||
|
ExternalApp,
|
||||||
|
Browser,
|
||||||
|
Chromecast
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LoadType.toSet() : Set<ExtractorLinkType> {
|
||||||
|
return when(this) {
|
||||||
|
LoadType.InApp -> setOf(
|
||||||
|
ExtractorLinkType.VIDEO,
|
||||||
|
ExtractorLinkType.DASH,
|
||||||
|
ExtractorLinkType.M3U8
|
||||||
|
)
|
||||||
|
LoadType.Browser -> setOf(
|
||||||
|
ExtractorLinkType.VIDEO,
|
||||||
|
ExtractorLinkType.DASH,
|
||||||
|
ExtractorLinkType.M3U8
|
||||||
|
)
|
||||||
|
LoadType.InAppDownload -> setOf(
|
||||||
|
ExtractorLinkType.VIDEO,
|
||||||
|
ExtractorLinkType.M3U8
|
||||||
|
)
|
||||||
|
LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.values().toSet()
|
||||||
|
LoadType.Chromecast -> setOf(
|
||||||
|
ExtractorLinkType.VIDEO,
|
||||||
|
ExtractorLinkType.DASH,
|
||||||
|
ExtractorLinkType.M3U8
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface IGenerator {
|
interface IGenerator {
|
||||||
val hasCache: Boolean
|
val hasCache: Boolean
|
||||||
|
|
||||||
|
@ -19,7 +54,7 @@ interface IGenerator {
|
||||||
/* not safe, must use try catch */
|
/* not safe, must use try catch */
|
||||||
suspend fun generateLinks(
|
suspend fun generateLinks(
|
||||||
clearCache: Boolean,
|
clearCache: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||||
subtitleCallback: (SubtitleData) -> Unit,
|
subtitleCallback: (SubtitleData) -> Unit,
|
||||||
offset: Int = 0,
|
offset: Int = 0,
|
||||||
|
|
|
@ -48,7 +48,7 @@ class LinkGenerator(
|
||||||
|
|
||||||
override suspend fun generateLinks(
|
override suspend fun generateLinks(
|
||||||
clearCache: Boolean,
|
clearCache: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||||
subtitleCallback: (SubtitleData) -> Unit,
|
subtitleCallback: (SubtitleData) -> Unit,
|
||||||
offset: Int
|
offset: Int
|
||||||
|
|
|
@ -78,10 +78,10 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
if (generator?.hasCache == true && generator?.hasNext() == true) {
|
if (generator?.hasCache == true && generator?.hasNext() == true) {
|
||||||
safeApiCall {
|
safeApiCall {
|
||||||
generator?.generateLinks(
|
generator?.generateLinks(
|
||||||
|
type = LoadType.InApp,
|
||||||
clearCache = false,
|
clearCache = false,
|
||||||
isCasting = false,
|
callback = {},
|
||||||
{},
|
subtitleCallback = {},
|
||||||
{},
|
|
||||||
offset = 1
|
offset = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) {
|
fun loadLinks(clearCache: Boolean = false, type: LoadType = LoadType.InApp) {
|
||||||
Log.i(TAG, "loadLinks")
|
Log.i(TAG, "loadLinks")
|
||||||
currentJob?.cancel()
|
currentJob?.cancel()
|
||||||
|
|
||||||
|
@ -162,14 +162,14 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
// load more data
|
// load more data
|
||||||
_loadingLinks.postValue(Resource.Loading())
|
_loadingLinks.postValue(Resource.Loading())
|
||||||
val loadingState = safeApiCall {
|
val loadingState = safeApiCall {
|
||||||
generator?.generateLinks(clearCache = clearCache, isCasting = isCasting, {
|
generator?.generateLinks(type = type,clearCache = clearCache, callback = {
|
||||||
currentLinks.add(it)
|
currentLinks.add(it)
|
||||||
// Clone to prevent ConcurrentModificationException
|
// Clone to prevent ConcurrentModificationException
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
// Extra normalSafeApiCall since .toSet() iterates.
|
// Extra normalSafeApiCall since .toSet() iterates.
|
||||||
_currentLinks.postValue(currentLinks.toSet())
|
_currentLinks.postValue(currentLinks.toSet())
|
||||||
}
|
}
|
||||||
}, {
|
}, subtitleCallback = {
|
||||||
currentSubs.add(it)
|
currentSubs.add(it)
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
_currentSubs.postValue(currentSubs.toSet())
|
_currentSubs.postValue(currentSubs.toSet())
|
||||||
|
|
|
@ -67,18 +67,19 @@ class RepoLinkGenerator(
|
||||||
|
|
||||||
override suspend fun generateLinks(
|
override suspend fun generateLinks(
|
||||||
clearCache: Boolean,
|
clearCache: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||||
subtitleCallback: (SubtitleData) -> Unit,
|
subtitleCallback: (SubtitleData) -> Unit,
|
||||||
offset: Int,
|
offset: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
val allowedTypes = type.toSet()
|
||||||
val index = currentIndex
|
val index = currentIndex
|
||||||
val current = episodes.getOrNull(index + offset) ?: return false
|
val current = episodes.getOrNull(index + offset) ?: return false
|
||||||
|
|
||||||
val (currentLinkCache, currentSubsCache) = if (clearCache) {
|
val (currentLinkCache, currentSubsCache) = if (clearCache) {
|
||||||
Pair(mutableSetOf(), mutableSetOf())
|
Pair(mutableSetOf(), mutableSetOf())
|
||||||
} else {
|
} else {
|
||||||
cache[Pair(current.apiName, current.id)] ?: Pair(mutableSetOf(), mutableSetOf())
|
cache[current.apiName to current.id] ?: Pair(mutableSetOf(), mutableSetOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
//val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet()
|
//val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet()
|
||||||
|
@ -88,9 +89,9 @@ class RepoLinkGenerator(
|
||||||
val currentSubsUrls = mutableSetOf<String>() // makes all subs urls unique
|
val currentSubsUrls = mutableSetOf<String>() // makes all subs urls unique
|
||||||
val currentSubsNames = mutableSetOf<String>() // makes all subs names unique
|
val currentSubsNames = mutableSetOf<String>() // makes all subs names unique
|
||||||
|
|
||||||
currentLinkCache.forEach { link ->
|
currentLinkCache.filter { allowedTypes.contains(it.type) }.forEach { link ->
|
||||||
currentLinks.add(link.url)
|
currentLinks.add(link.url)
|
||||||
callback(Pair(link, null))
|
callback(link to null)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSubsCache.forEach { sub ->
|
currentSubsCache.forEach { sub ->
|
||||||
|
@ -108,8 +109,8 @@ class RepoLinkGenerator(
|
||||||
val result = APIRepository(
|
val result = APIRepository(
|
||||||
getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist")
|
getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist")
|
||||||
).loadLinks(current.data,
|
).loadLinks(current.data,
|
||||||
isCasting,
|
isCasting = LoadType.Chromecast == type,
|
||||||
{ file ->
|
subtitleCallback = { file ->
|
||||||
val correctFile = PlayerSubtitleHelper.getSubtitleData(file)
|
val correctFile = PlayerSubtitleHelper.getSubtitleData(file)
|
||||||
if (!currentSubsUrls.contains(correctFile.url)) {
|
if (!currentSubsUrls.contains(correctFile.url)) {
|
||||||
currentSubsUrls.add(correctFile.url)
|
currentSubsUrls.add(correctFile.url)
|
||||||
|
@ -132,12 +133,14 @@ class RepoLinkGenerator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ link ->
|
callback = { link ->
|
||||||
Log.d(TAG, "Loaded ExtractorLink: $link")
|
Log.d(TAG, "Loaded ExtractorLink: $link")
|
||||||
if (!currentLinks.contains(link.url)) {
|
if (!currentLinks.contains(link.url)) {
|
||||||
if (!currentLinkCache.contains(link)) {
|
if (!currentLinkCache.contains(link)) {
|
||||||
currentLinks.add(link.url)
|
currentLinks.add(link.url)
|
||||||
|
if (allowedTypes.contains(link.type)) {
|
||||||
callback(Pair(link, null))
|
callback(Pair(link, null))
|
||||||
|
}
|
||||||
currentLinkCache.add(link)
|
currentLinkCache.add(link)
|
||||||
//linkCache[index] = currentLinkCache
|
//linkCache[index] = currentLinkCache
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||||
import com.lagradost.cloudstream3.ui.player.IGenerator
|
import com.lagradost.cloudstream3.ui.player.IGenerator
|
||||||
|
import com.lagradost.cloudstream3.ui.player.LoadType
|
||||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
|
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
|
||||||
|
@ -745,7 +746,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val generator = RepoLinkGenerator(listOf(episode))
|
val generator = RepoLinkGenerator(listOf(episode))
|
||||||
val currentLinks = mutableSetOf<ExtractorLink>()
|
val currentLinks = mutableSetOf<ExtractorLink>()
|
||||||
val currentSubs = mutableSetOf<SubtitleData>()
|
val currentSubs = mutableSetOf<SubtitleData>()
|
||||||
generator.generateLinks(clearCache = false, isCasting = false, callback = {
|
generator.generateLinks(clearCache = false, LoadType.Chromecast, callback = {
|
||||||
it.first?.let { link ->
|
it.first?.let { link ->
|
||||||
currentLinks.add(link)
|
currentLinks.add(link)
|
||||||
}
|
}
|
||||||
|
@ -825,7 +826,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
isVisible: Boolean = true
|
isVisible: Boolean = true
|
||||||
) {
|
) {
|
||||||
if (activity == null) return
|
if (activity == null) return
|
||||||
loadLinks(result, isVisible = isVisible, isCasting = true) { data ->
|
loadLinks(result, isVisible = isVisible, LoadType.Chromecast) { data ->
|
||||||
startChromecast(activity, result, data.links, data.subs, 0)
|
startChromecast(activity, result, data.links, data.subs, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -936,7 +937,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private fun loadLinks(
|
private fun loadLinks(
|
||||||
result: ResultEpisode,
|
result: ResultEpisode,
|
||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
clearCache: Boolean = false,
|
clearCache: Boolean = false,
|
||||||
work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit)
|
work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit)
|
||||||
) {
|
) {
|
||||||
|
@ -945,7 +946,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val links = loadLinks(
|
val links = loadLinks(
|
||||||
result,
|
result,
|
||||||
isVisible = isVisible,
|
isVisible = isVisible,
|
||||||
isCasting = isCasting,
|
type = type,
|
||||||
clearCache = clearCache
|
clearCache = clearCache
|
||||||
)
|
)
|
||||||
if (!this.isActive) return@ioSafe
|
if (!this.isActive) return@ioSafe
|
||||||
|
@ -956,11 +957,11 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private var currentLoadLinkJob: Job? = null
|
private var currentLoadLinkJob: Job? = null
|
||||||
private fun acquireSingleLink(
|
private fun acquireSingleLink(
|
||||||
result: ResultEpisode,
|
result: ResultEpisode,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
text: UiText,
|
text: UiText,
|
||||||
callback: (Pair<LinkLoadingResult, Int>) -> Unit,
|
callback: (Pair<LinkLoadingResult, Int>) -> Unit,
|
||||||
) {
|
) {
|
||||||
loadLinks(result, isVisible = true, isCasting = isCasting) { links ->
|
loadLinks(result, isVisible = true, type) { links ->
|
||||||
postPopup(
|
postPopup(
|
||||||
text,
|
text,
|
||||||
links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
|
links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
|
||||||
|
@ -971,11 +972,10 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
private fun acquireSingleSubtitle(
|
private fun acquireSingleSubtitle(
|
||||||
result: ResultEpisode,
|
result: ResultEpisode,
|
||||||
isCasting: Boolean,
|
|
||||||
text: UiText,
|
text: UiText,
|
||||||
callback: (Pair<LinkLoadingResult, Int>) -> Unit,
|
callback: (Pair<LinkLoadingResult, Int>) -> Unit,
|
||||||
) {
|
) {
|
||||||
loadLinks(result, isVisible = true, isCasting = isCasting) { links ->
|
loadLinks(result, isVisible = true, type = LoadType.Unknown) { links ->
|
||||||
postPopup(
|
postPopup(
|
||||||
text,
|
text,
|
||||||
links.subs.map { txt(it.name) })
|
links.subs.map { txt(it.name) })
|
||||||
|
@ -988,7 +988,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private suspend fun CoroutineScope.loadLinks(
|
private suspend fun CoroutineScope.loadLinks(
|
||||||
result: ResultEpisode,
|
result: ResultEpisode,
|
||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
isCasting: Boolean,
|
type: LoadType,
|
||||||
clearCache: Boolean = false,
|
clearCache: Boolean = false,
|
||||||
): LinkLoadingResult {
|
): LinkLoadingResult {
|
||||||
val tempGenerator = RepoLinkGenerator(listOf(result))
|
val tempGenerator = RepoLinkGenerator(listOf(result))
|
||||||
|
@ -1002,7 +1002,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
updatePage()
|
updatePage()
|
||||||
tempGenerator.generateLinks(clearCache, isCasting, { (link, _) ->
|
tempGenerator.generateLinks(clearCache, type, { (link, _) ->
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
links += link
|
links += link
|
||||||
updatePage()
|
updatePage()
|
||||||
|
@ -1272,7 +1272,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
acquireSingleSubtitle(
|
acquireSingleSubtitle(
|
||||||
click.data,
|
click.data,
|
||||||
false,
|
|
||||||
txt(R.string.episode_action_download_subtitle)
|
txt(R.string.episode_action_download_subtitle)
|
||||||
) { (links, index) ->
|
) { (links, index) ->
|
||||||
downloadSubtitle(
|
downloadSubtitle(
|
||||||
|
@ -1317,7 +1316,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val response = currentResponse ?: return
|
val response = currentResponse ?: return
|
||||||
acquireSingleLink(
|
acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
false,
|
LoadType.InAppDownload,
|
||||||
txt(R.string.episode_action_download_mirror)
|
txt(R.string.episode_action_download_mirror)
|
||||||
) { (result, index) ->
|
) { (result, index) ->
|
||||||
ioSafe {
|
ioSafe {
|
||||||
|
@ -1347,7 +1346,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
loadLinks(
|
loadLinks(
|
||||||
click.data,
|
click.data,
|
||||||
isVisible = false,
|
isVisible = false,
|
||||||
isCasting = false,
|
type = LoadType.InApp,
|
||||||
clearCache = true
|
clearCache = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1356,7 +1355,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
ACTION_CHROME_CAST_MIRROR -> {
|
ACTION_CHROME_CAST_MIRROR -> {
|
||||||
acquireSingleLink(
|
acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
LoadType.Chromecast,
|
||||||
txt(R.string.episode_action_chromecast_mirror)
|
txt(R.string.episode_action_chromecast_mirror)
|
||||||
) { (result, index) ->
|
) { (result, index) ->
|
||||||
startChromecast(activity, click.data, result.links, result.subs, index)
|
startChromecast(activity, click.data, result.links, result.subs, index)
|
||||||
|
@ -1365,7 +1364,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
|
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
LoadType.Browser,
|
||||||
txt(R.string.episode_action_play_in_browser)
|
txt(R.string.episode_action_play_in_browser)
|
||||||
) { (result, index) ->
|
) { (result, index) ->
|
||||||
try {
|
try {
|
||||||
|
@ -1380,7 +1379,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
ACTION_COPY_LINK -> {
|
ACTION_COPY_LINK -> {
|
||||||
acquireSingleLink(
|
acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
LoadType.ExternalApp,
|
||||||
txt(R.string.episode_action_copy_link)
|
txt(R.string.episode_action_copy_link)
|
||||||
) { (result, index) ->
|
) { (result, index) ->
|
||||||
val act = activity ?: return@acquireSingleLink
|
val act = activity ?: return@acquireSingleLink
|
||||||
|
@ -1399,7 +1398,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
|
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
|
||||||
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
|
loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links ->
|
||||||
if (links.links.isEmpty()) {
|
if (links.links.isEmpty()) {
|
||||||
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||||
return@loadLinks
|
return@loadLinks
|
||||||
|
@ -1415,7 +1414,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
|
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
LoadType.Chromecast,
|
||||||
txt(
|
txt(
|
||||||
R.string.episode_action_play_in_format,
|
R.string.episode_action_play_in_format,
|
||||||
txt(R.string.player_settings_play_in_web)
|
txt(R.string.player_settings_play_in_web)
|
||||||
|
@ -1432,7 +1431,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
|
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
LoadType.Chromecast,
|
||||||
txt(
|
txt(
|
||||||
R.string.episode_action_play_in_format,
|
R.string.episode_action_play_in_format,
|
||||||
txt(R.string.player_settings_play_in_mpv)
|
txt(R.string.player_settings_play_in_mpv)
|
||||||
|
@ -1461,7 +1460,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
it.goto(index)
|
it.goto(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
} ?: return, list
|
} ?: return, list
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -2173,7 +2171,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
trailerData.extractorUrl,
|
trailerData.extractorUrl,
|
||||||
trailerData.referer ?: "",
|
trailerData.referer ?: "",
|
||||||
Qualities.Unknown.value,
|
Qualities.Unknown.value,
|
||||||
trailerData.extractorUrl.contains(".m3u8")
|
type = INFER_TYPE
|
||||||
)
|
)
|
||||||
) to arrayListOf()
|
) to arrayListOf()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,7 +55,11 @@ object CastHelper {
|
||||||
|
|
||||||
val builder = MediaInfo.Builder(link.url)
|
val builder = MediaInfo.Builder(link.url)
|
||||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||||
.setContentType(if (link.isM3u8) MimeTypes.APPLICATION_M3U8 else MimeTypes.VIDEO_MP4)
|
.setContentType(when(link.type) {
|
||||||
|
ExtractorLinkType.M3U8 -> MimeTypes.APPLICATION_M3U8
|
||||||
|
ExtractorLinkType.DASH -> MimeTypes.APPLICATION_MPD
|
||||||
|
else -> MimeTypes.VIDEO_MP4
|
||||||
|
})
|
||||||
.setMetadata(movieMetadata)
|
.setMetadata(movieMetadata)
|
||||||
.setMediaTracks(tracks)
|
.setMediaTracks(tracks)
|
||||||
data?.let {
|
data?.let {
|
||||||
|
|
|
@ -1,12 +1,204 @@
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.TvType
|
||||||
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.extractors.AStreamHub
|
||||||
|
import com.lagradost.cloudstream3.extractors.Acefile
|
||||||
|
import com.lagradost.cloudstream3.extractors.Ahvsh
|
||||||
|
import com.lagradost.cloudstream3.extractors.Aico
|
||||||
|
import com.lagradost.cloudstream3.extractors.AsianLoad
|
||||||
|
import com.lagradost.cloudstream3.extractors.Bestx
|
||||||
|
import com.lagradost.cloudstream3.extractors.Blogger
|
||||||
|
import com.lagradost.cloudstream3.extractors.BullStream
|
||||||
|
import com.lagradost.cloudstream3.extractors.ByteShare
|
||||||
|
import com.lagradost.cloudstream3.extractors.Cda
|
||||||
|
import com.lagradost.cloudstream3.extractors.Cdnplayer
|
||||||
|
import com.lagradost.cloudstream3.extractors.Chillx
|
||||||
|
import com.lagradost.cloudstream3.extractors.CineGrabber
|
||||||
|
import com.lagradost.cloudstream3.extractors.Cinestart
|
||||||
|
import com.lagradost.cloudstream3.extractors.DBfilm
|
||||||
|
import com.lagradost.cloudstream3.extractors.Dailymotion
|
||||||
|
import com.lagradost.cloudstream3.extractors.DatabaseGdrive
|
||||||
|
import com.lagradost.cloudstream3.extractors.DatabaseGdrive2
|
||||||
|
import com.lagradost.cloudstream3.extractors.DesuArcg
|
||||||
|
import com.lagradost.cloudstream3.extractors.DesuDrive
|
||||||
|
import com.lagradost.cloudstream3.extractors.DesuOdchan
|
||||||
|
import com.lagradost.cloudstream3.extractors.DesuOdvip
|
||||||
|
import com.lagradost.cloudstream3.extractors.Dokicloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodCxExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodLaExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodPmExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodShExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodSoExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodToExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodWatchExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodWfExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodWsExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.DoodYtExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.Dooood
|
||||||
|
import com.lagradost.cloudstream3.extractors.Embedgram
|
||||||
|
import com.lagradost.cloudstream3.extractors.Evoload
|
||||||
|
import com.lagradost.cloudstream3.extractors.Evoload1
|
||||||
|
import com.lagradost.cloudstream3.extractors.FEmbed
|
||||||
|
import com.lagradost.cloudstream3.extractors.FEnet
|
||||||
|
import com.lagradost.cloudstream3.extractors.Fastream
|
||||||
|
import com.lagradost.cloudstream3.extractors.FeHD
|
||||||
|
import com.lagradost.cloudstream3.extractors.Fembed9hd
|
||||||
|
import com.lagradost.cloudstream3.extractors.FileMoon
|
||||||
|
import com.lagradost.cloudstream3.extractors.FileMoonIn
|
||||||
|
import com.lagradost.cloudstream3.extractors.FileMoonSx
|
||||||
|
import com.lagradost.cloudstream3.extractors.Filesim
|
||||||
|
import com.lagradost.cloudstream3.extractors.Fplayer
|
||||||
|
import com.lagradost.cloudstream3.extractors.GMPlayer
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayer
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerapi
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerapp
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerbiz
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerco
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerfun
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerio
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerme
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerorg
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gdriveplayerus
|
||||||
|
import com.lagradost.cloudstream3.extractors.Gofile
|
||||||
|
import com.lagradost.cloudstream3.extractors.GuardareStream
|
||||||
|
import com.lagradost.cloudstream3.extractors.Guccihide
|
||||||
|
import com.lagradost.cloudstream3.extractors.Hxfile
|
||||||
|
import com.lagradost.cloudstream3.extractors.JWPlayer
|
||||||
|
import com.lagradost.cloudstream3.extractors.Jawcloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.Jeniusplay
|
||||||
|
import com.lagradost.cloudstream3.extractors.Keephealth
|
||||||
|
import com.lagradost.cloudstream3.extractors.KotakAnimeid
|
||||||
|
import com.lagradost.cloudstream3.extractors.Kotakajair
|
||||||
|
import com.lagradost.cloudstream3.extractors.Krakenfiles
|
||||||
|
import com.lagradost.cloudstream3.extractors.LayarKaca
|
||||||
|
import com.lagradost.cloudstream3.extractors.Linkbox
|
||||||
|
import com.lagradost.cloudstream3.extractors.Luxubu
|
||||||
|
import com.lagradost.cloudstream3.extractors.Lvturbo
|
||||||
|
import com.lagradost.cloudstream3.extractors.Maxstream
|
||||||
|
import com.lagradost.cloudstream3.extractors.Mcloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.Megacloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.Meownime
|
||||||
|
import com.lagradost.cloudstream3.extractors.MixDrop
|
||||||
|
import com.lagradost.cloudstream3.extractors.MixDropBz
|
||||||
|
import com.lagradost.cloudstream3.extractors.MixDropCh
|
||||||
|
import com.lagradost.cloudstream3.extractors.MixDropTo
|
||||||
|
import com.lagradost.cloudstream3.extractors.Movhide
|
||||||
|
import com.lagradost.cloudstream3.extractors.Moviehab
|
||||||
|
import com.lagradost.cloudstream3.extractors.MoviehabNet
|
||||||
|
import com.lagradost.cloudstream3.extractors.Moviesapi
|
||||||
|
import com.lagradost.cloudstream3.extractors.Moviesm4u
|
||||||
|
import com.lagradost.cloudstream3.extractors.Mp4Upload
|
||||||
|
import com.lagradost.cloudstream3.extractors.Mvidoo
|
||||||
|
import com.lagradost.cloudstream3.extractors.MwvnVizcloudInfo
|
||||||
|
import com.lagradost.cloudstream3.extractors.Neonime7n
|
||||||
|
import com.lagradost.cloudstream3.extractors.Neonime8n
|
||||||
|
import com.lagradost.cloudstream3.extractors.OkRu
|
||||||
|
import com.lagradost.cloudstream3.extractors.OkRuHttps
|
||||||
|
import com.lagradost.cloudstream3.extractors.Okrulink
|
||||||
|
import com.lagradost.cloudstream3.extractors.Pixeldrain
|
||||||
|
import com.lagradost.cloudstream3.extractors.PlayLtXyz
|
||||||
|
import com.lagradost.cloudstream3.extractors.PlayerVoxzer
|
||||||
|
import com.lagradost.cloudstream3.extractors.Rabbitstream
|
||||||
|
import com.lagradost.cloudstream3.extractors.Rasacintaku
|
||||||
|
import com.lagradost.cloudstream3.extractors.SBfull
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbasian
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbface
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbflix
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sblona
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sblongvu
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbnet
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbrapid
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbsonic
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbspeed
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sbthe
|
||||||
|
import com.lagradost.cloudstream3.extractors.Sendvid
|
||||||
|
import com.lagradost.cloudstream3.extractors.ShaveTape
|
||||||
|
import com.lagradost.cloudstream3.extractors.Solidfiles
|
||||||
|
import com.lagradost.cloudstream3.extractors.SpeedoStream
|
||||||
|
import com.lagradost.cloudstream3.extractors.SpeedoStream1
|
||||||
|
import com.lagradost.cloudstream3.extractors.SpeedoStream2
|
||||||
|
import com.lagradost.cloudstream3.extractors.Ssbstream
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamM4u
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB1
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB10
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB11
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB2
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB3
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB4
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB5
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB6
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB7
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB8
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamSB9
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamTape
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamTapeNet
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamhideCom
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamhideTo
|
||||||
|
import com.lagradost.cloudstream3.extractors.Streamhub2
|
||||||
|
import com.lagradost.cloudstream3.extractors.Streamlare
|
||||||
|
import com.lagradost.cloudstream3.extractors.StreamoUpload
|
||||||
|
import com.lagradost.cloudstream3.extractors.Streamplay
|
||||||
|
import com.lagradost.cloudstream3.extractors.Streamsss
|
||||||
|
import com.lagradost.cloudstream3.extractors.Supervideo
|
||||||
|
import com.lagradost.cloudstream3.extractors.Tantifilm
|
||||||
|
import com.lagradost.cloudstream3.extractors.Tomatomatela
|
||||||
|
import com.lagradost.cloudstream3.extractors.TomatomatelalClub
|
||||||
|
import com.lagradost.cloudstream3.extractors.Tubeless
|
||||||
|
import com.lagradost.cloudstream3.extractors.Upstream
|
||||||
|
import com.lagradost.cloudstream3.extractors.UpstreamExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.Uqload
|
||||||
|
import com.lagradost.cloudstream3.extractors.Uqload1
|
||||||
|
import com.lagradost.cloudstream3.extractors.Uqload2
|
||||||
|
import com.lagradost.cloudstream3.extractors.Userload
|
||||||
|
import com.lagradost.cloudstream3.extractors.Userscloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.Uservideo
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vanfem
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vicloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.VidSrcExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.VidSrcExtractor2
|
||||||
|
import com.lagradost.cloudstream3.extractors.VideoVard
|
||||||
|
import com.lagradost.cloudstream3.extractors.VideovardSX
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vidgomunime
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vidgomunimesb
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vidmoly
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vidmolyme
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vido
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vidstreamz
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vizcloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.Vizcloud2
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudCloud
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudDigital
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudInfo
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudLive
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudOnline
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudSite
|
||||||
|
import com.lagradost.cloudstream3.extractors.VizcloudXyz
|
||||||
|
import com.lagradost.cloudstream3.extractors.Voe
|
||||||
|
import com.lagradost.cloudstream3.extractors.Watchx
|
||||||
|
import com.lagradost.cloudstream3.extractors.WcoStream
|
||||||
|
import com.lagradost.cloudstream3.extractors.Wibufile
|
||||||
|
import com.lagradost.cloudstream3.extractors.XStreamCdn
|
||||||
|
import com.lagradost.cloudstream3.extractors.YourUpload
|
||||||
|
import com.lagradost.cloudstream3.extractors.YoutubeExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.YoutubeMobileExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.YoutubeNoCookieExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.YoutubeShortLinkExtractor
|
||||||
|
import com.lagradost.cloudstream3.extractors.Yufiles
|
||||||
|
import com.lagradost.cloudstream3.extractors.Zorofile
|
||||||
|
import com.lagradost.cloudstream3.extractors.Zplayer
|
||||||
|
import com.lagradost.cloudstream3.extractors.ZplayerV2
|
||||||
|
import com.lagradost.cloudstream3.extractors.Ztreamhub
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.extractors.*
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import kotlin.collections.MutableList
|
import java.net.URL
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For use in the ConcatenatingMediaSource.
|
* For use in the ConcatenatingMediaSource.
|
||||||
|
@ -35,22 +227,145 @@ data class ExtractorLinkPlayList(
|
||||||
val playlist: List<PlayListItem>,
|
val playlist: List<PlayListItem>,
|
||||||
override val referer: String,
|
override val referer: String,
|
||||||
override val quality: Int,
|
override val quality: Int,
|
||||||
override val isM3u8: Boolean = false,
|
val isM3u8: Boolean = false,
|
||||||
override val headers: Map<String, String> = mapOf(),
|
override val headers: Map<String, String> = mapOf(),
|
||||||
/** Used for getExtractorVerifierJob() */
|
/** Used for getExtractorVerifierJob() */
|
||||||
override val extractorData: String? = null,
|
override val extractorData: String? = null,
|
||||||
|
override val type: ExtractorLinkType,
|
||||||
) : ExtractorLink(
|
) : ExtractorLink(
|
||||||
source,
|
source = source,
|
||||||
name,
|
name = name,
|
||||||
// Blank as un-used
|
url = "",
|
||||||
"",
|
referer = referer,
|
||||||
referer,
|
quality = quality,
|
||||||
quality,
|
headers = headers,
|
||||||
isM3u8,
|
extractorData = extractorData,
|
||||||
headers,
|
type = type
|
||||||
extractorData
|
) {
|
||||||
|
constructor(
|
||||||
|
source: String,
|
||||||
|
name: String,
|
||||||
|
playlist: List<PlayListItem>,
|
||||||
|
referer: String,
|
||||||
|
quality: Int,
|
||||||
|
isM3u8: Boolean = false,
|
||||||
|
headers: Map<String, String> = mapOf(),
|
||||||
|
extractorData: String? = null,
|
||||||
|
) : this(
|
||||||
|
source = source,
|
||||||
|
name = name,
|
||||||
|
playlist = playlist,
|
||||||
|
referer = referer,
|
||||||
|
quality = quality,
|
||||||
|
type = if (isM3u8) ExtractorLinkType.M3U8 else ExtractorLinkType.VIDEO,
|
||||||
|
headers = headers,
|
||||||
|
extractorData = extractorData,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Metadata about the file type used for downloads and exoplayer hint,
|
||||||
|
* if you respond with the wrong one the file will fail to download or be played */
|
||||||
|
enum class ExtractorLinkType {
|
||||||
|
/** Single stream of bytes no matter the actual file type */
|
||||||
|
VIDEO,
|
||||||
|
/** Split into several .ts files, has support for encrypted m3u8s */
|
||||||
|
M3U8,
|
||||||
|
/** Like m3u8 but uses xml, currently no download support */
|
||||||
|
DASH,
|
||||||
|
/** No support at the moment */
|
||||||
|
TORRENT,
|
||||||
|
/** No support at the moment */
|
||||||
|
MAGNET,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
||||||
|
val path = normalSafeApiCall { URL(url).path }
|
||||||
|
return when {
|
||||||
|
path?.endsWith(".m3u8") == true -> ExtractorLinkType.M3U8
|
||||||
|
path?.endsWith(".mpd") == true -> ExtractorLinkType.DASH
|
||||||
|
path?.endsWith(".torrent") == true -> ExtractorLinkType.TORRENT
|
||||||
|
url.startsWith("magnet:") -> ExtractorLinkType.MAGNET
|
||||||
|
else -> ExtractorLinkType.VIDEO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val INFER_TYPE : ExtractorLinkType? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID for the ClearKey DRM scheme.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up.
|
||||||
|
*/
|
||||||
|
val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID for the Widevine DRM scheme.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Widevine is supported on Android devices running Android 4.3 (API Level 18) and up.
|
||||||
|
*/
|
||||||
|
val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID for the PlayReady DRM scheme.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not
|
||||||
|
* provide PlayReady support.
|
||||||
|
*/
|
||||||
|
val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL)
|
||||||
|
|
||||||
|
open class DrmExtractorLink private constructor(
|
||||||
|
override val source: String,
|
||||||
|
override val name: String,
|
||||||
|
override val url: String,
|
||||||
|
override val referer: String,
|
||||||
|
override val quality: Int,
|
||||||
|
override val headers: Map<String, String> = mapOf(),
|
||||||
|
/** Used for getExtractorVerifierJob() */
|
||||||
|
override val extractorData: String? = null,
|
||||||
|
override val type: ExtractorLinkType,
|
||||||
|
open val kid : String,
|
||||||
|
open val key : String,
|
||||||
|
open val uuid : UUID,
|
||||||
|
open val kty : String,
|
||||||
|
|
||||||
|
open val keyRequestParameters : HashMap<String, String>
|
||||||
|
) : ExtractorLink(
|
||||||
|
source, name, url, referer, quality, type, headers, extractorData
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
source: String,
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
referer: String,
|
||||||
|
quality: Int,
|
||||||
|
/** the type of the media, use INFER_TYPE if you want to auto infer the type from the url */
|
||||||
|
type: ExtractorLinkType?,
|
||||||
|
headers: Map<String, String> = mapOf(),
|
||||||
|
/** Used for getExtractorVerifierJob() */
|
||||||
|
extractorData: String? = null,
|
||||||
|
kid : String,
|
||||||
|
key : String,
|
||||||
|
uuid : UUID = CLEARKEY_UUID,
|
||||||
|
kty : String = "oct",
|
||||||
|
keyRequestParameters : HashMap<String, String> = hashMapOf(),
|
||||||
|
) : this(
|
||||||
|
source = source,
|
||||||
|
name = name,
|
||||||
|
url = url,
|
||||||
|
referer = referer,
|
||||||
|
quality = quality,
|
||||||
|
headers = headers,
|
||||||
|
extractorData = extractorData,
|
||||||
|
type = type ?: inferTypeFromUrl(url),
|
||||||
|
kid = kid,
|
||||||
|
key = key,
|
||||||
|
uuid = uuid,
|
||||||
|
keyRequestParameters = keyRequestParameters,
|
||||||
|
kty = kty,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
open class ExtractorLink constructor(
|
open class ExtractorLink constructor(
|
||||||
open val source: String,
|
open val source: String,
|
||||||
|
@ -58,12 +373,33 @@ open class ExtractorLink constructor(
|
||||||
override val url: String,
|
override val url: String,
|
||||||
override val referer: String,
|
override val referer: String,
|
||||||
open val quality: Int,
|
open val quality: Int,
|
||||||
open val isM3u8: Boolean = false,
|
|
||||||
override val headers: Map<String, String> = mapOf(),
|
override val headers: Map<String, String> = mapOf(),
|
||||||
/** Used for getExtractorVerifierJob() */
|
/** Used for getExtractorVerifierJob() */
|
||||||
open val extractorData: String? = null,
|
open val extractorData: String? = null,
|
||||||
open val isDash: Boolean = false,
|
open val type: ExtractorLinkType,
|
||||||
) : VideoDownloadManager.IDownloadableMinimum {
|
) : VideoDownloadManager.IDownloadableMinimum {
|
||||||
|
constructor(
|
||||||
|
source: String,
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
referer: String,
|
||||||
|
quality: Int,
|
||||||
|
/** the type of the media, use INFER_TYPE if you want to auto infer the type from the url */
|
||||||
|
type: ExtractorLinkType?,
|
||||||
|
headers: Map<String, String> = mapOf(),
|
||||||
|
/** Used for getExtractorVerifierJob() */
|
||||||
|
extractorData: String? = null,
|
||||||
|
) : this(
|
||||||
|
source = source,
|
||||||
|
name = name,
|
||||||
|
url = url,
|
||||||
|
referer = referer,
|
||||||
|
quality = quality,
|
||||||
|
headers = headers,
|
||||||
|
extractorData = extractorData,
|
||||||
|
type = type ?: inferTypeFromUrl(url)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Old constructor without isDash, allows for backwards compatibility with extensions.
|
* Old constructor without isDash, allows for backwards compatibility with extensions.
|
||||||
* Should be removed after all extensions have updated their cloudstream.jar
|
* Should be removed after all extensions have updated their cloudstream.jar
|
||||||
|
@ -80,8 +416,30 @@ open class ExtractorLink constructor(
|
||||||
extractorData: String? = null
|
extractorData: String? = null
|
||||||
) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false)
|
) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
source: String,
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
referer: String,
|
||||||
|
quality: Int,
|
||||||
|
isM3u8: Boolean = false,
|
||||||
|
headers: Map<String, String> = mapOf(),
|
||||||
|
/** Used for getExtractorVerifierJob() */
|
||||||
|
extractorData: String? = null,
|
||||||
|
isDash: Boolean,
|
||||||
|
) : this(
|
||||||
|
source = source,
|
||||||
|
name = name,
|
||||||
|
url = url,
|
||||||
|
referer = referer,
|
||||||
|
quality = quality,
|
||||||
|
headers = headers,
|
||||||
|
extractorData = extractorData,
|
||||||
|
type = if (isDash) ExtractorLinkType.DASH else if (isM3u8) ExtractorLinkType.M3U8 else ExtractorLinkType.VIDEO
|
||||||
|
)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)"
|
return "ExtractorLink(name=$name, url=$url, referer=$referer, type=$type)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +493,7 @@ enum class Qualities(var value: Int, val defaultPriority: Int) {
|
||||||
else -> "${qual}p"
|
else -> "${qual}p"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStringByIntFull(quality: Int): String {
|
fun getStringByIntFull(quality: Int): String {
|
||||||
return when (quality) {
|
return when (quality) {
|
||||||
0 -> "Auto"
|
0 -> "Auto"
|
||||||
|
|
|
@ -53,7 +53,7 @@ import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.net.URL
|
import java.lang.IllegalArgumentException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general"
|
const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general"
|
||||||
|
@ -951,7 +951,10 @@ object VideoDownloadManager {
|
||||||
/** how many bytes every connection should be, by default it is 10 MiB */
|
/** how many bytes every connection should be, by default it is 10 MiB */
|
||||||
chuckSize: Long = (1 shl 20) * 10,
|
chuckSize: Long = (1 shl 20) * 10,
|
||||||
/** maximum bytes in the buffer that responds */
|
/** maximum bytes in the buffer that responds */
|
||||||
bufferSize: Int = DEFAULT_BUFFER_SIZE
|
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||||
|
/** how many bytes bytes it should require to use the parallel downloader instead,
|
||||||
|
* if we download a very small file we don't want it parallel */
|
||||||
|
maximumSmallSize : Long = chuckSize * 2
|
||||||
): LazyStreamDownloadData {
|
): LazyStreamDownloadData {
|
||||||
// we don't want to make a separate connection for every 1kb
|
// we don't want to make a separate connection for every 1kb
|
||||||
require(chuckSize > 1000)
|
require(chuckSize > 1000)
|
||||||
|
@ -963,7 +966,7 @@ object VideoDownloadManager {
|
||||||
var downloadLength: Long? = null
|
var downloadLength: Long? = null
|
||||||
var totalLength: Long? = null
|
var totalLength: Long? = null
|
||||||
|
|
||||||
val ranges = if (contentLength == null) {
|
val ranges = if (contentLength == null || contentLength < maximumSmallSize) {
|
||||||
// is the equivalent of [startByte..EOF] as we don't know the size we can only do one
|
// is the equivalent of [startByte..EOF] as we don't know the size we can only do one
|
||||||
// connection
|
// connection
|
||||||
LongArray(1) { startByte }
|
LongArray(1) { startByte }
|
||||||
|
@ -1024,6 +1027,7 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** download a file that consist of a single stream of data*/
|
||||||
suspend fun downloadThing(
|
suspend fun downloadThing(
|
||||||
context: Context,
|
context: Context,
|
||||||
link: IDownloadableMinimum,
|
link: IDownloadableMinimum,
|
||||||
|
@ -1035,8 +1039,7 @@ object VideoDownloadManager {
|
||||||
createNotificationCallback: (CreateNotificationMetadata) -> Unit,
|
createNotificationCallback: (CreateNotificationMetadata) -> Unit,
|
||||||
parallelConnections: Int = 3
|
parallelConnections: Int = 3
|
||||||
): DownloadStatus = withContext(Dispatchers.IO) {
|
): DownloadStatus = withContext(Dispatchers.IO) {
|
||||||
// we cant download torrents with this implementation, aria2c might be used in the future
|
if (parallelConnections < 1) {
|
||||||
if (link.url.startsWith("magnet") || link.url.endsWith(".torrent") || parallelConnections < 1) {
|
|
||||||
return@withContext DOWNLOAD_INVALID_INPUT
|
return@withContext DOWNLOAD_INVALID_INPUT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1529,6 +1532,11 @@ object VideoDownloadManager {
|
||||||
notificationCallback: (Int, Notification) -> Unit,
|
notificationCallback: (Int, Notification) -> Unit,
|
||||||
tryResume: Boolean = false,
|
tryResume: Boolean = false,
|
||||||
): DownloadStatus {
|
): DownloadStatus {
|
||||||
|
// no support for these file formats
|
||||||
|
if(link.type == ExtractorLinkType.MAGNET || link.type == ExtractorLinkType.TORRENT || link.type == ExtractorLinkType.DASH) {
|
||||||
|
return DOWNLOAD_INVALID_INPUT
|
||||||
|
}
|
||||||
|
|
||||||
val name = getFileName(context, ep)
|
val name = getFileName(context, ep)
|
||||||
|
|
||||||
// Make sure this is cancelled when download is done or cancelled.
|
// Make sure this is cancelled when download is done or cancelled.
|
||||||
|
@ -1557,7 +1565,8 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (link.isM3u8 || normalSafeApiCall { URL(link.url).path.endsWith(".m3u8") } == true) {
|
when(link.type) {
|
||||||
|
ExtractorLinkType.M3U8 -> {
|
||||||
val startIndex = if (tryResume) {
|
val startIndex = if (tryResume) {
|
||||||
context.getKey<DownloadedFileInfo>(
|
context.getKey<DownloadedFileInfo>(
|
||||||
KEY_DOWNLOAD_INFO,
|
KEY_DOWNLOAD_INFO,
|
||||||
|
@ -1575,7 +1584,8 @@ object VideoDownloadManager {
|
||||||
startIndex,
|
startIndex,
|
||||||
callback, parallelConnections = maxConcurrentConnections
|
callback, parallelConnections = maxConcurrentConnections
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
ExtractorLinkType.VIDEO -> {
|
||||||
return downloadThing(
|
return downloadThing(
|
||||||
context,
|
context,
|
||||||
link,
|
link,
|
||||||
|
@ -1587,6 +1597,8 @@ object VideoDownloadManager {
|
||||||
callback, parallelConnections = maxConcurrentConnections
|
callback, parallelConnections = maxConcurrentConnections
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
else -> throw IllegalArgumentException("unsuported download type")
|
||||||
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
return DOWNLOAD_FAILED
|
return DOWNLOAD_FAILED
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue