diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index f22ac0b73a..eca99d2b28 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -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; diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 01909c3283..5d2a87ef05 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -425,6 +425,8 @@ public class MediaInfoHelper } } + FilterXboxUnsupportedCodecs(profile, httpContext.User.GetDeviceId()); + if (profile is not null) { var item = _libraryManager.GetItemById(request.ItemId) @@ -524,4 +526,67 @@ public class MediaInfoHelper return maxBitrate; } + + /// + /// Filters unsupported codecs from Xbox device profiles. + /// Xbox UWP WebView2 incorrectly reports support for AV1 and Opus codecs. + /// + /// The device profile to filter. + /// The device identifier. + 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); + } }