add all content sources to scripted schedules (#2340)
* add show content * add multi collection content * add smart collection content * add playlist content * fix infinite loop * add marathon content
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Filler;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
@@ -98,7 +99,7 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<ISchedulingEngine> AddCollection(string key, string collectionName, PlaybackOrder playbackOrder)
|
||||
public async Task AddCollection(string key, string collectionName, PlaybackOrder playbackOrder)
|
||||
{
|
||||
if (!_enumerators.ContainsKey(key))
|
||||
{
|
||||
@@ -107,7 +108,7 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
if (items.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Skipping invalid or empty collection {Name}", collectionName);
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
@@ -128,11 +129,159 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<ISchedulingEngine> AddSearch(string key, string query, PlaybackOrder playbackOrder)
|
||||
public async Task AddMarathon(
|
||||
string key,
|
||||
Dictionary<string, List<string>> guids,
|
||||
List<string> searches,
|
||||
string groupBy,
|
||||
bool shuffleGroups,
|
||||
PlaybackOrder itemPlaybackOrder,
|
||||
bool playAllItems)
|
||||
{
|
||||
var helper = new MarathonHelper(mediaCollectionRepository);
|
||||
|
||||
int index = _enumerators.Count;
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
Option<MarathonContentResult> maybeResult = await helper.GetEnumerator(
|
||||
guids,
|
||||
searches,
|
||||
groupBy,
|
||||
shuffleGroups,
|
||||
itemPlaybackOrder,
|
||||
playAllItems,
|
||||
state,
|
||||
CancellationToken.None);
|
||||
|
||||
foreach (MarathonContentResult result in maybeResult)
|
||||
{
|
||||
foreach (PlaylistEnumerator enumerator in Optional(result.PlaylistEnumerator))
|
||||
{
|
||||
string historyKey = HistoryDetails.KeyForSchedulingMarathonContent(key, itemPlaybackOrder, groupBy);
|
||||
var details = new EnumeratorDetails(enumerator, historyKey, PlaybackOrder.None);
|
||||
|
||||
if (_enumerators.TryAdd(key, details))
|
||||
{
|
||||
logger.LogDebug("Added marathon with key {Key}", key);
|
||||
ApplyPlaylistHistory(
|
||||
historyKey,
|
||||
result.Content,
|
||||
enumerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
public async Task AddMultiCollection(
|
||||
string key,
|
||||
string multiCollectionName,
|
||||
PlaybackOrder playbackOrder)
|
||||
{
|
||||
if (!_enumerators.ContainsKey(key))
|
||||
{
|
||||
int index = _enumerators.Count;
|
||||
List<MediaItem> items = await mediaCollectionRepository.GetMultiCollectionItemsByName(multiCollectionName);
|
||||
if (items.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Skipping invalid or empty multi collection {Name}", multiCollectionName);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
foreach (var enumerator in EnumeratorForContent(items, state, playbackOrder))
|
||||
{
|
||||
string historyKey = HistoryDetails.KeyForSchedulingContent(key, playbackOrder);
|
||||
var details = new EnumeratorDetails(enumerator, historyKey, playbackOrder);
|
||||
|
||||
if (_enumerators.TryAdd(key, details))
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Added multi collection {Name} with key {Key} and order {Order}",
|
||||
multiCollectionName,
|
||||
key,
|
||||
playbackOrder);
|
||||
|
||||
ApplyHistory(historyKey, items, enumerator, playbackOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddPlaylist(string key, string playlist, string playlistGroup)
|
||||
{
|
||||
if (!_enumerators.ContainsKey(key))
|
||||
{
|
||||
int index = _enumerators.Count;
|
||||
Dictionary<PlaylistItem, List<MediaItem>> itemMap =
|
||||
await mediaCollectionRepository.GetPlaylistItemMap(playlistGroup, playlist);
|
||||
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
|
||||
var enumerator = await PlaylistEnumerator.Create(
|
||||
mediaCollectionRepository,
|
||||
itemMap,
|
||||
state,
|
||||
false,
|
||||
CancellationToken.None);
|
||||
|
||||
string historyKey = HistoryDetails.KeyForSchedulingContent(key, PlaybackOrder.None);
|
||||
var details = new EnumeratorDetails(enumerator, historyKey, PlaybackOrder.None);
|
||||
|
||||
if (_enumerators.TryAdd(key, details))
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Added playlist {Group} / {Name} with key {Key}",
|
||||
playlistGroup,
|
||||
playlist,
|
||||
key);
|
||||
|
||||
ApplyPlaylistHistory(
|
||||
historyKey,
|
||||
itemMap.ToImmutableDictionary(m => CollectionKey.ForPlaylistItem(m.Key), m => m.Value),
|
||||
enumerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddSmartCollection(
|
||||
string key,
|
||||
string smartCollectionName,
|
||||
PlaybackOrder playbackOrder)
|
||||
{
|
||||
if (!_enumerators.ContainsKey(key))
|
||||
{
|
||||
int index = _enumerators.Count;
|
||||
List<MediaItem> items = await mediaCollectionRepository.GetSmartCollectionItemsByName(smartCollectionName);
|
||||
if (items.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Skipping invalid or empty smart collection {Name}", smartCollectionName);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
foreach (var enumerator in EnumeratorForContent(items, state, playbackOrder))
|
||||
{
|
||||
string historyKey = HistoryDetails.KeyForSchedulingContent(key, playbackOrder);
|
||||
var details = new EnumeratorDetails(enumerator, historyKey, playbackOrder);
|
||||
|
||||
if (_enumerators.TryAdd(key, details))
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Added smart collection {Name} with key {Key} and order {Order}",
|
||||
smartCollectionName,
|
||||
key,
|
||||
playbackOrder);
|
||||
|
||||
ApplyHistory(historyKey, items, enumerator, playbackOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddSearch(string key, string query, PlaybackOrder playbackOrder)
|
||||
{
|
||||
if (!_enumerators.ContainsKey(key))
|
||||
{
|
||||
@@ -141,7 +290,7 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
if (items.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Skipping invalid or empty search query {Query}", query);
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
@@ -162,11 +311,45 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISchedulingEngine AddCount(
|
||||
public async Task AddShow(
|
||||
string key,
|
||||
Dictionary<string, string> guids,
|
||||
PlaybackOrder playbackOrder)
|
||||
{
|
||||
if (!_enumerators.ContainsKey(key))
|
||||
{
|
||||
int index = _enumerators.Count;
|
||||
List<MediaItem> items =
|
||||
await mediaCollectionRepository.GetShowItemsByShowGuids(
|
||||
guids.Map(g => $"{g.Key}://{g.Value}").ToList());
|
||||
if (items.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Skipping invalid or empty show with key {Key}", key);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CollectionEnumeratorState { Seed = _state.Seed + index, Index = 0 };
|
||||
foreach (var enumerator in EnumeratorForContent(items, state, playbackOrder))
|
||||
{
|
||||
string historyKey = HistoryDetails.KeyForSchedulingContent(key, playbackOrder);
|
||||
var details = new EnumeratorDetails(enumerator, historyKey, playbackOrder);
|
||||
|
||||
if (_enumerators.TryAdd(key, details))
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Added show with key {Key} and order {Order}",
|
||||
key,
|
||||
playbackOrder);
|
||||
|
||||
ApplyHistory(historyKey, items, enumerator, playbackOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddCount(
|
||||
string content,
|
||||
int count,
|
||||
Option<FillerKind> fillerKind,
|
||||
@@ -176,9 +359,11 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
if (!_enumerators.TryGetValue(content, out EnumeratorDetails enumeratorDetails))
|
||||
{
|
||||
logger.LogWarning("Skipping invalid content {Key}", content);
|
||||
return this;
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = false;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
// foreach (string preRollSequence in context.GetPreRollSequence())
|
||||
@@ -244,6 +429,8 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
}
|
||||
|
||||
enumeratorDetails.Enumerator.MoveNext();
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
// foreach (string postRollSequence in context.GetPostRollSequence())
|
||||
@@ -254,7 +441,7 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
// }
|
||||
}
|
||||
|
||||
return this;
|
||||
return result;
|
||||
}
|
||||
|
||||
public ISchedulingEngine WaitUntil(TimeOnly waitUntil, bool tomorrow, bool rewindOnReset)
|
||||
@@ -330,6 +517,106 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
return Option<IMediaCollectionEnumerator>.None;
|
||||
}
|
||||
|
||||
private void ApplyPlaylistHistory(
|
||||
string historyKey,
|
||||
ImmutableDictionary<CollectionKey, List<MediaItem>> itemMap,
|
||||
PlaylistEnumerator playlistEnumerator)
|
||||
{
|
||||
DateTime historyTime = _state.CurrentTime.UtcDateTime;
|
||||
|
||||
var filteredHistory = _referenceData.PlayoutHistory.ToList();
|
||||
filteredHistory.RemoveAll(h => _state.HistoryToRemove.Contains(h.Id));
|
||||
|
||||
Option<DateTime> maxWhen = filteredHistory
|
||||
.Filter(h => h.Key == historyKey)
|
||||
.Filter(h => h.When < historyTime)
|
||||
.Map(h => h.When)
|
||||
.OrderByDescending(h => h)
|
||||
.HeadOrNone()
|
||||
.IfNone(DateTime.MinValue);
|
||||
|
||||
var maybeHistory = filteredHistory
|
||||
.Filter(h => h.Key == historyKey)
|
||||
.Filter(h => h.When == maxWhen)
|
||||
.ToList();
|
||||
|
||||
Option<PlayoutHistory> maybePrimaryHistory = maybeHistory
|
||||
.Filter(h => string.IsNullOrWhiteSpace(h.ChildKey))
|
||||
.HeadOrNone();
|
||||
|
||||
foreach (PlayoutHistory primaryHistory in maybePrimaryHistory)
|
||||
{
|
||||
var hasSetEnumeratorIndex = false;
|
||||
|
||||
var childEnumeratorKeys = playlistEnumerator.ChildEnumerators.Map(x => x.CollectionKey).ToList();
|
||||
foreach ((IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) in
|
||||
playlistEnumerator.ChildEnumerators)
|
||||
{
|
||||
PlaybackOrder itemPlaybackOrder = childEnumerator switch
|
||||
{
|
||||
ChronologicalMediaCollectionEnumerator => PlaybackOrder.Chronological,
|
||||
RandomizedMediaCollectionEnumerator => PlaybackOrder.Random,
|
||||
ShuffledMediaCollectionEnumerator => PlaybackOrder.Shuffle,
|
||||
_ => PlaybackOrder.None
|
||||
};
|
||||
|
||||
Option<PlayoutHistory> maybeApplicableHistory = maybeHistory
|
||||
.Filter(h => h.ChildKey == HistoryDetails.KeyForCollectionKey(collectionKey))
|
||||
.HeadOrNone();
|
||||
|
||||
if (!itemMap.TryGetValue(collectionKey, out List<MediaItem> collectionItems) ||
|
||||
collectionItems.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (PlayoutHistory h in maybeApplicableHistory)
|
||||
{
|
||||
// logger.LogDebug(
|
||||
// "History is applicable: {When}: {ChildKey} / {History} / {IsCurrentChild}",
|
||||
// h.When,
|
||||
// h.ChildKey,
|
||||
// h.Details,
|
||||
// h.IsCurrentChild);
|
||||
|
||||
playlistEnumerator.ResetState(
|
||||
new CollectionEnumeratorState
|
||||
{
|
||||
Seed = playlistEnumerator.State.Seed,
|
||||
Index = h.Index + (h.IsCurrentChild ? 1 : 0)
|
||||
});
|
||||
|
||||
if (itemPlaybackOrder is PlaybackOrder.Chronological)
|
||||
{
|
||||
HistoryDetails.MoveToNextItem(
|
||||
collectionItems,
|
||||
h.Details,
|
||||
childEnumerator,
|
||||
itemPlaybackOrder,
|
||||
true);
|
||||
}
|
||||
|
||||
if (h.IsCurrentChild)
|
||||
{
|
||||
// try to find enumerator based on collection key
|
||||
playlistEnumerator.SetEnumeratorIndex(childEnumeratorKeys.IndexOf(collectionKey));
|
||||
hasSetEnumeratorIndex = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSetEnumeratorIndex)
|
||||
{
|
||||
// falling back to enumerator based on index
|
||||
playlistEnumerator.SetEnumeratorIndex(primaryHistory.Index);
|
||||
}
|
||||
|
||||
// only move next at the end, because that may also move
|
||||
// the enumerator index
|
||||
playlistEnumerator.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyHistory(
|
||||
string historyKey,
|
||||
List<MediaItem> collectionItems,
|
||||
@@ -354,106 +641,30 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
|
||||
.Filter(h => h.When == maxWhen)
|
||||
.ToList();
|
||||
|
||||
if (enumerator is PlaylistEnumerator playlistEnumerator)
|
||||
if (enumerator is PlaylistEnumerator)
|
||||
{
|
||||
Option<PlayoutHistory> maybePrimaryHistory = maybeHistory
|
||||
.Filter(h => string.IsNullOrWhiteSpace(h.ChildKey))
|
||||
.HeadOrNone();
|
||||
|
||||
foreach (PlayoutHistory primaryHistory in maybePrimaryHistory)
|
||||
{
|
||||
var hasSetEnumeratorIndex = false;
|
||||
|
||||
var childEnumeratorKeys = playlistEnumerator.ChildEnumerators.Map(x => x.CollectionKey).ToList();
|
||||
foreach ((IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) in
|
||||
playlistEnumerator.ChildEnumerators)
|
||||
{
|
||||
PlaybackOrder itemPlaybackOrder = childEnumerator switch
|
||||
{
|
||||
ChronologicalMediaCollectionEnumerator => PlaybackOrder.Chronological,
|
||||
RandomizedMediaCollectionEnumerator => PlaybackOrder.Random,
|
||||
ShuffledMediaCollectionEnumerator => PlaybackOrder.Shuffle,
|
||||
_ => PlaybackOrder.None
|
||||
};
|
||||
|
||||
Option<PlayoutHistory> maybeApplicableHistory = maybeHistory
|
||||
.Filter(h => h.ChildKey == HistoryDetails.KeyForCollectionKey(collectionKey))
|
||||
.HeadOrNone();
|
||||
|
||||
if (collectionItems.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (PlayoutHistory h in maybeApplicableHistory)
|
||||
{
|
||||
// logger.LogDebug(
|
||||
// "History is applicable: {When}: {ChildKey} / {History} / {IsCurrentChild}",
|
||||
// h.When,
|
||||
// h.ChildKey,
|
||||
// h.Details,
|
||||
// h.IsCurrentChild);
|
||||
|
||||
enumerator.ResetState(
|
||||
new CollectionEnumeratorState
|
||||
{
|
||||
Seed = enumerator.State.Seed,
|
||||
Index = h.Index + (h.IsCurrentChild ? 1 : 0)
|
||||
});
|
||||
|
||||
if (itemPlaybackOrder is PlaybackOrder.Chronological)
|
||||
{
|
||||
HistoryDetails.MoveToNextItem(
|
||||
collectionItems,
|
||||
h.Details,
|
||||
childEnumerator,
|
||||
itemPlaybackOrder,
|
||||
true);
|
||||
}
|
||||
|
||||
if (h.IsCurrentChild)
|
||||
{
|
||||
// try to find enumerator based on collection key
|
||||
playlistEnumerator.SetEnumeratorIndex(childEnumeratorKeys.IndexOf(collectionKey));
|
||||
hasSetEnumeratorIndex = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSetEnumeratorIndex)
|
||||
{
|
||||
// falling back to enumerator based on index
|
||||
playlistEnumerator.SetEnumeratorIndex(primaryHistory.Index);
|
||||
}
|
||||
|
||||
// only move next at the end, because that may also move
|
||||
// the enumerator index
|
||||
playlistEnumerator.MoveNext();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (collectionItems.Count == 0)
|
||||
{
|
||||
if (collectionItems.Count == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// seek to the appropriate place in the collection enumerator
|
||||
foreach (PlayoutHistory h in maybeHistory)
|
||||
{
|
||||
// logger.LogDebug("History is applicable: {When}: {History}", h.When, h.Details);
|
||||
|
||||
enumerator.ResetState(new CollectionEnumeratorState { Seed = enumerator.State.Seed, Index = h.Index + 1 });
|
||||
|
||||
if (playbackOrder is PlaybackOrder.Chronological)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// seek to the appropriate place in the collection enumerator
|
||||
foreach (PlayoutHistory h in maybeHistory)
|
||||
{
|
||||
// logger.LogDebug("History is applicable: {When}: {History}", h.When, h.Details);
|
||||
|
||||
enumerator.ResetState(
|
||||
new CollectionEnumeratorState { Seed = enumerator.State.Seed, Index = h.Index + 1 });
|
||||
|
||||
if (playbackOrder is PlaybackOrder.Chronological)
|
||||
{
|
||||
HistoryDetails.MoveToNextItem(
|
||||
collectionItems,
|
||||
h.Details,
|
||||
enumerator,
|
||||
playbackOrder);
|
||||
}
|
||||
HistoryDetails.MoveToNextItem(
|
||||
collectionItems,
|
||||
h.Details,
|
||||
enumerator,
|
||||
playbackOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user