Browse Source

fix tail and fallback filler scheduling (#981)

pull/983/head
Jason Dove 3 years ago committed by GitHub
parent
commit
555b156154
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 210
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs
  3. 19
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  4. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  5. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  6. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  7. 7
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

3
CHANGELOG.md

@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Fixed
- Fix bug where tail or fallback filler would sometimes schedule much longer than expected
- This only happened with fixed start schedule items following a schedule item with tail or fallback filler
## [0.6.8-beta] - 2022-10-05
### Fixed

210
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs

@ -824,6 +824,216 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase @@ -824,6 +824,216 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback);
}
[Test]
public void Should_Not_Schedule_Fallback_Filler_Incomplete_Flood()
{
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(20));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(1));
var scheduleItem = new ProgramScheduleItemFlood
{
Id = 1,
Index = 1,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = null,
PlaybackOrder = PlaybackOrder.Chronological,
TailFiller = null,
FallbackFiller = new FillerPreset
{
FillerKind = FillerKind.Fallback,
Collection = collectionTwo,
CollectionId = collectionTwo.Id
}
};
var enumerator1 = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var enumerator2 = new ChronologicalMediaCollectionEnumerator(
collectionTwo.MediaItems,
new CollectionEnumeratorState());
var sortedScheduleItems = new List<ProgramScheduleItem>
{
scheduleItem,
NextScheduleItem
};
var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
sortedScheduleItems,
new CollectionEnumeratorState());
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
var scheduler = new PlayoutModeSchedulerFlood(new Mock<ILogger>().Object);
// hard stop at 2, an hour before the "next schedule item" at 3
DateTimeOffset hardStop = StartState(scheduleItemsEnumerator).CurrentTime.AddHours(2);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
startState,
CollectionEnumerators(
scheduleItem,
enumerator1,
scheduleItem.FallbackFiller,
enumerator2),
scheduleItem,
NextScheduleItem,
hardStop);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(2));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(7);
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);
enumerator1.State.Index.Should().Be(0);
enumerator2.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[1].MediaItemId.Should().Be(2);
playoutItems[1].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(20));
playoutItems[1].GuideGroup.Should().Be(2);
playoutItems[1].FillerKind.Should().Be(FillerKind.None);
playoutItems[2].MediaItemId.Should().Be(1);
playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(40));
playoutItems[2].GuideGroup.Should().Be(3);
playoutItems[2].FillerKind.Should().Be(FillerKind.None);
playoutItems[3].MediaItemId.Should().Be(2);
playoutItems[3].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(60));
playoutItems[3].GuideGroup.Should().Be(4);
playoutItems[3].FillerKind.Should().Be(FillerKind.None);
playoutItems[4].MediaItemId.Should().Be(1);
playoutItems[4].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(80));
playoutItems[4].GuideGroup.Should().Be(5);
playoutItems[4].FillerKind.Should().Be(FillerKind.None);
playoutItems[5].MediaItemId.Should().Be(2);
playoutItems[5].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(100));
playoutItems[5].GuideGroup.Should().Be(6);
playoutItems[5].FillerKind.Should().Be(FillerKind.None);
}
[Test]
public void Should_Not_Schedule_Tail_Filler_Incomplete_Flood()
{
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(20));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(1));
var scheduleItem = new ProgramScheduleItemFlood
{
Id = 1,
Index = 1,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = null,
PlaybackOrder = PlaybackOrder.Chronological,
TailFiller = new FillerPreset
{
FillerKind = FillerKind.Tail,
Collection = collectionTwo,
CollectionId = collectionTwo.Id
},
FallbackFiller = null
};
var enumerator1 = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var enumerator2 = new ChronologicalMediaCollectionEnumerator(
collectionTwo.MediaItems,
new CollectionEnumeratorState());
var sortedScheduleItems = new List<ProgramScheduleItem>
{
scheduleItem,
NextScheduleItem
};
var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
sortedScheduleItems,
new CollectionEnumeratorState());
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
var scheduler = new PlayoutModeSchedulerFlood(new Mock<ILogger>().Object);
// hard stop at 2, an hour before the "next schedule item" at 3
DateTimeOffset hardStop = StartState(scheduleItemsEnumerator).CurrentTime.AddHours(2);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
startState,
CollectionEnumerators(
scheduleItem,
enumerator1,
scheduleItem.TailFiller,
enumerator2),
scheduleItem,
NextScheduleItem,
hardStop);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(2));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(7);
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);
enumerator1.State.Index.Should().Be(0);
enumerator2.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[1].MediaItemId.Should().Be(2);
playoutItems[1].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(20));
playoutItems[1].GuideGroup.Should().Be(2);
playoutItems[1].FillerKind.Should().Be(FillerKind.None);
playoutItems[2].MediaItemId.Should().Be(1);
playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(40));
playoutItems[2].GuideGroup.Should().Be(3);
playoutItems[2].FillerKind.Should().Be(FillerKind.None);
playoutItems[3].MediaItemId.Should().Be(2);
playoutItems[3].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(60));
playoutItems[3].GuideGroup.Should().Be(4);
playoutItems[3].FillerKind.Should().Be(FillerKind.None);
playoutItems[4].MediaItemId.Should().Be(1);
playoutItems[4].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(80));
playoutItems[4].GuideGroup.Should().Be(5);
playoutItems[4].FillerKind.Should().Be(FillerKind.None);
playoutItems[5].MediaItemId.Should().Be(2);
playoutItems[5].StartOffset.Should().Be(startState.CurrentTime.AddMinutes(100));
playoutItems[5].GuideGroup.Should().Be(6);
playoutItems[5].FillerKind.Should().Be(FillerKind.None);
}
[Test]
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback()
{

19
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -20,9 +20,24 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -20,9 +20,24 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop);
public static DateTimeOffset GetStartTimeAfter(
public static DateTimeOffset GetFillerStartTimeAfter(
PlayoutBuilderState state,
ProgramScheduleItem scheduleItem)
ProgramScheduleItem scheduleItem,
DateTimeOffset hardStop
)
{
DateTimeOffset startTime = GetStartTimeAfter(state, scheduleItem);
// filler should always stop at the hard stop
if (hardStop < startTime)
{
startTime = hardStop;
}
return startTime;
}
public static DateTimeOffset GetStartTimeAfter(PlayoutBuilderState state, ProgramScheduleItem scheduleItem)
{
DateTimeOffset startTime = state.CurrentTime.ToLocalTime();

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -196,7 +196,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -196,7 +196,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
// clear guide finish on all but the last item
var all = playoutItems.Filter(pi => pi.FillerKind == FillerKind.None).ToList();
PlayoutItem last = all.OrderBy(pi => pi.FinishOffset).LastOrDefault();
PlayoutItem last = all.MaxBy(pi => pi.FinishOffset);
foreach (PlayoutItem item in all.Filter(pi => pi != last))
{
item.GuideFinish = null;

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -138,7 +138,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul @@ -138,7 +138,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
}
ProgramScheduleItem peekItem = nextScheduleItem;
DateTimeOffset peekItemStart = GetStartTimeAfter(nextState, peekItem);
DateTimeOffset peekItemStart = GetFillerStartTimeAfter(nextState, peekItem, hardStop);
if (scheduleItem.TailFiller != null)
{

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -119,7 +119,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -119,7 +119,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
nextState.ScheduleItemsEnumerator.MoveNext();
}
DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem);
DateTimeOffset nextItemStart = GetFillerStartTimeAfter(nextState, nextScheduleItem, hardStop);
if (scheduleItem.TailFiller != null)
{

7
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -23,10 +23,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI @@ -23,10 +23,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
foreach (MediaItem mediaItem in contentEnumerator.Current)
{
// find when we should start this item, based on the current time
DateTimeOffset itemStartTime = GetStartTimeAfter(
playoutBuilderState,
scheduleItem);
DateTimeOffset itemStartTime = GetStartTimeAfter(playoutBuilderState, scheduleItem);
if (itemStartTime >= hardStop)
{
playoutBuilderState = playoutBuilderState with { CurrentTime = hardStop };
@ -84,7 +81,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI @@ -84,7 +81,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
// "Advancing to next schedule item after playout mode {PlayoutMode}",
// "One");
DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem);
DateTimeOffset nextItemStart = GetFillerStartTimeAfter(nextState, nextScheduleItem, hardStop);
if (scheduleItem.TailFiller != null)
{

Loading…
Cancel
Save