From d9457c01e5ccb4da2ec49ab3aae29b99fd751740 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sun, 11 Jul 2021 14:25:42 -0500 Subject: [PATCH] add special case to multiple playout mode (#298) --- CHANGELOG.md | 3 + .../ProgramScheduleItemCommandBase.cs | 4 +- .../Scheduling/PlayoutBuilderTests.cs | 90 +++++++++++++++++++ ErsatzTV.Core/Scheduling/PlayoutBuilder.cs | 11 ++- ErsatzTV/Pages/ScheduleItemsEditor.razor | 2 +- ...ogramScheduleItemEditViewModelValidator.cs | 2 +- 6 files changed, 107 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e2b33f7..026039640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - Include audio language metadata in all streaming modes +- Add special zero-count case to `Multiple` playout mode + - This configuration will automatically maintain the multiple count so that it is equal to the number of items in the collection + - This configuration should be used if you want to play every item in a collection exactly once before advancing ### Changed - Use case-insensitive sorting for collections page and `Add to Collection` dialog diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs b/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs index d22a0b37d..dc0ed409e 100644 --- a/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs +++ b/ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs @@ -30,9 +30,9 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands case PlayoutMode.One: break; case PlayoutMode.Multiple: - if (item.MultipleCount.GetValueOrDefault() <= 0) + if (item.MultipleCount.GetValueOrDefault() < 0) { - return BaseError.New("[MultipleCount] must be greater than 0 for playout mode 'multiple'"); + return BaseError.New("[MultipleCount] must be greater than or equal to 0 for playout mode 'multiple'"); } break; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index b0af9f0d0..8299333f5 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -905,6 +905,96 @@ namespace ErsatzTV.Core.Tests.Scheduling result.Anchor.MultipleRemaining.Should().Be(1); } + [Test] + public async Task Auto_Zero_MultipleCount() + { + var collectionOne = new Collection + { + Id = 1, + Name = "Multiple Items 1", + MediaItems = new List + { + TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), + TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), + TestMovie(3, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) + } + }; + + var collectionTwo = new Collection + { + Id = 2, + Name = "Multiple Items 2", + MediaItems = new List + { + TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), + TestMovie(5, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), + } + }; + + var fakeRepository = new FakeMediaCollectionRepository( + Map( + (collectionOne.Id, collectionOne.MediaItems.ToList()), + (collectionTwo.Id, collectionTwo.MediaItems.ToList()))); + + var items = new List + { + new ProgramScheduleItemMultiple + { + Id = 1, + Index = 1, + Collection = collectionOne, + CollectionId = collectionOne.Id, + StartTime = null, + Count = 0 + }, + new ProgramScheduleItemMultiple + { + Id = 2, + Index = 2, + Collection = collectionTwo, + CollectionId = collectionTwo.Id, + StartTime = null, + Count = 0 + } + }; + + var playout = new Playout + { + ProgramSchedule = new ProgramSchedule + { + Items = items, + MediaCollectionPlaybackOrder = PlaybackOrder.Chronological + }, + Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, + }; + + var televisionRepo = new FakeTelevisionRepository(); + var artistRepo = new Mock(); + var builder = new PlayoutBuilder(fakeRepository, televisionRepo, artistRepo.Object, _logger); + + DateTimeOffset start = HoursAfterMidnight(0); + DateTimeOffset finish = start + TimeSpan.FromHours(5); + + Playout result = await builder.BuildPlayoutItems(playout, start, finish); + + result.Items.Count.Should().Be(5); + + result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); + result.Items[0].MediaItemId.Should().Be(1); + result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); + result.Items[1].MediaItemId.Should().Be(2); + result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); + result.Items[2].MediaItemId.Should().Be(3); + + result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); + result.Items[3].MediaItemId.Should().Be(4); + result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); + result.Items[4].MediaItemId.Should().Be(5); + + result.Anchor.NextScheduleItem.Should().Be(items[0]); + result.Anchor.MultipleRemaining.Should().BeNull(); + } + [Test] public async Task Alternating_Duration_Should_Maintain_Duration() { diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index bc85279f7..0eaa76715 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -270,7 +270,16 @@ namespace ErsatzTV.Core.Scheduling case ProgramScheduleItemMultiple multiple: if (multipleRemaining.IsNone) { - multipleRemaining = multiple.Count; + if (multiple.Count == 0) + { + multipleRemaining = collectionMediaItems[CollectionKeyForItem(scheduleItem)] + .Count; + } + else + { + multipleRemaining = multiple.Count; + } + customGroup = true; } diff --git a/ErsatzTV/Pages/ScheduleItemsEditor.razor b/ErsatzTV/Pages/ScheduleItemsEditor.razor index 7f2ee67a1..3d44cc5f5 100644 --- a/ErsatzTV/Pages/ScheduleItemsEditor.razor +++ b/ErsatzTV/Pages/ScheduleItemsEditor.razor @@ -164,7 +164,7 @@ Playout Mode
  • One - to play one media item from the collection before advancing to the next schedule item.
  • -
  • Multiple - to play a specified count of media items from the collection before advancing to the next schedule item.
  • +
  • Multiple - to play a specified count of media items from the collection before advancing to the next schedule item. A count of 0 is a special case that will automatically and always equal the number of items in the collection, so each item from the collection will be played once before advancing.
  • Duration - to play the maximum number of complete media items that will fit in the specified playout duration, before either going offline for the remainder of the playout duration (an offline tail), or immediately advancing to the next schedule item.
  • Flood - to play media items from the collection forever, or until the next schedule item's start time if one exists.
diff --git a/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs b/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs index 19ccd4834..53d504263 100644 --- a/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs +++ b/ErsatzTV/Validators/ProgramScheduleItemEditViewModelValidator.cs @@ -13,7 +13,7 @@ namespace ErsatzTV.Validators () => RuleFor(i => i.StartTime).NotNull()); When( i => i.PlayoutMode == PlayoutMode.Multiple, - () => RuleFor(i => i.MultipleCount).NotNull().GreaterThan(0)); + () => RuleFor(i => i.MultipleCount).NotNull().GreaterThanOrEqualTo(0)); When( i => i.PlayoutMode == PlayoutMode.Duration, () => RuleFor(i => i.PlayoutDuration).NotNull());