diff --git a/backend/src/TeleWave.Api/Endpoints/ChannelEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/ChannelEndpoints.cs index 370a6d8..9262621 100644 --- a/backend/src/TeleWave.Api/Endpoints/ChannelEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/ChannelEndpoints.cs @@ -156,7 +156,13 @@ public static class ChannelEndpoints ) { var result = await sender.Send( - new UpdateChannelSettingsCommand(id, body.Name, body.IsEnabled, body.FillerAssetId), + new UpdateChannelSettingsCommand( + id, + body.Name, + body.IsEnabled, + body.FillerAssetId, + body.IconImageId + ), cancellationToken ); return result.ToHttpResult(); @@ -187,7 +193,12 @@ public sealed record UpdateChannelTimeBody( TimeOnly DayStartTime ); -public sealed record UpdateChannelSettingsBody(string Name, bool IsEnabled, Guid? FillerAssetId); +public sealed record UpdateChannelSettingsBody( + string Name, + bool IsEnabled, + Guid? FillerAssetId, + Guid? IconImageId +); /// Оверлеи и аналоговый фильтр — как канал выглядит у зрителя (см. 6.8). public sealed record UpdateViewerSettingsBody( diff --git a/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs index bd5b475..d09024e 100644 --- a/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs @@ -21,6 +21,9 @@ public static class IptvEndpoints /// Сколько суток программы класть в XMLTV: горизонт ленты и так неделя. private static readonly TimeSpan GuideWindow = TimeSpan.FromDays(7); + /// Заглушка иконки канала, когда своя не задана: фавикон сайта из wwwroot. + private const string FaviconPath = "/favicon.svg"; + public static IEndpointRouteBuilder MapIptvEndpoints(this IEndpointRouteBuilder app) { var iptv = app.MapGroup("/api/iptv").WithTags("IPTV"); @@ -109,9 +112,8 @@ public static class IptvEndpoints foreach (var channel in channels) { - var logo = channel.LogoImageId is { } imageId - ? $" tvg-logo=\"{origin}/api/images/{imageId}\"" - : string.Empty; + // tvg-logo ставим всегда: своя иконка либо фавикон-заглушка — иначе в плеере пустой квадрат. + var logo = $" tvg-logo=\"{ChannelIcon(origin, channel.IconImageId)}\""; var number = channel.Number is { } value ? $" tvg-chno=\"{value.ToString(CultureInfo.InvariantCulture)}\"" : string.Empty; @@ -189,7 +191,10 @@ public static class IptvEndpoints "display-name", number.ToString(CultureInfo.InvariantCulture) ); - WriteIcon(writer, channel.LogoImageId, origin); + // Иконку канала пишем всегда: своя либо фавикон-заглушка (в отличие от иконок передач ниже). + writer.WriteStartElement("icon"); + writer.WriteAttributeString("src", ChannelIcon(origin, channel.IconImageId)); + writer.WriteEndElement(); writer.WriteEndElement(); } @@ -225,6 +230,10 @@ public static class IptvEndpoints writer.WriteEndElement(); } + /// URL иконки канала: своя из реестра либо фавикон-заглушка сайта (абсолютный адрес). + private static string ChannelIcon(string origin, Guid? iconImageId) => + iconImageId is { } id ? $"{origin}/api/images/{id}" : $"{origin}{FaviconPath}"; + /// Время XMLTV: YYYYMMDDHHMMSS +0000. Отдаём в UTC — плеер сдвинет сам. private static string XmltvTime(DateTimeOffset moment) => moment.ToUniversalTime().ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) diff --git a/backend/src/TeleWave.Application/Broadcast/ChannelDtos.cs b/backend/src/TeleWave.Application/Broadcast/ChannelDtos.cs index 6a5f016..aee4b31 100644 --- a/backend/src/TeleWave.Application/Broadcast/ChannelDtos.cs +++ b/backend/src/TeleWave.Application/Broadcast/ChannelDtos.cs @@ -14,6 +14,8 @@ public sealed record ChannelDto( TimeOnly DayStartTime, Guid? TemplateId, Guid? FillerAssetId, + /// Иконка канала для внешних IPTV-плееров (M3U/XMLTV); null — отдаётся заглушка. + Guid? IconImageId, /// Оверлеи и фильтр зрительской части — всё опционально (см. 6.8). ViewerSettingsDto Viewer ); diff --git a/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs b/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs index f34041b..c0013aa 100644 --- a/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs @@ -32,6 +32,7 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext) channel.DayStartTime, channel.TemplateId, channel.FillerAssetId, + channel.IconImageId, new ViewerSettingsDto( channel.LogoImageId, channel.LogoCorner, diff --git a/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommand.cs b/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommand.cs index 410578d..ccfbc9a 100644 --- a/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommand.cs +++ b/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommand.cs @@ -7,5 +7,6 @@ public sealed record UpdateChannelSettingsCommand( Guid ChannelId, string Name, bool IsEnabled, - Guid? FillerAssetId + Guid? FillerAssetId, + Guid? IconImageId ) : ICommand; diff --git a/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommandHandler.cs b/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommandHandler.cs index a437509..03b7730 100644 --- a/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommandHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/UpdateChannelSettings/UpdateChannelSettingsCommandHandler.cs @@ -30,7 +30,12 @@ public sealed class UpdateChannelSettingsCommandHandler(IAppDbContext dbContext) return Result.Failure(ChannelErrors.AssetNotFound); } - channel.UpdateSettings(command.Name, command.IsEnabled, command.FillerAssetId); + channel.UpdateSettings( + command.Name, + command.IsEnabled, + command.FillerAssetId, + command.IconImageId + ); return Result.Success(); } } diff --git a/backend/src/TeleWave.Application/Streaming/GetIptvGuide/GetIptvGuideQueryHandler.cs b/backend/src/TeleWave.Application/Streaming/GetIptvGuide/GetIptvGuideQueryHandler.cs index b95a525..b1045cd 100644 --- a/backend/src/TeleWave.Application/Streaming/GetIptvGuide/GetIptvGuideQueryHandler.cs +++ b/backend/src/TeleWave.Application/Streaming/GetIptvGuide/GetIptvGuideQueryHandler.cs @@ -25,7 +25,7 @@ public sealed class GetIptvGuideQueryHandler(IAppDbContext dbContext) c.Slug, c.Name, c.Number, - c.LogoImageId, + c.IconImageId, }) .ToListAsync(cancellationToken); if (channels.Count == 0) @@ -129,7 +129,7 @@ public sealed class GetIptvGuideQueryHandler(IAppDbContext dbContext) channel.Slug, channel.Name, channel.Number, - channel.LogoImageId, + channel.IconImageId, programmes ) ); diff --git a/backend/src/TeleWave.Application/Streaming/GetIptvGuide/IptvDtos.cs b/backend/src/TeleWave.Application/Streaming/GetIptvGuide/IptvDtos.cs index 2e9c146..3f30b2c 100644 --- a/backend/src/TeleWave.Application/Streaming/GetIptvGuide/IptvDtos.cs +++ b/backend/src/TeleWave.Application/Streaming/GetIptvGuide/IptvDtos.cs @@ -5,7 +5,7 @@ public sealed record IptvChannelDto( string Slug, string Name, int? Number, - Guid? LogoImageId, + Guid? IconImageId, IReadOnlyList Programmes ); diff --git a/backend/src/TeleWave.Domain/Broadcast/Channel.cs b/backend/src/TeleWave.Domain/Broadcast/Channel.cs index 1c26d3e..9ea86f1 100644 --- a/backend/src/TeleWave.Domain/Broadcast/Channel.cs +++ b/backend/src/TeleWave.Domain/Broadcast/Channel.cs @@ -46,6 +46,14 @@ public class Channel /// Ассет-заглушка на случай пустого расписания (аварийная подстраховка). public Guid? FillerAssetId { get; private set; } + /// + /// Иконка канала для внешних IPTV-плееров (tvg-logo в M3U и <icon> в XMLTV): ссылка + /// на реестр изображений или null. Отдельная от экранного логотипа-оверлея + /// (): тот — полупрозрачный водяной знак в углу видео, а здесь нужна + /// нормальная квадратная иконка канала. Null — плеер получит заглушку (фавикон сайта). + /// + public Guid? IconImageId { get; private set; } + // ── Зрительская часть (см. 6.8). Всё рисуется на клиенте поверх