fix mid-roll filler scheduling bug (#675)

This commit is contained in:
Jason Dove
2022-03-04 21:40:08 -06:00
committed by GitHub
parent a3260b2316
commit 6c06fbe621
3 changed files with 262 additions and 163 deletions
+2
View File
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix playback of interlaced mpeg2video content with NVIDIA acceleration
- Fix playback of all interlaced content with QSV acceleration
- Fix adding songs to collections from search results page
- Fix bug scheduling mid-roll filler with content that contains one chapter
- No mid-roll filler will be inserted for content with zero or one chapters
### Added
- Add automated error reporting via Bugsnag
@@ -3,190 +3,287 @@ using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture]
public class PlayoutModeSchedulerBaseTests
public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
{
[Test]
public void CalculateEndTimeWithFiller_Should_Not_Touch_Enumerator()
[TestFixture]
public class CalculateEndTimeWithFiller
{
var collection = new Collection
[Test]
public void Should_Not_Touch_Enumerator()
{
Id = 1,
Name = "Filler Items",
MediaItems = new List<MediaItem>()
};
var collection = new Collection
{
Id = 1,
Name = "Filler Items",
MediaItems = new List<MediaItem>()
};
for (var i = 0; i < 5; i++)
{
collection.MediaItems.Add(TestMovie(i + 1, TimeSpan.FromHours(i + 1), new DateTime(2020, 2, i + 1)));
for (var i = 0; i < 5; i++)
{
collection.MediaItems.Add(TestMovie(i + 1, TimeSpan.FromHours(i + 1), new DateTime(2020, 2, i + 1)));
}
var fillerPreset = new FillerPreset
{
FillerKind = FillerKind.PreRoll,
FillerMode = FillerMode.Count,
Count = 3,
Collection = collection,
CollectionId = collection.Id
};
var enumerator = new ChronologicalMediaCollectionEnumerator(
collection.MediaItems,
new CollectionEnumeratorState { Index = 0, Seed = 1 });
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>
{
{ CollectionKey.ForFillerPreset(fillerPreset), enumerator }
},
new ProgramScheduleItemOne
{
PreRollFiller = fillerPreset
},
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 18, 12, 30, TimeSpan.FromHours(-5)));
enumerator.State.Index.Should().Be(0);
enumerator.State.Seed.Should().Be(1);
}
var fillerPreset = new FillerPreset
[Test]
public void Should_Pad_To_15_Minutes_15()
{
FillerKind = FillerKind.PreRoll,
FillerMode = FillerMode.Count,
Count = 3,
Collection = collection,
CollectionId = collection.Id
};
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
var enumerator = new ChronologicalMediaCollectionEnumerator(
collection.MediaItems,
new CollectionEnumeratorState { Index = 0, Seed = 1 });
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 15, 0, TimeSpan.FromHours(-5)));
}
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>
{
{ CollectionKey.ForFillerPreset(fillerPreset), enumerator }
},
new ProgramScheduleItemOne
{
PreRollFiller = fillerPreset
},
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
[Test]
public void Should_Pad_To_15_Minutes_30()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 16, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 18, 12, 30, TimeSpan.FromHours(-5)));
enumerator.State.Index.Should().Be(0);
enumerator.State.Seed.Should().Be(1);
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void Should_Pad_To_15_Minutes_45()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 45, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void Should_Pad_To_15_Minutes_00()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 46, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void Should_Pad_To_30_Minutes_30()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 30
}
},
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void Should_Pad_To_30_Minutes_00()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 30
}
},
new DateTimeOffset(2020, 2, 1, 12, 20, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5)));
}
}
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_15()
[TestFixture]
public class AddFiller
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
[Test]
public void Should_Not_Crash_Mid_Roll_Zero_Chapters()
{
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 15, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_30()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
var scheduleItem = new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 16, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
};
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_45()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
new List<ProgramScheduleItem> { scheduleItem },
new CollectionEnumeratorState());
var enumerator = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
List<PlayoutItem> playoutItems = PlayoutModeSchedulerBase<ProgramScheduleItem>
.AddFiller(
startState,
CollectionEnumerators(scheduleItem, enumerator),
scheduleItem,
new PlayoutItem(),
new List<MediaChapter>());
playoutItems.Count.Should().Be(1);
}
[Test]
public void Should_Not_Crash_Mid_Roll_One_Chapter()
{
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,
MidRollFiller = new FillerPreset
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
};
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 45, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_00()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 15
}
},
new DateTimeOffset(2020, 2, 1, 12, 46, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
new List<ProgramScheduleItem> { scheduleItem },
new CollectionEnumeratorState());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_30_Minutes_30()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 30
}
},
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
var enumerator = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)));
}
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_30_Minutes_00()
{
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne
{
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Pad,
PadToNearestMinute = 30
}
},
new DateTimeOffset(2020, 2, 1, 12, 20, 0, TimeSpan.FromHours(-5)),
new TimeSpan(0, 12, 30),
new List<MediaChapter>());
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5)));
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators = CollectionEnumerators(
scheduleItem,
enumerator);
// too lazy to make another enumerator for the filler that we don't want
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), enumerator);
List<PlayoutItem> playoutItems = PlayoutModeSchedulerBase<ProgramScheduleItem>
.AddFiller(
startState,
enumerators,
scheduleItem,
new PlayoutItem(),
new List<MediaChapter> { new() });
playoutItems.Count.Should().Be(1);
}
}
private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) =>
@@ -310,7 +310,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
return itemStartTime + totalDuration;
}
protected List<PlayoutItem> AddFiller(
internal static List<PlayoutItem> AddFiller(
PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem,
@@ -333,7 +333,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
}
List<MediaChapter> effectiveChapters = chapters;
if (allFiller.All(fp => fp.FillerKind != FillerKind.MidRoll))
if (allFiller.All(fp => fp.FillerKind != FillerKind.MidRoll) || effectiveChapters.Count <= 1)
{
effectiveChapters = new List<MediaChapter>();
}
@@ -356,7 +356,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
}
}
if (!effectiveChapters.Any())
if (effectiveChapters.Count <= 1)
{
result.Add(playoutItem);
}
@@ -493,7 +493,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
? remainingToFill
: remainingToFill / (effectiveChapters.Count - 1);
TimeSpan filled = TimeSpan.Zero;
for (var i = 0; i < chapters.Count; i++)
for (var i = 0; i < effectiveChapters.Count; i++)
{
result.Add(playoutItem.ForChapter(effectiveChapters[i]));
if (i < effectiveChapters.Count - 1)
@@ -650,7 +650,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
return result;
}
private Option<PlayoutItem> FallbackFillerForPad(
private static Option<PlayoutItem> FallbackFillerForPad(
PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem,