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); } } }