mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-30 04:43:18 +00:00
flavouring yt extraction
This commit is contained in:
parent
49a178c7f9
commit
f65f934acd
20 changed files with 225 additions and 84 deletions
|
|
@ -210,6 +210,15 @@ android {
|
|||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
flavorDimensions += "distribution"
|
||||
productFlavors {
|
||||
create("full") {
|
||||
dimension = "distribution"
|
||||
}
|
||||
create("playstore") {
|
||||
dimension = "distribution"
|
||||
}
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.nuvio.app.features.player
|
||||
|
||||
import androidx.media3.datasource.DataSource
|
||||
import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory
|
||||
|
||||
internal object PlatformPlaybackDataSourceFactory {
|
||||
fun create(defaultRequestHeaders: Map<String, String>): DataSource.Factory =
|
||||
YoutubeChunkedDataSourceFactory(defaultRequestHeaders = defaultRequestHeaders)
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@ import androidx.media3.ui.AspectRatioFrameLayout
|
|||
import androidx.media3.ui.PlayerView
|
||||
import androidx.media3.ui.SubtitleView
|
||||
import androidx.media3.ui.CaptionStyleCompat
|
||||
import com.nuvio.app.features.trailer.YoutubeChunkedDataSourceFactory
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -112,7 +111,7 @@ actual fun PlatformPlayerSurface(
|
|||
.setTsExtractorTimestampSearchBytes(1500 * TsExtractor.TS_PACKET_SIZE)
|
||||
|
||||
val mediaSourceFactory = DefaultMediaSourceFactory(
|
||||
YoutubeChunkedDataSourceFactory(defaultRequestHeaders = sanitizedSourceHeaders),
|
||||
PlatformPlaybackDataSourceFactory.create(defaultRequestHeaders = sanitizedSourceHeaders),
|
||||
extractorsFactory,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.nuvio.app.features.player
|
||||
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
|
||||
internal object PlatformPlaybackDataSourceFactory {
|
||||
fun create(defaultRequestHeaders: Map<String, String>): DataSource.Factory =
|
||||
DefaultHttpDataSource.Factory().setDefaultRequestProperties(defaultRequestHeaders)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.nuvio.app.features.trailer
|
||||
|
||||
actual object TrailerPlaybackResolver {
|
||||
actual suspend fun resolveFromYouTubeUrl(youtubeUrl: String): TrailerPlaybackSource? = null
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ import coil3.ImageLoader
|
|||
import coil3.compose.setSingletonImageLoaderFactory
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.crossfade
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.core.auth.AuthRepository
|
||||
import com.nuvio.app.core.auth.AuthState
|
||||
import com.nuvio.app.core.deeplink.AppDeepLink
|
||||
|
|
@ -609,7 +610,11 @@ private fun MainAppContent(
|
|||
onMetaScreenSettingsClick = { navController.navigate(MetaScreenSettingsRoute) },
|
||||
onContinueWatchingSettingsClick = { navController.navigate(ContinueWatchingSettingsRoute) },
|
||||
onAddonsSettingsClick = { navController.navigate(AddonsSettingsRoute) },
|
||||
onPluginsSettingsClick = { navController.navigate(PluginsSettingsRoute) },
|
||||
onPluginsSettingsClick = {
|
||||
if (AppFeaturePolicy.pluginsEnabled) {
|
||||
navController.navigate(PluginsSettingsRoute)
|
||||
}
|
||||
},
|
||||
onAccountSettingsClick = { navController.navigate(AccountSettingsRoute) },
|
||||
onInitialHomeContentRendered = { initialHomeReady = true },
|
||||
)
|
||||
|
|
@ -1084,10 +1089,12 @@ private fun MainAppContent(
|
|||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
composable<PluginsSettingsRoute> {
|
||||
PluginsSettingsScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
if (AppFeaturePolicy.pluginsEnabled) {
|
||||
composable<PluginsSettingsRoute> {
|
||||
PluginsSettingsScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
}
|
||||
composable<AccountSettingsRoute> {
|
||||
AccountSettingsScreen(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nuvio.app.core.storage
|
||||
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.catalog.CatalogRepository
|
||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||
|
|
@ -28,7 +29,9 @@ internal object LocalAccountDataCleaner {
|
|||
|
||||
ProfileRepository.clearInMemory()
|
||||
AddonRepository.clearLocalState()
|
||||
PluginRepository.clearLocalState()
|
||||
if (AppFeaturePolicy.pluginsEnabled) {
|
||||
PluginRepository.clearLocalState()
|
||||
}
|
||||
HomeRepository.clear()
|
||||
HomeCatalogSettingsRepository.clearLocalState()
|
||||
MetaScreenSettingsRepository.clearLocalState()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.nuvio.app.core.sync
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.core.auth.AuthRepository
|
||||
import com.nuvio.app.core.auth.AuthState
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
|
|
@ -31,10 +32,12 @@ object SyncManager {
|
|||
.onSuccess { log.i { "pullAllForProfile — addons pull completed" } }
|
||||
.onFailure { log.e(it) { "Addon pull failed" } }
|
||||
|
||||
log.i { "pullAllForProfile — pulling plugins (await)..." }
|
||||
runCatching { PluginRepository.pullFromServer(profileId) }
|
||||
.onSuccess { log.i { "pullAllForProfile — plugins pull completed" } }
|
||||
.onFailure { log.e(it) { "Plugin pull failed" } }
|
||||
if (AppFeaturePolicy.pluginsEnabled) {
|
||||
log.i { "pullAllForProfile — pulling plugins (await)..." }
|
||||
runCatching { PluginRepository.pullFromServer(profileId) }
|
||||
.onSuccess { log.i { "pullAllForProfile — plugins pull completed" } }
|
||||
.onFailure { log.e(it) { "Plugin pull failed" } }
|
||||
}
|
||||
|
||||
log.i { "pullAllForProfile — launching remaining pulls in parallel" }
|
||||
launch {
|
||||
|
|
|
|||
|
|
@ -44,12 +44,15 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.core.build.TrailerPlaybackMode
|
||||
import com.nuvio.app.core.ui.NuvioBackButton
|
||||
import com.nuvio.app.core.ui.TraktListPickerDialog
|
||||
import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding
|
||||
|
|
@ -295,37 +298,43 @@ fun MetaDetailsScreen(
|
|||
val hasTrailersSection = remember(meta) {
|
||||
meta.trailers.isNotEmpty()
|
||||
}
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val inAppTrailerPlaybackEnabled = AppFeaturePolicy.trailerPlaybackMode == TrailerPlaybackMode.IN_APP
|
||||
val trailerScope = rememberCoroutineScope()
|
||||
var selectedTrailer by remember(meta.id) { mutableStateOf<MetaTrailer?>(null) }
|
||||
var trailerPlaybackSource by remember(meta.id) { mutableStateOf<TrailerPlaybackSource?>(null) }
|
||||
var trailerLoading by remember(meta.id) { mutableStateOf(false) }
|
||||
var trailerErrorMessage by remember(meta.id) { mutableStateOf<String?>(null) }
|
||||
var trailerRequestToken by remember(meta.id) { mutableIntStateOf(0) }
|
||||
val resolveTrailer: (MetaTrailer) -> Unit = remember(meta.id) {
|
||||
val resolveTrailer: (MetaTrailer) -> Unit = remember(meta.id, inAppTrailerPlaybackEnabled, uriHandler) {
|
||||
{ trailer ->
|
||||
selectedTrailer = trailer
|
||||
trailerPlaybackSource = null
|
||||
trailerErrorMessage = null
|
||||
trailerLoading = true
|
||||
trailerRequestToken += 1
|
||||
val currentRequestToken = trailerRequestToken
|
||||
trailerScope.launch {
|
||||
val youtubeUrl = trailer.key.takeIf {
|
||||
it.startsWith("http://") || it.startsWith("https://")
|
||||
} ?: "https://www.youtube.com/watch?v=${trailer.key}"
|
||||
val resolvedSource = runCatching {
|
||||
TrailerPlaybackResolver.resolveFromYouTubeUrl(youtubeUrl)
|
||||
}.getOrNull()
|
||||
if (currentRequestToken != trailerRequestToken) {
|
||||
return@launch
|
||||
val youtubeUrl = trailer.key.takeIf {
|
||||
it.startsWith("http://") || it.startsWith("https://")
|
||||
} ?: "https://www.youtube.com/watch?v=${trailer.key}"
|
||||
if (!inAppTrailerPlaybackEnabled) {
|
||||
runCatching { uriHandler.openUri(youtubeUrl) }
|
||||
} else {
|
||||
selectedTrailer = trailer
|
||||
trailerPlaybackSource = null
|
||||
trailerErrorMessage = null
|
||||
trailerLoading = true
|
||||
trailerRequestToken += 1
|
||||
val currentRequestToken = trailerRequestToken
|
||||
trailerScope.launch {
|
||||
val resolvedSource = runCatching {
|
||||
TrailerPlaybackResolver.resolveFromYouTubeUrl(youtubeUrl)
|
||||
}.getOrNull()
|
||||
if (currentRequestToken != trailerRequestToken) {
|
||||
return@launch
|
||||
}
|
||||
trailerPlaybackSource = resolvedSource
|
||||
trailerErrorMessage = if (resolvedSource == null) {
|
||||
"No playable trailer stream found."
|
||||
} else {
|
||||
null
|
||||
}
|
||||
trailerLoading = false
|
||||
}
|
||||
trailerPlaybackSource = resolvedSource
|
||||
trailerErrorMessage = if (resolvedSource == null) {
|
||||
"No playable trailer stream found."
|
||||
} else {
|
||||
null
|
||||
}
|
||||
trailerLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -654,25 +663,27 @@ fun MetaDetailsScreen(
|
|||
)
|
||||
}
|
||||
|
||||
TrailerPlayerPopup(
|
||||
visible = selectedTrailer != null,
|
||||
trailerTitle = selectedTrailer?.displayName ?: selectedTrailer?.name.orEmpty(),
|
||||
trailerType = selectedTrailer?.type.orEmpty(),
|
||||
contentTitle = meta.name,
|
||||
playbackSource = trailerPlaybackSource,
|
||||
isLoading = trailerLoading,
|
||||
errorMessage = trailerErrorMessage,
|
||||
onDismiss = {
|
||||
trailerRequestToken += 1
|
||||
trailerLoading = false
|
||||
trailerPlaybackSource = null
|
||||
trailerErrorMessage = null
|
||||
selectedTrailer = null
|
||||
},
|
||||
onRetry = selectedTrailer?.let { trailer ->
|
||||
{ resolveTrailer(trailer) }
|
||||
},
|
||||
)
|
||||
if (inAppTrailerPlaybackEnabled) {
|
||||
TrailerPlayerPopup(
|
||||
visible = selectedTrailer != null,
|
||||
trailerTitle = selectedTrailer?.displayName ?: selectedTrailer?.name.orEmpty(),
|
||||
trailerType = selectedTrailer?.type.orEmpty(),
|
||||
contentTitle = meta.name,
|
||||
playbackSource = trailerPlaybackSource,
|
||||
isLoading = trailerLoading,
|
||||
errorMessage = trailerErrorMessage,
|
||||
onDismiss = {
|
||||
trailerRequestToken += 1
|
||||
trailerLoading = false
|
||||
trailerPlaybackSource = null
|
||||
trailerErrorMessage = null
|
||||
selectedTrailer = null
|
||||
},
|
||||
onRetry = selectedTrailer?.let { trailer ->
|
||||
{ resolveTrailer(trailer) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
TraktListPickerDialog(
|
||||
visible = showLibraryListPicker,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nuvio.app.features.player
|
||||
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.features.player.skip.NextEpisodeThresholdMode
|
||||
import com.nuvio.app.features.streams.StreamAutoPlayMode
|
||||
import com.nuvio.app.features.streams.StreamAutoPlaySource
|
||||
|
|
@ -141,6 +142,17 @@ object PlayerSettingsRepository {
|
|||
?: StreamAutoPlaySource.ALL_SOURCES
|
||||
streamAutoPlaySelectedAddons = PlayerSettingsStorage.loadStreamAutoPlaySelectedAddons() ?: emptySet()
|
||||
streamAutoPlaySelectedPlugins = PlayerSettingsStorage.loadStreamAutoPlaySelectedPlugins() ?: emptySet()
|
||||
if (!AppFeaturePolicy.pluginsEnabled) {
|
||||
val normalizedSource = normalizeStreamAutoPlaySource(streamAutoPlaySource)
|
||||
if (normalizedSource != streamAutoPlaySource) {
|
||||
streamAutoPlaySource = normalizedSource
|
||||
PlayerSettingsStorage.saveStreamAutoPlaySource(normalizedSource.name)
|
||||
}
|
||||
if (streamAutoPlaySelectedPlugins.isNotEmpty()) {
|
||||
streamAutoPlaySelectedPlugins = emptySet()
|
||||
PlayerSettingsStorage.saveStreamAutoPlaySelectedPlugins(emptySet())
|
||||
}
|
||||
}
|
||||
streamAutoPlayRegex = PlayerSettingsStorage.loadStreamAutoPlayRegex() ?: ""
|
||||
streamAutoPlayTimeoutSeconds = PlayerSettingsStorage.loadStreamAutoPlayTimeoutSeconds() ?: 3
|
||||
skipIntroEnabled = PlayerSettingsStorage.loadSkipIntroEnabled() ?: true
|
||||
|
|
@ -261,10 +273,11 @@ object PlayerSettingsRepository {
|
|||
|
||||
fun setStreamAutoPlaySource(source: StreamAutoPlaySource) {
|
||||
ensureLoaded()
|
||||
if (streamAutoPlaySource == source) return
|
||||
streamAutoPlaySource = source
|
||||
val normalizedSource = normalizeStreamAutoPlaySource(source)
|
||||
if (streamAutoPlaySource == normalizedSource) return
|
||||
streamAutoPlaySource = normalizedSource
|
||||
publish()
|
||||
PlayerSettingsStorage.saveStreamAutoPlaySource(source.name)
|
||||
PlayerSettingsStorage.saveStreamAutoPlaySource(normalizedSource.name)
|
||||
}
|
||||
|
||||
fun setStreamAutoPlaySelectedAddons(addons: Set<String>) {
|
||||
|
|
@ -277,10 +290,11 @@ object PlayerSettingsRepository {
|
|||
|
||||
fun setStreamAutoPlaySelectedPlugins(plugins: Set<String>) {
|
||||
ensureLoaded()
|
||||
if (streamAutoPlaySelectedPlugins == plugins) return
|
||||
streamAutoPlaySelectedPlugins = plugins
|
||||
val normalizedPlugins = if (AppFeaturePolicy.pluginsEnabled) plugins else emptySet()
|
||||
if (streamAutoPlaySelectedPlugins == normalizedPlugins) return
|
||||
streamAutoPlaySelectedPlugins = normalizedPlugins
|
||||
publish()
|
||||
PlayerSettingsStorage.saveStreamAutoPlaySelectedPlugins(plugins)
|
||||
PlayerSettingsStorage.saveStreamAutoPlaySelectedPlugins(normalizedPlugins)
|
||||
}
|
||||
|
||||
fun setStreamAutoPlayRegex(regex: String) {
|
||||
|
|
@ -392,4 +406,12 @@ object PlayerSettingsRepository {
|
|||
nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd,
|
||||
)
|
||||
}
|
||||
|
||||
private fun normalizeStreamAutoPlaySource(source: StreamAutoPlaySource): StreamAutoPlaySource {
|
||||
return if (!AppFeaturePolicy.pluginsEnabled && source == StreamAutoPlaySource.ENABLED_PLUGINS_ONLY) {
|
||||
StreamAutoPlaySource.ALL_SOURCES
|
||||
} else {
|
||||
source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.nuvio.app.features.player
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||
|
|
@ -149,8 +150,12 @@ object PlayerStreamsRepository {
|
|||
}
|
||||
|
||||
val installedAddons = AddonRepository.uiState.value.addons
|
||||
PluginRepository.initialize()
|
||||
val pluginScrapers = PluginRepository.getEnabledScrapersForType(type)
|
||||
val pluginScrapers = if (AppFeaturePolicy.pluginsEnabled) {
|
||||
PluginRepository.initialize()
|
||||
PluginRepository.getEnabledScrapersForType(type)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
if (installedAddons.isEmpty() && pluginScrapers.isEmpty()) {
|
||||
stateFlow.value = StreamsUiState(
|
||||
|
|
|
|||
|
|
@ -132,7 +132,9 @@ object ProfileRepository {
|
|||
LibraryRepository.onProfileChanged(profileIndex)
|
||||
WatchProgressRepository.onProfileChanged(profileIndex)
|
||||
AddonRepository.onProfileChanged(profileIndex)
|
||||
PluginRepository.onProfileChanged(profileIndex)
|
||||
if (com.nuvio.app.core.build.AppFeaturePolicy.pluginsEnabled) {
|
||||
PluginRepository.onProfileChanged(profileIndex)
|
||||
}
|
||||
ThemeSettingsRepository.onProfileChanged()
|
||||
PlayerSettingsRepository.onProfileChanged()
|
||||
HomeCatalogSettingsRepository.onProfileChanged()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.material.icons.rounded.Tune
|
|||
|
||||
internal fun LazyListScope.contentDiscoveryContent(
|
||||
isTablet: Boolean,
|
||||
showPluginsEntry: Boolean,
|
||||
onAddonsClick: () -> Unit,
|
||||
onPluginsClick: () -> Unit,
|
||||
onHomescreenClick: () -> Unit,
|
||||
|
|
@ -26,13 +27,15 @@ internal fun LazyListScope.contentDiscoveryContent(
|
|||
isTablet = isTablet,
|
||||
onClick = onAddonsClick,
|
||||
)
|
||||
SettingsNavigationRow(
|
||||
title = "Plugins",
|
||||
description = "Install JavaScript scraper repositories and test providers internally.",
|
||||
icon = Icons.Rounded.Hub,
|
||||
isTablet = isTablet,
|
||||
onClick = onPluginsClick,
|
||||
)
|
||||
if (showPluginsEntry) {
|
||||
SettingsNavigationRow(
|
||||
title = "Plugins",
|
||||
description = "Install JavaScript scraper repositories and test providers internally.",
|
||||
icon = Icons.Rounded.Hub,
|
||||
isTablet = isTablet,
|
||||
onClick = onPluginsClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nuvio.app.features.settings
|
||||
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -51,6 +52,7 @@ import com.nuvio.app.features.player.AvailableLanguageOptions
|
|||
import com.nuvio.app.features.player.PlayerSettingsRepository
|
||||
import com.nuvio.app.features.player.SubtitleLanguageOption
|
||||
import com.nuvio.app.features.player.languageLabelForCode
|
||||
import com.nuvio.app.features.plugins.PluginsUiState
|
||||
import com.nuvio.app.features.plugins.PluginRepository
|
||||
import com.nuvio.app.features.streams.StreamAutoPlayMode
|
||||
import com.nuvio.app.features.streams.StreamAutoPlaySource
|
||||
|
|
@ -111,9 +113,15 @@ private fun PlaybackSettingsSection(
|
|||
var showAutoPlayAddonSelectionDialog by remember { mutableStateOf(false) }
|
||||
var showAutoPlayPluginSelectionDialog by remember { mutableStateOf(false) }
|
||||
var showAutoPlayRegexDialog by remember { mutableStateOf(false) }
|
||||
val pluginsEnabled = AppFeaturePolicy.pluginsEnabled
|
||||
val autoPlayPlayerSettings by PlayerSettingsRepository.uiState.collectAsStateWithLifecycle()
|
||||
val addonUiState by AddonRepository.uiState.collectAsStateWithLifecycle()
|
||||
val pluginUiState by PluginRepository.uiState.collectAsStateWithLifecycle()
|
||||
val pluginUiState = if (pluginsEnabled) {
|
||||
val state by PluginRepository.uiState.collectAsStateWithLifecycle()
|
||||
state
|
||||
} else {
|
||||
PluginsUiState(pluginsEnabled = false)
|
||||
}
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val sectionSpacing = if (isTablet) 18.dp else 12.dp
|
||||
|
||||
|
|
@ -292,7 +300,7 @@ private fun PlaybackSettingsSection(
|
|||
SettingsNavigationRow(
|
||||
title = "Source Scope",
|
||||
description = when (autoPlayPlayerSettings.streamAutoPlaySource) {
|
||||
StreamAutoPlaySource.ALL_SOURCES -> "All Sources"
|
||||
StreamAutoPlaySource.ALL_SOURCES -> if (pluginsEnabled) "All Sources" else "All Addons"
|
||||
StreamAutoPlaySource.INSTALLED_ADDONS_ONLY -> "Installed Addons Only"
|
||||
StreamAutoPlaySource.ENABLED_PLUGINS_ONLY -> "Enabled Plugins Only"
|
||||
},
|
||||
|
|
@ -313,7 +321,7 @@ private fun PlaybackSettingsSection(
|
|||
onClick = { showAutoPlayAddonSelectionDialog = true },
|
||||
)
|
||||
}
|
||||
if (autoPlayPlayerSettings.streamAutoPlaySource != StreamAutoPlaySource.INSTALLED_ADDONS_ONLY) {
|
||||
if (pluginsEnabled && autoPlayPlayerSettings.streamAutoPlaySource != StreamAutoPlaySource.INSTALLED_ADDONS_ONLY) {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
val pluginSubtitle = if (autoPlayPlayerSettings.streamAutoPlaySelectedPlugins.isEmpty()) {
|
||||
"All Plugins"
|
||||
|
|
@ -678,6 +686,7 @@ private fun PlaybackSettingsSection(
|
|||
|
||||
if (showAutoPlaySourceDialog) {
|
||||
StreamAutoPlaySourceDialog(
|
||||
pluginsEnabled = pluginsEnabled,
|
||||
selectedSource = autoPlayPlayerSettings.streamAutoPlaySource,
|
||||
onSourceSelected = {
|
||||
PlayerSettingsRepository.setStreamAutoPlaySource(it)
|
||||
|
|
@ -707,7 +716,7 @@ private fun PlaybackSettingsSection(
|
|||
)
|
||||
}
|
||||
|
||||
if (showAutoPlayPluginSelectionDialog) {
|
||||
if (pluginsEnabled && showAutoPlayPluginSelectionDialog) {
|
||||
val pluginNames = pluginUiState.scrapers
|
||||
.filter { it.enabled }
|
||||
.map { it.name }
|
||||
|
|
@ -1121,15 +1130,40 @@ private fun StreamAutoPlayModeDialog(
|
|||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun StreamAutoPlaySourceDialog(
|
||||
pluginsEnabled: Boolean,
|
||||
selectedSource: StreamAutoPlaySource,
|
||||
onSourceSelected: (StreamAutoPlaySource) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val options = listOf(
|
||||
Triple(StreamAutoPlaySource.ALL_SOURCES, "All Sources", "Consider streams from both addons and plugins."),
|
||||
Triple(StreamAutoPlaySource.INSTALLED_ADDONS_ONLY, "Installed Addons Only", "Only consider streams from installed addons."),
|
||||
Triple(StreamAutoPlaySource.ENABLED_PLUGINS_ONLY, "Enabled Plugins Only", "Only consider streams from enabled plugins."),
|
||||
)
|
||||
val options = buildList {
|
||||
add(
|
||||
Triple(
|
||||
StreamAutoPlaySource.ALL_SOURCES,
|
||||
if (pluginsEnabled) "All Sources" else "All Addons",
|
||||
if (pluginsEnabled) {
|
||||
"Consider streams from both addons and plugins."
|
||||
} else {
|
||||
"Consider streams from all installed addons."
|
||||
},
|
||||
),
|
||||
)
|
||||
add(
|
||||
Triple(
|
||||
StreamAutoPlaySource.INSTALLED_ADDONS_ONLY,
|
||||
"Installed Addons Only",
|
||||
"Only consider streams from installed addons.",
|
||||
),
|
||||
)
|
||||
if (pluginsEnabled) {
|
||||
add(
|
||||
Triple(
|
||||
StreamAutoPlaySource.ENABLED_PLUGINS_ONLY,
|
||||
"Enabled Plugins Only",
|
||||
"Only consider streams from enabled plugins.",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nuvio.app.features.settings
|
||||
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -135,6 +136,11 @@ fun AddonsSettingsScreen(
|
|||
fun PluginsSettingsScreen(
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
if (!AppFeaturePolicy.pluginsEnabled) {
|
||||
AddonsSettingsScreen(onBack = onBack)
|
||||
return
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
PluginRepository.initialize()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.nuvio.app.features.settings
|
||||
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -290,13 +292,14 @@ private fun MobileSettingsScreen(
|
|||
)
|
||||
SettingsPage.ContentDiscovery -> contentDiscoveryContent(
|
||||
isTablet = false,
|
||||
showPluginsEntry = AppFeaturePolicy.pluginsEnabled,
|
||||
onAddonsClick = onAddonsClick,
|
||||
onPluginsClick = onPluginsClick,
|
||||
onHomescreenClick = onHomescreenClick,
|
||||
onMetaScreenClick = onMetaScreenClick,
|
||||
)
|
||||
SettingsPage.Addons -> addonsSettingsContent()
|
||||
SettingsPage.Plugins -> pluginsSettingsContent()
|
||||
SettingsPage.Plugins -> if (AppFeaturePolicy.pluginsEnabled) pluginsSettingsContent() else addonsSettingsContent()
|
||||
SettingsPage.Homescreen -> homescreenSettingsContent(
|
||||
isTablet = false,
|
||||
heroEnabled = homescreenHeroEnabled,
|
||||
|
|
@ -484,13 +487,14 @@ private fun TabletSettingsScreen(
|
|||
)
|
||||
SettingsPage.ContentDiscovery -> contentDiscoveryContent(
|
||||
isTablet = true,
|
||||
showPluginsEntry = AppFeaturePolicy.pluginsEnabled,
|
||||
onAddonsClick = { openInlinePage(SettingsPage.Addons) },
|
||||
onPluginsClick = { openInlinePage(SettingsPage.Plugins) },
|
||||
onHomescreenClick = { openInlinePage(SettingsPage.Homescreen) },
|
||||
onMetaScreenClick = { openInlinePage(SettingsPage.MetaScreen) },
|
||||
)
|
||||
SettingsPage.Addons -> addonsSettingsContent()
|
||||
SettingsPage.Plugins -> pluginsSettingsContent()
|
||||
SettingsPage.Plugins -> if (AppFeaturePolicy.pluginsEnabled) pluginsSettingsContent() else addonsSettingsContent()
|
||||
SettingsPage.Homescreen -> homescreenSettingsContent(
|
||||
isTablet = true,
|
||||
heroEnabled = homescreenHeroEnabled,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.nuvio.app.features.streams
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||
import com.nuvio.app.features.player.PlayerSettingsRepository
|
||||
import com.nuvio.app.features.plugins.PluginRepository
|
||||
import com.nuvio.app.features.plugins.PluginsUiState
|
||||
import com.nuvio.app.features.plugins.PluginRepositoryItem
|
||||
import com.nuvio.app.features.plugins.PluginRuntimeResult
|
||||
import com.nuvio.app.features.plugins.PluginScraper
|
||||
|
|
@ -39,8 +41,12 @@ object StreamsRepository {
|
|||
}
|
||||
|
||||
private fun load(type: String, videoId: String, season: Int?, episode: Int?, forceRefresh: Boolean) {
|
||||
PluginRepository.initialize()
|
||||
val pluginUiState = PluginRepository.uiState.value
|
||||
val pluginUiState = if (AppFeaturePolicy.pluginsEnabled) {
|
||||
PluginRepository.initialize()
|
||||
PluginRepository.uiState.value
|
||||
} else {
|
||||
PluginsUiState(pluginsEnabled = false)
|
||||
}
|
||||
val requestKey = "$type::$videoId::$season::$episode::pluginsGrouped=${pluginUiState.groupStreamsByRepository}"
|
||||
val currentState = _uiState.value
|
||||
if (
|
||||
|
|
@ -89,7 +95,11 @@ object StreamsRepository {
|
|||
}
|
||||
|
||||
val installedAddons = AddonRepository.uiState.value.addons
|
||||
val pluginScrapers = PluginRepository.getEnabledScrapersForType(type)
|
||||
val pluginScrapers = if (AppFeaturePolicy.pluginsEnabled) {
|
||||
PluginRepository.getEnabledScrapersForType(type)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
val pluginProviderGroups = pluginScrapers.toPluginProviderGroups(
|
||||
repositories = pluginUiState.repositories,
|
||||
groupByRepository = pluginUiState.groupStreamsByRepository,
|
||||
|
|
|
|||
Loading…
Reference in a new issue