feat: adding exoplayer for HDR playback

Currently MPV doesn't support HDR via external displays. giving people
the choice of HDR/limited ass sub support/SDR full sub support

Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
Lance Chant
2026-07-01 13:07:35 +02:00
parent 28a75a2b8c
commit faa250bfdd
19 changed files with 1735 additions and 82 deletions

View File

@@ -1129,7 +1129,16 @@ export const Controls: FC<Props> = ({
{/* Skip intro card */}
<TVSkipSegmentCard
show={showSkipButton && !isCountdownActive}
onPress={skipIntro}
onPress={() => {
// After the seek lands, showSkipButton flips false and this card
// unmounts. With controls visible the focus-stealing overlay is
// disabled, so without an explicit handoff the focus engine is
// stranded. Prime the play button to receive focus on the next
// render — when controls are hidden the focus overlay takes over
// naturally and this is a harmless no-op.
if (showControls) setFocusPlayButton(true);
skipIntro();
}}
type='intro'
controlsVisible={showControls}
refSetter={setSkipSegmentRef}
@@ -1144,7 +1153,11 @@ export const Controls: FC<Props> = ({
(hasContentAfterCredits || !nextItem) &&
!isCountdownActive
}
onPress={skipCredit}
onPress={() => {
// See the intro card above for the focus-handoff rationale.
if (showControls) setFocusPlayButton(true);
skipCredit();
}}
type='credits'
controlsVisible={showControls}
refSetter={setSkipSegmentRef}

View File

@@ -213,13 +213,10 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
);
return {
container: mediaSource.Container,
videoRange: videoStream?.VideoRangeType,
bitDepth: videoStream?.BitDepth,
audioChannels: audioStream?.Channels,
audioCodecFromSource: audioStream?.Codec,
subtitleCodec: subtitleStream?.Codec,
subtitleTitle: subtitleStream?.DisplayTitle,
};
}, [mediaSource, currentAudioIndex, currentSubtitleIndex]);
@@ -305,9 +302,13 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
<Text style={textStyle}>
{info.videoWidth}x{info.videoHeight}
{streamInfo?.bitDepth ? ` ${streamInfo.bitDepth}bit` : ""}
{formatVideoRange(streamInfo?.videoRange)
? ` ${formatVideoRange(streamInfo?.videoRange)}`
: ""}
{/* Prefer the player-reported HDR format (authoritative —
what's actually being decoded) over Jellyfin metadata. */}
{info?.hdrFormat
? ` ${info.hdrFormat}`
: formatVideoRange(streamInfo?.videoRange)
? ` ${formatVideoRange(streamInfo?.videoRange)}`
: ""}
</Text>
)}
{info?.videoCodec && (
@@ -319,8 +320,15 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
{info?.audioCodec && (
<Text style={textStyle}>
Audio: {formatCodec(info.audioCodec)}
{streamInfo?.audioChannels
? ` ${formatAudioChannels(streamInfo.audioChannels)}`
{/* Prefer player-reported channel count; fall back to
Jellyfin metadata for MPV which doesn't populate it. */}
{(info.audioChannels ?? streamInfo?.audioChannels)
? ` ${formatAudioChannels(
info.audioChannels ?? streamInfo!.audioChannels!,
)}`
: ""}
{info.audioSampleRate
? ` @ ${(info.audioSampleRate / 1000).toFixed(1)}kHz`
: ""}
</Text>
)}
@@ -339,6 +347,17 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
: "N/A"}
</Text>
)}
{(info?.colorSpace || info?.colorRange || info?.colorTransfer) && (
<Text style={textStyle}>
Color:
{[info.colorSpace, info.colorRange, info.colorTransfer]
.filter(Boolean)
.join(" / ")}
</Text>
)}
{info?.videoCodecs && (
<Text style={textStyle}>Codec tag: {info.videoCodecs}</Text>
)}
{info?.cacheSeconds !== undefined && (
<Text style={textStyle}>
Buffer: {info.cacheSeconds.toFixed(1)}s
@@ -356,6 +375,12 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
{info.hwdec ? ` / ${info.hwdec}` : ""}
</Text>
)}
{info?.decoderName && (
<Text style={textStyle}>
Decoder: {info.decoderName}
{info.decoderType ? ` (${info.decoderType})` : ""}
</Text>
)}
{info?.estimatedVfFps !== undefined && (
<Text style={textStyle}>
Output FPS: {info.estimatedVfFps.toFixed(2)}