Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.lagradost.cloudstream3.ui.player

import com.lagradost.cloudstream3.utils.ExtractorLinkType

internal data class EpisodeSourceSelection(
val source: String?,
val name: String,
val quality: Int?,
val type: ExtractorLinkType?,
)

internal sealed interface EpisodeSubtitleSelection {
data class Selected(
val originalName: String,
val nameSuffix: String,
) : EpisodeSubtitleSelection

data object Disabled : EpisodeSubtitleSelection
}

internal data class EpisodeSubtitleResolution(
val subtitle: SubtitleData?,
val selectionMatched: Boolean,
val subtitlesDisabled: Boolean,
)

internal fun VideoLink.toEpisodeSourceSelection(): EpisodeSourceSelection? {
val link = first
if (link != null) {
return EpisodeSourceSelection(link.source, link.name, link.quality, link.type)
}

val uri = second ?: return null
return EpisodeSourceSelection(null, uri.name, null, null)
}

internal fun findEpisodeSource(
links: List<VideoLink>,
selection: EpisodeSourceSelection?,
): VideoLink? {
return selection?.let { remembered ->
links.firstOrNull { it.toEpisodeSourceSelection() == remembered }
}
}

internal fun createEpisodeSubtitleSelection(
subtitle: SubtitleData?,
subtitlesDisabled: Boolean,
): EpisodeSubtitleSelection? {
return when {
subtitle != null -> EpisodeSubtitleSelection.Selected(
subtitle.originalName,
subtitle.nameSuffix,
)

subtitlesDisabled -> EpisodeSubtitleSelection.Disabled
else -> null
}
}

internal fun findEpisodeSubtitle(
subtitles: Set<SubtitleData>,
selection: EpisodeSubtitleSelection?,
): SubtitleData? {
val remembered = selection as? EpisodeSubtitleSelection.Selected ?: return null
return subtitles.firstOrNull {
it.originalName == remembered.originalName &&
it.nameSuffix == remembered.nameSuffix
}
}

internal fun resolveEpisodeSubtitle(
subtitles: Set<SubtitleData>,
selection: EpisodeSubtitleSelection?,
fallback: () -> SubtitleData?,
): EpisodeSubtitleResolution {
val rememberedSubtitle = findEpisodeSubtitle(subtitles, selection)
val subtitlesDisabled = selection == EpisodeSubtitleSelection.Disabled
return EpisodeSubtitleResolution(
subtitle = when {
subtitlesDisabled -> null
rememberedSubtitle != null -> rememberedSubtitle
else -> fallback()
},
selectionMatched = rememberedSubtitle != null,
subtitlesDisabled = subtitlesDisabled,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.CloudStreamApp
import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
Expand Down Expand Up @@ -96,7 +95,6 @@ import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageTagIETF
import com.lagradost.cloudstream3.utils.AppContextUtils.getShortSeasonText
Expand Down Expand Up @@ -177,13 +175,32 @@ class GeneratorPlayer : FullScreenPlayer() {

private var currentSelectedLink: Pair<ExtractorLink?, ExtractorUri?>? = null
private var currentSelectedSubtitles: SubtitleData? = null
private var nextEpisodeSelection: EpisodePlaybackSelection? = null
private val currentMeta: Any? get() = viewModel.state.generatorState?.meta
private val nextMeta: Any? get() = viewModel.state.generatorState?.nextMeta

private var isPlayerActive: AtomicBoolean = AtomicBoolean(false)
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 subtitlesDisabledByUser = false

private data class EpisodePlaybackSelection(
val source: EpisodeSourceSelection?,
val subtitle: EpisodeSubtitleSelection?,
)

private fun rememberEpisodePlaybackSelection() {
val subtitle = player.getCurrentPreferredSubtitle() ?: currentSelectedSubtitles
nextEpisodeSelection = EpisodePlaybackSelection(
source = currentSelectedLink?.toEpisodeSourceSelection(),
subtitle = createEpisodeSubtitleSelection(
subtitle,
subtitlesDisabled = subtitlesDisabledByUser,
),
)
}

private val allMeta: List<ResultEpisode>?
get() = viewModel.state.generatorState?.allMeta?.filterIsInstance<ResultEpisode>()
?.map { episode ->
Expand All @@ -194,19 +211,12 @@ class GeneratorPlayer : FullScreenPlayer() {
}

private fun setSubtitles(subtitle: SubtitleData?, userInitiated: Boolean): Boolean {
// If subtitle is changed and user initiated -> Save the language
if (subtitle != currentSelectedSubtitles && userInitiated) {
val subtitleLanguageTagIETF = if (subtitle == null) {
"" // -> No Subtitles
} else {
subtitle.getIETF_tag()
}

if (subtitleLanguageTagIETF != null) {
Log.i(TAG, "Set SUBTITLE_AUTO_SELECT_KEY to '$subtitleLanguageTagIETF'")
setKey(SUBTITLE_AUTO_SELECT_KEY, subtitleLanguageTagIETF)
preferredAutoSelectSubtitles = subtitleLanguageTagIETF
}
if (userInitiated) {
nextEpisodeSelection = null
subtitlesDisabledByUser = subtitle == null
}
if (subtitle != null) {
subtitlesDisabledByUser = false
}

currentSelectedSubtitles = subtitle
Expand Down Expand Up @@ -523,6 +533,22 @@ class GeneratorPlayer : FullScreenPlayer() {
context?.let { ctx ->
val (url, uri) = link
val subtitles = viewModel.state.subtitles
val subtitleSelection = nextEpisodeSelection?.subtitle
val subtitleResolution = resolveEpisodeSubtitle(subtitles, subtitleSelection) {
getAutoSelectSubtitle(subtitles, settings = true, downloads = true)
}
val preferredSubtitle =
if (sameEpisode) currentSelectedSubtitles else subtitleResolution.subtitle
if (!sameEpisode) {
subtitlesDisabledByUser = subtitleResolution.subtitlesDisabled
}
currentSelectedSubtitles = preferredSubtitle
if (!sameEpisode &&
(subtitleSelection !is EpisodeSubtitleSelection.Selected ||
subtitleResolution.selectionMatched)
) {
nextEpisodeSelection = null
}
player.loadPlayer(
ctx,
sameEpisode,
Expand All @@ -532,9 +558,7 @@ class GeneratorPlayer : FullScreenPlayer() {
if (isNextEpisode) 0L else getPos()
},
subtitles,
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
subtitles, settings = true, downloads = true
),
preferredSubtitle,
preview = true
)
}
Expand Down Expand Up @@ -1574,7 +1598,19 @@ class GeneratorPlayer : FullScreenPlayer() {
if (!isPlayerActive.compareAndSet(false, true)) {
return
}
loadLink(links.first(), false)

val preferredSource = nextEpisodeSelection?.source
val preferredLink = findEpisodeSource(links, preferredSource)
if (preferredSource != null &&
preferredLink == null &&
viewModel.state.loading is Resource.Loading
) {
isPlayerActive.set(false)
return
}

nextEpisodeSelection = nextEpisodeSelection?.copy(source = null)
loadLink(preferredLink ?: links.first(), false)
showPlayerMetadata()
}

Expand Down Expand Up @@ -1628,6 +1664,7 @@ class GeneratorPlayer : FullScreenPlayer() {
override fun nextEpisode() {
if (viewModel.hasNextEpisode() == true) {
isNextEpisode = true
rememberEpisodePlaybackSelection()
releasePlayer()
viewModel.loadLinksNext()
}
Expand All @@ -1636,6 +1673,7 @@ class GeneratorPlayer : FullScreenPlayer() {
override fun prevEpisode() {
if (viewModel.hasPrevEpisode() == true) {
isNextEpisode = true
rememberEpisodePlaybackSelection()
releasePlayer()
viewModel.loadLinksPrev()
}
Expand Down Expand Up @@ -1806,10 +1844,40 @@ class GeneratorPlayer : FullScreenPlayer() {
player.handleEvent(CSPlayerEvent.Play)
}

private fun autoSelectFromPreviousEpisode(): Boolean {
if (!isPlayerActive.get()) return false
val selection = nextEpisodeSelection ?: return false
if (selection.subtitle == EpisodeSubtitleSelection.Disabled) {
nextEpisodeSelection = null
return true
}
val subtitle = findEpisodeSubtitle(
viewModel.state.subtitles,
selection.subtitle,
) ?: run {
if (selection.subtitle == null) {
nextEpisodeSelection = null
}
return false
}
nextEpisodeSelection = null
context?.let { ctx ->
if (setSubtitles(subtitle, false)) {
player.saveData()
player.reloadPlayer(ctx)
player.handleEvent(CSPlayerEvent.Play)
}
}
return true
}

private fun autoSelectSubtitles() {
//Log.i(TAG, "autoSelectSubtitles")
safe {
if (!autoSelectFromSettings()) {
if (!subtitlesDisabledByUser &&
!autoSelectFromPreviousEpisode() &&
!autoSelectFromSettings()
) {
autoSelectFromDownloads()
}
}
Expand Down Expand Up @@ -2095,6 +2163,7 @@ class GeneratorPlayer : FullScreenPlayer() {
{ episodeClick ->
if (episodeClick.action == ACTION_CLICK_DEFAULT) {
isNextEpisode = false
nextEpisodeSelection = null
releasePlayer()
playerEpisodeOverlay.isGone = true
episodeClick.position?.let { viewModel.loadThisEpisode(it) }
Expand Down
Loading