|
@@ -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>();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|