Browse Source

timeout playout builds after 2 minutes (#1269)

* add cancellation token support to playout builds and collection enumerators

* fix playout bug with shuffle in order

* update changelog
pull/1270/head
Jason Dove 3 years ago committed by GitHub
parent
commit
2ccba9e476
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 26
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 8
      ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs
  4. 8
      ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs
  5. 161
      ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
  6. 44
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs
  7. 35
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs
  8. 47
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs
  9. 32
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs
  10. 38
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs
  11. 8
      ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs
  12. 59
      ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs
  13. 8
      ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs
  14. 20
      ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs
  15. 16
      ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs
  16. 3
      ErsatzTV.Core/Interfaces/Scheduling/IMultiEpisodeShuffleCollectionEnumeratorFactory.cs
  17. 2
      ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs
  18. 3
      ErsatzTV.Core/Interfaces/Scheduling/IPlayoutModeScheduler.cs
  19. 91
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  20. 60
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  21. 20
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  22. 17
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  23. 17
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  24. 12
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  25. 8
      ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs
  26. 8
      ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs
  27. 2
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  28. 8
      ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs
  29. 11
      ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumeratorFactory.cs
  30. 8
      ErsatzTV/Pages/MultiCollectionEditor.razor
  31. 26
      ErsatzTV/Services/WorkerService.cs

4
CHANGELOG.md

@ -9,6 +9,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix fallback filler looping by forcing software mode for this content - Fix fallback filler looping by forcing software mode for this content
- Other content will still use hardware acceleration as configured - Other content will still use hardware acceleration as configured
- Hardware-accelerated fallback filler may be re-enabled in the future - Hardware-accelerated fallback filler may be re-enabled in the future
- Fix playout building when shuffle in order is used with a single media item
### Changed
- Timeout playout builds after 2 minutes; this should prevent playout bugs from blocking other functionality
## [0.7.8-beta] - 2023-04-29 ## [0.7.8-beta] - 2023-04-29
### Added ### Added

26
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -40,19 +40,25 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Playout> validation = await Validate(dbContext, request); Validation<BaseError, Playout> validation = await Validate(dbContext, request);
return await validation.Apply(playout => ApplyUpdateRequest(dbContext, request, playout)); return await validation.Match(
playout => ApplyUpdateRequest(dbContext, request, playout, cancellationToken),
error => Task.FromResult<Either<BaseError, Unit>>(error.Join()));
} }
private async Task<Unit> ApplyUpdateRequest(TvContext dbContext, BuildPlayout request, Playout playout) private async Task<Either<BaseError, Unit>> ApplyUpdateRequest(
TvContext dbContext,
BuildPlayout request,
Playout playout,
CancellationToken cancellationToken)
{ {
try try
{ {
await _playoutBuilder.Build(playout, request.Mode); await _playoutBuilder.Build(playout, request.Mode, cancellationToken);
// let any active segmenter processes know that the playout has been modified // let any active segmenter processes know that the playout has been modified
// and therefore the segmenter may need to seek into the next item instead of // and therefore the segmenter may need to seek into the next item instead of
// starting at the beginning (if already working ahead) // starting at the beginning (if already working ahead)
bool hasChanges = await dbContext.SaveChangesAsync() > 0; bool hasChanges = await dbContext.SaveChangesAsync(cancellationToken) > 0;
if (request.Mode != PlayoutBuildMode.Continue && hasChanges) if (request.Mode != PlayoutBuildMode.Continue && hasChanges)
{ {
_ffmpegSegmenterService.PlayoutUpdated(playout.Channel.Number); _ffmpegSegmenterService.PlayoutUpdated(playout.Channel.Number);
@ -71,15 +77,23 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
string fileName = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channelNumber}.xml"); string fileName = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channelNumber}.xml");
if (hasChanges || !File.Exists(fileName)) if (hasChanges || !File.Exists(fileName))
{ {
await _workerChannel.WriteAsync(new RefreshChannelData(channelNumber)); await _workerChannel.WriteAsync(new RefreshChannelData(channelNumber), cancellationToken);
} }
} }
await _workerChannel.WriteAsync(new ExtractEmbeddedSubtitles(playout.Id)); await _workerChannel.WriteAsync(new ExtractEmbeddedSubtitles(playout.Id), cancellationToken);
}
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
{
_client.Notify(ex);
return BaseError.New(
$"Timeout building playout for channel {playout.Channel.Name}; this may be a bug!");
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex); _client.Notify(ex);
return BaseError.New(
$"Unexpected error building playout for channel {playout.Channel.Name}: {ex.Message}");
} }
return Unit.Default; return Unit.Default;

8
ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs

@ -8,6 +8,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class ChronologicalContentTests public class ChronologicalContentTests
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Episodes_Should_Sort_By_Aired() public void Episodes_Should_Sort_By_Aired()
{ {

8
ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs

@ -7,6 +7,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
public class CustomOrderContentTests public class CustomOrderContentTests
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void MediaItems_Should_Sort_By_CustomOrder() public void MediaItems_Should_Sort_By_CustomOrder()
{ {

161
ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs

@ -32,6 +32,14 @@ public class PlayoutBuilderTests
_logger = loggerFactory?.CreateLogger<PlayoutBuilder>(); _logger = loggerFactory?.CreateLogger<PlayoutBuilder>();
} }
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[TestFixture] [TestFixture]
public class NewPlayout : PlayoutBuilderTests public class NewPlayout : PlayoutBuilderTests
{ {
@ -46,7 +54,7 @@ public class PlayoutBuilderTests
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Random); (PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Random);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, _cancellationToken);
result.Items.Should().BeEmpty(); result.Items.Should().BeEmpty();
} }
@ -64,7 +72,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().MediaItemId.Should().Be(2); result.Items.Head().MediaItemId.Should().Be(2);
@ -94,7 +102,7 @@ public class PlayoutBuilderTests
(PlayoutBuilder builder, Playout playout) = (PlayoutBuilder builder, Playout playout) =
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, _cancellationToken);
configRepo.Verify(); configRepo.Verify();
@ -123,7 +131,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
configRepo.Verify(); configRepo.Verify();
@ -155,7 +163,7 @@ public class PlayoutBuilderTests
(PlayoutBuilder builder, Playout playout) = (PlayoutBuilder builder, Playout playout) =
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, _cancellationToken);
configRepo.Verify(); configRepo.Verify();
@ -184,7 +192,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
configRepo.Verify(); configRepo.Verify();
@ -217,7 +225,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -247,7 +255,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -268,7 +276,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -289,7 +297,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(1); DateTimeOffset start = HoursAfterMidnight(1);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(2); result.Items.Count.Should().Be(2);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -313,7 +321,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(4); DateTimeOffset finish = start + TimeSpan.FromHours(4);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(4); result.Items.Count.Should().Be(4);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -342,7 +350,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().MediaItemId.Should().Be(1); result.Items.Head().MediaItemId.Should().Be(1);
@ -355,7 +363,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(1); DateTimeOffset start2 = HoursAfterMidnight(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
playout,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
result2.Items.Count.Should().Be(2); result2.Items.Count.Should().Be(2);
result2.Items.Last().StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); result2.Items.Last().StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
@ -380,7 +393,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().MediaItemId.Should().Be(1); result.Items.Head().MediaItemId.Should().Be(1);
@ -392,7 +405,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(1); DateTimeOffset start2 = HoursAfterMidnight(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(12); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(12);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
playout,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
result2.Items.Count.Should().Be(3); result2.Items.Count.Should().Be(3);
result2.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); result2.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
@ -422,7 +440,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6));
@ -435,7 +453,7 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(0); DateTimeOffset start2 = HoursAfterMidnight(0);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Reset, start2, finish2); Playout result2 = await builder.Build(playout, PlayoutBuildMode.Reset, start2, finish2, _cancellationToken);
result2.Items.Count.Should().Be(6); result2.Items.Count.Should().Be(6);
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6));
@ -462,7 +480,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
result.ProgramScheduleAnchors.Count.Should().Be(1); result.ProgramScheduleAnchors.Count.Should().Be(1);
@ -475,7 +493,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(0); DateTimeOffset start2 = HoursAfterMidnight(0);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
playout,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed; int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed;
@ -564,7 +587,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(5); result.Items.Count.Should().Be(5);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -661,7 +684,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(30); DateTimeOffset finish = start + TimeSpan.FromHours(30);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(28); result.Items.Count.Should().Be(28);
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
@ -806,7 +829,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(7); DateTimeOffset finish = start + TimeSpan.FromHours(7);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
@ -909,7 +932,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(24); DateTimeOffset finish = start + TimeSpan.FromHours(24);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
@ -1021,7 +1044,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(32); DateTimeOffset finish = start + TimeSpan.FromHours(32);
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken);
result.Items.Count.Should().Be(5); result.Items.Count.Should().Be(5);
@ -1126,7 +1149,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(7); result.Items.Count.Should().Be(7);
@ -1235,7 +1258,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
@ -1349,7 +1372,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5); DateTimeOffset finish = start + TimeSpan.FromHours(5);
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken);
result.Items.Count.Should().Be(4); result.Items.Count.Should().Be(4);
@ -1452,7 +1475,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5); DateTimeOffset finish = start + TimeSpan.FromHours(5);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(5); result.Items.Count.Should().Be(5);
@ -1566,7 +1589,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5); DateTimeOffset finish = start + TimeSpan.FromHours(5);
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken);
result.Items.Count.Should().Be(4); result.Items.Count.Should().Be(4);
@ -1691,7 +1714,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(12); result.Items.Count.Should().Be(12);
@ -1808,7 +1831,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(1); DateTimeOffset finish = start + TimeSpan.FromHours(1);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(2); result.Items.Count.Should().Be(2);
@ -1885,7 +1908,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
@ -1921,7 +1944,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromDays(2); DateTimeOffset finish = start + TimeSpan.FromDays(2);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(8); result.Items.Count.Should().Be(8);
result.Items[0].MediaItemId.Should().Be(1); result.Items[0].MediaItemId.Should().Be(1);
@ -1980,7 +2003,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6));
@ -1993,7 +2016,7 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(0); DateTimeOffset start2 = HoursAfterMidnight(0);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Reset, start2, finish2); Playout result2 = await builder.Build(playout, PlayoutBuildMode.Reset, start2, finish2, _cancellationToken);
result2.Items.Count.Should().Be(6); result2.Items.Count.Should().Be(6);
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6));
@ -2098,7 +2121,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(24); DateTimeOffset start = HoursAfterMidnight(24);
DateTimeOffset finish = start + TimeSpan.FromDays(1); DateTimeOffset finish = start + TimeSpan.FromDays(1);
Playout result = await builder.Build(playout, PlayoutBuildMode.Refresh, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Refresh, start, finish, _cancellationToken);
result.Items.Count.Should().Be(4); result.Items.Count.Should().Be(4);
result.Items[0].MediaItemId.Should().Be(2); result.Items[0].MediaItemId.Should().Be(2);
@ -2135,7 +2158,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().MediaItemId.Should().Be(1); result.Items.Head().MediaItemId.Should().Be(1);
@ -2148,7 +2171,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(1); DateTimeOffset start2 = HoursAfterMidnight(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
playout,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
result2.Items.Count.Should().Be(2); result2.Items.Count.Should().Be(2);
result2.Items.Last().StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); result2.Items.Last().StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
@ -2173,7 +2201,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(1); result.Items.Count.Should().Be(1);
result.Items.Head().MediaItemId.Should().Be(1); result.Items.Head().MediaItemId.Should().Be(1);
@ -2185,7 +2213,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(1); DateTimeOffset start2 = HoursAfterMidnight(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(12); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(12);
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
playout,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
result2.Items.Count.Should().Be(3); result2.Items.Count.Should().Be(3);
result2.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); result2.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
@ -2212,7 +2245,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromDays(1); DateTimeOffset finish = start + TimeSpan.FromDays(1);
await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
playout.Items.Count.Should().Be(4); playout.Items.Count.Should().Be(4);
playout.Items.Map(i => i.MediaItemId).ToList().Should().Equal(1, 2, 1, 2); playout.Items.Map(i => i.MediaItemId).ToList().Should().Equal(1, 2, 1, 2);
@ -2249,7 +2282,7 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(1); DateTimeOffset start2 = HoursAfterMidnight(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(1); DateTimeOffset finish2 = start2 + TimeSpan.FromDays(1);
await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2, _cancellationToken);
playout.Items.Count.Should().Be(5); playout.Items.Count.Should().Be(5);
playout.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); playout.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0));
@ -2272,7 +2305,7 @@ public class PlayoutBuilderTests
DateTimeOffset start3 = HoursAfterMidnight(2); DateTimeOffset start3 = HoursAfterMidnight(2);
DateTimeOffset finish3 = start3 + TimeSpan.FromDays(1); DateTimeOffset finish3 = start3 + TimeSpan.FromDays(1);
await builder.Build(playout, PlayoutBuildMode.Continue, start3, finish3); await builder.Build(playout, PlayoutBuildMode.Continue, start3, finish3, _cancellationToken);
playout.Items.Count.Should().Be(5); playout.Items.Count.Should().Be(5);
playout.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); playout.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0));
@ -2306,7 +2339,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
result.ProgramScheduleAnchors.Count.Should().Be(1); result.ProgramScheduleAnchors.Count.Should().Be(1);
@ -2318,7 +2351,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(0); DateTimeOffset start2 = HoursAfterMidnight(0);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
result,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed; int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed;
@ -2340,7 +2378,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5); DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5);
DateTimeOffset finish = start + TimeSpan.FromDays(2); DateTimeOffset finish = start + TimeSpan.FromDays(2);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(53); result.Items.Count.Should().Be(53);
result.ProgramScheduleAnchors.Count.Should().Be(2); result.ProgramScheduleAnchors.Count.Should().Be(2);
@ -2368,7 +2406,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = start.AddHours(1); DateTimeOffset start2 = start.AddHours(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2); DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2);
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
result,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
PlayoutProgramScheduleAnchor continueAnchor = PlayoutProgramScheduleAnchor continueAnchor =
result2.ProgramScheduleAnchors.First(x => x.AnchorDate is null); result2.ProgramScheduleAnchors.First(x => x.AnchorDate is null);
@ -2393,7 +2436,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6); DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(6); result.Items.Count.Should().Be(6);
result.ProgramScheduleAnchors.Count.Should().Be(2); result.ProgramScheduleAnchors.Count.Should().Be(2);
@ -2406,7 +2449,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = HoursAfterMidnight(0); DateTimeOffset start2 = HoursAfterMidnight(0);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
result,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
primaryAnchor = result2.ProgramScheduleAnchors.First(a => a.SmartCollectionId == 1); primaryAnchor = result2.ProgramScheduleAnchors.First(a => a.SmartCollectionId == 1);
int secondSeedValue = primaryAnchor.EnumeratorState.Seed; int secondSeedValue = primaryAnchor.EnumeratorState.Seed;
@ -2430,7 +2478,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5); DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5);
DateTimeOffset finish = start + TimeSpan.FromDays(2); DateTimeOffset finish = start + TimeSpan.FromDays(2);
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
result.Items.Count.Should().Be(53); result.Items.Count.Should().Be(53);
result.ProgramScheduleAnchors.Count.Should().Be(4); result.ProgramScheduleAnchors.Count.Should().Be(4);
@ -2450,7 +2498,12 @@ public class PlayoutBuilderTests
DateTimeOffset start2 = start.AddHours(i); DateTimeOffset start2 = start.AddHours(i);
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2); DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2);
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); Playout result2 = await builder.Build(
result,
PlayoutBuildMode.Continue,
start2,
finish2,
_cancellationToken);
PlayoutProgramScheduleAnchor continueAnchor = PlayoutProgramScheduleAnchor continueAnchor =
result2.ProgramScheduleAnchors result2.ProgramScheduleAnchors
@ -2554,7 +2607,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(32); DateTimeOffset finish = start + TimeSpan.FromHours(32);
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken);
result.Items.Count.Should().Be(5); result.Items.Count.Should().Be(5);
@ -2666,7 +2719,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5); DateTimeOffset finish = start + TimeSpan.FromHours(5);
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken);
result.Items.Count.Should().Be(4); result.Items.Count.Should().Be(4);
@ -2778,7 +2831,7 @@ public class PlayoutBuilderTests
DateTimeOffset start = HoursAfterMidnight(0); DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5); DateTimeOffset finish = start + TimeSpan.FromHours(5);
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish, _cancellationToken);
result.Items.Count.Should().Be(4); result.Items.Count.Should().Be(4);

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

@ -13,8 +13,18 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class PlayoutModeSchedulerBaseTests : SchedulerTestBase public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
{ {
private CancellationToken _cancellationToken;
private PlayoutModeSchedulerBase<ProgramScheduleItem> _scheduler;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
_scheduler = new TestScheduler();
}
[TestFixture] [TestFixture]
public class CalculateEndTimeWithFiller public class CalculateEndTimeWithFiller : PlayoutModeSchedulerBaseTests
{ {
[Test] [Test]
public void Should_Not_Touch_Enumerator() public void Should_Not_Touch_Enumerator()
@ -197,7 +207,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
} }
[TestFixture] [TestFixture]
public class AddFiller public class AddFiller : PlayoutModeSchedulerBaseTests
{ {
[Test] [Test]
public void Should_Not_Crash_Mid_Roll_Zero_Chapters() public void Should_Not_Crash_Mid_Roll_Zero_Chapters()
@ -224,13 +234,14 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator); PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
List<PlayoutItem> playoutItems = Scheduler() List<PlayoutItem> playoutItems = _scheduler
.AddFiller( .AddFiller(
startState, startState,
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
new PlayoutItem(), new PlayoutItem(),
new List<MediaChapter>()); new List<MediaChapter>(),
_cancellationToken);
playoutItems.Count.Should().Be(1); playoutItems.Count.Should().Be(1);
} }
@ -275,13 +286,14 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
// too lazy to make another enumerator for the filler that we don't want // too lazy to make another enumerator for the filler that we don't want
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), enumerator); enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), enumerator);
List<PlayoutItem> playoutItems = Scheduler() List<PlayoutItem> playoutItems = _scheduler
.AddFiller( .AddFiller(
startState, startState,
enumerators, enumerators,
scheduleItem, scheduleItem,
new PlayoutItem(), new PlayoutItem(),
new List<MediaChapter> { new() }); new List<MediaChapter> { new() },
_cancellationToken);
playoutItems.Count.Should().Be(1); playoutItems.Count.Should().Be(1);
} }
@ -331,7 +343,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), fillerEnumerator); enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), fillerEnumerator);
List<PlayoutItem> playoutItems = Scheduler() List<PlayoutItem> playoutItems = _scheduler
.AddFiller( .AddFiller(
startState, startState,
enumerators, enumerators,
@ -346,7 +358,8 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
{ {
new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) }, new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) },
new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(60) } new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(60) }
}); },
_cancellationToken);
playoutItems.Count.Should().Be(3); playoutItems.Count.Should().Be(3);
playoutItems[0].MediaItemId.Should().Be(1); playoutItems[0].MediaItemId.Should().Be(1);
@ -421,7 +434,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), midRollFillerEnumerator); enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), midRollFillerEnumerator);
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.PostRollFiller), postRollFillerEnumerator); enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.PostRollFiller), postRollFillerEnumerator);
List<PlayoutItem> playoutItems = Scheduler() List<PlayoutItem> playoutItems = _scheduler
.AddFiller( .AddFiller(
startState, startState,
enumerators, enumerators,
@ -436,7 +449,8 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
{ {
new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) }, new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) },
new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(45) } new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(45) }
}); },
_cancellationToken);
playoutItems.Count.Should().Be(5); playoutItems.Count.Should().Be(5);
@ -525,7 +539,7 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), midRollFillerEnumerator); enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.MidRollFiller), midRollFillerEnumerator);
enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.PostRollFiller), postRollFillerEnumerator); enumerators.Add(CollectionKey.ForFillerPreset(scheduleItem.PostRollFiller), postRollFillerEnumerator);
List<PlayoutItem> playoutItems = Scheduler() List<PlayoutItem> playoutItems = _scheduler
.AddFiller( .AddFiller(
startState, startState,
enumerators, enumerators,
@ -540,7 +554,8 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
{ {
new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) }, new() { StartTime = TimeSpan.Zero, EndTime = TimeSpan.FromMinutes(6) },
new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(45) } new() { StartTime = TimeSpan.FromMinutes(6), EndTime = TimeSpan.FromMinutes(45) }
}); },
_cancellationToken);
playoutItems.Count.Should().Be(5); playoutItems.Count.Should().Be(5);
@ -606,8 +621,6 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
} }
}; };
private static PlayoutModeSchedulerBase<ProgramScheduleItem> Scheduler() =>
new TestScheduler();
private class TestScheduler : PlayoutModeSchedulerBase<ProgramScheduleItem> private class TestScheduler : PlayoutModeSchedulerBase<ProgramScheduleItem>
{ {
@ -632,7 +645,8 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop) => DateTimeOffset hardStop,
CancellationToken cancellationToken) =>
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }

35
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs

@ -11,6 +11,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class PlayoutModeSchedulerDurationTests : SchedulerTestBase public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Should_Fill_Exact_Duration() public void Should_Fill_Exact_Duration()
{ {
@ -45,7 +53,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -117,7 +126,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -188,7 +198,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0))); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -256,7 +267,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
// duration block should end after exact duration, with gap // duration block should end after exact duration, with gap
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
@ -338,7 +350,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -424,7 +437,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -522,7 +536,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0))); playoutItems.Last().FinishOffset.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0)));
@ -637,7 +652,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -738,7 +754,8 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutItems.Should().BeEmpty(); playoutItems.Should().BeEmpty();

47
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs

@ -11,6 +11,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class PlayoutModeSchedulerFloodTests : SchedulerTestBase public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Should_Fill_Exactly_To_Next_Schedule_Item() public void Should_Fill_Exactly_To_Next_Schedule_Item()
{ {
@ -51,7 +59,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -125,7 +134,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
scheduleItem, scheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(6)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(6));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -221,7 +231,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -305,7 +316,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.PostRollFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.PostRollFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -392,7 +404,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0))); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -473,7 +486,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -570,7 +584,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -657,7 +672,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0))); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -770,7 +786,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -881,7 +898,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
enumerator2), enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
hardStop); hardStop,
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(2)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(2));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -986,7 +1004,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
enumerator2), enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
hardStop); hardStop,
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(2)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(2));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -1099,7 +1118,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -1172,7 +1192,8 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutItems.Should().BeEmpty(); playoutItems.Should().BeEmpty();

32
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs

@ -11,6 +11,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Should_Fill_Exactly_To_Next_Schedule_Item() public void Should_Fill_Exactly_To_Next_Schedule_Item()
{ {
@ -52,7 +60,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -126,7 +135,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0))); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 45, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -208,7 +218,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -306,7 +317,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -394,7 +406,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0))); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -509,7 +522,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -630,7 +644,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -709,7 +724,8 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutItems.Should().BeEmpty(); playoutItems.Should().BeEmpty();

38
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs

@ -11,6 +11,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class PlayoutModeSchedulerOneTests : SchedulerTestBase public class PlayoutModeSchedulerOneTests : SchedulerTestBase
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Should_Have_Gap_With_No_Tail_No_Fallback() public void Should_Have_Gap_With_No_Tail_No_Fallback()
{ {
@ -45,7 +53,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(1)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(1));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -127,7 +136,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(1)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(1));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -194,7 +204,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -275,7 +286,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -346,7 +358,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0))); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.Add(new TimeSpan(2, 57, 0)));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -443,7 +456,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -546,7 +560,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -631,7 +646,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -730,7 +746,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
enumerator3), enumerator3),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3)); playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(3));
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
@ -808,7 +825,8 @@ public class PlayoutModeSchedulerOneTests : SchedulerTestBase
CollectionEnumerators(scheduleItem, enumerator), CollectionEnumerators(scheduleItem, enumerator),
scheduleItem, scheduleItem,
NextScheduleItem, NextScheduleItem,
HardStop(scheduleItemsEnumerator)); HardStop(scheduleItemsEnumerator),
_cancellationToken);
playoutItems.Should().BeEmpty(); playoutItems.Should().BeEmpty();

8
ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs

@ -17,6 +17,14 @@ public class RandomizedContentTests
1, 8, 2, 1, 1, 5, 5, 5, 3, 5, 8, 10, 4, 8, 7, 3, 3, 4, 4, 9, 2, 8, 8, 10, 8, 4, 3, 10, 7, 8, 9, 9 1, 8, 2, 1, 1, 5, 5, 5, 3, 5, 8, 10, 4, 8, 7, 3, 3, 4, 4, 9, 2, 8, 8, 10, 8, 4, 3, 10, 7, 8, 9, 9
}; };
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Episodes_Should_Randomize() public void Episodes_Should_Randomize()
{ {

59
ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs

@ -30,6 +30,8 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[Explicit] [Explicit]
public class ScheduleIntegrationTests public class ScheduleIntegrationTests
{ {
private CancellationToken _cancellationToken;
public ScheduleIntegrationTests() public ScheduleIntegrationTests()
{ {
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
@ -40,6 +42,12 @@ public class ScheduleIntegrationTests
.Destructure.UsingAttributes() .Destructure.UsingAttributes()
.CreateLogger(); .CreateLogger();
} }
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token;
}
[Test] [Test]
public async Task TestExistingData() public async Task TestExistingData()
@ -122,38 +130,48 @@ public class ScheduleIntegrationTests
provider.GetRequiredService<ILogger<PlayoutBuilder>>()); provider.GetRequiredService<ILogger<PlayoutBuilder>>());
{ {
await using TvContext context = await factory.CreateDbContextAsync(); await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken);
Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID); Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID);
Playout playout = maybePlayout.ValueUnsafe(); Playout playout = maybePlayout.ValueUnsafe();
await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); await builder.Build(playout, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync(_cancellationToken);
} }
for (var i = 1; i <= (24 * 1); i++) for (var i = 1; i <= (24 * 1); i++)
{ {
await using TvContext context = await factory.CreateDbContextAsync(); await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken);
Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID); Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID);
Playout playout = maybePlayout.ValueUnsafe(); Playout playout = maybePlayout.ValueUnsafe();
await builder.Build(playout, PlayoutBuildMode.Continue, start.AddHours(i), finish.AddHours(i)); await builder.Build(
playout,
PlayoutBuildMode.Continue,
start.AddHours(i),
finish.AddHours(i),
_cancellationToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync(_cancellationToken);
} }
for (var i = 25; i <= 26; i++) for (var i = 25; i <= 26; i++)
{ {
await using TvContext context = await factory.CreateDbContextAsync(); await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken);
Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID); Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID);
Playout playout = maybePlayout.ValueUnsafe(); Playout playout = maybePlayout.ValueUnsafe();
await builder.Build(playout, PlayoutBuildMode.Continue, start.AddHours(i), finish.AddHours(i)); await builder.Build(
playout,
PlayoutBuildMode.Continue,
start.AddHours(i),
finish.AddHours(i),
_cancellationToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync(_cancellationToken);
} }
} }
@ -216,8 +234,8 @@ public class ScheduleIntegrationTests
MediaSource = new LocalMediaSource() MediaSource = new LocalMediaSource()
}; };
await dbContext.Libraries.AddAsync(library); await dbContext.Libraries.AddAsync(library, _cancellationToken);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync(_cancellationToken);
var movies = new List<Movie>(); var movies = new List<Movie>();
for (var i = 1; i < 25; i++) for (var i = 1; i < 25; i++)
@ -243,8 +261,8 @@ public class ScheduleIntegrationTests
movies.Add(movie); movies.Add(movie);
} }
await dbContext.Movies.AddRangeAsync(movies); await dbContext.Movies.AddRangeAsync(movies, _cancellationToken);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync(_cancellationToken);
var collection = new Collection var collection = new Collection
{ {
@ -252,8 +270,8 @@ public class ScheduleIntegrationTests
MediaItems = movies.Cast<MediaItem>().ToList() MediaItems = movies.Cast<MediaItem>().ToList()
}; };
await dbContext.Collections.AddAsync(collection); await dbContext.Collections.AddAsync(collection, _cancellationToken);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync(_cancellationToken);
var scheduleItems = new List<ProgramScheduleItem> var scheduleItems = new List<ProgramScheduleItem>
{ {
@ -283,14 +301,19 @@ public class ScheduleIntegrationTests
for (var i = 0; i <= (24 * 4); i++) for (var i = 0; i <= (24 * 4); i++)
{ {
await using TvContext context = await factory.CreateDbContextAsync(); await using TvContext context = await factory.CreateDbContextAsync(_cancellationToken);
Option<Playout> maybePlayout = await GetPlayout(context, playoutId); Option<Playout> maybePlayout = await GetPlayout(context, playoutId);
Playout playout = maybePlayout.ValueUnsafe(); Playout playout = maybePlayout.ValueUnsafe();
await builder.Build(playout, PlayoutBuildMode.Continue, start.AddHours(i), finish.AddHours(i)); await builder.Build(
playout,
PlayoutBuildMode.Continue,
start.AddHours(i),
finish.AddHours(i),
_cancellationToken);
await context.SaveChangesAsync(); await context.SaveChangesAsync(_cancellationToken);
} }
} }

8
ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs

@ -8,6 +8,14 @@ namespace ErsatzTV.Core.Tests.Scheduling;
[TestFixture] [TestFixture]
public class SeasonEpisodeContentTests public class SeasonEpisodeContentTests
{ {
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Episodes_Should_Sort_By_EpisodeNumber() public void Episodes_Should_Sort_By_EpisodeNumber()
{ {

20
ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs

@ -11,6 +11,14 @@ public class ShuffledContentTests
// this seed will produce (shuffle) 1-10 in order // this seed will produce (shuffle) 1-10 in order
private const int MagicSeed = 670596; private const int MagicSeed = 670596;
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Episodes_Should_Not_Duplicate_When_Reshuffling() public void Episodes_Should_Not_Duplicate_When_Reshuffling()
{ {
@ -20,7 +28,7 @@ public class ShuffledContentTests
var state = new CollectionEnumeratorState { Seed = 8 }; var state = new CollectionEnumeratorState { Seed = 8 };
var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList(); var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList();
var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state); var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state, _cancellationToken);
var list = new List<int>(); var list = new List<int>();
for (var i = 1; i <= 1000; i++) for (var i = 1; i <= 1000; i++)
@ -48,7 +56,7 @@ public class ShuffledContentTests
var state = new CollectionEnumeratorState(); var state = new CollectionEnumeratorState();
var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList(); var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList();
var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state); var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state, _cancellationToken);
var list = new List<int>(); var list = new List<int>();
for (var i = 1; i <= 10; i++) for (var i = 1; i <= 10; i++)
@ -69,7 +77,7 @@ public class ShuffledContentTests
var state = new CollectionEnumeratorState(); var state = new CollectionEnumeratorState();
var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList(); var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList();
var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state); var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state, _cancellationToken);
var list = new List<int>(); var list = new List<int>();
for (var i = 1; i <= 10; i++) for (var i = 1; i <= 10; i++)
@ -90,7 +98,7 @@ public class ShuffledContentTests
var state = new CollectionEnumeratorState(); var state = new CollectionEnumeratorState();
var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList(); var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList();
var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state); var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state, _cancellationToken);
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
@ -106,7 +114,7 @@ public class ShuffledContentTests
var state = new CollectionEnumeratorState { Index = 5, Seed = MagicSeed }; var state = new CollectionEnumeratorState { Index = 5, Seed = MagicSeed };
var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList(); var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList();
var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state); var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state, _cancellationToken);
for (var i = 6; i <= 10; i++) for (var i = 6; i <= 10; i++)
{ {
@ -125,7 +133,7 @@ public class ShuffledContentTests
var state = new CollectionEnumeratorState { Index = 10, Seed = MagicSeed }; var state = new CollectionEnumeratorState { Index = 10, Seed = MagicSeed };
var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList(); var groupedMediaItems = contents.Map(mi => new GroupedMediaItem(mi, null)).ToList();
var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state); var shuffledContent = new ShuffledMediaCollectionEnumerator(groupedMediaItems, state, _cancellationToken);
shuffledContent.State.Index.Should().Be(0); shuffledContent.State.Index.Should().Be(0);
shuffledContent.State.Seed.Should().NotBe(MagicSeed); shuffledContent.State.Seed.Should().NotBe(MagicSeed);

16
ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs

@ -15,12 +15,20 @@ public class ShuffledMediaCollectionEnumeratorTests
new GroupedMediaItem(new MediaItem { Id = 2 }, new List<MediaItem>()), new GroupedMediaItem(new MediaItem { Id = 2 }, new List<MediaItem>()),
new GroupedMediaItem(new MediaItem { Id = 3 }, new List<MediaItem>()) new GroupedMediaItem(new MediaItem { Id = 3 }, new List<MediaItem>())
}; };
private CancellationToken _cancellationToken;
[SetUp]
public void SetUp()
{
_cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
}
[Test] [Test]
public void Peek_Zero_Should_Match_Current() public void Peek_Zero_Should_Match_Current()
{ {
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 }; var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state); var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state, _cancellationToken);
Option<MediaItem> peek = enumerator.Peek(0); Option<MediaItem> peek = enumerator.Peek(0);
Option<MediaItem> current = enumerator.Current; Option<MediaItem> current = enumerator.Current;
@ -35,7 +43,7 @@ public class ShuffledMediaCollectionEnumeratorTests
public void Peek_One_Should_Match_Next() public void Peek_One_Should_Match_Next()
{ {
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 }; var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state); var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state, _cancellationToken);
Option<MediaItem> peek = enumerator.Peek(1); Option<MediaItem> peek = enumerator.Peek(1);
@ -52,7 +60,7 @@ public class ShuffledMediaCollectionEnumeratorTests
public void Peek_Two_Should_Match_NextNext() public void Peek_Two_Should_Match_NextNext()
{ {
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 }; var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state); var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state, _cancellationToken);
Option<MediaItem> peek = enumerator.Peek(2); Option<MediaItem> peek = enumerator.Peek(2);
@ -70,7 +78,7 @@ public class ShuffledMediaCollectionEnumeratorTests
public void Peek_Three_Should_Match_NextNextNext() public void Peek_Three_Should_Match_NextNextNext()
{ {
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 }; var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state); var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state, _cancellationToken);
Option<MediaItem> peek = enumerator.Peek(3); Option<MediaItem> peek = enumerator.Peek(3);

3
ErsatzTV.Core/Interfaces/Scheduling/IMultiEpisodeShuffleCollectionEnumeratorFactory.cs

@ -7,5 +7,6 @@ public interface IMultiEpisodeShuffleCollectionEnumeratorFactory
IMediaCollectionEnumerator Create( IMediaCollectionEnumerator Create(
string jsScriptPath, string jsScriptPath,
IList<MediaItem> mediaItems, IList<MediaItem> mediaItems,
CollectionEnumeratorState state); CollectionEnumeratorState state,
CancellationToken cancellationToken);
} }

2
ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs

@ -5,5 +5,5 @@ namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IPlayoutBuilder public interface IPlayoutBuilder
{ {
Task<Playout> Build(Playout playout, PlayoutBuildMode mode); Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
} }

3
ErsatzTV.Core/Interfaces/Scheduling/IPlayoutModeScheduler.cs

@ -10,5 +10,6 @@ public interface IPlayoutModeScheduler<in T> where T : ProgramScheduleItem
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
T scheduleItem, T scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop); DateTimeOffset hardStop,
CancellationToken cancellationToken);
} }

91
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -40,7 +40,7 @@ public class PlayoutBuilder : IPlayoutBuilder
_logger = logger; _logger = logger;
} }
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode) public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
{ {
foreach (PlayoutParameters parameters in await Validate(playout)) foreach (PlayoutParameters parameters in await Validate(playout))
{ {
@ -50,35 +50,43 @@ public class PlayoutBuilder : IPlayoutBuilder
// return await Build(playout, mode, parameters with { Start = parameters.Start.AddDays(-2) }); // return await Build(playout, mode, parameters with { Start = parameters.Start.AddDays(-2) });
// } // }
return await Build(playout, mode, parameters); return await Build(playout, mode, parameters, cancellationToken);
} }
return playout; return playout;
} }
private Task<Playout> Build(Playout playout, PlayoutBuildMode mode, PlayoutParameters parameters) => private Task<Playout> Build(
Playout playout,
PlayoutBuildMode mode,
PlayoutParameters parameters,
CancellationToken cancellationToken) =>
mode switch mode switch
{ {
PlayoutBuildMode.Refresh => RefreshPlayout(playout, parameters), PlayoutBuildMode.Refresh => RefreshPlayout(playout, parameters, cancellationToken),
PlayoutBuildMode.Reset => ResetPlayout(playout, parameters), PlayoutBuildMode.Reset => ResetPlayout(playout, parameters, cancellationToken),
_ => ContinuePlayout(playout, parameters) _ => ContinuePlayout(playout, parameters, cancellationToken)
}; };
internal async Task<Playout> Build( internal async Task<Playout> Build(
Playout playout, Playout playout,
PlayoutBuildMode mode, PlayoutBuildMode mode,
DateTimeOffset start, DateTimeOffset start,
DateTimeOffset finish) DateTimeOffset finish,
CancellationToken cancellationToken)
{ {
foreach (PlayoutParameters parameters in await Validate(playout)) foreach (PlayoutParameters parameters in await Validate(playout))
{ {
return await Build(playout, mode, parameters with { Start = start, Finish = finish }); return await Build(playout, mode, parameters with { Start = start, Finish = finish }, cancellationToken);
} }
return playout; return playout;
} }
private async Task<Playout> RefreshPlayout(Playout playout, PlayoutParameters parameters) private async Task<Playout> RefreshPlayout(
Playout playout,
PlayoutParameters parameters,
CancellationToken cancellationToken)
{ {
_logger.LogDebug( _logger.LogDebug(
"Refreshing playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}", "Refreshing playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
@ -183,10 +191,14 @@ public class PlayoutBuilder : IPlayoutBuilder
parameters.Start, parameters.Start,
parameters.Finish, parameters.Finish,
parameters.CollectionMediaItems, parameters.CollectionMediaItems,
false); false,
cancellationToken);
} }
private async Task<Playout> ResetPlayout(Playout playout, PlayoutParameters parameters) private async Task<Playout> ResetPlayout(
Playout playout,
PlayoutParameters parameters,
CancellationToken cancellationToken)
{ {
_logger.LogDebug( _logger.LogDebug(
"Resetting playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}", "Resetting playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
@ -203,12 +215,16 @@ public class PlayoutBuilder : IPlayoutBuilder
parameters.Start, parameters.Start,
parameters.Finish, parameters.Finish,
parameters.CollectionMediaItems, parameters.CollectionMediaItems,
true); true,
cancellationToken);
return playout; return playout;
} }
private async Task<Playout> ContinuePlayout(Playout playout, PlayoutParameters parameters) private async Task<Playout> ContinuePlayout(
Playout playout,
PlayoutParameters parameters,
CancellationToken cancellationToken)
{ {
_logger.LogDebug( _logger.LogDebug(
"Building playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}", "Building playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
@ -227,7 +243,8 @@ public class PlayoutBuilder : IPlayoutBuilder
parameters.Start, parameters.Start,
parameters.Finish, parameters.Finish,
parameters.CollectionMediaItems, parameters.CollectionMediaItems,
false); false,
cancellationToken);
return playout; return playout;
} }
@ -289,7 +306,8 @@ public class PlayoutBuilder : IPlayoutBuilder
DateTimeOffset playoutStart, DateTimeOffset playoutStart,
DateTimeOffset playoutFinish, DateTimeOffset playoutFinish,
Map<CollectionKey, List<MediaItem>> collectionMediaItems, Map<CollectionKey, List<MediaItem>> collectionMediaItems,
bool randomStartPoint) bool randomStartPoint,
CancellationToken cancellationToken)
{ {
DateTimeOffset trimBefore = playoutStart.AddHours(-4); DateTimeOffset trimBefore = playoutStart.AddHours(-4);
DateTimeOffset trimAfter = playoutFinish; DateTimeOffset trimAfter = playoutFinish;
@ -308,7 +326,14 @@ public class PlayoutBuilder : IPlayoutBuilder
while (finish < playoutFinish) while (finish < playoutFinish)
{ {
_logger.LogDebug("Building playout from {Start} to {Finish}", start, finish); _logger.LogDebug("Building playout from {Start} to {Finish}", start, finish);
playout = await BuildPlayoutItems(playout, start, finish, collectionMediaItems, true, randomStartPoint); playout = await BuildPlayoutItems(
playout,
start,
finish,
collectionMediaItems,
true,
randomStartPoint,
cancellationToken);
// only randomize once (at the start of the playout) // only randomize once (at the start of the playout)
randomStartPoint = false; randomStartPoint = false;
@ -327,7 +352,8 @@ public class PlayoutBuilder : IPlayoutBuilder
playoutFinish, playoutFinish,
collectionMediaItems, collectionMediaItems,
false, false,
randomStartPoint); randomStartPoint,
cancellationToken);
} }
// remove old items // remove old items
@ -359,7 +385,8 @@ public class PlayoutBuilder : IPlayoutBuilder
DateTimeOffset playoutFinish, DateTimeOffset playoutFinish,
Map<CollectionKey, List<MediaItem>> collectionMediaItems, Map<CollectionKey, List<MediaItem>> collectionMediaItems,
bool saveAnchorDate, bool saveAnchorDate,
bool randomStartPoint) bool randomStartPoint,
CancellationToken cancellationToken)
{ {
ProgramSchedule activeSchedule = PlayoutScheduleSelector.GetProgramScheduleFor( ProgramSchedule activeSchedule = PlayoutScheduleSelector.GetProgramScheduleFor(
playout.ProgramSchedule, playout.ProgramSchedule,
@ -391,7 +418,8 @@ public class PlayoutBuilder : IPlayoutBuilder
collectionKey, collectionKey,
mediaItems, mediaItems,
playbackOrder, playbackOrder,
randomStartPoint); randomStartPoint,
cancellationToken);
collectionEnumerators.Add(collectionKey, enumerator); collectionEnumerators.Add(collectionKey, enumerator);
} }
@ -443,7 +471,7 @@ public class PlayoutBuilder : IPlayoutBuilder
var timeCount = new Dictionary<DateTimeOffset, int>(); var timeCount = new Dictionary<DateTimeOffset, int>();
// loop until we're done filling the desired amount of time // loop until we're done filling the desired amount of time
while (playoutBuilderState.CurrentTime < playoutFinish) while (playoutBuilderState.CurrentTime < playoutFinish && !cancellationToken.IsCancellationRequested)
{ {
if (timeCount.TryGetValue(playoutBuilderState.CurrentTime, out int count)) if (timeCount.TryGetValue(playoutBuilderState.CurrentTime, out int count))
{ {
@ -477,25 +505,29 @@ public class PlayoutBuilder : IPlayoutBuilder
collectionEnumerators, collectionEnumerators,
multiple, multiple,
nextScheduleItem, nextScheduleItem,
playoutFinish), playoutFinish,
cancellationToken),
ProgramScheduleItemDuration duration => schedulerDuration.Schedule( ProgramScheduleItemDuration duration => schedulerDuration.Schedule(
playoutBuilderState, playoutBuilderState,
collectionEnumerators, collectionEnumerators,
duration, duration,
nextScheduleItem, nextScheduleItem,
playoutFinish), playoutFinish,
cancellationToken),
ProgramScheduleItemFlood flood => schedulerFlood.Schedule( ProgramScheduleItemFlood flood => schedulerFlood.Schedule(
playoutBuilderState, playoutBuilderState,
collectionEnumerators, collectionEnumerators,
flood, flood,
nextScheduleItem, nextScheduleItem,
playoutFinish), playoutFinish,
cancellationToken),
ProgramScheduleItemOne one => schedulerOne.Schedule( ProgramScheduleItemOne one => schedulerOne.Schedule(
playoutBuilderState, playoutBuilderState,
collectionEnumerators, collectionEnumerators,
one, one,
nextScheduleItem, nextScheduleItem,
playoutFinish), playoutFinish,
cancellationToken),
_ => throw new ArgumentOutOfRangeException(nameof(scheduleItem)) _ => throw new ArgumentOutOfRangeException(nameof(scheduleItem))
}; };
@ -715,7 +747,8 @@ public class PlayoutBuilder : IPlayoutBuilder
CollectionKey collectionKey, CollectionKey collectionKey,
List<MediaItem> mediaItems, List<MediaItem> mediaItems,
PlaybackOrder playbackOrder, PlaybackOrder playbackOrder,
bool randomStartPoint) bool randomStartPoint,
CancellationToken cancellationToken)
{ {
Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors
.OrderByDescending(a => a.AnchorDate ?? DateTime.MaxValue) .OrderByDescending(a => a.AnchorDate ?? DateTime.MaxValue)
@ -789,7 +822,8 @@ public class PlayoutBuilder : IPlayoutBuilder
return new ShuffleInOrderCollectionEnumerator( return new ShuffleInOrderCollectionEnumerator(
await GetCollectionItemsForShuffleInOrder(collectionKey), await GetCollectionItemsForShuffleInOrder(collectionKey),
state, state,
activeSchedule.RandomStartPoint); activeSchedule.RandomStartPoint,
cancellationToken);
case PlaybackOrder.MultiEpisodeShuffle when case PlaybackOrder.MultiEpisodeShuffle when
collectionKey.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow && collectionKey.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow &&
collectionKey.MediaItemId.HasValue: collectionKey.MediaItemId.HasValue:
@ -808,7 +842,7 @@ public class PlayoutBuilder : IPlayoutBuilder
_logger.LogDebug("Found JS Script at {Path}", jsScriptPath); _logger.LogDebug("Found JS Script at {Path}", jsScriptPath);
try try
{ {
return _multiEpisodeFactory.Create(jsScriptPath, mediaItems, state); return _multiEpisodeFactory.Create(jsScriptPath, mediaItems, state, cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -828,7 +862,8 @@ public class PlayoutBuilder : IPlayoutBuilder
case PlaybackOrder.Shuffle: case PlaybackOrder.Shuffle:
return new ShuffledMediaCollectionEnumerator( return new ShuffledMediaCollectionEnumerator(
await GetGroupedMediaItemsForShuffle(activeSchedule, mediaItems, collectionKey), await GetGroupedMediaItemsForShuffle(activeSchedule, mediaItems, collectionKey),
state); state,
cancellationToken);
default: default:
// TODO: handle this error case differently? // TODO: handle this error case differently?
return new RandomizedMediaCollectionEnumerator(mediaItems, state); return new RandomizedMediaCollectionEnumerator(mediaItems, state);

60
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -18,13 +18,13 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
T scheduleItem, T scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop); DateTimeOffset hardStop,
CancellationToken cancellationToken);
public static DateTimeOffset GetFillerStartTimeAfter( public static DateTimeOffset GetFillerStartTimeAfter(
PlayoutBuilderState state, PlayoutBuilderState state,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
DateTimeOffset hardStop DateTimeOffset hardStop)
)
{ {
DateTimeOffset startTime = GetStartTimeAfter(state, scheduleItem); DateTimeOffset startTime = GetStartTimeAfter(state, scheduleItem);
@ -74,7 +74,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
List<PlayoutItem> playoutItems, List<PlayoutItem> playoutItems,
DateTimeOffset nextItemStart) DateTimeOffset nextItemStart,
CancellationToken cancellationToken)
{ {
var newItems = new List<PlayoutItem>(playoutItems); var newItems = new List<PlayoutItem>(playoutItems);
PlayoutBuilderState nextState = playoutBuilderState; PlayoutBuilderState nextState = playoutBuilderState;
@ -131,7 +132,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
List<PlayoutItem> playoutItems, List<PlayoutItem> playoutItems,
DateTimeOffset nextItemStart) DateTimeOffset nextItemStart,
CancellationToken cancellationToken)
{ {
var newItems = new List<PlayoutItem>(playoutItems); var newItems = new List<PlayoutItem>(playoutItems);
PlayoutBuilderState nextState = playoutBuilderState; PlayoutBuilderState nextState = playoutBuilderState;
@ -328,7 +330,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
PlayoutItem playoutItem, PlayoutItem playoutItem,
List<MediaChapter> chapters) List<MediaChapter> chapters,
CancellationToken cancellationToken)
{ {
var result = new List<PlayoutItem>(); var result = new List<PlayoutItem>();
@ -363,7 +366,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
e1, e1,
filler.Duration.Value, filler.Duration.Value,
FillerKind.PreRoll, FillerKind.PreRoll,
filler.AllowWatermarks)); filler.AllowWatermarks,
cancellationToken));
break; break;
case FillerMode.Count when filler.Count.HasValue: case FillerMode.Count when filler.Count.HasValue:
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)]; IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)];
@ -373,7 +377,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
e2, e2,
filler.Count.Value, filler.Count.Value,
FillerKind.PreRoll, FillerKind.PreRoll,
filler.AllowWatermarks)); filler.AllowWatermarks,
cancellationToken));
break; break;
} }
} }
@ -402,7 +407,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
e1, e1,
filler.Duration.Value, filler.Duration.Value,
FillerKind.MidRoll, FillerKind.MidRoll,
filler.AllowWatermarks)); filler.AllowWatermarks,
cancellationToken));
} }
} }
@ -420,7 +426,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
e2, e2,
filler.Count.Value, filler.Count.Value,
FillerKind.MidRoll, FillerKind.MidRoll,
filler.AllowWatermarks)); filler.AllowWatermarks,
cancellationToken));
} }
} }
@ -442,7 +449,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
e1, e1,
filler.Duration.Value, filler.Duration.Value,
FillerKind.PostRoll, FillerKind.PostRoll,
filler.AllowWatermarks)); filler.AllowWatermarks,
cancellationToken));
break; break;
case FillerMode.Count when filler.Count.HasValue: case FillerMode.Count when filler.Count.HasValue:
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)]; IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)];
@ -452,7 +460,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
e2, e2,
filler.Count.Value, filler.Count.Value,
FillerKind.PostRoll, FillerKind.PostRoll,
filler.AllowWatermarks)); filler.AllowWatermarks,
cancellationToken));
break; break;
} }
} }
@ -508,7 +517,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
pre1, pre1,
remainingToFill, remainingToFill,
FillerKind.PreRoll, FillerKind.PreRoll,
padFiller.AllowWatermarks)); padFiller.AllowWatermarks,
cancellationToken));
totalDuration = totalDuration =
TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds)); TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds));
remainingToFill = targetTime - totalDuration - playoutItem.StartOffset; remainingToFill = targetTime - totalDuration - playoutItem.StartOffset;
@ -520,7 +530,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState, playoutBuilderState,
enumerators, enumerators,
scheduleItem, scheduleItem,
remainingToFill)); remainingToFill,
cancellationToken));
} }
break; break;
@ -532,7 +543,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
mid1, mid1,
remainingToFill, remainingToFill,
FillerKind.MidRoll, FillerKind.MidRoll,
padFiller.AllowWatermarks)); padFiller.AllowWatermarks,
cancellationToken));
TimeSpan average = effectiveChapters.Count <= 1 TimeSpan average = effectiveChapters.Count <= 1
? remainingToFill ? remainingToFill
: remainingToFill / (effectiveChapters.Count - 1); : remainingToFill / (effectiveChapters.Count - 1);
@ -569,7 +581,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState, playoutBuilderState,
enumerators, enumerators,
scheduleItem, scheduleItem,
i < effectiveChapters.Count - 1 ? maxThisBreak : leftOverall); i < effectiveChapters.Count - 1 ? maxThisBreak : leftOverall,
cancellationToken);
foreach (PlayoutItem fallback in maybeFallback) foreach (PlayoutItem fallback in maybeFallback)
{ {
@ -593,7 +606,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
post1, post1,
remainingToFill, remainingToFill,
FillerKind.PostRoll, FillerKind.PostRoll,
padFiller.AllowWatermarks)); padFiller.AllowWatermarks,
cancellationToken));
totalDuration = totalDuration =
TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds)); TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds));
remainingToFill = targetTime - totalDuration - playoutItem.StartOffset; remainingToFill = targetTime - totalDuration - playoutItem.StartOffset;
@ -604,7 +618,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
playoutBuilderState, playoutBuilderState,
enumerators, enumerators,
scheduleItem, scheduleItem,
remainingToFill)); remainingToFill,
cancellationToken));
} }
break; break;
@ -630,7 +645,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
IMediaCollectionEnumerator enumerator, IMediaCollectionEnumerator enumerator,
int count, int count,
FillerKind fillerKind, FillerKind fillerKind,
bool allowWatermarks) bool allowWatermarks,
CancellationToken cancellationToken)
{ {
var result = new List<PlayoutItem>(); var result = new List<PlayoutItem>();
@ -665,7 +681,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
IMediaCollectionEnumerator enumerator, IMediaCollectionEnumerator enumerator,
TimeSpan duration, TimeSpan duration,
FillerKind fillerKind, FillerKind fillerKind,
bool allowWatermarks) bool allowWatermarks,
CancellationToken cancellationToken)
{ {
var result = new List<PlayoutItem>(); var result = new List<PlayoutItem>();
@ -736,7 +753,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
PlayoutBuilderState playoutBuilderState, PlayoutBuilderState playoutBuilderState,
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators,
ProgramScheduleItem scheduleItem, ProgramScheduleItem scheduleItem,
TimeSpan duration) TimeSpan duration,
CancellationToken cancellationToken)
{ {
if (scheduleItem.FallbackFiller != null) if (scheduleItem.FallbackFiller != null)
{ {

20
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -17,7 +17,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItemDuration scheduleItem, ProgramScheduleItemDuration scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop) DateTimeOffset hardStop,
CancellationToken cancellationToken)
{ {
var playoutItems = new List<PlayoutItem>(); var playoutItems = new List<PlayoutItem>();
@ -101,7 +102,13 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
{ {
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime); // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
playoutItems.AddRange( playoutItems.AddRange(
AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters)); AddFiller(
nextState,
collectionEnumerators,
scheduleItem,
playoutItem,
itemChapters,
cancellationToken));
nextState = nextState with nextState = nextState with
{ {
@ -163,7 +170,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
if (scheduleItem.FallbackFiller != null) if (scheduleItem.FallbackFiller != null)
@ -173,7 +181,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
nextState = nextState with { CurrentTime = nextItemStart }; nextState = nextState with { CurrentTime = nextItemStart };
@ -186,7 +195,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
nextState = nextState with { CurrentTime = nextItemStart }; nextState = nextState with { CurrentTime = nextItemStart };

17
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -18,7 +18,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItemFlood scheduleItem, ProgramScheduleItemFlood scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop) DateTimeOffset hardStop,
CancellationToken cancellationToken)
{ {
var playoutItems = new List<PlayoutItem>(); var playoutItems = new List<PlayoutItem>();
@ -88,7 +89,13 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
if (willFinishInTime) if (willFinishInTime)
{ {
playoutItems.AddRange( playoutItems.AddRange(
AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters)); AddFiller(
nextState,
collectionEnumerators,
scheduleItem,
playoutItem,
itemChapters,
cancellationToken));
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime); // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
if (playoutItems.Count > 0) if (playoutItems.Count > 0)
@ -150,7 +157,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
peekItemStart); peekItemStart,
cancellationToken);
} }
if (scheduleItem.FallbackFiller != null) if (scheduleItem.FallbackFiller != null)
@ -160,7 +168,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
peekItemStart); peekItemStart,
cancellationToken);
} }
nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup }; nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup };

17
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -19,7 +19,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItemMultiple scheduleItem, ProgramScheduleItemMultiple scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop) DateTimeOffset hardStop,
CancellationToken cancellationToken)
{ {
var playoutItems = new List<PlayoutItem>(); var playoutItems = new List<PlayoutItem>();
@ -84,7 +85,13 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
itemChapters); itemChapters);
playoutItems.AddRange( playoutItems.AddRange(
AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters)); AddFiller(
nextState,
collectionEnumerators,
scheduleItem,
playoutItem,
itemChapters,
cancellationToken));
nextState = nextState with nextState = nextState with
{ {
@ -128,7 +135,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
if (scheduleItem.FallbackFiller != null) if (scheduleItem.FallbackFiller != null)
@ -138,7 +146,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup }; nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup };

12
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -16,7 +16,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
ProgramScheduleItemOne scheduleItem, ProgramScheduleItemOne scheduleItem,
ProgramScheduleItem nextScheduleItem, ProgramScheduleItem nextScheduleItem,
DateTimeOffset hardStop) DateTimeOffset hardStop,
CancellationToken cancellationToken)
{ {
IMediaCollectionEnumerator contentEnumerator = IMediaCollectionEnumerator contentEnumerator =
collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)]; collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)];
@ -64,7 +65,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItem, playoutItem,
itemChapters); itemChapters,
cancellationToken);
PlayoutBuilderState nextState = playoutBuilderState with PlayoutBuilderState nextState = playoutBuilderState with
{ {
@ -90,7 +92,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
if (scheduleItem.FallbackFiller != null) if (scheduleItem.FallbackFiller != null)
@ -100,7 +103,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
collectionEnumerators, collectionEnumerators,
scheduleItem, scheduleItem,
playoutItems, playoutItems,
nextItemStart); nextItemStart,
cancellationToken);
} }
nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup }; nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup };

8
ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs

@ -8,16 +8,19 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
private readonly IList<CollectionWithItems> _collections; private readonly IList<CollectionWithItems> _collections;
private readonly int _mediaItemCount; private readonly int _mediaItemCount;
private readonly bool _randomStartPoint; private readonly bool _randomStartPoint;
private readonly CancellationToken _cancellationToken;
private Random _random; private Random _random;
private IList<MediaItem> _shuffled; private IList<MediaItem> _shuffled;
public ShuffleInOrderCollectionEnumerator( public ShuffleInOrderCollectionEnumerator(
IList<CollectionWithItems> collections, IList<CollectionWithItems> collections,
CollectionEnumeratorState state, CollectionEnumeratorState state,
bool randomStartPoint) bool randomStartPoint,
CancellationToken cancellationToken)
{ {
_collections = collections; _collections = collections;
_randomStartPoint = randomStartPoint; _randomStartPoint = randomStartPoint;
_cancellationToken = cancellationToken;
_mediaItemCount = collections.Sum(c => c.MediaItems.Count); _mediaItemCount = collections.Sum(c => c.MediaItems.Count);
if (state.Index >= _mediaItemCount) if (state.Index >= _mediaItemCount)
@ -52,7 +55,8 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
State.Seed = _random.Next(); State.Seed = _random.Next();
_random = new Random(State.Seed); _random = new Random(State.Seed);
_shuffled = Shuffle(_collections, _random); _shuffled = Shuffle(_collections, _random);
} while (_collections.Count > 1 && Current.Map(x => x.Id) == tail.Map(x => x.Id)); } while (!_cancellationToken.IsCancellationRequested && _collections.Count > 1 &&
Current.Map(x => x.Id) == tail.Map(x => x.Id));
} }
else else
{ {

8
ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

@ -7,15 +7,18 @@ public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
{ {
private readonly int _mediaItemCount; private readonly int _mediaItemCount;
private readonly IList<GroupedMediaItem> _mediaItems; private readonly IList<GroupedMediaItem> _mediaItems;
private readonly CancellationToken _cancellationToken;
private CloneableRandom _random; private CloneableRandom _random;
private IList<MediaItem> _shuffled; private IList<MediaItem> _shuffled;
public ShuffledMediaCollectionEnumerator( public ShuffledMediaCollectionEnumerator(
IList<GroupedMediaItem> mediaItems, IList<GroupedMediaItem> mediaItems,
CollectionEnumeratorState state) CollectionEnumeratorState state,
CancellationToken cancellationToken)
{ {
_mediaItemCount = mediaItems.Sum(i => 1 + Optional(i.Additional).Flatten().Count()); _mediaItemCount = mediaItems.Sum(i => 1 + Optional(i.Additional).Flatten().Count());
_mediaItems = mediaItems; _mediaItems = mediaItems;
_cancellationToken = cancellationToken;
if (state.Index >= _mediaItems.Count) if (state.Index >= _mediaItems.Count)
{ {
@ -49,7 +52,8 @@ public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
State.Seed = _random.Next(); State.Seed = _random.Next();
_random = new CloneableRandom(State.Seed); _random = new CloneableRandom(State.Seed);
_shuffled = Shuffle(_mediaItems, _random); _shuffled = Shuffle(_mediaItems, _random);
} while (_mediaItems.Count > 1 && Current.Map(x => x.Id) == tail.Map(x => x.Id)); } while (!_cancellationToken.IsCancellationRequested && _mediaItems.Count > 1 &&
Current.Map(x => x.Id) == tail.Map(x => x.Id));
} }
else else
{ {

2
ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

@ -437,7 +437,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
PlaybackOrder.Chronological, PlaybackOrder.Chronological,
false)); false));
return result; return result.Filter(c => c.MediaItems.Any()).ToList();
} }
private async Task<List<Movie>> GetMovieItems(TvContext dbContext, int collectionId) private async Task<List<Movie>> GetMovieItems(TvContext dbContext, int collectionId)

8
ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs

@ -9,6 +9,7 @@ namespace ErsatzTV.Infrastructure.Scheduling;
public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerator public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerator
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
private readonly int _mediaItemCount; private readonly int _mediaItemCount;
private readonly Dictionary<int, List<MediaItem>> _mediaItemGroups; private readonly Dictionary<int, List<MediaItem>> _mediaItemGroups;
private readonly List<MediaItem> _ungrouped; private readonly List<MediaItem> _ungrouped;
@ -20,9 +21,11 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato
CollectionEnumeratorState state, CollectionEnumeratorState state,
IScriptEngine scriptEngine, IScriptEngine scriptEngine,
string scriptFile, string scriptFile,
ILogger logger) ILogger logger,
CancellationToken cancellationToken)
{ {
_logger = logger; _logger = logger;
_cancellationToken = cancellationToken;
scriptEngine.Load(scriptFile); scriptEngine.Load(scriptFile);
@ -93,7 +96,8 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato
State.Seed = _random.Next(); State.Seed = _random.Next();
_random = new CloneableRandom(State.Seed); _random = new CloneableRandom(State.Seed);
_shuffled = Shuffle(_random); _shuffled = Shuffle(_random);
} while (_mediaItemCount > 1 && Current.Map(x => x.Id) == tail.Map(x => x.Id)); } while (!_cancellationToken.IsCancellationRequested && _mediaItemCount > 1 &&
Current.Map(x => x.Id) == tail.Map(x => x.Id));
} }
else else
{ {

11
ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumeratorFactory.cs

@ -22,6 +22,13 @@ public class MultiEpisodeShuffleCollectionEnumeratorFactory
public IMediaCollectionEnumerator Create( public IMediaCollectionEnumerator Create(
string jsScriptPath, string jsScriptPath,
IList<MediaItem> mediaItems, IList<MediaItem> mediaItems,
CollectionEnumeratorState state) => CollectionEnumeratorState state,
new MultiEpisodeShuffleCollectionEnumerator(mediaItems, state, _scriptEngine, jsScriptPath, _logger); CancellationToken cancellationToken) =>
new MultiEpisodeShuffleCollectionEnumerator(
mediaItems,
state,
_scriptEngine,
jsScriptPath,
_logger,
cancellationToken);
} }

8
ErsatzTV/Pages/MultiCollectionEditor.razor

@ -218,7 +218,7 @@
private void RemoveCollection(MultiCollectionItemEditViewModel item) => _model.Items.Remove(item); private void RemoveCollection(MultiCollectionItemEditViewModel item) => _model.Items.Remove(item);
private void AddCollection() private async Task AddCollection()
{ {
if (_selectedCollection != null && _model.Items.All(i => i.Collection != _selectedCollection)) if (_selectedCollection != null && _model.Items.All(i => i.Collection != _selectedCollection))
{ {
@ -229,11 +229,11 @@
}); });
_selectedCollection = null; _selectedCollection = null;
_collectionSelect.Reset(); await _collectionSelect.ResetAsync();
} }
} }
private void AddSmartCollection() private async Task AddSmartCollection()
{ {
if (_selectedSmartCollection != null && _model.Items.OfType<MultiCollectionSmartItemEditViewModel>().All(i => i.SmartCollection != _selectedSmartCollection)) if (_selectedSmartCollection != null && _model.Items.OfType<MultiCollectionSmartItemEditViewModel>().All(i => i.SmartCollection != _selectedSmartCollection))
{ {
@ -244,7 +244,7 @@
}); });
_selectedSmartCollection = null; _selectedSmartCollection = null;
_smartCollectionSelect.Reset(); await _smartCollectionSelect.ResetAsync();
} }
} }

26
ErsatzTV/Services/WorkerService.cs

@ -49,62 +49,46 @@ public class WorkerService : BackgroundService
switch (request) switch (request)
{ {
case RefreshChannelList refreshChannelList: case RefreshChannelList refreshChannelList:
_logger.LogDebug("WorkerService - RefreshChannelList START");
await mediator.Send(refreshChannelList, cancellationToken); await mediator.Send(refreshChannelList, cancellationToken);
_logger.LogDebug("WorkerService - RefreshChannelList FINISH");
break; break;
case RefreshChannelData refreshChannelData: case RefreshChannelData refreshChannelData:
_logger.LogDebug("WorkerService - RefreshChannelData START");
await mediator.Send(refreshChannelData, cancellationToken); await mediator.Send(refreshChannelData, cancellationToken);
_logger.LogDebug("WorkerService - RefreshChannelData FINISH");
break; break;
case BuildPlayout buildPlayout: case BuildPlayout buildPlayout:
_logger.LogDebug("WorkerService - BuildPlayout START"); var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var linkedTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
Either<BaseError, Unit> buildPlayoutResult = await mediator.Send( Either<BaseError, Unit> buildPlayoutResult = await mediator.Send(
buildPlayout, buildPlayout,
cancellationToken); linkedTokenSource.Token);
buildPlayoutResult.BiIter( buildPlayoutResult.BiIter(
_ => _logger.LogDebug("Built playout {PlayoutId}", buildPlayout.PlayoutId), _ => _logger.LogDebug("Built playout {PlayoutId}", buildPlayout.PlayoutId),
error => _logger.LogWarning( error => _logger.LogWarning(
"Unable to build playout {PlayoutId}: {Error}", "Unable to build playout {PlayoutId}: {Error}",
buildPlayout.PlayoutId, buildPlayout.PlayoutId,
error.Value)); error.Value));
_logger.LogDebug("WorkerService - BuildPlayout FINISH");
break; break;
case DeleteOrphanedArtwork deleteOrphanedArtwork: case DeleteOrphanedArtwork deleteOrphanedArtwork:
_logger.LogDebug("WorkerService - DeleteOrphanedArtwork START");
await mediator.Send(deleteOrphanedArtwork, cancellationToken); await mediator.Send(deleteOrphanedArtwork, cancellationToken);
_logger.LogDebug("WorkerService - DeleteOrphanedArtwork FINISH");
break; break;
case DeleteOrphanedSubtitles deleteOrphanedSubtitles: case DeleteOrphanedSubtitles deleteOrphanedSubtitles:
_logger.LogDebug("WorkerService - DeleteOrphanedSubtitles START");
await mediator.Send(deleteOrphanedSubtitles, cancellationToken); await mediator.Send(deleteOrphanedSubtitles, cancellationToken);
_logger.LogDebug("WorkerService - DeleteOrphanedSubtitles FINISH");
break; break;
case AddTraktList addTraktList: case AddTraktList addTraktList:
_logger.LogDebug("WorkerService - AddTraktList START");
await mediator.Send(addTraktList, cancellationToken); await mediator.Send(addTraktList, cancellationToken);
_logger.LogDebug("WorkerService - AddTraktList FINISH");
break; break;
case DeleteTraktList deleteTraktList: case DeleteTraktList deleteTraktList:
_logger.LogDebug("WorkerService - DeleteTraktList START");
await mediator.Send(deleteTraktList, cancellationToken); await mediator.Send(deleteTraktList, cancellationToken);
_logger.LogDebug("WorkerService - DeleteTraktList FINISH");
break; break;
case MatchTraktListItems matchTraktListItems: case MatchTraktListItems matchTraktListItems:
_logger.LogDebug("WorkerService - MatchTraktListItems START");
await mediator.Send(matchTraktListItems, cancellationToken); await mediator.Send(matchTraktListItems, cancellationToken);
_logger.LogDebug("WorkerService - MatchTraktListItems FINISH");
break; break;
case ExtractEmbeddedSubtitles extractEmbeddedSubtitles: case ExtractEmbeddedSubtitles extractEmbeddedSubtitles:
_logger.LogDebug("WorkerService - ExtractEmbeddedSubtitles START");
await mediator.Send(extractEmbeddedSubtitles, cancellationToken); await mediator.Send(extractEmbeddedSubtitles, cancellationToken);
_logger.LogDebug("WorkerService - ExtractEmbeddedSubtitles FINISH");
break; break;
case ReleaseMemory aggressivelyReleaseMemory: case ReleaseMemory aggressivelyReleaseMemory:
_logger.LogDebug("WorkerService - ReleaseMemory START");
await mediator.Send(aggressivelyReleaseMemory, cancellationToken); await mediator.Send(aggressivelyReleaseMemory, cancellationToken);
_logger.LogDebug("WorkerService - ReleaseMemory FINISH");
break; break;
} }
} }

Loading…
Cancel
Save