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). Всё рисуется на клиенте поверх