Browse Source

fix multiple filler scheduling bugs (#738)

pull/739/head
Jason Dove 4 years ago committed by GitHub
parent
commit
741b00fd52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 107
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs
  3. 48
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  4. 7
      ErsatzTV/ViewModels/FillerPresetEditViewModel.cs

2
CHANGELOG.md

@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix subtitles using software encoders, videotoolbox, VAAPI
- Fix setting VAAPI driver name
- Fix ffmpeg troubleshooting reports
- Fix bug where filler would behave as if it were configured to pad even though a different mode was selected
- Fix bug where mid-roll count filler would skip scheduling the final chapter in an episode
## [0.5.0-beta] - 2022-04-13
### Fixed

107
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs

@ -3,8 +3,10 @@ using ErsatzTV.Core.Domain.Filler; @@ -3,8 +3,10 @@ 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;
using Serilog;
namespace ErsatzTV.Core.Tests.Scheduling;
@ -222,7 +224,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase @@ -222,7 +224,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
List<PlayoutItem> playoutItems = PlayoutModeSchedulerBase<ProgramScheduleItem>
List<PlayoutItem> playoutItems = Scheduler()
.AddFiller(
startState,
CollectionEnumerators(scheduleItem, enumerator),
@ -273,7 +275,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase @@ -273,7 +275,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
// 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>
List<PlayoutItem> playoutItems = Scheduler()
.AddFiller(
startState,
enumerators,
@ -283,6 +285,77 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase @@ -283,6 +285,77 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
playoutItems.Count.Should().Be(1);
}
[Test]
public void Should_Schedule_Mid_Roll_Count_Filler_Correctly()
{
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 = null,
MidRollFiller = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
FillerMode = FillerMode.Count,
PadToNearestMinute = 60, // this should be ignored
Count = 1
}
};
var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
new List<ProgramScheduleItem> { scheduleItem },
new CollectionEnumeratorState());
var enumerator = new ChronologicalMediaCollectionEnumerator(
collectionOne.MediaItems,
new CollectionEnumeratorState());
var fillerEnumerator = new ChronologicalMediaCollectionEnumerator(
collectionTwo.MediaItems,
new CollectionEnumeratorState());
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators = CollectionEnumerators(
scheduleItem,
enumerator);
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), fillerEnumerator);
List<PlayoutItem> playoutItems = Scheduler()
.AddFiller(
startState,
enumerators,
scheduleItem,
new PlayoutItem
{
MediaItemId = 1,
Start = startState.CurrentTime.UtcDateTime,
Finish = startState.CurrentTime.AddHours(1).UtcDateTime
},
new List<MediaChapter>
{
new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) },
new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(60) }
});
playoutItems.Count.Should().Be(3);
playoutItems[0].MediaItemId.Should().Be(1);
playoutItems[0].StartOffset.Should().Be(startState.CurrentTime);
playoutItems[1].MediaItemId.Should().Be(3);
playoutItems[1].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(6));
playoutItems[2].MediaItemId.Should().Be(1);
playoutItems[2].StartOffset.Should().Be(startState.CurrentTime + TimeSpan.FromMinutes(11));
}
}
[TestFixture]
@ -324,4 +397,34 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase @@ -324,4 +397,34 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
new() { Duration = duration }
}
};
private static PlayoutModeSchedulerBase<ProgramScheduleItem> Scheduler() =>
new TestScheduler();
private class TestScheduler : PlayoutModeSchedulerBase<ProgramScheduleItem>
{
private static readonly ILoggerFactory LoggerFactory;
static TestScheduler()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
LoggerFactory = new LoggerFactory().AddSerilog(Log.Logger);
}
public TestScheduler() : base(LoggerFactory.CreateLogger<TestScheduler>())
{
}
public override Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule(
PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItem scheduleItem,
ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop) =>
throw new NotSupportedException();
}
}

48
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -191,11 +191,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -191,11 +191,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
.Append(Optional(scheduleItem.PostRollFiller))
.ToList();
if (allFiller.Count(f => f.PadToNearestMinute.HasValue) > 1)
// if (allFiller.Map(f => Optional(f.PadToNearestMinute)).Sequence().Flatten().Distinct().Count() > 1)
// multiple pad-to-nearest-minute values are invalid; use no filler
if (allFiller.Count(f => f.FillerMode == FillerMode.Pad && f.PadToNearestMinute.HasValue) > 1)
{
// multiple pad-to-nearest-minute values are invalid; use no filler
// TODO: log error?
return itemStartTime + itemDuration;
}
@ -287,16 +285,17 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -287,16 +285,17 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
}
}
foreach (FillerPreset padFiller in Optional(allFiller.FirstOrDefault(f => f.PadToNearestMinute.HasValue)))
foreach (FillerPreset padFiller in Optional(
allFiller.FirstOrDefault(f => f.FillerMode == FillerMode.Pad && f.PadToNearestMinute.HasValue)))
{
int currentMinute = (itemStartTime + totalDuration).Minute;
// ReSharper disable once PossibleInvalidOperationException
int targetMinute = (currentMinute + padFiller.PadToNearestMinute.Value - 1) /
padFiller.PadToNearestMinute.Value * padFiller.PadToNearestMinute.Value;
DateTimeOffset targetTime = itemStartTime + totalDuration - TimeSpan.FromMinutes(currentMinute) +
TimeSpan.FromMinutes(targetMinute);
return new DateTimeOffset(
targetTime.Year,
targetTime.Month,
@ -310,7 +309,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -310,7 +309,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
return itemStartTime + totalDuration;
}
internal static List<PlayoutItem> AddFiller(
internal List<PlayoutItem> AddFiller(
PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem,
@ -324,11 +323,10 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -324,11 +323,10 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
.Append(Optional(scheduleItem.PostRollFiller))
.ToList();
if (allFiller.Count(f => f.PadToNearestMinute.HasValue) > 1)
// if (allFiller.Map(f => Optional(f.PadToNearestMinute)).Sequence().Flatten().Distinct().Count() > 1)
// multiple pad-to-nearest-minute values are invalid; use no filler
if (allFiller.Count(f => f.FillerMode == FillerMode.Pad && f.PadToNearestMinute.HasValue) > 1)
{
// multiple pad-to-nearest-minute values are invalid; use no filler
// TODO: log error?
_logger.LogError("Multiple pad-to-nearest-minute values are invalid; no filler will be used");
return new List<PlayoutItem> { playoutItem };
}
@ -386,15 +384,18 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -386,15 +384,18 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
break;
case FillerMode.Count when filler.Count.HasValue:
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)];
for (var i = 0; i < effectiveChapters.Count - 1; i++)
for (var i = 0; i < effectiveChapters.Count; i++)
{
result.Add(playoutItem.ForChapter(effectiveChapters[i]));
result.AddRange(
AddCountFiller(
playoutBuilderState,
e2,
filler.Count.Value,
FillerKind.MidRoll));
if (i < effectiveChapters.Count - 1)
{
result.AddRange(
AddCountFiller(
playoutBuilderState,
e2,
filler.Count.Value,
FillerKind.MidRoll));
}
}
break;
@ -421,13 +422,14 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -421,13 +422,14 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
}
// after all non-padded filler has been added, figure out padding
foreach (FillerPreset padFiller in Optional(allFiller.FirstOrDefault(f => f.PadToNearestMinute.HasValue)))
foreach (FillerPreset padFiller in Optional(
allFiller.FirstOrDefault(f => f.FillerMode == FillerMode.Pad && f.PadToNearestMinute.HasValue)))
{
var totalDuration =
TimeSpan.FromMilliseconds(
result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds) +
effectiveChapters.Sum(c => (c.EndTime - c.StartTime).TotalMilliseconds));
int currentMinute = (playoutItem.StartOffset + totalDuration).Minute;
// ReSharper disable once PossibleInvalidOperationException
int targetMinute = (currentMinute + padFiller.PadToNearestMinute.Value - 1) /
@ -437,7 +439,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -437,7 +439,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
TimeSpan.FromMinutes(currentMinute) +
TimeSpan.FromMinutes(targetMinute);
var targetTime = new DateTimeOffset(
almostTargetTime.Year,
almostTargetTime.Month,
@ -532,7 +534,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -532,7 +534,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
}
}
}
break;
case FillerKind.PostRoll:
IMediaCollectionEnumerator post1 = enumerators[CollectionKey.ForFillerPreset(padFiller)];

7
ErsatzTV/ViewModels/FillerPresetEditViewModel.cs

@ -15,6 +15,7 @@ public class FillerPresetEditViewModel @@ -15,6 +15,7 @@ public class FillerPresetEditViewModel
private FillerMode _fillerMode;
private int? _count;
private ProgramScheduleItemCollectionType _collectionType;
private int? _padToNearestMinute;
public int Id { get; set; }
public string Name { get; set; }
@ -50,7 +51,11 @@ public class FillerPresetEditViewModel @@ -50,7 +51,11 @@ public class FillerPresetEditViewModel
set => _count = value;
}
public int? PadToNearestMinute { get; set; }
public int? PadToNearestMinute
{
get => FillerMode == FillerMode.Pad ? _padToNearestMinute : null;
set => _padToNearestMinute = value;
}
public ProgramScheduleItemCollectionType CollectionType
{

Loading…
Cancel
Save