Resolve saved native channel references after receiver migrations

This commit is contained in:
2026-10-23 18:31:55 +02:00
parent 0ab790d1e4
commit 4ff46f547b
9 changed files with 227 additions and 5 deletions
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.LiveTv;
namespace Jellyfin.Plugin.Enigma2;
/// <summary>Keep saved native channels playable after a receiver bouquet migration.</summary>
public static class ChannelReferenceResolver
{
public static string Resolve(string reference, IEnumerable<ChannelInfo> channels, Func<string> previousName)
{
var current = channels.Where(c => !string.IsNullOrWhiteSpace(c.Id)).ToArray();
var exact = current.FirstOrDefault(c => string.Equals(c.Id, reference, StringComparison.OrdinalIgnoreCase));
if (exact != null) return exact.Id;
// A changed Enigma service type does not change the DVB service identity.
var identity = Identity(reference);
var matches = current.Where(c => identity != null && Identity(c.Id) == identity)
.Select(c => c.Id).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
if (matches.Length == 1) return matches[0];
if (matches.Length > 1) return reference;
// A moved service has a new SID/TSID. Use only a unique exact historical
// name; never guess between HD/SD, regional or alternative services.
var name = previousName()?.Trim();
if (string.IsNullOrEmpty(name)) return reference;
matches = current.Where(c => string.Equals(c.Name?.Trim(), name, StringComparison.OrdinalIgnoreCase))
.Select(c => c.Id).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
return matches.Length == 1 ? matches[0] : reference;
}
private static string Identity(string reference)
{
var fields = reference?.TrimEnd(':').Split(':');
if (fields == null || fields.Length != 10 || fields[0] != "1" || fields[1] != "0") return null;
// No URLs, labels or non-DVB references participate in this fallback.
if (fields.Any(f => f.Length == 0 || !f.All(Uri.IsHexDigit))) return null;
fields[2] = "";
return string.Join(':', fields).ToUpperInvariant();
}
}
@@ -2,8 +2,8 @@
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<AssemblyVersion>7.0.8.0</AssemblyVersion>
<FileVersion>7.0.8.0</FileVersion>
<AssemblyVersion>7.0.9.0</AssemblyVersion>
<FileVersion>7.0.9.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
+26 -1
View File
@@ -14,6 +14,9 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
@@ -32,6 +35,7 @@ namespace Jellyfin.Plugin.Enigma2
private int _liveStreams;
private readonly LiveStreamProbe _streamProbe;
private readonly LiveStreamRelay _streamRelay;
private readonly ILibraryManager _library;
private string tvBouquetSRef;
private List<ChannelInfo> tvChannelInfos = new List<ChannelInfo>();
@@ -42,12 +46,13 @@ namespace Jellyfin.Plugin.Enigma2
public static LiveTvService Instance { get; private set; }
public LiveTvService(ILogger<LiveTvService> logger, IHttpClientFactory httpClientFactory, InternetEpg internetEpg, LiveStreamProbe streamProbe, LiveStreamRelay streamRelay)
public LiveTvService(ILogger<LiveTvService> logger, IHttpClientFactory httpClientFactory, InternetEpg internetEpg, LiveStreamProbe streamProbe, LiveStreamRelay streamRelay, ILibraryManager library)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_streamProbe = streamProbe;
_streamRelay = streamRelay;
_library = library;
Instance = this;
}
@@ -1139,6 +1144,26 @@ namespace Jellyfin.Plugin.Enigma2
{
_logger.LogInformation("[Enigma2] Start GetChannelStream");
var requestedReference = channelOid;
var currentChannels = await GetChannelsAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
channelOid = ChannelReferenceResolver.Resolve(channelOid, currentChannels, () =>
{
// Native playback has already checked the user's channel access.
// This internal lookup reads only Enigma2 channel metadata and
// does not expose items or alter the user's native channel ID.
var names = _library.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }
}).OfType<LiveTvChannel>()
.Where(c => string.Equals(c.ServiceName, "Enigma2", StringComparison.OrdinalIgnoreCase)
&& string.Equals(c.ExternalId, requestedReference, StringComparison.OrdinalIgnoreCase))
.Select(c => c.Name).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
return names.Length == 1 ? names[0] : null;
});
if (!string.Equals(channelOid, requestedReference, StringComparison.OrdinalIgnoreCase))
_logger.LogInformation("[Enigma2] Resolved saved channel reference {PreviousReference} to current bouquet reference {CurrentReference}", requestedReference, channelOid);
var protocol = "http";
if (Plugin.Instance.Configuration.UseSecureHTTPSForStreams)
{