chromecast stuff

This commit is contained in:
LagradOst 2021-06-10 17:15:14 +02:00
parent 1151196c44
commit 90d6f17181
8 changed files with 159 additions and 45 deletions

View file

@ -189,7 +189,7 @@ class ShiroProvider : MainAPI() {
data.english,
data.japanese,
data.name.replace("Dubbed", ""),//data.canonicalTitle ?: data.name.replace("Dubbed", ""),
"$mainUrl/${slug}",
"$mainUrl/anime/${slug}",
this.name,
getType(data.type ?: ""),
"https://cdn.shiro.is/${data.image}",

View file

@ -8,19 +8,23 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import kotlinx.android.synthetic.main.result_episode.view.*
const val ACTION_PLAY_EPISODE = 1
const val ACTION_RELOAD_EPISODE = 2
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
const val ACTION_RELOAD_EPISODE = 4
const val ACTION_CHROME_CAST_EPISODE = 2
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter(
private var activity: Activity,
var cardList: ArrayList<ResultEpisode>,
val resView: RecyclerView,
val clickCallback: (EpisodeClickEvent) -> Unit,
private val resView: RecyclerView,
private val clickCallback: (EpisodeClickEvent) -> Unit,
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -70,11 +74,23 @@ class EpisodeAdapter(
)
v.layoutParams = param
}
setWidth(episodeViewPrecentage, card.watchProgress)
setWidth(episodeViewPercentageOff, 1 - card.watchProgress)
val watchProgress = card.getWatchProgress()
setWidth(episodeViewPrecentage, watchProgress)
setWidth(episodeViewPercentageOff, 1 - watchProgress)
episodeHolder.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE, card))
if (activity.isCastApiAvailable()) {
val castContext = CastContext.getSharedInstance(activity)
println("SSTATE: " + castContext.castState + "<<")
if (castContext.castState == CastState.CONNECTED) {
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else {
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
}
} else {
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
}
}
}
}

View file

@ -1,12 +1,16 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.NestedScrollView
@ -18,9 +22,16 @@ 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.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_SINGLE
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.common.images.WebImage
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.AnimeLoadResponse
import com.lagradost.cloudstream3.LoadResponse
@ -35,20 +46,27 @@ import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.ExtractorLink
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.*
import org.json.JSONObject
const val MAX_SYNO_LENGH = 300
data class ResultEpisode(
val name: String?,
val poster: String?,
val episode: Int,
val season: Int?,
val data: Any,
val apiName: String,
val id: Int,
val index: Int,
val watchProgress: Float, // 0-1
val progress: Long, // time in MS
val duration: Long, // duration in MS
)
fun ResultEpisode.getWatchProgress(): Float {
return progress.toFloat() / duration
}
class ResultFragment : Fragment() {
companion object {
fun newInstance(url: String, slug: String, apiName: String) =
@ -80,6 +98,8 @@ class ResultFragment : Fragment() {
super.onDestroy()
}
var currentPoster: String? = null
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -120,7 +140,6 @@ class ResultFragment : Fragment() {
activity?.onBackPressed()
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let { it ->
EpisodeAdapter(
it,
@ -130,6 +149,64 @@ class ResultFragment : Fragment() {
val id = episodeClick.data.id
val index = episodeClick.data.index
val buildInPlayer = true
when (episodeClick.action) {
ACTION_CHROME_CAST_EPISODE -> {
Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
viewModel.loadEpisode(episodeClick.data, true) { data ->
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 = data.value
val castContext = CastContext.getSharedInstance(requireContext())
val mediaItems = links.map {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
movieMetadata.putString(
MediaMetadata.KEY_TITLE,
"Episode ${epData.episode}" +
if (epData.name != null)
"- ${epData.name}"
else
""
)
movieMetadata.putString(MediaMetadata.KEY_ALBUM_ARTIST,
epData.name ?: "Episode ${epData.episode}")
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", it.name))
.setMetadata(movieMetadata)
.build()
)
.build()
}.toTypedArray()
val castPlayer = CastPlayer(castContext)
castPlayer.loadItems(
mediaItems,
0,
epData.progress,
REPEAT_MODE_REPEAT_SINGLE
)
}
}
}
}
ACTION_PLAY_EPISODE_IN_PLAYER -> {
if (buildInPlayer) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim,
@ -138,27 +215,16 @@ class ResultFragment : Fragment() {
R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index, null, 0)))
.commit()
} else {
when (episodeClick.action) {
/*
ACTION_PLAY_EPISODE -> {
if (allEpisodes.containsKey(id)) {
playEpisode(allEpisodes[id], index)
} else {
viewModel.loadEpisode(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}
}
}
ACTION_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res ->
ACTION_RELOAD_EPISODE -> {
/*viewModel.load(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}*/
}
}
}
}
@ -184,6 +250,22 @@ class ResultFragment : Fragment() {
if (d is LoadResponse) {
result_bookmark_button.text = "Watching"
currentPoster = d.posterUrl
result_openinbrower.setOnClickListener {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(d.url)
startActivity(i)
}
result_share.setOnClickListener {
val i = Intent(Intent.ACTION_SEND)
i.type = "text/plain"
i.putExtra(Intent.EXTRA_SUBJECT, d.name)
i.putExtra(Intent.EXTRA_TEXT, d.url)
startActivity(Intent.createChooser(i, d.name))
}
if (d.year != null) {
result_year.visibility = View.VISIBLE
result_year.text = d.year.toString()

View file

@ -41,13 +41,15 @@ class ResultViewModel : ViewModel() {
for ((index, i) in dataList.withIndex()) {
episodes.add(ResultEpisode(
null, // TODO ADD NAMES
null,
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
null, // TODO FIX SEASON
i,
apiName,
(d.url + index).hashCode(),
index,
0f,//(index * 0.1f),//TODO TEST; REMOVE
0,//(index * 0.1f),//TODO TEST; REMOVE
0,
))
}
_episodes.postValue(episodes)
@ -59,25 +61,28 @@ class ResultViewModel : ViewModel() {
for ((index, i) in d.episodes.withIndex()) {
episodes.add(ResultEpisode(
null, // TODO ADD NAMES
null,
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
null, // TODO FIX SEASON
i,
apiName,
(d.url + index).hashCode(),
index,
0f,//(index * 0.1f),//TODO TEST; REMOVE
0,//(index * 0.1f),//TODO TEST; REMOVE
0,
))
}
_episodes.postValue(episodes)
}
is MovieLoadResponse -> {
_episodes.postValue(arrayListOf(ResultEpisode(null,
null,
0, null,
d.movieUrl,
d.apiName,
(d.url).hashCode(),
0,
0f)))
0, 0)))
}
}
}
@ -95,17 +100,26 @@ class ResultViewModel : ViewModel() {
private var _apiName: MutableLiveData<String> = MutableLiveData()
fun loadEpisode(episode: ResultEpisode, isCasting : Boolean, callback: (Resource<Boolean>) -> Unit) {
fun loadEpisode(
episode: ResultEpisode,
isCasting: Boolean,
callback: (Resource<ArrayList<ExtractorLink>>) -> Unit,
) {
loadEpisode(episode.id, episode.data, isCasting, callback)
}
fun loadEpisode(id: Int, data: Any, isCasting : Boolean, callback: (Resource<Boolean>) -> Unit) =
private fun loadEpisode(
id: Int,
data: Any,
isCasting: Boolean,
callback: (Resource<ArrayList<ExtractorLink>>) -> Unit,
) =
viewModelScope.launch {
if (_allEpisodes.value?.contains(id) == true) {
_allEpisodes.value?.remove(id)
}
val links = ArrayList<ExtractorLink>()
val data = safeApiCall {
val localData = safeApiCall {
getApiFromName(_apiName.value).loadLinks(data, isCasting) { //TODO IMPLEMENT CASTING
for (i in links) {
if (i.url == it.url) return@loadLinks
@ -116,8 +130,9 @@ class ResultViewModel : ViewModel() {
// _allEpisodes.value?.get(episode.id)?.add(it)
}
links
}
callback.invoke(data)
callback.invoke(localData)
}
fun loadIndex(index: Int): ResultEpisode? {

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.utils
import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
@ -35,7 +36,7 @@ class CastOptionsProvider : OptionsProvider {
.build()
return CastOptions.Builder()
.setReceiverApplicationId("A12D4273")
.setReceiverApplicationId( CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setStopReceiverApplicationWhenEndingSession(true)
.setCastMediaOptions(mediaOptions)
.build()

View file

@ -1,8 +1,8 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="636dp"
android:height="714dp"
android:width="21.378dp"
android:height="24dp"
android:viewportWidth="636"
android:viewportHeight="714">
<path

View file

@ -1,8 +1,8 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="636dp"
android:height="714dp"
android:width="21.378dp"
android:height="24dp"
android:viewportWidth="636"
android:viewportHeight="714">
<path

View file

@ -27,7 +27,7 @@
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout android:layout_width="match_parent" android:id="@+id/result_poster_blur_holder"
android:layout_height="200dp">
android:layout_height="180dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -149,14 +149,14 @@
android:id="@+id/result_share"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_margin="5dp"
android:layout_marginRight="10dp"
android:elevation="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_outline_share_24"
android:layout_gravity="center" android:contentDescription="@string/result_share">
</ImageView>
<ImageView
android:id="@+id/result_browser"
android:id="@+id/result_openinbrower"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_margin="5dp"