diff options
| author | Patrick Barron <barronpm@gmail.com> | 2024-01-12 20:45:05 -0500 |
|---|---|---|
| committer | Patrick Barron <barronpm@gmail.com> | 2024-01-12 21:17:09 -0500 |
| commit | 9c2c066e6f62fb713d5bad0fcf5a0b3dcf58e6e1 (patch) | |
| tree | d7f05a2503c72696be6754d9da86ec9e36cff388 | |
| parent | 449365182cb710e5d0d18b1d599a53f76b814dd8 (diff) | |
Add ITunerHostManager service
| -rw-r--r-- | Emby.Server.Implementations/ApplicationHost.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/LiveTvController.cs | 19 | ||||
| -rw-r--r-- | MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 15 | ||||
| -rw-r--r-- | MediaBrowser.Controller/LiveTv/ITunerHostManager.cs | 47 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs | 89 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs | 6 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/LiveTvManager.cs | 76 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs | 181 |
8 files changed, 258 insertions, 177 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 48d5d8c6a..5870fed76 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -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>()); } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 550283623..1a2a3caae 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -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. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 4206159e7..26f9fe42d 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -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. @@ -254,14 +253,6 @@ namespace MediaBrowser.Controller.LiveTv 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> /// <param name="info">The information.</param> @@ -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); diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs new file mode 100644 index 000000000..7e4caaf13 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs @@ -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); +} diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs index 532e1c897..625451fa3 100644 --- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs +++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs @@ -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>(); - } - } } } diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs index 5865e88af..5490547ec 100644 --- a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs +++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs @@ -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>(); } } diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs index 0b3d35731..71822f376 100644 --- a/src/Jellyfin.LiveTv/LiveTvManager.cs +++ b/src/Jellyfin.LiveTv/LiveTvManager.cs @@ -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 diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs new file mode 100644 index 000000000..04eb8293a --- /dev/null +++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs @@ -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>(); + } + } +} |
