From 50abf2c74cd4eea96bdf5d1bc8255b86ab70f7e7 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Mon, 14 Jun 2021 18:58:43 +0200 Subject: [PATCH] chromecast stuff --- .../com/lagradost/cloudstream3/MainAPI.kt | 6 +- .../lagradost/cloudstream3/MainActivity.kt | 21 +-- .../animeproviders/ShiroProvider.kt | 11 +- .../cloudstream3/ui/ControllerActivity.kt | 138 ++++++++++++++---- .../cloudstream3/ui/player/PlayerFragment.kt | 21 ++- .../cloudstream3/ui/result/ResultFragment.kt | 106 ++++++++------ .../cloudstream3/ui/result/ResultViewModel.kt | 8 +- .../cloudstream3/utils/CastHelper.kt | 95 ++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 4 +- .../cloudstream3/utils/extractors/MixDrop.kt | 2 +- .../utils/extractors/StreamTape.kt | 6 +- .../utils/extractors/Vidstream.kt | 5 +- .../utils/extractors/XStreamCdn.kt | 2 +- app/src/main/res/layout/dialog_loading.xml | 16 +- 14 files changed, 323 insertions(+), 118 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 6a63e5e1..94386124 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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?, - val subEpisodes: ArrayList?, + val dubEpisodes: ArrayList?, + val subEpisodes: ArrayList?, val showStatus: ShowStatus?, override val plot: String?, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c4f60756..744203c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt index f4c505fa..fc127d28 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt @@ -178,7 +178,7 @@ class ShiroProvider : MainAPI() { val mapped = response.let { mapper.readValue(it.text) } val data = mapped.data val isDubbed = data.language == "dubbed" - val episodes = ArrayList(data.episodes ?: ArrayList()) + val episodes = ArrayList(data.episodes?.map { it.videos[0].video_id } ?: ArrayList()) 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) { - callback.invoke(it) - } + override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean { + return Vidstream().getUrl(data, isCasting) { + callback.invoke(it) } - return false } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index d6042a6d..29bceda6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -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) +data class MetadataHolder( + val apiName: String, + val title: String?, + val poster: String?, + val currentEpisodeIndex: Int, + val episodes: List, + val currentLinks: List, +) 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(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 - ?: 0) > 1 - ) VISIBLE else INVISIBLE + 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() + + 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()) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index 0133b5e5..7eb5c782 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index dc08cea5..14a265ca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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> = HashMap() var currentHeaderName: String? = null + var currentEpisodes: ArrayList? = null override fun onCreateView( inflater: LayoutInflater, @@ -142,70 +146,79 @@ 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 epData = episodeClick.data - val links = sortUrls(data.value) + 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) - val castContext = CastContext.getSharedInstance(requireContext()) + val castContext = CastContext.getSharedInstance(requireContext()) - val customData = - links.map { JSONObject().put("name", it.name) } - val jsonArray = JSONArray() - for (item in customData) { - jsonArray.put(item) - } - - val mediaItems = links.map { - val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) - movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, - epData.name ?: "Episode ${epData.episode}") - - if (currentHeaderName != null) - movieMetadata.putString(MediaMetadata.KEY_TITLE, currentHeaderName) - - val srcPoster = epData.poster ?: currentPoster - if (srcPoster != null) { - movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) + val customData = + links.map { JSONObject().put("name", it.name) } + val jsonArray = JSONArray() + for (item in customData) { + jsonArray.put(item) } - MediaQueueItem.Builder( - MediaInfo.Builder(it.url) - .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) - .setContentType(MimeTypes.VIDEO_UNKNOWN) - .setCustomData(JSONObject().put("data", jsonArray)) - .setMetadata(movieMetadata) - .build() - ) - .build() - }.toTypedArray() - val castPlayer = CastPlayer(castContext) - castPlayer.loadItems( - mediaItems, - 0, - epData.progress, - REPEAT_MODE_REPEAT_SINGLE - ) + val mediaItems = links.map { + val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) + movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, + epData.name ?: "Episode ${epData.episode}") + + if (currentHeaderName != null) + movieMetadata.putString(MediaMetadata.KEY_TITLE, currentHeaderName) + + val srcPoster = epData.poster ?: currentPoster + if (srcPoster != null) { + movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) + } + MediaQueueItem.Builder( + MediaInfo.Builder(it.url) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(MimeTypes.VIDEO_UNKNOWN) + .setCustomData(JSONObject().put("data", jsonArray)) + .setMetadata(movieMetadata) + .build() + ) + .build() + }.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() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index e4a95c39..67d61998 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -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>> get() = _allEpisodes private var _apiName: MutableLiveData = MutableLiveData() + val apiName: LiveData 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>) -> 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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt new file mode 100644 index 00000000..1986cee9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -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?) { + 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, + currentLinks: List, + 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, + )) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 982184f1..79be3f3e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt index 0dcdd040..b3e2aa7d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/MixDrop.kt @@ -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" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt index f284c779..b8661f03 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/StreamTape.kt @@ -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(|" \+ ')=.*)'""") diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt index 7cc510cc..1fa3af56 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/Vidstream.kt @@ -12,10 +12,11 @@ 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 - fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit) : Boolean { + fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { try { normalApis.pmap { api -> val url = api.getExtractorUrl(id) @@ -36,7 +37,7 @@ class Vidstream { //val name = element.text() // Matches vidstream links with extractors - extractorApis.filter { !it.requiresReferer || !isCasting}.pmap { api -> + extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> if (link.startsWith(api.mainUrl)) { val extractedLinks = api.getUrl(link, url) if (extractedLinks?.isNotEmpty() == true) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt index acf8896b..6edf65dd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/extractors/XStreamCdn.kt @@ -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, diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layout/dialog_loading.xml index 0f5b8580..ff001d8c 100644 --- a/app/src/main/res/layout/dialog_loading.xml +++ b/app/src/main/res/layout/dialog_loading.xml @@ -2,15 +2,17 @@ - + + + \ No newline at end of file