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 interlaced mpeg2video content with NVIDIA acceleration
- Fix playback of all interlaced content with QSV acceleration - Fix playback of all interlaced content with QSV acceleration
- Fix adding songs to collections from search results page - 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 ### Added
- Add automated error reporting via Bugsnag - Add automated error reporting via Bugsnag
@@ -3,190 +3,287 @@ using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework; using NUnit.Framework;
namespace ErsatzTV.Core.Tests.Scheduling; namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class PlayoutModeSchedulerBaseTests public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
{ {
[Test] [TestFixture]
public void CalculateEndTimeWithFiller_Should_Not_Touch_Enumerator() public class CalculateEndTimeWithFiller
{ {
var collection = new Collection [Test]
public void Should_Not_Touch_Enumerator()
{ {
Id = 1, var collection = new Collection
Name = "Filler Items", {
MediaItems = new List<MediaItem>() Id = 1,
}; Name = "Filler Items",
MediaItems = new List<MediaItem>()
};
for (var i = 0; i < 5; i++) for (var i = 0; i < 5; i++)
{ {
collection.MediaItems.Add(TestMovie(i + 1, TimeSpan.FromHours(i + 1), new DateTime(2020, 2, i + 1))); 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, DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
FillerMode = FillerMode.Count, .CalculateEndTimeWithFiller(
Count = 3, new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
Collection = collection, new ProgramScheduleItemOne
CollectionId = collection.Id {
}; 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( result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 15, 0, TimeSpan.FromHours(-5)));
collection.MediaItems, }
new CollectionEnumeratorState { Index = 0, Seed = 1 });
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> [Test]
.CalculateEndTimeWithFiller( public void Should_Pad_To_15_Minutes_30()
new Dictionary<CollectionKey, IMediaCollectionEnumerator> {
{ DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem>
{ CollectionKey.ForFillerPreset(fillerPreset), enumerator } .CalculateEndTimeWithFiller(
}, new Dictionary<CollectionKey, IMediaCollectionEnumerator>(),
new ProgramScheduleItemOne new ProgramScheduleItemOne
{ {
PreRollFiller = fillerPreset MidRollFiller = new FillerPreset
}, {
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)), FillerKind = FillerKind.MidRoll,
new TimeSpan(0, 12, 30), FillerMode = FillerMode.Pad,
new List<MediaChapter>()); 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))); result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)));
enumerator.State.Index.Should().Be(0); }
enumerator.State.Seed.Should().Be(1);
[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] [TestFixture]
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_15() public class AddFiller
{ {
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> [Test]
.CalculateEndTimeWithFiller( public void Should_Not_Crash_Mid_Roll_Zero_Chapters()
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), {
new ProgramScheduleItemOne Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
{
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>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 15, 0, TimeSpan.FromHours(-5))); var scheduleItem = new ProgramScheduleItemOne
} {
MidRollFiller = new FillerPreset
[Test]
public void CalculateEndTimeWithFiller_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,
FillerKind = FillerKind.MidRoll, PadToNearestMinute = 15
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, 12, 30, 0, TimeSpan.FromHours(-5))); var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
} new List<ProgramScheduleItem> { scheduleItem },
new CollectionEnumeratorState());
[Test]
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_45() var enumerator = new ChronologicalMediaCollectionEnumerator(
{ collectionOne.MediaItems,
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> new CollectionEnumeratorState());
.CalculateEndTimeWithFiller(
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
new ProgramScheduleItemOne
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,
FillerKind = FillerKind.MidRoll, PadToNearestMinute = 15
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))); var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
} new List<ProgramScheduleItem> { scheduleItem },
new CollectionEnumeratorState());
[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>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5))); var enumerator = new ChronologicalMediaCollectionEnumerator(
} collectionOne.MediaItems,
new CollectionEnumeratorState());
[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>());
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5))); PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
}
[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>());
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) => 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; return itemStartTime + totalDuration;
} }
protected List<PlayoutItem> AddFiller( internal static List<PlayoutItem> AddFiller(
PlayoutBuilderState playoutBuilderState, PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
@@ -333,7 +333,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
} }
List<MediaChapter> effectiveChapters = chapters; 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>(); 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); result.Add(playoutItem);
} }
@@ -493,7 +493,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
? remainingToFill ? remainingToFill
: remainingToFill / (effectiveChapters.Count - 1); : remainingToFill / (effectiveChapters.Count - 1);
TimeSpan filled = TimeSpan.Zero; 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])); result.Add(playoutItem.ForChapter(effectiveChapters[i]));
if (i < effectiveChapters.Count - 1) if (i < effectiveChapters.Count - 1)
@@ -650,7 +650,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
return result; return result;
} }
private Option<PlayoutItem> FallbackFillerForPad( private static Option<PlayoutItem> FallbackFillerForPad(
PlayoutBuilderState playoutBuilderState, PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,