mirror of
https://github.com/jellyfin/jellyfin-androidtv.git
synced 2026-10-26 16:45:23 +00:00
Add user-defined AVC/HEVC codec level override (#5520)
This commit is contained in:
1 parent
13f1c036ff
commit
d21b4e9a35
10 files changed
+282
-4
No files matched your search
@@ -3,10 +3,12 @@ package org.jellyfin.androidtv.preference
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.jellyfin.androidtv.preference.UserPreferences.Companion.screensaverInAppEnabled
|
||||
import org.jellyfin.androidtv.preference.constant.AVCLevel
|
||||
import org.jellyfin.androidtv.preference.constant.AppTheme
|
||||
import org.jellyfin.androidtv.preference.constant.AudioBehavior
|
||||
import org.jellyfin.androidtv.preference.constant.BackdropBehavior
|
||||
import org.jellyfin.androidtv.preference.constant.ClockBehavior
|
||||
import org.jellyfin.androidtv.preference.constant.HEVCLevel
|
||||
import org.jellyfin.androidtv.preference.constant.NextUpBehavior
|
||||
import org.jellyfin.androidtv.preference.constant.RefreshRateSwitchingBehavior
|
||||
import org.jellyfin.androidtv.preference.constant.StillWatchingBehavior
|
||||
@@ -103,6 +105,16 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
|
||||
*/
|
||||
var preferExoPlayerFfmpeg = booleanPreference("exoplayer_prefer_ffmpeg", defaultValue = false)
|
||||
|
||||
/**
|
||||
* User defined AVC level override. AUTO uses device-reported capabilities.
|
||||
*/
|
||||
var userAVCLevel = enumPreference("user_avc_level", AVCLevel.AUTO)
|
||||
|
||||
/**
|
||||
* User defined HEVC level override. AUTO uses device-reported capabilities.
|
||||
*/
|
||||
var userHEVCLevel = enumPreference("user_hevc_level", HEVCLevel.AUTO)
|
||||
|
||||
/* Playback - Audio related */
|
||||
/**
|
||||
* Preferred behavior for audio streaming.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.jellyfin.androidtv.preference.constant
|
||||
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.preference.PreferenceEnum
|
||||
|
||||
/**
|
||||
* User-defined AVC (H.264) codec level override.
|
||||
* Values correspond to MediaCodecInfo.CodecProfileLevel constants.
|
||||
*/
|
||||
enum class AVCLevel(
|
||||
override val nameRes: Int,
|
||||
val level: Int?,
|
||||
) : PreferenceEnum {
|
||||
AUTO(R.string.auto, null),
|
||||
|
||||
LEVEL_6_2(R.string.codec_level_6_2, 62),
|
||||
LEVEL_6_1(R.string.codec_level_6_1, 61),
|
||||
LEVEL_6_0(R.string.codec_level_6_0, 60),
|
||||
LEVEL_5_2(R.string.codec_level_5_2, 52),
|
||||
LEVEL_5_1(R.string.codec_level_5_1, 51),
|
||||
LEVEL_5_0(R.string.codec_level_5_0, 50),
|
||||
LEVEL_4_2(R.string.codec_level_4_2, 42),
|
||||
LEVEL_4_1(R.string.codec_level_4_1, 41),
|
||||
LEVEL_4_0(R.string.codec_level_4_0, 40),
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.jellyfin.androidtv.preference.constant
|
||||
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.preference.PreferenceEnum
|
||||
|
||||
/**
|
||||
* User-defined HEVC (H.265) codec level override.
|
||||
* Values correspond to MediaCodecInfo.CodecProfileLevel constants.
|
||||
*/
|
||||
enum class HEVCLevel(
|
||||
override val nameRes: Int,
|
||||
val level: Int?,
|
||||
) : PreferenceEnum {
|
||||
AUTO(R.string.auto, null),
|
||||
|
||||
LEVEL_6_2(R.string.codec_level_6_2, 186),
|
||||
LEVEL_6_1(R.string.codec_level_6_1, 183),
|
||||
LEVEL_6_0(R.string.codec_level_6_0, 180),
|
||||
LEVEL_5_2(R.string.codec_level_5_2, 156),
|
||||
LEVEL_5_1(R.string.codec_level_5_1, 153),
|
||||
LEVEL_5_0(R.string.codec_level_5_0, 150),
|
||||
LEVEL_4_1(R.string.codec_level_4_1, 123),
|
||||
LEVEL_4_0(R.string.codec_level_4_0, 120),
|
||||
}
|
||||
@@ -31,8 +31,11 @@ import org.jellyfin.androidtv.ui.settings.screen.license.SettingsLicensesScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.livetv.SettingsLiveTvGuideChannelOrderScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.livetv.SettingsLiveTvGuideFiltersScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.livetv.SettingsLiveTvGuideOptionsScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackAVCLevelScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackAdvancedScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackAudioBehaviorScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackCodecScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackHEVCLevelScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackInactivityPromptScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackMaxBitrateScreen
|
||||
import org.jellyfin.androidtv.ui.settings.screen.playback.SettingsPlaybackPlayerScreen
|
||||
@@ -95,6 +98,9 @@ object Routes {
|
||||
const val PLAYBACK_REFRESH_RATE_SWITCHING_BEHAVIOR = "/playback/refresh-rate-switching-behavior"
|
||||
const val PLAYBACK_ZOOM_MODE = "/playback/zoom-mode"
|
||||
const val PLAYBACK_AUDIO_BEHAVIOR = "/playback/audio-behavior"
|
||||
const val PLAYBACK_CODEC = "/playback/codec"
|
||||
const val PLAYBACK_AVC_LEVEL = "/playback/codec/avc-level"
|
||||
const val PLAYBACK_HEVC_LEVEL = "/playback/codec/hevc-level"
|
||||
const val TELEMETRY = "/telemetry"
|
||||
const val DEVELOPER = "/developer"
|
||||
const val ABOUT = "/about"
|
||||
@@ -245,6 +251,15 @@ val routes = mapOf<String, RouteComposable>(
|
||||
Routes.PLAYBACK_AUDIO_BEHAVIOR to {
|
||||
SettingsPlaybackAudioBehaviorScreen()
|
||||
},
|
||||
Routes.PLAYBACK_CODEC to {
|
||||
SettingsPlaybackCodecScreen()
|
||||
},
|
||||
Routes.PLAYBACK_AVC_LEVEL to {
|
||||
SettingsPlaybackAVCLevelScreen()
|
||||
},
|
||||
Routes.PLAYBACK_HEVC_LEVEL to {
|
||||
SettingsPlaybackHEVCLevelScreen()
|
||||
},
|
||||
Routes.TELEMETRY to {
|
||||
SettingsTelemetryScreen()
|
||||
},
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.jellyfin.androidtv.ui.settings.screen.playback
|
||||
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.androidtv.preference.UserPreferences
|
||||
import org.jellyfin.androidtv.preference.constant.AVCLevel
|
||||
import org.jellyfin.androidtv.ui.base.Text
|
||||
import org.jellyfin.androidtv.ui.base.form.RadioButton
|
||||
import org.jellyfin.androidtv.ui.base.list.ListButton
|
||||
import org.jellyfin.androidtv.ui.base.list.ListSection
|
||||
import org.jellyfin.androidtv.ui.navigation.LocalRouter
|
||||
import org.jellyfin.androidtv.ui.settings.compat.rememberPreference
|
||||
import org.jellyfin.androidtv.ui.settings.composable.SettingsColumn
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun SettingsPlaybackAVCLevelScreen() {
|
||||
val router = LocalRouter.current
|
||||
val userPreferences = koinInject<UserPreferences>()
|
||||
|
||||
var userAVCLevel by rememberPreference(userPreferences, UserPreferences.userAVCLevel)
|
||||
val manualOptions = AVCLevel.entries.filter { it != AVCLevel.AUTO }
|
||||
|
||||
SettingsColumn {
|
||||
item {
|
||||
ListSection(
|
||||
overlineContent = { Text(stringResource(R.string.preference_codecs).uppercase()) },
|
||||
headingContent = { Text(stringResource(R.string.user_avc_level)) },
|
||||
captionContent = { Text(stringResource(R.string.codec_level_warning)) },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(AVCLevel.AUTO.nameRes)) },
|
||||
trailingContent = { RadioButton(checked = userAVCLevel == AVCLevel.AUTO) },
|
||||
onClick = {
|
||||
userAVCLevel = AVCLevel.AUTO
|
||||
router.back()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item { ListSection(headingContent = { Text(stringResource(R.string.codec_level_manual)) }) }
|
||||
|
||||
items(manualOptions) { level ->
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(level.nameRes)) },
|
||||
trailingContent = { RadioButton(checked = userAVCLevel == level) },
|
||||
onClick = {
|
||||
userAVCLevel = level
|
||||
router.back()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -187,6 +187,14 @@ fun SettingsPlaybackAdvancedScreen() {
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(R.string.preference_codecs)) },
|
||||
captionContent = { Text(stringResource(R.string.preference_codecs_summary)) },
|
||||
onClick = { router.push(Routes.PLAYBACK_CODEC) }
|
||||
)
|
||||
}
|
||||
|
||||
item { ListSection(headingContent = { Text(stringResource(R.string.pref_live_tv_cat)) }) }
|
||||
|
||||
item {
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package org.jellyfin.androidtv.ui.settings.screen.playback
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.androidtv.preference.UserPreferences
|
||||
import org.jellyfin.androidtv.ui.base.Text
|
||||
import org.jellyfin.androidtv.ui.base.list.ListButton
|
||||
import org.jellyfin.androidtv.ui.base.list.ListSection
|
||||
import org.jellyfin.androidtv.ui.navigation.LocalRouter
|
||||
import org.jellyfin.androidtv.ui.settings.Routes
|
||||
import org.jellyfin.androidtv.ui.settings.compat.rememberPreference
|
||||
import org.jellyfin.androidtv.ui.settings.composable.SettingsColumn
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun SettingsPlaybackCodecScreen() {
|
||||
val router = LocalRouter.current
|
||||
val userPreferences = koinInject<UserPreferences>()
|
||||
|
||||
SettingsColumn {
|
||||
item {
|
||||
ListSection(
|
||||
overlineContent = { Text(stringResource(R.string.pref_playback_advanced).uppercase()) },
|
||||
headingContent = { Text(stringResource(R.string.preference_codecs)) },
|
||||
captionContent = { Text(stringResource(R.string.preference_codecs_summary)) },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
var userAVCLevel by rememberPreference(userPreferences, UserPreferences.userAVCLevel)
|
||||
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(R.string.user_avc_level)) },
|
||||
captionContent = { Text(stringResource(userAVCLevel.nameRes)) },
|
||||
onClick = { router.push(Routes.PLAYBACK_AVC_LEVEL) }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
var userHEVCLevel by rememberPreference(userPreferences, UserPreferences.userHEVCLevel)
|
||||
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(R.string.user_hevc_level)) },
|
||||
captionContent = { Text(stringResource(userHEVCLevel.nameRes)) },
|
||||
onClick = { router.push(Routes.PLAYBACK_HEVC_LEVEL) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.jellyfin.androidtv.ui.settings.screen.playback
|
||||
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.androidtv.preference.UserPreferences
|
||||
import org.jellyfin.androidtv.preference.constant.HEVCLevel
|
||||
import org.jellyfin.androidtv.ui.base.Text
|
||||
import org.jellyfin.androidtv.ui.base.form.RadioButton
|
||||
import org.jellyfin.androidtv.ui.base.list.ListButton
|
||||
import org.jellyfin.androidtv.ui.base.list.ListSection
|
||||
import org.jellyfin.androidtv.ui.navigation.LocalRouter
|
||||
import org.jellyfin.androidtv.ui.settings.compat.rememberPreference
|
||||
import org.jellyfin.androidtv.ui.settings.composable.SettingsColumn
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun SettingsPlaybackHEVCLevelScreen() {
|
||||
val router = LocalRouter.current
|
||||
val userPreferences = koinInject<UserPreferences>()
|
||||
|
||||
var userHEVCLevel by rememberPreference(userPreferences, UserPreferences.userHEVCLevel)
|
||||
val manualOptions = HEVCLevel.entries.filter { it != HEVCLevel.AUTO }
|
||||
|
||||
SettingsColumn {
|
||||
item {
|
||||
ListSection(
|
||||
overlineContent = { Text(stringResource(R.string.preference_codecs).uppercase()) },
|
||||
headingContent = { Text(stringResource(R.string.user_hevc_level)) },
|
||||
captionContent = { Text(stringResource(R.string.codec_level_warning)) },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(HEVCLevel.AUTO.nameRes)) },
|
||||
trailingContent = { RadioButton(checked = userHEVCLevel == HEVCLevel.AUTO) },
|
||||
onClick = {
|
||||
userHEVCLevel = HEVCLevel.AUTO
|
||||
router.back()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item { ListSection(headingContent = { Text(stringResource(R.string.codec_level_manual)) }) }
|
||||
|
||||
items(manualOptions) { level ->
|
||||
ListButton(
|
||||
headingContent = { Text(stringResource(level.nameRes)) },
|
||||
trailingContent = { RadioButton(checked = userHEVCLevel == level) },
|
||||
onClick = {
|
||||
userHEVCLevel = level
|
||||
router.back()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,8 @@ fun createDeviceProfile(
|
||||
downMixAudio = userPreferences[UserPreferences.audioBehaviour] == AudioBehavior.DOWNMIX_TO_STEREO,
|
||||
assDirectPlay = userPreferences[UserPreferences.assDirectPlay],
|
||||
pgsDirectPlay = userPreferences[UserPreferences.pgsDirectPlay],
|
||||
userAVCLevel = userPreferences[UserPreferences.userAVCLevel].level,
|
||||
userHEVCLevel = userPreferences[UserPreferences.userHEVCLevel].level,
|
||||
)
|
||||
|
||||
fun createDeviceProfile(
|
||||
@@ -94,6 +96,8 @@ fun createDeviceProfile(
|
||||
downMixAudio: Boolean,
|
||||
assDirectPlay: Boolean,
|
||||
pgsDirectPlay: Boolean,
|
||||
userAVCLevel: Int?,
|
||||
userHEVCLevel: Int?,
|
||||
) = buildDeviceProfile {
|
||||
val allowedAudioCodecs = when {
|
||||
downMixAudio -> downmixSupportedAudioCodecs
|
||||
@@ -103,12 +107,12 @@ fun createDeviceProfile(
|
||||
|
||||
val supportsHevc = mediaTest.supportsHevc()
|
||||
val supportsHevcMain10 = mediaTest.supportsHevcMain10()
|
||||
val hevcMainLevel = mediaTest.getHevcMainLevel()
|
||||
val hevcMain10Level = mediaTest.getHevcMain10Level()
|
||||
val hevcMainLevel = userHEVCLevel ?: mediaTest.getHevcMainLevel()
|
||||
val hevcMain10Level = userHEVCLevel ?: mediaTest.getHevcMain10Level()
|
||||
val supportsAVC = mediaTest.supportsAVC()
|
||||
val supportsAVCHigh10 = mediaTest.supportsAVCHigh10()
|
||||
val avcMainLevel = mediaTest.getAVCMainLevel()
|
||||
val avcHigh10Level = mediaTest.getAVCHigh10Level()
|
||||
val avcMainLevel = userAVCLevel ?: mediaTest.getAVCMainLevel()
|
||||
val avcHigh10Level = userAVCLevel ?: mediaTest.getAVCHigh10Level()
|
||||
val supportsAV1 = mediaTest.supportsAV1()
|
||||
val supportsAV1Main10 = mediaTest.supportsAV1Main10()
|
||||
val supportsVC1 = mediaTest.supportsVc1()
|
||||
|
||||
@@ -579,6 +579,22 @@
|
||||
<string name="filters">Filters</string>
|
||||
<string name="backdrop_behavior_with_blur">Backdrop with blur</string>
|
||||
<string name="backdrop_behavior_without_blur">Backdrop without blur</string>
|
||||
<string name="user_avc_level">H.264 (AVC) level</string>
|
||||
<string name="user_hevc_level">H.265 (HEVC) level</string>
|
||||
<string name="codec_level_warning">Changing from auto may cause playback issues if your device doesn\'t actually support the selected level.</string>
|
||||
<string name="preference_codecs">Media codecs</string>
|
||||
<string name="preference_codecs_summary">Override device-reported codec capabilities</string>
|
||||
<string name="codec_level_manual">Manual override</string>
|
||||
<string name="codec_level_6_2">Level 6.2</string>
|
||||
<string name="codec_level_6_1">Level 6.1</string>
|
||||
<string name="codec_level_6_0">Level 6.0</string>
|
||||
<string name="codec_level_5_2">Level 5.2</string>
|
||||
<string name="codec_level_5_1">Level 5.1</string>
|
||||
<string name="codec_level_5_0">Level 5.0</string>
|
||||
<string name="codec_level_4_2">Level 4.2</string>
|
||||
<string name="codec_level_4_1">Level 4.1</string>
|
||||
<string name="codec_level_4_0">Level 4.0</string>
|
||||
<string name="auto">Auto</string>
|
||||
<plurals name="seconds">
|
||||
<item quantity="one">%1$s second</item>
|
||||
<item quantity="other">%1$s seconds</item>
|
||||
|
||||
Reference in new issue
Block a user