diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 43d2d53a..4213fe94 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -389,6 +389,19 @@ sealed interface AppPreference { }, ) + val MaxResolutionPref = + AppChoicePreference( + title = R.string.max_resolution, + defaultValue = MaxResolution.NO_LIMIT, + getter = { MaxResolution.fromValue(it.playbackPreferences.overrides.maxResolution) }, + setter = { prefs, value -> + prefs.updatePlaybackOverrides { maxResolution = value.value } + }, + displayValues = R.array.max_resolution_options, + indexToValue = { MaxResolution.entries.getOrNull(it) ?: MaxResolution.NO_LIMIT }, + valueToIndex = { it.ordinal }, + ) + val Ac3Supported = AppSwitchPreference( title = R.string.ac3_supported, @@ -1242,6 +1255,7 @@ val advancedPreferences = AppPreference.GlobalContentScale, AppPreference.SkipSegments, AppPreference.DpadSeekModePref, + AppPreference.MaxResolutionPref, AppPreference.MaxBitrate, AppPreference.RefreshRateSwitching, AppPreference.ResolutionSwitching, diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/MaxResolution.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/MaxResolution.kt new file mode 100644 index 00000000..8b389927 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/MaxResolution.kt @@ -0,0 +1,19 @@ +package com.github.damontecres.wholphin.preferences + +enum class MaxResolution( + val value: Int, +) { + NO_LIMIT(0), + ULTRA_HD_8K(4320), + ULTRA_HD_4K(2160), + FULL_HD(1080), + HD(720), + PAL_SD(576), + NTSC_SD(480), + LOW_DEF(360), + ; + + companion object { + fun fromValue(value: Int): MaxResolution = entries.firstOrNull { it.value == value } ?: NO_LIMIT + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt index 29df802c..b9b15f97 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt @@ -65,6 +65,7 @@ class DeviceProfileService decodeAv1 = prefs.overrides.decodeAv1, preferAc3ForSurround = appPrefs.experimentalPreferences.enabled { preferAc3Surround }, jellyfinTenEleven = newConfig.jellyfinTenEleven, + maxResolution = newConfig.overrides.maxResolution, ) } this@DeviceProfileService.deviceProfile!! diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt index d2efeb30..5c5004ac 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt @@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.util.profile // Adapted from https://github.com/jellyfin/jellyfin-androidtv/blob/v0.19.4/app/src/main/java/org/jellyfin/androidtv/util/profile/deviceProfile.kt import android.media.MediaCodecInfo +import android.util.Size import androidx.media3.common.MimeTypes import com.github.damontecres.wholphin.util.profile.KnownDefects.supportsHi10P52 import org.jellyfin.sdk.model.api.CodecType @@ -61,6 +62,8 @@ val supportedAudioCodecs = // ) // } +private fun Size.min(min: Int) = if (min > 0 && height > min) Size(width, min) else this + fun createDeviceProfile( mediaTest: MediaCodecCapabilitiesTest, maxBitrate: Int, @@ -72,6 +75,7 @@ fun createDeviceProfile( decodeAv1: Boolean, jellyfinTenEleven: Boolean, preferAc3ForSurround: Boolean, + maxResolution: Int, ) = buildDeviceProfile { val allowedAudioCodecs = when { @@ -106,10 +110,10 @@ fun createDeviceProfile( val supportsAV1 = mediaTest.supportsAV1() val supportsAV1Main10 = mediaTest.supportsAV1Main10() val supportsVC1 = mediaTest.supportsVc1() - val maxResolutionAVC = mediaTest.getMaxResolution(MimeTypes.VIDEO_H264) - val maxResolutionHevc = mediaTest.getMaxResolution(MimeTypes.VIDEO_H265) - val maxResolutionAV1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_AV1) - val maxResolutionVC1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_VC1) + val maxResolutionAVC = mediaTest.getMaxResolution(MimeTypes.VIDEO_H264).min(maxResolution) + val maxResolutionHevc = mediaTest.getMaxResolution(MimeTypes.VIDEO_H265).min(maxResolution) + val maxResolutionAV1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_AV1).min(maxResolution) + val maxResolutionVC1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_VC1).min(maxResolution) // / HDR capabilities @@ -133,59 +137,43 @@ fun createDeviceProfile( // / Transcoding profiles // Video - if (preferAc3ForSurround) { - transcodingProfile { - type = DlnaProfileType.VIDEO - context = EncodingContext.STREAMING + transcodingProfile { + type = DlnaProfileType.VIDEO + context = EncodingContext.STREAMING - container = Codec.Container.TS - protocol = MediaStreamProtocol.HLS + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS - if (supportsHevc) videoCodec(Codec.Video.HEVC) - videoCodec(Codec.Video.H264) - - audioCodec(Codec.Audio.AC3) - - copyTimestamps = false - enableSubtitlesInManifest = true - } - } else { - transcodingProfile { - type = DlnaProfileType.VIDEO - context = EncodingContext.STREAMING - - container = Codec.Container.TS - protocol = MediaStreamProtocol.HLS - - if (supportsHevc) videoCodec(Codec.Video.HEVC) - videoCodec(Codec.Video.H264) + if (supportsHevc) videoCodec(Codec.Video.HEVC) + videoCodec(Codec.Video.H264) + if (preferAc3ForSurround) { audioCodec(*allowedAudioCodecs) + } else { + audioCodec(Codec.Audio.AC3) + } - copyTimestamps = false - enableSubtitlesInManifest = true + copyTimestamps = false + enableSubtitlesInManifest = true + + if (maxResolution > 0) { + conditions { + ProfileConditionValue.HEIGHT lowerThanOrEquals maxResolution + } } } // Audio - if (preferAc3ForSurround) { - transcodingProfile { - type = DlnaProfileType.AUDIO - context = EncodingContext.STREAMING + transcodingProfile { + type = DlnaProfileType.AUDIO + context = EncodingContext.STREAMING - container = Codec.Container.TS - protocol = MediaStreamProtocol.HLS + container = Codec.Container.TS + protocol = MediaStreamProtocol.HLS + if (preferAc3ForSurround) { audioCodec(Codec.Audio.AC3) - } - } else { - transcodingProfile { - type = DlnaProfileType.AUDIO - context = EncodingContext.STREAMING - - container = Codec.Container.TS - protocol = MediaStreamProtocol.HLS - + } else { audioCodec(Codec.Audio.AAC) } } @@ -459,6 +447,24 @@ fun createDeviceProfile( } } + if (maxResolution > 0) { + listOf( + Codec.Video.MPEG, + Codec.Video.MPEG2VIDEO, + Codec.Video.VP8, + Codec.Video.VP9, + ).forEach { codecName -> + codecProfile { + type = CodecType.VIDEO + codec = codecName + + conditions { + ProfileConditionValue.HEIGHT lowerThanOrEquals maxResolution + } + } + } + } + // / HDR exclude list // TODO Use VideoRangeType enum with Jellyfin 10.11 based SDK diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 3fba78f6..38f6099c 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -57,6 +57,7 @@ message PlaybackOverrides{ bool direct_play_dolby_vision_e_l = 6; bool decode_av1 = 7; AssPlaybackMode ass_playback_mode = 8; + int32 max_resolution = 9; } message PlaybackPreferences { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 423530a3..dd87f6da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -927,6 +927,25 @@ Remove from playlist Credentials expired, please login in again You are not authorized to access the server at this time + Max resolution before transcoding + + 8K + 4K + 1080 + 720 + 576 + 480 + 360 + + @string/no_limit + @string/resolution_8k + @string/resolution_4k + @string/resolution_1080 + @string/resolution_720 + @string/resolution_576 + @string/resolution_480 + @string/resolution_360 + %1$d Episodes %1$d Episode