allow more collection types and playback orders in blocks (#1554)
This commit is contained in:
@@ -25,6 +25,13 @@ public class BlockPlayoutBuilder(
|
||||
playout.Channel.Number,
|
||||
playout.Channel.Name);
|
||||
|
||||
List<PlaybackOrder> allowedPlaybackOrders =
|
||||
[
|
||||
PlaybackOrder.Chronological,
|
||||
PlaybackOrder.SeasonEpisode,
|
||||
PlaybackOrder.Random
|
||||
];
|
||||
|
||||
DateTimeOffset start = DateTimeOffset.Now;
|
||||
|
||||
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
||||
@@ -58,10 +65,10 @@ public class BlockPlayoutBuilder(
|
||||
|
||||
DateTimeOffset currentTime = effectiveBlock.Start;
|
||||
|
||||
foreach (BlockItem blockItem in effectiveBlock.Block.Items)
|
||||
foreach (BlockItem blockItem in effectiveBlock.Block.Items.OrderBy(i => i.Index))
|
||||
{
|
||||
// TODO: support other playback orders
|
||||
if (blockItem.PlaybackOrder is not PlaybackOrder.SeasonEpisode and not PlaybackOrder.Chronological)
|
||||
if (!allowedPlaybackOrders.Contains(blockItem.PlaybackOrder))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -82,61 +89,27 @@ public class BlockPlayoutBuilder(
|
||||
|
||||
var collectionKey = CollectionKey.ForBlockItem(blockItem);
|
||||
List<MediaItem> collectionItems = collectionMediaItems[collectionKey];
|
||||
|
||||
// get enumerator
|
||||
var enumerator = new SeasonEpisodeMediaCollectionEnumerator(collectionItems, state);
|
||||
IMediaCollectionEnumerator enumerator = blockItem.PlaybackOrder switch
|
||||
{
|
||||
PlaybackOrder.Chronological => new ChronologicalMediaCollectionEnumerator(collectionItems, state),
|
||||
PlaybackOrder.SeasonEpisode => new SeasonEpisodeMediaCollectionEnumerator(collectionItems, state),
|
||||
_ => new RandomizedMediaCollectionEnumerator(
|
||||
collectionItems,
|
||||
new CollectionEnumeratorState { Seed = new Random().Next(), Index = 0 })
|
||||
};
|
||||
|
||||
// seek to the appropriate place in the collection enumerator
|
||||
foreach (PlayoutHistory history in maybeHistory)
|
||||
{
|
||||
logger.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
|
||||
|
||||
// find next media item
|
||||
HistoryDetails.Details details =
|
||||
JsonConvert.DeserializeObject<HistoryDetails.Details>(history.Details);
|
||||
if (details.SeasonNumber.HasValue && details.EpisodeNumber.HasValue)
|
||||
{
|
||||
Option<MediaItem> maybeMatchedItem = Optional(
|
||||
collectionItems.Find(
|
||||
ci => ci is Episode e &&
|
||||
e.EpisodeMetadata.Any(em => em.EpisodeNumber == details.EpisodeNumber.Value) &&
|
||||
e.Season.SeasonNumber == details.SeasonNumber.Value));
|
||||
|
||||
var copy = collectionItems.ToList();
|
||||
|
||||
if (maybeMatchedItem.IsNone)
|
||||
{
|
||||
var fakeItem = new Episode
|
||||
{
|
||||
Season = new Season { SeasonNumber = details.SeasonNumber.Value },
|
||||
EpisodeMetadata =
|
||||
[
|
||||
new EpisodeMetadata
|
||||
{
|
||||
EpisodeNumber = details.EpisodeNumber.Value,
|
||||
ReleaseDate = details.ReleaseDate
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
copy.Add(fakeItem);
|
||||
maybeMatchedItem = fakeItem;
|
||||
}
|
||||
|
||||
foreach (MediaItem matchedItem in maybeMatchedItem)
|
||||
{
|
||||
IComparer<MediaItem> comparer = blockItem.PlaybackOrder switch
|
||||
{
|
||||
PlaybackOrder.Chronological => new ChronologicalMediaComparer(),
|
||||
_ => new SeasonEpisodeMediaComparer()
|
||||
};
|
||||
|
||||
copy.Sort(comparer);
|
||||
|
||||
state.Index = copy.IndexOf(matchedItem);
|
||||
enumerator.ResetState(state);
|
||||
enumerator.MoveNext();
|
||||
}
|
||||
}
|
||||
HistoryDetails.MoveToNextItem(
|
||||
collectionItems,
|
||||
history.Details,
|
||||
enumerator,
|
||||
blockItem.PlaybackOrder);
|
||||
}
|
||||
|
||||
foreach (MediaItem mediaItem in enumerator.Current)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
|
||||
@@ -32,12 +33,104 @@ internal static class HistoryDetails
|
||||
Details details = mediaItem switch
|
||||
{
|
||||
Episode e => ForEpisode(e),
|
||||
Movie m => ForMovie(m),
|
||||
_ => new Details(mediaItem.Id, null, null, null)
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(details, Formatting.None, JsonSettings);
|
||||
}
|
||||
|
||||
public static void MoveToNextItem(
|
||||
List<MediaItem> collectionItems,
|
||||
string detailsString,
|
||||
IMediaCollectionEnumerator enumerator,
|
||||
PlaybackOrder playbackOrder)
|
||||
{
|
||||
if (playbackOrder is PlaybackOrder.Random)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Option<MediaItem> maybeMatchedItem = Option<MediaItem>.None;
|
||||
var copy = collectionItems.ToList();
|
||||
|
||||
Details details = JsonConvert.DeserializeObject<Details>(detailsString);
|
||||
if (details.SeasonNumber.HasValue && details.EpisodeNumber.HasValue)
|
||||
{
|
||||
int season = details.SeasonNumber.Value;
|
||||
int episode = details.EpisodeNumber.Value;
|
||||
|
||||
maybeMatchedItem = Optional(collectionItems.Find(ci => MatchSeasonAndEpisode(ci, season, episode)));
|
||||
|
||||
if (maybeMatchedItem.IsNone)
|
||||
{
|
||||
var fakeItem = new Episode
|
||||
{
|
||||
Season = new Season { SeasonNumber = season },
|
||||
EpisodeMetadata =
|
||||
[
|
||||
new EpisodeMetadata
|
||||
{
|
||||
EpisodeNumber = episode,
|
||||
ReleaseDate = details.ReleaseDate
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
copy.Add(fakeItem);
|
||||
maybeMatchedItem = fakeItem;
|
||||
}
|
||||
}
|
||||
else if (playbackOrder is PlaybackOrder.Chronological && details.ReleaseDate.HasValue)
|
||||
{
|
||||
maybeMatchedItem = Optional(collectionItems.Find(ci => MatchReleaseDate(ci, details.ReleaseDate.Value)));
|
||||
|
||||
if (maybeMatchedItem.IsNone)
|
||||
{
|
||||
var fakeItem = new Movie { MovieMetadata = [new MovieMetadata { ReleaseDate = details.ReleaseDate }] };
|
||||
copy.Add(fakeItem);
|
||||
maybeMatchedItem = fakeItem;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MediaItem matchedItem in maybeMatchedItem)
|
||||
{
|
||||
IComparer<MediaItem> comparer = playbackOrder switch
|
||||
{
|
||||
PlaybackOrder.Chronological => new ChronologicalMediaComparer(),
|
||||
_ => new SeasonEpisodeMediaComparer(),
|
||||
};
|
||||
|
||||
copy.Sort(comparer);
|
||||
|
||||
var state = new CollectionEnumeratorState
|
||||
{
|
||||
Seed = enumerator.State.Seed,
|
||||
Index = copy.IndexOf(matchedItem)
|
||||
};
|
||||
enumerator.ResetState(state);
|
||||
enumerator.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool MatchReleaseDate(MediaItem mediaItem, DateTime releaseDate) =>
|
||||
mediaItem switch
|
||||
{
|
||||
Movie m => m.MovieMetadata.Any(mm => mm.ReleaseDate == releaseDate),
|
||||
Episode e => e.EpisodeMetadata.Any(em => em.ReleaseDate == releaseDate),
|
||||
//MusicVideo mv => mv.MusicVideoMetadata.Any(mvm => mvm.ReleaseDate == releaseDate),
|
||||
OtherVideo ov => ov.OtherVideoMetadata.Any(ovm => ovm.ReleaseDate == releaseDate),
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static bool MatchSeasonAndEpisode(MediaItem mediaItem, int seasonNumber, int episodeNumber) =>
|
||||
mediaItem switch
|
||||
{
|
||||
Episode e => e.Season.SeasonNumber == seasonNumber &&
|
||||
e.EpisodeMetadata.Any(em => em.EpisodeNumber == episodeNumber),
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static Details ForEpisode(Episode e)
|
||||
{
|
||||
int? episodeNumber = null;
|
||||
@@ -51,5 +144,16 @@ internal static class HistoryDetails
|
||||
return new Details(e.Id, releaseDate, e.Season.SeasonNumber, episodeNumber);
|
||||
}
|
||||
|
||||
private static Details ForMovie(Movie m)
|
||||
{
|
||||
DateTime? releaseDate = null;
|
||||
foreach (MovieMetadata movieMetadata in m.MovieMetadata.HeadOrNone())
|
||||
{
|
||||
releaseDate = movieMetadata.ReleaseDate;
|
||||
}
|
||||
|
||||
return new Details(m.Id, releaseDate, null, null);
|
||||
}
|
||||
|
||||
public record Details(int? MediaItemId, DateTime? ReleaseDate, int? SeasonNumber, int? EpisodeNumber);
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudSelect Class="mt-3" Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)">
|
||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.Collection">Collection</MudSelectItem> *@
|
||||
<MudSelectItem Value="ProgramScheduleItemCollectionType.Collection">Collection</MudSelectItem>
|
||||
<MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionShow">Television Show</MudSelectItem>
|
||||
<MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionSeason">Television Season</MudSelectItem>
|
||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem> *@
|
||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem> *@
|
||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.SmartCollection">Smart Collection</MudSelectItem> *@
|
||||
<MudSelectItem Value="ProgramScheduleItemCollectionType.SmartCollection">Smart Collection</MudSelectItem>
|
||||
</MudSelect>
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
|
||||
{
|
||||
@@ -180,14 +180,14 @@
|
||||
case ProgramScheduleItemCollectionType.Collection:
|
||||
case ProgramScheduleItemCollectionType.SmartCollection:
|
||||
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
||||
@* <MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem> *@
|
||||
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem>
|
||||
@* <MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> *@
|
||||
@* <MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem> *@
|
||||
break;
|
||||
case ProgramScheduleItemCollectionType.TelevisionShow:
|
||||
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
||||
<MudSelectItem Value="PlaybackOrder.SeasonEpisode">Season, Episode</MudSelectItem>
|
||||
@* <MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem> *@
|
||||
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem>
|
||||
@* <MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> *@
|
||||
@* <MudSelectItem Value="PlaybackOrder.MultiEpisodeShuffle">Multi-Episode Shuffle</MudSelectItem> *@
|
||||
break;
|
||||
@@ -196,7 +196,7 @@
|
||||
case ProgramScheduleItemCollectionType.FakeCollection:
|
||||
default:
|
||||
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
||||
@* <MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem> *@
|
||||
<MudSelectItem Value="PlaybackOrder.Random">Random</MudSelectItem>
|
||||
@* <MudSelectItem Value="PlaybackOrder.Shuffle">Shuffle</MudSelectItem> *@
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user