Enhance channel and settings functionalities: introduce viewer settings in ChannelEndpoints, update SiteSettings to include channel number toggling, and refactor related data structures. Implement new endpoints for validating templates and diffing scheduling changes, improving overall user experience and configuration management.
This commit is contained in:
+93
-77
@@ -1,77 +1,93 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Domain.Broadcast;
|
||||
|
||||
namespace TeleWave.Application.Streaming.ListPublicChannels;
|
||||
|
||||
public sealed class ListPublicChannelsQueryHandler(IAppDbContext dbContext)
|
||||
: IQueryHandler<ListPublicChannelsQuery, IReadOnlyList<PublicChannelDto>>
|
||||
{
|
||||
public async Task<IReadOnlyList<PublicChannelDto>> Handle(
|
||||
ListPublicChannelsQuery query,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var channels = await dbContext
|
||||
.Channels.AsNoTracking()
|
||||
.Where(c => c.IsEnabled)
|
||||
.OrderBy(c => c.Name)
|
||||
.Select(c => new
|
||||
{
|
||||
c.Id,
|
||||
c.Slug,
|
||||
c.Name,
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
if (channels.Count == 0)
|
||||
return [];
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var channelIds = channels.Select(c => c.Id).ToList();
|
||||
|
||||
// Что идёт прямо сейчас на каждом канале (программа) — для постера-обложки плитки.
|
||||
var currentByChannel = await dbContext
|
||||
.ScheduleEntries.AsNoTracking()
|
||||
.Where(e =>
|
||||
channelIds.Contains(e.ChannelId)
|
||||
&& e.Kind == ScheduleEntryKind.Program
|
||||
&& e.StartsAtUtc <= now
|
||||
&& e.EndsAtUtc > now
|
||||
&& e.ShowId != null
|
||||
)
|
||||
.Select(e => new { e.ChannelId, ShowId = e.ShowId!.Value })
|
||||
.ToListAsync(cancellationToken);
|
||||
var currentShowByChannel = currentByChannel
|
||||
.GroupBy(x => x.ChannelId)
|
||||
.ToDictionary(g => g.Key, g => g.First().ShowId);
|
||||
|
||||
var showIds = currentShowByChannel.Values.Distinct().ToList();
|
||||
var shows = await dbContext
|
||||
.Shows.AsNoTracking()
|
||||
.Where(s => showIds.Contains(s.Id))
|
||||
.Select(s => new
|
||||
{
|
||||
s.Id,
|
||||
s.Name,
|
||||
s.PosterImageId,
|
||||
})
|
||||
.ToDictionaryAsync(s => s.Id, cancellationToken);
|
||||
|
||||
return channels
|
||||
.Select(c =>
|
||||
{
|
||||
var showId = currentShowByChannel.GetValueOrDefault(c.Id);
|
||||
var show = showId != Guid.Empty ? shows.GetValueOrDefault(showId) : null;
|
||||
return new PublicChannelDto(
|
||||
c.Id,
|
||||
c.Slug,
|
||||
c.Name,
|
||||
show is null ? null : showId,
|
||||
show?.Name,
|
||||
show?.PosterImageId
|
||||
);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Domain.Broadcast;
|
||||
|
||||
namespace TeleWave.Application.Streaming.ListPublicChannels;
|
||||
|
||||
public sealed class ListPublicChannelsQueryHandler(IAppDbContext dbContext)
|
||||
: IQueryHandler<ListPublicChannelsQuery, IReadOnlyList<PublicChannelDto>>
|
||||
{
|
||||
public async Task<IReadOnlyList<PublicChannelDto>> Handle(
|
||||
ListPublicChannelsQuery query,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var channels = await dbContext
|
||||
.Channels.AsNoTracking()
|
||||
.Where(c => c.IsEnabled)
|
||||
// Каналы без номера — в конец: на телевизоре порядок задаёт номер, а имя лишь
|
||||
// разрешает ничью между ненумерованными.
|
||||
.OrderBy(c => c.Number == null)
|
||||
.ThenBy(c => c.Number)
|
||||
.ThenBy(c => c.Name)
|
||||
.Select(c => new
|
||||
{
|
||||
c.Id,
|
||||
c.Slug,
|
||||
c.Name,
|
||||
c.Number,
|
||||
c.LogoImageId,
|
||||
c.LogoCorner,
|
||||
c.LogoOpacity,
|
||||
c.ShowClock,
|
||||
c.AnalogFilterStrength,
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
if (channels.Count == 0)
|
||||
return [];
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var channelIds = channels.Select(c => c.Id).ToList();
|
||||
|
||||
// Что идёт прямо сейчас на каждом канале (программа) — для постера-обложки плитки.
|
||||
var currentByChannel = await dbContext
|
||||
.ScheduleEntries.AsNoTracking()
|
||||
.Where(e =>
|
||||
channelIds.Contains(e.ChannelId)
|
||||
&& e.Kind == ScheduleEntryKind.Program
|
||||
&& e.StartsAtUtc <= now
|
||||
&& e.EndsAtUtc > now
|
||||
&& e.ShowId != null
|
||||
)
|
||||
.Select(e => new { e.ChannelId, ShowId = e.ShowId!.Value })
|
||||
.ToListAsync(cancellationToken);
|
||||
var currentShowByChannel = currentByChannel
|
||||
.GroupBy(x => x.ChannelId)
|
||||
.ToDictionary(g => g.Key, g => g.First().ShowId);
|
||||
|
||||
var showIds = currentShowByChannel.Values.Distinct().ToList();
|
||||
var shows = await dbContext
|
||||
.Shows.AsNoTracking()
|
||||
.Where(s => showIds.Contains(s.Id))
|
||||
.Select(s => new
|
||||
{
|
||||
s.Id,
|
||||
s.Name,
|
||||
s.PosterImageId,
|
||||
})
|
||||
.ToDictionaryAsync(s => s.Id, cancellationToken);
|
||||
|
||||
return channels
|
||||
.Select(c =>
|
||||
{
|
||||
var showId = currentShowByChannel.GetValueOrDefault(c.Id);
|
||||
var show = showId != Guid.Empty ? shows.GetValueOrDefault(showId) : null;
|
||||
return new PublicChannelDto(
|
||||
c.Id,
|
||||
c.Slug,
|
||||
c.Name,
|
||||
c.Number,
|
||||
show is null ? null : showId,
|
||||
show?.Name,
|
||||
show?.PosterImageId,
|
||||
c.LogoImageId,
|
||||
c.LogoCorner,
|
||||
c.LogoOpacity,
|
||||
c.ShowClock,
|
||||
c.AnalogFilterStrength
|
||||
);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,42 @@
|
||||
using TeleWave.Domain.Broadcast;
|
||||
|
||||
namespace TeleWave.Application.Streaming;
|
||||
|
||||
public sealed record PublicChannelDto(
|
||||
Guid Id,
|
||||
string Slug,
|
||||
string Name,
|
||||
Guid? CurrentShowId,
|
||||
string? CurrentShowName,
|
||||
Guid? CurrentShowPosterImageId
|
||||
);
|
||||
|
||||
/// <summary>Запись публичного телегида с метаданными (без имён файлов и номеров серий).</summary>
|
||||
public sealed record PublicEpgEntryDto(
|
||||
ScheduleEntryKind Kind,
|
||||
DateTimeOffset StartsAtUtc,
|
||||
DateTimeOffset EndsAtUtc,
|
||||
Guid? ShowId,
|
||||
string? ShowName,
|
||||
Guid? ShowPosterImageId,
|
||||
Guid? EpisodeId,
|
||||
string? EpisodeTitle,
|
||||
string? EpisodeOverview,
|
||||
Guid? EpisodeStillImageId
|
||||
);
|
||||
|
||||
public sealed record LiveSegmentDto(Guid AssetId, int LocalIndex, bool Discontinuity);
|
||||
|
||||
public sealed record LivePlaylistDto(
|
||||
long MediaSequence,
|
||||
int TargetDuration,
|
||||
IReadOnlyList<LiveSegmentDto> Segments
|
||||
);
|
||||
using TeleWave.Domain.Broadcast;
|
||||
|
||||
namespace TeleWave.Application.Streaming;
|
||||
|
||||
public sealed record PublicChannelDto(
|
||||
Guid Id,
|
||||
string Slug,
|
||||
string Name,
|
||||
/// <summary>Номер канала для переключения по номерам или null, если не задан.</summary>
|
||||
int? Number,
|
||||
Guid? CurrentShowId,
|
||||
string? CurrentShowName,
|
||||
Guid? CurrentShowPosterImageId,
|
||||
/// <summary>Что рисовать поверх картинки — оверлеи считает клиент, ffmpeg их не касается.</summary>
|
||||
Guid? LogoImageId,
|
||||
LogoCorner LogoCorner,
|
||||
double LogoOpacity,
|
||||
bool ShowClock,
|
||||
double AnalogFilterStrength
|
||||
);
|
||||
|
||||
/// <summary>Запись публичного телегида с метаданными (без имён файлов и номеров серий).</summary>
|
||||
public sealed record PublicEpgEntryDto(
|
||||
ScheduleEntryKind Kind,
|
||||
DateTimeOffset StartsAtUtc,
|
||||
DateTimeOffset EndsAtUtc,
|
||||
Guid? ShowId,
|
||||
string? ShowName,
|
||||
Guid? ShowPosterImageId,
|
||||
Guid? EpisodeId,
|
||||
string? EpisodeTitle,
|
||||
string? EpisodeOverview,
|
||||
Guid? EpisodeStillImageId
|
||||
);
|
||||
|
||||
public sealed record LiveSegmentDto(Guid AssetId, int LocalIndex, bool Discontinuity);
|
||||
|
||||
public sealed record LivePlaylistDto(
|
||||
long MediaSequence,
|
||||
int TargetDuration,
|
||||
IReadOnlyList<LiveSegmentDto> Segments
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user