diff --git a/CHANGELOG.md b/CHANGELOG.md index eea4b30f1..8b6a3f958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix bug with XMLTV that caused some filler to display with primary content details - Multiple fixes for content scaling with `Nvidia`, `Qsv` and `Vaapi` accelerations - Properly scale image-based subtitles -- Abort when an infinite playout building loop is detected; proper bug fix will be released soon +- Fix bug where a schedule containing a single item (fixed start and flood) would never finish building a playout + - Logic was also added to detect infinite playout build loops in the future and stop them ### Added - Add `Preferred Audio Title` feature diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index e4a48c2ec..b868f7681 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -515,6 +515,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -523,6 +524,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemOne { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -605,6 +607,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -613,6 +616,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemOne { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -742,6 +746,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -750,6 +755,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemMultiple { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -839,6 +845,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -847,6 +854,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemOne { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -934,6 +942,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -942,6 +951,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemOne { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -1040,6 +1050,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -1048,6 +1059,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemDuration { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -1141,6 +1153,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemMultiple { + Id = 1, Index = 1, Collection = multipleCollection, CollectionId = multipleCollection.Id, @@ -1150,6 +1163,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemDuration { + Id = 2, Index = 2, Collection = dynamicCollection, CollectionId = dynamicCollection.Id, @@ -2329,6 +2343,7 @@ public class PlayoutBuilderTests { new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = floodCollection, CollectionId = floodCollection.Id, @@ -2337,6 +2352,7 @@ public class PlayoutBuilderTests }, new ProgramScheduleItemOne { + Id = 2, Index = 2, Collection = fixedCollection, CollectionId = fixedCollection.Id, @@ -2622,6 +2638,7 @@ public class PlayoutBuilderTests private static ProgramScheduleItem Flood(Collection mediaCollection, PlaybackOrder playbackOrder) => new ProgramScheduleItemFlood { + Id = 1, Index = 1, Collection = mediaCollection, CollectionId = mediaCollection.Id, diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs index 2b24a1ae9..8bd3bf90e 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs @@ -85,6 +85,98 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase playoutItems[2].FillerKind.Should().Be(FillerKind.None); playoutItems[2].CustomTitle.Should().Be("CustomTitle"); } + + [Test] + public void Should_Schedule_Single_Item_Fixed_Start_Flood() + { + Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); + + var scheduleItem = new ProgramScheduleItemFlood + { + Id = 1, + Index = 1, + Collection = collectionOne, + CollectionId = collectionOne.Id, + StartTime = TimeSpan.Zero, + PlaybackOrder = PlaybackOrder.Chronological, + TailFiller = null, + FallbackFiller = null, + CustomTitle = "CustomTitle" + }; + + var enumerator = new ChronologicalMediaCollectionEnumerator( + collectionOne.MediaItems, + new CollectionEnumeratorState()); + + var sortedScheduleItems = new List + { + scheduleItem + }; + + var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator( + sortedScheduleItems, + new CollectionEnumeratorState()); + + PlayoutBuilderState startState = StartState(scheduleItemsEnumerator); + + var scheduler = new PlayoutModeSchedulerFlood(new Mock().Object); + (PlayoutBuilderState playoutBuilderState, List playoutItems) = scheduler.Schedule( + startState, + CollectionEnumerators(scheduleItem, enumerator), + scheduleItem, + scheduleItem, + HardStop(scheduleItemsEnumerator)); + + playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(6)); + playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); + + playoutBuilderState.NextGuideGroup.Should().Be(2); // one guide group here because of custom title + playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); + playoutBuilderState.InFlood.Should().BeTrue(); + playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); + playoutBuilderState.InDurationFiller.Should().BeFalse(); + playoutBuilderState.ScheduleItemsEnumerator.State.Index.Should().Be(0); + + enumerator.State.Index.Should().Be(0); + + playoutItems.Count.Should().Be(6); + + playoutItems[0].MediaItemId.Should().Be(1); + playoutItems[0].StartOffset.Should().Be(startState.CurrentTime); + playoutItems[0].GuideGroup.Should().Be(1); + playoutItems[0].FillerKind.Should().Be(FillerKind.None); + playoutItems[0].CustomTitle.Should().Be("CustomTitle"); + + playoutItems[1].MediaItemId.Should().Be(2); + playoutItems[1].StartOffset.Should().Be(startState.CurrentTime.AddHours(1)); + playoutItems[1].GuideGroup.Should().Be(1); + playoutItems[1].FillerKind.Should().Be(FillerKind.None); + playoutItems[1].CustomTitle.Should().Be("CustomTitle"); + + playoutItems[2].MediaItemId.Should().Be(1); + playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.AddHours(2)); + playoutItems[2].GuideGroup.Should().Be(1); + playoutItems[2].FillerKind.Should().Be(FillerKind.None); + playoutItems[2].CustomTitle.Should().Be("CustomTitle"); + + playoutItems[3].MediaItemId.Should().Be(2); + playoutItems[3].StartOffset.Should().Be(startState.CurrentTime.AddHours(3)); + playoutItems[3].GuideGroup.Should().Be(1); + playoutItems[3].FillerKind.Should().Be(FillerKind.None); + playoutItems[3].CustomTitle.Should().Be("CustomTitle"); + + playoutItems[4].MediaItemId.Should().Be(1); + playoutItems[4].StartOffset.Should().Be(startState.CurrentTime.AddHours(4)); + playoutItems[4].GuideGroup.Should().Be(1); + playoutItems[4].FillerKind.Should().Be(FillerKind.None); + playoutItems[4].CustomTitle.Should().Be("CustomTitle"); + + playoutItems[5].MediaItemId.Should().Be(2); + playoutItems[5].StartOffset.Should().Be(startState.CurrentTime.AddHours(5)); + playoutItems[5].GuideGroup.Should().Be(1); + playoutItems[5].FillerKind.Should().Be(FillerKind.None); + playoutItems[5].CustomTitle.Should().Be("CustomTitle"); + } [Test] public void Should_Fill_Exactly_To_Next_Schedule_Item_Flood() diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index 355faeadf..adb585183 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -423,21 +423,21 @@ public class PlayoutBuilder : IPlayoutBuilder var schedulerDuration = new PlayoutModeSchedulerDuration(_logger); var schedulerFlood = new PlayoutModeSchedulerFlood(_logger); - var timeHash = new Dictionary(); + var timeCount = new Dictionary(); // loop until we're done filling the desired amount of time while (playoutBuilderState.CurrentTime < playoutFinish) { - if (timeHash.TryGetValue(playoutBuilderState.CurrentTime, out int count)) + if (timeCount.TryGetValue(playoutBuilderState.CurrentTime, out int count)) { - timeHash[playoutBuilderState.CurrentTime] = count + 1; + timeCount[playoutBuilderState.CurrentTime] = count + 1; } else { - timeHash[playoutBuilderState.CurrentTime] = 1; + timeCount[playoutBuilderState.CurrentTime] = 1; } - if (timeHash[playoutBuilderState.CurrentTime] == 6) + if (timeCount[playoutBuilderState.CurrentTime] == 6) { _logger.LogWarning( "Failed to schedule beyond {Time}; aborting playout build - this is a bug", diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs index a6ae4fdc1..4a1b7bc87 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs @@ -67,8 +67,9 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase