diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt index 7e39d2e662..043a67a886 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt @@ -102,6 +102,16 @@ class UserPreferences(context: Context) : SharedPreferenceStore( */ var preferExoPlayerFfmpeg = booleanPreference("exoplayer_prefer_ffmpeg", defaultValue = false) + /** + * User defined AVC levels for main and high 10. + */ + var userAVCLevel = stringPreference("user_avc_level", "auto") + + /** + * User defined HEVC levels for main and main 10. + */ + var userHEVCLevel = stringPreference("user_hevc_level", "auto") + /* Playback - Audio related */ /** * Preferred behavior for audio streaming. diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java index 622d75da05..1a7c44ad68 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java @@ -514,7 +514,9 @@ private VideoOptions buildExoPlayerOptions(@Nullable Integer forcedSubtitleIndex DeviceProfile internalProfile = new ExoPlayerProfile( !internalOptions.getEnableDirectStream(), userPreferences.getValue().get(UserPreferences.Companion.getAc3Enabled()), - userPreferences.getValue().get(UserPreferences.Companion.getAudioBehaviour()) == AudioBehavior.DOWNMIX_TO_STEREO + userPreferences.getValue().get(UserPreferences.Companion.getAudioBehaviour()) == AudioBehavior.DOWNMIX_TO_STEREO, + userPreferences.getValue().get(UserPreferences.Companion.getUserAVCLevel()), + userPreferences.getValue().get(UserPreferences.Companion.getUserHEVCLevel()) ); internalOptions.setProfile(internalProfile); return internalOptions; diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedCodecPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedCodecPreferencesScreen.kt new file mode 100644 index 0000000000..0e6a418109 --- /dev/null +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedCodecPreferencesScreen.kt @@ -0,0 +1,66 @@ +package org.jellyfin.androidtv.ui.preference.screen + +import org.jellyfin.androidtv.R +import org.jellyfin.androidtv.preference.UserPreferences +import org.jellyfin.androidtv.ui.preference.dsl.OptionsFragment +import org.jellyfin.androidtv.ui.preference.dsl.checkbox +import org.jellyfin.androidtv.ui.preference.dsl.list +import org.jellyfin.androidtv.ui.preference.dsl.optionsScreen +import org.koin.android.ext.android.inject + +class PlaybackAdvancedCodecPreferencesScreen : OptionsFragment() { + private val userPreferences: UserPreferences by inject() + + override val screen by optionsScreen { + setTitle(R.string.codec) + + category { + setTitle(R.string.level_warning) + + list { + setTitle(R.string.user_avc_level) + entries = mapOf( + // AVC levels as reported by ffprobe are multiplied by 10, e.g. level 4.1 is 41. + "auto" to "Auto", + "62" to "Level 6.2", + "61" to "Level 6.1", + "60" to "Level 6.0", + "52" to "Level 5.2", + "51" to "Level 5.1", + "50" to "Level 5.0", + "42" to "Level 4.2", + "41" to "Level 4.1", + "40" to "Level 4.0" + ) + bind(userPreferences, UserPreferences.userAVCLevel) + } + + list { + setTitle(R.string.user_hevc_level) + entries = mapOf( + // HEVC levels as reported by ffprobe are multiplied by 30, e.g. level 4.1 is 123. + "auto" to "Auto", + "186" to "Level 6.2", + "183" to "Level 6.1", + "180" to "Level 6.0", + "156" to "Level 5.2", + "153" to "Level 5.1", + "150" to "Level 5.0", + "123" to "Level 4.1", + "120" to "Level 4.0" + ) + bind(userPreferences, UserPreferences.userHEVCLevel) + } + } + + category { + setTitle(R.string.pref_audio) + + checkbox { + setTitle(R.string.lbl_bitstream_ac3) + setContent(R.string.desc_bitstream_ac3) + bind(userPreferences, UserPreferences.ac3Enabled) + } + } + } +} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt index 9eaddc3baa..5ca2ba4510 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt @@ -109,12 +109,6 @@ class PlaybackAdvancedPreferencesScreen : OptionsFragment() { bind(userPreferences, UserPreferences.audioNightMode) depends { Build.VERSION.SDK_INT >= Build.VERSION_CODES.P } } - - checkbox { - setTitle(R.string.lbl_bitstream_ac3) - setContent(R.string.desc_bitstream_ac3) - bind(userPreferences, UserPreferences.ac3Enabled) - } } } } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackPreferencesScreen.kt index b57c30538f..f1bb8e1ee7 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackPreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackPreferencesScreen.kt @@ -194,6 +194,12 @@ class PlaybackPreferencesScreen : OptionsFragment() { icon = R.drawable.ic_more withFragment() } + + link { + setTitle(R.string.codec_advanced) + icon = R.drawable.ic_more + withFragment() + } } } } diff --git a/app/src/main/java/org/jellyfin/androidtv/util/profile/ExoPlayerProfile.kt b/app/src/main/java/org/jellyfin/androidtv/util/profile/ExoPlayerProfile.kt index 5c7bed755e..46e2dcfa96 100644 --- a/app/src/main/java/org/jellyfin/androidtv/util/profile/ExoPlayerProfile.kt +++ b/app/src/main/java/org/jellyfin/androidtv/util/profile/ExoPlayerProfile.kt @@ -26,7 +26,9 @@ import org.jellyfin.apiclient.model.dlna.TranscodingProfile class ExoPlayerProfile( disableVideoDirectPlay: Boolean, isAC3Enabled: Boolean, - downMixAudio: Boolean + downMixAudio: Boolean, + userAVCLevel: String? = "auto", + userHEVCLevel: String? = "auto" ) : DeviceProfile() { private val downmixSupportedAudioCodecs = arrayOf( Codec.Audio.AAC, @@ -153,8 +155,22 @@ class ExoPlayerProfile( codecProfiles = buildList { // H264 profile - add(deviceAVCCodecProfile) - addAll(deviceAVCLevelCodecProfiles) + if (userAVCLevel == "auto") { + add(deviceAVCCodecProfile) + addAll(deviceAVCLevelCodecProfiles) + } else { + add(CodecProfile().apply { + type = CodecType.Video + codec = Codec.Video.H264 + conditions = arrayOf( + ProfileCondition( + ProfileConditionType.LessThanEqual, + ProfileConditionValue.VideoLevel, + userAVCLevel + ) + ) + }) + } // H264 ref frames profile add(CodecProfile().apply { type = CodecType.Video @@ -194,8 +210,23 @@ class ExoPlayerProfile( ) }) // HEVC profiles - add(deviceHevcCodecProfile) - addAll(deviceHevcLevelCodecProfiles) + if (userHEVCLevel == "auto") { + add(deviceHevcCodecProfile) + addAll(deviceHevcLevelCodecProfiles) + } else { + add(CodecProfile().apply { + type = CodecType.Video + codec = Codec.Video.HEVC + conditions = arrayOf( + ProfileCondition( + ProfileConditionType.LessThanEqual, + ProfileConditionValue.VideoLevel, + userHEVCLevel + ) + ) + }) + } + // AV1 profile add(deviceAV1CodecProfile) // Limit video resolution support for older devices diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 84ab1f7be7..8046a2a563 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -526,6 +526,11 @@ Unknown segments Skip forward length Enable trickplay in video player + Advanced codec preferences + Codec + H.264 Level + H.265 Level + Video\n\nSwitching from \'Auto\' could lead to playback issues %1$s second %1$s seconds