@ -9,6 +9,7 @@ namespace ErsatzTV.Core.Scheduling;
public class PlaylistEnumerator : IMediaCollectionEnumerator
public class PlaylistEnumerator : IMediaCollectionEnumerator
{
{
private readonly System . Collections . Generic . HashSet < int > _ remainingMediaItemIds = [ ] ;
private readonly System . Collections . Generic . HashSet < int > _ remainingMediaItemIds = [ ] ;
private CancellationToken _ cancellationToken ;
private System . Collections . Generic . HashSet < int > _ allMediaItemIds ;
private System . Collections . Generic . HashSet < int > _ allMediaItemIds ;
private System . Collections . Generic . HashSet < int > _ idsToIncludeInEPG ;
private System . Collections . Generic . HashSet < int > _ idsToIncludeInEPG ;
private CloneableRandom _ random ;
private CloneableRandom _ random ;
@ -30,9 +31,12 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
public int CountForFiller = > _ sortedEnumerators . Select ( t = > t . PlayAll ? t . Enumerator . Count : t . Count ? ? 1 ) . Sum ( ) ;
public int CountForFiller = > _ sortedEnumerators . Select ( t = > t . PlayAll ? t . Enumerator . Count : t . Count ? ? 1 ) . Sum ( ) ;
public ImmutableList < PlaylistEnumeratorCollectionKey > ChildEnumerators { get ; private set ; }
// callers use EnumeratorIndex on this list, so it must hold the shuffled order
public ImmutableList < PlaylistEnumeratorCollectionKey > ChildEnumerators { get ; private set ; } =
ImmutableList < PlaylistEnumeratorCollectionKey > . Empty ;
public bool CurrentEnumeratorPlayAll = > _ sortedEnumerators [ EnumeratorIndex ] . PlayAll ;
public bool CurrentEnumeratorPlayAll = >
_ sortedEnumerators . Count > 0 & & _ sortedEnumerators [ EnumeratorIndex ] . PlayAll ;
public int EnumeratorIndex { get ; private set ; }
public int EnumeratorIndex { get ; private set ; }
@ -76,6 +80,12 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
public void MoveNext ( Option < DateTimeOffset > scheduledAt )
public void MoveNext ( Option < DateTimeOffset > scheduledAt )
{
{
// Create removes a playlist item that has no playable content, so the list can be empty
if ( _ sortedEnumerators . Count = = 0 )
{
return ;
}
foreach ( MediaItem maybeMediaItem in _ sortedEnumerators [ EnumeratorIndex ] . Enumerator . Current )
foreach ( MediaItem maybeMediaItem in _ sortedEnumerators [ EnumeratorIndex ] . Enumerator . Current )
{
{
_ remainingMediaItemIds . Remove ( maybeMediaItem . Id ) ;
_ remainingMediaItemIds . Remove ( maybeMediaItem . Id ) ;
@ -122,14 +132,44 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
{
{
State . Seed = _ random . Next ( ) ;
State . Seed = _ random . Next ( ) ;
_ random = new CloneableRandom ( State . Seed ) ;
_ random = new CloneableRandom ( State . Seed ) ;
_ sortedEnumerators = ShufflePlaylistItems ( ) ;
SetSortedEnumerators ( ShufflePlaylistItems ( ) ) ;
}
}
}
}
State . Started = true ;
State . Started = true ;
}
}
public void SetEnumeratorIndex ( int enumeratorIndex ) = > EnumeratorIndex = enumeratorIndex % _ sortedEnumerators . Count ;
public void SetEnumeratorIndex ( int enumeratorIndex )
{
if ( _ sortedEnumerators . Count = = 0 )
{
return ;
}
int newIndex = enumeratorIndex % _ sortedEnumerators . Count ;
if ( newIndex ! = EnumeratorIndex )
{
EnumeratorIndex = newIndex ;
// these counts are for the previous child
_ itemsTakenFromCurrent = 0 ;
}
}
// more than one playlist item can use one collection, and IndexOf gives the first of them
public void EnsureCurrentChild ( CollectionKey collectionKey )
{
if ( _ sortedEnumerators . Count = = 0 | | _ sortedEnumerators [ EnumeratorIndex ] . CollectionKey = = collectionKey )
{
return ;
}
int index = _ sortedEnumerators . FindIndex ( e = > e . CollectionKey = = collectionKey ) ;
if ( index > = 0 )
{
SetEnumeratorIndex ( index ) ;
}
}
public static async Task < PlaylistEnumerator > Create (
public static async Task < PlaylistEnumerator > Create (
IMediaCollectionRepository mediaCollectionRepository ,
IMediaCollectionRepository mediaCollectionRepository ,
@ -145,7 +185,8 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
_ sortedEnumerators = [ ] ,
_ sortedEnumerators = [ ] ,
_ idsToIncludeInEPG = [ ] ,
_ idsToIncludeInEPG = [ ] ,
_ shufflePlaylistItems = shufflePlaylistItems ,
_ shufflePlaylistItems = shufflePlaylistItems ,
_ batchSize = batchSize
_ batchSize = batchSize ,
_ cancellationToken = cancellationToken
} ;
} ;
// random start points must be applied on every build, not just the first
// random start points must be applied on every build, not just the first
@ -169,7 +210,11 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
if ( enumeratorMap . TryGetValue ( collectionKey , out IMediaCollectionEnumerator enumerator ) )
if ( enumeratorMap . TryGetValue ( collectionKey , out IMediaCollectionEnumerator enumerator ) )
{
{
result . _ sortedEnumerators . Add (
result . _ sortedEnumerators . Add (
new EnumeratorPlayAllCount ( enumerator , playlistItem . PlayAll , playlistItem . Count ) ) ;
new EnumeratorPlayAllCount (
enumerator ,
collectionKey ,
playlistItem . PlayAll ,
playlistItem . Count ) ) ;
result . TrackMediaItemIds ( items , playlistItem . IncludeInProgramGuide ) ;
result . TrackMediaItemIds ( items , playlistItem . IncludeInProgramGuide ) ;
continue ;
continue ;
}
}
@ -208,6 +253,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
await PlayoutBuilder . GetCollectionItemsForShuffleInOrder (
await PlayoutBuilder . GetCollectionItemsForShuffleInOrder (
mediaCollectionRepository ,
mediaCollectionRepository ,
CollectionKey . ForPlaylistItem ( playlistItem ) ,
CollectionKey . ForPlaylistItem ( playlistItem ) ,
items ,
cancellationToken ) ,
cancellationToken ) ,
initState ,
initState ,
randomStartPoint ,
randomStartPoint ,
@ -237,7 +283,11 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
{
{
enumeratorMap . Add ( collectionKey , enumerator ) ;
enumeratorMap . Add ( collectionKey , enumerator ) ;
result . _ sortedEnumerators . Add (
result . _ sortedEnumerators . Add (
new EnumeratorPlayAllCount ( enumerator , playlistItem . PlayAll , playlistItem . Count ) ) ;
new EnumeratorPlayAllCount (
enumerator ,
collectionKey ,
playlistItem . PlayAll ,
playlistItem . Count ) ) ;
result . TrackMediaItemIds ( items , playlistItem . IncludeInProgramGuide ) ;
result . TrackMediaItemIds ( items , playlistItem . IncludeInProgramGuide ) ;
}
}
}
}
@ -259,10 +309,8 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
result . _ random = new CloneableRandom ( state . Seed ) ;
result . _ random = new CloneableRandom ( state . Seed ) ;
if ( shufflePlaylistItems )
result . SetSortedEnumerators (
{
shufflePlaylistItems ? result . ShufflePlaylistItems ( ) : result . _ sortedEnumerators ) ;
result . _ sortedEnumerators = result . ShufflePlaylistItems ( ) ;
}
result . State = new CollectionEnumeratorState { Seed = state . Seed , Started = state . Started } ;
result . State = new CollectionEnumeratorState { Seed = state . Seed , Started = state . Started } ;
result . EnumeratorIndex = 0 ;
result . EnumeratorIndex = 0 ;
@ -275,20 +323,19 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
result . ReplayTo ( state . Index ) ;
result . ReplayTo ( state . Index ) ;
var childEnumerators = new List < PlaylistEnumeratorCollectionKey > ( ) ;
foreach ( ( IMediaCollectionEnumerator enumerator , _ , _ ) in result . _ sortedEnumerators )
{
foreach ( ( CollectionKey collectionKey , _ ) in enumeratorMap . Find ( e = > e . Value = = enumerator ) )
{
childEnumerators . Add ( new PlaylistEnumeratorCollectionKey ( enumerator , collectionKey ) ) ;
}
}
result . ChildEnumerators = childEnumerators . ToImmutableList ( ) ;
return result ;
return result ;
}
}
// the only writer of _sortedEnumerators, so ChildEnumerators always agrees with it
private void SetSortedEnumerators ( List < EnumeratorPlayAllCount > sortedEnumerators )
{
_ sortedEnumerators = sortedEnumerators ;
ChildEnumerators =
[
. . sortedEnumerators . Map ( e = > new PlaylistEnumeratorCollectionKey ( e . Enumerator , e . CollectionKey ) )
] ;
}
private void TrackMediaItemIds ( List < MediaItem > items , bool includeInProgramGuide )
private void TrackMediaItemIds ( List < MediaItem > items , bool includeInProgramGuide )
{
{
foreach ( MediaItem mediaItem in items )
foreach ( MediaItem mediaItem in items )
@ -311,13 +358,8 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
enumerator . ResetState ( childState . Clone ( ) ) ;
enumerator . ResetState ( childState . Clone ( ) ) ;
}
}
_ sortedEnumerators = [ . . _ enumeratorsInPlaylistOrder ] ;
_ random = new CloneableRandom ( seed ) ;
_ random = new CloneableRandom ( seed ) ;
SetSortedEnumerators ( _ shufflePlaylistItems ? ShufflePlaylistItems ( ) : [ . . _ enumeratorsInPlaylistOrder ] ) ;
if ( _ shufflePlaylistItems )
{
_ sortedEnumerators = ShufflePlaylistItems ( ) ;
}
_ remainingMediaItemIds . Clear ( ) ;
_ remainingMediaItemIds . Clear ( ) ;
_ remainingMediaItemIds . UnionWith ( _ allMediaItemIds ) ;
_ remainingMediaItemIds . UnionWith ( _ allMediaItemIds ) ;
@ -360,6 +402,12 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
EnumeratorPlayAllCount [ ] copy = _ enumeratorsInPlaylistOrder . ToArray ( ) ;
EnumeratorPlayAllCount [ ] copy = _ enumeratorsInPlaylistOrder . ToArray ( ) ;
EnumeratorPlayAllCount last = _ enumeratorsInPlaylistOrder . Last ( ) ;
EnumeratorPlayAllCount last = _ enumeratorsInPlaylistOrder . Last ( ) ;
// a record compares by value. If every entry equals the last one, the loop below cannot end.
if ( _ enumeratorsInPlaylistOrder . All ( e = > e = = last ) )
{
return [ . . _ enumeratorsInPlaylistOrder ] ;
}
do
do
{
{
int n = copy . Length ;
int n = copy . Length ;
@ -369,10 +417,14 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
int k = _ random . Next ( n + 1 ) ;
int k = _ random . Next ( n + 1 ) ;
( copy [ k ] , copy [ n ] ) = ( copy [ n ] , copy [ k ] ) ;
( copy [ k ] , copy [ n ] ) = ( copy [ n ] , copy [ k ] ) ;
}
}
} while ( copy . First ( ) = = last ) ;
} while ( ! _ cancellationToken . IsCancellationRequested & & copy . First ( ) = = last ) ;
return copy . ToList ( ) ;
return copy . ToList ( ) ;
}
}
private record EnumeratorPlayAllCount ( IMediaCollectionEnumerator Enumerator , bool PlayAll , int? Count ) ;
private record EnumeratorPlayAllCount (
IMediaCollectionEnumerator Enumerator ,
CollectionKey CollectionKey ,
bool PlayAll ,
int? Count ) ;
}
}