chromecast stuff

This commit is contained in:
LagradOst 2021-06-14 18:58:43 +02:00
parent 6579eb3083
commit 50abf2c74c
14 changed files with 323 additions and 118 deletions

View file

@ -53,7 +53,7 @@ abstract class MainAPI {
}
// callback is fired once a link is found, will return true if method is executed successfully
open fun loadLinks(data: Any, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
open fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
return false
}
}
@ -169,8 +169,8 @@ data class AnimeLoadResponse(
override val posterUrl: String?,
override val year: Int?,
val dubEpisodes: ArrayList<Any>?,
val subEpisodes: ArrayList<Any>?,
val dubEpisodes: ArrayList<String>?,
val subEpisodes: ArrayList<String>?,
val showStatus: ShowStatus?,
override val plot: String?,

View file

@ -2,32 +2,27 @@ package com.lagradost.cloudstream3
import android.app.PictureInPictureParams
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus
import com.google.android.gms.cast.ApplicationMetadata
import com.google.android.gms.cast.Cast
import com.google.android.gms.cast.LaunchOptions
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.lagradost.cloudstream3.UIHelper.checkWrite
import com.lagradost.cloudstream3.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.UIHelper.requestRW
import com.lagradost.cloudstream3.UIHelper.shouldShowPIPMode
import kotlinx.android.synthetic.main.fragment_result.*
class MainActivity : AppCompatActivity() {
/*, ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
@ -110,8 +105,8 @@ class MainActivity : AppCompatActivity() {
requestRW()
if (checkWrite()) return
}
CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
/*
val castContext = CastContext.getSharedInstance(applicationContext)
fun buildMediaQueueItem(video: String): MediaQueueItem {

View file

@ -178,7 +178,7 @@ class ShiroProvider : MainAPI() {
val mapped = response.let { mapper.readValue<AnimePage>(it.text) }
val data = mapped.data
val isDubbed = data.language == "dubbed"
val episodes = ArrayList<Any>(data.episodes ?: ArrayList())
val episodes = ArrayList<String>(data.episodes?.map { it.videos[0].video_id } ?: ArrayList<String>())
val status = when (data.status) {
"current" -> ShowStatus.Ongoing
"finished" -> ShowStatus.Completed
@ -205,12 +205,9 @@ class ShiroProvider : MainAPI() {
)
}
override fun loadLinks(data: Any, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
if (data is ShiroEpisodes) {
return Vidstream().getUrl(data.videos[0].video_id, isCasting) {
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
return Vidstream().getUrl(data, isCasting) {
callback.invoke(it)
}
}
return false
}
}

View file

@ -9,19 +9,25 @@ import androidx.appcompat.app.AlertDialog
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_SINGLE
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.uicontroller.UIController
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.Coroutines
import kotlinx.coroutines.delay
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.sortUrls
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.lang.Exception
class SkipOpController(val view: ImageView) : UIController() {
init {
@ -32,8 +38,14 @@ class SkipOpController(val view: ImageView) : UIController() {
}
}
data class MetadataSource(val name: String)
data class MetadataHolder(val data: List<MetadataSource>)
data class MetadataHolder(
val apiName: String,
val title: String?,
val poster: String?,
val currentEpisodeIndex: Int,
val episodes: List<ResultEpisode>,
val currentLinks: List<ExtractorLink>,
)
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
@ -42,30 +54,48 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
init {
view.setImageResource(R.drawable.ic_baseline_playlist_play_24)
view.setOnClickListener {
//remoteMediaClient.mediaQueue.itemCount
//println(remoteMediaClient.mediaInfo.customData)
//remoteMediaClient.queueJumpToItem()
lateinit var dialog: AlertDialog
val holder = getCurrentMetaData()
if (holder != null) {
val items = holder.data
val items = holder.currentLinks
if (items.isNotEmpty() && remoteMediaClient.currentItem != null) {
val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom)
builder.setTitle("Pick source")
//https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation
val contentUrl = (remoteMediaClient.currentItem.media.contentUrl
?: remoteMediaClient.currentItem.media.contentId)
builder.setSingleChoiceItems(
items.map { it.name }.toTypedArray(),
remoteMediaClient.mediaQueue.indexOfItemWithId(remoteMediaClient.currentItem.itemId)
items.indexOfFirst { it.url == contentUrl }
) { _, which ->
val itemId = remoteMediaClient.mediaQueue.itemIds?.get(which)
val epData = holder.episodes[holder.currentEpisodeIndex]
itemId?.let { id ->
remoteMediaClient.queueJumpToItem(
id,
remoteMediaClient.approximateStreamPosition,
remoteMediaClient.mediaInfo.customData
)
val mediaItem = getMediaInfo(epData,
holder,
holder.currentLinks[which],
remoteMediaClient.mediaInfo.customData)
val startAt = remoteMediaClient.approximateStreamPosition
try {
val currentIdIndex = getItemIndex() ?: return@setSingleChoiceItems
val nextId = remoteMediaClient.mediaQueue.itemIds?.get(currentIdIndex + 1)
if (nextId != null) {
remoteMediaClient.queueInsertAndPlayItem(MediaQueueItem.Builder(mediaItem).build(),
nextId,
startAt,
JSONObject())
} else {
remoteMediaClient.load(mediaItem, true, startAt)
}
} catch (e: Exception) {
remoteMediaClient.load(mediaItem, true, startAt)
}
dialog.dismiss()
@ -77,26 +107,78 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
}
}
private fun getItemIndex(): Int? {
val index = remoteMediaClient?.mediaQueue?.itemIds?.indexOf(remoteMediaClient.currentItem.itemId)
return if (index == null || index < 0) null else index
}
private fun getCurrentMetaData(): MetadataHolder? {
return try {
val data = remoteMediaClient.mediaInfo.customData
mapper.readValue<MetadataHolder>(data.toString())
val data = remoteMediaClient?.mediaInfo?.customData?.toString()
data?.toKotlinObject()
} catch (e: Exception) {
null
}
}
var isLoadingMore = false
override fun onMediaStatusUpdated() {
super.onMediaStatusUpdated()
view.visibility =
if ((getCurrentMetaData()?.data?.size
val meta = getCurrentMetaData()
view.visibility = if ((meta?.currentLinks?.size
?: 0) > 1
) VISIBLE else INVISIBLE
try {
if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) {
val currentIdIndex = getItemIndex() ?: return
val itemCount = remoteMediaClient?.mediaQueue?.itemCount
if (itemCount != null && itemCount - currentIdIndex == 1 && !isLoadingMore) {
isLoadingMore = true
main {
val index = meta.currentEpisodeIndex + 1
val epData = meta.episodes[index]
val links = ArrayList<ExtractorLink>()
val res = safeApiCall {
getApiFromName(meta.apiName).loadLinks(epData.data, true) {
for (i in links) {
if (i.url == it.url) return@loadLinks
}
links.add(it)
}
}
if (res is Resource.Success) {
val sorted = sortUrls(links)
if (sorted.isNotEmpty()) {
val jsonCopy = meta.copy(currentLinks = sorted, currentEpisodeIndex = index)
val done = withContext(Dispatchers.IO) {
getMediaInfo(epData,
meta,
sorted.first(),
JSONObject(mapper.writeValueAsString(jsonCopy)))
}
remoteMediaClient?.queueAppendItem(MediaQueueItem.Builder(done).build(), JSONObject())
isLoadingMore = false
}
}
}
}
}
} catch (e: Exception) {
println(e)
}
}
override fun onSessionConnected(castSession: CastSession?) {
super.onSessionConnected(castSession)
remoteMediaClient.queueSetRepeatMode(REPEAT_MODE_REPEAT_SINGLE, JSONObject())
remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject())
}
}

View file

@ -74,9 +74,11 @@ import com.lagradost.cloudstream3.UIHelper.requestLocalAudioFocus
import com.lagradost.cloudstream3.UIHelper.showSystemUI
import com.lagradost.cloudstream3.UIHelper.toPx
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeDirectly
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.ResultViewModel
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -142,6 +144,8 @@ class PlayerFragment : Fragment() {
private val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
lateinit var apiName: String
private var isFullscreen = false
private var isPlayerPlaying = true
private lateinit var viewModel: ResultViewModel
@ -733,7 +737,17 @@ class PlayerFragment : Fragment() {
val epData = getEpisode() ?: return@addCastStateListener
val index = links.indexOf(getCurrentUrl())
context?.startCast(
apiName,
currentHeaderName,
currentPoster,
epData.index,
episodes,
links,
index,
exoPlayer.currentPosition)
/*
val customData =
links.map { JSONObject().put("name", it.name) }
val jsonArray = JSONArray()
@ -773,8 +787,9 @@ class PlayerFragment : Fragment() {
if (index > 0) index else 0,
exoPlayer.currentPosition,
MediaStatus.REPEAT_MODE_REPEAT_SINGLE
)
)*/
// activity?.popCurrentPage(isInPlayer = true, isInExpandedView = false, isInResults = false)
releasePlayer()
activity?.popCurrentPage()
}
}
@ -820,6 +835,10 @@ class PlayerFragment : Fragment() {
}
}
observe(viewModel.apiName) {
apiName = it
}
overlay_loading_skip_button?.alpha = 0.5f
observeDirectly(viewModel.allEpisodes) { _allEpisodes ->
allEpisodes = _allEpisodes

View file

@ -20,11 +20,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.fasterxml.jackson.annotation.JsonProperty
import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_SINGLE
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
@ -38,6 +40,7 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.player.PlayerData
import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.ExtractorLink
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.*
@ -52,7 +55,7 @@ data class ResultEpisode(
val poster: String?,
val episode: Int,
val season: Int?,
val data: Any,
val data: String,
val apiName: String,
val id: Int,
val index: Int,
@ -80,6 +83,7 @@ class ResultFragment : Fragment() {
private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
var currentHeaderName: String? = null
var currentEpisodes: ArrayList<ResultEpisode>? = null
override fun onCreateView(
inflater: LayoutInflater,
@ -142,28 +146,35 @@ class ResultFragment : Fragment() {
ArrayList(),
result_episodes,
) { episodeClick ->
val id = episodeClick.data.id
//val id = episodeClick.data.id
val index = episodeClick.data.index
val buildInPlayer = true
when (episodeClick.action) {
ACTION_CHROME_CAST_EPISODE -> {
/*
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null);
builder.setView(customLayout)
val dialog = builder.create()*/
//dialog.show()
val dialog = builder.create()
dialog.show()
Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
viewModel.loadEpisode(episodeClick.data, false) { data ->
// dialog.dismiss()
viewModel.loadEpisode(episodeClick.data, true) { data ->
dialog.dismiss()
when (data) {
is Resource.Failure -> {
Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show()
}
is Resource.Success -> {
val eps = currentEpisodes ?: return@loadEpisode
context?.startCast(
apiName ?: return@loadEpisode,
currentHeaderName,
currentPoster,
episodeClick.data.index,
eps,
sortUrls(data.value))
/*
val epData = episodeClick.data
val links = sortUrls(data.value)
@ -200,12 +211,14 @@ class ResultFragment : Fragment() {
}.toTypedArray()
val castPlayer = CastPlayer(castContext)
castPlayer.loadItems(
mediaItems,
0,
epData.progress,
REPEAT_MODE_REPEAT_SINGLE
)
//REPEAT_MODE_REPEAT_SINGLE
)*/
}
}
}
@ -244,6 +257,7 @@ class ResultFragment : Fragment() {
observe(viewModel.episodes) { episodes ->
if (result_episodes == null || result_episodes.adapter == null) return@observe
result_episodes_text.text = "${episodes.size} Episode${if (episodes.size == 1) "" else "s"}"
currentEpisodes = episodes
(result_episodes.adapter as EpisodeAdapter).cardList = episodes
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
}

View file

@ -82,7 +82,8 @@ class ResultViewModel : ViewModel() {
d.apiName,
(d.url).hashCode(),
0,
0, 0)))
0, 0,
)))
}
}
}
@ -99,6 +100,8 @@ class ResultViewModel : ViewModel() {
val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes
private var _apiName: MutableLiveData<String> = MutableLiveData()
val apiName: LiveData<String> get() = _apiName
fun loadEpisode(
episode: ResultEpisode,
@ -110,7 +113,7 @@ class ResultViewModel : ViewModel() {
private fun loadEpisode(
id: Int,
data: Any,
data: String,
isCasting: Boolean,
callback: (Resource<ArrayList<ExtractorLink>>) -> Unit,
) =
@ -124,7 +127,6 @@ class ResultViewModel : ViewModel() {
for (i in links) {
if (i.url == it.url) return@loadLinks
}
println("LINK ADDED::::: " + it.url)
links.add(it)
_allEpisodes.value?.set(id, links)

View file

@ -0,0 +1,95 @@
package com.lagradost.cloudstream3.utils
import android.content.Context
import android.net.Uri
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.gms.cast.CastStatusCodes
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.common.api.PendingResult
import com.google.android.gms.common.images.WebImage
import com.lagradost.cloudstream3.ui.MetadataHolder
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.Coroutines.main
import kotlinx.coroutines.awaitAll
import org.json.JSONObject
import kotlin.concurrent.thread
object CastHelper {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
fun getMediaInfo(epData: ResultEpisode, holder: MetadataHolder, link: ExtractorLink, data: JSONObject?): MediaInfo {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}")
movieMetadata.putString(MediaMetadata.KEY_TITLE, holder.title)
val srcPoster = epData.poster ?: holder.poster
if (srcPoster != null) {
movieMetadata.addImage(WebImage(Uri.parse(srcPoster)))
}
return MediaInfo.Builder(link.url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(MimeTypes.VIDEO_UNKNOWN)
.setCustomData(data)
.setMetadata(movieMetadata)
.build()
}
fun awaitLinks(pending: PendingResult<RemoteMediaClient.MediaChannelResult>?) {
if (pending == null) return
thread {
val res = pending.await()
when (res.status?.statusCode) {
CastStatusCodes.FAILED -> {
println("FAILED WITH DATA: " + res.customData)
}
else -> {
}
}
}
}
fun Context.startCast(
apiName: String,
title: String?,
poster: String?,
currentEpisodeIndex: Int,
episodes: List<ResultEpisode>,
currentLinks: List<ExtractorLink>,
startIndex: Int? = null,
startTime: Long? = null,
) {
if (episodes.isEmpty()) return
val castContext = CastContext.getSharedInstance(this)
val epData = episodes[currentEpisodeIndex]
val holder = MetadataHolder(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks)
val mediaItem =
getMediaInfo(epData, holder, currentLinks[startIndex ?: 0], JSONObject(mapper.writeValueAsString(holder)))
val castPlayer = CastPlayer(castContext)
castPlayer.repeatMode = REPEAT_MODE_REPEAT_OFF
castPlayer.stop()
awaitLinks(castPlayer.loadItem(
MediaQueueItem.Builder(mediaItem).build(),
startTime ?: 0,
))
}
}

View file

@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.utils
import com.lagradost.cloudstream3.utils.extractors.MixDrop
import com.lagradost.cloudstream3.utils.extractors.Mp4Upload
import com.lagradost.cloudstream3.utils.extractors.Shiro
import com.lagradost.cloudstream3.cloudstream3.extractors.StreamTape
import com.lagradost.cloudstream3.utils.extractors.StreamTape
import com.lagradost.cloudstream3.utils.extractors.XStreamCdn
data class ExtractorLink(
@ -19,8 +19,6 @@ fun ExtractorLink.getId() : Int {
return url.hashCode()
}
enum class Qualities(var value: Int) {
Unknown(0),
SD(-1), // 360p - 480p

View file

@ -6,7 +6,7 @@ class MixDrop : ExtractorApi() {
override val name: String = "MixDrop"
override val mainUrl: String = "https://mixdrop.co"
private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""")
override val requiresReferer = true
override val requiresReferer = false
override fun getExtractorUrl(id: String): String {
return "$mainUrl/e/$id"

View file

@ -1,4 +1,4 @@
package com.lagradost.cloudstream3.cloudstream3.extractors
package com.lagradost.cloudstream3.utils.extractors
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -7,9 +7,9 @@ import com.lagradost.cloudstream3.utils.Qualities
class StreamTape : ExtractorApi() {
override val name: String = "StreamTape"
override val mainUrl: String = "https://streamtape.com"
override val requiresReferer = true
override val requiresReferer = false
// Because they add concatenation to fuck up scrapers
// Because they add concatenation to fuck up scrapers, DON'T LET LAG CODE ANYTHING
private val linkRegex =
Regex("""(i(|" \+ ')d(|" \+ ')=.*?&(|" \+ ')e(|" \+ ')x(|" \+ ')p(|" \+ ')i(|" \+ ')r(|" \+ ')e(|" \+ ')s(|" \+ ')=.*?&(|" \+ ')i(|" \+ ')p(|" \+ ')=.*?&(|" \+ ')t(|" \+ ')o(|" \+ ')k(|" \+ ')e(|" \+ ')n(|" \+ ')=.*)'""")

View file

@ -12,6 +12,7 @@ class Vidstream {
private fun getExtractorUrl(id: String): String {
return "$mainUrl/streaming.php?id=$id"
}
private val normalApis = arrayListOf(Shiro(), MultiQuality())
// https://gogo-stream.com/streaming.php?id=MTE3NDg5

View file

@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.Qualities
class XStreamCdn : ExtractorApi() {
override val name: String = "XStreamCdn"
override val mainUrl: String = "https://fcdn.stream"
override val requiresReferer = true
override val requiresReferer = false
private data class ResponseData(
@JsonProperty("file") val file: String,

View file

@ -2,15 +2,17 @@
<LinearLayout android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Loading..." android:textColor="?attr/textColor" android:textSize="20sp"
android:textStyle="bold" android:layout_margin="10dp"/>
<androidx.core.widget.ContentLoadingProgressBar
android:textStyle="bold" android:layout_margin="10dp"/>-->
<!-- style="@android:style/Widget.Material.ProgressBar.Horizontal"
-->
<ProgressBar
android:layout_margin="10dp"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:indeterminate="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_width="60dp"
android:layout_height="60dp"
/>
</LinearLayout>