diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 9ee85a941..2dfd5ef4d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -143,7 +143,11 @@ class GeneratorPlayer : FullScreenPlayer() { const val STOP_ACTION = "stopcs3" private val generators = ConcurrentHashMap>() - fun newInstance(generator: VideoGenerator<*>, index : Int, syncData: HashMap? = null): Bundle { + fun newInstance( + generator: VideoGenerator<*>, + index: Int, + syncData: HashMap? = null + ): Bundle { Log.i(TAG, "newInstance = $syncData") val uuid = UUID.randomUUID().toString() generators[uuid] = generator @@ -178,12 +182,14 @@ class GeneratorPlayer : FullScreenPlayer() { private var isNextEpisode: Boolean = false // this is used to reset the watch time private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none - private val allMeta: List? get() = viewModel.state.generatorState?.allMeta?.filterIsInstance()?.map { episode -> - // Refresh all the episodes watch duration - getViewPos(episode.id)?.let { data -> - episode.copy(position = data.position, duration = data.duration) - } ?: episode - } + private val allMeta: List? + get() = viewModel.state.generatorState?.allMeta?.filterIsInstance() + ?.map { episode -> + // Refresh all the episodes watch duration + getViewPos(episode.id)?.let { data -> + episode.copy(position = data.position, duration = data.duration) + } ?: episode + } private fun setSubtitles(subtitle: SubtitleData?, userInitiated: Boolean): Boolean { // If subtitle is changed and user initiated -> Save the language @@ -1541,7 +1547,7 @@ class GeneratorPlayer : FullScreenPlayer() { private fun startPlayer() { // We don't want double load when you skip loading - if(isPlayerActive.get()) { + if (isPlayerActive.get()) { return } @@ -2140,6 +2146,7 @@ class GeneratorPlayer : FullScreenPlayer() { fun releasePlayer() { player.release() currentSelectedSubtitles = null + currentSelectedLink = null isPlayerActive.set(false) binding?.overlayLoadingSkipButton?.isVisible = false binding?.playerLoadingOverlay?.isVisible = true @@ -2152,19 +2159,31 @@ class GeneratorPlayer : FullScreenPlayer() { activity?.popCurrentPage() } + override fun onSaveInstanceState(outState: Bundle) { + outState.putInt("index", viewModel.episodeIndex) + super.onSaveInstanceState(outState) + } + override fun onBindingCreated(binding: FragmentPlayerBinding, savedInstanceState: Bundle?) { viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] sync = ViewModelProvider(this)[SyncViewModel::class.java] val uuid = savedInstanceState?.getString("uuid") ?: arguments?.getString("uuid") val index = savedInstanceState?.getInt("index") ?: arguments?.getInt("index") - - viewModel.attachGenerator(generators[uuid], index) + val generator = generators[uuid] unwrapBundle(savedInstanceState) unwrapBundle(arguments) super.onBindingCreated(binding, savedInstanceState) + + // Avoid showing no links found + if (generator == null || index == null) { + exitPlayer() + return + } + viewModel.attachGenerator(generator, index) + context?.let { ctx -> val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) showName = settingsManager.getBoolean(ctx.getString(R.string.show_name_key), true) @@ -2193,14 +2212,18 @@ class GeneratorPlayer : FullScreenPlayer() { preferredAutoSelectSubtitles = getAutoSelectLanguageTagIETF() - if (currentSelectedLink == null) { + val selectedLink = currentSelectedLink + if (selectedLink == null) { viewModel.loadLinks() + } else { + // Recreated view, so we need to recreate the + loadLink(selectedLink, true) } binding.overlayLoadingSkipButton.setOnClickListener { // Mark as "success" early viewModel.modifyState { - copy(loading = Resource.Success(true)) + copy(loading = Resource.Success(Unit)) } } @@ -2218,11 +2241,13 @@ class GeneratorPlayer : FullScreenPlayer() { } } - observe(viewModel.currentStamps) { stamps -> + observe(viewModel.currentStamps) { (stamps, instance) -> + if (instance != viewModel.state.instance) return@observe // Outdated observe player.addTimeStamps(stamps) } - observe(viewModel.currentSubtitles) { subtitles -> + observe(viewModel.currentSubtitles) { (subtitles, instance) -> + if (instance != viewModel.state.instance) return@observe // Outdated observe player.setActiveSubtitles(subtitles) // If the file is downloaded then do not select auto select the subtitles @@ -2233,7 +2258,9 @@ class GeneratorPlayer : FullScreenPlayer() { autoSelectSubtitles() } } - observe(viewModel.loadingLinks) { loading -> + observe(viewModel.loadingLinks) { (loading, instance) -> + if (instance != viewModel.state.instance) return@observe // Outdated observe + when (loading) { is Resource.Loading -> { releasePlayer() @@ -2254,7 +2281,9 @@ class GeneratorPlayer : FullScreenPlayer() { } } - observe(viewModel.currentLinks) { links -> + observe(viewModel.currentLinks) { (links, instance) -> + if (instance != viewModel.state.instance) return@observe // Outdated observe + val turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true val wasGone = binding.overlayLoadingSkipButton.isGone @@ -2269,7 +2298,7 @@ class GeneratorPlayer : FullScreenPlayer() { } safe { - if (viewModel.state.links.any { link -> + if (!isPlayerActive.get() && viewModel.state.links.any { link -> getLinkPriority(currentQualityProfile, link.first) >= QualityDataHelper.AUTO_SKIP_PRIORITY } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 049ed06d6..e3c390d50 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -47,8 +47,9 @@ data class VideoState( val subtitles: PersistentSet = persistentSetOf(), val links: PersistentSet = persistentSetOf(), val stamps: PersistentList = persistentListOf(), - val loading: Resource = Resource.Loading(), + val loading: Resource = Resource.Loading(), val generatorState: GeneratorState? = null, + val instance: Int, ) { /** * This acts as a local cache for sorted links that are not copied over by the copy constructor. @@ -114,6 +115,11 @@ data class VideoState( fun set(items: Collection): VideoState = copy(stamps = items.toPersistentList()) } +data class VideoLive( + val value: T, + val instance: Int, +) + class PlayerGeneratorViewModel : ViewModel() { companion object { const val TAG = "PlayViewGen" @@ -123,7 +129,7 @@ class PlayerGeneratorViewModel : ViewModel() { var generator: VideoGenerator<*>? = null @Volatile - private var episodeIndex: Int = 0 + var episodeIndex: Int = 0 /** * The state of the video player, only modify it by modifyState to make sure observe is called, @@ -132,20 +138,21 @@ class PlayerGeneratorViewModel : ViewModel() { * This value can be used without Synchronized or locking when reading, as all fields are immutable. * */ @Volatile - var state = VideoState() + var state = VideoState(instance = 0) private set - private val _currentLinks = MutableLiveData>>(setOf()) - val currentLinks: LiveData>> = _currentLinks + private val _currentLinks = + MutableLiveData>>>(null) + val currentLinks: LiveData>>> = _currentLinks - private val _currentSubtitles = MutableLiveData>(setOf()) - val currentSubtitles: LiveData> = _currentSubtitles + private val _currentSubtitles = MutableLiveData>>(null) + val currentSubtitles: LiveData>> = _currentSubtitles - private val _loadingLinks = MutableLiveData>() - val loadingLinks: LiveData> = _loadingLinks + private val _loadingLinks = MutableLiveData>>() + val loadingLinks: LiveData>> = _loadingLinks - private val _currentStamps = MutableLiveData>(emptyList()) - val currentStamps: LiveData> = _currentStamps + private val _currentStamps = MutableLiveData>>(null) + val currentStamps: LiveData>> = _currentStamps /** * Modifies the `state` variable safely, and with the correct observe behavior. @@ -158,6 +165,15 @@ class PlayerGeneratorViewModel : ViewModel() { val oldState = state state = op.invoke(oldState) + /** New instance, always push state */ + if (state.instance != oldState.instance) { + _currentSubtitles.postValue(VideoLive(state.subtitles, state.instance)) + _currentStamps.postValue(VideoLive(state.stamps, state.instance)) + _currentLinks.postValue(VideoLive(state.links, state.instance)) + _loadingLinks.postValue(VideoLive(state.loading, state.instance)) + return + } + /** * Only post the changed values, this makes sure we do not invoke the "observe" * @@ -165,15 +181,15 @@ class PlayerGeneratorViewModel : ViewModel() { * to avoid comparing the entire set or list as "Persistent" classes will hold the same reference if they are unchanged. * */ if (state.links !== oldState.links) - _currentLinks.postValue(state.links) + _currentLinks.postValue(VideoLive(state.links, state.instance)) if (state.stamps !== oldState.stamps) - _currentStamps.postValue(state.stamps) + _currentStamps.postValue(VideoLive(state.stamps, state.instance)) if (state.subtitles !== oldState.subtitles) - _currentSubtitles.postValue(state.subtitles) + _currentSubtitles.postValue(VideoLive(state.subtitles, state.instance)) /** Normal equality here as it is not a collection */ if (state.loading != oldState.loading) - _loadingLinks.postValue(state.loading) + _loadingLinks.postValue(VideoLive(state.loading, state.instance)) } private val _currentSubtitleYear = MutableLiveData(null) @@ -252,13 +268,10 @@ class PlayerGeneratorViewModel : ViewModel() { loadLinks() } - fun attachGenerator(newGenerator: VideoGenerator<*>?, index: Int?) { - if (generator == null) { - generator = newGenerator - if (index != null) { - episodeIndex = index - } - } + fun attachGenerator(newGenerator: VideoGenerator<*>, index: Int) { + Log.i(TAG, "attachGenerator with generator=$newGenerator and index=$index") + generator = newGenerator + episodeIndex = index } /** @@ -321,45 +334,49 @@ class PlayerGeneratorViewModel : ViewModel() { } fun loadLinks(sourceTypes: Set = LOADTYPE_INAPP) { - Log.i(TAG, "loadLinks") + Log.i(TAG, "loadLinks with generator=$generator and index=$episodeIndex") currentJob?.cancel() val index = episodeIndex - currentJob = viewModelScope.launchSafe { - // Clear old data and reset the state - modifyState { - VideoState( - generatorState = generator?.let { gen -> - GeneratorState( - meta = gen.videos.getOrNull(index), - nextMeta = gen.videos.getOrNull(index + 1), - id = gen.getId(index), - response = (gen as? RepoLinkGenerator)?.page, - index = index, - allMeta = gen.videos - ) - } - ) - } + // Clear old data and reset the state + modifyState { + VideoState( + loading = Resource.Loading(), + generatorState = generator?.let { gen -> + GeneratorState( + meta = gen.videos.getOrNull(index), + nextMeta = gen.videos.getOrNull(index + 1), + id = gen.getId(index), + response = (gen as? RepoLinkGenerator)?.page, + index = index, + allMeta = gen.videos + ) + }, + instance = instance + 1 + ) + } + currentJob = viewModelScope.launchSafe { // Load more data val loadingState = safeApiCall { generator?.generateLinks( sourceTypes = sourceTypes, clearCache = forceClearCache, callback = { link -> - modifyState { - add(link) - } + if (isActive) + modifyState { + add(link) + } }, isCasting = false, offset = index, subtitleCallback = { link -> - if (isValidSubtitle(link)) + if (isActive && isValidSubtitle(link)) modifyState { add(link) } }) + Unit } if (!isActive) { @@ -368,9 +385,13 @@ class PlayerGeneratorViewModel : ViewModel() { /** Only mark as success if we have not skipped loading */ modifyState { - when (loading) { - is Resource.Loading -> copy(loading = loadingState) - else -> this + if (!isActive) { + this + } else { + when (loading) { + is Resource.Loading -> copy(loading = loadingState) + else -> this + } } } }