Refactor bumper preview functionality: update API endpoints to support rendering previews for all bumper sub-blocks, modify related query and handler to generate individual asset previews, and enhance frontend components for improved user experience with variant-specific previews. Update translations for consistency in terminology.
This commit is contained in:
@@ -85,11 +85,11 @@ public static class ChannelEndpoints
|
||||
.MapPost("/{id:guid}/bumper/templates/{templateId:guid}/preview", RenderPreview)
|
||||
.Produces(StatusCodes.Status204NoContent);
|
||||
admin.MapGet(
|
||||
"/{id:guid}/bumper/templates/{templateId:guid}/preview/index.m3u8",
|
||||
"/{id:guid}/bumper/templates/{templateId:guid}/preview/{variantId:guid}/index.m3u8",
|
||||
PreviewPlaylist
|
||||
);
|
||||
admin.MapGet(
|
||||
"/{id:guid}/bumper/templates/{templateId:guid}/preview/{file}",
|
||||
"/{id:guid}/bumper/templates/{templateId:guid}/preview/{variantId:guid}/{file}",
|
||||
PreviewSegment
|
||||
);
|
||||
|
||||
@@ -452,10 +452,10 @@ public static class ChannelEndpoints
|
||||
return result.IsSuccess ? Results.NoContent() : result.ToHttpResult();
|
||||
}
|
||||
|
||||
/// <summary>Плейлист превью: переписываем ffmpeg-index.m3u8, направляя сегменты на admin-роут.</summary>
|
||||
private static IResult PreviewPlaylist(Guid id, Guid templateId, MediaPathResolver paths)
|
||||
/// <summary>Плейлист превью подблока: переписываем ffmpeg-index.m3u8, направляя сегменты на admin-роут.</summary>
|
||||
private static IResult PreviewPlaylist(Guid id, Guid templateId, Guid variantId, MediaPathResolver paths)
|
||||
{
|
||||
var previewId = BumperPreview.AssetId(templateId);
|
||||
var previewId = BumperPreview.AssetId(variantId);
|
||||
string indexPath;
|
||||
try
|
||||
{
|
||||
@@ -468,7 +468,7 @@ public static class ChannelEndpoints
|
||||
if (!File.Exists(indexPath))
|
||||
return Results.NotFound();
|
||||
|
||||
var baseUrl = $"/api/admin/channels/{id}/bumper/templates/{templateId}/preview/";
|
||||
var baseUrl = $"/api/admin/channels/{id}/bumper/templates/{templateId}/preview/{variantId}/";
|
||||
var sb = new StringBuilder();
|
||||
foreach (var line in File.ReadLines(indexPath))
|
||||
{
|
||||
@@ -483,12 +483,18 @@ public static class ChannelEndpoints
|
||||
return Results.Text(sb.ToString(), "application/vnd.apple.mpegurl");
|
||||
}
|
||||
|
||||
private static IResult PreviewSegment(Guid id, Guid templateId, string file, MediaPathResolver paths)
|
||||
private static IResult PreviewSegment(
|
||||
Guid id,
|
||||
Guid templateId,
|
||||
Guid variantId,
|
||||
string file,
|
||||
MediaPathResolver paths
|
||||
)
|
||||
{
|
||||
if (!BumperSegmentFileName.IsMatch(file))
|
||||
return Results.NotFound();
|
||||
|
||||
var previewId = BumperPreview.AssetId(templateId);
|
||||
var previewId = BumperPreview.AssetId(variantId);
|
||||
string path;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -4,8 +4,7 @@ using TeleWave.Application.Common.Models;
|
||||
namespace TeleWave.Application.Broadcast.Bumpers;
|
||||
|
||||
/// <summary>
|
||||
/// Синхронно рендерит пример заставки блока (с примерными названиями шоу) и возвращает id
|
||||
/// ассета-превью. БД не меняет — это read-side генерация артефакта для предпросмотра.
|
||||
/// Синхронно рендерит примеры всех подблоков блока (с примерными названиями шоу). Каждый подблок —
|
||||
/// в свой ассет-превью (id детерминирован по подблоку). БД не меняет — read-side генерация артефактов.
|
||||
/// </summary>
|
||||
public sealed record RenderBumperPreviewQuery(Guid ChannelId, Guid TemplateId)
|
||||
: IQuery<Result<Guid>>;
|
||||
public sealed record RenderBumperPreviewQuery(Guid ChannelId, Guid TemplateId) : IQuery<Result>;
|
||||
|
||||
+36
-35
@@ -15,7 +15,7 @@ public sealed class RenderBumperPreviewQueryHandler(
|
||||
IImageStore imageStore,
|
||||
IOptions<BumperOptions> bumperOptions,
|
||||
IOptions<StreamingOptions> streamingOptions
|
||||
) : IQueryHandler<RenderBumperPreviewQuery, Result<Guid>>
|
||||
) : IQueryHandler<RenderBumperPreviewQuery, Result>
|
||||
{
|
||||
private readonly BumperOptions _bumper = bumperOptions.Value;
|
||||
private readonly int _segmentSeconds = Math.Max(1, streamingOptions.Value.SegmentSeconds);
|
||||
@@ -23,7 +23,7 @@ public sealed class RenderBumperPreviewQueryHandler(
|
||||
/// <summary>Длительность заставки без загруженного звука (сек) — как в генераторе.</summary>
|
||||
private const int DefaultBumperDurationSeconds = 8;
|
||||
|
||||
public async Task<Result<Guid>> Handle(
|
||||
public async Task<Result> Handle(
|
||||
RenderBumperPreviewQuery query,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
@@ -35,21 +35,17 @@ public sealed class RenderBumperPreviewQueryHandler(
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(c => c.Id == query.ChannelId, cancellationToken);
|
||||
if (channel is null)
|
||||
return Result.Failure<Guid>(ChannelErrors.NotFound);
|
||||
return Result.Failure(ChannelErrors.NotFound);
|
||||
|
||||
var template = channel.BumperTemplates.FirstOrDefault(t => t.Id == query.TemplateId);
|
||||
if (template is null)
|
||||
return Result.Failure<Guid>(ChannelErrors.BumperTemplateNotFound);
|
||||
|
||||
// Превью показываем по первому подблоку (стиль/звук блока + его текст).
|
||||
var variant = template.Variants.OrderBy(v => v.Position).FirstOrDefault();
|
||||
if (variant is null)
|
||||
return Result.Failure<Guid>(ChannelErrors.BumperTemplateNotFound);
|
||||
return Result.Failure(ChannelErrors.BumperTemplateNotFound);
|
||||
|
||||
var (fromName, toName) = await SampleNamesAsync(channel, cancellationToken);
|
||||
var free = variant.Kind == BumperTextKind.Free;
|
||||
var fontFile =
|
||||
channel.BumperFont == BumperFont.Serif ? _bumper.FontFileSerif : _bumper.FontFileSans;
|
||||
|
||||
// Фон блока — из общего реестра по id.
|
||||
// Фон блока — из общего реестра по id (общий для всех подблоков).
|
||||
string? backgroundPath = null;
|
||||
if (template.BackgroundImageId is { } bgId)
|
||||
{
|
||||
@@ -66,32 +62,37 @@ public sealed class RenderBumperPreviewQueryHandler(
|
||||
var aligned = (int)(
|
||||
Math.Ceiling(Math.Max(_segmentSeconds, seconds) / _segmentSeconds) * _segmentSeconds
|
||||
);
|
||||
var audioPath = storage.AudioPath(template.Id, template.AudioExtension);
|
||||
|
||||
var spec = new BumperRenderSpec(
|
||||
aligned,
|
||||
_bumper.Width,
|
||||
_bumper.Height,
|
||||
template.BackgroundColor,
|
||||
template.BackgroundColor2,
|
||||
template.AccentColor,
|
||||
template.TextColor,
|
||||
channel.BumperFont == BumperFont.Serif ? _bumper.FontFileSerif : _bumper.FontFileSans,
|
||||
free ? "" : variant.NowLabel,
|
||||
free ? "" : fromName,
|
||||
free ? "" : variant.NextLabel,
|
||||
free ? "" : toName,
|
||||
backgroundPath,
|
||||
storage.AudioPath(template.Id, template.AudioExtension),
|
||||
// Постер зависит от конкретного «следующего» шоу — в превью не подставляем.
|
||||
null,
|
||||
free,
|
||||
variant.Line1,
|
||||
variant.Line2
|
||||
);
|
||||
// Рендерим каждый подблок в свой ассет-превью (id по подблоку).
|
||||
foreach (var variant in template.Variants.OrderBy(v => v.Position))
|
||||
{
|
||||
var free = variant.Kind == BumperTextKind.Free;
|
||||
var spec = new BumperRenderSpec(
|
||||
aligned,
|
||||
_bumper.Width,
|
||||
_bumper.Height,
|
||||
template.BackgroundColor,
|
||||
template.BackgroundColor2,
|
||||
template.AccentColor,
|
||||
template.TextColor,
|
||||
fontFile,
|
||||
free ? "" : variant.NowLabel,
|
||||
free ? "" : fromName,
|
||||
free ? "" : variant.NextLabel,
|
||||
free ? "" : toName,
|
||||
backgroundPath,
|
||||
audioPath,
|
||||
// Постер зависит от конкретного «следующего» шоу — в превью не подставляем.
|
||||
null,
|
||||
free,
|
||||
variant.Line1,
|
||||
variant.Line2
|
||||
);
|
||||
await renderer.RenderAsync(BumperPreview.AssetId(variant.Id), spec, cancellationToken);
|
||||
}
|
||||
|
||||
var previewId = BumperPreview.AssetId(template.Id);
|
||||
await renderer.RenderAsync(previewId, spec, cancellationToken);
|
||||
return Result.Success(previewId);
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
/// <summary>Примерные названия «из/в» — берём первые два шоу канала, иначе заглушки.</summary>
|
||||
|
||||
Reference in New Issue
Block a user