mirror of https://github.com/jellyfin/jellyfin.git
Add ITunerHostManager service
This commit is contained in:
parent
449365182c
commit
9c2c066e6f
|
@ -695,7 +695,7 @@ namespace Emby.Server.Implementations
|
|||
GetExports<IMetadataSaver>(),
|
||||
GetExports<IExternalId>());
|
||||
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<IListingsProvider>());
|
||||
|
||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
|
@ -43,6 +42,7 @@ namespace Jellyfin.Api.Controllers;
|
|||
public class LiveTvController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
@ -55,6 +55,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
|
||||
/// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> 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>
|
||||
|
@ -64,6 +65,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
/// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
|
||||
public LiveTvController(
|
||||
ILiveTvManager liveTvManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IUserManager userManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager,
|
||||
|
@ -73,6 +75,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
ITranscodeManager transcodeManager)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_userManager = userManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
|
@ -951,9 +954,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
|
||||
{
|
||||
return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||
}
|
||||
=> await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a tuner host.
|
||||
|
@ -1130,10 +1131,8 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
[HttpGet("TunerHosts/Types")]
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
|
||||
{
|
||||
return _liveTvManager.GetTunerHostTypes();
|
||||
}
|
||||
public IEnumerable<NameIdPair> GetTunerHostTypes()
|
||||
=> _tunerHostManager.GetTunerHostTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Discover tuners.
|
||||
|
@ -1146,9 +1145,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
||||
{
|
||||
return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
=> await _tunerHostManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a live tv recording stream.
|
||||
|
|
|
@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// Adds the parts.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
/// <param name="tunerHosts">The tuner hosts.</param>
|
||||
/// <param name="listingProviders">The listing providers.</param>
|
||||
void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders);
|
||||
void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timer.
|
||||
|
@ -253,14 +252,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 tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">Turner host to save.</param>
|
||||
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
|
||||
/// <returns>Tuner host information wrapped in a task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
|
@ -298,10 +289,6 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
|
||||
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
|
||||
|
||||
List<NameIdPair> GetTunerHostTypes();
|
||||
|
||||
Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
|
||||
|
||||
string GetEmbyTvActiveRecordingPath(string id);
|
||||
|
||||
ActiveRecordingInfo GetActiveRecordingInfo(string path);
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
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 the <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
public interface ITunerHostManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the available <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
IReadOnlyList<ITunerHost> TunerHosts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="NameIdPair"/>s for the available <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="NameIdPair"/>s.</returns>
|
||||
IEnumerable<NameIdPair> GetTunerHostTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">Turner host to save.</param>
|
||||
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
|
||||
/// <returns>Tuner host information wrapped in a task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Discovers the available tuners.
|
||||
/// </summary>
|
||||
/// <param name="newDevicesOnly">A value indicating whether to only return new devices.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>The <see cref="TunerHostInfo"/>s.</returns>
|
||||
Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Scans for tuner devices that have changed URLs.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>A task that represents the scanning operation.</returns>
|
||||
Task ScanForTunerDeviceChanges(CancellationToken cancellationToken);
|
||||
}
|
|
@ -44,8 +44,6 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
{
|
||||
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
private const int TunerDiscoveryDurationMs = 3000;
|
||||
|
||||
private readonly ILogger<EmbyTV> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
@ -54,6 +52,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
private readonly TimerManager _timerProvider;
|
||||
|
||||
private readonly LiveTvManager _liveTvManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly ILibraryMonitor _libraryMonitor;
|
||||
|
@ -80,6 +79,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
IHttpClientFactory httpClientFactory,
|
||||
IServerConfigurationManager config,
|
||||
ILiveTvManager liveTvManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
|
@ -97,6 +97,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
_providerManager = providerManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_streamHelper = streamHelper;
|
||||
|
||||
|
@ -310,7 +311,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -510,7 +511,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -966,7 +967,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
return result;
|
||||
}
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -998,7 +999,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
throw new ArgumentNullException(nameof(channelId));
|
||||
}
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -2537,81 +2538,5 @@ namespace Jellyfin.LiveTv.EmbyTV
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<TunerHostInfo>();
|
||||
|
||||
var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
|
||||
.Select(i => i.DeviceId)
|
||||
.ToList();
|
||||
|
||||
foreach (var host in _liveTvManager.TunerHosts)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (newDevicesOnly)
|
||||
{
|
||||
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
list.AddRange(discoveredDevices);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var host in _liveTvManager.TunerHosts)
|
||||
{
|
||||
await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url);
|
||||
|
||||
configuredDevice.Url = device.Url;
|
||||
await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
_logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url);
|
||||
}
|
||||
|
||||
return discoveredDevices;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error discovering tuner devices");
|
||||
|
||||
return new List<TunerHostInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Jellyfin.LiveTv.Channels;
|
||||
using Jellyfin.LiveTv.TunerHosts;
|
||||
using Jellyfin.LiveTv.TunerHosts.HdHomerun;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
@ -21,5 +23,9 @@ public static class LiveTvServiceCollectionExtensions
|
|||
services.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||
services.AddSingleton<IChannelManager, ChannelManager>();
|
||||
services.AddSingleton<IStreamHelper, StreamHelper>();
|
||||
services.AddSingleton<ITunerHostManager, TunerHostManager>();
|
||||
|
||||
services.AddSingleton<ITunerHost, HdHomerunHost>();
|
||||
services.AddSingleton<ITunerHost, M3UTunerHost>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ namespace Jellyfin.LiveTv
|
|||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly LiveTvDtoService _tvDtoService;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
|
||||
private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
|
||||
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
|
||||
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
|
||||
|
||||
public LiveTvManager(
|
||||
|
@ -74,7 +74,8 @@ namespace Jellyfin.LiveTv
|
|||
ILocalizationManager localization,
|
||||
IFileSystem fileSystem,
|
||||
IChannelManager channelManager,
|
||||
LiveTvDtoService liveTvDtoService)
|
||||
LiveTvDtoService liveTvDtoService,
|
||||
ITunerHostManager tunerHostManager)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
|
@ -88,6 +89,7 @@ namespace Jellyfin.LiveTv
|
|||
_userDataManager = userDataManager;
|
||||
_channelManager = channelManager;
|
||||
_tvDtoService = liveTvDtoService;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
}
|
||||
|
||||
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
|
||||
|
@ -104,8 +106,6 @@ namespace Jellyfin.LiveTv
|
|||
/// <value>The services.</value>
|
||||
public IReadOnlyList<ILiveTvService> Services => _services;
|
||||
|
||||
public IReadOnlyList<ITunerHost> TunerHosts => _tunerHosts;
|
||||
|
||||
public IReadOnlyList<IListingsProvider> ListingProviders => _listingProviders;
|
||||
|
||||
public string GetEmbyTvActiveRecordingPath(string id)
|
||||
|
@ -113,16 +113,10 @@ namespace Jellyfin.LiveTv
|
|||
return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the parts.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
/// <param name="tunerHosts">The tuner hosts.</param>
|
||||
/// <param name="listingProviders">The listing providers.</param>
|
||||
public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders)
|
||||
{
|
||||
_services = services.ToArray();
|
||||
_tunerHosts = tunerHosts.Where(i => i.IsSupported).ToArray();
|
||||
|
||||
_listingProviders = listingProviders.ToArray();
|
||||
|
||||
|
@ -154,20 +148,6 @@ namespace Jellyfin.LiveTv
|
|||
}));
|
||||
}
|
||||
|
||||
public List<NameIdPair> GetTunerHostTypes()
|
||||
{
|
||||
return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Type
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
|
||||
{
|
||||
return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = query.UserId.Equals(default)
|
||||
|
@ -1029,7 +1009,7 @@ namespace Jellyfin.LiveTv
|
|||
{
|
||||
await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false);
|
||||
|
||||
await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
|
||||
await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var numComplete = 0;
|
||||
double progressPerService = _services.Length == 0
|
||||
|
@ -2166,48 +2146,6 @@ namespace Jellyfin.LiveTv
|
|||
return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
|
||||
}
|
||||
|
||||
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
||||
{
|
||||
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
|
||||
|
||||
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (provider is IConfigurableTunerHost configurable)
|
||||
{
|
||||
await configurable.Validate(info).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var list = config.TunerHosts.ToList();
|
||||
var 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.TunerHosts = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.TunerHosts[index] = info;
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
if (dataSourceChanged)
|
||||
{
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
|
||||
{
|
||||
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
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.Extensions;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
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.TunerHosts;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class TunerHostManager : ITunerHostManager
|
||||
{
|
||||
private const int TunerDiscoveryDurationMs = 3000;
|
||||
|
||||
private readonly ILogger<TunerHostManager> _logger;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly ITunerHost[] _tunerHosts;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TunerHostManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger{T}"/>.</param>
|
||||
/// <param name="config">The <see cref="IConfigurationManager"/>.</param>
|
||||
/// <param name="taskManager">The <see cref="ITaskManager"/>.</param>
|
||||
/// <param name="tunerHosts">The <see cref="IEnumerable{T}"/>.</param>
|
||||
public TunerHostManager(
|
||||
ILogger<TunerHostManager> logger,
|
||||
IConfigurationManager config,
|
||||
ITaskManager taskManager,
|
||||
IEnumerable<ITunerHost> tunerHosts)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_taskManager = taskManager;
|
||||
_tunerHosts = tunerHosts.Where(t => t.IsSupported).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ITunerHost> TunerHosts => _tunerHosts;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NameIdPair> GetTunerHostTypes()
|
||||
=> _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Type
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
||||
{
|
||||
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.SerializeToUtf8Bytes(info))!;
|
||||
|
||||
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (provider is IConfigurableTunerHost configurable)
|
||||
{
|
||||
await configurable.Validate(info).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var list = config.TunerHosts.ToList();
|
||||
var 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.TunerHosts = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.TunerHosts[index] = info;
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
if (dataSourceChanged)
|
||||
{
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<TunerHostInfo>();
|
||||
|
||||
var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
|
||||
.Select(i => i.DeviceId)
|
||||
.ToList();
|
||||
|
||||
foreach (var host in _tunerHosts)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (newDevicesOnly)
|
||||
{
|
||||
discoveredDevices = discoveredDevices
|
||||
.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
list.AddRange(discoveredDevices);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var host in _tunerHosts)
|
||||
{
|
||||
await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url);
|
||||
|
||||
configuredDevice.Url = device.Url;
|
||||
await SaveTunerHost(configuredDevice).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
_logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url);
|
||||
}
|
||||
|
||||
return discoveredDevices;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error discovering tuner devices");
|
||||
|
||||
return new List<TunerHostInfo>();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue