Skip to content

Commit

Permalink
优先选择偏好类型数据源,不需要等其他源查询完毕 (#1002)
Browse files Browse the repository at this point in the history
* auto select

* optimize something

* add argument preferKind

* use value class

* fix bug

* fix bug and add test

* optimize something

* optimize something

* optimize something

* optimize something
  • Loading branch information
NieR4ever authored Sep 30, 2024
1 parent f074700 commit ffcf03d
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,20 @@ interface MediaFetchSession {
* 注意, 即使 [hasCompletedOrDisabled] 现在为 `true`, 它也可能在未来因为数据源重试, 或者 [request] 变更而变为 `false`.
* 因此该 flow 永远不会完结.
*/
val hasCompleted: Flow<Boolean>
val hasCompleted: Flow<CompletedConditions>
}

/**
* 启动所有 [MediaSource] 的查询, 挂起当前协程, 直到所有 [MediaSource] 都查询完成.
*
* 支持 cancellation.
*/
suspend fun MediaFetchSession.awaitCompletion() {
suspend fun MediaFetchSession.awaitCompletion(
onHasCompletedChanged: suspend (completedConditions: CompletedConditions) -> Boolean = { it.allCompleted() }
) {
cancellableCoroutineScope {
cumulativeResults.shareIn(this, started = SharingStarted.Eagerly, replay = 1)
hasCompleted.first { it }
hasCompleted.first { onHasCompletedChanged(it) }
cancelScope()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import me.him188.ani.utils.coroutines.cancellableCoroutineScope
import me.him188.ani.utils.logging.error
import me.him188.ani.utils.logging.info
import me.him188.ani.utils.logging.logger
import me.him188.ani.utils.platform.collections.EnumMap
import me.him188.ani.utils.platform.collections.ImmutableEnumMap
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

Expand Down Expand Up @@ -338,10 +340,23 @@ class MediaSourceMediaFetcher(
}

override val hasCompleted = if (mediaSourceResults.isEmpty()) {
flowOf(true)
flowOf(CompletedConditions.AllCompleted)
} else {
combine(mediaSourceResults.map { it.state }) { states ->
states.all { it is MediaSourceFetchState.Completed || it is MediaSourceFetchState.Disabled }
combine(mediaSourceResults.map { it.state }) {
val pairs = mediaSourceResults.groupBy { it.kind }.mapValues { results ->
val states = results.value.map { it.state }
when {
// 该类型数据源全部禁用时返回 null,如果返回 false 会导致 awaitCompletion 无法结束
states.all { it.value is MediaSourceFetchState.Disabled } -> null
states.all { it.value is MediaSourceFetchState.Completed || it.value is MediaSourceFetchState.Disabled } -> true
else -> false
}
}
CompletedConditions(
ImmutableEnumMap<MediaSourceKind, _> { kind ->
pairs[kind]
},
)
}.flowOn(flowContext)
}
}
Expand All @@ -358,3 +373,21 @@ class MediaSourceMediaFetcher(
private const val ENABLE_WATCHDOG = false
}
}

class CompletedConditions(
private val values: EnumMap<MediaSourceKind, Boolean?>
) {
fun allCompleted() = values.values.all { it ?: true }

operator fun get(kind: MediaSourceKind): Boolean? = try {
values[kind]
} catch (e: NoSuchElementException) {
null
}

companion object {
val AllCompleted = CompletedConditions(
ImmutableEnumMap { true },
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import me.him188.ani.app.data.models.preference.MediaSelectorSettings
import me.him188.ani.app.data.models.preference.MediaPreference
import me.him188.ani.app.data.models.preference.MediaSelectorSettings
import me.him188.ani.datasources.api.Media
import me.him188.ani.datasources.api.source.MediaSourceKind
import me.him188.ani.datasources.api.source.MediaSourceLocation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package me.him188.ani.app.data.source.media.selector

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.takeWhile
import me.him188.ani.app.data.source.media.fetch.MediaFetchSession
import me.him188.ani.app.data.source.media.fetch.awaitCompletion
Expand All @@ -28,9 +30,16 @@ value class MediaSelectorAutoSelect(
*
* 返回成功选择的 [Media] 对象. 当用户已经手动选择过一个别的 [Media], 或者没有可选的 [Media] 时返回 `null`.
*/
suspend fun awaitCompletedAndSelectDefault(mediaFetchSession: MediaFetchSession): Media? {
suspend fun awaitCompletedAndSelectDefault(
mediaFetchSession: MediaFetchSession,
preferKind: Flow<MediaSourceKind?> = flowOf(null)
): Media? {
// 等全部加载完成
mediaFetchSession.awaitCompletion()
mediaFetchSession.awaitCompletion { completedConditions ->
return@awaitCompletion preferKind.first()?.let {
completedConditions[it]
} ?: completedConditions.allCompleted()
}
if (mediaSelector.selected.value == null) {
val selected = mediaSelector.trySelectDefault()
return selected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,14 @@ private class EpisodeViewModelImpl(
)
.apply {
autoSelect.run {

launchInBackground {
mediaFetchSession.collectLatest {
awaitSwitchEpisodeCompleted()
awaitCompletedAndSelectDefault(it)
awaitCompletedAndSelectDefault(
it,
settingsRepository.mediaSelectorSettings.flow.map { it.preferKind },
)
}
}
launchInBackground {
Expand Down Expand Up @@ -374,7 +378,7 @@ private class EpisodeViewModelImpl(
private val playerLauncher: PlayerLauncher = PlayerLauncher(
mediaSelector, videoSourceResolver, playerState, mediaSourceInfoProvider,
episodeInfo,
mediaFetchSession.flatMapLatest { it.hasCompleted }.map { !it },
mediaFetchSession.flatMapLatest { it.hasCompleted }.map { !it.allCompleted() },
backgroundScope.coroutineContext,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class MediaFetcherTest {
assertEquals(1, session.mediaSourceResults.size)
val res = session.mediaSourceResults.first()
assertIs<MediaSourceFetchState.Idle>(res.state.value)
assertEquals(false, session.hasCompleted.first())
assertEquals(false, session.hasCompleted.first().allCompleted())
}

///////////////////////////////////////////////////////////////////////////
Expand All @@ -104,7 +104,7 @@ class MediaFetcherTest {
fun `hasCompleted is initially true if no source`() = runTest {
val session = createFetcher().newSession(request1)
assertEquals(0, session.mediaSourceResults.size)
assertEquals(true, session.hasCompleted.first())
assertEquals(true, session.hasCompleted.first().allCompleted())
}

@Test
Expand All @@ -113,7 +113,7 @@ class MediaFetcherTest {
assertEquals(1, session.mediaSourceResults.size)
val res = session.mediaSourceResults.first()
assertIs<MediaSourceFetchState.Idle>(res.state.value)
assertEquals(false, session.hasCompleted.first())
assertEquals(false, session.hasCompleted.first().allCompleted())
}

@Test
Expand All @@ -125,7 +125,7 @@ class MediaFetcherTest {
assertEquals(2, session.mediaSourceResults.size)
assertIs<MediaSourceFetchState.Disabled>(session.mediaSourceResults.first().state.value)
assertIs<MediaSourceFetchState.Disabled>(session.mediaSourceResults.toList()[1].state.value)
assertEquals(true, session.hasCompleted.first())
assertEquals(true, session.hasCompleted.first().allCompleted())
}

@Test
Expand All @@ -137,7 +137,7 @@ class MediaFetcherTest {
assertEquals(2, session.mediaSourceResults.size)
assertIs<MediaSourceFetchState.Disabled>(session.mediaSourceResults.first().state.value)
assertIs<MediaSourceFetchState.Idle>(session.mediaSourceResults[1].state.value)
assertEquals(false, session.hasCompleted.first())
assertEquals(false, session.hasCompleted.first().allCompleted())
}

///////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -396,7 +396,7 @@ class MediaFetcherTest {
assertIs<MediaSourceFetchState.Disabled>(session.mediaSourceResults.first().state.value)
assertIs<MediaSourceFetchState.Disabled>(session.mediaSourceResults[1].state.value)
assertEquals(0, session.awaitCompletedResults().size)
assertEquals(true, session.hasCompleted.first())
assertEquals(true, session.hasCompleted.first().allCompleted())
}

///////////////////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ package me.him188.ani.app.data.source.media.framework
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import me.him188.ani.app.data.models.preference.MediaPreference
import me.him188.ani.app.data.source.media.selector.DefaultMediaSelector
import me.him188.ani.app.data.source.media.selector.MediaPreferenceItem
import me.him188.ani.app.data.source.media.selector.MediaSelector
import me.him188.ani.app.data.source.media.selector.MediaSelectorEvents
import me.him188.ani.app.data.source.media.selector.MutableMediaSelectorEvents
import me.him188.ani.app.data.source.media.selector.OptionalPreference
import me.him188.ani.app.data.source.media.selector.orElse
import me.him188.ani.app.data.models.preference.MediaPreference
import me.him188.ani.datasources.api.Media
import me.him188.ani.datasources.api.topic.Resolution
import me.him188.ani.datasources.api.topic.SubtitleLanguage.ChineseSimplified
Expand Down
Loading

0 comments on commit ffcf03d

Please sign in to comment.