Browse Source

add random start point option (#804)

pull/805/head
Jason Dove 4 years ago committed by GitHub
parent
commit
d19e95fb38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramSchedule.cs
  3. 3
      ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs
  4. 3
      ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramSchedule.cs
  5. 4
      ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramScheduleHandler.cs
  6. 3
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  7. 3
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleViewModel.cs
  8. 3
      ErsatzTV.Application/ProgramSchedules/Queries/GetAllProgramSchedulesHandler.cs
  9. 1
      ErsatzTV.Core/Domain/ProgramSchedule.cs
  10. 43
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  11. 22
      ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs
  12. 4291
      ErsatzTV.Infrastructure/Migrations/20220513230433_Add_ProgramSchedule_RandomStartPoint.Designer.cs
  13. 26
      ErsatzTV.Infrastructure/Migrations/20220513230433_Add_ProgramSchedule_RandomStartPoint.cs
  14. 3
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  15. 6
      ErsatzTV/Pages/ScheduleEditor.razor
  16. 5
      ErsatzTV/ViewModels/ProgramScheduleEditViewModel.cs

3
CHANGELOG.md

@ -19,6 +19,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -19,6 +19,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add filler preset option to allow watermarks to overlay on top of filler (disabled by default)
- This option is applied when new items are added to a playout; rebuilding is needed if you want the change to take effect immediately
- Read `track` field from music video NFO metadata and use it for chronological sorting (after release date)
- Add `Random Start Point` option to schedules
- When this option is enabled, all `Chronological` or `Shuffle In Order` content groups will have their start points randomized
- When this option is disabled, all `Chronological` or `Shuffle In Order` content groups will start with the chronologically earliest item
### Changed
- Replace invalid (control) characters in NFO metadata with replacement character `<EFBFBD>` before parsing

3
ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramSchedule.cs

@ -6,4 +6,5 @@ public record CreateProgramSchedule( @@ -6,4 +6,5 @@ public record CreateProgramSchedule(
string Name,
bool KeepMultiPartEpisodesTogether,
bool TreatCollectionsAsShows,
bool ShuffleScheduleItems) : IRequest<Either<BaseError, CreateProgramScheduleResult>>;
bool ShuffleScheduleItems,
bool RandomStartPoint) : IRequest<Either<BaseError, CreateProgramScheduleResult>>;

3
ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs

@ -44,7 +44,8 @@ public class CreateProgramScheduleHandler : @@ -44,7 +44,8 @@ public class CreateProgramScheduleHandler :
Name = name,
KeepMultiPartEpisodesTogether = keepMultiPartEpisodesTogether,
TreatCollectionsAsShows = keepMultiPartEpisodesTogether && request.TreatCollectionsAsShows,
ShuffleScheduleItems = request.ShuffleScheduleItems
ShuffleScheduleItems = request.ShuffleScheduleItems,
RandomStartPoint = request.RandomStartPoint
};
});

3
ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramSchedule.cs

@ -8,4 +8,5 @@ public record UpdateProgramSchedule @@ -8,4 +8,5 @@ public record UpdateProgramSchedule
string Name,
bool KeepMultiPartEpisodesTogether,
bool TreatCollectionsAsShows,
bool ShuffleScheduleItems) : IRequest<Either<BaseError, UpdateProgramScheduleResult>>;
bool ShuffleScheduleItems,
bool RandomStartPoint) : IRequest<Either<BaseError, UpdateProgramScheduleResult>>;

4
ErsatzTV.Application/ProgramSchedules/Commands/UpdateProgramScheduleHandler.cs

@ -41,13 +41,15 @@ public class UpdateProgramScheduleHandler : @@ -41,13 +41,15 @@ public class UpdateProgramScheduleHandler :
bool needToRefreshPlayout =
programSchedule.KeepMultiPartEpisodesTogether != request.KeepMultiPartEpisodesTogether ||
programSchedule.TreatCollectionsAsShows != request.TreatCollectionsAsShows ||
programSchedule.ShuffleScheduleItems != request.ShuffleScheduleItems;
programSchedule.ShuffleScheduleItems != request.ShuffleScheduleItems ||
programSchedule.RandomStartPoint != request.RandomStartPoint;
programSchedule.Name = request.Name;
programSchedule.KeepMultiPartEpisodesTogether = request.KeepMultiPartEpisodesTogether;
programSchedule.TreatCollectionsAsShows = programSchedule.KeepMultiPartEpisodesTogether &&
request.TreatCollectionsAsShows;
programSchedule.ShuffleScheduleItems = request.ShuffleScheduleItems;
programSchedule.RandomStartPoint = request.RandomStartPoint;
await dbContext.SaveChangesAsync();

3
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -10,7 +10,8 @@ internal static class Mapper @@ -10,7 +10,8 @@ internal static class Mapper
programSchedule.Name,
programSchedule.KeepMultiPartEpisodesTogether,
programSchedule.TreatCollectionsAsShows,
programSchedule.ShuffleScheduleItems);
programSchedule.ShuffleScheduleItems,
programSchedule.RandomStartPoint);
internal static ProgramScheduleItemViewModel ProjectToViewModel(ProgramScheduleItem programScheduleItem) =>
programScheduleItem switch

3
ErsatzTV.Application/ProgramSchedules/ProgramScheduleViewModel.cs

@ -5,4 +5,5 @@ public record ProgramScheduleViewModel( @@ -5,4 +5,5 @@ public record ProgramScheduleViewModel(
string Name,
bool KeepMultiPartEpisodesTogether,
bool TreatCollectionsAsShows,
bool ShuffleScheduleItems);
bool ShuffleScheduleItems,
bool RandomStartPoint);

3
ErsatzTV.Application/ProgramSchedules/Queries/GetAllProgramSchedulesHandler.cs

@ -22,7 +22,8 @@ public class GetAllProgramSchedulesHandler : IRequestHandler<GetAllProgramSchedu @@ -22,7 +22,8 @@ public class GetAllProgramSchedulesHandler : IRequestHandler<GetAllProgramSchedu
ps.Name,
ps.KeepMultiPartEpisodesTogether,
ps.TreatCollectionsAsShows,
ps.ShuffleScheduleItems))
ps.ShuffleScheduleItems,
ps.RandomStartPoint))
.ToListAsync(cancellationToken);
}
}

1
ErsatzTV.Core/Domain/ProgramSchedule.cs

@ -7,6 +7,7 @@ public class ProgramSchedule @@ -7,6 +7,7 @@ public class ProgramSchedule
public bool KeepMultiPartEpisodesTogether { get; set; }
public bool TreatCollectionsAsShows { get; set; }
public bool ShuffleScheduleItems { get; set; }
public bool RandomStartPoint { get; set; }
public List<ProgramScheduleItem> Items { get; set; }
public List<Playout> Playouts { get; set; }
}

43
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -175,7 +175,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -175,7 +175,8 @@ public class PlayoutBuilder : IPlayoutBuilder
playout,
parameters.Start,
parameters.Finish,
parameters.CollectionMediaItems);
parameters.CollectionMediaItems,
false);
}
private async Task<Playout> ResetPlayout(Playout playout, PlayoutParameters parameters)
@ -194,7 +195,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -194,7 +195,8 @@ public class PlayoutBuilder : IPlayoutBuilder
playout,
parameters.Start,
parameters.Finish,
parameters.CollectionMediaItems);
parameters.CollectionMediaItems,
playout.ProgramSchedule.RandomStartPoint);
return playout;
}
@ -217,7 +219,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -217,7 +219,8 @@ public class PlayoutBuilder : IPlayoutBuilder
playout,
parameters.Start,
parameters.Finish,
parameters.CollectionMediaItems);
parameters.CollectionMediaItems,
false);
return playout;
}
@ -282,7 +285,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -282,7 +285,8 @@ public class PlayoutBuilder : IPlayoutBuilder
Playout playout,
DateTimeOffset playoutStart,
DateTimeOffset playoutFinish,
Map<CollectionKey, List<MediaItem>> collectionMediaItems)
Map<CollectionKey, List<MediaItem>> collectionMediaItems,
bool randomStartPoint)
{
DateTimeOffset trimBefore = playoutStart.AddHours(-4);
DateTimeOffset trimAfter = playoutFinish;
@ -301,7 +305,10 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -301,7 +305,10 @@ public class PlayoutBuilder : IPlayoutBuilder
while (finish < playoutFinish)
{
_logger.LogDebug("Building playout from {Start} to {Finish}", start, finish);
playout = await BuildPlayoutItems(playout, start, finish, collectionMediaItems, true);
playout = await BuildPlayoutItems(playout, start, finish, collectionMediaItems, true, randomStartPoint);
// only randomize once (at the start of the playout)
randomStartPoint = false;
start = playout.Anchor.NextStartOffset;
finish = finish.AddDays(1);
@ -316,7 +323,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -316,7 +323,8 @@ public class PlayoutBuilder : IPlayoutBuilder
start,
playoutFinish,
collectionMediaItems,
false);
false,
randomStartPoint);
}
// remove any items outside the desired range
@ -330,7 +338,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -330,7 +338,8 @@ public class PlayoutBuilder : IPlayoutBuilder
DateTimeOffset playoutStart,
DateTimeOffset playoutFinish,
Map<CollectionKey, List<MediaItem>> collectionMediaItems,
bool saveAnchorDate)
bool saveAnchorDate,
bool randomStartPoint)
{
var sortedScheduleItems = playout.ProgramSchedule.Items.OrderBy(i => i.Index).ToList();
CollectionEnumeratorState scheduleItemsEnumeratorState =
@ -348,7 +357,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -348,7 +357,7 @@ public class PlayoutBuilder : IPlayoutBuilder
PlaybackOrder playbackOrder = maybeScheduleItem
.Match(item => item.PlaybackOrder, () => PlaybackOrder.Shuffle);
IMediaCollectionEnumerator enumerator =
await GetMediaCollectionEnumerator(playout, collectionKey, mediaItems, playbackOrder);
await GetMediaCollectionEnumerator(playout, collectionKey, mediaItems, playbackOrder, randomStartPoint);
collectionEnumerators.Add(collectionKey, enumerator);
}
@ -643,7 +652,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -643,7 +652,8 @@ public class PlayoutBuilder : IPlayoutBuilder
Playout playout,
CollectionKey collectionKey,
List<MediaItem> mediaItems,
PlaybackOrder playbackOrder)
PlaybackOrder playbackOrder,
bool randomStartPoint)
{
Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors
.OrderByDescending(a => a.AnchorDate is null)
@ -681,9 +691,21 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -681,9 +691,21 @@ public class PlayoutBuilder : IPlayoutBuilder
}
}
// index shouldn't ever be greater than zero with randomStartPoint since anchors shouldn't exist, but
randomStartPoint = randomStartPoint && state.Index == 0;
switch (playbackOrder)
{
case PlaybackOrder.Chronological:
if (randomStartPoint)
{
state = new CollectionEnumeratorState
{
Seed = state.Seed,
Index = Random.Next(0, mediaItems.Count - 1)
};
}
return new ChronologicalMediaCollectionEnumerator(mediaItems, state);
case PlaybackOrder.Random:
return new RandomizedMediaCollectionEnumerator(mediaItems, state);
@ -694,7 +716,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -694,7 +716,8 @@ public class PlayoutBuilder : IPlayoutBuilder
case PlaybackOrder.ShuffleInOrder:
return new ShuffleInOrderCollectionEnumerator(
await GetCollectionItemsForShuffleInOrder(collectionKey),
state);
state,
playout.ProgramSchedule.RandomStartPoint);
default:
// TODO: handle this error case differently?
return new RandomizedMediaCollectionEnumerator(mediaItems, state);

22
ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs

@ -7,14 +7,17 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator @@ -7,14 +7,17 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
{
private readonly IList<CollectionWithItems> _collections;
private readonly int _mediaItemCount;
private readonly bool _randomStartPoint;
private Random _random;
private IList<MediaItem> _shuffled;
public ShuffleInOrderCollectionEnumerator(
IList<CollectionWithItems> collections,
CollectionEnumeratorState state)
CollectionEnumeratorState state,
bool randomStartPoint)
{
_collections = collections;
_randomStartPoint = randomStartPoint;
_mediaItemCount = collections.Sum(c => c.MediaItems.Count);
if (state.Index >= _mediaItemCount)
@ -87,7 +90,14 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator @@ -87,7 +90,14 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
var result = new List<MediaItem>();
for (var i = 0; i < filled[0].Items.Count; i++)
{
var batch = filled.Select(collection => collection.Items[i]).ToList();
var batch = new List<Option<MediaItem>>();
foreach (OrderedCollection collection in filled)
{
int index = (collection.Index + i) % collection.Items.Count;
batch.Add(collection.Items[index]);
}
foreach (Option<MediaItem> maybeItem in Shuffle(batch, random))
{
result.AddRange(maybeItem);
@ -144,7 +154,13 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator @@ -144,7 +154,13 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
ordered.AddRange(larger);
}
result.Add(new OrderedCollection { Index = 0, Items = ordered });
var index = 0;
if (_randomStartPoint)
{
index = random.Next(0, ordered.Count - 1);
}
result.Add(new OrderedCollection { Index = index, Items = ordered });
}
return result;

4291
ErsatzTV.Infrastructure/Migrations/20220513230433_Add_ProgramSchedule_RandomStartPoint.Designer.cs generated

File diff suppressed because it is too large Load Diff

26
ErsatzTV.Infrastructure/Migrations/20220513230433_Add_ProgramSchedule_RandomStartPoint.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_ProgramSchedule_RandomStartPoint : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "RandomStartPoint",
table: "ProgramSchedule",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "RandomStartPoint",
table: "ProgramSchedule");
}
}
}

3
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -1548,6 +1548,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -1548,6 +1548,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<bool>("RandomStartPoint")
.HasColumnType("INTEGER");
b.Property<bool>("ShuffleScheduleItems")
.HasColumnType("INTEGER");

6
ErsatzTV/Pages/ScheduleEditor.razor

@ -38,6 +38,11 @@ @@ -38,6 +38,11 @@
For="@(() => _model.ShuffleScheduleItems)"/>
</MudTooltip>
</MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Label="Random Start Point"
@bind-Checked="@_model.RandomStartPoint"
For="@(() => _model.RandomStartPoint)"/>
</MudElement>
</MudCardContent>
<MudCardActions>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
@ -78,6 +83,7 @@ @@ -78,6 +83,7 @@
_model.ShuffleScheduleItems = viewModel.ShuffleScheduleItems;
_model.KeepMultiPartEpisodesTogether = viewModel.KeepMultiPartEpisodesTogether;
_model.TreatCollectionsAsShows = viewModel.TreatCollectionsAsShows;
_model.RandomStartPoint = viewModel.RandomStartPoint;
},
() => _navigationManager.NavigateTo("404"));
}

5
ErsatzTV/ViewModels/ProgramScheduleEditViewModel.cs

@ -9,10 +9,11 @@ public class ProgramScheduleEditViewModel @@ -9,10 +9,11 @@ public class ProgramScheduleEditViewModel
public bool KeepMultiPartEpisodesTogether { get; set; }
public bool TreatCollectionsAsShows { get; set; }
public bool ShuffleScheduleItems { get; set; }
public bool RandomStartPoint { get; set; }
public UpdateProgramSchedule ToUpdate() =>
new(Id, Name, KeepMultiPartEpisodesTogether, TreatCollectionsAsShows, ShuffleScheduleItems);
new(Id, Name, KeepMultiPartEpisodesTogether, TreatCollectionsAsShows, ShuffleScheduleItems, RandomStartPoint);
public CreateProgramSchedule ToCreate() =>
new(Name, KeepMultiPartEpisodesTogether, TreatCollectionsAsShows, ShuffleScheduleItems);
new(Name, KeepMultiPartEpisodesTogether, TreatCollectionsAsShows, ShuffleScheduleItems, RandomStartPoint);
}

Loading…
Cancel
Save