Skip to content

Commit

Permalink
auto select
Browse files Browse the repository at this point in the history
  • Loading branch information
NieR4ever committed Sep 24, 2024
1 parent e54ac69 commit 8744284
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 15 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<CompletedCondition>
}

/**
* 启动所有 [MediaSource] 的查询, 挂起当前协程, 直到所有 [MediaSource] 都查询完成.
*
* 支持 cancellation.
*/
suspend fun MediaFetchSession.awaitCompletion() {
suspend fun MediaFetchSession.awaitCompletion(
onHasCompletedChanged: suspend (completedCondition: CompletedCondition) -> 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 @@ -338,10 +338,43 @@ class MediaSourceMediaFetcher(
}

override val hasCompleted = if (mediaSourceResults.isEmpty()) {
flowOf(true)
flowOf(CompletedCondition.AllCompleted)
} else {
combine(mediaSourceResults.map { it.state }) { states ->
val webStates = mediaSourceResults.filter { it.kind == MediaSourceKind.WEB }
.map { it.state }
val bitTorrentStates = mediaSourceResults.filter { it.kind == MediaSourceKind.BitTorrent }
.map { it.state }
val localCacheStates = mediaSourceResults.filter { it.kind == MediaSourceKind.LocalCache }
.map { it.state }

val webCompleted = combine(webStates) { states ->
states.all { it is MediaSourceFetchState.Completed || it is MediaSourceFetchState.Disabled }
}.onStart {
if (webStates.isEmpty()) emit(false)
}
val btCompleted = combine(bitTorrentStates) { states ->
states.all { it is MediaSourceFetchState.Completed || it is MediaSourceFetchState.Disabled }
}.onStart {
if (bitTorrentStates.isEmpty()) emit(false)
}
val localCacheCompleted = combine(localCacheStates) { states ->
states.all { it is MediaSourceFetchState.Completed || it is MediaSourceFetchState.Disabled }
}.onStart {
if (localCacheStates.isEmpty()) emit(false)
}
val allCompleted = combine(mediaSourceResults.map { it.state }) { states ->
states.all { it is MediaSourceFetchState.Completed || it is MediaSourceFetchState.Disabled }
}

combine(
webCompleted, btCompleted, localCacheCompleted, allCompleted,
) { web, bt, local, all ->
CompletedCondition(
webCompleted = web,
btCompleted = bt,
localCacheCompleted = local,
allCompleted = all,
)
}.flowOn(flowContext)
}
}
Expand All @@ -358,3 +391,20 @@ class MediaSourceMediaFetcher(
private const val ENABLE_WATCHDOG = false
}
}

class CompletedCondition(
val webCompleted: Boolean,
val btCompleted: Boolean,
val localCacheCompleted: Boolean,
val allCompleted: Boolean,
) {

companion object {
val AllCompleted = CompletedCondition(
webCompleted = true,
btCompleted = true,
localCacheCompleted = true,
allCompleted = 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 Expand Up @@ -45,6 +45,7 @@ interface MediaSelector {
val subtitleLanguageId: MediaPreferenceItem<String>
val mediaSourceId: MediaPreferenceItem<String>

val preferKind: Flow<MediaSourceKind?>
/**
* 经过 [alliance], [resolution] 等[偏好][MediaPreference]筛选后的列表.
*/
Expand Down Expand Up @@ -302,6 +303,8 @@ class DefaultMediaSelector(
getFromPreference = { it.mediaSourceId },
)

override val preferKind: Flow<MediaSourceKind?> = mediaSelectorSettings.map { it.preferKind }

/**
* 当前会话中的生效偏好
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ value class MediaSelectorAutoSelect(
*/
suspend fun awaitCompletedAndSelectDefault(mediaFetchSession: MediaFetchSession): Media? {
// 等全部加载完成
mediaFetchSession.awaitCompletion()
mediaFetchSession.awaitCompletion { completedCondition ->
if (completedCondition.allCompleted) return@awaitCompletion true
return@awaitCompletion mediaSelector.preferKind.first()?.let {
when (it) {
MediaSourceKind.WEB -> completedCondition.webCompleted
MediaSourceKind.BitTorrent -> completedCondition.btCompleted
MediaSourceKind.LocalCache -> completedCondition.localCacheCompleted
}
} ?: completedCondition.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 @@ -386,7 +386,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,15 +5,17 @@ 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 kotlinx.coroutines.flow.flowOf
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.source.MediaSourceKind
import me.him188.ani.datasources.api.topic.Resolution
import me.him188.ani.datasources.api.topic.SubtitleLanguage.ChineseSimplified
import me.him188.ani.datasources.api.topic.SubtitleLanguage.ChineseTraditional
Expand Down Expand Up @@ -57,6 +59,7 @@ open class TestMediaSelector(
final override val resolution: TestMediaPreferenceItem<String> = TestMediaPreferenceItem()
final override val subtitleLanguageId: TestMediaPreferenceItem<String> = TestMediaPreferenceItem()
final override val mediaSourceId: TestMediaPreferenceItem<String> = TestMediaPreferenceItem()
final override val preferKind: Flow<MediaSourceKind?> = flowOf(MediaSourceKind.WEB)

private val mergedPreference = combine(
defaultPreference,
Expand Down

0 comments on commit 8744284

Please sign in to comment.