fix hdr playback, lint warnings

This commit is contained in:
2026-06-04 22:50:46 +03:00
parent 2fce5dfb59
commit 6756bf9d40
10 changed files with 144 additions and 72 deletions
+61 -26
View File
@@ -48,33 +48,66 @@ function supportedVideoCodecs(): string[] {
}
/**
* HDR support detection. Probes for the codec strings the major HDR
* formats use:
* - HDR10 / HLG via HEVC main10 level 5.1 (`hvc1.2.4.L153.B0`) or VP9.2
* (`vp09.02.10.10.01.09.16.09.00`)
* - Dolby Vision via the `dvh1.05.06` codec string
* When a format is detected we add it to the `VideoRangeType` matrix on
* the corresponding CodecProfile so the server direct-plays HDR content
* instead of falling back to a tone-mapped SDR transcode.
* Check whether the display is in HDR mode. On Windows this means the
* user has "Use HDR" turned on in Display Settings. The media query
* returns false on SDR-only panels and on HDR panels with HDR mode off.
*
* jellyfin-web uses browser-identification heuristics (hardcoding
* `browser.chrome && !browser.mobile` etc.) because it doesn't control
* the shell. We control the Tauri shell so we can use the actual media
* query - same outcome, fewer moving parts.
*/
function displaySupportsHdr(): boolean {
try {
return window.matchMedia('(dynamic-range: high)').matches
} catch {
return false
}
}
/**
* HDR format detection. Two-stage gate:
* 1. Codec probe - does the browser's MSE stack understand the codec
* string for this HDR format?
* 2. Display check - is the user's display actually in HDR mode?
*
* Both must be true. Advertising HDR10/HLG/DOVI in VideoRangeType when
* the display is SDR causes the server to direct-play the file; the
* browser then tone-maps it to SDR, which works but bypasses the
* server's configurable FFmpeg tone-mapping and can produce different
* (often worse) results.
*
* Codec strings probed:
* - HDR10 / HDR10+ / HLG: HEVC main10 level 5.1 (`hvc1.2.4.L153.B0`)
* or VP9.2 (`vp09.02.10.10.01.09.16.09.00`)
* - Dolby Vision: `dvh1.05.06` / `dvhe.05.06`
*/
function supportedVideoRanges(): string {
const ranges = ['SDR']
if (
const hdrCodec =
canPlayInMse('video/mp4; codecs="hvc1.2.4.L153.B0"') ||
canPlayInMse('video/mp4; codecs="hev1.2.4.L153.B0"') ||
canPlayInMse('video/webm; codecs="vp09.02.10.10.01.09.16.09.00"')
) {
ranges.push('HDR10', 'HLG')
if (hdrCodec && displaySupportsHdr()) {
ranges.push('HDR10', 'HDR10Plus', 'HLG')
}
if (
const dvCodec =
canPlayInMse('video/mp4; codecs="dvh1.05.06"') ||
canPlayInMse('video/mp4; codecs="dvhe.05.06"')
) {
if (dvCodec && displaySupportsHdr()) {
ranges.push('DOVI', 'DOVIWithHDR10', 'DOVIWithHLG', 'DOVIWithSDR')
}
return ranges.join('|')
}
/**
* Exported helper so StreamInfo and other diagnostics can show whether
* the display is in HDR mode without re-probing the codec strings.
*/
export function isHdrDisplayActive(): boolean {
return displaySupportsHdr()
}
export function browserDeviceProfile(audioPassthrough = false) {
const videoCodecs = supportedVideoCodecs()
const videoCodecsCsv = videoCodecs.join(',')
@@ -88,9 +121,12 @@ export function browserDeviceProfile(audioPassthrough = false) {
DirectPlayProfiles: [
{
// MSE / hls.js can remux these on the fly - no need to force a
// server-side transcode just because the container isn't mp4.
Container: 'mp4,m4v,mkv,avi,mov,wmv,ts,mpeg,mpegts',
// Chromium can natively play mp4/m4v. Everything else (mkv, avi, etc)
// goes through the HLS TranscodingProfile below, which remuxes into
// fMP4 that hls.js + MSE can handle. Listing mkv here would make the
// server return SupportsDirectPlay=true, but Chromium's <video> can't
// actually decode raw MKV - the player would just stall.
Container: 'mp4,m4v',
Type: 'Video',
VideoCodec: videoCodecsCsv,
AudioCodec: audioPassthrough
@@ -156,8 +192,9 @@ export function browserDeviceProfile(audioPassthrough = false) {
},
// HEVC: cap at main / main10 + level 5.1 - matches what hardware
// decoders on most consumer GPUs can chew through. VideoRangeType
// advertises whichever HDR formats the browser actually supports so
// the server direct-plays HDR sources instead of tone-mapping to SDR.
// only advertises HDR when the display is actually in HDR mode, so
// the server does FFmpeg tone-mapping for SDR displays instead of
// relying on the browser's built-in tone-mapping.
{
Type: 'Video',
Codec: 'hevc',
@@ -210,18 +247,16 @@ export function browserDeviceProfile(audioPassthrough = false) {
},
],
// Mirror jellyfin-web: only External delivery. The server decides
// whether to burn-in or deliver externally based on these profiles.
// With no Embed/Hls methods listed, the server won't try to mux
// subtitles into the transcode stream - it delivers them as separate
// files that the client loads via libass-wasm or native VTT tracks.
SubtitleProfiles: [
{ Format: 'vtt', Method: 'External' },
{ Format: 'vtt', Method: 'Hls' },
{ Format: 'subrip', Method: 'External' },
{ Format: 'subrip', Method: 'Embed' },
{ Format: 'subrip', Method: 'Hls' },
{ Format: 'ass', Method: 'External' },
{ Format: 'ass', Method: 'Embed' },
{ Format: 'ass', Method: 'Hls' },
{ Format: 'ssa', Method: 'External' },
{ Format: 'ssa', Method: 'Embed' },
{ Format: 'ssa', Method: 'Hls' },
{ Format: 'subrip', Method: 'External' },
],
ResponseProfiles: [],