From 8cd2293ff9bf4e0a797d1bcdc7d9addda53324b2 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 15 Sep 2026 11:17:08 -0400 Subject: Backport pull request #18037 from jellyfin/release-12.z Enforce channel mediasource id Original-merge: 941e419a2cf9e06c9dbf3b441ab8eef55f1fc5e8 Merged-by: crobibero Backported-by: Cody Robibero --- src/Jellyfin.LiveTv/Channels/ChannelManager.cs | 39 ++++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs index ed02fe6a1d..256a652fd2 100644 --- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs +++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs @@ -372,9 +372,7 @@ namespace Jellyfin.LiveTv.Channels { IEnumerable results = GetSavedMediaSources(item); - return results - .Select(i => NormalizeMediaSource(item, i)) - .ToList(); + return NormalizeMediaSources(item, results); } /// @@ -400,9 +398,7 @@ namespace Jellyfin.LiveTv.Channels results = Enumerable.Empty(); } - return results - .Select(i => NormalizeMediaSource(item, i)) - .ToList(); + return NormalizeMediaSources(item, results); } private async Task> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) @@ -420,11 +416,36 @@ namespace Jellyfin.LiveTv.Channels return list; } - private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info) + private static IReadOnlyList NormalizeMediaSources(BaseItem item, IEnumerable infos) { - info.RunTimeTicks ??= item.RunTimeTicks; + var list = infos.ToList(); + var itemId = item.Id.ToString("N", CultureInfo.InvariantCulture); + var hasDefaultSource = list.Any(i => string.Equals(i.Id, itemId, StringComparison.OrdinalIgnoreCase)); - return info; + for (var index = 0; index < list.Count; index++) + { + var info = list[index]; + info.RunTimeTicks ??= item.RunTimeTicks; + + if (!string.IsNullOrEmpty(info.Id)) + { + continue; + } + + if (!hasDefaultSource) + { + // The source carrying the item id sorts first and becomes the client's default. + info.Id = itemId; + hasDefaultSource = true; + continue; + } + + // Remaining sources need ids that are distinct but stable, as clients send them back to request playback. + var key = string.IsNullOrEmpty(info.Path) ? index.ToString(CultureInfo.InvariantCulture) : info.Path; + info.Id = (itemId + key).GetMD5().ToString("N", CultureInfo.InvariantCulture); + } + + return list; } private async Task GetChannel(IChannel channelInfo, CancellationToken cancellationToken) -- cgit v1.2.3