Browse Source

add channel active mode (#2083)

pull/2086/head
Jason Dove 2 months ago committed by GitHub
parent
commit
583cbf7b14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/Channels/ChannelViewModel.cs
  3. 3
      ErsatzTV.Application/Channels/Commands/CreateChannel.cs
  4. 3
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  5. 15
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  6. 2
      ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs
  7. 3
      ErsatzTV.Application/Channels/Commands/UpdateChannel.cs
  8. 1
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  9. 3
      ErsatzTV.Application/Channels/Mapper.cs
  10. 13
      ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs
  11. 5
      ErsatzTV.Application/Channels/Queries/GetChannelLineupHandler.cs
  12. 5
      ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs
  13. 1
      ErsatzTV.Core/Domain/Channel.cs
  14. 8
      ErsatzTV.Core/Domain/ChannelActiveMode.cs
  15. 2
      ErsatzTV.FFmpeg/Format/AvailablePixelFormats.cs
  16. 5893
      ErsatzTV.Infrastructure.MySql/Migrations/20250627201016_Add_ChannelActiveMode.Designer.cs
  17. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20250627201016_Add_ChannelActiveMode.cs
  18. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  19. 5732
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250627201210_Add_ChannelActiveMode.Designer.cs
  20. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250627201210_Add_ChannelActiveMode.cs
  21. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  22. 14
      ErsatzTV/Controllers/IptvController.cs
  23. 6
      ErsatzTV/Pages/ChannelEditor.razor
  24. 7
      ErsatzTV/ViewModels/ChannelEditViewModel.cs

8
CHANGELOG.md

@ -29,10 +29,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -29,10 +29,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `channels` (audio only)
- An example subtitle condition: `lang like 'en%' and external`
- An example audio condition: `title like '%movie%' and channels > 2`
- Add new channel setting `Active Mode`
- `Active` - default value, channel streams as normal and has normal visibility
- `Hidden` - channel streams as normal and is hidden from M3U/XMLTV/HDHR
- `Inactive` - channel cannot stream (will 404) and is hidden from M3U/XMLTV/HDHR
### Fixed
- Fix QSV acceleration in docker with older Intel devices
- Fix software tonemap when used with NVIDIA accel (`ETV_DISABLE_VULKAN` env var)
- Fix HDR transcoding with NVIDIA accel for:
- All NVIDIA docker users
- Windows NVIDIA users who have set the `ETV_DISABLE_VULKAN` env var
## [25.2.0] - 2025-06-24
### Added

3
ErsatzTV.Application/Channels/ChannelViewModel.cs

@ -24,7 +24,8 @@ public record ChannelViewModel( @@ -24,7 +24,8 @@ public record ChannelViewModel(
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode)
ChannelSongVideoMode SongVideoMode,
ChannelActiveMode ActiveMode)
{
public string WebEncodedName => WebUtility.UrlEncode(Name);
}

3
ErsatzTV.Application/Channels/Commands/CreateChannel.cs

@ -22,4 +22,5 @@ public record CreateChannel( @@ -22,4 +22,5 @@ public record CreateChannel(
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode) : IRequest<Either<BaseError, CreateChannelResult>>;
ChannelSongVideoMode SongVideoMode,
ChannelActiveMode ActiveMode) : IRequest<Either<BaseError, CreateChannelResult>>;

3
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -85,7 +85,8 @@ public class CreateChannelHandler( @@ -85,7 +85,8 @@ public class CreateChannelHandler(
SubtitleMode = request.SubtitleMode,
MusicVideoCreditsMode = request.MusicVideoCreditsMode,
MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate,
SongVideoMode = request.SongVideoMode
SongVideoMode = request.SongVideoMode,
ActiveMode = request.ActiveMode
};
foreach (int id in watermarkId)

15
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -49,6 +49,18 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -49,6 +49,18 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
_localFileSystem.EnsureFolderExists(FileSystemLayout.ChannelGuideCacheFolder);
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)
.CountAsync(cancellationToken);
if (inactiveCount > 0)
{
File.Delete(targetFile);
return;
}
string movieTemplateFileName = GetMovieTemplateFileName();
string episodeTemplateFileName = GetEpisodeTemplateFileName();
string musicVideoTemplateFileName = GetMusicVideoTemplateFileName();
@ -85,8 +97,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -85,8 +97,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string otherVideoText = await File.ReadAllTextAsync(otherVideoTemplateFileName, cancellationToken);
var otherVideoTemplate = Template.Parse(otherVideoText, otherVideoTemplateFileName);
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
List<Playout> playouts = await dbContext.Playouts
.AsNoTracking()
.Filter(pi => pi.Channel.Number == request.ChannelNumber)
@ -244,7 +254,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -244,7 +254,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string tempFile = Path.GetTempFileName();
await File.WriteAllBytesAsync(tempFile, ms.ToArray(), cancellationToken);
string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml");
File.Move(tempFile, targetFile, true);
}

2
ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs

@ -118,7 +118,7 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList> @@ -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)
where C.Id in (select ChannelId from Playout) and C.ActiveMode = 0
order by CAST(C.Number as double)";
// TODO: this needs to be fixed for sqlite/mariadb

3
ErsatzTV.Application/Channels/Commands/UpdateChannel.cs

@ -23,4 +23,5 @@ public record UpdateChannel( @@ -23,4 +23,5 @@ public record UpdateChannel(
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode) : IRequest<Either<BaseError, ChannelViewModel>>;
ChannelSongVideoMode SongVideoMode,
ChannelActiveMode ActiveMode) : IRequest<Either<BaseError, ChannelViewModel>>;

1
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -44,6 +44,7 @@ public class UpdateChannelHandler( @@ -44,6 +44,7 @@ public class UpdateChannelHandler(
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;
c.MusicVideoCreditsTemplate = update.MusicVideoCreditsTemplate;
c.SongVideoMode = update.SongVideoMode;
c.ActiveMode = update.ActiveMode;
c.Artwork ??= [];
if (!string.IsNullOrWhiteSpace(update.Logo))

3
ErsatzTV.Application/Channels/Mapper.cs

@ -27,7 +27,8 @@ internal static class Mapper @@ -27,7 +27,8 @@ internal static class Mapper
channel.SubtitleMode,
channel.MusicVideoCreditsMode,
channel.MusicVideoCreditsTemplate,
channel.SongVideoMode);
channel.SongVideoMode,
channel.ActiveMode);
internal static ChannelResponseModel ProjectToResponseModel(Channel channel) =>
new(

13
ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
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;
@ -29,6 +31,12 @@ public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, Either<Ba @@ -29,6 +31,12 @@ 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)
.Select(c => c.Number)
.AsEnumerable()
.Select(n => $"{n}.xml")
.ToImmutableHashSet();
string channelsFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, "channels.xml");
if (!_localFileSystem.FileExists(channelsFile))
@ -60,6 +68,11 @@ public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, Either<Ba @@ -60,6 +68,11 @@ public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, Either<Ba
continue;
}
if (inactiveChannelNumbers.Contains(Path.GetFileName(fileName)))
{
continue;
}
string channelDataFragment = await File.ReadAllTextAsync(fileName, Encoding.UTF8, cancellationToken);
channelDataFragment = channelDataFragment

5
ErsatzTV.Application/Channels/Queries/GetChannelLineupHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Channels;
@ -11,5 +12,5 @@ public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<Li @@ -11,5 +12,5 @@ public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<Li
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
_channelRepository.GetAll()
.Map(channels => channels.Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
.Map(channels => channels.Where(c => c.ActiveMode is ChannelActiveMode.Active).Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
}

5
ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs

@ -28,6 +28,11 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha @@ -28,6 +28,11 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
var result = new List<Channel>();
foreach (Channel channel in channels)
{
if (channel.ActiveMode is not ChannelActiveMode.Active)
{
continue;
}
switch (mode.ToLowerInvariant())
{
case "segmenter":

1
ErsatzTV.Core/Domain/Channel.cs

@ -33,5 +33,6 @@ public class Channel @@ -33,5 +33,6 @@ public class Channel
public string MusicVideoCreditsTemplate { get; set; }
public ChannelSongVideoMode SongVideoMode { get; set; }
public ChannelProgressMode ProgressMode { get; set; }
public ChannelActiveMode ActiveMode { get; set; }
public string WebEncodedName => WebUtility.UrlEncode(Name);
}

8
ErsatzTV.Core/Domain/ChannelActiveMode.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain;
public enum ChannelActiveMode
{
Active = 0,
Hidden = 1,
Inactive = 2
}

2
ErsatzTV.FFmpeg/Format/AvailablePixelFormats.cs

@ -18,7 +18,7 @@ public static class AvailablePixelFormats @@ -18,7 +18,7 @@ public static class AvailablePixelFormats
private static Option<IPixelFormat> LogUnknownPixelFormat(string pixelFormat, ILogger? logger)
{
logger?.LogWarning("Unexpected pixel format {PixelFormat} may have playback issues", pixelFormat);
logger?.LogDebug("Unexpected pixel format {PixelFormat} may have playback issues", pixelFormat);
return Option<IPixelFormat>.None;
}
}

5893
ErsatzTV.Infrastructure.MySql/Migrations/20250627201016_Add_ChannelActiveMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20250627201016_Add_ChannelActiveMode.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_ChannelActiveMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ActiveMode",
table: "Channel",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ActiveMode",
table: "Channel");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -241,6 +241,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -241,6 +241,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("ActiveMode")
.HasColumnType("int");
b.Property<string>("Categories")
.HasColumnType("longtext");

5732
ErsatzTV.Infrastructure.Sqlite/Migrations/20250627201210_Add_ChannelActiveMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20250627201210_Add_ChannelActiveMode.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_ChannelActiveMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ActiveMode",
table: "Channel",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ActiveMode",
table: "Channel");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -228,6 +228,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -228,6 +228,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ActiveMode")
.HasColumnType("INTEGER");
b.Property<string>("Categories")
.HasColumnType("TEXT");

14
ErsatzTV/Controllers/IptvController.cs

@ -83,10 +83,15 @@ public class IptvController : ControllerBase @@ -83,10 +83,15 @@ public class IptvController : ControllerBase
[FromQuery]
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)
{
return NotFound();
}
// if mode is "unspecified" - find the configured mode and set it or redirect
if (string.IsNullOrWhiteSpace(mode) || mode == "mixed")
{
Option<ChannelViewModel> maybeChannel = await _mediator.Send(new GetChannelByNumber(channelNumber));
foreach (ChannelViewModel channel in maybeChannel)
{
switch (channel.StreamingMode)
@ -180,10 +185,15 @@ public class IptvController : ControllerBase @@ -180,10 +185,15 @@ public class IptvController : ControllerBase
[FromQuery]
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)
{
return NotFound();
}
// if mode is "unspecified" - find the configured mode and set it or redirect
if (string.IsNullOrWhiteSpace(mode) || mode == "mixed")
{
Option<ChannelViewModel> maybeChannel = await _mediator.Send(new GetChannelByNumber(channelNumber));
foreach (ChannelViewModel channel in maybeChannel)
{
switch (channel.StreamingMode)

6
ErsatzTV/Pages/ChannelEditor.razor

@ -30,6 +30,11 @@ @@ -30,6 +30,11 @@
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
<MudTextField Class="mt-3" Label="Group" @bind-Value="_model.Group" For="@(() => _model.Group)"/>
<MudTextField Class="mt-3" Label="Categories" @bind-Value="_model.Categories" For="@(() => _model.Categories)" Placeholder="Comma-separated list of categories"/>
<MudSelect Class="mt-3" Label="Active Mode" @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>
<MudSelect Class="mt-3" Label="Progress Mode" @bind-Value="_model.ProgressMode" For="@(() => _model.ProgressMode)">
<MudSelectItem Value="@(ChannelProgressMode.Always)">Always</MudSelectItem>
<MudSelectItem Value="@(ChannelProgressMode.OnDemand)">On Demand</MudSelectItem>
@ -233,6 +238,7 @@ @@ -233,6 +238,7 @@
_model.MusicVideoCreditsMode = channelViewModel.MusicVideoCreditsMode;
_model.MusicVideoCreditsTemplate = channelViewModel.MusicVideoCreditsTemplate;
_model.SongVideoMode = channelViewModel.SongVideoMode;
_model.ActiveMode = channelViewModel.ActiveMode;
},
() => NavigationManager.NavigateTo("404"));
}

7
ErsatzTV/ViewModels/ChannelEditViewModel.cs

@ -33,6 +33,7 @@ public class ChannelEditViewModel @@ -33,6 +33,7 @@ public class ChannelEditViewModel
set => _musicVideoCreditsTemplate = value;
}
public ChannelSongVideoMode SongVideoMode { get; set; }
public ChannelActiveMode ActiveMode { get; set; }
public UpdateChannel ToUpdate() =>
new(
@ -55,7 +56,8 @@ public class ChannelEditViewModel @@ -55,7 +56,8 @@ public class ChannelEditViewModel
SubtitleMode,
MusicVideoCreditsMode,
MusicVideoCreditsTemplate,
SongVideoMode);
SongVideoMode,
ActiveMode);
public CreateChannel ToCreate() =>
new(
@ -77,5 +79,6 @@ public class ChannelEditViewModel @@ -77,5 +79,6 @@ public class ChannelEditViewModel
SubtitleMode,
MusicVideoCreditsMode,
MusicVideoCreditsTemplate,
SongVideoMode);
SongVideoMode,
ActiveMode);
}

Loading…
Cancel
Save