using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using Microsoft.Extensions.Logging; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache) : IYamlPlayoutHandler { public bool Reset => false; public abstract Task Handle( YamlPlayoutContext context, YamlPlayoutInstruction instruction, PlayoutBuildMode mode, Func executeSequence, ILogger logger, CancellationToken cancellationToken); protected async Task> GetContentEnumerator( YamlPlayoutContext context, string contentKey, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(contentKey)) { return Option.None; } Option maybeEnumerator = await enumeratorCache.GetCachedEnumeratorForContent( context, contentKey, cancellationToken); if (maybeEnumerator.IsNone) { if (!enumeratorCache.MissingContentKeys.Contains(contentKey)) { logger.LogWarning("Unable to locate content with key {Key}", contentKey); enumeratorCache.MissingContentKeys.Add(contentKey); } } return maybeEnumerator; } protected static List GetHistoryForItem( YamlPlayoutContext context, string contentKey, IMediaCollectionEnumerator enumerator, PlayoutItem playoutItem, MediaItem mediaItem, ILogger logger) { int index = context.Definition.Content.FindIndex(c => c.Key == contentKey); if (index < 0) { logger.LogDebug("Unable to find history for content matching key {ContentKey}", contentKey); return []; } YamlPlayoutContentItem contentItem = context.Definition.Content[index]; if (!Enum.TryParse(contentItem.Order, true, out PlaybackOrder playbackOrder)) { logger.LogDebug( "Unable to find history for content matching playback order {PlaybackOrder}", contentItem.Order); return []; } var result = new List(); string historyKey = HistoryDetails.KeyForYamlContent(contentItem); if (enumerator is PlaylistEnumerator playlistEnumerator) { // create a playout history record var nextHistory = new PlayoutHistory { PlayoutId = context.Playout.Id, PlaybackOrder = playbackOrder, Index = playlistEnumerator.EnumeratorIndex, When = playoutItem.StartOffset.UtcDateTime, Finish = playoutItem.FinishOffset.UtcDateTime, Key = historyKey, Details = HistoryDetails.ForMediaItem(mediaItem) }; result.Add(nextHistory); for (var i = 0; i < playlistEnumerator.ChildEnumerators.Count; i++) { (IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) = playlistEnumerator.ChildEnumerators[i]; bool isCurrentChild = i == playlistEnumerator.EnumeratorIndex; foreach (MediaItem currentMediaItem in childEnumerator.Current) { // create a playout history record var childHistory = new PlayoutHistory { PlayoutId = context.Playout.Id, PlaybackOrder = playbackOrder, Index = childEnumerator.State.Index, When = playoutItem.StartOffset.UtcDateTime, Finish = playoutItem.FinishOffset.UtcDateTime, Key = historyKey, ChildKey = HistoryDetails.KeyForCollectionKey(collectionKey), IsCurrentChild = isCurrentChild, Details = HistoryDetails.ForMediaItem(currentMediaItem) }; result.Add(childHistory); } } } else { // create a playout history record var nextHistory = new PlayoutHistory { PlayoutId = context.Playout.Id, PlaybackOrder = playbackOrder, Index = enumerator.State.Index, When = playoutItem.StartOffset.UtcDateTime, Finish = playoutItem.FinishOffset.UtcDateTime, Key = historyKey, Details = HistoryDetails.ForMediaItem(mediaItem) }; result.Add(nextHistory); } return result; } protected static TimeSpan DurationForMediaItem(MediaItem mediaItem) { if (mediaItem is Image image) { return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds); } MediaVersion version = mediaItem.GetHeadVersion(); return version.Duration; } protected static FillerKind GetFillerKind(YamlPlayoutInstruction instruction, YamlPlayoutContext context) { if (!string.IsNullOrWhiteSpace(instruction.FillerKind) && Enum.TryParse(instruction.FillerKind, true, out FillerKind result)) { return result; } foreach (FillerKind fillerKind in context.GetFillerKind()) { return fillerKind; } return FillerKind.None; } protected static List ChaptersForMediaItem(MediaItem mediaItem) { MediaVersion version = mediaItem.GetHeadVersion(); return Optional(version.Chapters).Flatten().OrderBy(c => c.StartTime).ToList(); } protected static async Task AddItemAndMidRoll(YamlPlayoutContext context, PlayoutItem playoutItem, Func executeSequence) { List itemChapters = ChaptersForMediaItem(playoutItem.MediaItem); Option maybeMidRollSequence = context.GetMidRollSequence(); if (itemChapters.Count < 2 || maybeMidRollSequence.IsNone) { context.Playout.Items.Add(playoutItem); context.CurrentTime += playoutItem.OutPoint - playoutItem.InPoint; } else { foreach (var midRollSequence in maybeMidRollSequence) { var filteredChapters = FillerExpression.FilterChapters( midRollSequence.Expression, itemChapters, playoutItem); if (filteredChapters.Count < 2) { context.Playout.Items.Add(playoutItem); context.CurrentTime += playoutItem.OutPoint - playoutItem.InPoint; } else { for (var j = 0; j < filteredChapters.Count; j++) { var nextItem = playoutItem.ForChapter(filteredChapters[j]); nextItem.Start = context.CurrentTime.UtcDateTime; nextItem.Finish = context.CurrentTime.UtcDateTime + (nextItem.OutPoint - nextItem.InPoint); context.Playout.Items.Add(nextItem); context.CurrentTime += nextItem.OutPoint - nextItem.InPoint; if (j < filteredChapters.Count - 1) { context.PushFillerKind(FillerKind.MidRoll); await executeSequence(midRollSequence.Sequence); context.PopFillerKind(); } } } } } } }