using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Media;
namespace TeleWave.Infrastructure.Media;
/// Захваченная на рендер заставка: спецификация собирается уже в самой работе.
internal sealed record BumperRenderJob(Guid AssetId);
///
/// Асинхронно рендерит ТВ-заставки расписания: планировщик лишь создаёт ассет (Source=Generated) в
/// статусе Pending и кэш-строку , а сам ffmpeg крутится здесь,
/// вне тика планировщика и его транзакции. Захват работы и устойчивость к рестарту — в
/// . До готовности ассета плейлист отдаёт филлер.
///
/// Рендер строго последовательный: ffmpeg заставки короткий, а параллелить его смысла нет —
/// очередь разбирается быстрее, чем планировщик успевает её пополнять.
///
internal sealed class BumperRenderBackgroundService(
IBumperRenderQueue queue,
IServiceScopeFactory scopeFactory,
IBumperRenderer renderer,
ILogger logger
) : MediaClaimingBackgroundService(scopeFactory, logger)
{
protected override bool HandlesGenerated => true;
protected override string LoopErrorMessage => "Ошибка цикла рендера заставок";
protected override ValueTask WaitForWorkAsync(CancellationToken cancellationToken) =>
queue.WaitAsync(cancellationToken);
protected override BumperRenderJob ToJob(MediaAsset asset) => new(asset.Id);
protected override async Task ProcessAsync(
BumperRenderJob job,
CancellationToken cancellationToken
)
{
try
{
var spec = await WithScopeAsync(loader =>
loader.LoadAsync(job.AssetId, cancellationToken)
);
if (spec is null)
{
await FailAsync(
job.AssetId,
"Не удалось восстановить спецификацию заставки",
cancellationToken
);
return;
}
var render = await renderer.RenderAsync(job.AssetId, spec, cancellationToken);
await WithAssetAsync(
job.AssetId,
asset =>
asset.MarkReady(
new MediaReadyInfo(
render.Duration,
render.SegmentSeconds,
render.SegmentCount,
render.Width,
render.Height,
"h264",
"aac",
render.RelativePath
)
),
cancellationToken
);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
logger.LogError(ex, "Рендер заставки {AssetId} провалился", job.AssetId);
await FailAsync(job.AssetId, ex.Message, CancellationToken.None);
}
}
}