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.english,
data.japanese, data.japanese,
data.name.replace("Dubbed", ""),//data.canonicalTitle ?: data.name.replace("Dubbed", ""), data.name.replace("Dubbed", ""),//data.canonicalTitle ?: data.name.replace("Dubbed", ""),
"$mainUrl/${slug}", "$mainUrl/anime/${slug}",
this.name, this.name,
getType(data.type ?: ""), getType(data.type ?: ""),
"https://cdn.shiro.is/${data.image}", "https://cdn.shiro.is/${data.image}",

View file

@ -8,19 +8,23 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView 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.*
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import kotlinx.android.synthetic.main.result_episode.view.* import kotlinx.android.synthetic.main.result_episode.view.*
const val ACTION_PLAY_EPISODE = 1 const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
const val ACTION_RELOAD_EPISODE = 2 const val ACTION_RELOAD_EPISODE = 4
const val ACTION_CHROME_CAST_EPISODE = 2
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter( class EpisodeAdapter(
private var activity: Activity, private var activity: Activity,
var cardList: ArrayList<ResultEpisode>, var cardList: ArrayList<ResultEpisode>,
val resView: RecyclerView, private val resView: RecyclerView,
val clickCallback: (EpisodeClickEvent) -> Unit, private val clickCallback: (EpisodeClickEvent) -> Unit,
) : ) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -70,11 +74,23 @@ class EpisodeAdapter(
) )
v.layoutParams = param 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 { 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 package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
@ -18,9 +22,16 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.request.RequestOptions.bitmapTransform 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.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState 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.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.AnimeLoadResponse import com.lagradost.cloudstream3.AnimeLoadResponse
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse
@ -35,20 +46,27 @@ import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
import org.json.JSONObject
const val MAX_SYNO_LENGH = 300 const val MAX_SYNO_LENGH = 300
data class ResultEpisode( data class ResultEpisode(
val name: String?, val name: String?,
val poster: String?,
val episode: Int, val episode: Int,
val season: Int?, val season: Int?,
val data: Any, val data: Any,
val apiName: String, val apiName: String,
val id: Int, val id: Int,
val index: 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() { class ResultFragment : Fragment() {
companion object { companion object {
fun newInstance(url: String, slug: String, apiName: String) = fun newInstance(url: String, slug: String, apiName: String) =
@ -80,6 +98,8 @@ class ResultFragment : Fragment() {
super.onDestroy() super.onDestroy()
} }
var currentPoster: String? = null
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -120,7 +140,6 @@ class ResultFragment : Fragment() {
activity?.onBackPressed() activity?.onBackPressed()
} }
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let { it -> val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let { it ->
EpisodeAdapter( EpisodeAdapter(
it, it,
@ -130,35 +149,82 @@ class ResultFragment : Fragment() {
val id = episodeClick.data.id val id = episodeClick.data.id
val index = episodeClick.data.index val index = episodeClick.data.index
val buildInPlayer = true val buildInPlayer = true
if (buildInPlayer) { when (episodeClick.action) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction() ACTION_CHROME_CAST_EPISODE -> {
.setCustomAnimations(R.anim.enter_anim, Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index, null, 0)))
.commit()
} else {
when (episodeClick.action) {
/* viewModel.loadEpisode(episodeClick.data, true) { data ->
ACTION_PLAY_EPISODE -> { when (data) {
if (allEpisodes.containsKey(id)) { is Resource.Failure -> {
playEpisode(allEpisodes[id], index) Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show()
} else { }
viewModel.loadEpisode(episodeClick.data) { res -> is Resource.Success -> {
if (res is Resource.Success) { val epData = episodeClick.data
playEpisode(allEpisodes[id], index) 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_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res -> }
ACTION_PLAY_EPISODE_IN_PLAYER -> {
if (buildInPlayer) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit)
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index, null, 0)))
.commit()
}
}
ACTION_RELOAD_EPISODE -> {
/*viewModel.load(episodeClick.data) { res ->
if (res is Resource.Success) { if (res is Resource.Success) {
playEpisode(allEpisodes[id], index) playEpisode(allEpisodes[id], index)
} }
}*/ }*/
} }
} }
} }
} }
@ -184,6 +250,22 @@ class ResultFragment : Fragment() {
if (d is LoadResponse) { if (d is LoadResponse) {
result_bookmark_button.text = "Watching" 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) { if (d.year != null) {
result_year.visibility = View.VISIBLE result_year.visibility = View.VISIBLE
result_year.text = d.year.toString() result_year.text = d.year.toString()

View file

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

View file

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

View file

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

View file

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

View file

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