From bba83b13e41eae5b4136f98aa626b598225637de Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:39:40 +0530 Subject: [PATCH] feat: libass --- composeApp/build.gradle.kts | 1 + .../features/player/PlayerEngine.android.kt | 49 ++++-- .../app/features/player/PlayerLibassCompat.kt | 98 ++++++++++++ .../features/player/PlayerLibassExtensions.kt | 21 +++ .../player/PlayerSettingsStorage.android.kt | 29 ++++ .../player/PlayerSettingsRepository.kt | 26 ++++ .../features/player/PlayerSettingsStorage.kt | 4 + .../features/settings/PlaybackSettingsPage.kt | 144 ++++++++++++++++++ .../app/features/settings/SettingsScreen.kt | 12 ++ .../player/PlayerSettingsStorage.ios.kt | 8 + 10 files changed, 379 insertions(+), 13 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt create mode 100644 composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassExtensions.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 21c7c8c3..c3a0dc86 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -210,6 +210,7 @@ kotlin { implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.11.0") + implementation("io.github.peerless2012:ass-media:0.4.0-beta01") implementation(libs.ktor.client.android) implementation(libs.androidx.media3.exoplayer.hls) implementation(libs.androidx.media3.exoplayer.dash) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt index ba5fea4d..09d8901e 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerEngine.android.kt @@ -120,24 +120,47 @@ actual fun PlatformPlayerSurface( .setTsExtractorFlags(DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS) .setTsExtractorTimestampSearchBytes(1500 * TsExtractor.TS_PACKET_SIZE) - val mediaSourceFactory = DefaultMediaSourceFactory( - PlatformPlaybackDataSourceFactory.create( + val dataSourceFactory = PlatformPlaybackDataSourceFactory.create( defaultRequestHeaders = sanitizedSourceHeaders, defaultResponseHeaders = sanitizedSourceResponseHeaders, useYoutubeChunkedPlayback = useYoutubeChunkedPlayback, - ), - extractorsFactory, - ) + ) - ExoPlayer.Builder(context) - .setRenderersFactory(renderersFactory) - .setTrackSelector(trackSelector) - .setLoadControl(loadControl) - .setMediaSourceFactory(mediaSourceFactory) - .build().apply { + val useLibass = playerSettings.useLibass + val libassRenderType = runCatching { + LibassRenderType.valueOf(playerSettings.libassRenderType) + }.getOrDefault(LibassRenderType.CUES) + + val player = if (useLibass) { + ExoPlayer.Builder(context) + .setTrackSelector(trackSelector) + .setLoadControl(loadControl) + .buildWithAssSupportCompat( + context = context, + renderType = libassRenderType.toAssRenderType(), + dataSourceFactory = dataSourceFactory, + extractorsFactory = extractorsFactory, + renderersFactory = renderersFactory + ) + } else { + val mediaSourceFactory = DefaultMediaSourceFactory( + dataSourceFactory, + extractorsFactory, + ) + + ExoPlayer.Builder(context) + .setRenderersFactory(renderersFactory) + .setTrackSelector(trackSelector) + .setLoadControl(loadControl) + .setMediaSourceFactory(mediaSourceFactory) + .build() + } + + player.apply { if (!sourceAudioUrl.isNullOrBlank()) { - val videoSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(sourceUrl)) - val audioSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(sourceAudioUrl)) + val msf = DefaultMediaSourceFactory(dataSourceFactory, extractorsFactory) + val videoSource = msf.createMediaSource(MediaItem.fromUri(sourceUrl)) + val audioSource = msf.createMediaSource(MediaItem.fromUri(sourceAudioUrl)) setMediaSource(MergingMediaSource(videoSource, audioSource)) } else { setMediaItem(MediaItem.fromUri(sourceUrl)) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt new file mode 100644 index 00000000..798eb450 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassCompat.kt @@ -0,0 +1,98 @@ +package com.nuvio.app.features.player + +import android.content.Context +import androidx.annotation.OptIn +import androidx.media3.common.Format +import androidx.media3.common.MimeTypes +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.exoplayer.DefaultRenderersFactory +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.RenderersFactory +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.extractor.DefaultExtractorsFactory +import androidx.media3.extractor.ExtractorsFactory +import androidx.media3.extractor.mkv.MatroskaExtractor +import androidx.media3.extractor.text.SubtitleParser +import io.github.peerless2012.ass.media.AssHandler +import io.github.peerless2012.ass.media.extractor.AssMatroskaExtractor +import io.github.peerless2012.ass.media.kt.withAssSupport +import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory +import io.github.peerless2012.ass.media.type.AssRenderType + +@OptIn(UnstableApi::class) +internal fun ExoPlayer.Builder.buildWithAssSupportCompat( + context: Context, + renderType: AssRenderType = AssRenderType.CUES, + dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(context), + extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory(), + renderersFactory: RenderersFactory = DefaultRenderersFactory(context) +): ExoPlayer { + val assHandler = AssHandler(renderType) + val assSubtitleParserFactory = CompatAssSubtitleParserFactory(assHandler) + val assExtractorsFactory = extractorsFactory.withAssMkvSupportCompat( + subtitleParserFactory = assSubtitleParserFactory, + assHandler = assHandler + ) + + val mediaSourceFactory = DefaultMediaSourceFactory( + dataSourceFactory, + assExtractorsFactory + ) + mediaSourceFactory.setSubtitleParserFactory(assSubtitleParserFactory) + + val player = this + .setMediaSourceFactory(mediaSourceFactory) + .setRenderersFactory(renderersFactory.withAssSupport(assHandler)) + .build() + + assHandler.init(player) + return player +} + +@OptIn(UnstableApi::class) +private class CompatAssSubtitleParserFactory( + private val assHandler: AssHandler +) : SubtitleParser.Factory { + private val delegate = AssSubtitleParserFactory(assHandler) + + override fun supportsFormat(format: Format): Boolean { + return delegate.supportsFormat(normalizeSsaFormat(format)) + } + + override fun getCueReplacementBehavior(format: Format): Int { + return delegate.getCueReplacementBehavior(normalizeSsaFormat(format)) + } + + override fun create(format: Format): SubtitleParser { + return delegate.create(normalizeSsaFormat(format)) + } + + private fun normalizeSsaFormat(format: Format): Format { + val isSsaByCodecs = format.codecs == MimeTypes.TEXT_SSA + val isSsaByMime = format.sampleMimeType == MimeTypes.TEXT_SSA + if (isSsaByCodecs && !isSsaByMime) { + return format.buildUpon() + .setSampleMimeType(MimeTypes.TEXT_SSA) + .build() + } + return format + } +} + +@OptIn(UnstableApi::class) +private fun ExtractorsFactory.withAssMkvSupportCompat( + subtitleParserFactory: SubtitleParser.Factory, + assHandler: AssHandler +): ExtractorsFactory { + return ExtractorsFactory { + val extractors = createExtractors() + extractors.forEachIndexed { index, extractor -> + if (extractor is MatroskaExtractor) { + extractors[index] = AssMatroskaExtractor(subtitleParserFactory, assHandler) + } + } + extractors + } +} diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassExtensions.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassExtensions.kt new file mode 100644 index 00000000..83a53a5c --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerLibassExtensions.kt @@ -0,0 +1,21 @@ +package com.nuvio.app.features.player + +import io.github.peerless2012.ass.media.type.AssRenderType + +enum class LibassRenderType { + CUES, + EFFECTS_CANVAS, + EFFECTS_OPEN_GL, + OVERLAY_CANVAS, + OVERLAY_OPEN_GL +} + +internal fun LibassRenderType.toAssRenderType(): AssRenderType { + return when (this) { + LibassRenderType.CUES -> AssRenderType.CUES + LibassRenderType.EFFECTS_CANVAS -> AssRenderType.EFFECTS_CANVAS + LibassRenderType.EFFECTS_OPEN_GL -> AssRenderType.EFFECTS_OPEN_GL + LibassRenderType.OVERLAY_CANVAS -> AssRenderType.OVERLAY_CANVAS + LibassRenderType.OVERLAY_OPEN_GL -> AssRenderType.OVERLAY_OPEN_GL + } +} diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt index 69ac7593..e06c5891 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt @@ -34,6 +34,8 @@ actual object PlayerSettingsStorage { private const val nextEpisodeThresholdModeKey = "next_episode_threshold_mode" private const val nextEpisodeThresholdPercentKey = "next_episode_threshold_percent_v2" private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2" + private const val useLibassKey = "use_libass" + private const val libassRenderTypeKey = "libass_render_type" private var preferences: SharedPreferences? = null @@ -460,4 +462,31 @@ actual object PlayerSettingsStorage { ?.putFloat(ProfileScopedKey.of(nextEpisodeThresholdMinutesBeforeEndKey), minutes) ?.apply() } + + actual fun loadUseLibass(): Boolean? = + preferences?.let { sharedPreferences -> + val key = ProfileScopedKey.of(useLibassKey) + if (sharedPreferences.contains(key)) { + sharedPreferences.getBoolean(key, false) + } else { + null + } + } + + actual fun saveUseLibass(enabled: Boolean) { + preferences + ?.edit() + ?.putBoolean(ProfileScopedKey.of(useLibassKey), enabled) + ?.apply() + } + + actual fun loadLibassRenderType(): String? = + preferences?.getString(ProfileScopedKey.of(libassRenderTypeKey), null) + + actual fun saveLibassRenderType(renderType: String) { + preferences + ?.edit() + ?.putString(ProfileScopedKey.of(libassRenderTypeKey), renderType) + ?.apply() + } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt index a0f30bbc..3d1f2054 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt @@ -34,6 +34,8 @@ data class PlayerSettingsUiState( val nextEpisodeThresholdMode: NextEpisodeThresholdMode = NextEpisodeThresholdMode.PERCENTAGE, val nextEpisodeThresholdPercent: Float = 99f, val nextEpisodeThresholdMinutesBeforeEnd: Float = 2f, + val useLibass: Boolean = false, + val libassRenderType: String = "CUES", ) object PlayerSettingsRepository { @@ -66,6 +68,8 @@ object PlayerSettingsRepository { private var nextEpisodeThresholdMode = NextEpisodeThresholdMode.PERCENTAGE private var nextEpisodeThresholdPercent = 99f private var nextEpisodeThresholdMinutesBeforeEnd = 2f + private var useLibass = false + private var libassRenderType = "CUES" fun ensureLoaded() { if (hasLoaded) return @@ -103,6 +107,8 @@ object PlayerSettingsRepository { nextEpisodeThresholdMode = NextEpisodeThresholdMode.PERCENTAGE nextEpisodeThresholdPercent = 99f nextEpisodeThresholdMinutesBeforeEnd = 2f + useLibass = false + libassRenderType = "CUES" publish() } @@ -165,6 +171,8 @@ object PlayerSettingsRepository { ?: NextEpisodeThresholdMode.PERCENTAGE nextEpisodeThresholdPercent = PlayerSettingsStorage.loadNextEpisodeThresholdPercent() ?: 99f nextEpisodeThresholdMinutesBeforeEnd = PlayerSettingsStorage.loadNextEpisodeThresholdMinutesBeforeEnd() ?: 2f + useLibass = PlayerSettingsStorage.loadUseLibass() ?: false + libassRenderType = PlayerSettingsStorage.loadLibassRenderType() ?: "CUES" publish() } @@ -377,6 +385,22 @@ object PlayerSettingsRepository { PlayerSettingsStorage.saveNextEpisodeThresholdMinutesBeforeEnd(minutes) } + fun setUseLibass(enabled: Boolean) { + ensureLoaded() + if (useLibass == enabled) return + useLibass = enabled + publish() + PlayerSettingsStorage.saveUseLibass(enabled) + } + + fun setLibassRenderType(renderType: String) { + ensureLoaded() + if (libassRenderType == renderType) return + libassRenderType = renderType + publish() + PlayerSettingsStorage.saveLibassRenderType(renderType) + } + private fun publish() { _uiState.value = PlayerSettingsUiState( showLoadingOverlay = showLoadingOverlay, @@ -404,6 +428,8 @@ object PlayerSettingsRepository { nextEpisodeThresholdMode = nextEpisodeThresholdMode, nextEpisodeThresholdPercent = nextEpisodeThresholdPercent, nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd, + useLibass = useLibass, + libassRenderType = libassRenderType, ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt index d0965bb3..7ca40fcd 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt @@ -57,4 +57,8 @@ internal expect object PlayerSettingsStorage { fun saveNextEpisodeThresholdPercent(percent: Float) fun loadNextEpisodeThresholdMinutesBeforeEnd(): Float? fun saveNextEpisodeThresholdMinutesBeforeEnd(minutes: Float) + fun loadUseLibass(): Boolean? + fun saveUseLibass(enabled: Boolean) + fun loadLibassRenderType(): String? + fun saveLibassRenderType(renderType: String) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt index 64589c41..8663d9a9 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt @@ -70,6 +70,8 @@ internal fun LazyListScope.playbackSettingsContent( decoderPriority: Int, mapDV7ToHevc: Boolean, tunnelingEnabled: Boolean, + useLibass: Boolean, + libassRenderType: String, ) { item { PlaybackSettingsSection( @@ -84,6 +86,8 @@ internal fun LazyListScope.playbackSettingsContent( decoderPriority = decoderPriority, mapDV7ToHevc = mapDV7ToHevc, tunnelingEnabled = tunnelingEnabled, + useLibass = useLibass, + libassRenderType = libassRenderType, ) } } @@ -101,6 +105,8 @@ private fun PlaybackSettingsSection( decoderPriority: Int, mapDV7ToHevc: Boolean, tunnelingEnabled: Boolean, + useLibass: Boolean, + libassRenderType: String, ) { var showPreferredAudioDialog by remember { mutableStateOf(false) } var showSecondaryAudioDialog by remember { mutableStateOf(false) } @@ -108,6 +114,7 @@ private fun PlaybackSettingsSection( var showSecondarySubtitleDialog by remember { mutableStateOf(false) } var showReuseCacheDurationDialog by remember { mutableStateOf(false) } var showDecoderPriorityDialog by remember { mutableStateOf(false) } + var showLibassRenderTypeDialog by remember { mutableStateOf(false) } var showAutoPlayModeDialog by remember { mutableStateOf(false) } var showAutoPlaySourceDialog by remember { mutableStateOf(false) } var showAutoPlayAddonSelectionDialog by remember { mutableStateOf(false) } @@ -376,6 +383,39 @@ private fun PlaybackSettingsSection( } } + if (!isIos) { + SettingsSection( + title = "SUBTITLE RENDERING", + isTablet = isTablet, + ) { + SettingsGroup(isTablet = isTablet) { + SettingsSwitchRow( + title = "Enable libass", + description = "Use libass for ASS/SSA subtitle rendering instead of the default renderer.", + checked = useLibass, + isTablet = isTablet, + onCheckedChange = PlayerSettingsRepository::setUseLibass, + ) + if (useLibass) { + SettingsGroupDivider(isTablet = isTablet) + SettingsNavigationRow( + title = "Render Type", + description = when (libassRenderType) { + "OVERLAY_OPEN_GL" -> "Overlay OpenGL" + "OVERLAY_CANVAS" -> "Overlay Canvas" + "EFFECTS_OPEN_GL" -> "Effects OpenGL" + "EFFECTS_CANVAS" -> "Effects Canvas" + "CUES" -> "Standard (Cues)" + else -> "Standard (Cues)" + }, + isTablet = isTablet, + onClick = { showLibassRenderTypeDialog = true }, + ) + } + } + } + } + SettingsSection( title = "SKIP SEGMENTS", isTablet = isTablet, @@ -673,6 +713,17 @@ private fun PlaybackSettingsSection( ) } + if (showLibassRenderTypeDialog) { + LibassRenderTypeDialog( + selectedRenderType = libassRenderType, + onRenderTypeSelected = { renderType -> + PlayerSettingsRepository.setLibassRenderType(renderType) + showLibassRenderTypeDialog = false + }, + onDismiss = { showLibassRenderTypeDialog = false }, + ) + } + if (showAutoPlayModeDialog) { StreamAutoPlayModeDialog( selectedMode = autoPlayPlayerSettings.streamAutoPlayMode, @@ -1029,6 +1080,99 @@ private fun DecoderPriorityDialog( } } +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun LibassRenderTypeDialog( + selectedRenderType: String, + onRenderTypeSelected: (String) -> Unit, + onDismiss: () -> Unit, +) { + val options = listOf( + "OVERLAY_OPEN_GL" to "Overlay OpenGL", + "OVERLAY_CANVAS" to "Overlay Canvas", + "EFFECTS_OPEN_GL" to "Effects OpenGL", + "EFFECTS_CANVAS" to "Effects Canvas", + "CUES" to "Standard (Cues)", + ) + + BasicAlertDialog( + onDismissRequest = onDismiss, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surface, + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = "Render Type", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.SemiBold, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + options.forEach { (value, label) -> + val isSelected = value == selectedRenderType + val containerColor = if (isSelected) { + MaterialTheme.colorScheme.primary.copy(alpha = 0.14f) + } else { + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f) + } + + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable { onRenderTypeSelected(value) }, + shape = RoundedCornerShape(12.dp), + color = containerColor, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f), + ) + Box( + modifier = Modifier.size(24.dp), + contentAlignment = Alignment.Center, + ) { + if (isSelected) { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } + } + } + } + } + } + + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "Tap outside to close", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } +} + @Composable @OptIn(ExperimentalMaterial3Api::class) private fun StreamAutoPlayModeDialog( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt index 3b5b6b33..2408f7d3 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt @@ -161,6 +161,8 @@ fun SettingsScreen( decoderPriority = playerSettingsUiState.decoderPriority, mapDV7ToHevc = playerSettingsUiState.mapDV7ToHevc, tunnelingEnabled = playerSettingsUiState.tunnelingEnabled, + useLibass = playerSettingsUiState.useLibass, + libassRenderType = playerSettingsUiState.libassRenderType, selectedTheme = selectedTheme, onThemeSelected = ThemeSettingsRepository::setTheme, amoledEnabled = amoledEnabled, @@ -191,6 +193,8 @@ fun SettingsScreen( decoderPriority = playerSettingsUiState.decoderPriority, mapDV7ToHevc = playerSettingsUiState.mapDV7ToHevc, tunnelingEnabled = playerSettingsUiState.tunnelingEnabled, + useLibass = playerSettingsUiState.useLibass, + libassRenderType = playerSettingsUiState.libassRenderType, selectedTheme = selectedTheme, onThemeSelected = ThemeSettingsRepository::setTheme, amoledEnabled = amoledEnabled, @@ -231,6 +235,8 @@ private fun MobileSettingsScreen( decoderPriority: Int, mapDV7ToHevc: Boolean, tunnelingEnabled: Boolean, + useLibass: Boolean, + libassRenderType: String, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -289,6 +295,8 @@ private fun MobileSettingsScreen( decoderPriority = decoderPriority, mapDV7ToHevc = mapDV7ToHevc, tunnelingEnabled = tunnelingEnabled, + useLibass = useLibass, + libassRenderType = libassRenderType, ) SettingsPage.Appearance -> appearanceSettingsContent( isTablet = false, @@ -365,6 +373,8 @@ private fun TabletSettingsScreen( decoderPriority: Int, mapDV7ToHevc: Boolean, tunnelingEnabled: Boolean, + useLibass: Boolean, + libassRenderType: String, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -486,6 +496,8 @@ private fun TabletSettingsScreen( decoderPriority = decoderPriority, mapDV7ToHevc = mapDV7ToHevc, tunnelingEnabled = tunnelingEnabled, + useLibass = useLibass, + libassRenderType = libassRenderType, ) SettingsPage.Appearance -> appearanceSettingsContent( isTablet = true, diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt index 33a9c354..85f75912 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt @@ -392,4 +392,12 @@ actual object PlayerSettingsStorage { actual fun saveNextEpisodeThresholdMinutesBeforeEnd(minutes: Float) { NSUserDefaults.standardUserDefaults.setFloat(minutes, forKey = ProfileScopedKey.of(nextEpisodeThresholdMinutesBeforeEndKey)) } + + actual fun loadUseLibass(): Boolean? = null + + actual fun saveUseLibass(enabled: Boolean) {} + + actual fun loadLibassRenderType(): String? = null + + actual fun saveLibassRenderType(renderType: String) {} }