fix tail and fallback filler scheduling (#981)
This commit is contained in:
@@ -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/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [0.6.8-beta] - 2022-10-05
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -823,6 +823,216 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
|
|||||||
playoutItems[6].GuideGroup.Should().Be(3);
|
playoutItems[6].GuideGroup.Should().Be(3);
|
||||||
playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback);
|
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]
|
[Test]
|
||||||
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback()
|
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback()
|
||||||
|
|||||||
@@ -20,9 +20,24 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
|
|||||||
ProgramScheduleItem nextScheduleItem,
|
ProgramScheduleItem nextScheduleItem,
|
||||||
DateTimeOffset hardStop);
|
DateTimeOffset hardStop);
|
||||||
|
|
||||||
public static DateTimeOffset GetStartTimeAfter(
|
public static DateTimeOffset GetFillerStartTimeAfter(
|
||||||
PlayoutBuilderState state,
|
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();
|
DateTimeOffset startTime = state.CurrentTime.ToLocalTime();
|
||||||
|
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
|
|||||||
|
|
||||||
// clear guide finish on all but the last item
|
// clear guide finish on all but the last item
|
||||||
var all = playoutItems.Filter(pi => pi.FillerKind == FillerKind.None).ToList();
|
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))
|
foreach (PlayoutItem item in all.Filter(pi => pi != last))
|
||||||
{
|
{
|
||||||
item.GuideFinish = null;
|
item.GuideFinish = null;
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProgramScheduleItem peekItem = nextScheduleItem;
|
ProgramScheduleItem peekItem = nextScheduleItem;
|
||||||
DateTimeOffset peekItemStart = GetStartTimeAfter(nextState, peekItem);
|
DateTimeOffset peekItemStart = GetFillerStartTimeAfter(nextState, peekItem, hardStop);
|
||||||
|
|
||||||
if (scheduleItem.TailFiller != null)
|
if (scheduleItem.TailFiller != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
|
|||||||
nextState.ScheduleItemsEnumerator.MoveNext();
|
nextState.ScheduleItemsEnumerator.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem);
|
DateTimeOffset nextItemStart = GetFillerStartTimeAfter(nextState, nextScheduleItem, hardStop);
|
||||||
|
|
||||||
if (scheduleItem.TailFiller != null)
|
if (scheduleItem.TailFiller != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
|
|||||||
foreach (MediaItem mediaItem in contentEnumerator.Current)
|
foreach (MediaItem mediaItem in contentEnumerator.Current)
|
||||||
{
|
{
|
||||||
// find when we should start this item, based on the current time
|
// find when we should start this item, based on the current time
|
||||||
DateTimeOffset itemStartTime = GetStartTimeAfter(
|
DateTimeOffset itemStartTime = GetStartTimeAfter(playoutBuilderState, scheduleItem);
|
||||||
playoutBuilderState,
|
|
||||||
scheduleItem);
|
|
||||||
|
|
||||||
if (itemStartTime >= hardStop)
|
if (itemStartTime >= hardStop)
|
||||||
{
|
{
|
||||||
playoutBuilderState = playoutBuilderState with { CurrentTime = hardStop };
|
playoutBuilderState = playoutBuilderState with { CurrentTime = hardStop };
|
||||||
@@ -84,7 +81,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
|
|||||||
// "Advancing to next schedule item after playout mode {PlayoutMode}",
|
// "Advancing to next schedule item after playout mode {PlayoutMode}",
|
||||||
// "One");
|
// "One");
|
||||||
|
|
||||||
DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem);
|
DateTimeOffset nextItemStart = GetFillerStartTimeAfter(nextState, nextScheduleItem, hardStop);
|
||||||
|
|
||||||
if (scheduleItem.TailFiller != null)
|
if (scheduleItem.TailFiller != null)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user