New user preference to force enable or disable common HDR and Dolby Vision formats (#1644)

Description
Adding per-format HDR override preferences (HDR10, HDR10+, Dolby Vision
Profile 5/7/8) to control device compatibility information sent to the
Jellyfin server, so users can force specific formats on or off
independently instead of disabling Dolby Vision as a whole.

Related issues
This is a common problem across the Jellyfin ecosystem, but there isn't
a specific issue for Wholphin. My experience is the issue is FireTV
(model and generation) + TV dependent + media dependent. I tested videos
from
https://kodi.wiki/view/Samples with the same DOVIWithHDR10 flag that
worked while other movies I have with It is a very frustrating user
experience and the current workaround of setting the bitrate really low
to force a transcode is sub-optimal IMO as it lowers the video quality.
This allows the user to maintain full quality while disabling only the
specific format(s) causing problems.

https://github.com/jellyfin/jellyfin-androidtv/issues/4021
https://github.com/jellyfin/jellyfin-androidtv/issues/4892
https://github.com/nova-video-player/aos-AVP/issues/1503

https://www.reddit.com/r/jellyfin/comments/12jqoxh/dolby_vision_video_not_working_on_fire_stick_4k/

Testing

<!-- Describe how this change was tested and on what device(s) -->
FireTV 4K (gen 1) + Vizio
FireTV 4K Max (gen 1) + LG
FireTV 4K Max (gen 2) + Vizio
FireTV (gen 1) + Olevia


AI or LLM usage
Claude code assisted and wrote much of the code in this PR. I've
reviewed it all and run the pre-commit linting and formatting.

---------

Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
Jeff Whiting 2026-10-13 13:41:53 -06:00 • committed by GitHub
parent f01c5d367c
commit 9f557416ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 468 additions and 37 deletions

View file

@ -0,0 +1,85 @@
package com.github.damontecres.wholphin.test
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.HdrOverrideMode
import com.github.damontecres.wholphin.preferences.PlaybackOverrides
import com.github.damontecres.wholphin.preferences.getHdrRangeTypesFor
import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides
import com.github.damontecres.wholphin.util.profile.WholphinVideoRangeType
import org.junit.Assert
import org.junit.Test
class TestHdrFormat {
private fun appPreferences(block: PlaybackOverrides.Builder.() -> Unit): AppPreferences =
AppPreferences.getDefaultInstance().updatePlaybackOverrides(block)
@Test
fun `Test all auto returns empty sets`() {
val prefs = appPreferences { }
Assert.assertEquals(emptySet<String>(), prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_ENABLE, jellyfinTenEleven = true))
Assert.assertEquals(emptySet<String>(), prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_DISABLE, jellyfinTenEleven = true))
}
@Test
fun `Test HDR10 override`() {
val prefs = appPreferences { hdr10Override = HdrOverrideMode.HDR_OVERRIDE_ENABLE }
Assert.assertEquals(
setOf(WholphinVideoRangeType.HDR10.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_ENABLE, jellyfinTenEleven = true),
)
Assert.assertEquals(emptySet<String>(), prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_DISABLE, jellyfinTenEleven = true))
}
@Test
fun `Test Dolby Vision Profile 5 override`() {
val prefs = appPreferences { doviProfile5Override = HdrOverrideMode.HDR_OVERRIDE_DISABLE }
Assert.assertEquals(
setOf(WholphinVideoRangeType.DOVI.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_DISABLE, jellyfinTenEleven = true),
)
}
@Test
fun `Test Dolby Vision Profile 7 override requires Jellyfin 10_11`() {
val prefs = appPreferences { doviProfile7Override = HdrOverrideMode.HDR_OVERRIDE_ENABLE }
Assert.assertEquals(
emptySet<String>(),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_ENABLE, jellyfinTenEleven = false),
)
Assert.assertEquals(
setOf(WholphinVideoRangeType.DOVI_WITH_EL.serialName, WholphinVideoRangeType.DOVI_WITH_EL_HDR10_PLUS.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_ENABLE, jellyfinTenEleven = true),
)
}
@Test
fun `Test Dolby Vision Profile 8 override`() {
val prefs = appPreferences { doviProfile8Override = HdrOverrideMode.HDR_OVERRIDE_DISABLE }
Assert.assertEquals(
setOf(WholphinVideoRangeType.DOVI_WITH_HDR10.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_DISABLE, jellyfinTenEleven = false),
)
Assert.assertEquals(
setOf(WholphinVideoRangeType.DOVI_WITH_HDR10.serialName, WholphinVideoRangeType.DOVI_WITH_HDR10_PLUS.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_DISABLE, jellyfinTenEleven = true),
)
}
@Test
fun `Test multiple formats set to the same mode are unioned`() {
val prefs =
appPreferences {
hdr10Override = HdrOverrideMode.HDR_OVERRIDE_ENABLE
hdr10PlusOverride = HdrOverrideMode.HDR_OVERRIDE_ENABLE
doviProfile5Override = HdrOverrideMode.HDR_OVERRIDE_DISABLE
}
Assert.assertEquals(
setOf(WholphinVideoRangeType.HDR10.serialName, WholphinVideoRangeType.HDR10_PLUS.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_ENABLE, jellyfinTenEleven = true),
)
Assert.assertEquals(
setOf(WholphinVideoRangeType.DOVI.serialName),
prefs.getHdrRangeTypesFor(HdrOverrideMode.HDR_OVERRIDE_DISABLE, jellyfinTenEleven = true),
)
}
}

View file

@ -0,0 +1,141 @@
package com.github.damontecres.wholphin.util.profile
import io.mockk.every
import io.mockk.mockk
import org.jellyfin.sdk.model.api.CodecType
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.ProfileConditionValue
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/**
* Verifies that forceEnabledHdr/forceDisabledHdr correctly override the device's own
* codec-capability detection when building the unsupported video range type list.
*/
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE, sdk = [30])
class TestDeviceProfileUtils {
private fun unsupportedRangeTypes(
profile: DeviceProfile,
codec: String,
): Set<String> =
profile.codecProfiles
.filter { it.type == CodecType.VIDEO && it.codec == codec }
.firstNotNullOf { codecProfile ->
codecProfile.applyConditions.firstOrNull { it.property == ProfileConditionValue.VIDEO_RANGE_TYPE }?.value
}.split("|")
.toSet()
private fun buildProfile(
mediaTest: MediaCodecCapabilitiesTest,
jellyfinTenEleven: Boolean = false,
decodeAv1: Boolean = false,
forceEnabledHdr: Set<String> = emptySet(),
forceDisabledHdr: Set<String> = emptySet(),
): DeviceProfile =
createDeviceProfile(
mediaTest = mediaTest,
maxBitrate = 100_000_000,
isAC3Enabled = true,
downMixAudio = false,
assDirectPlay = true,
pgsDirectPlay = true,
decodeAv1 = decodeAv1,
jellyfinTenEleven = jellyfinTenEleven,
preferAc3ForSurround = false,
forceEnabledHdr = forceEnabledHdr,
forceDisabledHdr = forceDisabledHdr,
maxResolution = 0,
)
@Test
fun `Test HEVC Profile 7 unsupported when device does not support it`() {
val mediaTest = mockk<MediaCodecCapabilitiesTest>(relaxed = true)
every { mediaTest.supportsHevcDolbyVisionEL() } returns false
every { mediaTest.supportsHevcDolbyVision() } returns true
every { mediaTest.supportsHevcHDR10Plus() } returns true
val profile = buildProfile(mediaTest, jellyfinTenEleven = true)
Assert.assertTrue(unsupportedRangeTypes(profile, Codec.Video.HEVC).contains(WholphinVideoRangeType.DOVI_WITH_EL.serialName))
}
@Test
fun `Test force enable overrides HEVC Profile 7 device support check`() {
val mediaTest = mockk<MediaCodecCapabilitiesTest>(relaxed = true)
every { mediaTest.supportsHevcDolbyVisionEL() } returns false
every { mediaTest.supportsHevcDolbyVision() } returns true
every { mediaTest.supportsHevcHDR10Plus() } returns true
val profile =
buildProfile(
mediaTest,
jellyfinTenEleven = true,
forceEnabledHdr =
setOf(
WholphinVideoRangeType.DOVI_WITH_EL.serialName,
WholphinVideoRangeType.DOVI_WITH_EL_HDR10_PLUS.serialName,
),
)
Assert.assertFalse(unsupportedRangeTypes(profile, Codec.Video.HEVC).contains(WholphinVideoRangeType.DOVI_WITH_EL.serialName))
}
@Test
fun `Test force disable overrides HEVC Profile 7 device support check`() {
val mediaTest = mockk<MediaCodecCapabilitiesTest>(relaxed = true)
every { mediaTest.supportsHevcDolbyVisionEL() } returns true
every { mediaTest.supportsHevcDolbyVision() } returns true
every { mediaTest.supportsHevcHDR10Plus() } returns true
val profile =
buildProfile(
mediaTest,
jellyfinTenEleven = true,
forceDisabledHdr =
setOf(
WholphinVideoRangeType.DOVI_WITH_EL.serialName,
WholphinVideoRangeType.DOVI_WITH_EL_HDR10_PLUS.serialName,
),
)
Assert.assertTrue(unsupportedRangeTypes(profile, Codec.Video.HEVC).contains(WholphinVideoRangeType.DOVI_WITH_EL.serialName))
}
@Test
fun `Test AV1 Profile 5 unsupported when device does not support it`() {
val mediaTest = mockk<MediaCodecCapabilitiesTest>(relaxed = true)
every { mediaTest.supportsAV1DolbyVision() } returns false
val profile = buildProfile(mediaTest, decodeAv1 = false)
Assert.assertTrue(unsupportedRangeTypes(profile, Codec.Video.AV1).contains(WholphinVideoRangeType.DOVI.serialName))
}
@Test
fun `Test force enable overrides AV1 Profile 5 device support check`() {
val mediaTest = mockk<MediaCodecCapabilitiesTest>(relaxed = true)
every { mediaTest.supportsAV1DolbyVision() } returns false
val profile =
buildProfile(
mediaTest,
decodeAv1 = false,
forceEnabledHdr = setOf(WholphinVideoRangeType.DOVI.serialName),
)
Assert.assertFalse(unsupportedRangeTypes(profile, Codec.Video.AV1).contains(WholphinVideoRangeType.DOVI.serialName))
}
@Test
fun `Test force disable overrides AV1 Profile 5 device support check`() {
val mediaTest = mockk<MediaCodecCapabilitiesTest>(relaxed = true)
every { mediaTest.supportsAV1DolbyVision() } returns true
val profile =
buildProfile(
mediaTest,
decodeAv1 = false,
forceDisabledHdr = setOf(WholphinVideoRangeType.DOVI.serialName),
)
Assert.assertTrue(unsupportedRangeTypes(profile, Codec.Video.AV1).contains(WholphinVideoRangeType.DOVI.serialName))
}
}