playout builder bugfixes

This commit is contained in:
Jason Dove
2021-02-10 07:43:18 -06:00
parent 32f4bfcf08
commit 0e262f227d
13 changed files with 156 additions and 185 deletions
@@ -7,7 +7,6 @@ namespace ErsatzTV.Core.Interfaces.Scheduling
{
MediaCollectionEnumeratorState State { get; }
public Option<MediaItem> Current { get; }
public Option<MediaItem> Peek { get; }
public void MoveNext();
}
}
@@ -10,8 +10,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling
public Task<Playout> BuildPlayoutItems(
Playout playout,
DateTimeOffset start,
DateTimeOffset finish,
DateTimeOffset playoutStart,
DateTimeOffset playoutFinish,
bool rebuild = false);
}
}
@@ -32,10 +32,6 @@ namespace ErsatzTV.Core.Scheduling
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;
}
}
+41 -22
View File
@@ -34,8 +34,8 @@ namespace ErsatzTV.Core.Scheduling
public async Task<Playout> BuildPlayoutItems(
Playout playout,
DateTimeOffset start,
DateTimeOffset finish,
DateTimeOffset playoutStart,
DateTimeOffset playoutFinish,
bool rebuild = false)
{
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));
// find start anchor
PlayoutAnchor startAnchor = FindStartAnchor(playout, start, sortedScheduleItems);
PlayoutAnchor startAnchor = FindStartAnchor(playout, playoutStart, sortedScheduleItems);
// start at the previously-decided time
DateTimeOffset currentTime = startAnchor.NextStart;
@@ -88,35 +88,39 @@ namespace ErsatzTV.Core.Scheduling
Option<int> multipleRemaining = None;
Option<DateTimeOffset> durationFinish = None;
// 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
ProgramScheduleItem scheduleItem = sortedScheduleItems[index % sortedScheduleItems.Count];
// find when we should start this item, based on the current time
DateTimeOffset startTime = GetStartTimeAfter(
DateTimeOffset itemStartTime = GetStartTimeAfter(
scheduleItem,
currentTime,
multipleRemaining.IsSome,
durationFinish.IsSome);
_logger.LogDebug(
"Schedule item: {ScheduleItemNumber} / {MediaCollectionName} / {StartTime}",
scheduleItem.Index,
scheduleItem.MediaCollection.Name,
startTime);
IMediaCollectionEnumerator enumerator = collectionEnumerators[scheduleItem.MediaCollection];
enumerator.Current.IfSome(
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
{
MediaItemId = mediaItem.Id,
Start = startTime,
Finish = startTime + mediaItem.Metadata.Duration
Start = itemStartTime,
Finish = itemStartTime + mediaItem.Metadata.Duration
};
currentTime = startTime + mediaItem.Metadata.Duration;
currentTime = itemStartTime + mediaItem.Metadata.Duration;
enumerator.MoveNext();
playout.Items.Add(playoutItem);
@@ -126,7 +130,7 @@ namespace ErsatzTV.Core.Scheduling
case ProgramScheduleItemOne:
// only play one item from collection, so always advance to the next item
_logger.LogDebug(
"Advancing to next playout item after playout mode {PlayoutMode}",
"Advancing to next schedule item after playout mode {PlayoutMode}",
"One");
index++;
break;
@@ -139,13 +143,16 @@ namespace ErsatzTV.Core.Scheduling
multipleRemaining = multipleRemaining.Map(i => i - 1);
if (multipleRemaining.IfNone(-1) == 0)
{
_logger.LogDebug(
"Advancing to next schedule item after playout mode {PlayoutMode}",
"Multiple");
index++;
multipleRemaining = None;
}
break;
case ProgramScheduleItemFlood:
enumerator.Peek.Do(
enumerator.Current.Do(
peekMediaItem =>
{
ProgramScheduleItem peekScheduleItem =
@@ -163,25 +170,32 @@ namespace ErsatzTV.Core.Scheduling
peekScheduleItemStart;
if (willNotFinishInTime)
{
_logger.LogDebug(
"Advancing to next schedule item after playout mode {PlayoutMode}",
"Flood");
index++;
}
});
break;
case ProgramScheduleItemDuration duration:
enumerator.Peek.Do(
enumerator.Current.Do(
peekMediaItem =>
{
// remember when we need to finish this duration item
if (durationFinish.IsNone)
{
durationFinish = startTime + duration.PlayoutDuration;
durationFinish = itemStartTime + duration.PlayoutDuration;
}
DateTimeOffset finish = durationFinish.IfNone(DateTime.MinValue);
bool willNotFinishInTime = currentTime <= finish &&
currentTime + peekMediaItem.Metadata.Duration >
finish;
bool willNotFinishInTime =
currentTime <= durationFinish.IfNone(DateTime.MinValue) &&
currentTime + peekMediaItem.Metadata.Duration >
durationFinish.IfNone(DateTime.MinValue);
if (willNotFinishInTime)
{
_logger.LogDebug(
"Advancing to next schedule item after playout mode {PlayoutMode}",
"Duration");
index++;
if (duration.OfflineTail)
@@ -211,7 +225,7 @@ namespace ErsatzTV.Core.Scheduling
playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(playout, collectionEnumerators);
// 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;
}
@@ -332,5 +346,10 @@ namespace ErsatzTV.Core.Scheduling
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;
}
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling;
@@ -14,13 +13,11 @@ namespace ErsatzTV.Core.Scheduling
private readonly IList<MediaItem> _mediaItems;
private readonly Random _random;
private int _index;
private Option<int> _peekNext;
public RandomizedMediaCollectionEnumerator(IList<MediaItem> mediaItems, MediaCollectionEnumeratorState state)
{
_mediaItems = mediaItems;
_random = new Random(state.Seed);
_peekNext = None;
State = new MediaCollectionEnumeratorState { Seed = state.Seed };
// 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> 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()
{
// TODO: reset seed at some predictable point so we don't overflow the index
Debug.WriteLine("resetting peek");
_index = _peekNext.IfNone(() => _random.Next() % _mediaItems.Count);
_peekNext = None;
_index = _random.Next() % _mediaItems.Count;
State.Index++;
}
}
@@ -4,7 +4,6 @@ using System.Linq;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling;
using LanguageExt;
using LanguageExt.UnsafeValueAccess;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Scheduling
@@ -12,7 +11,6 @@ namespace ErsatzTV.Core.Scheduling
public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
{
private readonly IList<MediaItem> _mediaItems;
private Option<int> _peekNextSeed;
private Random _random;
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> 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()
{
State.Index++;
if (State.Index % _shuffled.Count == 0)
{
State.Index = 0;
State.Seed = _peekNextSeed.IfNone(_random.Next());
State.Seed = _random.Next();
_random = new Random(State.Seed);
_shuffled = Shuffle(_mediaItems, _random);
}
State.Index %= _shuffled.Count;
_peekNextSeed = None;
}
private static IList<T> Shuffle<T>(IEnumerable<T> list, Random random)