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