From 9b60ff0863ee18579cecf9adec293188d40b9c12 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:33:08 -0500 Subject: [PATCH] optionally shuffle marathon groups (#1850) --- .../Scheduling/PlaylistEnumerator.cs | 46 ++++++++++++++++++- ErsatzTV.Core/Scheduling/PlayoutBuilder.cs | 1 + .../YamlScheduling/EnumeratorCache.cs | 1 + .../Models/YamlPlayoutContentMarathonItem.cs | 3 ++ .../YamlPlayoutMarathonHelper.cs | 1 + 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs b/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs index de06d7287..e72c5fb68 100644 --- a/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs @@ -12,7 +12,9 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator private int _enumeratorIndex; private System.Collections.Generic.HashSet _idsToIncludeInEPG; private IList _playAll; - private IList _sortedEnumerators; + private List _sortedEnumerators; + private bool _shufflePlaylistItems; + private CloneableRandom _random; private PlaylistEnumerator() { @@ -65,6 +67,13 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator { State.Index = 0; _remainingMediaItemIds.UnionWith(_allMediaItemIds); + + if (_shufflePlaylistItems) + { + State.Seed = _random.Next(); + _random = new CloneableRandom(State.Seed); + _sortedEnumerators = ShufflePlaylistItems(); + } } } @@ -72,13 +81,15 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator IMediaCollectionRepository mediaCollectionRepository, Dictionary> playlistItemMap, CollectionEnumeratorState state, + bool shufflePlaylistItems, CancellationToken cancellationToken) { var result = new PlaylistEnumerator { _sortedEnumerators = [], _playAll = [], - _idsToIncludeInEPG = [] + _idsToIncludeInEPG = [], + _shufflePlaylistItems = shufflePlaylistItems }; // collections should share enumerators @@ -165,6 +176,13 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator .OrderBy(identity) .HeadOrNone(); + result._random = new CloneableRandom(state.Seed); + + if (shufflePlaylistItems) + { + result._sortedEnumerators = result.ShufflePlaylistItems(); + } + result.State = new CollectionEnumeratorState { Seed = state.Seed }; result._enumeratorIndex = 0; @@ -181,4 +199,28 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator return result; } + + private List ShufflePlaylistItems() + { + if (_sortedEnumerators.Count < 3) + { + return _sortedEnumerators; + } + + IMediaCollectionEnumerator[] copy = _sortedEnumerators.ToArray(); + IMediaCollectionEnumerator last = _sortedEnumerators.Last(); + + do + { + int n = copy.Length; + while (n > 1) + { + n--; + int k = _random.Next(n + 1); + (copy[k], copy[n]) = (copy[n], copy[k]); + } + } while (copy.First() == last); + + return copy.ToList(); + } } diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index d7c5abb7e..ae4c79414 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -1020,6 +1020,7 @@ public class PlayoutBuilder : IPlayoutBuilder _mediaCollectionRepository, playlistItemMap, state, + shufflePlaylistItems: false, cancellationToken); } } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs index 6c980ca6e..5c4c91699 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs @@ -112,6 +112,7 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor mediaCollectionRepository, itemMap, state, + shufflePlaylistItems: false, cancellationToken); } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentMarathonItem.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentMarathonItem.cs index 92ecac737..dfad0bdce 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentMarathonItem.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentMarathonItem.cs @@ -13,6 +13,9 @@ public class YamlPlayoutContentMarathonItem : YamlPlayoutContentItem [YamlMember(Alias = "group_by", ApplyNamingConventions = false)] public string GroupBy { get; set; } + [YamlMember(Alias = "shuffle_groups", ApplyNamingConventions = false)] + public bool ShuffleGroups { get; set; } + [YamlMember(Alias = "item_order", ApplyNamingConventions = false)] public string ItemOrder { get; set; } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutMarathonHelper.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutMarathonHelper.cs index 2cd308dbd..0ad243b5b 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutMarathonHelper.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutMarathonHelper.cs @@ -68,6 +68,7 @@ public class YamlPlayoutMarathonHelper(IMediaCollectionRepository mediaCollectio mediaCollectionRepository, itemMap, state, + marathon.ShuffleGroups, cancellationToken); }