Used a UUID in HDHomeRun config to allow multiple instances on a same network (#1810)
* Used a UUID in HDHomeRun config to allow multiple instances on a same network * tweak some async calls * try to fix line endings --------- Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.cs text diff=csharp
|
||||||
|
*.cshtml text diff=html
|
||||||
|
*.csx text diff=csharp
|
||||||
|
*.sln text eol=crlf
|
||||||
|
*.csproj text eol=crlf
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.HDHR;
|
||||||
|
|
||||||
|
public record GetHDHRUUID : IRequest<Guid>;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.HDHR;
|
||||||
|
|
||||||
|
public class GetHDHRUUIDHandler : IRequestHandler<GetHDHRUUID, Guid>
|
||||||
|
{
|
||||||
|
private readonly IConfigElementRepository _configElementRepository;
|
||||||
|
|
||||||
|
public GetHDHRUUIDHandler(IConfigElementRepository configElementRepository) =>
|
||||||
|
_configElementRepository = configElementRepository;
|
||||||
|
|
||||||
|
public async Task<Guid> Handle(GetHDHRUUID request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Option<Guid> maybeGuid = await _configElementRepository.GetValue<Guid>(ConfigElementKey.HDHRUUID);
|
||||||
|
return await maybeGuid.IfNoneAsync(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
Guid guid = Guid.NewGuid();
|
||||||
|
await _configElementRepository.Upsert(ConfigElementKey.HDHRUUID, guid);
|
||||||
|
return guid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace ErsatzTV.Core.Domain;
|
namespace ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
public class ConfigElementKey
|
public class ConfigElementKey
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,7 @@ public class ConfigElementKey
|
|||||||
public static ConfigElementKey FFmpegHlsDirectOutputFormat => new("ffmpeg.hls_direct.output_format");
|
public static ConfigElementKey FFmpegHlsDirectOutputFormat => new("ffmpeg.hls_direct.output_format");
|
||||||
public static ConfigElementKey SearchIndexVersion => new("search_index.version");
|
public static ConfigElementKey SearchIndexVersion => new("search_index.version");
|
||||||
public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count");
|
public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count");
|
||||||
|
public static ConfigElementKey HDHRUUID => new("hdhr.uuid");
|
||||||
public static ConfigElementKey ChannelsPageSize => new("pages.channels.page_size");
|
public static ConfigElementKey ChannelsPageSize => new("pages.channels.page_size");
|
||||||
public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size");
|
public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size");
|
||||||
public static ConfigElementKey MultiCollectionsPageSize => new("pages.multi_collections.page_size");
|
public static ConfigElementKey MultiCollectionsPageSize => new("pages.multi_collections.page_size");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ErsatzTV.Core.Hdhr;
|
namespace ErsatzTV.Core.Hdhr;
|
||||||
|
|
||||||
public record DeviceXml(string Scheme, string Host)
|
public record DeviceXml(string Scheme, string Host, Guid uuid)
|
||||||
{
|
{
|
||||||
public string ToXml() =>
|
public string ToXml() =>
|
||||||
@$"<root xmlns=""urn:schemas-upnp-org:device-1-0"">
|
@$"<root xmlns=""urn:schemas-upnp-org:device-1-0"">
|
||||||
@@ -15,8 +15,8 @@ public record DeviceXml(string Scheme, string Host)
|
|||||||
<manufacturer>Silicondust</manufacturer>
|
<manufacturer>Silicondust</manufacturer>
|
||||||
<modelName>HDTC-2US</modelName>
|
<modelName>HDTC-2US</modelName>
|
||||||
<modelNumber>HDTC-2US</modelNumber>
|
<modelNumber>HDTC-2US</modelNumber>
|
||||||
<serialNumber/>
|
<serialNumber>{uuid}</serialNumber>
|
||||||
<UDN>uuid:2020-03-S3LA-BG3LIA:2</UDN>
|
<UDN>uuid:{uuid}</UDN>
|
||||||
</device>
|
</device>
|
||||||
</root>";
|
</root>";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Hdhr;
|
namespace ErsatzTV.Core.Hdhr;
|
||||||
|
|
||||||
@@ -8,21 +8,23 @@ public class Discover
|
|||||||
{
|
{
|
||||||
private readonly string _host;
|
private readonly string _host;
|
||||||
private readonly string _scheme;
|
private readonly string _scheme;
|
||||||
|
private readonly Guid _UUID;
|
||||||
|
|
||||||
public Discover(string scheme, string host, int tunerCount)
|
public Discover(string scheme, string host, int tunerCount, Guid uuid)
|
||||||
{
|
{
|
||||||
_scheme = scheme;
|
_scheme = scheme;
|
||||||
_host = host;
|
_host = host;
|
||||||
TunerCount = tunerCount;
|
TunerCount = tunerCount;
|
||||||
|
_UUID = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DeviceAuth => "";
|
public string DeviceAuth => "";
|
||||||
public string DeviceID => "ErsatzTV";
|
public string DeviceID => _UUID.ToString();
|
||||||
public string FirmwareName => "hdhomeruntc_atsc";
|
public string FirmwareName => "hdhomeruntc_atsc";
|
||||||
public string FirmwareVersion => "20190621";
|
public string FirmwareVersion => "20190621";
|
||||||
public string FriendlyName => "ErsatzTV";
|
public string FriendlyName => "ErsatzTV";
|
||||||
public string LineupURL => $"{_scheme}://{_host}/lineup.json";
|
public string LineupURL => $"{_scheme}://{_host}/lineup.json";
|
||||||
public string Manufacturer => "ErsatzTV - Silicondust";
|
public string Manufacturer => "ErsatzTV";
|
||||||
public string ManufacturerURL => "https://github.com/ErsatzTV/ErsatzTV";
|
public string ManufacturerURL => "https://github.com/ErsatzTV/ErsatzTV";
|
||||||
public string ModelNumber => "HDTC-2US";
|
public string ModelNumber => "HDTC-2US";
|
||||||
public int TunerCount { get; }
|
public int TunerCount { get; }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Infrastructure.Extensions;
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
@@ -54,6 +54,11 @@ public class ConfigElementRepository : IConfigElementRepository
|
|||||||
GetConfigElement(key).MapT(
|
GetConfigElement(key).MapT(
|
||||||
ce =>
|
ce =>
|
||||||
{
|
{
|
||||||
|
if (typeof(T).Name == "Guid")
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(Guid.Parse(ce.Value), typeof(T), CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof(T).IsEnum)
|
if (typeof(T).IsEnum)
|
||||||
{
|
{
|
||||||
return (T)Enum.Parse(typeof(T), ce.Value);
|
return (T)Enum.Parse(typeof(T), ce.Value);
|
||||||
|
|||||||
@@ -9,21 +9,23 @@ namespace ErsatzTV.Controllers;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public class HdhrController : ControllerBase
|
public class HdhrController(IMediator mediator) : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IMediator _mediator;
|
|
||||||
|
|
||||||
public HdhrController(IMediator mediator) => _mediator = mediator;
|
|
||||||
|
|
||||||
[HttpGet("device.xml")]
|
[HttpGet("device.xml")]
|
||||||
public IActionResult DeviceXml() =>
|
public async Task<IActionResult> DeviceXml()
|
||||||
new OkObjectResult(new DeviceXml(Request.Scheme, Request.Host.ToString()));
|
{
|
||||||
|
Guid uuid = await mediator.Send(new GetHDHRUUID());
|
||||||
|
return new OkObjectResult(new DeviceXml(Request.Scheme, Request.Host.ToString(), uuid));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("discover.json")]
|
[HttpGet("discover.json")]
|
||||||
[ResponseCache(NoStore = true)]
|
[ResponseCache(NoStore = true)]
|
||||||
public Task<IActionResult> Discover() =>
|
public async Task<IActionResult> Discover()
|
||||||
_mediator.Send(new GetHDHRTunerCount()).Map<int, IActionResult>(
|
{
|
||||||
tunerCount => new OkObjectResult(new Discover(Request.Scheme, Request.Host.ToString(), tunerCount)));
|
Guid uuid = await mediator.Send(new GetHDHRUUID());
|
||||||
|
int tunerCount = await mediator.Send(new GetHDHRTunerCount());
|
||||||
|
return new OkObjectResult(new Discover(Request.Scheme, Request.Host.ToString(), tunerCount, uuid));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("lineup_status.json")]
|
[HttpGet("lineup_status.json")]
|
||||||
public IActionResult LineupStatus() =>
|
public IActionResult LineupStatus() =>
|
||||||
@@ -31,5 +33,5 @@ public class HdhrController : ControllerBase
|
|||||||
|
|
||||||
[HttpGet("lineup.json")]
|
[HttpGet("lineup.json")]
|
||||||
public Task<IActionResult> Lineup() =>
|
public Task<IActionResult> Lineup() =>
|
||||||
_mediator.Send(new GetChannelLineup(Request.Scheme, Request.Host.ToString())).ToActionResult();
|
mediator.Send(new GetChannelLineup(Request.Scheme, Request.Host.ToString())).ToActionResult();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/settings"
|
@page "/settings"
|
||||||
@using ErsatzTV.Application.Configuration
|
@using ErsatzTV.Application.Configuration
|
||||||
@using ErsatzTV.Application.FFmpegProfiles
|
@using ErsatzTV.Application.FFmpegProfiles
|
||||||
@using ErsatzTV.Application.Filler
|
@using ErsatzTV.Application.Filler
|
||||||
@@ -191,7 +191,10 @@
|
|||||||
<CardHeaderContent>
|
<CardHeaderContent>
|
||||||
<MudText Typo="Typo.h6">HDHomeRun Settings</MudText>
|
<MudText Typo="Typo.h6">HDHomeRun Settings</MudText>
|
||||||
</CardHeaderContent>
|
</CardHeaderContent>
|
||||||
</MudCardHeader>
|
</MudCardHeader>
|
||||||
|
<MudCardContent>
|
||||||
|
<MudTextField T="Guid" Label="UUID" @bind-Value="_uuid" ReadOnly="true" Disabled="true" />
|
||||||
|
</MudCardContent>
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudForm @bind-IsValid="@_hdhrSuccess">
|
<MudForm @bind-IsValid="@_hdhrSuccess">
|
||||||
<MudTextField T="int" Label="Tuner Count" @bind-Value="_tunerCount" Validation="@(new Func<int, string>(ValidateTunerCount))" Required="true" RequiredError="Tuner count is required!"/>
|
<MudTextField T="int" Label="Tuner Count" @bind-Value="_tunerCount" Validation="@(new Func<int, string>(ValidateTunerCount))" Required="true" RequiredError="Tuner count is required!"/>
|
||||||
@@ -325,42 +328,44 @@
|
|||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly CancellationTokenSource _cts = new();
|
private readonly CancellationTokenSource _cts = new();
|
||||||
|
|
||||||
private bool _success;
|
private bool _success;
|
||||||
private bool _hdhrSuccess;
|
private bool _hdhrSuccess;
|
||||||
private bool _scannerSuccess;
|
private bool _scannerSuccess;
|
||||||
private bool _playoutSuccess;
|
private bool _playoutSuccess;
|
||||||
private List<FFmpegProfileViewModel> _ffmpegProfiles = new();
|
private List<FFmpegProfileViewModel> _ffmpegProfiles = new();
|
||||||
private FFmpegSettingsViewModel _ffmpegSettings = new();
|
private FFmpegSettingsViewModel _ffmpegSettings = new();
|
||||||
private List<LanguageCodeViewModel> _availableCultures = new();
|
private List<LanguageCodeViewModel> _availableCultures = new();
|
||||||
private List<WatermarkViewModel> _watermarks = new();
|
private List<WatermarkViewModel> _watermarks = new();
|
||||||
private List<FillerPresetViewModel> _fillerPresets = new();
|
private List<FillerPresetViewModel> _fillerPresets = new();
|
||||||
private List<ResolutionViewModel> _customResolutions = new();
|
private List<ResolutionViewModel> _customResolutions = new();
|
||||||
private int _tunerCount;
|
private int _tunerCount;
|
||||||
private int _libraryRefreshInterval;
|
private int _libraryRefreshInterval;
|
||||||
private PlayoutSettingsViewModel _playoutSettings = new();
|
private PlayoutSettingsViewModel _playoutSettings = new();
|
||||||
private GeneralSettingsViewModel _generalSettings = new();
|
private GeneralSettingsViewModel _generalSettings = new();
|
||||||
private XmltvSettingsViewModel _xmltvSettings = new();
|
private XmltvSettingsViewModel _xmltvSettings = new();
|
||||||
|
private Guid _uuid;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
await LoadFFmpegProfilesAsync();
|
await LoadFFmpegProfilesAsync();
|
||||||
|
|
||||||
_ffmpegSettings = await Mediator.Send(new GetFFmpegSettings(), _cts.Token);
|
_ffmpegSettings = await Mediator.Send(new GetFFmpegSettings(), _cts.Token);
|
||||||
_success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath);
|
_success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath);
|
||||||
_availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token);
|
_availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token);
|
||||||
_watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token);
|
_watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token);
|
||||||
_fillerPresets = await Mediator.Send(new GetAllFillerPresets(), _cts.Token)
|
_fillerPresets = await Mediator.Send(new GetAllFillerPresets(), _cts.Token)
|
||||||
.Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList());
|
.Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList());
|
||||||
_tunerCount = await Mediator.Send(new GetHDHRTunerCount(), _cts.Token);
|
_tunerCount = await Mediator.Send(new GetHDHRTunerCount(), _cts.Token);
|
||||||
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount));
|
_uuid = await Mediator.Send(new GetHDHRUUID(), _cts.Token);
|
||||||
|
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount));
|
||||||
_libraryRefreshInterval = await Mediator.Send(new GetLibraryRefreshInterval(), _cts.Token);
|
_libraryRefreshInterval = await Mediator.Send(new GetLibraryRefreshInterval(), _cts.Token);
|
||||||
_scannerSuccess = _libraryRefreshInterval is >= 0 and < 1_000_000;
|
_scannerSuccess = _libraryRefreshInterval is >= 0 and < 1_000_000;
|
||||||
_playoutSettings = await Mediator.Send(new GetPlayoutSettings(), _cts.Token);
|
_playoutSettings = await Mediator.Send(new GetPlayoutSettings(), _cts.Token);
|
||||||
|
|||||||
Reference in New Issue
Block a user