* fix hw accel for deco default filler * use custom filler kind for deco default filler
226 lines
8.6 KiB
C#
226 lines
8.6 KiB
C#
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Domain.Filler;
|
|
using ErsatzTV.Core.Domain.Scheduling;
|
|
using ErsatzTV.Core.Extensions;
|
|
using ErsatzTV.Core.Interfaces.Repositories;
|
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
|
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
|
|
|
|
public class BlockPlayoutFillerBuilder(
|
|
IMediaCollectionRepository mediaCollectionRepository,
|
|
ITelevisionRepository televisionRepository,
|
|
IArtistRepository artistRepository,
|
|
ILogger<BlockPlayoutFillerBuilder> logger) : IBlockPlayoutFillerBuilder
|
|
{
|
|
private static readonly JsonSerializerSettings JsonSettings = new()
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
};
|
|
|
|
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
|
|
{
|
|
if (mode is PlayoutBuildMode.Reset)
|
|
{
|
|
// remove all playout items with type filler
|
|
// except block items that are hidden from the guide (guide mode)
|
|
var toRemove = playout.Items
|
|
.Where(pi => pi.FillerKind is not FillerKind.None and not FillerKind.GuideMode)
|
|
.ToList();
|
|
foreach (PlayoutItem playoutItem in toRemove)
|
|
{
|
|
BlockPlayoutChangeDetection.RemoveItemAndHistory(playout, playoutItem);
|
|
}
|
|
}
|
|
|
|
var collectionEnumerators = new Dictionary<CollectionKey, IMediaCollectionEnumerator>();
|
|
|
|
// find all unscheduled periods
|
|
var queue = new Queue<PlayoutItem>(playout.Items);
|
|
while (queue.Count > 1)
|
|
{
|
|
PlayoutItem one = queue.Dequeue();
|
|
PlayoutItem two = queue.Peek();
|
|
|
|
DateTimeOffset start = one.FinishOffset;
|
|
DateTimeOffset finish = two.StartOffset;
|
|
|
|
if (start == finish)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find applicable deco
|
|
foreach (Deco deco in GetDecoFor(playout, start))
|
|
{
|
|
if (!HasDefaultFiller(deco))
|
|
continue;
|
|
|
|
var collectionKey = CollectionKey.ForDecoDefaultFiller(deco);
|
|
string historyKey = HistoryDetails.ForDefaultFiller(deco);
|
|
|
|
// load collection items from db on demand
|
|
if (!collectionEnumerators.TryGetValue(collectionKey, out IMediaCollectionEnumerator enumerator))
|
|
{
|
|
List<MediaItem> collectionItems = await MediaItemsForCollection.Collect(
|
|
mediaCollectionRepository,
|
|
televisionRepository,
|
|
artistRepository,
|
|
collectionKey);
|
|
|
|
enumerator = BlockPlayoutEnumerator.Shuffle(
|
|
collectionItems,
|
|
start,
|
|
playout,
|
|
deco,
|
|
historyKey);
|
|
|
|
if (enumerator.Count == 0)
|
|
{
|
|
logger.LogWarning(
|
|
"Block filler contains empty collection {@Key}; no filler will be scheduled",
|
|
collectionKey);
|
|
}
|
|
|
|
collectionEnumerators.Add(collectionKey, enumerator);
|
|
}
|
|
|
|
// skip this deco if the collection has no items
|
|
if (enumerator.Count == 0)
|
|
continue;
|
|
|
|
DateTimeOffset current = start;
|
|
var pastTime = false;
|
|
while (current < finish)
|
|
{
|
|
foreach (MediaItem mediaItem in enumerator.Current)
|
|
{
|
|
TimeSpan itemDuration = DurationForMediaItem(mediaItem);
|
|
|
|
// add filler from deco to unscheduled period
|
|
var filler = new PlayoutItem
|
|
{
|
|
MediaItemId = mediaItem.Id,
|
|
Start = current.UtcDateTime,
|
|
Finish = current.UtcDateTime + itemDuration,
|
|
InPoint = TimeSpan.Zero,
|
|
OutPoint = itemDuration,
|
|
|
|
// FillerKind.Fallback will loop and avoid hw accel, so don't use that
|
|
FillerKind = FillerKind.DecoDefault,
|
|
|
|
CollectionKey = JsonConvert.SerializeObject(collectionKey, JsonSettings),
|
|
GuideStart = one.GuideStart,
|
|
GuideFinish = one.GuideFinish,
|
|
GuideGroup = one.GuideGroup
|
|
};
|
|
|
|
if (filler.FinishOffset > finish)
|
|
{
|
|
if (deco.DefaultFillerTrimToFit)
|
|
{
|
|
filler.Finish = finish.UtcDateTime;
|
|
filler.OutPoint = filler.Finish - filler.Start;
|
|
}
|
|
else
|
|
{
|
|
pastTime = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
playout.Items.Add(filler);
|
|
|
|
// create a playout history record
|
|
var nextHistory = new PlayoutHistory
|
|
{
|
|
PlayoutId = playout.Id,
|
|
PlaybackOrder = PlaybackOrder.Shuffle,
|
|
Index = enumerator.State.Index,
|
|
When = current.UtcDateTime,
|
|
Key = historyKey,
|
|
Details = HistoryDetails.ForMediaItem(mediaItem)
|
|
};
|
|
|
|
playout.PlayoutHistory.Add(nextHistory);
|
|
|
|
current += itemDuration;
|
|
enumerator.MoveNext();
|
|
}
|
|
|
|
if (pastTime)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return playout;
|
|
}
|
|
|
|
private static Option<Deco> GetDecoFor(Playout playout, DateTimeOffset start)
|
|
{
|
|
Option<PlayoutTemplate> maybeTemplate = PlayoutTemplateSelector.GetPlayoutTemplateFor(playout.Templates, start);
|
|
foreach (PlayoutTemplate template in maybeTemplate)
|
|
{
|
|
if (template.DecoTemplate is not null)
|
|
{
|
|
foreach (DecoTemplateItem decoTemplateItem in template.DecoTemplate.Items)
|
|
{
|
|
if (decoTemplateItem.StartTime <= start.TimeOfDay && decoTemplateItem.EndTime == TimeSpan.Zero || decoTemplateItem.EndTime > start.TimeOfDay)
|
|
{
|
|
switch (decoTemplateItem.Deco.DefaultFillerMode)
|
|
{
|
|
case DecoMode.Inherit:
|
|
return Optional(playout.Deco);
|
|
case DecoMode.Override:
|
|
return decoTemplateItem.Deco;
|
|
case DecoMode.Disable:
|
|
default:
|
|
return Option<Deco>.None;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Optional(playout.Deco);
|
|
}
|
|
|
|
private static bool HasDefaultFiller(Deco deco)
|
|
{
|
|
switch (deco.DefaultFillerCollectionType)
|
|
{
|
|
case ProgramScheduleItemCollectionType.Collection:
|
|
return deco.DefaultFillerCollectionId.HasValue;
|
|
case ProgramScheduleItemCollectionType.TelevisionShow:
|
|
return deco.DefaultFillerMediaItemId.HasValue;
|
|
case ProgramScheduleItemCollectionType.TelevisionSeason:
|
|
return deco.DefaultFillerMediaItemId.HasValue;
|
|
case ProgramScheduleItemCollectionType.Artist:
|
|
return deco.DefaultFillerMediaItemId.HasValue;
|
|
case ProgramScheduleItemCollectionType.MultiCollection:
|
|
return deco.DefaultFillerMultiCollectionId.HasValue;
|
|
case ProgramScheduleItemCollectionType.SmartCollection:
|
|
return deco.DefaultFillerSmartCollectionId.HasValue;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
}
|