diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutShuffleSequenceHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutShuffleSequenceHandler.cs new file mode 100644 index 00000000..1568d174 --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutShuffleSequenceHandler.cs @@ -0,0 +1,51 @@ +using ErsatzTV.Core.Scheduling.YamlScheduling.Models; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; + +public class YamlPlayoutShuffleSequenceHandler : IYamlPlayoutHandler +{ + public bool Reset => false; + + public Task Handle( + YamlPlayoutContext context, + YamlPlayoutInstruction instruction, + ILogger logger, + CancellationToken cancellationToken) + { + if (instruction is not YamlPlayoutShuffleSequenceInstruction shuffleSequenceInstruction) + { + return Task.FromResult(false); + } + + if (string.IsNullOrWhiteSpace(shuffleSequenceInstruction.ShuffleSequence)) + { + logger.LogWarning("Sequence key is required to shuffle sequence"); + return Task.FromResult(false); + } + + string sequenceKey = shuffleSequenceInstruction.ShuffleSequence; + + var groupedSequenceItems = context.Definition.Playout + .Where(i => i.SequenceKey == sequenceKey) + .GroupBy(i => i.SequenceGuid) + .ToList(); + + foreach (IGrouping grouping in groupedSequenceItems) + { + // shuffle, avoiding starting with the tail of the last shuffle + YamlPlayoutInstruction tail = grouping.Last(); + var shuffledGroup = grouping.OrderBy(_ => Guid.NewGuid()).ToList(); + while (shuffledGroup.Count > 1 && shuffledGroup.Head() == tail) + { + shuffledGroup = grouping.OrderBy(_ => Guid.NewGuid()).ToList(); + } + + int firstIndex = context.Definition.Playout.FindIndex(i => i.SequenceGuid == grouping.Key); + context.Definition.Playout.RemoveRange(firstIndex, shuffledGroup.Count); + context.Definition.Playout.InsertRange(firstIndex, shuffledGroup); + } + + return Task.FromResult(true); + } +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutInstruction.cs index ee7bf400..9411ec80 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutInstruction.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutInstruction.cs @@ -13,4 +13,7 @@ public class YamlPlayoutInstruction [YamlIgnore] public string SequenceKey { get; set; } + + [YamlIgnore] + public Guid SequenceGuid { get; set; } } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutShuffleSequenceInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutShuffleSequenceInstruction.cs new file mode 100644 index 00000000..e13eda2a --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutShuffleSequenceInstruction.cs @@ -0,0 +1,9 @@ +using YamlDotNet.Serialization; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; + +public class YamlPlayoutShuffleSequenceInstruction : YamlPlayoutInstruction +{ + [YamlMember(Alias = "shuffle_sequence", ApplyNamingConventions = false)] + public string ShuffleSequence { get; set; } +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs index 8400ec4b..57a73eef 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs @@ -131,11 +131,14 @@ public class YamlPlayoutBuilder( .Map(s => s.Items) .Flatten(); + var sequenceGuid = Guid.NewGuid(); + // insert all instructions from the sequence foreach (YamlPlayoutInstruction i in sequenceInstructions) { - // maybe used for shuffling later? + // used for shuffling i.SequenceKey = sequenceInstruction.Sequence; + i.SequenceGuid = sequenceGuid; context.Definition.Playout.Add(i); } @@ -162,6 +165,7 @@ public class YamlPlayoutBuilder( YamlPlayoutRepeatInstruction => new YamlPlayoutRepeatHandler(), YamlPlayoutWaitUntilInstruction => new YamlPlayoutWaitUntilHandler(), YamlPlayoutNewEpgGroupInstruction => new YamlPlayoutNewEpgGroupHandler(), + YamlPlayoutShuffleSequenceInstruction => new YamlPlayoutShuffleSequenceHandler(), YamlPlayoutSkipItemsInstruction => new YamlPlayoutSkipItemsHandler(), // content handlers @@ -207,6 +211,7 @@ public class YamlPlayoutBuilder( { "pad_to_next", typeof(YamlPlayoutPadToNextInstruction) }, { "repeat", typeof(YamlPlayoutRepeatInstruction) }, { "sequence", typeof(YamlPlayoutSequenceInstruction) }, + { "shuffle_sequence", typeof(YamlPlayoutShuffleSequenceInstruction) }, { "skip_items", typeof(YamlPlayoutSkipItemsInstruction) }, { "wait_until", typeof(YamlPlayoutWaitUntilInstruction) } };