mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 09:59:06 -07:00
Add IListingsManager service
This commit is contained in:
parent
20f05f8103
commit
42b052a5a6
@ -45,6 +45,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IGuideManager _guideManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IListingsManager _listingsManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -59,6 +60,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
|
||||
/// <param name="guideManager">Instance of the <see cref="IGuideManager"/> interface.</param>
|
||||
/// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
|
||||
/// <param name="listingsManager">Instance of the <see cref="IListingsManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
@ -70,6 +72,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
ILiveTvManager liveTvManager,
|
||||
IGuideManager guideManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IListingsManager listingsManager,
|
||||
IUserManager userManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager,
|
||||
@ -81,6 +84,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
_liveTvManager = liveTvManager;
|
||||
_guideManager = guideManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_listingsManager = listingsManager;
|
||||
_userManager = userManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
@ -1015,7 +1019,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
listingsProviderInfo.Password = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(pw))).ToLowerInvariant();
|
||||
}
|
||||
|
||||
return await _liveTvManager.SaveListingProvider(listingsProviderInfo, validateLogin, validateListings).ConfigureAwait(false);
|
||||
return await _listingsManager.SaveListingProvider(listingsProviderInfo, validateLogin, validateListings).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1029,7 +1033,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult DeleteListingProvider([FromQuery] string? id)
|
||||
{
|
||||
_liveTvManager.DeleteListingsProvider(id);
|
||||
_listingsManager.DeleteListingsProvider(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@ -1050,9 +1054,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] string? type,
|
||||
[FromQuery] string? location,
|
||||
[FromQuery] string? country)
|
||||
{
|
||||
return await _liveTvManager.GetLineups(type, id, country, location).ConfigureAwait(false);
|
||||
}
|
||||
=> await _listingsManager.GetLineups(type, id, country, location).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets available countries.
|
||||
@ -1083,48 +1085,20 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[HttpGet("ChannelMappingOptions")]
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId)
|
||||
{
|
||||
var config = _configurationManager.GetConfiguration<LiveTvOptions>("livetv");
|
||||
|
||||
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name;
|
||||
|
||||
var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(providerId, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var mappings = listingsProviderInfo.ChannelMappings;
|
||||
|
||||
return new ChannelMappingOptionsDto
|
||||
{
|
||||
TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(),
|
||||
ProviderChannels = providerChannels.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Id
|
||||
}).ToList(),
|
||||
Mappings = mappings,
|
||||
ProviderName = listingsProviderName
|
||||
};
|
||||
}
|
||||
public Task<ChannelMappingOptionsDto> GetChannelMappingOptions([FromQuery] string? providerId)
|
||||
=> _listingsManager.GetChannelMappingOptions(providerId);
|
||||
|
||||
/// <summary>
|
||||
/// Set channel mappings.
|
||||
/// </summary>
|
||||
/// <param name="setChannelMappingDto">The set channel mapping dto.</param>
|
||||
/// <param name="dto">The set channel mapping dto.</param>
|
||||
/// <response code="200">Created channel mapping returned.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
|
||||
[HttpPost("ChannelMappings")]
|
||||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
|
||||
{
|
||||
return await _liveTvManager.SetChannelMapping(setChannelMappingDto.ProviderId, setChannelMappingDto.TunerChannelId, setChannelMappingDto.ProviderChannelId).ConfigureAwait(false);
|
||||
}
|
||||
public Task<TunerChannelMapping> SetChannelMapping([FromBody, Required] SetChannelMappingDto dto)
|
||||
=> _listingsManager.SetChannelMapping(dto.ProviderId, dto.TunerChannelId, dto.ProviderChannelId);
|
||||
|
||||
/// <summary>
|
||||
/// Get tuner host types.
|
||||
|
79
MediaBrowser.Controller/LiveTv/IListingsManager.cs
Normal file
79
MediaBrowser.Controller/LiveTv/IListingsManager.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for managing <see cref="IListingsProvider"/>s and mapping
|
||||
/// their channels to channels provided by <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
public interface IListingsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
/// <param name="info">The listing provider information.</param>
|
||||
/// <param name="validateLogin">A value indicating whether to validate login.</param>
|
||||
/// <param name="validateListings">A value indicating whether to validate listings..</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the listing provider.
|
||||
/// </summary>
|
||||
/// <param name="id">The listing provider's id.</param>
|
||||
void DeleteListingsProvider(string? id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lineups.
|
||||
/// </summary>
|
||||
/// <param name="providerType">Type of the provider.</param>
|
||||
/// <param name="providerId">The provider identifier.</param>
|
||||
/// <param name="country">The country.</param>
|
||||
/// <param name="location">The location.</param>
|
||||
/// <returns>The available lineups.</returns>
|
||||
Task<List<NameIdPair>> GetLineups(string? providerType, string? providerId, string? country, string? location);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the programs for a provided channel.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel to retrieve programs for.</param>
|
||||
/// <param name="startDateUtc">The earliest date to retrieve programs for.</param>
|
||||
/// <param name="endDateUtc">The latest date to retrieve programs for.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>The available programs.</returns>
|
||||
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(
|
||||
ChannelInfo channel,
|
||||
DateTime startDateUtc,
|
||||
DateTime endDateUtc,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Adds metadata from the <see cref="IListingsProvider"/>s to the provided channels.
|
||||
/// </summary>
|
||||
/// <param name="channels">The channels.</param>
|
||||
/// <param name="enableCache">A value indicating whether to use the EPG channel cache.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>A task representing the metadata population.</returns>
|
||||
Task AddProviderMetadata(IList<ChannelInfo> channels, bool enableCache, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel mapping options for a provider.
|
||||
/// </summary>
|
||||
/// <param name="providerId">The id of the provider to use.</param>
|
||||
/// <returns>The channel mapping options.</returns>
|
||||
Task<ChannelMappingOptionsDto> GetChannelMappingOptions(string? providerId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the channel mapping.
|
||||
/// </summary>
|
||||
/// <param name="providerId">The id of the provider for the mapping.</param>
|
||||
/// <param name="tunerChannelNumber">The tuner channel number.</param>
|
||||
/// <param name="providerChannelNumber">The provider channel number.</param>
|
||||
/// <returns>The updated channel mapping.</returns>
|
||||
Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber);
|
||||
}
|
@ -36,8 +36,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <value>The services.</value>
|
||||
IReadOnlyList<ILiveTvService> Services { get; }
|
||||
|
||||
IReadOnlyList<IListingsProvider> ListingProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the new timer defaults asynchronous.
|
||||
/// </summary>
|
||||
@ -239,31 +237,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <param name="validateLogin">if set to <c>true</c> [validate login].</param>
|
||||
/// <param name="validateListings">if set to <c>true</c> [validate listings].</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
|
||||
|
||||
void DeleteListingsProvider(string id);
|
||||
|
||||
Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber);
|
||||
|
||||
TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lineups.
|
||||
/// </summary>
|
||||
/// <param name="providerType">Type of the provider.</param>
|
||||
/// <param name="providerId">The provider identifier.</param>
|
||||
/// <param name="country">The country.</param>
|
||||
/// <param name="location">The location.</param>
|
||||
/// <returns>Task<List<NameIdPair>>.</returns>
|
||||
Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the channel information.
|
||||
/// </summary>
|
||||
@ -272,10 +245,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <param name="user">The user.</param>
|
||||
void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user);
|
||||
|
||||
Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
|
||||
|
||||
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
|
||||
|
||||
string GetEmbyTvActiveRecordingPath(string id);
|
||||
|
||||
ActiveRecordingInfo GetActiveRecordingInfo(string path);
|
||||
|
@ -1,17 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class TunerChannelMapping
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string ProviderChannelName { get; set; }
|
||||
|
||||
public string ProviderChannelId { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace Jellyfin.Api.Models.LiveTvDtos;
|
||||
namespace MediaBrowser.Model.LiveTv;
|
||||
|
||||
/// <summary>
|
||||
/// Channel mapping options dto.
|
16
MediaBrowser.Model/LiveTv/TunerChannelMapping.cs
Normal file
16
MediaBrowser.Model/LiveTv/TunerChannelMapping.cs
Normal file
@ -0,0 +1,16 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Model.LiveTv;
|
||||
|
||||
public class TunerChannelMapping
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string ProviderChannelName { get; set; }
|
||||
|
||||
public string ProviderChannelId { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
}
|
@ -61,14 +61,11 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly LiveTvDtoService _tvDtoService;
|
||||
private readonly IListingsProvider[] _listingsProviders;
|
||||
private readonly IListingsManager _listingsManager;
|
||||
|
||||
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
|
||||
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
|
||||
new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly AsyncNonKeyedLocker _recordingDeleteSemaphore = new(1);
|
||||
|
||||
private bool _disposed;
|
||||
@ -86,7 +83,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
IProviderManager providerManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
LiveTvDtoService tvDtoService,
|
||||
IEnumerable<IListingsProvider> listingsProviders)
|
||||
IListingsManager listingsManager)
|
||||
{
|
||||
Current = this;
|
||||
|
||||
@ -102,7 +99,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_streamHelper = streamHelper;
|
||||
_listingsProviders = listingsProviders.ToArray();
|
||||
_listingsManager = listingsManager;
|
||||
|
||||
_seriesTimerProvider = new SeriesTimerManager(_logger, Path.Combine(DataPath, "seriestimers.json"));
|
||||
_timerProvider = new TimerManager(_logger, Path.Combine(DataPath, "timers.json"));
|
||||
@ -312,15 +309,15 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
|
||||
private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
var channels = new List<ChannelInfo>();
|
||||
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var channels = await hostInstance.GetChannels(enableCache, cancellationToken).ConfigureAwait(false);
|
||||
var tunerChannels = await hostInstance.GetChannels(enableCache, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
list.AddRange(channels);
|
||||
channels.AddRange(tunerChannels);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -328,209 +325,9 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var provider in GetListingProviders())
|
||||
{
|
||||
var enabledChannels = list
|
||||
.Where(i => IsListingProviderEnabledForTuner(provider.Item2, i.TunerHostId))
|
||||
.ToList();
|
||||
await _listingsManager.AddProviderMetadata(channels, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (enabledChannels.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await AddMetadata(provider.Item1, provider.Item2, enabledChannels, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding metadata");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private async Task AddMetadata(
|
||||
IListingsProvider provider,
|
||||
ListingsProviderInfo info,
|
||||
IEnumerable<ChannelInfo> tunerChannels,
|
||||
bool enableCache,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var tunerChannel in tunerChannels)
|
||||
{
|
||||
var epgChannel = GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
|
||||
|
||||
if (epgChannel is not null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(epgChannel.Name))
|
||||
{
|
||||
// tunerChannel.Name = epgChannel.Name;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
|
||||
{
|
||||
tunerChannel.ImageUrl = epgChannel.ImageUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<EpgChannelData> GetEpgChannels(
|
||||
IListingsProvider provider,
|
||||
ListingsProviderInfo info,
|
||||
bool enableCache,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!enableCache || !_epgChannels.TryGetValue(info.Id, out var result))
|
||||
{
|
||||
var channels = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var channel in channels)
|
||||
{
|
||||
_logger.LogInformation("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id);
|
||||
}
|
||||
|
||||
result = new EpgChannelData(channels);
|
||||
_epgChannels.AddOrUpdate(info.Id, result, (_, _) => result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ChannelInfo> GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken)
|
||||
{
|
||||
var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
|
||||
}
|
||||
|
||||
private static string GetMappedChannel(string channelId, NameValuePair[] mappings)
|
||||
{
|
||||
foreach (NameValuePair mapping in mappings)
|
||||
{
|
||||
if (string.Equals(mapping.Name, channelId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return mapping.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return channelId;
|
||||
}
|
||||
|
||||
internal ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
|
||||
{
|
||||
return GetEpgChannelFromTunerChannel(mappings, tunerChannel, new EpgChannelData(epgChannels));
|
||||
}
|
||||
|
||||
private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, EpgChannelData epgChannels)
|
||||
{
|
||||
return GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
|
||||
}
|
||||
|
||||
private ChannelInfo GetEpgChannelFromTunerChannel(
|
||||
NameValuePair[] mappings,
|
||||
ChannelInfo tunerChannel,
|
||||
EpgChannelData epgChannelData)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
|
||||
{
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannel.Id, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||
{
|
||||
mappedTunerChannelId = tunerChannel.Id;
|
||||
}
|
||||
|
||||
var channel = epgChannelData.GetChannelById(mappedTunerChannelId);
|
||||
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
||||
{
|
||||
var tunerChannelId = tunerChannel.TunerChannelId;
|
||||
if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
|
||||
}
|
||||
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||
{
|
||||
mappedTunerChannelId = tunerChannelId;
|
||||
}
|
||||
|
||||
var channel = epgChannelData.GetChannelById(mappedTunerChannelId);
|
||||
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
{
|
||||
var tunerChannelNumber = GetMappedChannel(tunerChannel.Number, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tunerChannelNumber))
|
||||
{
|
||||
tunerChannelNumber = tunerChannel.Number;
|
||||
}
|
||||
|
||||
var channel = epgChannelData.GetChannelByNumber(tunerChannelNumber);
|
||||
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Name))
|
||||
{
|
||||
var normalizedName = EpgChannelData.NormalizeName(tunerChannel.Name);
|
||||
|
||||
var channel = epgChannelData.GetChannelByName(normalizedName);
|
||||
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var channels = await hostInstance.GetChannels(false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
list.AddRange(channels);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting channels");
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
.Where(i => IsListingProviderEnabledForTuner(listingsProvider, i.TunerHostId))
|
||||
.ToList();
|
||||
return channels;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken)
|
||||
@ -877,75 +674,13 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll());
|
||||
}
|
||||
|
||||
private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId)
|
||||
{
|
||||
if (info.EnableAllTuners)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tunerHostId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tunerHostId));
|
||||
}
|
||||
|
||||
return info.EnabledTuners.Contains(tunerHostId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false);
|
||||
var channel = channels.First(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var provider in GetListingProviders())
|
||||
{
|
||||
if (!IsListingProviderEnabledForTuner(provider.Item2, channel.TunerHostId))
|
||||
{
|
||||
_logger.LogDebug("Skipping getting programs for channel {0}-{1} from {2}-{3}, because it's not enabled for this tuner.", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Getting programs for channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
|
||||
|
||||
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (epgChannel is null)
|
||||
{
|
||||
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ProgramInfo> programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
|
||||
.ConfigureAwait(false)).ToList();
|
||||
|
||||
// Replace the value that came from the provider with a normalized value
|
||||
foreach (var program in programs)
|
||||
{
|
||||
program.ChannelId = channelId;
|
||||
|
||||
program.Id += "_" + channelId;
|
||||
}
|
||||
|
||||
if (programs.Count > 0)
|
||||
{
|
||||
return programs;
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ProgramInfo>();
|
||||
}
|
||||
|
||||
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
|
||||
{
|
||||
return _config.GetLiveTvConfiguration().ListingProviders
|
||||
.Select(i =>
|
||||
{
|
||||
var provider = _listingsProviders.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return provider is null ? null : new Tuple<IListingsProvider, ListingsProviderInfo>(provider, i);
|
||||
})
|
||||
.Where(i => i is not null)
|
||||
.ToList();
|
||||
return await _listingsManager.GetProgramsAsync(channel, startDateUtc, endDateUtc, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
|
||||
|
@ -26,6 +26,7 @@ public static class LiveTvServiceCollectionExtensions
|
||||
services.AddSingleton<IChannelManager, ChannelManager>();
|
||||
services.AddSingleton<IStreamHelper, StreamHelper>();
|
||||
services.AddSingleton<ITunerHostManager, TunerHostManager>();
|
||||
services.AddSingleton<IListingsManager, ListingsManager>();
|
||||
services.AddSingleton<IGuideManager, GuideManager>();
|
||||
|
||||
services.AddSingleton<ILiveTvService, EmbyTV.EmbyTV>();
|
||||
|
470
src/Jellyfin.LiveTv/Listings/ListingsManager.cs
Normal file
470
src/Jellyfin.LiveTv/Listings/ListingsManager.cs
Normal file
@ -0,0 +1,470 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using Jellyfin.LiveTv.EmbyTV;
|
||||
using Jellyfin.LiveTv.Guide;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.LiveTv.Listings;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ListingsManager : IListingsManager
|
||||
{
|
||||
private readonly ILogger<ListingsManager> _logger;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IListingsProvider[] _listingsProviders;
|
||||
|
||||
private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListingsManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
|
||||
/// <param name="config">The <see cref="IConfigurationManager"/>.</param>
|
||||
/// <param name="taskManager">The <see cref="ITaskManager"/>.</param>
|
||||
/// <param name="tunerHostManager">The <see cref="ITunerHostManager"/>.</param>
|
||||
/// <param name="listingsProviders">The <see cref="IListingsProvider"/>.</param>
|
||||
public ListingsManager(
|
||||
ILogger<ListingsManager> logger,
|
||||
IConfigurationManager config,
|
||||
ITaskManager taskManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IEnumerable<IListingsProvider> listingsProviders)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_taskManager = taskManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_listingsProviders = listingsProviders.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
||||
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
|
||||
info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.SerializeToUtf8Bytes(info))!;
|
||||
|
||||
var provider = GetProvider(info.Type);
|
||||
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
|
||||
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var list = config.ListingProviders.ToList();
|
||||
int index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(info);
|
||||
config.ListingProviders = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.ListingProviders[index] = info;
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DeleteListingsProvider(string? id)
|
||||
{
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<List<NameIdPair>> GetLineups(string? providerType, string? providerId, string? country, string? location)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
{
|
||||
return GetProvider(providerType).GetLineups(null, country, location);
|
||||
}
|
||||
|
||||
var info = _config.GetLiveTvConfiguration().ListingProviders
|
||||
.FirstOrDefault(i => string.Equals(i.Id, providerId, StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new ResourceNotFoundException();
|
||||
|
||||
return GetProvider(info.Type).GetLineups(info, country, location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(
|
||||
ChannelInfo channel,
|
||||
DateTime startDateUtc,
|
||||
DateTime endDateUtc,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
|
||||
foreach (var (provider, providerInfo) in GetListingProviders())
|
||||
{
|
||||
if (!IsListingProviderEnabledForTuner(providerInfo, channel.TunerHostId))
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Skipping getting programs for channel {0}-{1} from {2}-{3}, because it's not enabled for this tuner.",
|
||||
channel.Number,
|
||||
channel.Name,
|
||||
provider.Name,
|
||||
providerInfo.ListingsId ?? string.Empty);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug(
|
||||
"Getting programs for channel {0}-{1} from {2}-{3}",
|
||||
channel.Number,
|
||||
channel.Name,
|
||||
provider.Name,
|
||||
providerInfo.ListingsId ?? string.Empty);
|
||||
|
||||
var epgChannels = await GetEpgChannels(provider, providerInfo, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var epgChannel = GetEpgChannelFromTunerChannel(providerInfo.ChannelMappings, channel, epgChannels);
|
||||
if (epgChannel is null)
|
||||
{
|
||||
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Name, providerInfo.ListingsId ?? string.Empty);
|
||||
continue;
|
||||
}
|
||||
|
||||
var programs = (await provider
|
||||
.GetProgramsAsync(providerInfo, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken).ConfigureAwait(false))
|
||||
.ToList();
|
||||
|
||||
// Replace the value that came from the provider with a normalized value
|
||||
foreach (var program in programs)
|
||||
{
|
||||
program.ChannelId = channel.Id;
|
||||
program.Id += "_" + channel.Id;
|
||||
}
|
||||
|
||||
if (programs.Count > 0)
|
||||
{
|
||||
return programs;
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ProgramInfo>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddProviderMetadata(IList<ChannelInfo> channels, bool enableCache, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channels);
|
||||
|
||||
foreach (var (provider, providerInfo) in GetListingProviders())
|
||||
{
|
||||
var enabledChannels = channels
|
||||
.Where(i => IsListingProviderEnabledForTuner(providerInfo, i.TunerHostId))
|
||||
.ToList();
|
||||
|
||||
if (enabledChannels.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await AddMetadata(provider, providerInfo, enabledChannels, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding metadata");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ChannelMappingOptionsDto> GetChannelMappingOptions(string? providerId)
|
||||
{
|
||||
var listingsProviderInfo = _config.GetLiveTvConfiguration().ListingProviders
|
||||
.First(info => string.Equals(providerId, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var provider = GetProvider(listingsProviderInfo.Type);
|
||||
|
||||
var tunerChannels = await GetChannelsForListingsProvider(listingsProviderInfo, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var providerChannels = await provider.GetChannels(listingsProviderInfo, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var mappings = listingsProviderInfo.ChannelMappings;
|
||||
|
||||
return new ChannelMappingOptionsDto
|
||||
{
|
||||
TunerChannels = tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList(),
|
||||
ProviderChannels = providerChannels.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Id
|
||||
}).ToList(),
|
||||
Mappings = mappings,
|
||||
ProviderName = provider.Name
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
|
||||
{
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var listingsProviderInfo = config.ListingProviders
|
||||
.First(info => string.Equals(providerId, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings
|
||||
.Where(pair => !string.Equals(pair.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var list = listingsProviderInfo.ChannelMappings.ToList();
|
||||
list.Add(new NameValuePair
|
||||
{
|
||||
Name = tunerChannelNumber,
|
||||
Value = providerChannelNumber
|
||||
});
|
||||
listingsProviderInfo.ChannelMappings = list.ToArray();
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
var tunerChannels = await GetChannelsForListingsProvider(listingsProviderInfo, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var providerChannels = await GetProvider(listingsProviderInfo.Type).GetChannels(listingsProviderInfo, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var tunerChannelMappings = tunerChannels
|
||||
.Select(i => GetTunerChannelMapping(i, listingsProviderInfo.ChannelMappings, providerChannels)).ToList();
|
||||
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
|
||||
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
|
||||
=> _config.GetLiveTvConfiguration().ListingProviders
|
||||
.Select(i =>
|
||||
{
|
||||
var provider = _listingsProviders
|
||||
.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return provider is null ? null : new Tuple<IListingsProvider, ListingsProviderInfo>(provider, i);
|
||||
})
|
||||
.Where(i => i is not null)
|
||||
.ToList()!; // Already filtered out null
|
||||
|
||||
private async Task AddMetadata(
|
||||
IListingsProvider provider,
|
||||
ListingsProviderInfo info,
|
||||
IEnumerable<ChannelInfo> tunerChannels,
|
||||
bool enableCache,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var tunerChannel in tunerChannels)
|
||||
{
|
||||
var epgChannel = GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
|
||||
if (epgChannel is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
|
||||
{
|
||||
tunerChannel.ImageUrl = epgChannel.ImageUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId)
|
||||
{
|
||||
if (info.EnableAllTuners)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tunerHostId);
|
||||
|
||||
return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetMappedChannel(string channelId, NameValuePair[] mappings)
|
||||
{
|
||||
foreach (NameValuePair mapping in mappings)
|
||||
{
|
||||
if (string.Equals(mapping.Name, channelId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return mapping.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return channelId;
|
||||
}
|
||||
|
||||
private async Task<EpgChannelData> GetEpgChannels(
|
||||
IListingsProvider provider,
|
||||
ListingsProviderInfo info,
|
||||
bool enableCache,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (enableCache && _epgChannels.TryGetValue(info.Id, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var channels = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var channel in channels)
|
||||
{
|
||||
_logger.LogInformation("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id);
|
||||
}
|
||||
|
||||
result = new EpgChannelData(channels);
|
||||
_epgChannels.AddOrUpdate(info.Id, result, (_, _) => result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ChannelInfo? GetEpgChannelFromTunerChannel(
|
||||
NameValuePair[] mappings,
|
||||
ChannelInfo tunerChannel,
|
||||
EpgChannelData epgChannelData)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
|
||||
{
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannel.Id, mappings);
|
||||
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||
{
|
||||
mappedTunerChannelId = tunerChannel.Id;
|
||||
}
|
||||
|
||||
var channel = epgChannelData.GetChannelById(mappedTunerChannelId);
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
||||
{
|
||||
var tunerChannelId = tunerChannel.TunerChannelId;
|
||||
if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
|
||||
}
|
||||
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings);
|
||||
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||
{
|
||||
mappedTunerChannelId = tunerChannelId;
|
||||
}
|
||||
|
||||
var channel = epgChannelData.GetChannelById(mappedTunerChannelId);
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
{
|
||||
var tunerChannelNumber = GetMappedChannel(tunerChannel.Number, mappings);
|
||||
if (string.IsNullOrWhiteSpace(tunerChannelNumber))
|
||||
{
|
||||
tunerChannelNumber = tunerChannel.Number;
|
||||
}
|
||||
|
||||
var channel = epgChannelData.GetChannelByNumber(tunerChannelNumber);
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Name))
|
||||
{
|
||||
var normalizedName = EpgChannelData.NormalizeName(tunerChannel.Name);
|
||||
|
||||
var channel = epgChannelData.GetChannelByName(normalizedName);
|
||||
if (channel is not null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, IList<ChannelInfo> providerChannels)
|
||||
{
|
||||
var result = new TunerChannelMapping
|
||||
{
|
||||
Name = tunerChannel.Name,
|
||||
Id = tunerChannel.Id
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
{
|
||||
result.Name = tunerChannel.Number + " " + result.Name;
|
||||
}
|
||||
|
||||
var providerChannel = GetEpgChannelFromTunerChannel(mappings, tunerChannel, new EpgChannelData(providerChannels));
|
||||
if (providerChannel is not null)
|
||||
{
|
||||
result.ProviderChannelName = providerChannel.Name;
|
||||
result.ProviderChannelId = providerChannel.Id;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var channels = new List<ChannelInfo>();
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tunerChannels = await hostInstance.GetChannels(false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
channels.AddRange(tunerChannels.Where(channel => IsListingProviderEnabledForTuner(info, channel.TunerHostId)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting channels");
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
private IListingsProvider GetProvider(string? providerType)
|
||||
=> _listingsProviders.FirstOrDefault(i => string.Equals(providerType, i.Type, StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new ResourceNotFoundException($"Couldn't find provider of type {providerType}");
|
||||
}
|
@ -6,14 +6,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using Jellyfin.LiveTv.Guide;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@ -27,7 +25,6 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.LiveTv
|
||||
@ -43,12 +40,10 @@ namespace Jellyfin.LiveTv
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly LiveTvDtoService _tvDtoService;
|
||||
private readonly ILiveTvService[] _services;
|
||||
private readonly IListingsProvider[] _listingProviders;
|
||||
|
||||
public LiveTvManager(
|
||||
IServerConfigurationManager config,
|
||||
@ -57,25 +52,21 @@ namespace Jellyfin.LiveTv
|
||||
IDtoService dtoService,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
ITaskManager taskManager,
|
||||
ILocalizationManager localization,
|
||||
IChannelManager channelManager,
|
||||
LiveTvDtoService liveTvDtoService,
|
||||
IEnumerable<ILiveTvService> services,
|
||||
IEnumerable<IListingsProvider> listingProviders)
|
||||
IEnumerable<ILiveTvService> services)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
_taskManager = taskManager;
|
||||
_localization = localization;
|
||||
_dtoService = dtoService;
|
||||
_userDataManager = userDataManager;
|
||||
_channelManager = channelManager;
|
||||
_tvDtoService = liveTvDtoService;
|
||||
_services = services.ToArray();
|
||||
_listingProviders = listingProviders.ToArray();
|
||||
|
||||
var defaultService = _services.OfType<EmbyTV.EmbyTV>().First();
|
||||
defaultService.TimerCreated += OnEmbyTvTimerCreated;
|
||||
@ -96,8 +87,6 @@ namespace Jellyfin.LiveTv
|
||||
/// <value>The services.</value>
|
||||
public IReadOnlyList<ILiveTvService> Services => _services;
|
||||
|
||||
public IReadOnlyList<IListingsProvider> ListingProviders => _listingProviders;
|
||||
|
||||
public string GetEmbyTvActiveRecordingPath(string id)
|
||||
{
|
||||
return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id);
|
||||
@ -1465,161 +1454,6 @@ namespace Jellyfin.LiveTv
|
||||
return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
|
||||
}
|
||||
|
||||
public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
|
||||
{
|
||||
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
||||
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
|
||||
info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
|
||||
|
||||
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Couldn't find provider of type: '{0}'",
|
||||
info.Type));
|
||||
}
|
||||
|
||||
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
|
||||
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var list = config.ListingProviders.ToList();
|
||||
int index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(info);
|
||||
config.ListingProviders = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.ListingProviders[index] = info;
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public void DeleteListingsProvider(string id)
|
||||
{
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
}
|
||||
|
||||
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
|
||||
{
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var list = listingsProviderInfo.ChannelMappings.ToList();
|
||||
list.Add(new NameValuePair
|
||||
{
|
||||
Name = tunerChannelNumber,
|
||||
Value = providerChannelNumber
|
||||
});
|
||||
listingsProviderInfo.ChannelMappings = list.ToArray();
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
var tunerChannels = await GetChannelsForListingsProvider(providerId, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var providerChannels = await GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var mappings = listingsProviderInfo.ChannelMappings;
|
||||
|
||||
var tunerChannelMappings =
|
||||
tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
|
||||
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
|
||||
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels)
|
||||
{
|
||||
var result = new TunerChannelMapping
|
||||
{
|
||||
Name = tunerChannel.Name,
|
||||
Id = tunerChannel.Id
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
{
|
||||
result.Name = tunerChannel.Number + " " + result.Name;
|
||||
}
|
||||
|
||||
var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, providerChannels);
|
||||
|
||||
if (providerChannel is not null)
|
||||
{
|
||||
result.ProviderChannelName = providerChannel.Name;
|
||||
result.ProviderChannelId = providerChannel.Id;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location)
|
||||
{
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
{
|
||||
var provider = _listingProviders.FirstOrDefault(i => string.Equals(providerType, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
return provider.GetLineups(null, country, location);
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = config.ListingProviders.FirstOrDefault(i => string.Equals(i.Id, providerId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
return provider.GetLineups(info, country, location);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
return EmbyTV.EmbyTV.Current.GetChannelsForListingsProvider(info, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase));
|
||||
return provider.GetChannels(info, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<BaseItem[]> GetRecordingFoldersAsync(User user)
|
||||
=> GetRecordingFoldersAsync(user, false);
|
||||
|
Loading…
Reference in New Issue
Block a user