namespace TeleWave.Domain.Broadcast;
///
/// Блок ТВ-заставки канала: свой звук + своё оформление (цвета, опциональная фон-картинка) + набор
/// подблоков () с разным текстом и правилом показа. Длительность заставки — по
/// длине звука (выравнивается на сегмент при рендере). Общий для канала — только шрифт.
///
/// Первый блок ( == 0) — дефолтный, не удаляется; если звук в нём не загружен,
/// рендер синтезирует джингл по умолчанию.
///
public class BumperTemplate
{
private readonly List _variants = [];
public Guid Id { get; private set; }
public Guid ChannelId { get; private set; }
/// Порядковый номер (0 — дефолтный блок). Используется ротацией и как признак дефолта.
public int Position { get; private set; }
public string Name { get; private set; } = string.Empty;
// ── Оформление блока (цвета — в нотации ffmpeg: 0xRRGGBB или имя) ──
public string BackgroundColor { get; private set; } = DefaultBackgroundColor;
public string BackgroundColor2 { get; private set; } = DefaultBackgroundColor2;
public string AccentColor { get; private set; } = DefaultAccentColor;
public string TextColor { get; private set; } = DefaultTextColor;
/// Фон-картинка блока — ссылка на запись реестра изображений или null (тогда фон градиент/постер).
public Guid? BackgroundImageId { get; private set; }
/// Расширение загруженного звука (с точкой) или null — тогда синтезируется джингл.
public string? AudioExtension { get; private set; }
/// Длина загруженного звука в секундах (замер ffprobe) или null, если звука нет.
public double? AudioDurationSeconds { get; private set; }
/// Версия файлов блока (звук/фон). Входит в кэш-ключ рендера — замена файла пересобирает заставки.
public int Revision { get; private set; }
public DateTimeOffset CreatedAt { get; private set; }
public const string DefaultBackgroundColor = "0x0b1020";
public const string DefaultBackgroundColor2 = "0x1e293b";
public const string DefaultAccentColor = "0x38bdf8";
public const string DefaultTextColor = "white";
public bool IsDefault => Position == 0;
/// Подблоки (текст-варианты); порядок — по .
public IReadOnlyList Variants => _variants;
private const string DefaultVariantName = "Текст 1";
private BumperTemplate() { }
internal static BumperTemplate Create(Guid channelId, int position, string name)
{
var template = new BumperTemplate
{
Id = Guid.NewGuid(),
ChannelId = channelId,
Position = position,
Name = name,
BackgroundColor = DefaultBackgroundColor,
BackgroundColor2 = DefaultBackgroundColor2,
AccentColor = DefaultAccentColor,
TextColor = DefaultTextColor,
BackgroundImageId = null,
AudioExtension = null,
AudioDurationSeconds = null,
Revision = 0,
CreatedAt = DateTimeOffset.UtcNow,
};
// Дефолтный подблок «Сейчас/Далее», показывается на смене шоу.
template._variants.Add(
BumperTextVariant.Create(template.Id, 0, DefaultVariantName, BumperTrigger.OnShowChange)
);
return template;
}
public BumperTextVariant AddVariant(string name)
{
var nextPosition = _variants.Count == 0 ? 0 : _variants.Max(v => v.Position) + 1;
var variant = BumperTextVariant.Create(Id, nextPosition, name, BumperTrigger.OnShowChange);
_variants.Add(variant);
return variant;
}
public BumperTextVariant? FindVariant(Guid variantId) =>
_variants.FirstOrDefault(v => v.Id == variantId);
/// Удалить подблок. Последний подблок удалить нельзя (нужен хотя бы один) — вернёт false.
public bool RemoveVariant(Guid variantId)
{
if (_variants.Count <= 1)
return false;
var variant = _variants.FirstOrDefault(v => v.Id == variantId);
if (variant is null)
return false;
_variants.Remove(variant);
return true;
}
/// Обновить имя и цвета блока. Цвета — в нотации ffmpeg (0xRRGGBB или имя).
public void UpdateStyle(
string name,
string backgroundColor,
string backgroundColor2,
string accentColor,
string textColor
)
{
Name = name;
BackgroundColor = backgroundColor;
BackgroundColor2 = backgroundColor2;
AccentColor = accentColor;
TextColor = textColor;
}
/// Отметить загруженный звук (extension — с точкой, нижний регистр) и его длину. Меняет ревизию.
public void SetAudio(string extension, double durationSeconds)
{
AudioExtension = extension;
AudioDurationSeconds = durationSeconds > 0 ? durationSeconds : null;
Revision++;
}
public void ClearAudio()
{
if (AudioExtension is null && AudioDurationSeconds is null)
return;
AudioExtension = null;
AudioDurationSeconds = null;
Revision++;
}
/// Привязать фон-картинку блока (ссылка на реестр изображений). Меняет ревизию.
public void SetBackgroundImage(Guid imageId)
{
BackgroundImageId = imageId;
Revision++;
}
public void ClearBackgroundImage()
{
if (BackgroundImageId is null)
return;
BackgroundImageId = null;
Revision++;
}
}