diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d31f28e..4ae3fd406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Always tell ffmpeg to stop encoding with a specific duration - This was removed to try to improve transitions with ffmpeg 7.x, but has been causing issues with other content - Move search debug logging to its own log category; add `Searching Minimum Log Level` to `Settings` > `Logging` +- Classic schedules: always schedule the full `Duration` amount instead of stopping mid-duration + - This allows duration items to be scheduled beyond midnight ## [25.3.1] - 2025-07-24 ### Fixed diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index d15428977..b2628babe 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -494,21 +494,18 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { - TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -545,9 +542,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -594,21 +591,18 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { - TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -645,9 +639,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -740,22 +734,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -793,9 +787,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -847,22 +841,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromMinutes(50), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -900,9 +894,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -954,22 +948,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1006,9 +1000,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1059,22 +1053,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1121,9 +1115,9 @@ public class PlayoutBuilderTests }, InFlood = true }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1175,22 +1169,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1229,9 +1223,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1286,22 +1280,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Multiple Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var dynamicCollection = new Collection { Id = 2, Name = "Dynamic Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1341,9 +1335,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1396,20 +1390,14 @@ public class PlayoutBuilderTests { Id = 1, Name = "Multiple Items 1", - MediaItems = new List - { - TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var collectionTwo = new Collection { Id = 2, Name = "Multiple Items 2", - MediaItems = new List - { - TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1458,9 +1446,9 @@ public class PlayoutBuilderTests }, MultipleRemaining = 2 }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1509,23 +1497,23 @@ public class PlayoutBuilderTests { Id = 1, Name = "Multiple Items 1", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(3, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + ] }; var collectionTwo = new Collection { Id = 2, Name = "Multiple Items 2", - MediaItems = new List - { + MediaItems = + [ TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(5, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1566,9 +1554,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1613,26 +1601,20 @@ public class PlayoutBuilderTests } [Test] - public async Task ContinuePlayout_Alternating_Duration_Should_Maintain_Duration() + public async Task ContinuePlayout_Alternating_Duration_Should_Complete_Duration() { var collectionOne = new Collection { Id = 1, Name = "Duration Items 1", - MediaItems = new List - { - TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var collectionTwo = new Collection { Id = 2, Name = "Duration Items 2", - MediaItems = new List - { - TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1683,9 +1665,9 @@ public class PlayoutBuilderTests }, DurationFinish = HoursAfterMidnight(3).UtcDateTime }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1710,7 +1692,7 @@ public class PlayoutBuilderTests Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken); - result.Items.Count.ShouldBe(4); + result.Items.Count.ShouldBe(5); result.Items[0].StartOffset.TimeOfDay.ShouldBe(TimeSpan.FromHours(1)); result.Items[0].MediaItemId.ShouldBe(1); @@ -1721,10 +1703,12 @@ public class PlayoutBuilderTests result.Items[2].MediaItemId.ShouldBe(2); result.Items[3].StartOffset.TimeOfDay.ShouldBe(TimeSpan.FromHours(4)); result.Items[3].MediaItemId.ShouldBe(2); + result.Items[4].StartOffset.TimeOfDay.ShouldBe(TimeSpan.FromHours(5)); + result.Items[4].MediaItemId.ShouldBe(2); - result.Anchor.ScheduleItemsEnumeratorState.Index.ShouldBe(1); - result.Anchor.DurationFinish.ShouldBe(HoursAfterMidnight(6).UtcDateTime); - result.Anchor.NextStartOffset.ShouldBe(HoursAfterMidnight(5)); + result.Anchor.ScheduleItemsEnumeratorState.Index.ShouldBe(0); + result.Anchor.DurationFinish.ShouldBeNull(); + result.Anchor.NextStartOffset.ShouldBe(HoursAfterMidnight(6)); } [Test] @@ -1734,30 +1718,21 @@ public class PlayoutBuilderTests { Id = 1, Name = "Duration Items 1", - MediaItems = new List - { - TestMovie(1, TimeSpan.FromMinutes(55), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(1, TimeSpan.FromMinutes(55), new DateTime(2020, 1, 1))] }; var collectionTwo = new Collection { Id = 2, Name = "Duration Items 2", - MediaItems = new List - { - TestMovie(2, TimeSpan.FromMinutes(55), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(2, TimeSpan.FromMinutes(55), new DateTime(2020, 1, 1))] }; var collectionThree = new Collection { Id = 3, Name = "Filler Items", - MediaItems = new List - { - TestMovie(3, TimeSpan.FromMinutes(5), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(3, TimeSpan.FromMinutes(5), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1811,9 +1786,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1881,20 +1856,14 @@ public class PlayoutBuilderTests { Id = 1, Name = "Duration Items 1", - MediaItems = new List - { - TestMovie(1, TimeSpan.FromMinutes(61), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(1, TimeSpan.FromMinutes(61), new DateTime(2020, 1, 1))] }; var collectionTwo = new Collection { Id = 2, Name = "Filler Items", - MediaItems = new List - { - TestMovie(2, TimeSpan.FromMinutes(4), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(2, TimeSpan.FromMinutes(4), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -1931,9 +1900,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -1977,13 +1946,13 @@ public class PlayoutBuilderTests { Id = 1, Name = "Duration Items 1", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + ] }; var fakeRepository = @@ -2011,9 +1980,9 @@ public class PlayoutBuilderTests Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -2168,12 +2137,12 @@ public class PlayoutBuilderTests { Id = 1, Name = "Duration Items 1", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(6), new DateTime(2002, 1, 1)), TestMovie(2, TimeSpan.FromHours(6), new DateTime(2003, 1, 1)), TestMovie(3, TimeSpan.FromHours(6), new DateTime(2004, 1, 1)) - } + ] }; var fakeRepository = @@ -2212,9 +2181,9 @@ public class PlayoutBuilderTests DurationFinish = HoursAfterMidnight(3).UtcDateTime }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -2528,11 +2497,11 @@ public class PlayoutBuilderTests PlayoutProgramScheduleAnchor newest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate) .First(); - result.ProgramScheduleAnchors = new List - { + result.ProgramScheduleAnchors = + [ oldest, newest - }; + ]; int firstSeedValue = lastCheckpoint.EnumeratorState.Seed; @@ -2657,22 +2626,22 @@ public class PlayoutBuilderTests { Id = 1, Name = "Flood Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) - } + ] }; var fixedCollection = new Collection { Id = 2, Name = "Fixed Items", - MediaItems = new List - { + MediaItems = + [ TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) - } + ] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -2719,9 +2688,9 @@ public class PlayoutBuilderTests }, InFlood = true }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -2772,20 +2741,14 @@ public class PlayoutBuilderTests { Id = 1, Name = "Multiple Items 1", - MediaItems = new List - { - TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var collectionTwo = new Collection { Id = 2, Name = "Multiple Items 2", - MediaItems = new List - { - TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -2834,9 +2797,9 @@ public class PlayoutBuilderTests }, MultipleRemaining = 2 }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -2879,26 +2842,20 @@ public class PlayoutBuilderTests } [Test] - public async Task Alternating_Duration_Should_Maintain_Duration() + public async Task Alternating_Duration_Should_Complete_Duration() { var collectionOne = new Collection { Id = 1, Name = "Duration Items 1", - MediaItems = new List - { - TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var collectionTwo = new Collection { Id = 2, Name = "Duration Items 2", - MediaItems = new List - { - TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) - } + MediaItems = [TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))] }; var fakeRepository = new FakeMediaCollectionRepository( @@ -2949,9 +2906,9 @@ public class PlayoutBuilderTests }, DurationFinish = HoursAfterMidnight(3).UtcDateTime }, - ProgramScheduleAnchors = new List(), - Items = new List(), - ProgramScheduleAlternates = new List(), + ProgramScheduleAnchors = [], + Items = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -2976,7 +2933,7 @@ public class PlayoutBuilderTests Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken); - result.Items.Count.ShouldBe(4); + result.Items.Count.ShouldBe(5); result.Items[0].StartOffset.TimeOfDay.ShouldBe(TimeSpan.FromHours(1)); result.Items[0].MediaItemId.ShouldBe(1); @@ -2987,10 +2944,12 @@ public class PlayoutBuilderTests result.Items[2].MediaItemId.ShouldBe(2); result.Items[3].StartOffset.TimeOfDay.ShouldBe(TimeSpan.FromHours(4)); result.Items[3].MediaItemId.ShouldBe(2); + result.Items[4].StartOffset.TimeOfDay.ShouldBe(TimeSpan.FromHours(5)); + result.Items[4].MediaItemId.ShouldBe(2); - result.Anchor.ScheduleItemsEnumeratorState.Index.ShouldBe(1); - result.Anchor.DurationFinish.ShouldBe(HoursAfterMidnight(6).UtcDateTime); - result.Anchor.NextStartOffset.ShouldBe(HoursAfterMidnight(5)); + result.Anchor.ScheduleItemsEnumeratorState.Index.ShouldBe(0); + result.Anchor.DurationFinish.ShouldBeNull(); + result.Anchor.NextStartOffset.ShouldBe(HoursAfterMidnight(6)); } } @@ -3039,17 +2998,14 @@ public class PlayoutBuilderTests new() { Id = id, - MovieMetadata = new List { new() { ReleaseDate = aired } }, - MediaVersions = new List - { - new() + MovieMetadata = [new MovieMetadata { ReleaseDate = aired }], + MediaVersions = + [ + new MediaVersion { - Duration = duration, MediaFiles = new List - { - new() { Path = $"/fake/path/{id}" } - } + Duration = duration, MediaFiles = [new MediaFile { Path = $"/fake/path/{id}" }] } - } + ] }; private TestData TestDataFloodForItems( @@ -3088,9 +3044,9 @@ public class PlayoutBuilderTests Id = 1, ProgramSchedule = new ProgramSchedule { Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - Items = new List(), - ProgramScheduleAnchors = new List(), - ProgramScheduleAlternates = new List(), + Items = [], + ProgramScheduleAnchors = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; @@ -3111,7 +3067,7 @@ public class PlayoutBuilderTests var fillerCollection = new SmartCollection { Id = 2, - Query = "ghjk" + Query = "qwerty" }; IConfigElementRepository configRepo = configMock ?? Substitute.For(); @@ -3144,9 +3100,9 @@ public class PlayoutBuilderTests Id = 1, ProgramSchedule = new ProgramSchedule { Items = items }, Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, - Items = new List(), - ProgramScheduleAnchors = new List(), - ProgramScheduleAlternates = new List(), + Items = [], + ProgramScheduleAnchors = [], + ProgramScheduleAlternates = [], FillGroupIndices = [] }; diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index 343955793..732e460a1 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -419,20 +419,17 @@ public class PlayoutBuilder : IPlayoutBuilder { // check for future items that aren't grouped inside range var futureItems = playout.Items.Filter(i => i.StartOffset > trimAfter).ToList(); - foreach (PlayoutItem futureItem in futureItems) - { - if (playout.Items.All(i => i == futureItem || i.GuideGroup != futureItem.GuideGroup)) - { - _logger.LogError( - "Playout item scheduled for {Time} after hard stop of {HardStop}", - futureItem.StartOffset, - trimAfter); + var futureItemCount = futureItems.Count(futureItem => + playout.Items.All(i => i == futureItem || i.GuideGroup != futureItem.GuideGroup)); - // it feels hacky to have to clean up a playlist like this, - // so only log the error, and leave the bad data to fail tests - // playout.Items.Remove(futureItem); - } - } + // it feels hacky to have to clean up a playlist like this, + // so only log the warning, and leave the bad data to fail tests + // playout.Items.Remove(futureItem); + + _logger.LogInformation( + "{Count} playout items are scheduled after hard stop of {HardStop}; this is expected if duration is used.", + futureItemCount, + trimAfter); } return playout; diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs index e667d39cc..68a5986d5 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs @@ -38,7 +38,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase= hardStop) + if (itemStartTime >= nextState.DurationFinish.IfNone(SystemTime.MaxValueUtc) || + // don't start if the first item will already be after the hard stop + (playoutItems.Count == 0 && itemStartTime >= hardStop)) { nextState = nextState with { CurrentTime = hardStop }; break; diff --git a/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs b/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs index 74d0fe343..70a27d686 100644 --- a/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs +++ b/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core.Search; using ErsatzTV.Infrastructure.Search; using Lucene.Net.Search; +using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Shouldly; @@ -19,7 +20,7 @@ public class SearchQueryParserTests public async Task Test(string input, string expected) { ISmartCollectionCache smartCollectionCache = Substitute.For(); - var parser = new SearchQueryParser(smartCollectionCache); + var parser = new SearchQueryParser(smartCollectionCache, Substitute.For>()); Query result = await parser.ParseQuery(input, null); result.ToString().ShouldBe(expected);