mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-06-19 20:05:41 +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"
|
const val STOP_ACTION = "stopcs3"
|
||||||
|
|
||||||
private val generators = ConcurrentHashMap<String, VideoGenerator<*>>()
|
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")
|
Log.i(TAG, "newInstance = $syncData")
|
||||||
val uuid = UUID.randomUUID().toString()
|
val uuid = UUID.randomUUID().toString()
|
||||||
generators[uuid] = generator
|
generators[uuid] = generator
|
||||||
|
|
@ -178,7 +182,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
private var isNextEpisode: Boolean = false // this is used to reset the watch time
|
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 var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none
|
||||||
private val allMeta: List<ResultEpisode>? get() = viewModel.state.generatorState?.allMeta?.filterIsInstance<ResultEpisode>()?.map { episode ->
|
private val allMeta: List<ResultEpisode>?
|
||||||
|
get() = viewModel.state.generatorState?.allMeta?.filterIsInstance<ResultEpisode>()
|
||||||
|
?.map { episode ->
|
||||||
// Refresh all the episodes watch duration
|
// Refresh all the episodes watch duration
|
||||||
getViewPos(episode.id)?.let { data ->
|
getViewPos(episode.id)?.let { data ->
|
||||||
episode.copy(position = data.position, duration = data.duration)
|
episode.copy(position = data.position, duration = data.duration)
|
||||||
|
|
@ -2140,6 +2146,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
fun releasePlayer() {
|
fun releasePlayer() {
|
||||||
player.release()
|
player.release()
|
||||||
currentSelectedSubtitles = null
|
currentSelectedSubtitles = null
|
||||||
|
currentSelectedLink = null
|
||||||
isPlayerActive.set(false)
|
isPlayerActive.set(false)
|
||||||
binding?.overlayLoadingSkipButton?.isVisible = false
|
binding?.overlayLoadingSkipButton?.isVisible = false
|
||||||
binding?.playerLoadingOverlay?.isVisible = true
|
binding?.playerLoadingOverlay?.isVisible = true
|
||||||
|
|
@ -2152,19 +2159,31 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
outState.putInt("index", viewModel.episodeIndex)
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentPlayerBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentPlayerBinding, savedInstanceState: Bundle?) {
|
||||||
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
||||||
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
||||||
|
|
||||||
val uuid = savedInstanceState?.getString("uuid") ?: arguments?.getString("uuid")
|
val uuid = savedInstanceState?.getString("uuid") ?: arguments?.getString("uuid")
|
||||||
val index = savedInstanceState?.getInt("index") ?: arguments?.getInt("index")
|
val index = savedInstanceState?.getInt("index") ?: arguments?.getInt("index")
|
||||||
|
val generator = generators[uuid]
|
||||||
viewModel.attachGenerator(generators[uuid], index)
|
|
||||||
|
|
||||||
unwrapBundle(savedInstanceState)
|
unwrapBundle(savedInstanceState)
|
||||||
unwrapBundle(arguments)
|
unwrapBundle(arguments)
|
||||||
|
|
||||||
super.onBindingCreated(binding, savedInstanceState)
|
super.onBindingCreated(binding, savedInstanceState)
|
||||||
|
|
||||||
|
// Avoid showing no links found
|
||||||
|
if (generator == null || index == null) {
|
||||||
|
exitPlayer()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
viewModel.attachGenerator(generator, index)
|
||||||
|
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
showName = settingsManager.getBoolean(ctx.getString(R.string.show_name_key), true)
|
showName = settingsManager.getBoolean(ctx.getString(R.string.show_name_key), true)
|
||||||
|
|
@ -2193,14 +2212,18 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
preferredAutoSelectSubtitles = getAutoSelectLanguageTagIETF()
|
preferredAutoSelectSubtitles = getAutoSelectLanguageTagIETF()
|
||||||
|
|
||||||
if (currentSelectedLink == null) {
|
val selectedLink = currentSelectedLink
|
||||||
|
if (selectedLink == null) {
|
||||||
viewModel.loadLinks()
|
viewModel.loadLinks()
|
||||||
|
} else {
|
||||||
|
// Recreated view, so we need to recreate the
|
||||||
|
loadLink(selectedLink, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.overlayLoadingSkipButton.setOnClickListener {
|
binding.overlayLoadingSkipButton.setOnClickListener {
|
||||||
// Mark as "success" early
|
// Mark as "success" early
|
||||||
viewModel.modifyState {
|
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)
|
player.addTimeStamps(stamps)
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.currentSubtitles) { subtitles ->
|
observe(viewModel.currentSubtitles) { (subtitles, instance) ->
|
||||||
|
if (instance != viewModel.state.instance) return@observe // Outdated observe
|
||||||
player.setActiveSubtitles(subtitles)
|
player.setActiveSubtitles(subtitles)
|
||||||
|
|
||||||
// If the file is downloaded then do not select auto select the subtitles
|
// If the file is downloaded then do not select auto select the subtitles
|
||||||
|
|
@ -2233,7 +2258,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
autoSelectSubtitles()
|
autoSelectSubtitles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
observe(viewModel.loadingLinks) { loading ->
|
observe(viewModel.loadingLinks) { (loading, instance) ->
|
||||||
|
if (instance != viewModel.state.instance) return@observe // Outdated observe
|
||||||
|
|
||||||
when (loading) {
|
when (loading) {
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
releasePlayer()
|
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 turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true
|
||||||
val wasGone = binding.overlayLoadingSkipButton.isGone
|
val wasGone = binding.overlayLoadingSkipButton.isGone
|
||||||
|
|
||||||
|
|
@ -2269,7 +2298,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
safe {
|
safe {
|
||||||
if (viewModel.state.links.any { link ->
|
if (!isPlayerActive.get() && viewModel.state.links.any { link ->
|
||||||
getLinkPriority(currentQualityProfile, link.first) >=
|
getLinkPriority(currentQualityProfile, link.first) >=
|
||||||
QualityDataHelper.AUTO_SKIP_PRIORITY
|
QualityDataHelper.AUTO_SKIP_PRIORITY
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,9 @@ data class VideoState(
|
||||||
val subtitles: PersistentSet<SubtitleData> = persistentSetOf(),
|
val subtitles: PersistentSet<SubtitleData> = persistentSetOf(),
|
||||||
val links: PersistentSet<VideoLink> = persistentSetOf(),
|
val links: PersistentSet<VideoLink> = persistentSetOf(),
|
||||||
val stamps: PersistentList<VideoSkipStamp> = persistentListOf(),
|
val stamps: PersistentList<VideoSkipStamp> = persistentListOf(),
|
||||||
val loading: Resource<Boolean?> = Resource.Loading(),
|
val loading: Resource<Unit> = Resource.Loading(),
|
||||||
val generatorState: GeneratorState? = null,
|
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.
|
* 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())
|
fun set(items: Collection<VideoSkipStamp>): VideoState = copy(stamps = items.toPersistentList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class VideoLive<T>(
|
||||||
|
val value: T,
|
||||||
|
val instance: Int,
|
||||||
|
)
|
||||||
|
|
||||||
class PlayerGeneratorViewModel : ViewModel() {
|
class PlayerGeneratorViewModel : ViewModel() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "PlayViewGen"
|
const val TAG = "PlayViewGen"
|
||||||
|
|
@ -123,7 +129,7 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
var generator: VideoGenerator<*>? = null
|
var generator: VideoGenerator<*>? = null
|
||||||
|
|
||||||
@Volatile
|
@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,
|
* 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.
|
* This value can be used without Synchronized or locking when reading, as all fields are immutable.
|
||||||
* */
|
* */
|
||||||
@Volatile
|
@Volatile
|
||||||
var state = VideoState()
|
var state = VideoState(instance = 0)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val _currentLinks = MutableLiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>>(setOf())
|
private val _currentLinks =
|
||||||
val currentLinks: LiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>> = _currentLinks
|
MutableLiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>>(null)
|
||||||
|
val currentLinks: LiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>> = _currentLinks
|
||||||
|
|
||||||
private val _currentSubtitles = MutableLiveData<Set<SubtitleData>>(setOf())
|
private val _currentSubtitles = MutableLiveData<VideoLive<Set<SubtitleData>>>(null)
|
||||||
val currentSubtitles: LiveData<Set<SubtitleData>> = _currentSubtitles
|
val currentSubtitles: LiveData<VideoLive<Set<SubtitleData>>> = _currentSubtitles
|
||||||
|
|
||||||
private val _loadingLinks = MutableLiveData<Resource<Boolean?>>()
|
private val _loadingLinks = MutableLiveData<VideoLive<Resource<Unit>>>()
|
||||||
val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks
|
val loadingLinks: LiveData<VideoLive<Resource<Unit>>> = _loadingLinks
|
||||||
|
|
||||||
private val _currentStamps = MutableLiveData<List<VideoSkipStamp>>(emptyList())
|
private val _currentStamps = MutableLiveData<VideoLive<List<VideoSkipStamp>>>(null)
|
||||||
val currentStamps: LiveData<List<VideoSkipStamp>> = _currentStamps
|
val currentStamps: LiveData<VideoLive<List<VideoSkipStamp>>> = _currentStamps
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies the `state` variable safely, and with the correct observe behavior.
|
* Modifies the `state` variable safely, and with the correct observe behavior.
|
||||||
|
|
@ -158,6 +165,15 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
val oldState = state
|
val oldState = state
|
||||||
state = op.invoke(oldState)
|
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"
|
* 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.
|
* 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)
|
if (state.links !== oldState.links)
|
||||||
_currentLinks.postValue(state.links)
|
_currentLinks.postValue(VideoLive(state.links, state.instance))
|
||||||
if (state.stamps !== oldState.stamps)
|
if (state.stamps !== oldState.stamps)
|
||||||
_currentStamps.postValue(state.stamps)
|
_currentStamps.postValue(VideoLive(state.stamps, state.instance))
|
||||||
if (state.subtitles !== oldState.subtitles)
|
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 */
|
/** Normal equality here as it is not a collection */
|
||||||
if (state.loading != oldState.loading)
|
if (state.loading != oldState.loading)
|
||||||
_loadingLinks.postValue(state.loading)
|
_loadingLinks.postValue(VideoLive(state.loading, state.instance))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _currentSubtitleYear = MutableLiveData<Int?>(null)
|
private val _currentSubtitleYear = MutableLiveData<Int?>(null)
|
||||||
|
|
@ -252,14 +268,11 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
loadLinks()
|
loadLinks()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun attachGenerator(newGenerator: VideoGenerator<*>?, index: Int?) {
|
fun attachGenerator(newGenerator: VideoGenerator<*>, index: Int) {
|
||||||
if (generator == null) {
|
Log.i(TAG, "attachGenerator with generator=$newGenerator and index=$index")
|
||||||
generator = newGenerator
|
generator = newGenerator
|
||||||
if (index != null) {
|
|
||||||
episodeIndex = index
|
episodeIndex = index
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If duplicate nothing will happen
|
* If duplicate nothing will happen
|
||||||
|
|
@ -321,14 +334,14 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadLinks(sourceTypes: Set<ExtractorLinkType> = LOADTYPE_INAPP) {
|
fun loadLinks(sourceTypes: Set<ExtractorLinkType> = LOADTYPE_INAPP) {
|
||||||
Log.i(TAG, "loadLinks")
|
Log.i(TAG, "loadLinks with generator=$generator and index=$episodeIndex")
|
||||||
currentJob?.cancel()
|
currentJob?.cancel()
|
||||||
val index = episodeIndex
|
val index = episodeIndex
|
||||||
|
|
||||||
currentJob = viewModelScope.launchSafe {
|
|
||||||
// Clear old data and reset the state
|
// Clear old data and reset the state
|
||||||
modifyState {
|
modifyState {
|
||||||
VideoState(
|
VideoState(
|
||||||
|
loading = Resource.Loading(),
|
||||||
generatorState = generator?.let { gen ->
|
generatorState = generator?.let { gen ->
|
||||||
GeneratorState(
|
GeneratorState(
|
||||||
meta = gen.videos.getOrNull(index),
|
meta = gen.videos.getOrNull(index),
|
||||||
|
|
@ -338,16 +351,19 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
index = index,
|
index = index,
|
||||||
allMeta = gen.videos
|
allMeta = gen.videos
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
instance = instance + 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentJob = viewModelScope.launchSafe {
|
||||||
// Load more data
|
// Load more data
|
||||||
val loadingState = safeApiCall {
|
val loadingState = safeApiCall {
|
||||||
generator?.generateLinks(
|
generator?.generateLinks(
|
||||||
sourceTypes = sourceTypes,
|
sourceTypes = sourceTypes,
|
||||||
clearCache = forceClearCache,
|
clearCache = forceClearCache,
|
||||||
callback = { link ->
|
callback = { link ->
|
||||||
|
if (isActive)
|
||||||
modifyState {
|
modifyState {
|
||||||
add(link)
|
add(link)
|
||||||
}
|
}
|
||||||
|
|
@ -355,11 +371,12 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
isCasting = false,
|
isCasting = false,
|
||||||
offset = index,
|
offset = index,
|
||||||
subtitleCallback = { link ->
|
subtitleCallback = { link ->
|
||||||
if (isValidSubtitle(link))
|
if (isActive && isValidSubtitle(link))
|
||||||
modifyState {
|
modifyState {
|
||||||
add(link)
|
add(link)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
|
|
@ -368,6 +385,9 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
|
|
||||||
/** Only mark as success if we have not skipped loading */
|
/** Only mark as success if we have not skipped loading */
|
||||||
modifyState {
|
modifyState {
|
||||||
|
if (!isActive) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
when (loading) {
|
when (loading) {
|
||||||
is Resource.Loading -> copy(loading = loadingState)
|
is Resource.Loading -> copy(loading = loadingState)
|
||||||
else -> this
|
else -> this
|
||||||
|
|
@ -376,3 +396,4 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue