fix hdr playback, lint warnings
This commit is contained in:
+61
-26
@@ -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: [],
|
||||
|
||||
Reference in New Issue
Block a user