@page "/channels/{Id:int?}" @page "/channels/add" @using static LanguageExt.Prelude @using ErsatzTV.Application.FFmpegProfiles @using ErsatzTV.Application.Filler @using ErsatzTV.Application.Images @using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.Watermarks @using System.Globalization @using ErsatzTV.Core.Domain.Filler @using ErsatzTV.Application.Channels @implements IDisposable @inject NavigationManager _navigationManager @inject ILogger _logger @inject ISnackbar _snackbar @inject IMediator _mediator
@(IsEdit ? "Edit Channel" : "Add Channel") MPEG-TS MPEG-TS (Legacy) HLS Direct HLS Segmenter @foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) { @profile.Name } (none) @foreach (CultureInfo culture in _availableCultures) { @culture.EnglishName } Upload Logo (none) @foreach (WatermarkViewModel watermark in _watermarks) { @watermark.Name } (none) @foreach (FillerPresetViewModel fillerPreset in _fillerPresets) { @fillerPreset.Name } @(IsEdit ? "Save Changes" : "Add Channel")
@code { private readonly CancellationTokenSource _cts = new(); [Parameter] public int? Id { get; set; } private readonly ChannelEditViewModel _model = new(); private EditContext _editContext; private ValidationMessageStore _messageStore; private List _ffmpegProfiles; private List _availableCultures; private List _watermarks; private List _fillerPresets; public void Dispose() { _cts.Cancel(); _cts.Dispose(); } protected override async Task OnParametersSetAsync() { await LoadFFmpegProfiles(_cts.Token); _availableCultures = await _mediator.Send(new GetAllLanguageCodes(), _cts.Token); await LoadWatermarks(_cts.Token); await LoadFillerPresets(_cts.Token); if (Id.HasValue) { Option maybeChannel = await _mediator.Send(new GetChannelById(Id.Value), _cts.Token); maybeChannel.Match( channelViewModel => { _model.Id = channelViewModel.Id; _model.Name = channelViewModel.Name; _model.Group = channelViewModel.Group; _model.Categories = channelViewModel.Categories; _model.Number = channelViewModel.Number; _model.FFmpegProfileId = channelViewModel.FFmpegProfileId; _model.Logo = channelViewModel.Logo; _model.StreamingMode = channelViewModel.StreamingMode; _model.PreferredLanguageCode = channelViewModel.PreferredLanguageCode; _model.WatermarkId = channelViewModel.WatermarkId; _model.FallbackFillerId = channelViewModel.FallbackFillerId; }, () => _navigationManager.NavigateTo("404")); } else { FFmpegSettingsViewModel ffmpegSettings = await _mediator.Send(new GetFFmpegSettings(), _cts.Token); // TODO: command for new channel IEnumerable channelNumbers = await _mediator.Send(new GetAllChannels(), _cts.Token) .Map(list => list.Map(c => int.TryParse(c.Number.Split(".").Head(), out int result) ? result : 0)); int maxNumber = Optional(channelNumbers).Flatten().DefaultIfEmpty(0).Max(); _model.Number = (maxNumber + 1).ToString(); _model.Name = "New Channel"; _model.Group = "ErsatzTV"; _model.FFmpegProfileId = ffmpegSettings.DefaultFFmpegProfileId; _model.StreamingMode = StreamingMode.TransportStreamHybrid; } } protected override void OnInitialized() { _editContext = new EditContext(_model); _messageStore = new ValidationMessageStore(_editContext); } private bool IsEdit => Id.HasValue; private async Task LoadFFmpegProfiles(CancellationToken cancellationToken) => _ffmpegProfiles = await _mediator.Send(new GetAllFFmpegProfiles(), cancellationToken); private async Task LoadWatermarks(CancellationToken cancellationToken) => _watermarks = await _mediator.Send(new GetAllWatermarks(), cancellationToken); private async Task LoadFillerPresets(CancellationToken cancellationToken) => _fillerPresets = await _mediator.Send(new GetAllFillerPresets(), cancellationToken) .Map(list => list.Filter(vm => vm.FillerKind == FillerKind.Fallback).ToList()); private async Task HandleSubmitAsync() { _messageStore.Clear(); if (_editContext.Validate()) { Seq errorMessage = IsEdit ? (await _mediator.Send(_model.ToUpdate(), _cts.Token)).LeftToSeq() : (await _mediator.Send(_model.ToCreate(), _cts.Token)).LeftToSeq(); errorMessage.HeadOrNone().Match( error => { _snackbar.Add(error.Value, Severity.Error); _logger.LogError("Unexpected error saving channel: {Error}", error.Value); }, () => _navigationManager.NavigateTo("/channels")); } } private async Task UploadLogo(InputFileChangeEventArgs e) { try { Either maybeCacheFileName = await _mediator.Send(new SaveArtworkToDisk(e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024), ArtworkKind.Logo), _cts.Token); maybeCacheFileName.Match( relativeFileName => { _model.Logo = relativeFileName; StateHasChanged(); }, error => { Console.WriteLine($"error saving {error}"); _snackbar.Add($"Unexpected error saving channel logo: {error.Value}", Severity.Error); _logger.LogError("Unexpected error saving channel logo: {Error}", error.Value); }); } catch (IOException ex) { Console.WriteLine(ex); _snackbar.Add("Channel logo exceeds maximum allowed file size of 10 MB", Severity.Error); _logger.LogError("Channel logo exceeds maximum allowed file size of 10 MB"); } catch (Exception ex) { _snackbar.Add($"Unexpected error saving channel logo: {ex.Message}", Severity.Error); _logger.LogError("Unexpected error saving channel logo: {Error}", ex.Message); } } }