mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-06-18 19:56:50 +00:00
Compare commits
7 commits
master
...
fixobserve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
519bea8117 |
||
|
|
62ed338c1d |
||
|
|
6fcb99489b |
||
|
|
1a77d2057d |
||
|
|
80acc5a6a2 |
||
|
|
35171b4a5d |
||
|
|
bcf04cfc09 |
2 changed files with 113 additions and 63 deletions
|
|
@ -143,7 +143,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
const val STOP_ACTION = "stopcs3"
|
||||
|
||||
private val generators = ConcurrentHashMap<String, VideoGenerator<*>>()
|
||||
fun newInstance(generator: VideoGenerator<*>, index : Int, syncData: HashMap<String, String>? = null): Bundle {
|
||||
fun newInstance(
|
||||
generator: VideoGenerator<*>,
|
||||
index: Int,
|
||||
syncData: HashMap<String, String>? = 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<ResultEpisode>? get() = viewModel.state.generatorState?.allMeta?.filterIsInstance<ResultEpisode>()?.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<ResultEpisode>?
|
||||
get() = viewModel.state.generatorState?.allMeta?.filterIsInstance<ResultEpisode>()
|
||||
?.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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ data class VideoState(
|
|||
val subtitles: PersistentSet<SubtitleData> = persistentSetOf(),
|
||||
val links: PersistentSet<VideoLink> = persistentSetOf(),
|
||||
val stamps: PersistentList<VideoSkipStamp> = persistentListOf(),
|
||||
val loading: Resource<Boolean?> = Resource.Loading(),
|
||||
val loading: Resource<Unit> = 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<VideoSkipStamp>): VideoState = copy(stamps = items.toPersistentList())
|
||||
}
|
||||
|
||||
data class VideoLive<T>(
|
||||
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<Set<Pair<ExtractorLink?, ExtractorUri?>>>(setOf())
|
||||
val currentLinks: LiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>> = _currentLinks
|
||||
private val _currentLinks =
|
||||
MutableLiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>>(null)
|
||||
val currentLinks: LiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>> = _currentLinks
|
||||
|
||||
private val _currentSubtitles = MutableLiveData<Set<SubtitleData>>(setOf())
|
||||
val currentSubtitles: LiveData<Set<SubtitleData>> = _currentSubtitles
|
||||
private val _currentSubtitles = MutableLiveData<VideoLive<Set<SubtitleData>>>(null)
|
||||
val currentSubtitles: LiveData<VideoLive<Set<SubtitleData>>> = _currentSubtitles
|
||||
|
||||
private val _loadingLinks = MutableLiveData<Resource<Boolean?>>()
|
||||
val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks
|
||||
private val _loadingLinks = MutableLiveData<VideoLive<Resource<Unit>>>()
|
||||
val loadingLinks: LiveData<VideoLive<Resource<Unit>>> = _loadingLinks
|
||||
|
||||
private val _currentStamps = MutableLiveData<List<VideoSkipStamp>>(emptyList())
|
||||
val currentStamps: LiveData<List<VideoSkipStamp>> = _currentStamps
|
||||
private val _currentStamps = MutableLiveData<VideoLive<List<VideoSkipStamp>>>(null)
|
||||
val currentStamps: LiveData<VideoLive<List<VideoSkipStamp>>> = _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<Int?>(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<ExtractorLinkType> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue