replace channel active mode with is enabled and show in epg (#2256)

* add channel enabled setting

* remove channel active mode
This commit is contained in:
Jason Dove
2025-08-04 21:24:26 +00:00
committed by GitHub
parent c055e59723
commit b40ac9ef52
30 changed files with 24613 additions and 58 deletions
+4
View File
@@ -74,6 +74,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- This controls the progression of the channel's playout, and has nothing to do with transcoding
- `Always` is now called `Continuous` (playout progresses with wall clock)
- `On Demand` is unchanged (playout only progresses while a client is watching the channel)
- Replace channel `Active Mode` setting with new `Is Enabled` and `Show In EPG` settings
- `Active` channels will be converted to `Is Enabled` = true and `Show In EPG` = true
- `Hidden` channels will be converted to `Is Enabled` = true and `Show In EPG` = false
- `Inactive` channels will be converted to `Is Enabled` = false and `Show In EPG` = false
## [25.3.1] - 2025-07-24
### Fixed
@@ -26,9 +26,10 @@ public record ChannelViewModel(
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode,
ChannelActiveMode ActiveMode,
ChannelTranscodeMode TranscodeMode,
ChannelIdleBehavior IdleBehavior)
ChannelIdleBehavior IdleBehavior,
bool IsEnabled,
bool ShowInEpg)
{
public string WebEncodedName => WebUtility.UrlEncode(Name);
}
@@ -24,6 +24,7 @@ public record CreateChannel(
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode,
ChannelActiveMode ActiveMode,
ChannelTranscodeMode TranscodeMode,
ChannelIdleBehavior IdleBehavior) : IRequest<Either<BaseError, CreateChannelResult>>;
ChannelIdleBehavior IdleBehavior,
bool IsEnabled,
bool ShowInEpg) : IRequest<Either<BaseError, CreateChannelResult>>;
@@ -88,9 +88,10 @@ public class CreateChannelHandler(
MusicVideoCreditsMode = request.MusicVideoCreditsMode,
MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate,
SongVideoMode = request.SongVideoMode,
ActiveMode = request.ActiveMode,
TranscodeMode = request.TranscodeMode,
IdleBehavior = request.IdleBehavior
IdleBehavior = request.IdleBehavior,
IsEnabled = request.IsEnabled,
ShowInEpg = request.ShowInEpg
};
foreach (int id in watermarkId)
@@ -52,10 +52,10 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml");
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
int inactiveCount = await dbContext.Channels
.Where(c => c.Number == request.ChannelNumber && c.ActiveMode != ChannelActiveMode.Active)
int hiddenCount = await dbContext.Channels
.Where(c => c.Number == request.ChannelNumber && c.ShowInEpg == false)
.CountAsync(cancellationToken);
if (inactiveCount > 0)
if (hiddenCount > 0)
{
File.Delete(targetFile);
return;
@@ -118,7 +118,7 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
const string QUERY = @"select C.Number, C.Name, C.Categories, A.Path as ArtworkPath
from Channel C
left outer join Artwork A on C.Id = A.ChannelId and A.ArtworkKind = 2
where C.Id in (select ChannelId from Playout) and C.ActiveMode = 0
where C.Id in (select ChannelId from Playout) and C.IsEnabled = 1 and C.ShowInEPG = 1
order by CAST(C.Number as double)";
// TODO: this needs to be fixed for sqlite/mariadb
@@ -25,6 +25,7 @@ public record UpdateChannel(
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode,
ChannelActiveMode ActiveMode,
ChannelTranscodeMode TranscodeMode,
ChannelIdleBehavior IdleBehavior) : IRequest<Either<BaseError, ChannelViewModel>>;
ChannelIdleBehavior IdleBehavior,
bool IsEnabled,
bool ShowInEpg) : IRequest<Either<BaseError, ChannelViewModel>>;
@@ -43,9 +43,10 @@ public class UpdateChannelHandler(
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;
c.MusicVideoCreditsTemplate = update.MusicVideoCreditsTemplate;
c.SongVideoMode = update.SongVideoMode;
c.ActiveMode = update.ActiveMode;
c.TranscodeMode = update.TranscodeMode;
c.IdleBehavior = update.IdleBehavior;
c.IsEnabled = update.IsEnabled;
c.ShowInEpg = update.ShowInEpg;
c.Artwork ??= [];
if (!string.IsNullOrWhiteSpace(update.Logo?.Path))
+3 -2
View File
@@ -29,9 +29,10 @@ internal static class Mapper
channel.MusicVideoCreditsMode,
channel.MusicVideoCreditsTemplate,
channel.SongVideoMode,
channel.ActiveMode,
channel.TranscodeMode,
channel.IdleBehavior);
channel.IdleBehavior,
channel.IsEnabled,
channel.ShowInEpg);
internal static ChannelResponseModel ProjectToResponseModel(Channel channel) =>
new(
@@ -1,7 +1,6 @@
using System.Collections.Immutable;
using System.Text;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Iptv;
using ErsatzTV.Infrastructure.Data;
@@ -31,8 +30,8 @@ public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, Either<Ba
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var inactiveChannelNumbers = dbContext.Channels
.Where(c => c.ActiveMode != ChannelActiveMode.Active)
var hiddenChannelNumbers = dbContext.Channels
.Where(c => c.ShowInEpg == false)
.Select(c => c.Number)
.AsEnumerable()
.Select(n => $"{n}.xml")
@@ -68,7 +67,7 @@ public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, Either<Ba
continue;
}
if (inactiveChannelNumbers.Contains(Path.GetFileName(fileName)))
if (hiddenChannelNumbers.Contains(Path.GetFileName(fileName)))
{
continue;
}
@@ -1,5 +1,4 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Channels;
@@ -12,6 +11,6 @@ public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<Li
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
_channelRepository.GetAll()
.Map(channels => channels.Where(c => c.ActiveMode is ChannelActiveMode.Active)
.Map(channels => channels.Where(c => c.IsEnabled)
.Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
}
@@ -27,7 +27,7 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
var result = new List<Channel>();
foreach (Channel channel in channels)
{
if (channel.ActiveMode is not ChannelActiveMode.Active)
if (channel.IsEnabled == false)
{
continue;
}
+2 -1
View File
@@ -33,8 +33,9 @@ public class Channel
public string MusicVideoCreditsTemplate { get; set; }
public ChannelSongVideoMode SongVideoMode { get; set; }
public ChannelPlayoutMode PlayoutMode { get; set; }
public ChannelActiveMode ActiveMode { get; set; }
public ChannelTranscodeMode TranscodeMode { get; set; }
public ChannelIdleBehavior IdleBehavior { get; set; }
public bool IsEnabled { get; set; }
public bool ShowInEpg { get; set; }
public string WebEncodedName => WebUtility.UrlEncode(Name);
}
@@ -1,8 +0,0 @@
namespace ErsatzTV.Core.Domain;
public enum ChannelActiveMode
{
Active = 0,
Hidden = 1,
Inactive = 2
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_ChannelIsEnabled : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsEnabled",
table: "Channel",
type: "tinyint(1)",
nullable: false,
defaultValue: true);
migrationBuilder.AddColumn<bool>(
name: "ShowInEpg",
table: "Channel",
type: "tinyint(1)",
nullable: false,
defaultValue: true);
migrationBuilder.Sql("UPDATE `Channel` SET `IsEnabled` = 0 WHERE `ActiveMode` = 2");
migrationBuilder.Sql("UPDATE `Channel` SET `ShowInEpg` = 0 WHERE `ActiveMode` = 1");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsEnabled",
table: "Channel");
migrationBuilder.DropColumn(
name: "ShowInEpg",
table: "Channel");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Remove_ChannelActiveMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ActiveMode",
table: "Channel");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ActiveMode",
table: "Channel",
type: "int",
nullable: false,
defaultValue: 0);
}
}
}
@@ -254,9 +254,6 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("ActiveMode")
.HasColumnType("int");
b.Property<string>("Categories")
.HasColumnType("longtext");
@@ -275,6 +272,11 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("IdleBehavior")
.HasColumnType("int");
b.Property<bool>("IsEnabled")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(true);
b.Property<int>("MusicVideoCreditsMode")
.HasColumnType("int");
@@ -299,6 +301,11 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<string>("PreferredSubtitleLanguageCode")
.HasColumnType("longtext");
b.Property<bool>("ShowInEpg")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(true);
b.Property<int>("SongVideoMode")
.HasColumnType("int");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_ChannelIsEnabled : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsEnabled",
table: "Channel",
type: "INTEGER",
nullable: false,
defaultValue: true);
migrationBuilder.AddColumn<bool>(
name: "ShowInEpg",
table: "Channel",
type: "INTEGER",
nullable: false,
defaultValue: true);
migrationBuilder.Sql("UPDATE `Channel` SET `IsEnabled` = 0 WHERE `ActiveMode` = 2");
migrationBuilder.Sql("UPDATE `Channel` SET `ShowInEpg` = 0 WHERE `ActiveMode` = 1");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsEnabled",
table: "Channel");
migrationBuilder.DropColumn(
name: "ShowInEpg",
table: "Channel");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Remove_ChannelActiveMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ActiveMode",
table: "Channel");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ActiveMode",
table: "Channel",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
}
}
@@ -241,9 +241,6 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ActiveMode")
.HasColumnType("INTEGER");
b.Property<string>("Categories")
.HasColumnType("TEXT");
@@ -262,6 +259,11 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("IdleBehavior")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<int>("MusicVideoCreditsMode")
.HasColumnType("INTEGER");
@@ -286,6 +288,11 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<string>("PreferredSubtitleLanguageCode")
.HasColumnType("TEXT");
b.Property<bool>("ShowInEpg")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<int>("SongVideoMode")
.HasColumnType("INTEGER");
@@ -13,6 +13,12 @@ public class ChannelConfiguration : IEntityTypeConfiguration<Channel>
builder.HasIndex(c => c.Number)
.IsUnique();
builder.Property(c => c.IsEnabled)
.HasDefaultValue(true);
builder.Property(c => c.ShowInEpg)
.HasDefaultValue(true);
builder.HasMany(c => c.Playouts) // TODO: is this correct, or should we have one to one?
.WithOne(p => p.Channel)
.HasForeignKey(p => p.ChannelId)
@@ -103,7 +103,9 @@ public static class DbInitializer
Number = "1",
Name = "ErsatzTV",
FFmpegProfile = defaultProfile,
StreamingMode = StreamingMode.TransportStreamHybrid
StreamingMode = StreamingMode.TransportStreamHybrid,
IsEnabled = true,
ShowInEpg = true
};
await context.Channels.AddAsync(defaultChannel, cancellationToken);
await context.SaveChangesAsync(cancellationToken);
+2 -6
View File
@@ -84,9 +84,7 @@ public class IptvController : ControllerBase
string mode = null)
{
Option<ChannelViewModel> maybeChannel = await _mediator.Send(new GetChannelByNumber(channelNumber));
if (maybeChannel.IsNone ||
await maybeChannel.Map(c => c.ActiveMode).IfNoneAsync(ChannelActiveMode.Inactive) is ChannelActiveMode
.Inactive)
if (maybeChannel.IsNone || await maybeChannel.Map(c => c.IsEnabled).IfNoneAsync(false) == false)
{
return NotFound();
}
@@ -189,9 +187,7 @@ public class IptvController : ControllerBase
string mode = "mixed")
{
Option<ChannelViewModel> maybeChannel = await _mediator.Send(new GetChannelByNumber(channelNumber));
if (maybeChannel.IsNone ||
await maybeChannel.Map(c => c.ActiveMode).IfNoneAsync(ChannelActiveMode.Inactive) is ChannelActiveMode
.Inactive)
if (maybeChannel.IsNone || await maybeChannel.Map(c => c.IsEnabled).IfNoneAsync(false) == false)
{
return NotFound();
}
+12 -7
View File
@@ -78,13 +78,15 @@
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Active Mode</MudText>
<MudText>Is Enabled</MudText>
</div>
<MudSelect @bind-Value="_model.ActiveMode" For="@(() => _model.ActiveMode)">
<MudSelectItem Value="@(ChannelActiveMode.Active)">Active</MudSelectItem>
<MudSelectItem Value="@(ChannelActiveMode.Hidden)">Hidden</MudSelectItem>
<MudSelectItem Value="@(ChannelActiveMode.Inactive)">Inactive</MudSelectItem>
</MudSelect>
<MudCheckBox @bind-Value="_model.IsEnabled" Dense="true"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Show In EPG</MudText>
</div>
<MudCheckBox @bind-Value="_model.ShowInEpg" Dense="true"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
@@ -340,9 +342,10 @@
_model.MusicVideoCreditsMode = channelViewModel.MusicVideoCreditsMode;
_model.MusicVideoCreditsTemplate = channelViewModel.MusicVideoCreditsTemplate;
_model.SongVideoMode = channelViewModel.SongVideoMode;
_model.ActiveMode = channelViewModel.ActiveMode;
_model.TranscodeMode = channelViewModel.TranscodeMode;
_model.IdleBehavior = channelViewModel.IdleBehavior;
_model.IsEnabled = channelViewModel.IsEnabled;
_model.ShowInEpg = channelViewModel.ShowInEpg;
},
() => NavigationManager.NavigateTo("404"));
}
@@ -359,6 +362,8 @@
_model.Group = "ErsatzTV";
_model.FFmpegProfileId = ffmpegSettings.DefaultFFmpegProfileId;
_model.StreamingMode = StreamingMode.TransportStreamHybrid;
_model.IsEnabled = true;
_model.ShowInEpg = true;
}
}
+1 -1
View File
@@ -151,7 +151,7 @@
private bool CanPreviewChannel(ChannelViewModel channel)
{
if (channel.ActiveMode is ChannelActiveMode.Inactive)
if (!channel.IsEnabled)
{
return false;
}
+8 -5
View File
@@ -36,9 +36,10 @@ public class ChannelEditViewModel
}
public ChannelSongVideoMode SongVideoMode { get; set; }
public ChannelActiveMode ActiveMode { get; set; }
public ChannelTranscodeMode TranscodeMode { get; set; }
public ChannelIdleBehavior IdleBehavior { get; set; }
public bool IsEnabled { get; set; }
public bool ShowInEpg { get; set; }
public UpdateChannel ToUpdate() =>
new(
@@ -64,9 +65,10 @@ public class ChannelEditViewModel
MusicVideoCreditsMode,
MusicVideoCreditsTemplate,
SongVideoMode,
ActiveMode,
TranscodeMode,
IdleBehavior);
IdleBehavior,
IsEnabled,
ShowInEpg);
public CreateChannel ToCreate() =>
new(
@@ -91,7 +93,8 @@ public class ChannelEditViewModel
MusicVideoCreditsMode,
MusicVideoCreditsTemplate,
SongVideoMode,
ActiveMode,
TranscodeMode,
IdleBehavior);
IdleBehavior,
IsEnabled,
ShowInEpg);
}