diff --git a/app/shared/data/common/data/subject/SubjectManager.kt b/app/shared/data/common/data/subject/SubjectManager.kt index b93b9d98b..bf3a26f58 100644 --- a/app/shared/data/common/data/subject/SubjectManager.kt +++ b/app/shared/data/common/data/subject/SubjectManager.kt @@ -126,6 +126,8 @@ interface SubjectManager { */ suspend fun getEpisode(episodeId: Int): Episode + suspend fun getEpisodeCollections(subjectId: Int): List + fun episodeCollectionFlow(subjectId: Int, episodeId: Int, contentPolicy: ContentPolicy): Flow suspend fun setSubjectCollectionType(subjectId: Int, type: UnifiedCollectionType) @@ -196,7 +198,6 @@ class SubjectManagerImpl( episodeSort = episode.episode.sort.toString(), watchStatus = episode.type.toCollectionType(), isOnAir = episode.episode.isOnAir(), - airDate = PackedDate.parseFromDate(episode.episode.airdate), cacheStatus = cacheStatus, ) } @@ -226,6 +227,13 @@ class SubjectManagerImpl( } } + override suspend fun getEpisodeCollections(subjectId: Int): List { + return findCachedSubjectCollection(subjectId)?._episodes + ?: runUntilSuccess { + episodeRepository.getSubjectEpisodeCollection(subjectId, EpType.MainStory) + }.toList() + } + override fun episodeCollectionFlow( subjectId: Int, episodeId: Int, diff --git a/app/shared/pages/episode-play/common/mediaFetch/EpisodeMediaFetchSession.kt b/app/shared/pages/episode-play/common/mediaFetch/EpisodeMediaFetchSession.kt index 00fb500dd..4ac9cde6a 100644 --- a/app/shared/pages/episode-play/common/mediaFetch/EpisodeMediaFetchSession.kt +++ b/app/shared/pages/episode-play/common/mediaFetch/EpisodeMediaFetchSession.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest @@ -26,13 +27,13 @@ import me.him188.ani.app.data.repositories.SubjectRepository import me.him188.ani.app.data.subject.PackedDate import me.him188.ani.app.data.subject.SubjectManager import me.him188.ani.app.data.subject.minus -import me.him188.ani.app.tools.caching.ContentPolicy import me.him188.ani.app.ui.foundation.BackgroundScope import me.him188.ani.app.ui.foundation.HasBackgroundScope import me.him188.ani.app.ui.foundation.launchInBackground import me.him188.ani.datasources.api.EpisodeSort import me.him188.ani.datasources.api.Media import me.him188.ani.datasources.api.source.MediaFetchRequest +import me.him188.ani.datasources.bangumi.processing.isOnAir import me.him188.ani.datasources.bangumi.processing.nameCNOrName import me.him188.ani.datasources.core.fetch.MediaFetchSession import me.him188.ani.datasources.core.fetch.MediaFetcher @@ -204,17 +205,22 @@ internal class DefaultEpisodeMediaFetchSession( ) } - val subjectProgress = subjectManager.subjectProgressFlow(subjectId, ContentPolicy.CACHE_ONLY) + val subjectProgress = flow { + emit(subjectManager.getEpisodeCollections(subjectId).map { it.episode }) + } DefaultMediaSelector( mediaSelectorContextNotCached = subjectProgress.map { eps -> - val allEpisodesFinished = eps.fastAll { it.isOnAir == false } + val allEpisodesFinished = eps.fastAll { it.isOnAir() == false } val finishedLongTimeAgo = allEpisodesFinished || run { val now = PackedDate.now() val maxAirDate = eps - .filter { it.airDate.isValid } - .maxOfOrNull { it.airDate } + .map { + PackedDate.parseFromDate(it.airdate) + } + .filter { it.isValid } + .maxOrNull() maxAirDate != null && now - maxAirDate >= 14.days } diff --git a/app/shared/pages/subject-collection/android/EpisodeProgress.android.kt b/app/shared/pages/subject-collection/android/EpisodeProgress.android.kt index ee858d993..f8fe54532 100644 --- a/app/shared/pages/subject-collection/android/EpisodeProgress.android.kt +++ b/app/shared/pages/subject-collection/android/EpisodeProgress.android.kt @@ -24,7 +24,6 @@ private val testEpisodes = listOf( episodeSort = "00", watchStatus = UnifiedCollectionType.DONE, isOnAir = false, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Caching(0.3f, 300.megaBytes), ), EpisodeProgressItem( @@ -32,7 +31,6 @@ private val testEpisodes = listOf( episodeSort = "01", watchStatus = UnifiedCollectionType.DONE, isOnAir = false, - airDate = airDate, cacheStatus = EpisodeCacheStatus.NotCached, ), EpisodeProgressItem( @@ -40,7 +38,6 @@ private val testEpisodes = listOf( episodeSort = "02", watchStatus = UnifiedCollectionType.DONE, isOnAir = false, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Cached(300.megaBytes), ), EpisodeProgressItem( @@ -48,7 +45,6 @@ private val testEpisodes = listOf( episodeSort = "03", watchStatus = UnifiedCollectionType.WISH, isOnAir = false, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Cached(300.megaBytes), ), EpisodeProgressItem( @@ -56,7 +52,6 @@ private val testEpisodes = listOf( episodeSort = "04", watchStatus = UnifiedCollectionType.WISH, isOnAir = false, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Caching(0.7f, 300.megaBytes), ), EpisodeProgressItem( @@ -64,7 +59,6 @@ private val testEpisodes = listOf( episodeSort = "05", watchStatus = UnifiedCollectionType.WISH, isOnAir = false, - airDate = airDate, cacheStatus = EpisodeCacheStatus.NotCached, ), EpisodeProgressItem( @@ -72,7 +66,6 @@ private val testEpisodes = listOf( episodeSort = "06", watchStatus = UnifiedCollectionType.WISH, isOnAir = true, - airDate = airDate, cacheStatus = EpisodeCacheStatus.NotCached, ), EpisodeProgressItem( @@ -80,7 +73,6 @@ private val testEpisodes = listOf( episodeSort = "07", watchStatus = UnifiedCollectionType.WISH, isOnAir = true, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Cached(300.megaBytes), ), EpisodeProgressItem( @@ -88,7 +80,6 @@ private val testEpisodes = listOf( episodeSort = "08", watchStatus = UnifiedCollectionType.WISH, isOnAir = true, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Caching(0.3f, 300.megaBytes), ), ) @@ -170,6 +161,5 @@ private fun item(id: Int) = EpisodeProgressItem( episodeSort = id.toString(), watchStatus = UnifiedCollectionType.WISH, isOnAir = true, - airDate = airDate, cacheStatus = EpisodeCacheStatus.Caching(0.3f, 300.megaBytes), ) diff --git a/app/shared/pages/subject-collection/common/progress/EpisodeProgress.kt b/app/shared/pages/subject-collection/common/progress/EpisodeProgress.kt index 43062da4a..b31b780bf 100644 --- a/app/shared/pages/subject-collection/common/progress/EpisodeProgress.kt +++ b/app/shared/pages/subject-collection/common/progress/EpisodeProgress.kt @@ -154,12 +154,15 @@ class EpisodeProgressItem( val episodeSort: String, val watchStatus: UnifiedCollectionType, val isOnAir: Boolean?, - val airDate: PackedDate, val cacheStatus: EpisodeCacheStatus?, ) { var isLoading by mutableStateOf(false) } +class EpisodeInfo( + val airDate: PackedDate, +) + @Composable fun EpisodeProgressRow( diff --git a/data-sources/nyafun/src/NyafunMediaSource.kt b/data-sources/nyafun/src/NyafunMediaSource.kt index 00c7f7107..c0b7e978f 100644 --- a/data-sources/nyafun/src/NyafunMediaSource.kt +++ b/data-sources/nyafun/src/NyafunMediaSource.kt @@ -11,7 +11,6 @@ import io.ktor.http.isSuccess import io.ktor.utils.io.jvm.javaio.toInputStream import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow @@ -134,12 +133,23 @@ class NyafunMediaSource(config: MediaSourceConfig) : MediaSource { alliance = ID, size = FileSize.Unspecified, ), - episodeRange = EpisodeRange.single(sort), + episodeRange = EpisodeRange.single( + if (isPossiblyMovie(ep.name) && sort is EpisodeSort.Special) { + EpisodeSort(1) // 电影总是 01 + } else { + sort + } + ), location = MediaSourceLocation.Online, kind = MediaSourceKind.WEB, ), MatchKind.FUZZY ) } + + private fun isPossiblyMovie(title: String): Boolean { + val t = title + return ("简" in t || "繁" in t) && ("2160P" in t || "1440P" in t || "2K" in t || "4K" in t || "1080P" in t || "720P" in t) + } } class Factory : MediaSourceFactory { @@ -183,26 +193,31 @@ class NyafunMediaSource(config: MediaSourceConfig) : MediaSource { bangumiList.asFlow() .flatMapMerge { bangumi -> - flow { + val result = flow { emit(getDocument(bangumi.url)) }.map { parseEpisodeList(it) }.retry(3) { e -> logger.warn(e) { "Failed to get episodes using name '$name'" } true - }.firstOrNull()?.map { ep -> - createMediaMatch(bangumi, ep) - }.orEmpty().also { - logger.info { "$ID fetched ${it.size} episodes for '$name': ${it.joinToString { it.media.episodeRange.toString() }}" } - }.asFlow() - } - .filter { - it.definitelyMatches(query) + }.firstOrNull() + .orEmpty() + .asSequence() + .map { ep -> + createMediaMatch(bangumi, ep) + } + .filter { + it.definitelyMatches(query) || + isPossiblyMovie(it.media.originalTitle) + } + .toList() + + logger.info { "$ID fetched ${result.size} episodes for '$name': ${result.joinToString { it.media.episodeRange.toString() }}" } + result.asFlow() } } } - private suspend inline fun getDocument( url: String, block: HttpRequestBuilder.() -> Unit = {}