mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-03 18:16:15 +00:00
feat: complete wiring bingeGroup handling for stream selection and update related components
This commit is contained in:
parent
3107ea162e
commit
f37c37d85d
5 changed files with 217 additions and 11 deletions
|
|
@ -964,6 +964,7 @@ private fun MainAppContent(
|
|||
episodeThumbnail = route.episodeThumbnail,
|
||||
streamTitle = stream.streamLabel,
|
||||
streamSubtitle = stream.streamSubtitle,
|
||||
bingeGroup = stream.behaviorHints.bingeGroup,
|
||||
pauseDescription = pauseDescription,
|
||||
providerName = stream.addonName,
|
||||
providerAddonId = stream.addonId,
|
||||
|
|
@ -1039,6 +1040,7 @@ private fun MainAppContent(
|
|||
episodeThumbnail = route.episodeThumbnail,
|
||||
streamTitle = stream.streamLabel,
|
||||
streamSubtitle = stream.streamSubtitle,
|
||||
bingeGroup = stream.behaviorHints.bingeGroup,
|
||||
pauseDescription = pauseDescription,
|
||||
providerName = stream.addonName,
|
||||
providerAddonId = stream.addonId,
|
||||
|
|
@ -1101,6 +1103,7 @@ private fun MainAppContent(
|
|||
episodeThumbnail = launch.episodeThumbnail,
|
||||
streamTitle = launch.streamTitle,
|
||||
streamSubtitle = launch.streamSubtitle,
|
||||
initialBingeGroup = launch.bingeGroup,
|
||||
pauseDescription = launch.pauseDescription,
|
||||
providerName = launch.providerName,
|
||||
providerAddonId = launch.providerAddonId,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ data class PlayerLaunch(
|
|||
val episodeThumbnail: String? = null,
|
||||
val streamTitle: String,
|
||||
val streamSubtitle: String? = null,
|
||||
val bingeGroup: String? = null,
|
||||
val pauseDescription: String? = null,
|
||||
val providerName: String,
|
||||
val providerAddonId: String? = null,
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ fun PlayerScreen(
|
|||
providerName: String,
|
||||
streamTitle: String,
|
||||
streamSubtitle: String?,
|
||||
initialBingeGroup: String? = null,
|
||||
pauseDescription: String? = null,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -135,6 +136,7 @@ fun PlayerScreen(
|
|||
var activeStreamSubtitle by rememberSaveable { mutableStateOf(streamSubtitle) }
|
||||
var activeProviderName by rememberSaveable { mutableStateOf(providerName) }
|
||||
var activeProviderAddonId by rememberSaveable { mutableStateOf(providerAddonId) }
|
||||
var currentStreamBingeGroup by rememberSaveable { mutableStateOf(initialBingeGroup) }
|
||||
var activeSeasonNumber by rememberSaveable { mutableStateOf(seasonNumber) }
|
||||
var activeEpisodeNumber by rememberSaveable { mutableStateOf(episodeNumber) }
|
||||
var activeEpisodeTitle by rememberSaveable { mutableStateOf(episodeTitle) }
|
||||
|
|
@ -531,6 +533,7 @@ fun PlayerScreen(
|
|||
activeStreamSubtitle = stream.streamSubtitle
|
||||
activeProviderName = stream.addonName
|
||||
activeProviderAddonId = stream.addonId
|
||||
currentStreamBingeGroup = stream.behaviorHints.bingeGroup
|
||||
activeInitialPositionMs = currentPositionMs
|
||||
activeInitialProgressFraction = null
|
||||
showSourcesPanel = false
|
||||
|
|
@ -584,6 +587,7 @@ fun PlayerScreen(
|
|||
activeStreamSubtitle = stream.streamSubtitle
|
||||
activeProviderName = stream.addonName
|
||||
activeProviderAddonId = stream.addonId
|
||||
currentStreamBingeGroup = stream.behaviorHints.bingeGroup
|
||||
activeSeasonNumber = episode.season
|
||||
activeEpisodeNumber = episode.episode
|
||||
activeEpisodeTitle = episode.title
|
||||
|
|
@ -606,19 +610,39 @@ fun PlayerScreen(
|
|||
|
||||
val type = contentType ?: parentMetaType
|
||||
val settings = playerSettingsUiState
|
||||
val shouldAutoSelectInManualMode =
|
||||
settings.streamAutoPlayMode == StreamAutoPlayMode.MANUAL &&
|
||||
(
|
||||
settings.streamAutoPlayNextEpisodeEnabled ||
|
||||
settings.streamAutoPlayPreferBingeGroup
|
||||
)
|
||||
|
||||
// Determine auto-play mode for next episode
|
||||
val effectiveMode = if (settings.streamAutoPlayMode == StreamAutoPlayMode.MANUAL) {
|
||||
if (settings.streamAutoPlayNextEpisodeEnabled) StreamAutoPlayMode.FIRST_STREAM
|
||||
else StreamAutoPlayMode.MANUAL
|
||||
val effectiveMode = if (shouldAutoSelectInManualMode) {
|
||||
StreamAutoPlayMode.FIRST_STREAM
|
||||
} else {
|
||||
settings.streamAutoPlayMode
|
||||
}
|
||||
|
||||
val currentBingeGroup = if (settings.streamAutoPlayPreferBingeGroup) {
|
||||
// Try to find binge group of current stream (not directly available, pass empty)
|
||||
val effectiveSource = if (shouldAutoSelectInManualMode) {
|
||||
com.nuvio.app.features.streams.StreamAutoPlaySource.ALL_SOURCES
|
||||
} else {
|
||||
settings.streamAutoPlaySource
|
||||
}
|
||||
val effectiveSelectedAddons = if (shouldAutoSelectInManualMode) {
|
||||
emptySet()
|
||||
} else {
|
||||
settings.streamAutoPlaySelectedAddons
|
||||
}
|
||||
val effectiveSelectedPlugins = if (shouldAutoSelectInManualMode) {
|
||||
emptySet()
|
||||
} else {
|
||||
settings.streamAutoPlaySelectedPlugins
|
||||
}
|
||||
val effectiveRegex = if (shouldAutoSelectInManualMode) {
|
||||
""
|
||||
} else ""
|
||||
} else {
|
||||
settings.streamAutoPlayRegex
|
||||
}
|
||||
|
||||
nextEpisodeAutoPlayJob = scope.launch {
|
||||
PlayerStreamsRepository.loadEpisodeStreams(
|
||||
|
|
@ -646,11 +670,17 @@ fun PlayerScreen(
|
|||
StreamAutoPlaySelector.selectAutoPlayStream(
|
||||
streams = allStreams,
|
||||
mode = effectiveMode,
|
||||
regexPattern = settings.streamAutoPlayRegex,
|
||||
source = settings.streamAutoPlaySource,
|
||||
regexPattern = effectiveRegex,
|
||||
source = effectiveSource,
|
||||
installedAddonNames = installedAddonNames,
|
||||
selectedAddons = settings.streamAutoPlaySelectedAddons,
|
||||
selectedPlugins = settings.streamAutoPlaySelectedPlugins,
|
||||
selectedAddons = effectiveSelectedAddons,
|
||||
selectedPlugins = effectiveSelectedPlugins,
|
||||
preferredBingeGroup = if (settings.streamAutoPlayPreferBingeGroup) {
|
||||
currentStreamBingeGroup
|
||||
} else {
|
||||
null
|
||||
},
|
||||
preferBingeGroupInSelection = settings.streamAutoPlayPreferBingeGroup,
|
||||
)
|
||||
} else null
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ object StreamAutoPlaySelector {
|
|||
installedAddonNames: Set<String>,
|
||||
selectedAddons: Set<String>,
|
||||
selectedPlugins: Set<String>,
|
||||
preferredBingeGroup: String? = null,
|
||||
preferBingeGroupInSelection: Boolean = false,
|
||||
): StreamItem? {
|
||||
if (streams.isEmpty()) return null
|
||||
|
||||
|
|
@ -29,6 +31,14 @@ object StreamAutoPlaySelector {
|
|||
if (candidateStreams.isEmpty()) return null
|
||||
if (mode == StreamAutoPlayMode.MANUAL) return null
|
||||
|
||||
val targetBingeGroup = preferredBingeGroup?.trim().orEmpty()
|
||||
if (preferBingeGroupInSelection && targetBingeGroup.isNotEmpty()) {
|
||||
val bingeGroupMatch = candidateStreams.firstOrNull { stream ->
|
||||
stream.behaviorHints.bingeGroup == targetBingeGroup && stream.directPlaybackUrl != null
|
||||
}
|
||||
if (bingeGroupMatch != null) return bingeGroupMatch
|
||||
}
|
||||
|
||||
return when (mode) {
|
||||
StreamAutoPlayMode.MANUAL -> null
|
||||
StreamAutoPlayMode.FIRST_STREAM -> candidateStreams.firstOrNull { it.directPlaybackUrl != null }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
package com.nuvio.app.features.streams
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class StreamAutoPlaySelectorTest {
|
||||
|
||||
@Test
|
||||
fun `bingeGroup-first selects matching stream before first stream mode`() {
|
||||
val first = stream(
|
||||
addonName = "AddonA",
|
||||
url = "https://example.com/first.m3u8",
|
||||
name = "1080p",
|
||||
bingeGroup = "other-group",
|
||||
)
|
||||
val preferred = stream(
|
||||
addonName = "AddonB",
|
||||
url = "https://example.com/preferred.m3u8",
|
||||
name = "720p",
|
||||
bingeGroup = "same-group",
|
||||
)
|
||||
|
||||
val selected = StreamAutoPlaySelector.selectAutoPlayStream(
|
||||
streams = listOf(first, preferred),
|
||||
mode = StreamAutoPlayMode.FIRST_STREAM,
|
||||
regexPattern = "",
|
||||
source = StreamAutoPlaySource.ALL_SOURCES,
|
||||
installedAddonNames = setOf("AddonA", "AddonB"),
|
||||
selectedAddons = emptySet(),
|
||||
selectedPlugins = emptySet(),
|
||||
preferredBingeGroup = "same-group",
|
||||
preferBingeGroupInSelection = true,
|
||||
)
|
||||
|
||||
assertEquals(preferred, selected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `falls back to normal mode when no bingeGroup match exists`() {
|
||||
val first = stream(
|
||||
addonName = "AddonA",
|
||||
url = "https://example.com/first.m3u8",
|
||||
name = "First",
|
||||
bingeGroup = "group-a",
|
||||
)
|
||||
val second = stream(
|
||||
addonName = "AddonB",
|
||||
url = "https://example.com/second.m3u8",
|
||||
name = "Second",
|
||||
bingeGroup = "group-b",
|
||||
)
|
||||
|
||||
val selected = StreamAutoPlaySelector.selectAutoPlayStream(
|
||||
streams = listOf(first, second),
|
||||
mode = StreamAutoPlayMode.FIRST_STREAM,
|
||||
regexPattern = "",
|
||||
source = StreamAutoPlaySource.ALL_SOURCES,
|
||||
installedAddonNames = setOf("AddonA", "AddonB"),
|
||||
selectedAddons = emptySet(),
|
||||
selectedPlugins = emptySet(),
|
||||
preferredBingeGroup = "missing-group",
|
||||
preferBingeGroupInSelection = true,
|
||||
)
|
||||
|
||||
assertEquals(first, selected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bingeGroup-first respects source and addon plugin filters`() {
|
||||
val filteredOutAddonMatch = stream(
|
||||
addonName = "AddonFilteredOut",
|
||||
url = "https://example.com/addon-match.m3u8",
|
||||
bingeGroup = "same-group",
|
||||
)
|
||||
val allowedPluginMatch = stream(
|
||||
addonName = "PluginAllowed",
|
||||
url = "https://example.com/plugin-match.m3u8",
|
||||
bingeGroup = "same-group",
|
||||
)
|
||||
|
||||
val selected = StreamAutoPlaySelector.selectAutoPlayStream(
|
||||
streams = listOf(filteredOutAddonMatch, allowedPluginMatch),
|
||||
mode = StreamAutoPlayMode.FIRST_STREAM,
|
||||
regexPattern = "",
|
||||
source = StreamAutoPlaySource.ENABLED_PLUGINS_ONLY,
|
||||
installedAddonNames = setOf("AddonFilteredOut"),
|
||||
selectedAddons = emptySet(),
|
||||
selectedPlugins = setOf("PluginAllowed"),
|
||||
preferredBingeGroup = "same-group",
|
||||
preferBingeGroupInSelection = true,
|
||||
)
|
||||
|
||||
assertEquals(allowedPluginMatch, selected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blank preferredBingeGroup behaves as disabled`() {
|
||||
val first = stream(
|
||||
addonName = "AddonA",
|
||||
url = "https://example.com/first.m3u8",
|
||||
bingeGroup = "group-a",
|
||||
)
|
||||
val second = stream(
|
||||
addonName = "AddonB",
|
||||
url = "https://example.com/second.m3u8",
|
||||
bingeGroup = "group-b",
|
||||
)
|
||||
|
||||
val selected = StreamAutoPlaySelector.selectAutoPlayStream(
|
||||
streams = listOf(first, second),
|
||||
mode = StreamAutoPlayMode.FIRST_STREAM,
|
||||
regexPattern = "",
|
||||
source = StreamAutoPlaySource.ALL_SOURCES,
|
||||
installedAddonNames = setOf("AddonA", "AddonB"),
|
||||
selectedAddons = emptySet(),
|
||||
selectedPlugins = emptySet(),
|
||||
preferredBingeGroup = " ",
|
||||
preferBingeGroupInSelection = true,
|
||||
)
|
||||
|
||||
assertEquals(first, selected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `manual mode remains manual even with matching bingeGroup`() {
|
||||
val matched = stream(
|
||||
addonName = "AddonA",
|
||||
url = "https://example.com/match.m3u8",
|
||||
bingeGroup = "same-group",
|
||||
)
|
||||
|
||||
val selected = StreamAutoPlaySelector.selectAutoPlayStream(
|
||||
streams = listOf(matched),
|
||||
mode = StreamAutoPlayMode.MANUAL,
|
||||
regexPattern = "",
|
||||
source = StreamAutoPlaySource.ALL_SOURCES,
|
||||
installedAddonNames = setOf("AddonA"),
|
||||
selectedAddons = emptySet(),
|
||||
selectedPlugins = emptySet(),
|
||||
preferredBingeGroup = "same-group",
|
||||
preferBingeGroupInSelection = true,
|
||||
)
|
||||
|
||||
assertNull(selected)
|
||||
}
|
||||
|
||||
private fun stream(
|
||||
addonName: String,
|
||||
url: String? = null,
|
||||
name: String? = null,
|
||||
bingeGroup: String? = null,
|
||||
): StreamItem = StreamItem(
|
||||
name = name,
|
||||
url = url,
|
||||
addonName = addonName,
|
||||
addonId = addonName,
|
||||
behaviorHints = StreamBehaviorHints(
|
||||
bingeGroup = bingeGroup,
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Reference in a new issue