add channel mirror (#2390)
* add channel playout source (doesn't do anything yet) * configure mirror channel * fix mirror playback * sync epg for mirror channel * update changelog
This commit is contained in:
+5
-1
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Classic schedules: allow selecting multiple graphics elements on schedule items
|
||||
- Add channel `Playout Source` setting
|
||||
- `Generated`: default/existing behavior where channel must have its own playout
|
||||
- `Mirror`: channel will play content from the specified `Mirror Source Channel`'s playout
|
||||
- This allows the exact same content on different channels with different channel settings
|
||||
|
||||
### Fixed
|
||||
- Fix transcoding content with bt709/pc color metadata
|
||||
@@ -15,7 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- `local_infile=ON` is required when using MySQL (for bulk inserts when building playouts)
|
||||
- ETV will set this automatically when it has permission
|
||||
- When ETV does not have permission, startup will fail with logged instructions on how to configure MySql
|
||||
- Fix scaling content in locales that don't use period as a decimal separator (e.g. `,`)
|
||||
- Fix scaling anamorphic content in locales that don't use period as a decimal separator (e.g. `,`)
|
||||
|
||||
### Changed
|
||||
- **BREAKING CHANGE**: change how `Scripted Schedule` system works
|
||||
|
||||
@@ -16,7 +16,9 @@ public record ChannelViewModel(
|
||||
string StreamSelector,
|
||||
string PreferredAudioLanguageCode,
|
||||
string PreferredAudioTitle,
|
||||
ChannelPlayoutSource PlayoutSource,
|
||||
ChannelPlayoutMode PlayoutMode,
|
||||
int? MirrorSourceChannelId,
|
||||
StreamingMode StreamingMode,
|
||||
int? WatermarkId,
|
||||
int? FallbackFillerId,
|
||||
|
||||
@@ -15,7 +15,9 @@ public record CreateChannel(
|
||||
string StreamSelector,
|
||||
string PreferredAudioLanguageCode,
|
||||
string PreferredAudioTitle,
|
||||
ChannelPlayoutSource PlayoutSource,
|
||||
ChannelPlayoutMode PlayoutMode,
|
||||
int? MirrorSourceChannelId,
|
||||
StreamingMode StreamingMode,
|
||||
int? WatermarkId,
|
||||
int? FallbackFillerId,
|
||||
|
||||
@@ -76,7 +76,9 @@ public class CreateChannelHandler(
|
||||
Group = request.Group,
|
||||
Categories = request.Categories,
|
||||
FFmpegProfileId = ffmpegProfileId,
|
||||
PlayoutSource = request.PlayoutSource,
|
||||
PlayoutMode = request.PlayoutMode,
|
||||
MirrorSourceChannelId = request.MirrorSourceChannelId,
|
||||
StreamingMode = request.StreamingMode,
|
||||
Artwork = artwork,
|
||||
StreamSelectorMode = request.StreamSelectorMode,
|
||||
@@ -94,6 +96,15 @@ public class CreateChannelHandler(
|
||||
ShowInEpg = request.IsEnabled && request.ShowInEpg
|
||||
};
|
||||
|
||||
if (channel.PlayoutSource is ChannelPlayoutSource.Mirror)
|
||||
{
|
||||
channel.PlayoutMode = ChannelPlayoutMode.Continuous;
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.MirrorSourceChannelId = null;
|
||||
}
|
||||
|
||||
foreach (int id in watermarkId)
|
||||
{
|
||||
channel.WatermarkId = id;
|
||||
|
||||
@@ -52,8 +52,10 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml");
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
string channelNumber = request.ChannelNumber;
|
||||
|
||||
int hiddenCount = await dbContext.Channels
|
||||
.Where(c => c.Number == request.ChannelNumber && c.ShowInEpg == false)
|
||||
.Where(c => c.Number == channelNumber && c.ShowInEpg == false)
|
||||
.CountAsync(cancellationToken);
|
||||
if (hiddenCount > 0)
|
||||
{
|
||||
@@ -97,6 +99,17 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
string otherVideoText = await File.ReadAllTextAsync(otherVideoTemplateFileName, cancellationToken);
|
||||
var otherVideoTemplate = Template.Parse(otherVideoText, otherVideoTemplateFileName);
|
||||
|
||||
Option<string> maybeMirrorNumber = await dbContext.Channels
|
||||
.Filter(c => c.Number == channelNumber)
|
||||
.Filter(c => c.PlayoutSource == ChannelPlayoutSource.Mirror && c.MirrorSourceChannelId != null)
|
||||
.Map(c => c.MirrorSourceChannel.Number)
|
||||
.ToListAsync(cancellationToken)
|
||||
.Map(list => list.HeadOrNone());
|
||||
foreach (string mirrorNumber in maybeMirrorNumber)
|
||||
{
|
||||
request = new RefreshChannelData(ChannelNumber: mirrorNumber);
|
||||
}
|
||||
|
||||
List<Playout> playouts = await dbContext.Playouts
|
||||
.AsNoTracking()
|
||||
.Filter(pi => pi.Channel.Number == request.ChannelNumber)
|
||||
|
||||
@@ -16,7 +16,9 @@ public record UpdateChannel(
|
||||
string StreamSelector,
|
||||
string PreferredAudioLanguageCode,
|
||||
string PreferredAudioTitle,
|
||||
ChannelPlayoutSource PlayoutSource,
|
||||
ChannelPlayoutMode PlayoutMode,
|
||||
int? MirrorSourceChannelId,
|
||||
StreamingMode StreamingMode,
|
||||
int? WatermarkId,
|
||||
int? FallbackFillerId,
|
||||
|
||||
@@ -33,6 +33,8 @@ public class UpdateChannelHandler(
|
||||
UpdateChannel update,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
bool hasEpgChange = c.PlayoutSource != update.PlayoutSource || c.ShowInEpg != update.ShowInEpg;
|
||||
|
||||
c.Name = update.Name;
|
||||
c.Number = update.Number;
|
||||
c.Group = update.Group;
|
||||
@@ -99,10 +101,24 @@ public class UpdateChannelHandler(
|
||||
}
|
||||
}
|
||||
|
||||
c.PlayoutSource = update.PlayoutSource;
|
||||
c.PlayoutMode = update.PlayoutMode;
|
||||
|
||||
if (c.PlayoutSource is ChannelPlayoutSource.Mirror)
|
||||
{
|
||||
c.PlayoutMode = ChannelPlayoutMode.Continuous;
|
||||
hasEpgChange |= c.MirrorSourceChannelId != update.MirrorSourceChannelId;
|
||||
}
|
||||
else
|
||||
{
|
||||
c.MirrorSourceChannelId = null;
|
||||
}
|
||||
|
||||
c.MirrorSourceChannelId = update.MirrorSourceChannelId;
|
||||
c.StreamingMode = update.StreamingMode;
|
||||
c.WatermarkId = update.WatermarkId;
|
||||
c.FallbackFillerId = update.FallbackFillerId;
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
searchTargets.SearchTargetsChanged();
|
||||
@@ -119,8 +135,12 @@ public class UpdateChannelHandler(
|
||||
}
|
||||
|
||||
await workerChannel.WriteAsync(new RefreshChannelList(), cancellationToken);
|
||||
if (hasEpgChange)
|
||||
{
|
||||
await workerChannel.WriteAsync(new RefreshChannelData(c.Number), cancellationToken);
|
||||
}
|
||||
|
||||
return ProjectToViewModel(c);
|
||||
return ProjectToViewModel(c, c.Playouts?.Count ?? 0);
|
||||
}
|
||||
|
||||
private static async Task<Validation<BaseError, Channel>> Validate(
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ErsatzTV.Application.Channels;
|
||||
|
||||
internal static class Mapper
|
||||
{
|
||||
internal static ChannelViewModel ProjectToViewModel(Channel channel) =>
|
||||
internal static ChannelViewModel ProjectToViewModel(Channel channel, int playoutCount) =>
|
||||
new(
|
||||
channel.Id,
|
||||
channel.Number,
|
||||
@@ -19,11 +19,13 @@ internal static class Mapper
|
||||
channel.StreamSelector,
|
||||
channel.PreferredAudioLanguageCode,
|
||||
channel.PreferredAudioTitle,
|
||||
channel.PlayoutSource,
|
||||
channel.PlayoutMode,
|
||||
channel.MirrorSourceChannelId,
|
||||
channel.StreamingMode,
|
||||
channel.WatermarkId,
|
||||
channel.FallbackFillerId,
|
||||
channel.Playouts?.Count ?? 0,
|
||||
playoutCount,
|
||||
channel.PreferredSubtitleLanguageCode,
|
||||
channel.SubtitleMode,
|
||||
channel.MusicVideoCreditsMode,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using static ErsatzTV.Application.Channels.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Channels;
|
||||
@@ -8,5 +9,22 @@ public class GetAllChannelsHandler(IChannelRepository channelRepository)
|
||||
{
|
||||
public async Task<List<ChannelViewModel>> Handle(GetAllChannels request, CancellationToken cancellationToken) =>
|
||||
await channelRepository.GetAll(cancellationToken)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
.Map(list => list.Map(c => ProjectToViewModel(c, GetPlayoutsCount(c))).ToList());
|
||||
|
||||
private static int GetPlayoutsCount(Channel channel)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
if (channel.Playouts != null)
|
||||
{
|
||||
result += channel.Playouts.Count;
|
||||
}
|
||||
|
||||
if (channel.PlayoutSource is ChannelPlayoutSource.Mirror && channel.MirrorSourceChannel?.Playouts != null)
|
||||
{
|
||||
result += channel.MirrorSourceChannel.Playouts.Count;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ public class GetChannelByIdHandler(IChannelRepository channelRepository)
|
||||
{
|
||||
public Task<Option<ChannelViewModel>> Handle(GetChannelById request, CancellationToken cancellationToken) =>
|
||||
channelRepository.GetChannel(request.Id)
|
||||
.MapT(ProjectToViewModel);
|
||||
.MapT(c => ProjectToViewModel(c, 0));
|
||||
}
|
||||
|
||||
@@ -3,12 +3,9 @@ using static ErsatzTV.Application.Channels.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Channels;
|
||||
|
||||
public class GetChannelByNumberHandler : IRequestHandler<GetChannelByNumber, Option<ChannelViewModel>>
|
||||
public class GetChannelByNumberHandler(IChannelRepository channelRepository)
|
||||
: IRequestHandler<GetChannelByNumber, Option<ChannelViewModel>>
|
||||
{
|
||||
private readonly IChannelRepository _channelRepository;
|
||||
|
||||
public GetChannelByNumberHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
|
||||
|
||||
public Task<Option<ChannelViewModel>> Handle(GetChannelByNumber request, CancellationToken cancellationToken) =>
|
||||
_channelRepository.GetByNumber(request.ChannelNumber).MapT(ProjectToViewModel);
|
||||
channelRepository.GetByNumber(request.ChannelNumber).MapT(c => ProjectToViewModel(c, 0));
|
||||
}
|
||||
|
||||
+1
-1
@@ -183,7 +183,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
.Include(i => i.MediaItem)
|
||||
.ThenInclude(mi => (mi as RemoteStream).RemoteStreamMetadata)
|
||||
.Include(i => i.Watermarks)
|
||||
.ForChannelAndTime(channel.Id, now)
|
||||
.ForChannelAndTime(channel.MirrorSourceChannelId ?? channel.Id, now)
|
||||
.Map(o => o.ToEither<BaseError>(new UnableToLocatePlayoutItem()))
|
||||
.BindT(item => ValidatePlayoutItemPath(dbContext, item, cancellationToken));
|
||||
|
||||
|
||||
@@ -32,7 +32,11 @@ public class Channel
|
||||
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
|
||||
public string MusicVideoCreditsTemplate { get; set; }
|
||||
public ChannelSongVideoMode SongVideoMode { get; set; }
|
||||
public ChannelPlayoutSource PlayoutSource { get; set; }
|
||||
public ChannelPlayoutMode PlayoutMode { get; set; }
|
||||
public int? MirrorSourceChannelId { get; set; }
|
||||
public Channel MirrorSourceChannel { get; set; }
|
||||
public TimeSpan? PlayoutOffset { get; set; }
|
||||
public ChannelTranscodeMode TranscodeMode { get; set; }
|
||||
public ChannelIdleBehavior IdleBehavior { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
public enum ChannelPlayoutSource
|
||||
{
|
||||
Generated = 0,
|
||||
Mirror = 1
|
||||
}
|
||||
Generated
+6384
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_ChannelPlayoutSource : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MirrorSourceChannelId",
|
||||
table: "Channel",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<TimeSpan>(
|
||||
name: "PlayoutOffset",
|
||||
table: "Channel",
|
||||
type: "time(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PlayoutSource",
|
||||
table: "Channel",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channel_MirrorSourceChannelId",
|
||||
table: "Channel",
|
||||
column: "MirrorSourceChannelId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channel_Channel_MirrorSourceChannelId",
|
||||
table: "Channel",
|
||||
column: "MirrorSourceChannelId",
|
||||
principalTable: "Channel",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channel_Channel_MirrorSourceChannelId",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Channel_MirrorSourceChannelId",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MirrorSourceChannelId",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlayoutOffset",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlayoutSource",
|
||||
table: "Channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +277,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<int?>("MirrorSourceChannelId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("MusicVideoCreditsMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -292,6 +295,12 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Property<int>("PlayoutMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<TimeSpan?>("PlayoutOffset")
|
||||
.HasColumnType("time(6)");
|
||||
|
||||
b.Property<int>("PlayoutSource")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("PreferredAudioLanguageCode")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
@@ -336,6 +345,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
|
||||
b.HasIndex("FallbackFillerId");
|
||||
|
||||
b.HasIndex("MirrorSourceChannelId");
|
||||
|
||||
b.HasIndex("Number")
|
||||
.IsUnique();
|
||||
|
||||
@@ -4048,6 +4059,11 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
.HasForeignKey("FallbackFillerId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.Channel", "MirrorSourceChannel")
|
||||
.WithMany()
|
||||
.HasForeignKey("MirrorSourceChannelId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
|
||||
.WithMany()
|
||||
.HasForeignKey("WatermarkId")
|
||||
@@ -4057,6 +4073,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
|
||||
b.Navigation("FallbackFiller");
|
||||
|
||||
b.Navigation("MirrorSourceChannel");
|
||||
|
||||
b.Navigation("Watermark");
|
||||
});
|
||||
|
||||
|
||||
Generated
+6219
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_ChannelPlayoutSource : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MirrorSourceChannelId",
|
||||
table: "Channel",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<TimeSpan>(
|
||||
name: "PlayoutOffset",
|
||||
table: "Channel",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PlayoutSource",
|
||||
table: "Channel",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channel_MirrorSourceChannelId",
|
||||
table: "Channel",
|
||||
column: "MirrorSourceChannelId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channel_Channel_MirrorSourceChannelId",
|
||||
table: "Channel",
|
||||
column: "MirrorSourceChannelId",
|
||||
principalTable: "Channel",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channel_Channel_MirrorSourceChannelId",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Channel_MirrorSourceChannelId",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MirrorSourceChannelId",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlayoutOffset",
|
||||
table: "Channel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlayoutSource",
|
||||
table: "Channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,6 +264,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<int?>("MirrorSourceChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MusicVideoCreditsMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -279,6 +282,12 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Property<int>("PlayoutMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<TimeSpan?>("PlayoutOffset")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PlayoutSource")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PreferredAudioLanguageCode")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -323,6 +332,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("FallbackFillerId");
|
||||
|
||||
b.HasIndex("MirrorSourceChannelId");
|
||||
|
||||
b.HasIndex("Number")
|
||||
.IsUnique();
|
||||
|
||||
@@ -3883,6 +3894,11 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.HasForeignKey("FallbackFillerId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.Channel", "MirrorSourceChannel")
|
||||
.WithMany()
|
||||
.HasForeignKey("MirrorSourceChannelId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ChannelWatermark", "Watermark")
|
||||
.WithMany()
|
||||
.HasForeignKey("WatermarkId")
|
||||
@@ -3892,6 +3908,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.Navigation("FallbackFiller");
|
||||
|
||||
b.Navigation("MirrorSourceChannel");
|
||||
|
||||
b.Navigation("Watermark");
|
||||
});
|
||||
|
||||
|
||||
@@ -43,5 +43,11 @@ public class ChannelConfiguration : IEntityTypeConfiguration<Channel>
|
||||
builder.Property(c => c.Group)
|
||||
.IsRequired()
|
||||
.HasDefaultValue("ErsatzTV");
|
||||
|
||||
builder.HasOne(i => i.MirrorSourceChannel)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.MirrorSourceChannelId)
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.IsRequired(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public class ChannelRepository : IChannelRepository
|
||||
.Include(c => c.FFmpegProfile)
|
||||
.Include(c => c.Artwork)
|
||||
.Include(c => c.Playouts)
|
||||
.Include(c => c.MirrorSourceChannel)
|
||||
.ThenInclude(mc => mc.Playouts)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/channels/{Id:int?}"
|
||||
@page "/channels/add"
|
||||
@using System.Globalization
|
||||
@using ErsatzTV.Application.Artworks
|
||||
@using ErsatzTV.Application.Channels
|
||||
@using ErsatzTV.Application.FFmpegProfiles
|
||||
@@ -69,13 +70,41 @@
|
||||
</MudStack>
|
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||
<div class="d-flex">
|
||||
<MudText>Playout Mode</MudText>
|
||||
<MudText>Playout Source</MudText>
|
||||
</div>
|
||||
<MudSelect @bind-Value="_model.PlayoutMode" For="@(() => _model.PlayoutMode)" HelperText="Controls the progression of the channel's playout">
|
||||
<MudSelectItem Value="@(ChannelPlayoutMode.Continuous)">Continuous</MudSelectItem>
|
||||
<MudSelectItem Value="@(ChannelPlayoutMode.OnDemand)">On Demand</MudSelectItem>
|
||||
<MudSelect @bind-Value="_model.PlayoutSource" For="@(() => _model.PlayoutSource)" HelperText="Controls the source of the channel's content">
|
||||
<MudSelectItem Value="@(ChannelPlayoutSource.Generated)">Generated</MudSelectItem>
|
||||
<MudSelectItem Value="@(ChannelPlayoutSource.Mirror)">Mirror</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
@if (_model.PlayoutSource is ChannelPlayoutSource.Mirror)
|
||||
{
|
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||
<div class="d-flex">
|
||||
<MudText>Mirror Source Channel</MudText>
|
||||
</div>
|
||||
<MudSelect @bind-Value="_model.MirrorSourceChannelId" For="@(() => _model.MirrorSourceChannelId)"
|
||||
Clearable="true">
|
||||
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
|
||||
@foreach (ChannelViewModel channel in _channels.OrderBy(c => decimal.Parse(c.Number, CultureInfo.InvariantCulture)))
|
||||
{
|
||||
<MudSelectItem T="int?" Value="@channel.Id">(@channel.Number) - @channel.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||
<div class="d-flex">
|
||||
<MudText>Playout Mode</MudText>
|
||||
</div>
|
||||
<MudSelect @bind-Value="_model.PlayoutMode" For="@(() => _model.PlayoutMode)" HelperText="Controls the progression of the channel's playout">
|
||||
<MudSelectItem Value="@(ChannelPlayoutMode.Continuous)">Continuous</MudSelectItem>
|
||||
<MudSelectItem Value="@(ChannelPlayoutMode.OnDemand)">On Demand</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
}
|
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||
<div class="d-flex">
|
||||
<MudText>Is Enabled</MudText>
|
||||
@@ -285,6 +314,7 @@ else
|
||||
private readonly ChannelEditViewModelValidator _validator = new();
|
||||
private MudForm _form;
|
||||
|
||||
private List<ChannelViewModel> _channels = [];
|
||||
private List<FFmpegProfileViewModel> _ffmpegProfiles = [];
|
||||
private List<LanguageCodeViewModel> _availableCultures = [];
|
||||
private List<WatermarkViewModel> _watermarks = [];
|
||||
@@ -309,6 +339,7 @@ else
|
||||
{
|
||||
var loadingTasks = new List<Task>
|
||||
{
|
||||
LoadDataAsync(() => Mediator.Send(new GetAllChannels(), token), r => _channels = r),
|
||||
LoadDataAsync(() => Mediator.Send(new GetAllFFmpegProfiles(), token), r => _ffmpegProfiles = r),
|
||||
LoadDataAsync(() => Mediator.Send(new GetAllLanguageCodes(), token), r => _availableCultures = r),
|
||||
LoadDataAsync(() => Mediator.Send(new GetAllWatermarks(), token), r => _watermarks = r),
|
||||
@@ -342,7 +373,9 @@ else
|
||||
_model.Logo = channelViewModel.Logo;
|
||||
}
|
||||
|
||||
_model.PlayoutSource = channelViewModel.PlayoutSource;
|
||||
_model.PlayoutMode = channelViewModel.PlayoutMode;
|
||||
_model.MirrorSourceChannelId = channelViewModel.MirrorSourceChannelId;
|
||||
_model.StreamingMode = channelViewModel.StreamingMode;
|
||||
_model.StreamSelectorMode = channelViewModel.StreamSelectorMode;
|
||||
_model.StreamSelector = channelViewModel.StreamSelector;
|
||||
|
||||
@@ -19,7 +19,9 @@ public class ChannelEditViewModel
|
||||
public string PreferredAudioTitle { get; set; }
|
||||
public ArtworkContentTypeModel Logo { get; set; }
|
||||
public string ExternalLogoUrl { get; set; }
|
||||
public ChannelPlayoutSource PlayoutSource { get; set; }
|
||||
public ChannelPlayoutMode PlayoutMode { get; set; }
|
||||
public int? MirrorSourceChannelId { get; set; }
|
||||
public StreamingMode StreamingMode { get; set; }
|
||||
public int? WatermarkId { get; set; }
|
||||
public int? FallbackFillerId { get; set; }
|
||||
@@ -56,7 +58,9 @@ public class ChannelEditViewModel
|
||||
StreamSelector,
|
||||
PreferredAudioLanguageCode,
|
||||
PreferredAudioTitle,
|
||||
PlayoutSource,
|
||||
PlayoutMode,
|
||||
MirrorSourceChannelId,
|
||||
StreamingMode,
|
||||
WatermarkId,
|
||||
FallbackFillerId,
|
||||
@@ -84,7 +88,9 @@ public class ChannelEditViewModel
|
||||
StreamSelector,
|
||||
PreferredAudioLanguageCode,
|
||||
PreferredAudioTitle,
|
||||
PlayoutSource,
|
||||
PlayoutMode,
|
||||
MirrorSourceChannelId,
|
||||
StreamingMode,
|
||||
WatermarkId,
|
||||
FallbackFillerId,
|
||||
|
||||
Reference in New Issue
Block a user