Browse Source

fix: randomize start points with classic playlists

pull/2948/head
Jason Dove 2 weeks ago
parent
commit
fea9b3225e
No known key found for this signature in database
  1. 2
      CHANGELOG.md
  2. 5
      ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs
  3. 1
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutEnumerator.cs
  4. 2
      ErsatzTV.Core/Scheduling/Engine/MarathonHelper.cs
  5. 1
      ErsatzTV.Core/Scheduling/Engine/PlaylistHelper.cs
  6. 1
      ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs
  7. 20
      ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs
  8. 1
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  9. 1
      ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs

2
CHANGELOG.md

@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Update Plex movie and other video titles when changed from Plex - Update Plex movie and other video titles when changed from Plex
- The first library scan after updating will act like a deep scan due to adding title to the Plex etag calculation - The first library scan after updating will act like a deep scan due to adding title to the Plex etag calculation
- Future periodic scans will update titles in ETV automatically (deep scans will not be required to update titles) - Future periodic scans will update titles in ETV automatically (deep scans will not be required to update titles)
- Randomize start points on playlist items in classic schedules when setting is enabled
- Playlists ignored this setting in earlier builds
## [26.6.0] - 2026-07-09 ## [26.6.0] - 2026-07-09
### Added ### Added

5
ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs

@ -62,6 +62,7 @@ public class PlaylistEnumeratorTests
new CollectionEnumeratorState(), new CollectionEnumeratorState(),
shufflePlaylistItems: false, shufflePlaylistItems: false,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
CancellationToken.None); CancellationToken.None);
var items = new List<int>(); var items = new List<int>();
@ -130,6 +131,7 @@ public class PlaylistEnumeratorTests
new CollectionEnumeratorState(), new CollectionEnumeratorState(),
shufflePlaylistItems: false, shufflePlaylistItems: false,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
CancellationToken.None); CancellationToken.None);
var items = new List<int>(); var items = new List<int>();
@ -200,6 +202,7 @@ public class PlaylistEnumeratorTests
state, state,
shufflePlaylistItems: true, shufflePlaylistItems: true,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
CancellationToken.None); CancellationToken.None);
var items = new List<int>(); var items = new List<int>();
@ -270,6 +273,7 @@ public class PlaylistEnumeratorTests
state, state,
shufflePlaylistItems: true, shufflePlaylistItems: true,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
CancellationToken.None); CancellationToken.None);
var items = new List<int>(); var items = new List<int>();
@ -352,6 +356,7 @@ public class PlaylistEnumeratorTests
state, state,
shufflePlaylistItems: true, shufflePlaylistItems: true,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
CancellationToken.None); CancellationToken.None);
enumerator.CountForFiller.ShouldBe(7); enumerator.CountForFiller.ShouldBe(7);

1
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutEnumerator.cs

@ -168,6 +168,7 @@ public static class BlockPlayoutEnumerator
state, state,
shufflePlaylistItems: false, shufflePlaylistItems: false,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
cancellationToken); cancellationToken);
DateTime historyTime = currentTime.UtcDateTime; DateTime historyTime = currentTime.UtcDateTime;

2
ErsatzTV.Core/Scheduling/Engine/MarathonHelper.cs

@ -66,6 +66,7 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
state, state,
marathonShuffleGroups, marathonShuffleGroups,
marathonBatchSize, marathonBatchSize,
randomStartPoint: false,
cancellationToken); cancellationToken);
} }
@ -136,6 +137,7 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
state, state,
shuffleGroups, shuffleGroups,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
cancellationToken); cancellationToken);
return new PlaylistContentResult( return new PlaylistContentResult(

1
ErsatzTV.Core/Scheduling/Engine/PlaylistHelper.cs

@ -58,6 +58,7 @@ public class PlaylistHelper(IMediaCollectionRepository mediaCollectionRepository
state, state,
shufflePlaylistItems: false, shufflePlaylistItems: false,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
cancellationToken); cancellationToken);
return new PlaylistContentResult( return new PlaylistContentResult(

1
ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs

@ -257,6 +257,7 @@ public class SchedulingEngine(
state, state,
shufflePlaylistItems: false, shufflePlaylistItems: false,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
CancellationToken.None); CancellationToken.None);
string historyKey = HistoryDetails.KeyForSchedulingContent(key, PlaybackOrder.None); string historyKey = HistoryDetails.KeyForSchedulingContent(key, PlaybackOrder.None);

20
ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs

@ -121,6 +121,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
CollectionEnumeratorState state, CollectionEnumeratorState state,
bool shufflePlaylistItems, bool shufflePlaylistItems,
Option<int> batchSize, Option<int> batchSize,
bool randomStartPoint,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var result = new PlaylistEnumerator var result = new PlaylistEnumerator
@ -131,6 +132,10 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
_batchSize = batchSize _batchSize = batchSize
}; };
// random start points only apply to a fresh build with no saved state
var random = new Random(state.Seed);
randomStartPoint = randomStartPoint && state.Index == 0;
// collections should share enumerators // collections should share enumerators
var enumeratorMap = new Dictionary<CollectionKey, IMediaCollectionEnumerator>(); var enumeratorMap = new Dictionary<CollectionKey, IMediaCollectionEnumerator>();
result._allMediaItemIds = []; result._allMediaItemIds = [];
@ -166,6 +171,11 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
switch (playlistItem.PlaybackOrder) switch (playlistItem.PlaybackOrder)
{ {
case PlaybackOrder.Chronological: case PlaybackOrder.Chronological:
if (randomStartPoint)
{
initState.Index = random.Next(0, items.Count - 1);
}
enumerator = new ChronologicalMediaCollectionEnumerator(items, initState); enumerator = new ChronologicalMediaCollectionEnumerator(items, initState);
break; break;
// TODO: fix multi episode shuffle? // TODO: fix multi episode shuffle?
@ -187,18 +197,22 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
CollectionKey.ForPlaylistItem(playlistItem), CollectionKey.ForPlaylistItem(playlistItem),
cancellationToken), cancellationToken),
initState, initState,
// TODO: fix this randomStartPoint,
false,
cancellationToken); cancellationToken);
break; break;
case PlaybackOrder.SeasonEpisode: case PlaybackOrder.SeasonEpisode:
// TODO: check random start point? if (randomStartPoint)
{
initState.Index = random.Next(0, items.Count - 1);
}
enumerator = new SeasonEpisodeMediaCollectionEnumerator(items, initState); enumerator = new SeasonEpisodeMediaCollectionEnumerator(items, initState);
// season, episode will filter out season 0, so we may get an empty enumerator back // season, episode will filter out season 0, so we may get an empty enumerator back
if (enumerator.Count == 0) if (enumerator.Count == 0)
{ {
enumerator = null; enumerator = null;
} }
break; break;
case PlaybackOrder.Random: case PlaybackOrder.Random:
enumerator = new RandomizedMediaCollectionEnumerator(items, initState); enumerator = new RandomizedMediaCollectionEnumerator(items, initState);

1
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -1325,6 +1325,7 @@ public class PlayoutBuilder : IPlayoutBuilder
state, state,
marathonShuffleGroups, marathonShuffleGroups,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint,
cancellationToken); cancellationToken);
} }
} }

1
ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs

@ -175,6 +175,7 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor
state, state,
shufflePlaylistItems: false, shufflePlaylistItems: false,
batchSize: Option<int>.None, batchSize: Option<int>.None,
randomStartPoint: false,
cancellationToken); cancellationToken);
} }

Loading…
Cancel
Save