Browse Source

playout builder bugfixes

pull/1/head
Jason Dove 5 years ago
parent
commit
0e262f227d
  1. 20
      ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs
  2. 106
      ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
  3. 18
      ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs
  4. 25
      ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs
  5. 1
      ErsatzTV.Core/Interfaces/Scheduling/IMediaCollectionEnumerator.cs
  6. 4
      ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs
  7. 4
      ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs
  8. 61
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  9. 37
      ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs
  10. 37
      ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs
  11. 4
      ErsatzTV/Pages/PlayoutEditor.razor
  12. 2
      ErsatzTV/ViewModels/PlayoutEditViewModel.cs

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

@ -59,26 +59,6 @@ namespace ErsatzTV.Core.Tests.Scheduling
} }
} }
[Test]
public void Peek_Should_Not_Impact_Current_Or_Wrapping()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var chronologicalContent = new ChronologicalMediaCollectionEnumerator(contents, state);
for (var i = 0; i < 100; i++)
{
chronologicalContent.Current.IsSome.Should().BeTrue();
chronologicalContent.Current.Map(x => x.Id).IfNone(-1).Should().Be(i % 10 + 1);
chronologicalContent.Peek.Map(x => x.Id).IfNone(-1).Should().Be((i + 1) % 10 + 1);
chronologicalContent.Peek.Map(x => x.Id).IfNone(-1).Should().Be((i + 1) % 10 + 1);
chronologicalContent.MoveNext();
}
}
private static List<MediaItem> Episodes(int count) => private static List<MediaItem> Episodes(int count) =>
Range(1, count).Map( Range(1, count).Map(
i => new MediaItem i => new MediaItem

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

@ -413,7 +413,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
} }
[Test] [Test]
public async Task FloodContent_Should_FloodAroundFixedContent_DurationWithOfflineTail() public async Task FloodContent_Should_FloodAroundFixedContent_DurationWithoutOfflineTail()
{ {
var floodCollection = new SimpleMediaCollection var floodCollection = new SimpleMediaCollection
{ {
@ -433,7 +433,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
Items = new List<MediaItem> Items = new List<MediaItem>
{ {
TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 2)) TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2))
} }
}; };
@ -458,7 +458,96 @@ namespace ErsatzTV.Core.Tests.Scheduling
MediaCollectionId = fixedCollection.Id, MediaCollectionId = fixedCollection.Id,
StartTime = TimeSpan.FromHours(2), StartTime = TimeSpan.FromHours(2),
PlayoutDuration = TimeSpan.FromHours(2), PlayoutDuration = TimeSpan.FromHours(2),
OfflineTail = true // last 30 minutes will be offline OfflineTail = false // immediately continue
}
};
var playout = new Playout
{
ProgramSchedule = new ProgramSchedule
{
Items = items,
MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
},
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }
};
var builder = new PlayoutBuilder(fakeRepository, _logger);
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
Playout result = await builder.BuildPlayoutItems(playout, start, finish);
result.Items.Count.Should().Be(7);
result.Items[0].Start.TimeOfDay.Should().Be(TimeSpan.Zero);
result.Items[0].MediaItemId.Should().Be(1);
result.Items[1].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(1));
result.Items[1].MediaItemId.Should().Be(2);
result.Items[2].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
result.Items[2].MediaItemId.Should().Be(3);
result.Items[3].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(2.75));
result.Items[3].MediaItemId.Should().Be(1);
result.Items[4].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(3.75));
result.Items[4].MediaItemId.Should().Be(2);
result.Items[5].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(4.75));
result.Items[5].MediaItemId.Should().Be(1);
result.Items[6].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(5.75));
result.Items[6].MediaItemId.Should().Be(2);
}
[Test]
public async Task MultipleContent_Should_WrapAroundDynamicContent_DurationWithoutOfflineTail()
{
var multipleCollection = new SimpleMediaCollection
{
Id = 1,
Name = "Multiple Items",
Items = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var dynamicCollection = new SimpleMediaCollection
{
Id = 2,
Name = "Dynamic Items",
Items = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2))
}
};
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(multipleCollection.Id, multipleCollection.Items.ToList()),
(dynamicCollection.Id, dynamicCollection.Items.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemMultiple
{
Index = 1,
MediaCollection = multipleCollection,
MediaCollectionId = multipleCollection.Id,
StartTime = null,
Count = 2
},
new ProgramScheduleItemDuration
{
Index = 2,
MediaCollection = dynamicCollection,
MediaCollectionId = dynamicCollection.Id,
StartTime = null,
PlayoutDuration = TimeSpan.FromHours(2),
OfflineTail = false // immediately continue
} }
}; };
@ -488,13 +577,14 @@ namespace ErsatzTV.Core.Tests.Scheduling
result.Items[2].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); result.Items[2].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
result.Items[2].MediaItemId.Should().Be(3); result.Items[2].MediaItemId.Should().Be(3);
result.Items[3].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(2.75)); result.Items[3].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(2.75));
result.Items[3].MediaItemId.Should().Be(4); result.Items[3].MediaItemId.Should().Be(1);
result.Items[4].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(3.75));
result.Items[4].MediaItemId.Should().Be(2);
result.Items[4].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); result.Items[5].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(4.75));
result.Items[4].MediaItemId.Should().Be(1); result.Items[5].MediaItemId.Should().Be(4);
result.Items[5].Start.TimeOfDay.Should().Be(TimeSpan.FromHours(5));
result.Items[5].MediaItemId.Should().Be(2);
} }
private static DateTimeOffset HoursAfterMidnight(int hours) private static DateTimeOffset HoursAfterMidnight(int hours)

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

@ -77,24 +77,6 @@ namespace ErsatzTV.Core.Tests.Scheduling
} }
} }
[Test]
public void Peek_Should_Not_Impact_Current_Or_Wrapping()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState { Seed = KnownSeed };
var randomizedContent = new RandomizedMediaCollectionEnumerator(contents, state);
for (var i = 0; i < 97; i++)
{
randomizedContent.MoveNext();
randomizedContent.Current.IsSome.Should().BeTrue();
randomizedContent.Current.Map(x => x.Id).IfNone(-1).Should().Be(_expected[i]);
randomizedContent.Peek.Map(x => x.Id).IfNone(-1).Should().Be(_expected[i + 1]);
randomizedContent.Peek.Map(x => x.Id).IfNone(-1).Should().Be(_expected[i + 1]);
}
}
private static List<MediaItem> Episodes(int count) => private static List<MediaItem> Episodes(int count) =>
Range(1, count).Map( Range(1, count).Map(
i => new MediaItem i => new MediaItem

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

@ -67,31 +67,6 @@ namespace ErsatzTV.Core.Tests.Scheduling
} }
} }
[Test]
public void Peek_Should_Not_Impact_Current_Or_Wrapping()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState { Seed = MagicSeed };
var expected = new List<int>
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 6, 1, 4, 2, 10, 7, 3, 5, 8, 9, 10, 9, 4, 5, 1, 7, 3, 2, 8, 6, 3, 5, 4, 2,
10, 8, 7, 1, 6, 9, 3, 4, 7, 10, 6, 9, 1, 2, 8, 5, 8, 1, 3, 6, 5, 7, 9, 4, 2, 10, 6, 1, 4, 3, 5, 10, 2,
7, 8, 9, 6, 10, 4, 3, 8, 1, 5, 9, 2, 7, 8, 6, 4, 1, 9, 7, 3, 10, 5, 2, 5, 9, 2, 6, 7, 10, 3, 4, 1, 8
};
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
for (var i = 0; i < 99; i++)
{
shuffledContent.Current.IsSome.Should().BeTrue();
shuffledContent.Current.Map(x => x.Id).IfNone(-1).Should().Be(expected[i]);
shuffledContent.Peek.Map(x => x.Id).IfNone(-1).Should().Be(expected[i + 1]);
shuffledContent.Peek.Map(x => x.Id).IfNone(-1).Should().Be(expected[i + 1]);
shuffledContent.MoveNext();
}
}
private static List<MediaItem> Episodes(int count) => private static List<MediaItem> Episodes(int count) =>
Range(1, count).Map( Range(1, count).Map(
i => new MediaItem i => new MediaItem

1
ErsatzTV.Core/Interfaces/Scheduling/IMediaCollectionEnumerator.cs

@ -7,7 +7,6 @@ namespace ErsatzTV.Core.Interfaces.Scheduling
{ {
MediaCollectionEnumeratorState State { get; } MediaCollectionEnumeratorState State { get; }
public Option<MediaItem> Current { get; } public Option<MediaItem> Current { get; }
public Option<MediaItem> Peek { get; }
public void MoveNext(); public void MoveNext();
} }
} }

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

@ -10,8 +10,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling
public Task<Playout> BuildPlayoutItems( public Task<Playout> BuildPlayoutItems(
Playout playout, Playout playout,
DateTimeOffset start, DateTimeOffset playoutStart,
DateTimeOffset finish, DateTimeOffset playoutFinish,
bool rebuild = false); bool rebuild = false);
} }
} }

4
ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs

@ -32,10 +32,6 @@ namespace ErsatzTV.Core.Scheduling
public Option<MediaItem> Current => _sortedMediaItems.Any() ? _sortedMediaItems[State.Index] : None; public Option<MediaItem> Current => _sortedMediaItems.Any() ? _sortedMediaItems[State.Index] : None;
public Option<MediaItem> Peek => _sortedMediaItems.Any()
? _sortedMediaItems[(State.Index + 1) % _sortedMediaItems.Count]
: None;
public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count; public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count;
} }
} }

61
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -34,8 +34,8 @@ namespace ErsatzTV.Core.Scheduling
public async Task<Playout> BuildPlayoutItems( public async Task<Playout> BuildPlayoutItems(
Playout playout, Playout playout,
DateTimeOffset start, DateTimeOffset playoutStart,
DateTimeOffset finish, DateTimeOffset playoutFinish,
bool rebuild = false) bool rebuild = false)
{ {
var collections = playout.ProgramSchedule.Items.Map(i => i.MediaCollection).Distinct().ToList(); var collections = playout.ProgramSchedule.Items.Map(i => i.MediaCollection).Distinct().ToList();
@ -71,7 +71,7 @@ namespace ErsatzTV.Core.Scheduling
MapExtensions.Map(collectionMediaItems, (c, i) => GetMediaCollectionEnumerator(playout, c, i)); MapExtensions.Map(collectionMediaItems, (c, i) => GetMediaCollectionEnumerator(playout, c, i));
// find start anchor // find start anchor
PlayoutAnchor startAnchor = FindStartAnchor(playout, start, sortedScheduleItems); PlayoutAnchor startAnchor = FindStartAnchor(playout, playoutStart, sortedScheduleItems);
// start at the previously-decided time // start at the previously-decided time
DateTimeOffset currentTime = startAnchor.NextStart; DateTimeOffset currentTime = startAnchor.NextStart;
@ -88,35 +88,39 @@ namespace ErsatzTV.Core.Scheduling
Option<int> multipleRemaining = None; Option<int> multipleRemaining = None;
Option<DateTimeOffset> durationFinish = None; Option<DateTimeOffset> durationFinish = None;
// loop until we're done filling the desired amount of time // loop until we're done filling the desired amount of time
while (currentTime < finish) while (currentTime < playoutFinish)
{ {
// get the schedule item out of the sorted list // get the schedule item out of the sorted list
ProgramScheduleItem scheduleItem = sortedScheduleItems[index % sortedScheduleItems.Count]; ProgramScheduleItem scheduleItem = sortedScheduleItems[index % sortedScheduleItems.Count];
// find when we should start this item, based on the current time // find when we should start this item, based on the current time
DateTimeOffset startTime = GetStartTimeAfter( DateTimeOffset itemStartTime = GetStartTimeAfter(
scheduleItem, scheduleItem,
currentTime, currentTime,
multipleRemaining.IsSome, multipleRemaining.IsSome,
durationFinish.IsSome); durationFinish.IsSome);
_logger.LogDebug(
"Schedule item: {ScheduleItemNumber} / {MediaCollectionName} / {StartTime}",
scheduleItem.Index,
scheduleItem.MediaCollection.Name,
startTime);
IMediaCollectionEnumerator enumerator = collectionEnumerators[scheduleItem.MediaCollection]; IMediaCollectionEnumerator enumerator = collectionEnumerators[scheduleItem.MediaCollection];
enumerator.Current.IfSome( enumerator.Current.IfSome(
mediaItem => mediaItem =>
{ {
_logger.LogDebug(
"Scheduling media item: {ScheduleItemNumber} / {MediaCollectionId} - {MediaCollectionName} / {MediaItemId} - {MediaItemTitle} / {StartTime}",
scheduleItem.Index,
scheduleItem.MediaCollection.Id,
scheduleItem.MediaCollection.Name,
mediaItem.Id,
DisplayTitle(mediaItem),
itemStartTime);
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem
{ {
MediaItemId = mediaItem.Id, MediaItemId = mediaItem.Id,
Start = startTime, Start = itemStartTime,
Finish = startTime + mediaItem.Metadata.Duration Finish = itemStartTime + mediaItem.Metadata.Duration
}; };
currentTime = startTime + mediaItem.Metadata.Duration; currentTime = itemStartTime + mediaItem.Metadata.Duration;
enumerator.MoveNext(); enumerator.MoveNext();
playout.Items.Add(playoutItem); playout.Items.Add(playoutItem);
@ -126,7 +130,7 @@ namespace ErsatzTV.Core.Scheduling
case ProgramScheduleItemOne: case ProgramScheduleItemOne:
// only play one item from collection, so always advance to the next item // only play one item from collection, so always advance to the next item
_logger.LogDebug( _logger.LogDebug(
"Advancing to next playout item after playout mode {PlayoutMode}", "Advancing to next schedule item after playout mode {PlayoutMode}",
"One"); "One");
index++; index++;
break; break;
@ -139,13 +143,16 @@ namespace ErsatzTV.Core.Scheduling
multipleRemaining = multipleRemaining.Map(i => i - 1); multipleRemaining = multipleRemaining.Map(i => i - 1);
if (multipleRemaining.IfNone(-1) == 0) if (multipleRemaining.IfNone(-1) == 0)
{ {
_logger.LogDebug(
"Advancing to next schedule item after playout mode {PlayoutMode}",
"Multiple");
index++; index++;
multipleRemaining = None; multipleRemaining = None;
} }
break; break;
case ProgramScheduleItemFlood: case ProgramScheduleItemFlood:
enumerator.Peek.Do( enumerator.Current.Do(
peekMediaItem => peekMediaItem =>
{ {
ProgramScheduleItem peekScheduleItem = ProgramScheduleItem peekScheduleItem =
@ -163,25 +170,32 @@ namespace ErsatzTV.Core.Scheduling
peekScheduleItemStart; peekScheduleItemStart;
if (willNotFinishInTime) if (willNotFinishInTime)
{ {
_logger.LogDebug(
"Advancing to next schedule item after playout mode {PlayoutMode}",
"Flood");
index++; index++;
} }
}); });
break; break;
case ProgramScheduleItemDuration duration: case ProgramScheduleItemDuration duration:
enumerator.Peek.Do( enumerator.Current.Do(
peekMediaItem => peekMediaItem =>
{ {
// remember when we need to finish this duration item
if (durationFinish.IsNone) if (durationFinish.IsNone)
{ {
durationFinish = startTime + duration.PlayoutDuration; durationFinish = itemStartTime + duration.PlayoutDuration;
} }
DateTimeOffset finish = durationFinish.IfNone(DateTime.MinValue); bool willNotFinishInTime =
bool willNotFinishInTime = currentTime <= finish && currentTime <= durationFinish.IfNone(DateTime.MinValue) &&
currentTime + peekMediaItem.Metadata.Duration > currentTime + peekMediaItem.Metadata.Duration >
finish; durationFinish.IfNone(DateTime.MinValue);
if (willNotFinishInTime) if (willNotFinishInTime)
{ {
_logger.LogDebug(
"Advancing to next schedule item after playout mode {PlayoutMode}",
"Duration");
index++; index++;
if (duration.OfflineTail) if (duration.OfflineTail)
@ -211,7 +225,7 @@ namespace ErsatzTV.Core.Scheduling
playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(playout, collectionEnumerators); playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(playout, collectionEnumerators);
// remove any items outside the desired range // remove any items outside the desired range
playout.Items.RemoveAll(old => old.Finish < start || old.Start > finish); playout.Items.RemoveAll(old => old.Finish < playoutStart || old.Start > playoutFinish);
return playout; return playout;
} }
@ -332,5 +346,10 @@ namespace ErsatzTV.Core.Scheduling
return new RandomizedMediaCollectionEnumerator(mediaItems, state); return new RandomizedMediaCollectionEnumerator(mediaItems, state);
} }
} }
private static string DisplayTitle(MediaItem mediaItem) =>
mediaItem.Metadata.MediaType == MediaType.TvShow
? $"{mediaItem.Metadata.Title} - s{mediaItem.Metadata.SeasonNumber:00}e{mediaItem.Metadata.EpisodeNumber:00}"
: mediaItem.Metadata.Title;
} }
} }

37
ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
@ -14,13 +13,11 @@ namespace ErsatzTV.Core.Scheduling
private readonly IList<MediaItem> _mediaItems; private readonly IList<MediaItem> _mediaItems;
private readonly Random _random; private readonly Random _random;
private int _index; private int _index;
private Option<int> _peekNext;
public RandomizedMediaCollectionEnumerator(IList<MediaItem> mediaItems, MediaCollectionEnumeratorState state) public RandomizedMediaCollectionEnumerator(IList<MediaItem> mediaItems, MediaCollectionEnumeratorState state)
{ {
_mediaItems = mediaItems; _mediaItems = mediaItems;
_random = new Random(state.Seed); _random = new Random(state.Seed);
_peekNext = None;
State = new MediaCollectionEnumeratorState { Seed = state.Seed }; State = new MediaCollectionEnumeratorState { Seed = state.Seed };
// we want to move at least once so we start with a random item and not the first // we want to move at least once so we start with a random item and not the first
@ -35,41 +32,9 @@ namespace ErsatzTV.Core.Scheduling
public Option<MediaItem> Current => _mediaItems.Any() ? _mediaItems[_index] : None; public Option<MediaItem> Current => _mediaItems.Any() ? _mediaItems[_index] : None;
public Option<MediaItem> Peek
{
get
{
if (_mediaItems.Any())
{
return _peekNext.Match(
peek =>
{
Debug.WriteLine("returning existing peek");
return _mediaItems[peek];
},
() =>
{
Debug.WriteLine("setting peek");
// gen a random index but save it so we can use it again when
// we actually move next
int index = _random.Next() % _mediaItems.Count;
_peekNext = index;
return _mediaItems[index];
});
}
return None;
}
}
public void MoveNext() public void MoveNext()
{ {
// TODO: reset seed at some predictable point so we don't overflow the index _index = _random.Next() % _mediaItems.Count;
Debug.WriteLine("resetting peek");
_index = _peekNext.IfNone(() => _random.Next() % _mediaItems.Count);
_peekNext = None;
State.Index++; State.Index++;
} }
} }

37
ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

@ -4,7 +4,6 @@ using System.Linq;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using LanguageExt; using LanguageExt;
using LanguageExt.UnsafeValueAccess;
using static LanguageExt.Prelude; using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Scheduling namespace ErsatzTV.Core.Scheduling
@ -12,7 +11,6 @@ namespace ErsatzTV.Core.Scheduling
public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
{ {
private readonly IList<MediaItem> _mediaItems; private readonly IList<MediaItem> _mediaItems;
private Option<int> _peekNextSeed;
private Random _random; private Random _random;
private IList<MediaItem> _shuffled; private IList<MediaItem> _shuffled;
@ -34,51 +32,18 @@ namespace ErsatzTV.Core.Scheduling
public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItems.Count] : None; public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItems.Count] : None;
public Option<MediaItem> Peek
{
get
{
if (_shuffled.Any())
{
// if we aren't peeking past the end of the list, things are simple
if (State.Index + 1 < _shuffled.Count)
{
return _shuffled[State.Index + 1];
}
// if we are peeking past the end of the list...
// gen a random seed but save it so we can use it again when we actually move next
Random random;
if (_peekNextSeed.IsSome)
{
random = new Random(_peekNextSeed.Value());
}
else
{
_peekNextSeed = _random.Next();
random = new Random(_peekNextSeed.Value());
}
return Shuffle(_mediaItems, random).Head();
}
return None;
}
}
public void MoveNext() public void MoveNext()
{ {
State.Index++; State.Index++;
if (State.Index % _shuffled.Count == 0) if (State.Index % _shuffled.Count == 0)
{ {
State.Index = 0; State.Index = 0;
State.Seed = _peekNextSeed.IfNone(_random.Next()); State.Seed = _random.Next();
_random = new Random(State.Seed); _random = new Random(State.Seed);
_shuffled = Shuffle(_mediaItems, _random); _shuffled = Shuffle(_mediaItems, _random);
} }
State.Index %= _shuffled.Count; State.Index %= _shuffled.Count;
_peekNextSeed = None;
} }
private static IList<T> Shuffle<T>(IEnumerable<T> list, Random random) private static IList<T> Shuffle<T>(IEnumerable<T> list, Random random)

4
ErsatzTV/Pages/PlayoutEditor.razor

@ -1,8 +1,8 @@
@page "/playouts/add" @page "/playouts/add"
@using ErsatzTV.Application.ProgramSchedules
@using ErsatzTV.Application.ProgramSchedules.Queries
@using ErsatzTV.Application.Channels @using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Channels.Queries @using ErsatzTV.Application.Channels.Queries
@using ErsatzTV.Application.ProgramSchedules
@using ErsatzTV.Application.ProgramSchedules.Queries
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ILogger<PlayoutEditor> Logger @inject ILogger<PlayoutEditor> Logger
@inject ISnackbar Snackbar @inject ISnackbar Snackbar

2
ErsatzTV/ViewModels/PlayoutEditViewModel.cs

@ -11,6 +11,6 @@ namespace ErsatzTV.ViewModels
public ProgramScheduleViewModel ProgramSchedule { get; set; } public ProgramScheduleViewModel ProgramSchedule { get; set; }
public CreatePlayout ToCreate() => public CreatePlayout ToCreate() =>
new CreatePlayout(Channel.Id, ProgramSchedule.Id, ProgramSchedulePlayoutType.Flood); new(Channel.Id, ProgramSchedule.Id, ProgramSchedulePlayoutType.Flood);
} }
} }

Loading…
Cancel
Save