Jellyfin Forum
How does Jellyfin-web determine what content a client is capable of playing? - Printable Version

+- Jellyfin Forum (https://forum.jellyfin.org)
+-- Forum: Support (https://forum.jellyfin.org/f-support)
+--- Forum: General Questions (https://forum.jellyfin.org/f-general-questions)
+--- Thread: How does Jellyfin-web determine what content a client is capable of playing? (/t-how-does-jellyfin-web-determine-what-content-a-client-is-capable-of-playing)

Pages: 1 2


How does Jellyfin-web determine what content a client is capable of playing? - kimbo - 2024-01-19

Hey guys, could anyone point me in the right direction as to where in code Jellyfin-web detects client capabilities?

In my case it is wrong, and emby has no issue direct playing h265 content, while jellyfin reports that the codec is incompatible

It seems its using some method other than isTypeSupported, canPlayType or mediaCapabilities, since all of these report that h265 is supported

Running Thorium with hardware decoding disabled so its using FFmpegVideoDecoder which should have no issue playing h265.

I have tried the option "Prefer fMP4-HLS Media Container", no effect

Thanks for reading


RE: How does Jellyfin-web determine what content a client is capable of playing? - tmsrxzar - 2024-01-19

https://github.com/jellyfin/jellyfin-web/blob/master/src/scripts/browserDeviceProfile.js


RE: How does Jellyfin-web determine what content a client is capable of playing? - kimbo - 2024-01-19

Nice! thanks!

Leaves me confused however, canPlayType reports that I do have support, but jellyfin wont direct play

Must be something else going on here


RE: How does Jellyfin-web determine what content a client is capable of playing? - tmsrxzar - 2024-01-19

if it's not due to canPlayType then it could be due to bandwidth settings
check the jellyfin server log and look for TranscodeReason; or provide it via a public paste site and i can do it for you


RE: How does Jellyfin-web determine what content a client is capable of playing? - kimbo - 2024-01-19

The error in the log

TranscodeReason=VideoCodecNotSupported, AudioCodecNotSupported

Yet if I set up a simple page testing in the exact same way with canPlayType and test for the codecs that Jellyfin does, I will get no for the 1.1.L120 versions but then I get probably for the 1.1.0.L120 versions, so it really should be direct playing, no?


RE: How does Jellyfin-web determine what content a client is capable of playing? - tmsrxzar - 2024-01-19

(2024-01-19, 05:18 PM)kimbo Wrote: The error in the log

TranscodeReason=VideoCodecNotSupported, AudioCodecNotSupported

Yet if I set up a simple page testing in the exact same way with canPlayType and test for the codecs that Jellyfin does, I will get no for the 1.1.L120 versions but then I get probably for the 1.1.0.L120 versions, so it really should be direct playing, no?

i have no context to answer this question, you haven't even mentioned your platform, browser or versions
however in my experience browsers make terrible jellyfin clients


RE: How does Jellyfin-web determine what content a client is capable of playing? - kimbo - 2024-01-19

Yes, I did, read the OP please.

A quick test shows probably for both 1.1.0 versions

Code:
var video = document.createElement('video');

types = [
    'video/mp4; codecs="hvc1.1.L120"',
    'video/mp4; codecs="hev1.1.L120"',
    'video/mp4; codecs="hvc1.1.0.L120"',
    'video/mp4; codecs="hev1.1.0.L120"'
    ];

types.forEach((t) => console.log('testing ' + t + ' ' + video.canPlayType(t)));



RE: How does Jellyfin-web determine what content a client is capable of playing? - tmsrxzar - 2024-01-19

(2024-01-19, 05:43 PM)kimbo Wrote: Yes, I did, read the OP please.
(2024-01-19, 03:52 PM)kimbo Wrote: Hey guys, could anyone point me in the right direction as to where in code Jellyfin-web detects client capabilities?
In my case it is wrong, and emby has no issue direct playing h265 content, while jellyfin reports that the codec is incompatible
It seems its using some method other than isTypeSupported, canPlayType or mediaCapabilities, since all of these report that h265 is supported
Running Thorium with hardware decoding disabled so its using FFmpegVideoDecoder which should have no issue playing h265.
I have tried the option "Prefer fMP4-HLS Media Container", no effect
Thanks for reading

https://www.google.com/search?q=thorium
"Thorium (chemical symbol Th) is a naturally occurring radioactive metal"

https://www.google.com/search?q=thorium+jellyfin
"Thorium: Chromium fork named after radioactive element"

so we know it's some sort of chrom(e)(ium) ...

for example, i am running a Firefox 115.6.0esr fork on Debian 12 Bookworm




a little looking and i wouldn't be able to help with thorium or chrome
the initial question of where does this code live turned into support for a browser i didn't know existed which is based on a browser i already cannot get to work

recommendations are
compare to a non forked browser, if it's a chrom* issue or a thorium one would be the question
compare to jellyfin media player which is better suited for the task at hand
compare to mpv-shim for jellyfin


RE: How does Jellyfin-web determine what content a client is capable of playing? - kimbo - 2024-01-19

This is a jellyfin issue, not a browser issue, hence me asking where this code lives. There is something else causing jellyfin not to direct play and it is not the canPlayType query, which it doesnt even appear to be using since testing in that manner does in fact report support for hevc.

I am not asking for browser support? lol

Official Chrome does not support hevc software decoding. I already stated thats what I am using.

Also, jellyfin-vue can direct play the same content just fine. this is an issue in jellyfin-web


RE: How does Jellyfin-web determine what content a client is capable of playing? - kimbo - 2024-01-19

Playbackinfo

Code:
{
  "DeviceProfile": {
    "MaxStreamingBitrate": 120000000,
    "MaxStaticBitrate": 100000000,
    "MusicStreamingTranscodingBitrate": 384000,
    "DirectPlayProfiles": [
      {
        "Container": "webm",
        "Type": "Video",
        "VideoCodec": "vp8,vp9,av1",
        "AudioCodec": "vorbis,opus"
      },
      {
        "Container": "mp4,m4v",
        "Type": "Video",
        "VideoCodec": "h264,hevc,vp9,av1",
        "AudioCodec": "aac,mp3,ac3,eac3,opus,flac,vorbis"
      },
      {
        "Container": "mov",
        "Type": "Video",
        "VideoCodec": "h264",
        "AudioCodec": "aac,mp3,ac3,eac3,opus,flac,vorbis"
      },
      {
        "Container": "opus",
        "Type": "Audio"
      },
      {
        "Container": "webm",
        "AudioCodec": "opus",
        "Type": "Audio"
      },
      {
        "Container": "mp3",
        "Type": "Audio"
      },
      {
        "Container": "aac",
        "Type": "Audio"
      },
      {
        "Container": "m4a",
        "AudioCodec": "aac",
        "Type": "Audio"
      },
      {
        "Container": "m4b",
        "AudioCodec": "aac",
        "Type": "Audio"
      },
      {
        "Container": "flac",
        "Type": "Audio"
      },
      {
        "Container": "webma",
        "Type": "Audio"
      },
      {
        "Container": "webm",
        "AudioCodec": "webma",
        "Type": "Audio"
      },
      {
        "Container": "wav",
        "Type": "Audio"
      },
      {
        "Container": "ogg",
        "Type": "Audio"
      }
    ],
    "TranscodingProfiles": [
      {
        "Container": "ts",
        "Type": "Audio",
        "AudioCodec": "aac",
        "Context": "Streaming",
        "Protocol": "hls",
        "MaxAudioChannels": "6",
        "MinSegments": "1",
        "BreakOnNonKeyFrames": true
      },
      {
        "Container": "aac",
        "Type": "Audio",
        "AudioCodec": "aac",
        "Context": "Streaming",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "mp3",
        "Type": "Audio",
        "AudioCodec": "mp3",
        "Context": "Streaming",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "opus",
        "Type": "Audio",
        "AudioCodec": "opus",
        "Context": "Streaming",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "wav",
        "Type": "Audio",
        "AudioCodec": "wav",
        "Context": "Streaming",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "opus",
        "Type": "Audio",
        "AudioCodec": "opus",
        "Context": "Static",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "mp3",
        "Type": "Audio",
        "AudioCodec": "mp3",
        "Context": "Static",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "aac",
        "Type": "Audio",
        "AudioCodec": "aac",
        "Context": "Static",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "wav",
        "Type": "Audio",
        "AudioCodec": "wav",
        "Context": "Static",
        "Protocol": "http",
        "MaxAudioChannels": "6"
      },
      {
        "Container": "ts",
        "Type": "Video",
        "AudioCodec": "aac,mp3",
        "VideoCodec": "h264",
        "Context": "Streaming",
        "Protocol": "hls",
        "MaxAudioChannels": "6",
        "MinSegments": "1",
        "BreakOnNonKeyFrames": true
      }
    ],
    "ContainerProfiles": [],
    "CodecProfiles": [
      {
        "Type": "VideoAudio",
        "Codec": "aac",
        "Conditions": [
          {
            "Condition": "Equals",
            "Property": "IsSecondaryAudio",
            "Value": "false",
            "IsRequired": false
          }
        ]
      },
      {
        "Type": "VideoAudio",
        "Conditions": [
          {
            "Condition": "Equals",
            "Property": "IsSecondaryAudio",
            "Value": "false",
            "IsRequired": false
          }
        ]
      },
      {
        "Type": "Video",
        "Codec": "h264",
        "Conditions": [
          {
            "Condition": "NotEquals",
            "Property": "IsAnamorphic",
            "Value": "true",
            "IsRequired": false
          },
          {
            "Condition": "EqualsAny",
            "Property": "VideoProfile",
            "Value": "high|main|baseline|constrained baseline|high 10",
            "IsRequired": false
          },
          {
            "Condition": "EqualsAny",
            "Property": "VideoRangeType",
            "Value": "SDR",
            "IsRequired": false
          },
          {
            "Condition": "LessThanEqual",
            "Property": "VideoLevel",
            "Value": "52",
            "IsRequired": false
          },
          {
            "Condition": "NotEquals",
            "Property": "IsInterlaced",
            "Value": "true",
            "IsRequired": false
          }
        ]
      },
      {
        "Type": "Video",
        "Codec": "hevc",
        "Conditions": [
          {
            "Condition": "NotEquals",
            "Property": "IsAnamorphic",
            "Value": "true",
            "IsRequired": false
          },
          {
            "Condition": "EqualsAny",
            "Property": "VideoProfile",
            "Value": "main|main 10",
            "IsRequired": false
          },
          {
            "Condition": "EqualsAny",
            "Property": "VideoRangeType",
            "Value": "SDR",
            "IsRequired": false
          },
          {
            "Condition": "LessThanEqual",
            "Property": "VideoLevel",
            "Value": "183",
            "IsRequired": false
          },
          {
            "Condition": "NotEquals",
            "Property": "IsInterlaced",
            "Value": "true",
            "IsRequired": false
          }
        ]
      },
      {
        "Type": "Video",
        "Codec": "vp9",
        "Conditions": [
          {
            "Condition": "EqualsAny",
            "Property": "VideoRangeType",
            "Value": "SDR|HDR10|HLG",
            "IsRequired": false
          }
        ]
      },
      {
        "Type": "Video",
        "Codec": "av1",
        "Conditions": [
          {
            "Condition": "EqualsAny",
            "Property": "VideoRangeType",
            "Value": "SDR|HDR10|HLG",
            "IsRequired": false
          }
        ]
      }
    ],
    "SubtitleProfiles": [
      {
        "Format": "vtt",
        "Method": "External"
      },
      {
        "Format": "ass",
        "Method": "External"
      },
      {
        "Format": "ssa",
        "Method": "External"
      }
    ],
    "ResponseProfiles": [
      {
        "Type": "Video",
        "Container": "m4v",
        "MimeType": "video/mp4"
      }
    ]
  }
}

Jellyfin web incorrectly informs the server that it does not support hevc when in fact it does
Actually, it would seem that instead of picking a container based on a codec its going straight for webm which does not have a codec which matches

This is very obviously an error in jellyfin-web's code