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 d376b2b3..da4eb526 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -50,7 +50,7 @@ private fun RemoteMediaClient.getItemIndex(): Int? { class SkipNextEpisodeController(val view: ImageView) : UIController() { init { - view.setImageResource(R.drawable.exo_controls_fastforward) + view.setImageResource(R.drawable.ic_baseline_skip_next_24) view.setOnClickListener { remoteMediaClient?.let { it.queueNext(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 caf0220e..fdf4c467 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 @@ -81,6 +81,8 @@ 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.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.saveViewPos import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getId import kotlinx.android.synthetic.main.fragment_player.* @@ -544,11 +546,15 @@ class PlayerFragment : Fragment() { private lateinit var volumeObserver: SettingsContentObserver companion object { - fun newInstance(data: PlayerData) = + fun newInstance(data: PlayerData, startPos: Long? = null) = PlayerFragment().apply { arguments = Bundle().apply { //println(data) putString("data", mapper.writeValueAsString(data)) + println("PUT START: " + startPos) + if (startPos != null) { + putLong(STATE_RESUME_POSITION, startPos) + } } } } @@ -556,8 +562,7 @@ class PlayerFragment : Fragment() { private fun savePos() { if (this::exoPlayer.isInitialized) { if (exoPlayer.duration > 0 && exoPlayer.currentPosition > 0) { - //TODO FIX SAVE POS - // setViewPosDur(data!!, exoPlayer.currentPosition, exoPlayer.duration) + context?.saveViewPos(getEpisode()?.id, exoPlayer.currentPosition, exoPlayer.duration) } } } @@ -610,7 +615,7 @@ class PlayerFragment : Fragment() { private var resizeMode = 0 private var playbackSpeed = 0f private var allEpisodes: HashMap> = HashMap() - private var episodes: ArrayList = ArrayList() + private var episodes: List = ArrayList() var currentPoster: String? = null var currentHeaderName: String? = null @@ -795,6 +800,14 @@ class PlayerFragment : Fragment() { } } + arguments?.getString("data")?.let { + playerData = mapper.readValue(it, PlayerData::class.java) + } + + arguments?.getLong(STATE_RESUME_POSITION)?.let { + playbackPosition = it + } + if (savedInstanceState != null) { currentWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW) playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION) @@ -819,9 +832,7 @@ class PlayerFragment : Fragment() { ) viewModel = ViewModelProvider(requireActivity()).get(ResultViewModel::class.java) - arguments?.getString("data")?.let { - playerData = mapper.readValue(it, PlayerData::class.java) - } + observeDirectly(viewModel.episodes) { _episodes -> episodes = _episodes @@ -1175,6 +1186,8 @@ class PlayerFragment : Fragment() { } override fun onDestroy() { + savePos() + super.onDestroy() isInPlayer = false releasePlayer() @@ -1184,6 +1197,7 @@ class PlayerFragment : Fragment() { } override fun onPause() { + savePos() super.onPause() if (Util.SDK_INT <= 23) { if (player_view != null) player_view.onPause() @@ -1192,6 +1206,7 @@ class PlayerFragment : Fragment() { } override fun onStop() { + savePos() super.onStop() if (Util.SDK_INT > 23) { if (player_view != null) player_view.onPause() @@ -1200,6 +1215,8 @@ class PlayerFragment : Fragment() { } override fun onSaveInstanceState(outState: Bundle) { + savePos() + if (this::exoPlayer.isInitialized) { outState.putInt(STATE_RESUME_WINDOW, exoPlayer.currentWindowIndex) outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition) @@ -1209,7 +1226,6 @@ class PlayerFragment : Fragment() { outState.putInt(RESIZE_MODE_KEY, resizeMode) outState.putFloat(PLAYBACK_SPEED, playbackSpeed) outState.putString("data", mapper.writeValueAsString(playerData)) - savePos() super.onSaveInstanceState(outState) } @@ -1305,6 +1321,7 @@ class PlayerFragment : Fragment() { .setTrackSelector(trackSelector) _exoPlayer.setMediaSourceFactory(DefaultMediaSourceFactory(CustomFactory())) + println("START POS: " + playbackPosition) exoPlayer = _exoPlayer.build().apply { playWhenReady = isPlayerPlaying seekTo(currentWindow, playbackPosition) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 576798ef..5babc16a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -22,7 +22,7 @@ data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) class EpisodeAdapter( private var activity: Activity, - var cardList: ArrayList, + var cardList: List, private val resView: RecyclerView, private val clickCallback: (EpisodeClickEvent) -> Unit, ) : 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 14a265ca..d0115488 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 @@ -1,6 +1,7 @@ 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 @@ -20,18 +21,9 @@ 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 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.* import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar @@ -41,11 +33,10 @@ 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.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLink import jp.wasabeef.glide.transformations.BlurTransformation import kotlinx.android.synthetic.main.fragment_result.* -import org.json.JSONArray -import org.json.JSONObject const val MAX_SYNO_LENGH = 300 @@ -59,12 +50,42 @@ data class ResultEpisode( val apiName: String, val id: Int, val index: Int, - val progress: Long, // time in MS + val position: Long, // time in MS val duration: Long, // duration in MS ) +fun ResultEpisode.getRealPosition(): Long { + if (duration <= 0) return 0 + val percentage = position * 100 / duration + if (percentage <= 5 || percentage >= 95) return 0 + return position +} + +fun Context.buildResultEpisode( + name: String?, + poster: String?, + episode: Int, + season: Int?, + data: String, + apiName: String, + id: Int, + index: Int, +): ResultEpisode { + val posDur = getViewPos(id) + return ResultEpisode(name, + poster, + episode, + season, + data, + apiName, + id, + index, + posDur?.position ?: 0, + posDur?.duration ?: 0) +} + fun ResultEpisode.getWatchProgress(): Float { - return progress.toFloat() / duration + return position.toFloat() / duration } class ResultFragment : Fragment() { @@ -79,11 +100,11 @@ class ResultFragment : Fragment() { } } - + private var currentLoadingCount = 0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED private lateinit var viewModel: ResultViewModel private var allEpisodes: HashMap> = HashMap() - var currentHeaderName: String? = null - var currentEpisodes: ArrayList? = null + private var currentHeaderName: String? = null + private var currentEpisodes: List? = null override fun onCreateView( inflater: LayoutInflater, @@ -100,7 +121,7 @@ class ResultFragment : Fragment() { super.onDestroy() } - var currentPoster: String? = null + private var currentPoster: String? = null @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -149,17 +170,24 @@ class ResultFragment : Fragment() { //val id = episodeClick.data.id val index = episodeClick.data.index val buildInPlayer = true + currentLoadingCount++ when (episodeClick.action) { ACTION_CHROME_CAST_EPISODE -> { - val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) - val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null); + val currentLoad = currentLoadingCount + val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent) + val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null) builder.setView(customLayout) val dialog = builder.create() + dialog.show() - Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show() + dialog.setOnDismissListener { + currentLoadingCount++ + } + // Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show() viewModel.loadEpisode(episodeClick.data, true) { data -> + if(currentLoadingCount != currentLoad) return@loadEpisode dialog.dismiss() when (data) { is Resource.Failure -> { @@ -173,52 +201,9 @@ class ResultFragment : Fragment() { currentPoster, episodeClick.data.index, eps, - sortUrls(data.value)) - /* - val epData = episodeClick.data - val links = sortUrls(data.value) - - 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))) - } - 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 - )*/ + sortUrls(data.value), + startTime = episodeClick.data.getRealPosition() + ) } } } @@ -231,7 +216,10 @@ class ResultFragment : Fragment() { R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit) - .add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index, null, 0))) + .add(R.id.homeRoot, + PlayerFragment.newInstance(PlayerData(index, null, 0), + episodeClick.data.getRealPosition()) + ) .commit() } } @@ -288,10 +276,10 @@ class ResultFragment : Fragment() { } if (d.year != null) { - result_year.visibility = View.VISIBLE + result_year.visibility = VISIBLE result_year.text = d.year.toString() } else { - result_year.visibility = View.GONE + result_year.visibility = GONE } if (d.posterUrl != null) { @@ -383,12 +371,12 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE) } result_tag.removeAllViews() - result_tag_holder.visibility = View.GONE - result_status.visibility = View.GONE + result_tag_holder.visibility = GONE + result_status.visibility = GONE when (d) { is AnimeLoadResponse -> { - result_status.visibility = View.VISIBLE + result_status.visibility = VISIBLE result_status.text = when (d.showStatus) { null -> "" ShowStatus.Ongoing -> "Ongoing" @@ -402,9 +390,9 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE) result_toolbar.title = titleName if (d.tags == null) { - result_tag_holder.visibility = View.GONE + result_tag_holder.visibility = GONE } else { - result_tag_holder.visibility = View.VISIBLE + result_tag_holder.visibility = VISIBLE for ((index, tag) in d.tags.withIndex()) { val viewBtt = layoutInflater.inflate(R.layout.result_tag, null) @@ -426,6 +414,6 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE) } if (viewModel.resultResponse.value == null && apiName != null && slug != null) - viewModel.load(slug, apiName) + viewModel.load(requireContext(), slug, apiName) } } \ No newline at end of file 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 67d61998..811dc9f6 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 @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.result +import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -8,17 +9,27 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLink import kotlinx.coroutines.launch class ResultViewModel : ViewModel() { private val _resultResponse: MutableLiveData> = MutableLiveData() - private val _episodes: MutableLiveData> = MutableLiveData() + private val _episodes: MutableLiveData> = MutableLiveData() val resultResponse: LiveData> get() = _resultResponse - val episodes: LiveData> get() = _episodes + val episodes: LiveData> get() = _episodes private val dubStatus: MutableLiveData = MutableLiveData() - fun load(url: String, apiName: String) = viewModelScope.launch { + fun reloadEpisodes(context: Context) { + val current = _episodes.value ?: return + val copy = current.map { + val posDur = context.getViewPos(it.id) + it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0) + } + _episodes.postValue(copy) + } + + fun load(context: Context, url: String, apiName: String) = viewModelScope.launch { _apiName.postValue(apiName) val data = safeApiCall { getApiFromName(apiName).load(url) @@ -39,7 +50,7 @@ class ResultViewModel : ViewModel() { if (dataList != null) { val episodes = ArrayList() for ((index, i) in dataList.withIndex()) { - episodes.add(ResultEpisode( + episodes.add(context.buildResultEpisode( null, // TODO ADD NAMES null, index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE @@ -48,8 +59,6 @@ class ResultViewModel : ViewModel() { apiName, (d.url + index).hashCode(), index, - 0,//(index * 0.1f),//TODO TEST; REMOVE - 0, )) } _episodes.postValue(episodes) @@ -59,7 +68,7 @@ class ResultViewModel : ViewModel() { is TvSeriesLoadResponse -> { val episodes = ArrayList() for ((index, i) in d.episodes.withIndex()) { - episodes.add(ResultEpisode( + episodes.add(context.buildResultEpisode( null, // TODO ADD NAMES null, index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE @@ -68,21 +77,19 @@ class ResultViewModel : ViewModel() { apiName, (d.url + index).hashCode(), index, - 0,//(index * 0.1f),//TODO TEST; REMOVE - 0, )) } _episodes.postValue(episodes) } is MovieLoadResponse -> { - _episodes.postValue(arrayListOf(ResultEpisode(null, + _episodes.postValue(arrayListOf(context.buildResultEpisode( + null, null, 0, null, d.movieUrl, d.apiName, (d.url).hashCode(), 0, - 0, 0, ))) } } @@ -141,5 +148,4 @@ class ResultViewModel : ViewModel() { fun loadIndex(index: Int): ResultEpisode? { return episodes.value?.get(index) } - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index c0722ba2..f8f2f50c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.utils -import android.app.Activity import android.content.Context import android.content.SharedPreferences import com.fasterxml.jackson.databind.DeserializationFeature diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt new file mode 100644 index 00000000..f6642700 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -0,0 +1,22 @@ +package com.lagradost.cloudstream3.utils + +import android.content.Context +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.setKey + +const val VIDEO_POS_DUR = "video_pos_dur" + +data class PosDur(val position: Long, val duration: Long) + +object DataStoreHelper { + var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION + + fun Context.saveViewPos(id: Int?, pos: Long, dur: Long) { + if(id == null) return + setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur)) + } + + fun Context.getViewPos(id: Int): PosDur? { + return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layout/dialog_loading.xml index ff001d8c..cb1ee5ac 100644 --- a/app/src/main/res/layout/dialog_loading.xml +++ b/app/src/main/res/layout/dialog_loading.xml @@ -1,15 +1,19 @@ + android:layout_height="match_parent" > + Share Open In Browser Skip Loading + Loading... \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 436ab06f..07ae4b14 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -114,7 +114,9 @@ @android:dimen/dialog_min_width_minor @drawable/dialog__window_background - +