Files
ersatztv/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs
T
Jason DoveandGitHub 6d147de2f3 filler rework (#449)
* add chapter statistics and new filler options

* refactor playout builder

* more refactor prep for filler

* rewrite schedulers

* refactor collectionkey

* add tail filler kind

* migrate tail filler to filler preset

* optionally show filler

* fix playout detail row count

* remove duration tail filler options

* implement tail and fallback in flood scheduler

* implement tail and fallback in one scheduler

* implement tail and fallback in multiple scheduler

* implement looping fallback filler

* more duration tests

* start to add post-roll filler to flood

* rework playoutitem filler tagging

* rework scheduler logging

* calculate whether configured filler will fit

* implement pre-roll and post-roll duration and count filler

* improve duration filler calculation

* add minutes to search index

* update channel guide to work with new filler

* add mid-roll filler

* don't clone enumerators for filler calculations

* support pre-roll and post-roll pad filler

* implement mid-roll pad filler

* allow clearing filler selections in schedule editor

* fix tests

* filler config validation

* use consistent time zone for tests
2021-10-21 20:23:14 -05:00

461 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Scheduling;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
namespace ErsatzTV.Core.Tests.Scheduling
{
[TestFixture]
public class PlayoutModeSchedulerOneTests : SchedulerTestBase
{
[Test]
public void Should_Have_Gap_With_No_Tail_No_Fallback()
{
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
var scheduleItem = new ProgramScheduleItemOne
{
Id = 1,
Index = 1,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = null,
PlaybackOrder = PlaybackOrder.Chronological,
TailFiller = null,
FallbackFiller = null
};
var enumerator = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
StartState,
CollectionEnumerators(scheduleItem, enumerator),
scheduleItem,
NextScheduleItem,
HardStop);
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(1));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(2);
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
playoutBuilderState.InFlood.Should().BeFalse();
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
playoutBuilderState.InDurationFiller.Should().BeFalse();
playoutBuilderState.ScheduleItemIndex.Should().Be(1);
enumerator.State.Index.Should().Be(1);
playoutItems.Count.Should().Be(1);
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);
}
[Test]
public void Should_Not_Have_Gap_With_Exact_Tail()
{
Collection collectionOne = TwoItemCollection(1, 2, new TimeSpan(2, 45, 0));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5));
var scheduleItem = new ProgramScheduleItemOne
{
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 scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
StartState,
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem,
NextScheduleItem,
HardStop);
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(2);
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
playoutBuilderState.InFlood.Should().BeFalse();
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
playoutBuilderState.InDurationFiller.Should().BeFalse();
playoutBuilderState.ScheduleItemIndex.Should().Be(1);
enumerator1.State.Index.Should().Be(1);
enumerator2.State.Index.Should().Be(1);
playoutItems.Count.Should().Be(4);
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(3);
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
playoutItems[1].GuideGroup.Should().Be(1);
playoutItems[1].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[2].MediaItemId.Should().Be(4);
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 50, 0)));
playoutItems[2].GuideGroup.Should().Be(1);
playoutItems[2].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[3].MediaItemId.Should().Be(3);
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 55, 0)));
playoutItems[3].GuideGroup.Should().Be(1);
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail);
}
[Test]
public void Should_Not_Have_Gap_With_Fallback()
{
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5));
var scheduleItem = new ProgramScheduleItemOne
{
Id = 1,
Index = 1,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = null,
PlaybackOrder = PlaybackOrder.Chronological,
TailFiller = null,
FallbackFiller = new FillerPreset
{
FillerKind = FillerKind.Tail,
Collection = collectionTwo,
CollectionId = collectionTwo.Id
}
};
var enumerator1 = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var enumerator2 = new ChronologicalMediaCollectionEnumerator(
collectionTwo.MediaItems,
new CollectionEnumeratorState());
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
StartState,
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2),
scheduleItem,
NextScheduleItem,
HardStop);
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(2);
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
playoutBuilderState.InFlood.Should().BeFalse();
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
playoutBuilderState.InDurationFiller.Should().BeFalse();
playoutBuilderState.ScheduleItemIndex.Should().Be(1);
enumerator1.State.Index.Should().Be(1);
enumerator2.State.Index.Should().Be(1);
playoutItems.Count.Should().Be(2);
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(3);
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddHours(1));
playoutItems[1].GuideGroup.Should().Be(1);
playoutItems[1].FillerKind.Should().Be(FillerKind.Fallback);
}
[Test]
public void Should_Have_Gap_With_Tail_No_Fallback()
{
Collection collectionOne = TwoItemCollection(1, 2, new TimeSpan(2, 45, 0));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4));
var scheduleItem = new ProgramScheduleItemOne
{
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 scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
StartState,
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem,
NextScheduleItem,
HardStop);
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(2);
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
playoutBuilderState.InFlood.Should().BeFalse();
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
playoutBuilderState.InDurationFiller.Should().BeFalse();
playoutBuilderState.ScheduleItemIndex.Should().Be(1);
enumerator1.State.Index.Should().Be(1);
enumerator2.State.Index.Should().Be(1);
playoutItems.Count.Should().Be(4);
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(3);
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
playoutItems[1].GuideGroup.Should().Be(1);
playoutItems[1].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[2].MediaItemId.Should().Be(4);
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0)));
playoutItems[2].GuideGroup.Should().Be(1);
playoutItems[2].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[3].MediaItemId.Should().Be(3);
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0)));
playoutItems[3].GuideGroup.Should().Be(1);
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail);
}
[Test]
public void Should_Not_Have_Gap_With_Tail_And_Fallback()
{
Collection collectionOne = TwoItemCollection(1, 2, new TimeSpan(2, 45, 0));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4));
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1));
var scheduleItem = new ProgramScheduleItemOne
{
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 = new FillerPreset
{
FillerKind = FillerKind.Fallback,
Collection = collectionThree,
CollectionId = collectionThree.Id
}
};
var enumerator1 = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var enumerator2 = new ChronologicalMediaCollectionEnumerator(
collectionTwo.MediaItems,
new CollectionEnumeratorState());
var enumerator3 = new ChronologicalMediaCollectionEnumerator(
collectionThree.MediaItems,
new CollectionEnumeratorState());
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
StartState,
CollectionEnumerators(
scheduleItem,
enumerator1,
scheduleItem.TailFiller,
enumerator2,
scheduleItem.FallbackFiller,
enumerator3),
scheduleItem,
NextScheduleItem,
HardStop);
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(2);
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
playoutBuilderState.InFlood.Should().BeFalse();
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
playoutBuilderState.InDurationFiller.Should().BeFalse();
playoutBuilderState.ScheduleItemIndex.Should().Be(1);
enumerator1.State.Index.Should().Be(1);
enumerator2.State.Index.Should().Be(1);
enumerator3.State.Index.Should().Be(1);
playoutItems.Count.Should().Be(5);
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(3);
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
playoutItems[1].GuideGroup.Should().Be(1);
playoutItems[1].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[2].MediaItemId.Should().Be(4);
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0)));
playoutItems[2].GuideGroup.Should().Be(1);
playoutItems[2].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[3].MediaItemId.Should().Be(3);
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0)));
playoutItems[3].GuideGroup.Should().Be(1);
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail);
playoutItems[4].MediaItemId.Should().Be(5);
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0)));
playoutItems[4].GuideGroup.Should().Be(1);
playoutItems[4].FillerKind.Should().Be(FillerKind.Fallback);
}
[Test]
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback()
{
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(3));
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4));
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1));
var scheduleItem = new ProgramScheduleItemOne
{
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 = new FillerPreset
{
FillerKind = FillerKind.Fallback,
Collection = collectionThree,
CollectionId = collectionThree.Id
}
};
var enumerator1 = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var enumerator2 = new ChronologicalMediaCollectionEnumerator(
collectionTwo.MediaItems,
new CollectionEnumeratorState());
var enumerator3 = new ChronologicalMediaCollectionEnumerator(
collectionThree.MediaItems,
new CollectionEnumeratorState());
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object);
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
StartState,
CollectionEnumerators(
scheduleItem,
enumerator1,
scheduleItem.TailFiller,
enumerator2,
scheduleItem.FallbackFiller,
enumerator3),
scheduleItem,
NextScheduleItem,
HardStop);
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
playoutBuilderState.NextGuideGroup.Should().Be(2);
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
playoutBuilderState.InFlood.Should().BeFalse();
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
playoutBuilderState.InDurationFiller.Should().BeFalse();
playoutBuilderState.ScheduleItemIndex.Should().Be(1);
enumerator1.State.Index.Should().Be(1);
enumerator2.State.Index.Should().Be(0);
enumerator3.State.Index.Should().Be(0);
playoutItems.Count.Should().Be(1);
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);
}
protected override ProgramScheduleItem NextScheduleItem => new ProgramScheduleItemOne
{
StartTime = TimeSpan.FromHours(3)
};
}
}