Fix Xbox UWP codec detection for AV1 and Opus

Xbox UWP applications use WebView2 which incorrectly reports support
for AV1 video and Opus audio codecs. While the underlying Chromium
engine supports these codecs, the Xbox UWP runtime cannot decode them,
resulting in playback failures.

This patch adds server-side filtering to remove AV1 and Opus from
device profiles when an Xbox device is detected, ensuring the server
will transcode these codecs to supported formats (H.264/AAC) instead
of attempting direct play.

Fixes #109
This commit is contained in:
mani
2026-01-08 00:34:13 +01:00
parent 7cc33291f5
commit bf6a20b32f
2 changed files with 67 additions and 0 deletions

View File

@@ -146,6 +146,8 @@ public class MediaInfoController : BaseJellyfinApiController
}
}
_mediaInfoHelper.FilterXboxUnsupportedCodecs(profile, User.GetDeviceId());
// Copy params from posted body
// TODO clean up when breaking API compatibility.
userId ??= playbackInfoDto?.UserId;

View File

@@ -425,6 +425,8 @@ public class MediaInfoHelper
}
}
FilterXboxUnsupportedCodecs(profile, httpContext.User.GetDeviceId());
if (profile is not null)
{
var item = _libraryManager.GetItemById<BaseItem>(request.ItemId)
@@ -524,4 +526,67 @@ public class MediaInfoHelper
return maxBitrate;
}
/// <summary>
/// Filters unsupported codecs from Xbox device profiles.
/// Xbox UWP WebView2 incorrectly reports support for AV1 and Opus codecs.
/// </summary>
/// <param name="profile">The device profile to filter.</param>
/// <param name="deviceId">The device identifier.</param>
public void FilterXboxUnsupportedCodecs(DeviceProfile? profile, string? deviceId)
{
if (profile is null || string.IsNullOrEmpty(deviceId))
{
return;
}
if (!deviceId.Contains("xbox", StringComparison.OrdinalIgnoreCase))
{
return;
}
_logger.LogDebug("Filtering unsupported codecs for Xbox device: {DeviceId}", deviceId);
if (profile.DirectPlayProfiles is not null)
{
foreach (var directPlay in profile.DirectPlayProfiles)
{
if (!string.IsNullOrEmpty(directPlay.VideoCodec))
{
directPlay.VideoCodec = FilterCodecList(directPlay.VideoCodec, "av1");
}
if (!string.IsNullOrEmpty(directPlay.AudioCodec))
{
directPlay.AudioCodec = FilterCodecList(directPlay.AudioCodec, "opus");
}
}
}
if (profile.TranscodingProfiles is not null)
{
foreach (var transcoding in profile.TranscodingProfiles)
{
if (!string.IsNullOrEmpty(transcoding.VideoCodec))
{
transcoding.VideoCodec = FilterCodecList(transcoding.VideoCodec, "av1");
}
if (!string.IsNullOrEmpty(transcoding.AudioCodec))
{
transcoding.AudioCodec = FilterCodecList(transcoding.AudioCodec, "opus");
}
}
}
}
private static string FilterCodecList(string codecList, string codecToRemove)
{
var codecs = codecList.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim())
.Where(c => !c.Equals(codecToRemove, StringComparison.OrdinalIgnoreCase))
.ToArray();
return string.Join(',', codecs);
}
}