playout builder bugfixes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user