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.Number,
|
||||||
playout.Channel.Name);
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
List<PlaybackOrder> allowedPlaybackOrders =
|
||||||
|
[
|
||||||
|
PlaybackOrder.Chronological,
|
||||||
|
PlaybackOrder.SeasonEpisode,
|
||||||
|
PlaybackOrder.Random
|
||||||
|
];
|
||||||
|
|
||||||
DateTimeOffset start = DateTimeOffset.Now;
|
DateTimeOffset start = DateTimeOffset.Now;
|
||||||
|
|
||||||
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
||||||
@@ -58,10 +65,10 @@ public class BlockPlayoutBuilder(
|
|||||||
|
|
||||||
DateTimeOffset currentTime = effectiveBlock.Start;
|
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
|
// TODO: support other playback orders
|
||||||
if (blockItem.PlaybackOrder is not PlaybackOrder.SeasonEpisode and not PlaybackOrder.Chronological)
|
if (!allowedPlaybackOrders.Contains(blockItem.PlaybackOrder))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -82,61 +89,27 @@ public class BlockPlayoutBuilder(
|
|||||||
|
|
||||||
var collectionKey = CollectionKey.ForBlockItem(blockItem);
|
var collectionKey = CollectionKey.ForBlockItem(blockItem);
|
||||||
List<MediaItem> collectionItems = collectionMediaItems[collectionKey];
|
List<MediaItem> collectionItems = collectionMediaItems[collectionKey];
|
||||||
|
|
||||||
// get enumerator
|
// 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
|
// seek to the appropriate place in the collection enumerator
|
||||||
foreach (PlayoutHistory history in maybeHistory)
|
foreach (PlayoutHistory history in maybeHistory)
|
||||||
{
|
{
|
||||||
logger.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
|
logger.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
|
||||||
|
|
||||||
// find next media item
|
HistoryDetails.MoveToNextItem(
|
||||||
HistoryDetails.Details details =
|
collectionItems,
|
||||||
JsonConvert.DeserializeObject<HistoryDetails.Details>(history.Details);
|
history.Details,
|
||||||
if (details.SeasonNumber.HasValue && details.EpisodeNumber.HasValue)
|
enumerator,
|
||||||
{
|
blockItem.PlaybackOrder);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (MediaItem mediaItem in enumerator.Current)
|
foreach (MediaItem mediaItem in enumerator.Current)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Domain.Scheduling;
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
|
namespace ErsatzTV.Core.Scheduling.BlockScheduling;
|
||||||
@@ -32,12 +33,104 @@ internal static class HistoryDetails
|
|||||||
Details details = mediaItem switch
|
Details details = mediaItem switch
|
||||||
{
|
{
|
||||||
Episode e => ForEpisode(e),
|
Episode e => ForEpisode(e),
|
||||||
|
Movie m => ForMovie(m),
|
||||||
_ => new Details(mediaItem.Id, null, null, null)
|
_ => new Details(mediaItem.Id, null, null, null)
|
||||||
};
|
};
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(details, Formatting.None, JsonSettings);
|
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)
|
private static Details ForEpisode(Episode e)
|
||||||
{
|
{
|
||||||
int? episodeNumber = null;
|
int? episodeNumber = null;
|
||||||
@@ -51,5 +144,16 @@ internal static class HistoryDetails
|
|||||||
return new Details(e.Id, releaseDate, e.Season.SeasonNumber, episodeNumber);
|
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);
|
public record Details(int? MediaItemId, DateTime? ReleaseDate, int? SeasonNumber, int? EpisodeNumber);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,12 +89,12 @@
|
|||||||
<MudCard>
|
<MudCard>
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
<MudSelect Class="mt-3" Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)">
|
<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.TelevisionShow">Television Show</MudSelectItem>
|
||||||
<MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionSeason">Television Season</MudSelectItem>
|
<MudSelectItem Value="ProgramScheduleItemCollectionType.TelevisionSeason">Television Season</MudSelectItem>
|
||||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem> *@
|
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.Artist">Artist</MudSelectItem> *@
|
||||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem> *@
|
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.MultiCollection">Multi Collection</MudSelectItem> *@
|
||||||
@* <MudSelectItem Value="ProgramScheduleItemCollectionType.SmartCollection">Smart Collection</MudSelectItem> *@
|
<MudSelectItem Value="ProgramScheduleItemCollectionType.SmartCollection">Smart Collection</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
|
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
|
||||||
{
|
{
|
||||||
@@ -180,14 +180,14 @@
|
|||||||
case ProgramScheduleItemCollectionType.Collection:
|
case ProgramScheduleItemCollectionType.Collection:
|
||||||
case ProgramScheduleItemCollectionType.SmartCollection:
|
case ProgramScheduleItemCollectionType.SmartCollection:
|
||||||
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
<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.Shuffle">Shuffle</MudSelectItem> *@
|
||||||
@* <MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem> *@
|
@* <MudSelectItem Value="PlaybackOrder.ShuffleInOrder">Shuffle In Order</MudSelectItem> *@
|
||||||
break;
|
break;
|
||||||
case ProgramScheduleItemCollectionType.TelevisionShow:
|
case ProgramScheduleItemCollectionType.TelevisionShow:
|
||||||
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
||||||
<MudSelectItem Value="PlaybackOrder.SeasonEpisode">Season, Episode</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.Shuffle">Shuffle</MudSelectItem> *@
|
||||||
@* <MudSelectItem Value="PlaybackOrder.MultiEpisodeShuffle">Multi-Episode Shuffle</MudSelectItem> *@
|
@* <MudSelectItem Value="PlaybackOrder.MultiEpisodeShuffle">Multi-Episode Shuffle</MudSelectItem> *@
|
||||||
break;
|
break;
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
case ProgramScheduleItemCollectionType.FakeCollection:
|
case ProgramScheduleItemCollectionType.FakeCollection:
|
||||||
default:
|
default:
|
||||||
<MudSelectItem Value="PlaybackOrder.Chronological">Chronological</MudSelectItem>
|
<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.Shuffle">Shuffle</MudSelectItem> *@
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user